/**
 * imui.Datepicker
 * @author riverhan
 * @data 2016-8-10
 *
 * @todo unit test
 * @todo 注释
 */
import React from 'react';
import ReactDom from 'react-dom';
import PropTypes from 'prop-types';
import Align from 'rc-align';
import classnames from 'classnames';
import DateInput from './DateInput';
import Calendar from './Calendar';
import {
  getPureDate,
  isSameDate,
  getDayOfWeek,
  getMaxDate,
  getMinDate,
  MODE,
  getMode,
  isRangeMode,
  isWaitForSecondDay,
  compareDate,
} from './dateUtils';
// @require '../style/index.scss'
const renderSubtreeIntoContainer = ReactDom.unstable_renderSubtreeIntoContainer;


class Datepicker extends React.Component {
  static propTypes = {
    /**
     * 是否inline展示
     */
    inline: PropTypes.bool, // 不依附Input标签，直接展示inline的calendar
    /**
     * 当前选中的日期
     */
    value: PropTypes.instanceOf(Date),
    /**
     * 当前选中的周中的一天
     */
    selectedWeek: PropTypes.instanceOf(Date),
    /**
     * 当前选中的日期范围
     */
    selectedRange: PropTypes.shape({
      from: PropTypes.instanceOf(Date),
      to: PropTypes.instanceOf(Date),
    }),
    /**
     * 显示范围日期的连接字符串
     */
    rangeJoin: PropTypes.string,
    /**
     * 日期选择器打开时显示的日期所在月分
     */
    initMonth: PropTypes.instanceOf(Date),
    /**
     * 是否初始展示选择器
     */
    showPicker: PropTypes.bool,
    /**
     * 选择器自定义className
     */
    className: PropTypes.string,
    /**
     * 选择器允许选择的最小日期
     */
    minDate: PropTypes.instanceOf(Date),
    /**
     * 选择器允许选择的最大日期
     */
    maxDate: PropTypes.instanceOf(Date),
    /**
     * 输入框的placeholder
     */
    placeholder: PropTypes.string,
    /**
     * 日历的z-index，仅在非inline模式下有效
     */
    calendarZindex: PropTypes.number,
    /**
     * 是否在输入框中显示日历icon，仅在非inline模式下有效
     */
    showIcon: PropTypes.bool,
    /**
     * 是否在输入框中显示清空按钮，仅在非inline模式下有效
     */
    showClean: PropTypes.bool,
    /**
     * 是否禁用datepicker操作
     */
    disabled: PropTypes.bool,
    /**
     * 是否启用周选模式
    */
    weekMode: PropTypes.bool,
    /**
     * 日期模式
     */
    mode: PropTypes.string,
    /**
     * input展示框的宽度（包括padding和border）
     */
    inputWidth: PropTypes.number,
    /**
     * 输入框的输出格式：'YYYY-MM-DD'
     */
    dateFormat: PropTypes.string,
    /**
     * 高亮的日期列表
     */
    highLightDates: PropTypes.array,
    /**
     * 是否仅高亮的日期可以选中
     */
    onlyEnableHighLightDate: PropTypes.bool,
    /**
     * 目标日期改变时的回调函数
     */
    onChange: PropTypes.func,
    /**
     * 月份切换时的回调函数
     */
    onChangeMonth: PropTypes.func,
  };

  static defaultProps = {
    showPicker: false,
    value: undefined,
    onChange: () => { },
    onChangeMonth: () => { },
    dateFormat: 'YYYY-M-D',
    showIcon: true,
    showClean: false,
    disabled: false,
    weekMode: false,
    mode: MODE.date,
    rangeJoin: ' 至 ',
    inline: false,
    highLightDates: [],
    onlyEnableHighLightDate: false,
  };

  constructor(props) {
    super(props);

    this.handleSelect = this.handleSelect.bind(this);
    this.handleSecondDateHover = this.handleSecondDateHover.bind(this);
    this.handleInputClick = this.handleInputClick.bind(this);
    this.handleClickOutPicker = this.handleClickOutPicker.bind(this);
    this.handleClean = this.handleClean.bind(this);
    this.handleScroll = this.handleScroll.bind(this);
    // 兼容weekMode配置，转化成mode统一识别&处理
    this.mode = getMode(props.mode, props.weekMode);

    this.changeState(this.props, true);
  }

  changeState(props, isInit = false) {
    const { selectedWeek, selectedRange, maxDate, minDate, value } = props;
    let selectedRangeState;
    let stateTarget = {};

    if (selectedWeek) {
      selectedRangeState = {
        from: getMaxDate(getDayOfWeek(selectedWeek, 0), minDate),
        to: getMinDate(getDayOfWeek(selectedWeek, 6), maxDate),
      };
    } else if (selectedRange) {
      selectedRangeState = {
        from: getMaxDate(selectedRange.from, minDate),
        to: getMinDate(selectedRange.to, maxDate),
      };
    } else {
      selectedRangeState = null;
    }

    if (isInit) {
      stateTarget = {
        selected: value && new Date(value),
        showPicker: this.props.showPicker,
        selectedRange: selectedRangeState,
      };
      this.state = stateTarget;
    } else {
      stateTarget = {
        showPicker: this.props.showPicker,
      };

      if (value !== undefined) {
        stateTarget.selected = getPureDate(value);
      }

      if (selectedRangeState) {
        stateTarget.selectedRange = selectedRangeState;
      }

      this.setState(stateTarget);
    }
  }

  handleScroll() {
    if (!this.props.inline) {
      if (this.state.showPicker) {
        this.setState({
          showPicker: false,
        });
      }
    }
  }

  handleSelect(date) {
    const { disabled, minDate, maxDate, onChange } = this.props;
    if (disabled) {
      return;
    }

    const { mode } = this;
    const selected = getPureDate(date);
    const selectedRange = this.state.selectedRange || null;
    const lastSelectedRange = this.state.lastSelectedRange || null;

    switch (mode) {
      // 选择单个时间时的回调
      case MODE.date:
        if (!isSameDate(selected, this.state.selected)) { // 选择的日期有变动
          onChange(selected, getPureDate(this.state.selected));
        }

        this.setState({
          showPicker: false,
          selected,
        });
        break;
      // 选取单周时间范围（周日到周六）
      case MODE.week: // eslint-disable-line no-case-declarations
        const rangeNew = {
          from: getMaxDate(getDayOfWeek(date, 0), minDate),
          to: getMinDate(getDayOfWeek(date, 6), maxDate),
        };

        if (selectedRange === null ||
          !isSameDate(rangeNew.from, selectedRange.from)) {
          onChange(rangeNew, selectedRange);
        }

        this.setState({
          showPicker: false,
          selectedRange: rangeNew,
        });
        break;
      // 选取自定义时间范围
      case MODE.range: // eslint-disable-line no-case-declarations
        // 第一/二次选取的日期
        let first = this.state.selected || selected;
        let second = this.state.selected ? selected : null;

        // 选择时间范围的第一个日期
        if (first && !second) {
          this.setState({
            showPicker: true,
            selected: first,
            lastSelectedRange: this.state.selectedRange,
            maySelectedRange: {
              from: first,
              to: first,
            },
            selectedRange: null,
          });
        } else {
          if (compareDate(first, second) > 0) {
            const tmp = second;
            second = first;
            first = tmp;
          }

          if (lastSelectedRange === null
            || !isSameDate(first, lastSelectedRange.from)
            || !isSameDate(second, lastSelectedRange.to)) {
            onChange({ from: first, to: second }, lastSelectedRange);
          }

          this.setState({
            showPicker: false,
            selected: undefined, // 清除日期选取记录
            maySelectedRange: null,
            selectedRange: {
              from: first,
              to: second,
            },
          });
        }
        break;
      default:
    }
  }

  handleSecondDateHover(date) {
    const mode = this.mode;
    const first = this.state.selected;
    const range = this.state.selectedRange;

    // 时间区间选取模式下，只选了第一个日期，第二个日期还没选
    if (isWaitForSecondDay(mode, first, range)) {
      this.setState({
        maySelectedRange: {
          from: compareDate(date, first) > 0 ? first : date,
          to: compareDate(date, first) > 0 ? date : first,
        },
      });
    }
  }

  handleInputClick() {
    if (this.props.disabled) {
      return;
    }

    this.setState({
      showPicker: !this.state.showPicker,
    });
  }

  handleClean(event) {
    if (this.props.disabled) {
      return;
    }

    this.setState({
      selected: undefined,
      selectedRange: null,
      lastSelectedRange: null,
      maySelectedRange: null,
    });

    if (this.state.selected) {
      this.props.onChange(undefined, getPureDate(this.state.selected));
    }
    event.stopPropagation();
  }

  handleClickOutPicker() {
    this.setState({
      showPicker: false,
    });
  }

  componentDidMount() {
    const body = document.body;
    const container = this.container = document.createElement('div');

    body.appendChild(container);
    this.renderCalendarToBody();

    window.addEventListener('scroll', this.handleScroll);
    window.addEventListener('resize', this.handleScroll);
  }

  componentWillReceiveProps(nextProps) {
    this.changeState(nextProps);
  }

  componentDidUpdate() {
    this.renderCalendarToBody();
  }

  componentWillUnmount() {
    if (this.container) {
      ReactDom.unmountComponentAtNode(this.container);
    }

    window.removeEventListener('scroll', this.handleScroll);
    window.removeEventListener('resize', this.handleScroll);
  }

  renderCalendarToBody() {
    const { inline } = this.props;

    if (!inline) { // 非inline模式下，需要将calendar渲染到body下，防止z-index问题
      renderSubtreeIntoContainer(this,
        <Align
          align={{
            points: ['tl', 'bl'],
            overflow: {
              adjustX: true,
              adjustY: true,
            },
          }}
          target={() => this.refs.dpInput.refs.inputWrap}
          monitorWindowResize
        >
          <Calendar
            inline={inline}
            zIndex={this.props.calendarZindex}
            selected={this.state.selected}
            day={this.props.initMonth}
            onSelect={this.handleSelect}
            onDayHover={this.handleSecondDateHover}
            show={this.state.showPicker}
            handleClickOutPicker={this.handleClickOutPicker}
            disableOnClickOutside={!this.state.showPicker || inline}
            handleChangeMonth={this.props.onChangeMonth}
            eventTypes="click"
            minDate={this.props.minDate}
            maxDate={this.props.maxDate}
            weekMode={this.props.weekMode}
            mode={this.mode}
            selectedRange={this.state.selectedRange}
            maySelectedRange={this.state.maySelectedRange}
            highLightDates={this.props.highLightDates}
            onlyEnableHighLightDate={this.props.onlyEnableHighLightDate}
          />
        </Align>, this.container
      );
    }
  }

  render() {
    const { mode, props } = this;
    const { inline } = props;

    return (
      <div className={classnames('im-datepicker', this.props.className)}>
        {inline
          ? <Calendar
            inline={inline}
            selected={this.state.selected}
            day={this.props.initMonth}
            onSelect={this.handleSelect}
            onDayHover={this.handleSecondDateHover}
            show={this.state.showPicker}
            handleClickOutPicker={this.handleClickOutPicker}
            disableOnClickOutside={!this.state.showPicker || inline}
            handleChangeMonth={this.props.onChangeMonth}
            eventTypes="click"
            minDate={this.props.minDate}
            maxDate={this.props.maxDate}
            weekMode={this.props.weekMode}
            mode={mode}
            selectedRange={this.state.selectedRange}
            maySelectedRange={this.state.maySelectedRange}
            highLightDates={this.props.highLightDates}
            onlyEnableHighLightDate={this.props.onlyEnableHighLightDate}
          />
          : <DateInput
            ref="dpInput"
            disabled={this.props.disabled}
            showClean={this.props.showClean}
            showIcon={this.props.showIcon}
            selected={this.state.selected}
            onInputClick={this.handleInputClick}
            placeholder={this.props.placeholder}
            dateFormat={this.props.dateFormat}
            handleClean={this.handleClean}
            rangeMode={isRangeMode(mode)}
            mode={mode}
            selectedRange={this.state.selectedRange}
            rangeJoin={this.props.rangeJoin}
            inputWidth={this.props.inputWidth}
          />}
      </div>
    );
  }
}

export default Datepicker;
