import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { polyfill } from 'react-lifecycles-compat';

import Icon from '../icon';
import Button from '../button';
import Input from '../input';
import ConfigProvider from '../config-provider';
import { func, obj } from '../util';

/** NumberPicker */
class NumberPicker extends React.Component {
  static propTypes = {
    /**
         * 样式前缀
         */
    prefix: PropTypes.string,
    /**
         * 设置类型
         * @enumdesc 普通, 内联
         */
    type: PropTypes.oneOf(['normal', 'inline']),
    /**
         * 大小
         */
    size: PropTypes.oneOf(['large', 'medium']),
    /**
         * 当前值
         */
    value: PropTypes.number,
    /**
         * 默认值
         */
    defaultValue: PropTypes.number,
    /**
         * 是否禁用
         */
    disabled: PropTypes.bool,
    /**
         * 步长
         */
    step: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    /**
         * 保留小数点后位数
         */
    precision: PropTypes.number,
    /**
         * 用户是否可以输入
         */
    editable: PropTypes.bool,
    /**
         * 自动焦点
         */
    autoFocus: PropTypes.bool,
    /**
         * 数值被改变的事件
         * @param {Number} value 数据
         * @param {Event} e DOM事件对象
         */
    onChange: PropTypes.func,
    /**
         * 键盘按下
         */
    onKeyDown: PropTypes.func,
    /**
         * 焦点获得
         */
    onFocus: PropTypes.func,
    /**
         * 焦点失去
         */
    onBlur: PropTypes.func,
    /**
         * 数值订正后的回调
         * @param {Object} obj {currentValue,oldValue:String}
         */
    onCorrect: PropTypes.func,
    onDisabled: PropTypes.func, // 兼容0.x onDisabled
    /**
         * 最大值
         */
    max: PropTypes.number,
    /**
         * 最小值
         */
    min: PropTypes.number,
    /**
         * 自定义class
         */
    className: PropTypes.string,
    /**
         * 自定义内联样式
         */
    style: PropTypes.object,
    state: PropTypes.oneOf(['error']),
    /**
         * 格式化当前值
         * @param {Number} value
         * @return {String|Number}
         */
    format: PropTypes.func,
    /**
         * 增加按钮的props
         */
    upBtnProps: PropTypes.object,
    /**
         * 减少按钮的props
         */
    downBtnProps: PropTypes.object,
    /**
         * 内联 label
         */
    label: PropTypes.node,
    /**
         * inner after
         */
    innerAfter: PropTypes.node,
    rtl: PropTypes.bool,
    /**
         * 是否为预览态
         */
    isPreview: PropTypes.bool,
    /**
         * 预览态模式下渲染的内容
         * @param {number} value 评分值
         */
    renderPreview: PropTypes.func,
    /**
         * 预设屏幕宽度
         */
    device: PropTypes.oneOf(['phone', 'tablet', 'desktop']),
  };

  static defaultProps = {
    prefix: 'next-',
    max: Infinity,
    min: -Infinity,
    type: 'normal',
    size: 'medium',
    step: 1,
    style: {},
    precision: 0,
    editable: true,
    onChange: func.noop,
    onKeyDown: func.noop,
    onBlur: func.noop,
    onCorrect: func.noop,
    onDisabled: func.noop,
  };

  constructor(props) {
    super(props);

    let value;
    if ('value' in props) {
      value = props.value;
    } else {
      value = props.defaultValue;
    }

    this.state = {
      value: value === undefined || value === null ? '' : value,
      hasFocused: false,
      reRender: true,
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (
      'value' in nextProps &&
            nextProps.value !== prevState.value &&
            prevState.reRender
    ) {
      const { value } = nextProps;
      return {
        value: value === undefined || value === null ? '' : value,
      };
    }

    return null;
  }

  onChange(value, e) {
    if (this.props.editable === true) {
      value = value.trim();
      // Compatible Chinese Input Method
      value = value.replace('。', '.');
      // ignore space
      if (this.state.value === value) {
        return;
      }

      // in case of autoCorrect ('0.'=>0, '0.0'=>0) , we have these steps
      if (value) {
        // ignore when input start form '-'
        if (value === '-' || this.state.value === '-') {
          this.setState({
            value,
            reRender: false,
          });
          return;
        }
        // ignore when input 0./0.0/0.00 to 0.001
        // but take care of Number('')=0;
        if (value.match(/\.0*$/)) {
          this.setState({
            value,
            reRender: false,
          });
          return;
        }
        // ignore when value < min (because number is inputted one by one)
        if (!isNaN(value) && Number(value) < this.props.min) {
          this.setState({
            value,
            reRender: false,
          });
          return;
        }
      }

      this.setInputValue(value, e);
    }
  }

  /**
     * @param {Float} currentValue correct value
     * @param {String} oldValue input value
     */
  onCorrect(currentValue, oldValue) {
    this.props.onCorrect({
      currentValue,
      oldValue,
    });
  }

  onKeyDown(e, ...args) {
    if (e.keyCode === 38) {
      this.up(false, e);
    } else if (e.keyCode === 40) {
      this.down(false, e);
    }
    this.props.onKeyDown(e, ...args);
  }

  onFocus(e, ...args) {
    const { onFocus } = this.props;
    this.setFocus(true);
    onFocus && onFocus(e, ...args);
  }

  onBlur(e, ...args) {
    const value = this.getCurrentValidValue(e.target.value.trim());
    if (this.state.value !== value) {
      this.setValue(value, e);
    }
    this.setFocus(false);
    const { onBlur } = this.props;
    onBlur && onBlur(e, ...args);
  }

  getCurrentValidValue(value) {
    let val = value;
    const { props } = this;
    if (val === '') {
      val = '';
    } else if (!isNaN(val)) {
      val = Number(val);
      if (val < props.min) {
        val = props.min;
      }
      if (val > props.max) {
        val = props.max;
      }

      // precision=2  and input from 1.99 to 1.999, should stay with 1.99 not 2
      const strValue = `${val}`;
      const pointPos = strValue.indexOf('.');
      const cutPos = pointPos + 1 + this.getPrecision();
      if (pointPos !== -1 && strValue.length > cutPos) {
        val = Number(strValue.substr(0, cutPos));
      }
    } else {
      val = this.state.value;
    }

    if (`${val}` !== `${value}`) {
      // under controled, set back to props.value
      if (
        'value' in this.props &&
                `${this.props.value}` !== `${this.state.value}`
      ) {
        this.setState({
          value: this.props.value,
        });
      }
      this.onCorrect(val, value);
    }

    return val;
  }

  setValue(v, e, triggerType) {
    if (!('value' in this.props)) {
      this.setState({
        value: v,
      });
    }

    this.setState({
      reRender: true,
    });

    this.props.onChange(isNaN(v) || v === '' ? undefined : v, {
      ...e,
      triggerType,
    });
  }

  setInputValue(v, e) {
    const value = this.getCurrentValidValue(v);
    if (this.state.value !== value) {
      this.setValue(value, e);
    }
  }

  setFocus(status) {
    const { format } = this.props;
    // Only trigger `setState` if `format` is settled to avoid unnecessary rendering
    if (typeof format === 'function') {
      this.setState({
        hasFocused: status,
      });
    }
  }

  getPrecision() {
    const { props } = this;
    const stepString = props.step.toString();
    if (stepString.indexOf('e-') >= 0) {
      return parseInt(stepString.slice(stepString.indexOf('e-')), 10);
    }
    let precision = 0;
    if (stepString.indexOf('.') >= 0) {
      precision = stepString.length - stepString.indexOf('.') - 1;
    }

    return Math.max(precision, this.props.precision);
  }

  getPrecisionFactor() {
    const precision = this.getPrecision();
    return Math.pow(10, precision);
  }

  upStep(val) {
    const { step, min } = this.props;
    const precisionFactor = this.getPrecisionFactor();
    let result;
    if (typeof val === 'number') {
      result =
                (precisionFactor * val + precisionFactor * step) /
                precisionFactor;

      result = this.hackChrome(result);
    } else {
      result = min === -Infinity ? step : min;
    }
    return result;
  }

  downStep(val) {
    const { step, min } = this.props;
    const precisionFactor = this.getPrecisionFactor();
    let result;
    if (typeof val === 'number') {
      result =
                (precisionFactor * val - precisionFactor * step) /
                precisionFactor;

      result = this.hackChrome(result);
    } else {
      result = min === -Infinity ? -step : min;
    }
    return result;
  }

  /**
     * fix bug in chrome browser
     * 0.28 + 0.01 = 0.29000000000000004
     * 0.29 - 0.01 = 0.27999999999999997
     * @param {Number} value value
     */
  hackChrome(value) {
    const precision = this.getPrecision();
    if (precision > 0) {
      return Number(Number(value).toFixed(precision));
    }
    return value;
  }

  step(type, disabled, e) {
    if (e) {
      e.preventDefault();
    }

    const { onDisabled, min, max } = this.props;
    if (disabled) {
      return onDisabled(e);
    }

    const { value } = this.state;
    if (isNaN(value)) {
      return;
    }

    let val = this[`${type}Step`](value);
    if (val > max) {
      val = max;
    }
    if (val < min) {
      val = min;
    }
    this.setValue(val, e, type);
  }

  down(disabled, e) {
    this.step('down', disabled, e);
  }

  up(disabled, e) {
    this.step('up', disabled, e);
  }

  renderValue() {
    const { value, hasFocused } = this.state;
    const { format } = this.props;
    return typeof format === 'function' && !hasFocused
      ? format(value)
      : value;
  }

  focus() {
    this.inputRef.getInstance().focus();
  }

  saveInputRef(ref) {
    this.inputRef = ref;
  }

  handleMouseDown(e) {
    e.preventDefault();
  }

  render() {
    const {
      device,
      prefix,
      rtl,
      disabled,
      style,
      className,
      size,
      max,
      min,
      autoFocus,
      editable,
      state,
      label,
      upBtnProps = {},
      downBtnProps = {},
      innerAfter,
      isPreview,
      renderPreview,
    } = this.props;

    const type = device === 'phone' ? 'inline' : this.props.type;

    const prefixCls = `${prefix}number-picker`;

    const cls = classNames({
      [prefixCls]: true,
      [`${prefixCls}-${type}`]: type,
      [`${prefix}${size}`]: true,
      [className]: className,
    });

    let upDisabled = false;
    let downDisabled = false;
    const { value } = this.state;
    if (!isNaN(value)) {
      const val = Number(value);
      if (val >= max) {
        upDisabled = true;
      }
      if (val <= min) {
        downDisabled = true;
      }
    }

    let extra = null;
    const innerAfterClassName = null;
    let addonBefore = null;
    let addonAfter = null;
    if (type === 'normal') {
      extra = (
        <span className={`${prefixCls}-handler`}>
          <Button
            {...upBtnProps}
            onMouseDown={this.handleMouseDown}
            disabled={disabled}
            className={`${upBtnProps.className || ''} ${
              upDisabled ? 'disabled' : ''
            }`}
            onClick={this.up.bind(this, upDisabled)}
          >
            <Icon type="arrow-up" />
          </Button>
          <Button
            {...downBtnProps}
            onMouseDown={this.handleMouseDown}
            disabled={disabled}
            className={`${downBtnProps.className || ''} ${
              downDisabled ? 'disabled' : ''
            }`}
            onClick={this.down.bind(this, downDisabled)}
          >
            <Icon type="arrow-down" />
          </Button>
        </span>
      );
    } else {
      addonBefore = (
        <Button
          {...downBtnProps}
          size={size}
          disabled={disabled}
          className={`${downBtnProps.className || ''} ${
            downDisabled ? 'disabled' : ''
          }`}
          onClick={this.down.bind(this, downDisabled)}
        >
          <Icon type="minus" />
        </Button>
      );
      addonAfter = (
        <Button
          {...upBtnProps}
          size={size}
          disabled={disabled}
          className={`${upBtnProps.className || ''} ${
            upDisabled ? 'disabled' : ''
          }`}
          onClick={this.up.bind(this, upDisabled)}
        >
          <Icon type="add" />
        </Button>
      );
    }

    const others = obj.pickOthers(NumberPicker.propTypes, this.props);
    const dataAttrs = obj.pickAttrsWith(this.props, 'data-');

    const previewCls = classNames({
      [`${prefix}form-preview`]: true,
      [className]: !!className,
    });

    if (isPreview) {
      if (typeof renderPreview === 'function') {
        return (
          <div {...others} style={style} className={previewCls}>
            {renderPreview(this.renderValue(), this.props)}
          </div>
        );
      }
      return (
        <p {...others} style={{ style }} className={previewCls}>
          {this.renderValue()}
        </p>
      );
    }

    return (
      <span
        className={cls}
        style={style}
        dir={rtl ? 'rtl' : undefined}
        {...dataAttrs}
      >
        <Input
          {...others}
          hasClear={false}
          aria-valuemax={max !== Infinity ? max : undefined}
          aria-valuemin={min !== -Infinity ? min : undefined}
          state={state === 'error' ? 'error' : null}
          onBlur={this.onBlur.bind(this)}
          onFocus={this.onFocus.bind(this)}
          onKeyDown={this.onKeyDown.bind(this)}
          autoFocus={autoFocus}
          readOnly={!editable}
          value={this.renderValue()}
          disabled={disabled}
          size={size}
          onChange={this.onChange.bind(this)}
          ref={this.saveInputRef.bind(this)}
          label={label}
          innerAfter={innerAfter}
          extra={extra}
          addonBefore={addonBefore}
          addonAfter={addonAfter}
        />
      </span>
    );
  }
}

export default polyfill(NumberPicker);
