content: import React from 'react';
  import {BaseMixin, ElementaryMixin} from './../common/common.js';
  import {Button, Glyphicon} from './../bricks/bricks.js';
  import TextInputMixin from './text-input-mixin.js';
  
  import './number.less';
  
  export default React.createClass({
  
    mixins: [
      BaseMixin,
      ElementaryMixin,
      TextInputMixin
    ],
  
    statics: {
      tagName: 'UU5.Forms.Number',
      classNames: {
        main: 'uu5-forms-number',
        buttonGroup: 'input-group-btn',
        inputGroup: 'input-group uu5-forms-text-group-with-feedback',
        inputFeedback: 'uu5-forms-text-input-feedback'
      },
      defaults: {
        minusIcon: 'uu-glyphicon-minus',
        plusIcon: 'uu-glyphicon-plus'
      },
      errors: {
        notNumber: 'Cannot set value \'%s\' (%s) as number.'
      }
    },
  
    propTypes: {
      value: React.PropTypes.oneOfType([
        React.PropTypes.number,
        React.PropTypes.string
      ]),
      step: React.PropTypes.number,
      min: React.PropTypes.number,
      max: React.PropTypes.number,
      decimals: React.PropTypes.number,
      decimalSeparator: React.PropTypes.string,
      isRounded: React.PropTypes.bool,
      nanMessage: React.PropTypes.string,
      lowerBoundMessage: React.PropTypes.string,
      upperBoundMessage: React.PropTypes.string
    },
  
    // Setting defaults
    getDefaultProps: function () {
      return {
        value: null,
        step: null,
        min: null,
        max: null,
        decimals: 0,
        decimalSeparator: null,
        isRounded: false,
        nanMessage: 'Please insert a number.',
        lowerBoundMessage: 'Please type in bigger number.',
        upperBoundMessage: 'Please type in smaller number.'
      };
    },
  
    getInitialState: function () {
      var decimals = this._validateNumber(this.props.decimals, 0);
      decimals === null && (decimals = this.getDefault().decimals);
  
      return {
        // stored in state only for preventing unnecessary repeated validations
        step: this._validateNumber(this.props.step, 0),
        min: this._validateNumber(this.props.min, decimals),
        max: this._validateNumber(this.props.max, decimals),
        decimals: decimals,
        decimalSeparator: this.props.decimalSeparator
      };
    },
  
    componentWillMount: function () {
      var newValue = '';
      if (this.props.value !== null) {
        newValue = this._checkValue(this.props.value ? this.props.value.toString() : '', this.getDecimals(), this.props.isRounded).toString();
      }
      this.setState({ value: newValue });
    },
  
    // Update
    componentWillReceiveProps: function (nextProps) {
      var newState = {};
  
      var decimals;
      if (nextProps.decimals === undefined) {
        decimals = this.getDecimals();
      } else {
        decimals = this._validateNumber(nextProps.decimals, 0);
        newState.decimals = decimals;
      }
  
      this.setState({
        step: nextProps.step !== null && this._validateNumber(nextProps.step, 0),
        min: nextProps.min !== null && this._validateNumber(nextProps.min, decimals),
        max: nextProps.max !== null && this._validateNumber(nextProps.max, decimals),
        decimals: decimals
      });
    },
  
    // Interface
    getMin: function () {
      return this.state.min;
    },
  
    getMax: function () {
      return this.state.max;
    },
  
    getStep: function () {
      return this.state.step;
    },
  
    getDecimals: function () {
      return this.state.decimals;
    },
  
    getDecimalSeparator: function () {
      return this.state && this.state.decimalSeparator;
    },
  
    // Overriding Functions
    getValue_: function () {
      return this._getValue(this.state.value);
    },
  
    // primary function, channeling all setState threads
    // we are expecting options object containing message and/or feedback props
    setValue_: function (value, setStateCallback, options) {
      if (options && typeof options === 'object' && Object.keys(options).length) {
        options.value = value === null ? '' : value.toString();
        this.setState(options, setStateCallback);
  
      } else {
        var requiredOptions = this._checkRequired(value);
  
        if (requiredOptions && typeof requiredOptions === 'object' && requiredOptions.feedback === 'error') {
          this.setError(requiredOptions.message, value, setStateCallback);
        } else {
          var newState = this._validateValue(value);
  
          if (newState) {
            if (newState.feedback) {
              this._setFeedback(newState.feedback, newState.message, newState.value, setStateCallback);
            } else {
              this.setState(newState, setStateCallback);
            }
  
          } else {
            newState = requiredOptions || { feedback: 'initial', message: '' };
            newState.value = value === null ?
              '' :
              this._checkValue(value.toString(), this.getDecimals(), this.props.isRounded).toString();
            if (newState.feedback) {
              this._setFeedback(newState.feedback, newState.message, newState.value, setStateCallback);
            } else {
              this.setState(newState, setStateCallback);
            }
          }
        }
      }
  
      return this;
    },
  
    onChange_: function (e) {
      var value = e.target.value.trim();
  
      var options = this._checkRequired(value);
  
      if (options && typeof options === 'object' && options.feedback === 'error') {
        this.setError(options.message, value);
      } else {
        var newState = this._validateValue(value);
  
        if (newState) {
          if (newState.feedback) {
            this._setFeedback(newState.feedback, newState.message, newState.value);
          } else {
            this.setState(newState);
          }
  
        } else if (typeof this.props.onChange === 'function' && !this._isNumberChar(value) && isFinite(value.replace(',', '.'))) {
          var params = { value: this._getValue(value), component: this, event: e, options: options };
  
          /*
           * @deprecated input since 03.10. Use component instead of this.
           */
          params.input = this;
  
          this.props.onChange(params);
  
        } else if (value === '') {
          this.setInitial('', value);
  
        } else if (value === '-') {
          if (this.getMin() !== null && this.getMin() >= 0) {
            this.setWarning(this.props.nanMessage, '');
          } else {
            this.setInitial('', value);
          }
  
        } else if (isFinite(value.replace(',', '.'))) {
          var newValue = this._checkValue(value, this.getDecimals(), this.props.isRounded);
          this.setInitial('', newValue.toString());
  
        } else {
          this.showError('notNumber', [value, typeof value]);
        }
      }
  
      return this;
    },
  
    validate_: function (value, init) {
      var result = null;
  
      value = value === null ? '' : value.toString();
      if (value.length > 0 && !this._isNumberChar(value)) {
        if (isFinite(value.replace(',', '.'))) {
          var newValue = this._validateNumber(+value, this.getDecimals());
  
          if ((this.state.max !== null) && (newValue > this.state.max)) {
            result = {
              value: newValue.toString(),
              message: this.props.upperBoundMessage,
              feedback: 'error'
            };
          } else if ((this.state.min !== null) && (newValue < this.state.min)) {
            result = {
              value: newValue.toString(),
              message: this.props.lowerBoundMessage,
              feedback: 'error'
            };
          }
        } else {
          result = {
            value: this.state.value,
            message: this.props.nanMessage,
            feedback: 'warning'
          };
        }
      }
  
      return result;
    },
  
    onBlur_: function (e) {
      var value = this.state.value;
  
      var onBlur;
      if (typeof this.props.onBlur === 'function') {
        onBlur = this.props.onBlur.bind(this, { value: this.getValue(), component: this, event: e });
      }
  
      if (this._isNumberChar(value)) {
        if (!this.getDecimals() && value.match(/[,.]$/)) {
          this.setState({ value: value.replace(/[,.]$/, '') }, onBlur);
        } else {
          this.setError(this.props.nanMessage, value, onBlur);
        }
      } else if (this.props.step) {
        var number = Math.round(this.getValue() / this.props.step) * this.props.step;
        var newValue = number.toString();
        this._isComma(this.state.value) && newValue.replace(".", ",");
        this.setState({ value: newValue }, onBlur);
      } else if (onBlur) {
        onBlur();
      }
      return this;
    },
  
    // Component Specific Helpers
    _getValue: function (value) {
      return value === '' || value === '-' || value === null ? null : +value;
    },
  
    _isComma: function (value) {
      return value.indexOf(',') > -1;
    },
  
    // value (number) => number
    _validateNumber: function (value, decimals, round) {
      var result = null;
  
      if (value !== null) {
        value = value.toString();
        var dotIndex = value.indexOf('.');
        var isFloat = dotIndex > 0;
        var number = value;
  
        if (isFloat && decimals) {
          if (round) {
            number = Number(value).toFixed(decimals).toString();
          } else {
            number = value.slice(0, dotIndex + 1 + decimals);
          }
        }
  
        number = number.slice(0, isFloat ? 17 : 16);
        result = Number(number);
      }
  
      return result;
    },
  
    _checkSeparator: function (newValue, isComma) {
      if (newValue !== null) {
        if (this.getDecimalSeparator()) {
          newValue = newValue.replace('.', this.getDecimalSeparator());
        } else if (isComma) {
          newValue = newValue.replace('.', ',');
        }
      }
  
      return newValue;
    },
  
    // value (String) => number
    _checkValue: function (value, decimals, round) {
      var result = '';
  
      var isComma = this._isComma(value);
      var newValue = value.replace(',', '.');
  
      if (newValue.match(/\.$/)) {
        if (decimals) {
          result = newValue;
        } else {
          result = newValue.replace(/\.$/, '');
        }
      } else if (isFinite(newValue)) {
        var number = this._validateNumber(+newValue, decimals, round);
        result = number.toString();
      }
  
      return this._checkSeparator(result, isComma);
    },
  
    _isNumberChar: function (value) {
      return !!value.match(/(^-$)|([,.]$)/) && !value.match(/[,.][,.]+/);
    },
  
    _addStep: function (e) {
      this._changeByStep(this.getStep(), e);
      return this;
    },
  
    _subtractStep: function (e) {
      this._changeByStep(-this.getStep(), e);
      return this;
    },
  
    _changeByStep: function (step, e) {
      var newValue = this.getValue() + step;
  
      if (typeof this.props.onChange === 'function') {
        var params = { value: newValue, component: this, event: e };
  
        /*
         * @deprecated input since 03.10. Use component instead of this.
         */
        params.input = this;
  
        this.props.onChange(params);
      } else {
        this.setValue(newValue, this._setInputFocus);
      }
      return this;
    },
  
    _setInputFocus: function () {
      this.getInput().focus();
      return this;
    },
  
    _getInputGroupAttrs: function () {
      var inputGroupAttrs = {};
      this.getStep() && (inputGroupAttrs.className = this.getClassName().inputGroup);
      return inputGroupAttrs;
    },
  
    _getButtonProps: function () {
      return {
        size: this.props.size,
        className: this.getClassName('button', 'UU5_Forms_InputMixin')
      };
    },
  
    _getPlusButtonProps: function () {
      var buttonProps = this._getButtonProps();
  
      if ((this.getMax() !== null && this.getValue() >= this.getMax()) || this.isDisabled()) {
        buttonProps.disabled = true;
      } else {
        buttonProps.onClick = this._addStep;
      }
  
      return buttonProps;
    },
  
    _getMinusButtonProps: function () {
      var buttonProps = this._getButtonProps();
  
      if ((this.getMin() !== null && this.getValue() <= this.getMin()) || this.isDisabled()) {
        buttonProps.disabled = true;
      } else {
        buttonProps.onClick = this._subtractStep;
      }
  
      return buttonProps;
    },
  
    _getButtonGroup: function () {
      var buttonGroup = null;
  
      if (this.getStep()) {
        buttonGroup = (
          <span className={this.getClassName().buttonGroup}>
            <span>
              <Button {...this._getMinusButtonProps()}>
                <Glyphicon glyphicon={this.getDefault().minusIcon} />
              </Button>
            </span>
            <span>
              <Button {...this._getPlusButtonProps()}>
                <Glyphicon glyphicon={this.getDefault().plusIcon} />
              </Button>
            </span>
          </span>
        );
      }
  
      return buttonGroup;
    },
  
    // Render
    render: function () {
      return (
        <div {...this.getTextInputMainAttrs()}>
          {this.getLabelChild()}
  
          <div {...this.getInputWrapperAttrs()}>
            <div {...this._getInputGroupAttrs()}>
              <div className={this.getClassName().inputFeedback}>
                <input {...this.getTextInputAttrs()} value={this.state.value} />
                {this.getGlyphiconChild()}
              </div>
              {this._getButtonGroup()}
            </div>
            {this.getMessageChild()}
          </div>
        </div>
      );
    }
  });