import React from 'react'
import PropTypes from 'prop-types'
import {findDOMNode} from 'react-dom'
import Component from '../utils/Component'
import classnames from 'classnames'
import {setPhPrefix, getClientHeight, getScrollTop, getDocumentHeight, preventDefault} from '../utils/Tool'
import Logger from '../utils/logger'

import Button from '../button'
import Icon from '../icon'

import '../style'
import 'phoenix-styles/less/modules/pullup.less'

/**
 * 加载更多组件<br/>
 * - 书写时PullUp组件在可加载列表的后面。
 * - 通过mode设置加载更多的模式,有点击按钮加载更多,以及滑到最底端自动加载,可选 [auto,button] 2种参数。
 * - 通过status设置当前状态,只需要在请求结束返回相应状态,包含请求成功返回2,请求成功并再没有数据返回4,请求失败返回3。
 * - 可通过tips设置按钮文字和状态提示语,默认['加载更多','','加载成功','加载失败','没有更多'],分别对应status的状态。
 * - 可通过phStyle设置按钮的样式,如果当前mode为auto设置无效。
 * - 可通过loadCallback设置点击按钮加载或滑到底部自动加载的回调函数,如果状态为4不执行。
 * - 如果当前列表存在自定义的滚动条,需要通过getTarget传递滚动的目标,且滚动元素的子元素必须只有一个。
 *
 * 主要属性和接口:
 * - mode:加载更多的模式,默认auto。
 * - status:当前状态:0加载更多, 1加载中, 2数据加载成功, 3数据加载失败, 4没有更多。
 * - tips:按钮文字和状态提示语,默认['加载更多','','加载成功','加载失败','没有更多']。
 * - phStyle:按钮的样式,默认'primary'。
 * - loadCallback:点击按钮加载或滑到底部自动加载的回调函数。
 * - getTarget: 如果当前列表存在自定义的滚动条,需要传递滚动的目标。
 * 
 * 示例:
 * ```code
 *  <div style={{height:'300px',overflow:'auto'}} ref={(list)=>this.list=list}> // 用到getTarget需要保证只有一个子元素,包裹住滚动的所有内容
 *      <div> 
 *          <List>...</List> // 可加载列表的位置
 *          <PullUp mode='button' status={this.state.status} 
 *              tips={['点击加载更多','加载中...','加载成功!','加载失败!','没有更多']} 
 *              phStyle='primary' 
 *              loadCallback={this.loadCallback.bind(this)}
 *              getTarget={()=>{return list;}} />
 *      </div>
 *  </div>
 * ```
 * 
 * @class PullUp
 * @module 操作类组件
 * @extends Component
 * @constructor
 * @since 2.0.0
 * @demo pullup|pullup.js {展示}
 * @show true
 * */
const MAX_HEIGHT = 800

export default class PullUp extends Component{
    static propTypes = {
        /**
         * 样式前缀
         * @property classPrefix
         * @type String
         * @default 'pullup'
         * */
        classPrefix: PropTypes.string,
         /**
         * 加载更多的模式,可选[auto,button], 默认auto
         * @property mode
         * @type String
         * @default 'auto'
         **/
        mode:PropTypes.string,
        /**
         * 加载状态:0初始状态, 1加载中, 2数据加载成功, 3数据加载失败, 4没有更多
         * @property status
         * @type Number
         * @default 0
         **/
        status: PropTypes.number,
        /**
         * 加载5个状态的文字描述,默认['加载更多','','加载成功','加载失败','没有更多']
         * @property tips
         * @type Array
         * @default ['加载更多','加载中','加载成功','加载失败','没有更多']
         **/
        tips: PropTypes.array,
        /**
         * 按钮颜色,默认primary
         * @property phStyle
         * @type Array
         * @default 'primary'
         **/
        phStyle: PropTypes.string,
        /**
         * 滑到底部自动加载的回调函数,用户在该函数内自定义请求
         * @method loadCallback
         * @type Function
         * @default null
         **/
        loadCallback: PropTypes.func,
        /**
         * 如果当前列表存在自定义的滚动条,需要传递滚动的目标
         * @method getTarget
         * @type Function
         * @default null
         * @return {object} 目标元素的ref
         **/
        getTarget: PropTypes.func
    }

    static defaultProps ={
        status: 4, // 0初始状态, 1加载中, 2加载成功, 3加载失败, 4没有更多
        mode: 'auto',
        phStyle: 'primary',
        tips: ['加载更多','','加载成功','加载失败','没有更多'],
        classPrefix:'pullup',
        hardware: true,
        classMapping : {}
    }

    constructor(props,context){ // 记得做数据没有触底的判断
        super(props,context)
        new Logger('PullUp')

        this.state = {
            status: props.status
        }

        if(props.mode=='button') return

        this.touchBottom = false
        this.distanceY = 0
    }

    scrollHandler(e){
        let {status} = this.state,
            {getTarget} = this.props,
            target = e.target
        
        if(getTarget){
            this.scrollTop = target.scrollTop
            this.bodyHeight = target.clientHeight
            this.documentHeight = target.children[0].offsetHeight
        }else{
            this.scrollTop = getScrollTop()
            this.bodyHeight = getClientHeight()
            this.documentHeight = getDocumentHeight()
        }
        // console.log('this.scrollTop', this.scrollTop)
        // console.log('this.bodyHeight', this.bodyHeight)
        // console.log('this.documentHeight', this.documentHeight)

        this.pullTop = this.documentHeight - this.pullUp.offsetHeight
        // if(!this.pullHeight) this.pullHeight = this.pullUp.offsetHeight

        if(this.scrollTop + this.bodyHeight >= this.pullTop){
            this.touchBottom = true
            
            if(status==3) return 
            this.loadCallback()
        }else{
            this.touchBottom = false
        }
    }

    componentWillReceiveProps(nextProps){
        if(nextProps.status !== this.state.status){
            this.setState({
                status: nextProps.status
            })
        }
    }

    componentDidMount(){
        let pullUpElem = findDOMNode(this.pullUp)

        this.scrollHandle = this.scrollHandler.bind(this)
        this.scrollElem = window

        this.dragElem = pullUpElem.parentNode
        this.prevElem = pullUpElem.previousElementSibling
        this.addClass(this.dragElem, 'animated')
        this.props.hardware && this.addClass(this.prevElem, 'hardware')

        this.dragEventHandle(this.dragElem)

        if(this.props.getTarget){
            setTimeout(()=>{
                this.scrollElem = this.props.getTarget()
                this.scrollElem.addEventListener('scroll', this.scrollHandle, false)
            },0);  
        }else{
            this.scrollElem.addEventListener('scroll', this.scrollHandle, false)
        }              
    }

    dragEventHandle(elem){
        this.touchStartHandle = this.touchStartHandle.bind(this)
        elem.addEventListener('touchstart', this.touchStartHandle, false)

        this.touchMoveHandle = this.touchMoveHandle.bind(this)
        elem.addEventListener('touchmove', this.touchMoveHandle, false)

        this.touchEndHandle = this.touchEndHandle.bind(this)
        elem.addEventListener('touchend', this.touchEndHandle, false)

        this.touchCancelHandle = this.touchCancelHandle.bind(this)
        elem.addEventListener('touchcancel', this.touchCancelHandle, false)
    }

    componentDidUpdate(){
        let {status} = this.state

        // 只有加载成功并传值才重置状态
        if(status==2){
            this.setState({
                status: 0
            })
        }
    }

    loadCallback(){
        this.touchBottom = false

        let {loadCallback} = this.props,
            {status} = this.state

        // 如果已经没有更多,不再继续请求数据的操作
        if(status==4 || status==1) return

        // 状态置为加载中(状态为0或3时执行)
        this.setState({
            status: 1
        }, ()=>{
            if(loadCallback) loadCallback()
        })
    }

    touchStartHandle(e){
        // preventDefault(e)
        if(!this.touchBottom) return
        
        this.distanceY = 0
        this.starY = event.touches[0].pageY
    }

    touchMoveHandle(e){
        // preventDefault(e)
        if(!this.touchBottom) return

        this.moveY = event.touches[0].pageY
        this.distanceY = this.moveY - this.starY

        if(this.distanceY>=0) return

        this.distanceY = Math.abs(this.distanceY)

        this.transform = Math.min(1, MAX_HEIGHT/this.distanceY) * Math.min(MAX_HEIGHT, this.distanceY)
        this.dragElem.style.transform = 'translateY('+(-this.transform)+'px)'   
    }

    touchEndHandle(e){
        // preventDefault(e)
        if(!this.touchBottom) return

        this.starY = this.moveY

        this.dragElem.style.transform = 'translateY(0)'
        
        if(Math.abs(this.distanceY) <= 80 || this.distanceY>=0) return

        this.loadCallback()
    }

    touchCancelHandle(){
        this.dragElem.style.transform = 'translateY(0)'
    }

    componentWillUnmount(){
        if(this.props.mode=='button') return

        this.scrollElem.removeEventListener('scroll', this.scrollHandle, false)

        this.dragElem.removeEventListener('touchstart', this.touchStartHandle, false)
        this.dragElem.removeEventListener('touchmove', this.touchMoveHandle, false)
        this.dragElem.removeEventListener('touchend', this.touchEndHandle, false)
    }

    renderContent(){
        let {mode, tips, phStyle} = this.props,
            {status} = this.state

        if(mode=='button'){
            return (
                <Button phStyle={status==3?'error':phStyle} disabled={status==4||status==1} 
                    onClick={this.loadCallback.bind(this)}>
                    {this.renderIcon(status)}
                    {tips[status]}
                </Button>
            )            
        }else{
            return (
                <div className={setPhPrefix('pullup-tip')}>
                    {this.renderIcon(status)}
                    {tips[status]}
                </div>
            )
        }        
    }

    renderIcon(status){
        if(status==1){
            return <Icon className='gfs-icon-loading' phIcon='loading-gray' phSize='sm' />;
        }    
    }

    renderPullUp(){
        return (
            <div {...this.otherProps} ref={(pullUp)=>{this.pullUp=pullUp}} className={classnames(this.getProperty(true),this.props.className)}>
                {this.renderContent()}
            </div>
        )
    }

    render(){
        return this.renderPullUp()
    }
}