import PropTypes from 'prop-types';
import React from 'react';
import utils from './utils';

const convertValidationsToObject = (validations) => {
  if (typeof validations === 'string') {
    return validations.split(/,(?![^{[]*[}\]])/g).reduce((validationsAccumulator, validation) => {
      let args = validation.split(':');
      const validateMethod = args.shift();

      args = args.map((arg) => {
        try {
          return JSON.parse(arg);
        } catch (e) {
          return arg; // It is a string if it can not parse it
        }
      });

      if (args.length > 1) {
        throw new Error('Formsy does not support multiple args on string validations. Use object format of validations instead.');
      }

      // Avoid parameter reassignment
      const validationsAccumulatorCopy = Object.assign({}, validationsAccumulator);
      validationsAccumulatorCopy[validateMethod] = args.length ? args[0] : true;
      return validationsAccumulatorCopy;
    }, {});
  }

  return validations || {};
};

const propTypes = {
  innerRef: PropTypes.func,
  name: PropTypes.string.isRequired,
  required: PropTypes.bool,
  validations: PropTypes.oneOfType([
    PropTypes.object,
    PropTypes.string,
  ]),
  value: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.string,
  ]),
};

export {
  propTypes,
};

export default (Component) => {
  class WrappedComponent extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        value: props.value,
        isRequired: false,
        isValid: true,
        isPristine: true,
        pristineValue: props.value,
        validationError: [],
        externalError: null,
        formSubmitted: false,
      };
      this.getErrorMessage = this.getErrorMessage.bind(this);
      this.getErrorMessages = this.getErrorMessages.bind(this);
      this.getValue = this.getValue.bind(this);
      this.isFormDisabled = this.isFormDisabled.bind(this);
      this.isPristine = this.isPristine.bind(this);
      this.isRequired = this.isRequired.bind(this);
      this.isValid = this.isValid.bind(this);
      this.resetValue = this.resetValue.bind(this);
      this.setValue = this.setValue.bind(this);
      this.showRequired = this.showRequired.bind(this);
    }

    componentWillMount() {
      const configure = () => {
        this.setValidations(this.props.validations, this.props.required);

        // Pass a function instead?
        this.context.formsy.attachToForm(this);
      };

      if (!this.props.name) {
        throw new Error('Form Input requires a name property when used');
      }

      configure();
    }

    // We have to make sure the validate method is kept when new props are added
    componentWillReceiveProps(nextProps) {
      this.setValidations(nextProps.validations, nextProps.required);
    }

    componentDidUpdate(prevProps) {
      // If the value passed has changed, set it. If value is not passed it will
      // internally update, and this will never run
      if (!utils.isSame(this.props.value, prevProps.value)) {
        this.setValue(this.props.value);
      }

      // If validations or required is changed, run a new validation
      if (!utils.isSame(this.props.validations, prevProps.validations) ||
        !utils.isSame(this.props.required, prevProps.required)) {
        this.context.formsy.validate(this);
      }
    }

    // Detach it when component unmounts
    componentWillUnmount() {
      this.context.formsy.detachFromForm(this);
    }

    getErrorMessage() {
      const messages = this.getErrorMessages();
      return messages.length ? messages[0] : null;
    }

    getErrorMessages() {
      return !this.isValid() || this.showRequired() ?
        (this.state.externalError || this.state.validationError || []) : [];
    }

    getValue() {
      return this.state.value;
    }

    hasValue() {
      return this.state.value !== '';
    }

    isFormDisabled() {
      return this.context.formsy.isFormDisabled();
    }

    isFormSubmitted() {
      return this.state.formSubmitted;
    }

    isPristine() {
      return this.state.isPristine;
    }

    isRequired() {
      return !!this.props.required;
    }

    isValid() {
      return this.state.isValid;
    }

    isValidValue(value) {
      return this.context.formsy.isValidValue.call(null, this, value);
      // return this.props.isValidValue.call(null, this, value);
    }

    resetValue() {
      this.setState({
        value: this.state.pristineValue,
        isPristine: true,
      }, () => {
        this.context.formsy.validate(this);
      });
    }

    setValidations(validations, required) {
      // Add validations to the store itself as the props object can not be modified
      this.validations = convertValidationsToObject(validations) || {};
      this.requiredValidations = required === true ? { isDefaultRequiredValue: true } :
        convertValidationsToObject(required);
    }

    // By default, we validate after the value has been set.
    // A user can override this and pass a second parameter of `false` to skip validation.
    setValue(value, validate = true) {
      if (!validate) {
        this.setState({
          value,
        });
      } else {
        this.setState({
          value,
          isPristine: false,
        }, () => {
          this.context.formsy.validate(this);
        });
      }
    }

    showError() {
      return !this.showRequired() && !this.isValid();
    }

    showRequired() {
      return this.state.isRequired;
    }

    render() {
      const { innerRef } = this.props;
      const propsForElement = {
        getErrorMessage: this.getErrorMessage,
        getErrorMessages: this.getErrorMessages,
        getValue: this.getValue,
        hasValue: this.hasValue,
        isFormDisabled: this.isFormDisabled,
        isValid: this.isValid,
        isPristine: this.isPristine,
        isFormSubmitted: this.isFormSubmitted,
        isRequired: this.isRequired,
        isValidValue: this.isValidValue,
        resetValue: this.resetValue,
        setValidations: this.setValidations,
        setValue: this.setValue,
        showRequired: this.showRequired,
        showError: this.showError,
        ...this.props,
      };

      if (innerRef) {
        propsForElement.ref = innerRef;
      }

      return React.createElement(Component, propsForElement);
    }
  }

  function getDisplayName(component) {
    return (
      component.displayName ||
      component.name ||
      (typeof component === 'string' ? component : 'Component')
    );
  }

  WrappedComponent.displayName = `Formsy(${getDisplayName(Component)})`;

  WrappedComponent.contextTypes = {
    formsy: PropTypes.object, // What about required?
  };

  WrappedComponent.defaultProps = {
    innerRef: () => {},
    required: false,
    validationError: '',
    validationErrors: {},
    validations: null,
    value: Component.defaultValue,
  };

  WrappedComponent.propTypes = propTypes;

  return WrappedComponent;
};

@@ -1236,32 +1236,53 @@
 pes.oneOfType(%5B%0A
+    PropTypes.array,%0A
     PropTypes.bo

Change PropType for `value` to allow array.