import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import classnames from 'classnames';
import Queue from 'promise-queue';

/**
 * 路由切换组件<br/>
 * - 通过`transitionName`设置动画类型,可选`[fade, slide-top, slide-bottom, slide-left, slide-right]`。
 * - 通过`loadedCallback`函数设置动画完成的回调。
 * - 可通过`timeout`设置动画时间,和设置的css的动画时间一致是最流畅的。
 * - 在列表页的最外层元素加`ph-transition-index`类,其他页面跳转到列表页都是回退的效果,到另一个新页面都是前进的效果。
 *
 * 主要属性和接口:
 * - transitionName:动画类型/动画名称,默认fade。
 * - loadedCallback:动画完成的回调函数。
 * - timeout:动画时间。
 * 
 * 示例:
 * ```code
 *  import RouterTransition from 'ph-router-transition';
 * 
 *  const PageTransition = (props)=>(
 *      <RouterTransition {...props} transitionName="slide-left" loadedCallback={()=>{console.log('end!!!');}} timeout={500}>{props.children}</RouterTransition>
 *  );
 * ```
 * ```code
 *  let Index = class index extends Component {
 *      render() {
 *          return (
 *              <div className="menu ph-transition-index">
 *                  ...
 *              </div>
 *          );
 *      }
 *  };
 * ```
 * ```code
 *  <Router history={this.history}>
 *      <Route component={PageTransition}>
 *          <Route path="/index" name="index" component={Index} />
 *          <Route path="/detail" name="detail" component={Detail} />
 *          ...
 *          <Redirect from="/" to="/index" />
 *      </Route>
 *  </Router>
 * ```
 * 
 * @class RouterTransition
 * @module 路由动画
 * @extends Component
 * @constructor
 * @since 0.1.0
 * @demo index|index.js {展示}
 * @show true
 * */

export default class RouterTransition extends React.Component{

    static propTypes={
        /**
         * 动画名称,可选[fade, slide-top, slide-bottom, slide-left, slide-right]
         * @property transitionName
         * @type String
         * @default 'fade'
         * */
        transitionName: PropTypes.string,
        /**
         * 动画结束执行的回调
         * @method loadedCallback
         * @type Function
         * @default null
         * */
        loadedCallback: PropTypes.func,
        /**
         * 动画时间,和设置的css的动画时间一致是最流畅的
         * @property timeout
         * @type Number
         * @default 500
         * */
        timeout: PropTypes.number,
        animateOnInit: PropTypes.bool,
        data: PropTypes.object,
    };

    static defaultProps = {
        timeout: 500,
        transitionName: 'fade',
        animateOnInit: true,
        classMapping : {}
    };

    constructor(props,context){
        super(props,context);

        if(this.props.animateOnInit){
            this.state = {
                child1: null,
                child2: null,
                nextChild: 1
            }
        }else{
            this.state = {
                child1: this.props.children,
                child2: null, 
                nextChild: 2
            }
        }

        this.transite = this.transite.bind(this);
        this.gerRef = this.getRef.bind(this);

        this.queue = new Queue(1, Infinity);

        this.itemClass = 'ph-transition-item';
        
        this.routeRecord = [props.location.pathname];
        this.forward = true;
    }

    componentDidMount(){
        let {animateOnInit, data, children} = this.props;

        if(!animateOnInit){
            const child = this.getRef('child1');
            if(child){
                const dom = ReactDOM.findDOMNode(child);
                child.onTransitionDidEnd && child.onTransitionDidEnd(data);
                dom.classList.remove(this.itemClass);
            }
        }else{
            this.transite(children);
        }
    }

    componentWillReceiveProps(nextProps){
        // 判断当前是往前还是后退
        this.forward = this.routeForward(nextProps.location.pathname);

        const transitNewChild = () => {
            this.queue.add(()=> this.transite(nextProps.children));
        };
        const updateChild = () => {
            const currentChild = this.state.nextChild === 1? 2:1;
            this.state[`child${currentChild}`] = nextProps.children;
            this.forceUpdate();
        }

        if(this.props.children && this.props.children.props && this.props.children.props['data-transition-id'] 
            && nextProps.children.props['data-transition-id']) {

            if (this.props.children.props['data-transition-id'] !== nextProps.children.props['data-transition-id']) {
                transitNewChild();
            } else {
                updateChild();
            }
        } else {
            if (this.props.children !== nextProps.children) {
                transitNewChild();
            } else {
                updateChild();
            }
        }
    }

    routeForward(nextPathName){
        let routeLen = this.routeRecord.length;

        if(routeLen>1 && this.routeRecord[routeLen-2]===nextPathName){// back
            this.routeRecord.pop();
            return false;
        }else{
            this.routeRecord.push(nextPathName);
            return true;
        }
    }

    getClass(mode){
        if(mode&&this.forward || !mode&&!this.forward){
            return 'ph-transition-from';
        }else{
            return 'ph-transition-to';
        }
    }

    getRef(ref){
        let child = this.refs[ref];
        if(child && child.getWrappedInstance){
            child = child.getWrappedInstance();
        }
        return child;
    }

    transite(nextChild){
        return new Promise((transiteDone, transiteFailed)=>{
            this.state[`child${this.state.nextChild}`] = nextChild;
            this.forceUpdate(() => {
                const prevChild = this.getRef(`child${this.state.nextChild === 1 ? 2 : 1}`);
                const newChild = this.getRef(`child${this.state.nextChild}`);
                const prevChildDom = ReactDOM.findDOMNode(prevChild);
                const newChildDom = ReactDOM.findDOMNode(newChild);
                let timeout = 0;
                
                const willStart = () => {
                    if (newChild.onTransitionWillStart) {
                        return newChild.onTransitionWillStart(this.props.data) || Promise.resolve();
                    }
                    if (prevChild && prevChild.onTransitionLeaveWillStart) {
                        return prevChild.onTransitionLeaveWillStart(this.props.data) || Promise.resolve();
                    }
                    return Promise.resolve();
                };

                const start = () => {
                    //强制设置列表页
                    if(newChildDom.classList.contains('ph-transition-index')){
                        this.forward = false;
                    }else if(prevChildDom && prevChildDom.classList.contains('ph-transition-index')){
                        this.forward = true;
                    }

                    if (newChildDom) {
                        timeout = this.props.timeout;
                        newChildDom.classList.add(this.props.transitionName + '-enter');
                        if(prevChildDom) newChildDom.classList.add(this.itemClass);
                        newChildDom.classList.add(this.getClass(true));

                        newChildDom.offsetHeight; // Trigger layout to make sure transition happen
                        
                        if (newChild.transitionManuallyStart) {
                            return newChild.transitionManuallyStart(this.props.data, start) || Promise.resolve();
                        }
                        newChildDom.classList.add(this.props.transitionName + '-enter-active');
                    }

                    if (prevChildDom) {
                        prevChildDom.classList.add(this.props.transitionName + '-leave');
                        prevChildDom.classList.add(this.itemClass);
                        prevChildDom.classList.add(this.getClass(false));
                        
                        timeout = this.props.timeout;
                        prevChildDom.offsetHeight; // Trigger layout to make sure transition happen

                        if (prevChild.transitionLeaveManuallyStart) {
                            return prevChild.transitionLeaveManuallyStart(this.props.data, start) || Promise.resolve();
                        }
                        prevChildDom.classList.add(this.props.transitionName + '-leave-active');
                    }
                    return Promise.resolve();
                };

                const didStart = () => {
                    if (newChild.onTransitionDidStart) {
                        return newChild.onTransitionDidStart(this.props.data) || Promise.resolve();
                    }
                    if (prevChild && prevChild.onTransitionDidStartLeave) {
                        return prevChild.onTransitionLeaveDidStart(this.props.data) || Promise.resolve();
                    }
                    return Promise.resolve();
                };

                // Wait for transition
                const waitForTransition = () => new Promise(resolve => {
                    setTimeout(() => {
                        // Swap child and remove the old child
                        this.state.nextChild = this.state.nextChild === 1 ? 2 : 1;
                        this.state[`child${this.state.nextChild}`] = null;
                        this.forceUpdate(resolve);
                    }, timeout);
                });

                // Before remove classes
                const willEnd = () => {
                    if (newChild.onTransitionWillEnd) {
                        return newChild.onTransitionWillEnd(this.props.data) || Promise.resolve();
                    }
                    if (prevChild && prevChild.onTransitionLeaveWillEnd) {
                        return prevChild.onTransitionLeaveWillEnd(this.props.data) || Promise.resolve();
                    }
                    return Promise.resolve();
                };

                // Remove appear and active class (or trigger manual end)
                const end = () => {
                    if (newChildDom) {
                        newChildDom.classList.remove(this.props.transitionName + '-enter');
                        newChildDom.classList.remove(this.getClass(true));
                        newChildDom.classList.remove(this.itemClass);

                        if (newChild.transitionManuallyStop) {
                            return newChild.transitionManuallyStop(this.props.data) || Promise.resolve();
                        }
                        newChildDom.classList.remove(this.props.transitionName + '-enter-active');
                    }

                    if (prevChildDom) {
                        prevChildDom.classList.remove(this.props.transitionName + '-leave');
                        prevChildDom.classList.remove(this.getClass(false));
                        prevChildDom.classList.remove(this.itemClass);

                        if (prevChild.transitionLeaveManuallyStop) {
                            return prevChild.transitionLeaveManuallyStop(this.props.data) || Promise.resolve();
                        }
                        prevChildDom.classList.remove(this.props.transitionName + '-leave-active');
                    }
                    return Promise.resolve();
                };

                // After remove classes
                const didEnd = () => {
                    if (newChild.onTransitionDidEnd) {
                        return newChild.onTransitionDidEnd(this.props.data) || Promise.resolve();
                    }
                    if (prevChild && prevChild.onTransitionLeaveDidEnd) {
                        return prevChild.onTransitionLeaveDidEnd(this.props.data) || Promise.resolve();
                    }
                    return Promise.resolve();
                };

                Promise.resolve()
                    .then(willStart)
                    .then(start)
                    .then(didStart)
                    .then(waitForTransition)
                    .then(willEnd)
                    .then(end)
                    .then(didEnd)
                    .then(()=>{
                        this.props.loadedCallback && this.props.loadedCallback();
                        transiteDone();
                    })
                    .catch(transiteFailed);
            });
        });
    }

    render(){
        return (
            <div {...this.props} className={classnames('ph-transition-wrapper', this.props.className)} >
                {
                    React.Children.map(this.state.child1, element => React.cloneElement(element, {ref: 'child1'}))
                }
                {
                    React.Children.map(this.state.child2, element => React.cloneElement(element, {ref: 'child2'}))
                }
            </div>
        );
    }
}