require('nativescript-dom');
import * as app from 'application';
import * as Platform from 'platform';
import * as utils from 'tns-core-modules/utils/utils';
import { AbsoluteLayout } from 'ui/layouts/absolute-layout';
import { StackLayout } from 'ui/layouts/stack-layout';
import { View } from 'ui/core/view';
import { Button } from 'ui/button';
import { Label } from 'ui/label';
import * as AnimationModule from 'ui/animation';
import * as gestures from 'ui/gestures';
import { AnimationCurve, Orientation } from 'ui/enums';
import { Color } from 'color';
import { Image } from 'ui/image';

declare const android: any;
declare const com: any;
declare const java: any;
const SLIDE_INDICATOR_INACTIVE = 'slide-indicator-inactive';
const SLIDE_INDICATOR_ACTIVE = 'slide-indicator-active';
const SLIDE_INDICATOR_WRAP = 'slide-indicator-wrap';
let LayoutParams: any;
if (app.android) {
  LayoutParams = <any>android.view.WindowManager.LayoutParams;
} else {
  LayoutParams = {};
}

export class Slide extends StackLayout {}

enum direction {
  none,
  left,
  right
}

enum cancellationReason {
  user,
  noPrevSlides,
  noMoreSlides
}

export interface ISlideMap {
  panel: StackLayout;
  index: number;
  left?: ISlideMap;
  right?: ISlideMap;
}

export class SlideContainer extends AbsoluteLayout {
  private currentPanel: ISlideMap;
  private transitioning: boolean = false;
  private direction: direction = direction.none;
  private _loaded: boolean;
  private _pageWidth: number;
  private _loop: boolean;
  private _pagerOffset: string;
  private _angular: boolean;
  private _disablePan: boolean;
  private _footer: StackLayout;
  private _pageIndicators: boolean;
  private _slideMap: ISlideMap[];
  private _slideWidth: string;

  public static startEvent = 'start';
  public static changedEvent = 'changed';
  public static cancelledEvent = 'cancelled';
  public static finishedEvent = 'finished';

  /* page indicator stuff*/
  get pageIndicators(): boolean {
    return this._pageIndicators;
  }
  set pageIndicators(value: boolean) {
    if (typeof value === 'string') {
      value = <any>value == 'true';
    }
    this._pageIndicators = value;
  }

  get pagerOffset(): string {
    return this._pagerOffset;
  }
  set pagerOffset(value: string) {
    this._pagerOffset = value;
  }

  get hasNext(): boolean {
    return !!this.currentPanel && !!this.currentPanel.right;
  }
  get hasPrevious(): boolean {
    return !!this.currentPanel && !!this.currentPanel.left;
  }

  get loop() {
    return this._loop;
  }

  set loop(value: boolean) {
    this._loop = value;
  }

  get disablePan() {
    return this._disablePan;
  }

  set disablePan(value: boolean) {
    if (this._disablePan === value) {
      return;
    } // Value did not change

    this._disablePan = value;
    if (this._loaded && this.currentPanel.panel !== undefined) {
      if (value === true) {
        this.currentPanel.panel.off('pan');
      } else if (value === false) {
        this.applySwipe(this.pageWidth);
      }
    }
  }

  get pageWidth() {
    if (!this.slideWidth) {
      return Platform.screen.mainScreen.widthDIPs;
    }
    return +this.slideWidth;
  }

  get angular(): boolean {
    return this._angular;
  }

  set angular(value: boolean) {
    this._angular = value;
  }

  get currentIndex(): number {
    return this.currentPanel.index;
  }

  get slideWidth(): string {
    return this._slideWidth;
  }
  set slideWidth(width: string) {
    this._slideWidth = width;
  }

  constructor() {
    super();
    this.setupDefaultValues();
    // if being used in an ng2 app we want to prevent it from excuting the constructView
    // until it is called manually in ngAfterViewInit.

    this.constructView(true);
  }

  private setupDefaultValues(): void {
    this.clipToBounds = true;

    this._loaded = false;
    if (this._loop == null) {
      this.loop = false;
    }

    this.transitioning = false;

    if (this._disablePan == null) {
      this.disablePan = false;
    }

    if (this._angular == null) {
      this.angular = false;
    }

    if (this._pageIndicators == null) {
      this._pageIndicators = false;
    }

    if (this._pagerOffset == null) {
      this._pagerOffset = '88%'; //defaults to white.
    }
  }

  public constructView(constructor: boolean = false): void {
    this.on(AbsoluteLayout.loadedEvent, (data: any) => {
      //// console.log('LOADDED EVENT');
      if (!this._loaded) {
        this._loaded = true;
        if (this.angular === true && constructor === true) {
          return;
        }

        let slides: StackLayout[] = [];

        if (!this.slideWidth) {
          this.slideWidth = <any>this.pageWidth;
        }
        this.width = +this.slideWidth;

        this.eachLayoutChild((view: View) => {
          if (view instanceof StackLayout) {
            AbsoluteLayout.setLeft(view, this.pageWidth);
            view.width = this.pageWidth;
            (<any>view).height = '100%'; //get around compiler
            slides.push(view);
          }
        });

        if (this.pageIndicators) {
          this._footer = this.buildFooter(slides.length, 0);
          this.setActivePageIndicator(0);
          this.insertChild(this._footer, this.getChildrenCount());
        }

        this.currentPanel = this.buildSlideMap(slides);
        if (this.currentPanel) {
          this.positionPanels(this.currentPanel);

          if (this.disablePan === false) {
            this.applySwipe(this.pageWidth);
          }
          if (app.ios) {
            this.ios.clipsToBound = true;
          }
          //handles application orientation change
          app.on(
            app.orientationChangedEvent,
            (args: app.OrientationChangedEventData) => {
              //event and page orientation didn't seem to alwasy be on the same page so setting it in the time out addresses this.
              setTimeout(() => {
                // console.log('orientationChangedEvent');
                this.width = parseInt(this.slideWidth);
                this.eachLayoutChild((view: View) => {
                  if (view instanceof StackLayout) {
                    AbsoluteLayout.setLeft(view, this.pageWidth);
                    view.width = this.pageWidth;
                  }
                });

                if (this.disablePan === false) {
                  this.applySwipe(this.pageWidth);
                }

                if (this.pageIndicators) {
                  AbsoluteLayout.setTop(this._footer, 0);
                  var pageIndicatorsLeftOffset = this.pageWidth / 4;
                  AbsoluteLayout.setLeft(
                    this._footer,
                    pageIndicatorsLeftOffset
                  );
                  this._footer.width = this.pageWidth / 2;
                  this._footer.marginTop = <any>this._pagerOffset;
                }

                this.positionPanels(this.currentPanel);
              }, 0);
            }
          );
        }
      }
    });
  }

  public nextSlide(): void {
    if (!this.hasNext) {
      this.triggerCancelEvent(cancellationReason.noMoreSlides);
      return;
    }

    this.direction = direction.left;
    this.transitioning = true;
    this.triggerStartEvent();
    this.showRightSlide(this.currentPanel).then(() => {
      this.setupPanel(this.currentPanel.right);
      this.triggerChangeEventRightToLeft();
    });
  }
  public previousSlide(): void {
    if (!this.hasPrevious) {
      this.triggerCancelEvent(cancellationReason.noPrevSlides);
      return;
    }

    this.direction = direction.right;
    this.transitioning = true;
    this.triggerStartEvent();
    this.showLeftSlide(this.currentPanel).then(() => {
      this.setupPanel(this.currentPanel.left);
      this.triggerChangeEventLeftToRight();
    });
  }

  private setupPanel(panel: ISlideMap) {
    this.direction = direction.none;
    this.transitioning = false;
    this.currentPanel.panel.off('pan');
    this.currentPanel = panel;

    // sets up each panel so that they are positioned to transition either way.
    this.positionPanels(this.currentPanel);

    if (this.disablePan === false) {
      this.applySwipe(this.pageWidth);
    }

    if (this.pageIndicators) {
      this.setActivePageIndicator(this.currentPanel.index);
    }
  }

  private positionPanels(panel: ISlideMap) {
    // sets up each panel so that they are positioned to transition either way.
    if (panel.left != null) {
      panel.left.panel.translateX = -this.pageWidth * 2;
    }
    panel.panel.translateX = -this.pageWidth;
    if (panel.right != null) {
      panel.right.panel.translateX = 0;
    }
  }

  public goToSlide(index: number): void {
    if (
      this._slideMap &&
      this._slideMap.length > 0 &&
      index < this._slideMap.length
    ) {
      let previousSlide = this.currentPanel;

      this.setupPanel(this._slideMap[index]);

      this.notify({
        eventName: SlideContainer.changedEvent,
        object: this,
        eventData: {
          direction: direction.none,
          newIndex: this.currentPanel.index,
          oldIndex: previousSlide.index
        }
      });
    } else {
      // console.log('invalid index');
    }
  }

  public applySwipe(pageWidth: number): void {
    let previousDelta = -1; //hack to get around ios firing pan event after release
    let endingVelocity = 0;
    let startTime, deltaTime;

    this.currentPanel.panel.on(
      'pan',
      (args: gestures.PanGestureEventData): void => {
        if (args.state === gestures.GestureStateTypes.began) {
          startTime = Date.now();
          previousDelta = 0;
          endingVelocity = 250;

          this.triggerStartEvent();
        } else if (args.state === gestures.GestureStateTypes.ended) {
          deltaTime = Date.now() - startTime;
          // if velocityScrolling is enabled then calculate the velocitty

          // swiping left to right.
          if (args.deltaX > pageWidth / 3) {
            if (this.hasPrevious) {
              this.transitioning = true;
              this.showLeftSlide(
                this.currentPanel,
                args.deltaX,
                endingVelocity
              ).then(() => {
                this.setupPanel(this.currentPanel.left);

                this.triggerChangeEventLeftToRight();
              });
            } else {
              //We're at the start
              //Notify no more slides
              this.triggerCancelEvent(cancellationReason.noPrevSlides);
            }
            return;
          }
          // swiping right to left
          else if (args.deltaX < -pageWidth / 3) {
            if (this.hasNext) {
              this.transitioning = true;
              this.showRightSlide(
                this.currentPanel,
                args.deltaX,
                endingVelocity
              ).then(() => {
                this.setupPanel(this.currentPanel.right);

                // Notify changed
                this.triggerChangeEventRightToLeft();

                if (!this.hasNext) {
                  // Notify finsihed
                  this.notify({
                    eventName: SlideContainer.finishedEvent,
                    object: this
                  });
                }
              });
            } else {
              // We're at the end
              // Notify no more slides
              this.triggerCancelEvent(cancellationReason.noMoreSlides);
            }
            return;
          }

          if (this.transitioning === false) {
            //Notify cancelled
            this.triggerCancelEvent(cancellationReason.user);
            this.transitioning = true;
            this.currentPanel.panel.animate({
              translate: { x: -this.pageWidth, y: 0 },
              duration: 200,
              curve: AnimationCurve.easeOut
            });
            if (this.hasNext) {
              this.currentPanel.right.panel.animate({
                translate: { x: 0, y: 0 },
                duration: 200,
                curve: AnimationCurve.easeOut
              });
              if (app.ios)
                //for some reason i have to set these in ios or there is some sort of bounce back.
                this.currentPanel.right.panel.translateX = 0;
            }
            if (this.hasPrevious) {
              this.currentPanel.left.panel.animate({
                translate: { x: -this.pageWidth * 2, y: 0 },
                duration: 200,
                curve: AnimationCurve.easeOut
              });
              if (app.ios)
                this.currentPanel.left.panel.translateX = -this.pageWidth;
            }
            if (app.ios) this.currentPanel.panel.translateX = -this.pageWidth;

            this.transitioning = false;
          }
        } else {
          if (
            !this.transitioning &&
            previousDelta !== args.deltaX &&
            args.deltaX != null &&
            args.deltaX < 0
          ) {
            if (this.hasNext) {
              this.direction = direction.left;
              this.currentPanel.panel.translateX = args.deltaX - this.pageWidth;
              this.currentPanel.right.panel.translateX = args.deltaX;
            }
          } else if (
            !this.transitioning &&
            previousDelta !== args.deltaX &&
            args.deltaX != null &&
            args.deltaX > 0
          ) {
            if (this.hasPrevious) {
              this.direction = direction.right;
              this.currentPanel.panel.translateX = args.deltaX - this.pageWidth;
              this.currentPanel.left.panel.translateX =
                -(this.pageWidth * 2) + args.deltaX;
            }
          }

          if (args.deltaX !== 0) {
            previousDelta = args.deltaX;
          }
        }
      }
    );
  }

  private showRightSlide(
    panelMap: ISlideMap,
    offset: number = this.pageWidth,
    endingVelocity: number = 32
  ): AnimationModule.AnimationPromise {
    let animationDuration: number;
    animationDuration = 300; // default value

    let transition = new Array();

    transition.push({
      target: panelMap.right.panel,
      translate: { x: -this.pageWidth, y: 0 },
      duration: animationDuration,
      curve: AnimationCurve.easeOut
    });
    transition.push({
      target: panelMap.panel,
      translate: { x: -this.pageWidth * 2, y: 0 },
      duration: animationDuration,
      curve: AnimationCurve.easeOut
    });
    let animationSet = new AnimationModule.Animation(transition, false);

    return animationSet.play();
  }

  private showLeftSlide(
    panelMap: ISlideMap,
    offset: number = this.pageWidth,
    endingVelocity: number = 32
  ): AnimationModule.AnimationPromise {
    let animationDuration: number;
    animationDuration = 300; // default value
    let transition = new Array();

    transition.push({
      target: panelMap.left.panel,
      translate: { x: -this.pageWidth, y: 0 },
      duration: animationDuration,
      curve: AnimationCurve.easeOut
    });
    transition.push({
      target: panelMap.panel,
      translate: { x: 0, y: 0 },
      duration: animationDuration,
      curve: AnimationCurve.easeOut
    });
    let animationSet = new AnimationModule.Animation(transition, false);

    return animationSet.play();
  }

  private buildFooter(
    pageCount: number = 5,
    activeIndex: number = 0
  ): StackLayout {
    let footerInnerWrap = new StackLayout();

    //footerInnerWrap.height = 50;
    if (app.ios) {
      footerInnerWrap.clipToBounds = false;
    }
    footerInnerWrap.className = SLIDE_INDICATOR_WRAP;

    AbsoluteLayout.setTop(footerInnerWrap, 0);

    footerInnerWrap.orientation = 'horizontal';
    footerInnerWrap.horizontalAlignment = 'center';
    footerInnerWrap.width = this.pageWidth / 2;

    let index = 0;
    while (index < pageCount) {
      footerInnerWrap.addChild(this.createIndicator(index));
      index++;
    }

    let pageIndicatorsLeftOffset = this.pageWidth / 4;
    AbsoluteLayout.setLeft(footerInnerWrap, pageIndicatorsLeftOffset);
    footerInnerWrap.marginTop = <any>this._pagerOffset;

    return footerInnerWrap;
  }

  private setwidthPercent(view: View, percentage: number) {
    (<any>view).width = percentage + '%';
  }

  private newFooterButton(name: string): Button {
    let button = new Button();
    button.id = 'btn-info-' + name.toLowerCase();
    button.text = name;
    this.setwidthPercent(button, 100);
    return button;
  }

  private buildSlideMap(views: StackLayout[]) {
    this._slideMap = [];
    views.forEach((view: StackLayout, index: number) => {
      this._slideMap.push({
        panel: view,
        index: index
      });
    });
    this._slideMap.forEach((mapping: ISlideMap, index: number) => {
      if (this._slideMap[index - 1] != null)
        mapping.left = this._slideMap[index - 1];
      if (this._slideMap[index + 1] != null)
        mapping.right = this._slideMap[index + 1];
    });

    if (this.loop === true) {
      this._slideMap[0].left = this._slideMap[this._slideMap.length - 1];
      this._slideMap[this._slideMap.length - 1].right = this._slideMap[0];
    }
    return this._slideMap[0];
  }

  private triggerStartEvent() {
    this.notify({
      eventName: SlideContainer.startEvent,
      object: this,
      eventData: {
        currentIndex: this.currentPanel.index
      }
    });
  }

  private triggerChangeEventLeftToRight() {
    this.notify({
      eventName: SlideContainer.changedEvent,
      object: this,
      eventData: {
        direction: direction.left,
        newIndex: this.currentPanel.index,
        oldIndex: this.currentPanel.index + 1
      }
    });
  }

  private triggerChangeEventRightToLeft() {
    this.notify({
      eventName: SlideContainer.changedEvent,
      object: this,
      eventData: {
        direction: direction.right,
        newIndex: this.currentPanel.index,
        oldIndex: this.currentPanel.index - 1
      }
    });
  }

  private triggerCancelEvent(cancelReason: cancellationReason) {
    this.notify({
      eventName: SlideContainer.cancelledEvent,
      object: this,
      eventData: {
        currentIndex: this.currentPanel.index,
        reason: cancelReason
      }
    });
  }

  createIndicator(index: number): Label {
    let indicator = new Label();

    (<any>indicator).classList.add(SLIDE_INDICATOR_INACTIVE);
    return indicator;
  }

  setActivePageIndicator(index: number) {
    let indicatorsToDeactivate = (<any>this._footer).getElementsByClassName(
      SLIDE_INDICATOR_ACTIVE
    );

    indicatorsToDeactivate.forEach(activeIndicator => {
      activeIndicator.classList.remove(SLIDE_INDICATOR_ACTIVE);
      activeIndicator.classList.add(SLIDE_INDICATOR_INACTIVE);
    });

    let activeIndicator = (<any>this._footer).getElementsByClassName(
      SLIDE_INDICATOR_INACTIVE
    )[index];
    if (activeIndicator) {
      activeIndicator.classList.remove(SLIDE_INDICATOR_INACTIVE);
      activeIndicator.classList.add(SLIDE_INDICATOR_ACTIVE);
    }
  }

  iosProperty(theClass, theProperty) {
    if (typeof theProperty === 'function') {
      // xCode 7 and below
      return theProperty.call(theClass);
    } else {
      // xCode 8+
      return theProperty;
    }
  }
}
