import _ from "underscore";
import React from "react";
const PropTypes = React.PropTypes;

import FormControl from "./FormControl";
import Util from "../utils/Util";

const METHODS_TO_BIND = [
  "handleSubmit",
  "handleBlur",
  "handleValueChange",
  "handleOnFocus"
];

function findFieldOption(options, field) {
  var flattenedOptions = _.flatten(options);
  return _.find(flattenedOptions, function (fieldOption) {
    return fieldOption.fieldName === field;
  });
}

export default class Form extends React.Component {
  constructor() {
    super();

    this.state = {
      prevSaveModel: {},
      model: {},
      editingField: "",
      erroredFields: {}
    };

    METHODS_TO_BIND.forEach((method) => {
      this[method] = this[method].bind(this);
    });
  }

  componentWillMount() {
    if (this.props.triggerSubmit) {
      this.props.triggerSubmit(this.handleSubmit.bind(this));
    }

    this.setState({
      model: this.buildModel(this.props.definition),
      prevSaveModel: this.buildModel(this.props.definition),
      erroredFields: this.buildErroredFields(this.props.definition)
    });
  }

  handleSubmit() {
    let validated = this.validateSubmit();

    if (validated && this.props.onSubmit) {
      this.props.onSubmit(this.state.model);
      this.setState({prevSaveModel: _.clone(this.state.model)});
      return;
    }
  }

  handleValueChange(field, value) {
    let model = _.clone(this.state.model);
    model[field] = value;

    this.setState({model});
  }

  handleBlur(fieldName) {
    let prevValue = this.state.prevSaveModel[fieldName];
    let currentValue = this.state.model[fieldName];

    if (prevValue !== currentValue) {
      this.handleSubmit();
    }

    this.setState({editingField: null});
  }

  handleOnFocus(fieldName) {
    let fieldOption = findFieldOption(this.props.definition, fieldName);

    if (fieldOption.writeType === "edit") {
      this.setState({editingField: fieldName});
    }
  }

  validateValue(field, value) {
    let validation = findFieldOption(this.props.definition, field).validation;

    if (typeof validation === "function") {
      return validation(value);
    }

    return validation.test(value);
  }

  validateSubmit() {
    var failedFields = _.filter(Object.keys(this.state.model), (fieldName) => {
      let model = this.state.model;
      let validated = this.validateValue(fieldName, model[fieldName]);

      if (!validated) {
        return true;
      }

      let options = findFieldOption(this.props.definition, fieldName);
      if (options.required) {
        return model[fieldName] == null;
      }

      return false;
    });

    let erroredFields = {};
    failedFields.forEach(function (field) {
      erroredFields[field] = true;
    });

    this.setState({erroredFields});

    return failedFields.length === 0;
  }

  buildModel(definition) {
    let model = {};

    definition.forEach((formControlOption) => {
      if (Util.isArray(formControlOption)) {
        _.extend(model, this.buildModel(formControlOption));
        return;
      }

      model[formControlOption.fieldName] = formControlOption.value || null;
    });

    return model;
  }

  buildErroredFields(definition) {
    let flattenedOptions = _.flatten(definition);
    let erroredFields = {};

    flattenedOptions.forEach((formControlOption) => {
      if (formControlOption.showError) {
        erroredFields[formControlOption.fieldName] = true;
      }
    });

    return erroredFields;
  }

  getFormControls(definition) {
    let flattenedOptions = _.flatten(definition);

    return flattenedOptions.map((formControlOption, i) => {
      let fieldName = formControlOption.fieldName;
      let currentlyEditing = fieldName === this.state.editingField;

      let showError = this.state.erroredFields[fieldName];

      return (
        <FormControl
          key={i}
          definition={formControlOption}
          onBlur={this.handleBlur}
          onFocus={this.handleOnFocus}
          triggerSubmit={this.handleSubmit}
          onChange={this.handleValueChange}
          editing={currentlyEditing}
          validationError={showError}
          currentValue={this.state.model[fieldName]} />
      );
    });
  }

  render() {
    let formControls = this.getFormControls(this.props.definition);

    return (
      <form>
        {formControls}
      </form>
    );
  }
}

Form.propTypes = {
  definition: PropTypes.array,
  triggerSubmit: PropTypes.func,
  onSubmit: PropTypes.func

};

Form.defaultProps = {
  triggerSubmit: function () {},
  onSubmit: function () {},
  definition: {}
};

/*
  TODO:
  -x- Util to combine schema/options
  -x- Different field types
  -x- Required fields
  -x- Placeholders
  -x- Only validate on submit
  -x- Submission within form
  -x- Validation error shows help text
  -x- Handle form submit (it takes a callback and passes in the model)
  -x- Have the current values be the value of the input
  Switch modes between edit, read, input (also, elements)
  (another PR) Figure out what a schema looks like and how to better merge it
*/

@@ -572,33 +572,8 @@
 = %7B%0A
-      prevSaveModel: %7B%7D,%0A
     
@@ -585,16 +585,16 @@
 el: %7B%7D,%0A
+
       ed
@@ -949,69 +949,8 @@
 n),%0A
-      prevSaveModel: this.buildModel(this.props.definition),%0A
     
@@ -1182,73 +1182,8 @@
 l);%0A
-      this.setState(%7BprevSaveModel: _.clone(this.state.model)%7D);%0A
     
@@ -1359,171 +1359,12 @@
 lur(
-fieldName) %7B%0A    let prevValue = this.state.prevSaveModel%5BfieldName%5D;%0A    let currentValue = this.state.model%5BfieldName%5D;%0A%0A    if (prevValue !== currentValue) %7B%0A  
+) %7B%0A
     
@@ -1383,22 +1383,16 @@
 ubmit();
-%0A    %7D
 %0A%0A    th
@@ -4276,486 +4276,4 @@
 %0A%7D;%0A
-%0A/*%0A  TODO:%0A  -x- Util to combine schema/options%0A  -x- Different field types%0A  -x- Required fields%0A  -x- Placeholders%0A  -x- Only validate on submit%0A  -x- Submission within form%0A  -x- Validation error shows help text%0A  -x- Handle form submit (it takes a callback and passes in the model)%0A  -x- Have the current values be the value of the input%0A  Switch modes between edit, read, input (also, elements)%0A  (another PR) Figure out what a schema looks like and how to better merge it%0A*/%0A

Remove prevSaveModel. maybe in the future