/// <reference path="../../typings/index.d.ts" />
/// <reference path="./definition.d.ts" />

import * as React from 'react';
import classNames from 'classnames';
import Hammer from 'react-hammerjs';
import Slider from '../slider';
import { setTranslate } from '../util/animation';
import { deviceWidth, deviceHeight } from '../util/device';

const prefix = 'tsp-component-carousel';
class Carousel extends React.Component<TspComponentCarouselProps, TspComponentCarouselState> {
  constructor(props: TspComponentCarouselProps, state: TspComponentCarouselState) {
    super(props, state);

    this.onPanStart = this.onPanStart.bind(this);
    this.onPan = this.onPan.bind(this);
    this.onPanEnd = this.onPanEnd.bind(this);
    this.onTap = this.onTap.bind(this);
    this.onTransitionEnd = this.onTransitionEnd.bind(this);
  }

  public static defaultProps: TspComponentCarouselProps = {
    width: deviceWidth,
    height: deviceHeight,
    direction: 'horizontal',
    refreshId: 1,
    selectedIndex: 0,
    swipeable: true
  };

  /**
   * Slider的实例
   */
  private slider: Slider;
  /**
   * 滑动容器
   */
  private sliderElem: HTMLElement;
  /**
   * 主容器Element
   */
  private containerElem: HTMLElement;
  /**
   * dots Element
   */
  private dotsElem: HTMLElement;
  /**
   * 自动轮播定时器对象
   */
  private autoplayInterval: number;
  /** 是否是自动轮播触发 */
  private isAuto: boolean;

  public componentDidMount(): void {
    this.sliderElem = this.refs['slider'] as HTMLElement;
    this.containerElem = this.refs['container'] as HTMLElement;
    this.dotsElem = this.refs['dots'] as HTMLElement;
    this.slider = new Slider({
      sliderElem: this.sliderElem,
      containerElem: this.containerElem,
      count: React.Children.toArray(this.props.children).length,
      direction: this.props.direction,
      width: this.props.width,
      height: this.props.height,
      selectedIndex: this.props.selectedIndex
    });

    this.carouselInitial(this.props.selectedIndex);
    this.setContainerHeight();
    this.autoplay();
    this.sliderElem.addEventListener('webkitTransitionEnd', this.onTransitionEnd);
  }

  public componentWillUnmount(): void {
    this.clearAtuoplay();
    this.sliderElem.removeEventListener('webkitTransitionEnd', this.onTransitionEnd);
  }

  public componentWillReceiveProps(nextProps: TspComponentCarouselProps): void {
    const selectedIndex = this.props.selectedIndex;
    const nextSelectedIndex = nextProps.selectedIndex;
    const selectedIndexChange = selectedIndex !== nextSelectedIndex;
    const updateIdChange =  this.props.refreshId !== nextProps.refreshId;
    const changeSelectedIndexType = selectedIndexChange ? '' : 'auto';

    if (selectedIndexChange && !updateIdChange) {
      this.slider.count = React.Children.toArray(nextProps['children']).length;
      this.changeSelectedIndex(selectedIndex, nextSelectedIndex);
    } else if (nextProps.refreshId === nextSelectedIndex) {
      this.setCarouselSort(selectedIndex, nextSelectedIndex);
    }
  }

  /**
   * 开始滑动
   */
  public onPanStart(evt: TspComponentSliderEvt): void {
    evt.preventDefault();
    if (!this.props.swipeable) {
      return;
    }
    this.slider.panStart();
    this.clearAtuoplay();
  }

  /**
   * 滑动中
   */
  public onPan(evt: TspComponentSliderEvt): void {
    evt.preventDefault();
    if (!this.props.swipeable) {
      return;
    }
    this.slider.panMove(evt);
  }

  /**
   * 滑动结束
   */
  public onPanEnd(evt: TspComponentSliderEvt): void {
    evt.preventDefault();
    if (this.slider.isPanEnd) {
      return;
    }

    this.containerElem.classList.add('tsp-compoent-slider-disabled');
    this.slider.addPanEndClass();
    this.slider.panEnd(evt);
    this.setContainerHeight();
    this.autoplay();
  }

  public onTap(): void {
    if (this.props.onClick) {
      this.props.onClick(this.slider.selectedIndex);
    }
  }

  /**
   * 监听translate完成事件
   */
  public onTransitionEnd(): void {
    const { beforeSelectIndex, afterSelectedIndex } = this.slider.afterChangeIndexs;
    this.carouselInitial(afterSelectedIndex);

    if (this.props.dots) {
      this.dotsElem.children[beforeSelectIndex].classList.remove(`${prefix}-dots-item-current`);
      this.dotsElem.children[afterSelectedIndex].classList.add(`${prefix}-dots-item-current`);
    }

    if (this.props.afterChange) {
      this.props.afterChange(this.slider.afterChangeIndexs, this.isAuto);
    }

    this.isAuto = false;
    this.slider.removePanEndClass();
    this.containerElem.classList.remove('tsp-compoent-slider-disabled');
  }

  /**
   * 将待分配的children移动到排列的位置
   */
  public carouselInitial(selectedIndex: number): void {
    const position = this.slider.getMoveBeforePosition();

    this.setCarouselSort(selectedIndex);
    setTranslate(this.sliderElem, position.x, position.y);
  }

  /**
   * 移动后将容器高度设置为当前子元素的高度
   */
  public setContainerHeight(isReceive: boolean = false): void {
    if (this.props.autoHeight) {
      const index = isReceive ? 0 : 1;
      const sliderChildrenElem = this.slider.sliderElem.children[index];
      if (this.slider.sliderElem.style.height !== sliderChildrenElem.clientHeight + 'px') {
        this.slider.sliderElem.style.height = sliderChildrenElem.clientHeight + 'px';
      }
    }
  }

  /**
   * 设置要滑动内容的顺序
   */
  public setCarouselSort(selectedIndex: number, oldSelectedIndex?: number, isReceive: boolean = false): void {
    const children = this.refs['children']['cloneNode'](true).children;
    const childrenElemLength = children.length;
    const fragment = document.createDocumentFragment();
    let [prevElem, currentElem, nextElem] = [undefined, undefined, undefined];
    let i;

    for (i = 0; i < childrenElemLength; i++) {
      if (this.props.direction === 'horizontal') {
        children[i].style.width = this.props.width + 'px';
      } else {
        children[i].style.height = this.props.height + 'px';
      }
      children[i].classList.add(`${prefix}-slider-item`);
    }
    if (isReceive) {
      prevElem = children[selectedIndex];
      currentElem = children[oldSelectedIndex];
      nextElem = children[selectedIndex];
    } else {
      if (childrenElemLength > 1) {
        if (selectedIndex === 0) {
          prevElem = children[childrenElemLength - 1];
          nextElem = children[1];
        } else if (selectedIndex === childrenElemLength - 1) {
          prevElem = children[childrenElemLength - 2];
          nextElem = children[0];
        } else {
          prevElem = children[selectedIndex - 1];
          nextElem = children[selectedIndex + 1];
        }
      } else if (childrenElemLength === 1) {
        prevElem = children[0];
        nextElem = children[0];
      }
      currentElem = children[selectedIndex];
    }

    if (childrenElemLength >= 1) {
      fragment.appendChild(prevElem.cloneNode(true));
    }

    if (childrenElemLength === 1) {
      fragment.appendChild(currentElem.cloneNode(true));
    } else {
      fragment.appendChild(currentElem);
    }

    if (childrenElemLength >= 1) {
      fragment.appendChild(nextElem);
    }
    this.sliderElem.innerHTML = null;
    this.sliderElem.appendChild(fragment);
  }

  /**
   * 改变索引
   */
  public changeSelectedIndex(selectedIndex: number, nextSelectedIndex: number): void {
    this.setCarouselSort(nextSelectedIndex, selectedIndex, true);
    this.setContainerHeight(true);

    this.containerElem.classList.add('tsp-compoent-slider-disabled');
    setTimeout(() => {
      this.slider.onChangeSelectedIndex({
        deltaX: this.isAuto ? -1 : selectedIndex - nextSelectedIndex,
        deltaY: selectedIndex - nextSelectedIndex,
        preventDefault: () => null
      }, nextSelectedIndex);
    }, 0);
  }

  /**
   * 自动轮播
   */
  public autoplay(): void {
    if (this.props.autoplayInterval) {
      this.autoplayInterval = setInterval(() => {
        const selectedIndex = this.slider.selectedIndex;
        const nextSelectedIndex = selectedIndex + 1 >= this.slider.count ? 0 : selectedIndex + 1;

        this.isAuto = true;
        this.changeSelectedIndex(selectedIndex, nextSelectedIndex);
      }, this.props.autoplayInterval);
    }
  }

  /**
   * 清除自动轮播
   */
  public clearAtuoplay(): void {
    if (this.autoplayInterval !== undefined) {
      clearInterval(this.autoplayInterval);
    }
  }

  public render(): JSX.Element {
    const model = this.props.direction === 'horizontal' ? 'width' : 'height';
    const contentStyle = { [model]: this.props.width };
    const flexStyle = { [model]: this.props.width * 3 };

    return (
      <div
        className={classNames({
          [prefix]: true,
          [this.props.className]: this.props.className
        })}
        ref="container"
        id={this.props.id ? this.props.id : ''}
      >
        <Hammer
          className={`${prefix}-hammer`}
          onPanStart={this.onPanStart}
          onPan={this.onPan}
          onPanEnd={this.onPanEnd}
          onTap={this.onTap}
        >
          <div className={`${prefix}-content`} style={contentStyle}>
            <div className="clearfix" style={flexStyle} ref="slider" />
          </div>
        </Hammer>

        <div className={`${prefix}-children`} ref="children">{this.props.children}</div>

        {this.props.leftArrow ? (
          <div className={`${prefix}-left-arrow`}>{this.props.leftArrow}</div>
        ) : null}
        {this.props.rightArrow ? (
          <div className={`${prefix}-left-arrow`}>{this.props.leftArrow}</div>
        ) : null}

        {this.props.dots ? (
          <div className={`${prefix}-dots`} ref="dots">
            {React.Children.toArray(this.props.children).map((value, i) => (
              <div
                className={classNames({
                  [`${prefix}-dots-item`]: true,
                  [`${prefix}-dots-item-current`]: this.props.selectedIndex === i
                })}
                key={i}
              />
            ))}
          </div>
        ) : null}
      </div>
    );
  }
}

export default Carousel;