'use strict';

/** @jsx createElement */
import { Component, createElement, findDOMNode, PropTypes } from 'rax';
import { isWeex, isWeb } from 'nuke-env';
import View from 'nuke-view';
import RefreshControl from 'nuke-refresh-control';
import styles from './styles';

const SCROLLVIEW_CLS = 'rax-scrollview';

const LOADMORE_THRESHOLD = 500;
const ON_SCROLL_THROTTLE = 50;
const FULL_WIDTH = 750;

/**
 * ScrollView
 * @description  可滚动容器
 */
class ScrollView extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loadmoreretry: 0,
    };
    this.timer = null;
    this.childrenAll = {};
    this.scrollStartFlag = false;
    this.lastScrollEventTriggerTime = 0;
    this.lastScrollContentSize = 0;
    this.lastScrollDistance = 0;

    this.handleScroll = this.handleScroll.bind(this);
    this.scrollTo = this.scrollTo.bind(this);
    this.scrollToElement = this.scrollToElement.bind(this);
    this.resetLoadmore = this.resetLoadmore.bind(this);
    this.checkScrolling = this.checkScrolling.bind(this);
  }
  checkScrolling() {
    const now = new Date().getTime();
    if (this.lastScrollEventTriggerTime > 0 && now - this.lastScrollEventTriggerTime > 200) {
      this.scrollStartFlag = false;
      clearTimeout(this.timer);
      this.timer = null;
      this.props.onScrollEnd && this.props.onScrollEnd();
    }
  }
  handleScroll(e) {
    const { onScroll, onScrollStart, horizontal, onEndReached, onEndReachedThreshold } = this.props;
    if (isWeb) {
      if (!this.scrollStartFlag) {
        // first time trigger onScroll
        this.scrollStartFlag = true;
        onScrollStart && onScrollStart(e);
      }
      clearTimeout(this.timer);

      this.lastScrollEventTriggerTime = new Date().getTime();

      if (onScroll) {
        e.nativeEvent = {
          contentOffset: {
            x: e.target.scrollLeft,
            y: e.target.scrollTop,
          },
        };
        onScroll && onScroll(e);
      }
      this.timer = setTimeout(this.checkScrolling, 200);
      if (onEndReached) {
        if (!this.scrollerNode) {
          this.scrollerNode = findDOMNode(this.refs.scroller);
          this.scrollerContentNode = findDOMNode(this.refs.contentContainer);

          this.scrollerNodeSize = horizontal ? this.scrollerNode.offsetWidth : this.scrollerNode.offsetHeight;
        }

        // NOTE：in iOS7/8 offsetHeight/Width is is inaccurate （ use scrollHeight/Width ）
        const scrollContentSize = horizontal ? this.scrollerNode.scrollWidth : this.scrollerNode.scrollHeight;
        const scrollDistance = horizontal ? this.scrollerNode.scrollLeft : this.scrollerNode.scrollTop;
        const isEndReached = scrollContentSize - scrollDistance - this.scrollerNodeSize < onEndReachedThreshold;

        const isScrollToEnd = scrollDistance > this.lastScrollDistance;
        const isLoadedMoreContent = scrollContentSize !== this.lastScrollContentSize;

        if (isEndReached && isScrollToEnd && isLoadedMoreContent) {
          this.lastScrollContentSize = scrollContentSize;
          onEndReached(e);
        }

        this.lastScrollDistance = scrollDistance;
      }
    }
    if (isWeex) {
      e.nativeEvent = {
        contentOffset: {
          // HACK: weex scroll event value is opposite of web
          x: -e.contentOffset.x,
          y: -e.contentOffset.y,
        },
      };
      onScroll && onScroll(e);
    }
  }
  /**
   * 兼容旧的 resetScroll
   */
  resetLoadmore() {
    if (isWeb) {
      this.lastScrollContentSize = 0;
      this.lastScrollDistance = 0;
    } else {
      this.refs.scroller.resetLoadmore();
    }
  }

  scrollTo(options = {}) {
    const { horizontal } = this.props;
    const { x = 0, y = 0, animated = true, offset = 0 } = options;
    const offsetResult = parseInt(horizontal ? x : y || offset, 10);
    if (isWeex) {
      const dom = require('@weex-module/dom');
      const contentContainer = findDOMNode(this.refs.contentContainer);
      dom.scrollToElement(contentContainer.ref, {
        offset: offsetResult,
        animated,
      });
    } else {
      const pixelRatio = document.documentElement.clientWidth / FULL_WIDTH;

      if (horizontal) {
        findDOMNode(this.refs.scroller).scrollLeft = pixelRatio * offsetResult;
      } else {
        findDOMNode(this.refs.scroller).scrollTop = pixelRatio * offsetResult;
      }
    }
  }
  scrollToElement(ref, options = {}) {
    const { horizontal } = this.props;
    const { offset = 0, animated = true } = options;
    if (isWeex) {
      const dom = require('@weex-module/dom');
      // const contentContainer = findDOMNode(this.refs.contentContainer);
      dom.scrollToElement(findDOMNode(ref), {
        offset,
        animated,
      });
    } else {
      const pixelRatio = document.documentElement.clientWidth / FULL_WIDTH;

      // if (offset >= 0) {
      const refDOM = findDOMNode(ref);
      if (refDOM) {
        if (horizontal) {
          findDOMNode(this.refs.scroller).scrollLeft = refDOM.offsetLeft + pixelRatio * offset;
        } else {
          findDOMNode(this.refs.scroller).scrollTop = refDOM.offsetTop + pixelRatio * offset;
        }
        // }
      }
    }
  }

  splitChildren() {
    let { children } = this.props;
    if (!children) {
      this.childrenAll = {
        contents: null,
        refreshContent: null,
      };
      return;
    }

    const contents = [];
    let refreshContent;
    if (!Array.isArray(children)) {
      children = [children];
    }
    children.forEach((child) => {
      if (!child) return;
      if (child.type && child.type.displayName === RefreshControl.displayName) {
        refreshContent = child;
      } else {
        contents.push(child);
      }
    });
    this.childrenAll = {
      contents,
      refreshContent,
    };
  }
  hideWebScrollBar() {
    const styleNode = document.createElement('style');
    styleNode.id = 'rax-scrollview-style';
    document.head.appendChild(styleNode);
    styleNode.innerHTML = `.${SCROLLVIEW_CLS}::-webkit-scrollbar{display: none;}`;
  }

  render() {
    const {
      style,
      scrollEventThrottle,
      showsHorizontalScrollIndicator,
      showsVerticalScrollIndicator,
      onEndReached,
      onScrollStart,
      onScrollEnd,
      // loadmoreretry,
      onScroll,
      children,
      horizontal,
      onEndReachedThreshold,
      ...others
    } = this.props;
    let { showScrollBar } = this.props;
    if (typeof showScrollBar === 'undefined') {
      showScrollBar = horizontal ? showsHorizontalScrollIndicator : showsVerticalScrollIndicator;
    }

    const contentContainerStyle = [horizontal && styles.contentContainerHorizontal, this.props.contentContainerStyle];

    // bugfix: fix scrollview flex in ios 78
    if (!isWeex && !horizontal) {
      contentContainerStyle.push(styles.containerWebStyle);
    }

    this.splitChildren();
    const contentChild = this.childrenAll.contents;
    const refreshContent = this.childrenAll.refreshContent;

    const contentContainer = (
      <View ref="contentContainer" style={contentContainerStyle}>
        {contentChild}
      </View>
    );

    const baseStyle = horizontal ? styles.horizontal : styles.vertical;

    const scrollerStyle = Object.assign({}, baseStyle, isWeb ? styles.scrollerWeb : {}, this.props.style);

    if (isWeex) {
      const nativeProps = {
        ref: 'scroller',
        style: scrollerStyle,
        showScrollbar: showScrollBar,
        onLoadmore: onEndReached,
        onScrollStart,
        onScrollEnd,
        onScroll: onScroll ? this.handleScroll : null,
        loadmoreoffset: parseInt(onEndReachedThreshold, 10),
        loadmoreretry: true,
        offsetAccuracy: 20,
        scrollDirection: horizontal ? 'horizontal' : 'vertical',
        ...others,
      };
      return (
        <scroller {...nativeProps}>
          {refreshContent}
          {contentContainer}
        </scroller>
      );
    }
    /**
     * trigger times throttle
     */
    let handleScroll = this.handleScroll;
    if (scrollEventThrottle) {
      handleScroll = throttle(handleScroll, scrollEventThrottle);
    }
    const webProps = {
      ref: 'scroller',
      style: scrollerStyle,
      onScroll: handleScroll,
      id: 'scroller_rv',

      ...others,
    };
    if (!showScrollBar) {
      this.hideWebScrollBar();
    }
    return (
      <View {...webProps} className={SCROLLVIEW_CLS}>
        {refreshContent ? (
          <RefreshControl
            {...refreshContent.props}
            listId={webProps.id}
            refreshingTime={100}
            refreshing={refreshContent.props.refreshing}
          />
        ) : null}
        {contentContainer}
      </View>
    );
  }
}

function throttle(func, wait) {
  let ctx;
  let args;
  let rtn;
  let timeoutID;
  let last = 0;

  function call() {
    timeoutID = 0;
    last = +new Date();
    rtn = func.apply(ctx, args);
    ctx = null;
    args = null;
  }

  return function throttled() {
    ctx = this;
    args = arguments;
    const delta = new Date() - last;
    if (!timeoutID) {
      if (delta >= wait) call();
      else timeoutID = setTimeout(call, wait - delta);
    }
    return rtn;
  };
}

ScrollView.propTypes = {
  /**
   * ScrollView 样式 style of ScrollView
   */
  style: PropTypes.any,
  /**
   * 滚动时回调 callback when scrolling
   */
  onScroll: PropTypes.func,
  /**
   * 滚动开始时回调 callback when scrolling
   */
  onScrollStart: PropTypes.func,
  /**
   * 滚动结束时回调 callback when scrolling
   */
  onScrollEnd: PropTypes.func,
  /**
   * 加载到底部时回调 callback when scrolling to bottom
   */
  onEndReached: PropTypes.func,
  /**
   * 是否横向 is horizontal
   */
  horizontal: PropTypes.boolean,
  /**
   * 触发加载到底部回调的位移 offset of scrolling to bottom event been triggered
   */
  onEndReachedThreshold: PropTypes.number,
  /**
   * 是否展示横向滚动条  is show horizontal scrollbar
   */
  showsHorizontalScrollIndicator: PropTypes.boolean,
  /**
   * 是否展示纵向滚动条  is show vertical scrollbar
   */
  showsVerticalScrollIndicator: PropTypes.boolean,
  /**
   * 内容容器样式 the style of content's wrap container
   */
  contentContainerStyle: PropTypes.any,
  /**
   * onScroll 事件触发节流时间 the time inteval of next onScroll event being triggered
   */
  scrollEventThrottle: PropTypes.number,
  children: PropTypes.any,
};
ScrollView.defaultProps = {
  style: {},
  contentContainerStyle: {},
  horizontal: false,
  onScroll: null,
  onScrollStart: null,
  onScrollEnd: null,
  onEndReached: () => {},
  scrollEventThrottle: ON_SCROLL_THROTTLE,
  onEndReachedThreshold: LOADMORE_THRESHOLD,
  showsHorizontalScrollIndicator: true,
  showsVerticalScrollIndicator: true,
};
export default ScrollView;
