'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _lodash = require('lodash');

var _lodash2 = _interopRequireDefault(_lodash);

var _validators = require('./validators');

var validators = _interopRequireWildcard(_validators);

var _formatters = require('./formatters');

var formatters = _interopRequireWildcard(_formatters);

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

/** Param class. */
var Param = function () {

  /**
   * Create a param.
   * @param {string} name - Param name.
   * @param {*} [value] - The value of the param.
   * @param {Object} [options] - Options of the param.
   */
  function Param(name, value) {
    var _this = this;

    var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

    _classCallCheck(this, Param);

    this.name = name;
    this.handlers = {
      formatters: {},
      validators: {}
    };
    this.options = _lodash2.default.assign({
      multiple: false,
      trim: true,
      separator: ',',
      format: function format(value, param) {
        return value;
      },
      validate: function validate(value, param) {
        return { valid: true };
      }
    }, options);

    if (_lodash2.default.isNil(this.options.type)) {
      this.options.type = this._getType(value);
    }

    if (_lodash2.default.isArray(this.options.type)) {
      this.options.multiple = true;
      this.options.type = this.options.type[0];
    }

    this.formatter('default', function (defaultValue, value, param) {
      if (_lodash2.default.isNil(value) || _lodash2.default.isNaN(value) || value === '') {
        value = _lodash2.default.isFunction(defaultValue) ? defaultValue(_this) : defaultValue;
        if (_lodash2.default.isNil(options.type)) {
          param.option('type', _this._getType(value));
        }
      }
      return value;
    });

    this.formatter('normalize', formatters.normalize);
    this.formatter('lowercase', formatters.lowercase);
    this.formatter('uppercase', formatters.uppercase);
    this.formatter('trim', formatters.trim);

    this.validator('required', validators.required);
    this.validator('min', validators.min);
    this.validator('max', validators.max);
    this.validator('minlength', validators.minlength);
    this.validator('maxlength', validators.maxlength);
    this.validator('enum', validators.enumerator);
    this.validator('match', validators.match);

    this.value(value);
  }

  /**
   * Get or set an option.
   * @param {string} name - Option name.
   * @param {*} [value] - Set the value of the option.
   * @return {*} Value of the option.
   */


  _createClass(Param, [{
    key: 'option',
    value: function option(name, value) {
      if (arguments.length > 1) {
        this.options[name] = value;
      }

      return this.options[name];
    }

    /**
     * Get or set a handler.
     * @param {string} type - Handler type.
     * @param {string} name - Handler name.
     * @param {Function} [fn] - Set the handler method.
     */

  }, {
    key: 'handler',
    value: function handler(type, name, fn) {
      if (arguments.length > 2) {
        this.handlers[type][name] = fn;
      }

      return this.handlers[type][name];
    }

    /**
     * Get or set a formatter.
     * @param {string} name - Formatter name.
     * @param {formatterFn} [fn] - Set the formatter method.
     * @return {formatterFn} The formatter method.
     */

  }, {
    key: 'formatter',
    value: function formatter(name, fn) {
      return this.handler.apply(this, ['formatters'].concat(Array.prototype.slice.call(arguments)));
    }

    /**
     * Get or set a validator.
     * @param {string} name - Validator name.
     * @param {validatorFn} [fn] - Set the validator method.
     * @return {validatorFn} The validator method.
     */

  }, {
    key: 'validator',
    value: function validator(name, fn) {
      return this.handler.apply(this, ['validators'].concat(Array.prototype.slice.call(arguments)));
    }

    /**
     * Get or set the param value.
     * @param {*} [value] - Set the param value.
     * @param {boolean} [bind=true] - Set if value must be bound to parameter or not.
     * @return {*} The formatted value.
     */

  }, {
    key: 'value',
    value: function value(_value) {
      var _this2 = this;

      var bind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

      var options = this.options;

      if (arguments.length === 0) {
        return this._value;
      }

      if (options.multiple) {
        var values = _value;

        if (_lodash2.default.isString(_value) && ~_value.search(options.separator)) {
          values = _value.split(options.separator);
        }

        if (_lodash2.default.isArray(values)) {
          values = values.map(function (value) {
            return _this2.value(value, false);
          });

          if (bind) {
            this._value = values;
          }

          return values;
        }
      }

      _lodash2.default.forIn(this.options, function (optionValue, option) {
        var formatter = _this2.handlers.formatters[option];
        if (_lodash2.default.isFunction(formatter)) {
          _value = formatter(optionValue, _value, _this2);
        }
      });

      if (!_lodash2.default.isNil(_value)) {
        if (options.type.name === 'RegExp') {
          _value = new RegExp(_value, 'i');
        } else if (options.type.name === 'Date') {
          _value = new Date(/^\d{5,}$/.test(_value) ? Number(_value) : _value);
        } else if (options.type.name === 'Boolean') {
          _value = !(_value === 'false' || _value === '0' || !_value);
        } else if (options.type.name === 'Number') {
          _value = Number(_value);
        } else if (options.type.name === 'Object') {
          _value = Object(_value);
        } else {
          _value = String(_value);
        }
      }

      _value = options.format(_value, this);

      if (bind) {
        this._value = _value;
      }

      return _value;
    }

    /**
     * Validates the param.
     * @param {*} [value] - Set the param value.
     * @param {Function} [next] - Callback to be called with error
     * @return {boolean} Result of the validation.
     */

  }, {
    key: 'validate',
    value: function validate() {
      var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._value;
      var next = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function (error) {
        return !error;
      };

      var error = void 0;

      if (_lodash2.default.isFunction(value)) {
        next = value;
        value = this._value;
      }

      if (_lodash2.default.isArray(value)) {
        for (var i = 0; i < value.length; i++) {
          this.validate(value[i], function (err) {
            error = err;
          });
          if (error) break;
        }

        return next(error);
      }

      for (var option in this.options) {
        var optionValue = this.options[option];
        var validator = void 0;

        if (option === 'validate' && _lodash2.default.isFunction(optionValue)) {
          validator = optionValue;
        } else if (_lodash2.default.isFunction(this.handlers.validators[option])) {
          validator = this.handlers.validators[option].bind(this, optionValue);
        } else {
          continue;
        }

        var validation = validator(value, this);

        if (!validation.valid) {
          error = _lodash2.default.assign(_defineProperty({
            name: option,
            param: this.name,
            value: value
          }, option, optionValue), validation);
          break;
        }
      }

      return next(error);
    }
  }, {
    key: '_getType',
    value: function _getType() {
      var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._value;

      if (_lodash2.default.isNil(value)) {
        return String;
      } else if (_lodash2.default.isNumber(value)) {
        return Number;
      } else if (_lodash2.default.isBoolean(value)) {
        return Boolean;
      } else if (_lodash2.default.isDate(value)) {
        return Date;
      } else if (_lodash2.default.isRegExp(value)) {
        return RegExp;
      } else if (_lodash2.default.isArray(value)) {
        this.option('multiple', true);
        return this._getType(value[0]);
      } else if (_lodash2.default.isObject(value)) {
        return Object;
      } else {
        return String;
      }
    }
  }]);

  return Param;
}();

exports.default = Param;