'use strict';

function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }

var _slicedToArray = _interopDefault(require('@babel/runtime/helpers/slicedToArray'));
var _regeneratorRuntime = _interopDefault(require('@babel/runtime/regenerator'));
var _asyncToGenerator = _interopDefault(require('@babel/runtime/helpers/asyncToGenerator'));
var _classCallCheck = _interopDefault(require('@babel/runtime/helpers/classCallCheck'));
var _createClass = _interopDefault(require('@babel/runtime/helpers/createClass'));
var _defineProperty = _interopDefault(require('@babel/runtime/helpers/defineProperty'));
var Constants = require('./Constants');
var StateException = _interopDefault(require('./Exceptions/StateException'));
var DataSourceNotSetException = _interopDefault(require('./Exceptions/DataSourceNotSetException'));
var RulesNotSetException = _interopDefault(require('./Exceptions/RulesNotSetException'));
var utils = require('@forensic-js/utils');
var FilesSourceNotSetException = _interopDefault(require('./Exceptions/FilesSourceNotSetException'));
var regex = require('@forensic-js/regex');
var Validator = _interopDefault(require('./Validator'));
var CustomDate = _interopDefault(require('./CustomDate'));
var DataProxy = _interopDefault(require('./DataProxy'));
var DBChecker = _interopDefault(require('./DBChecker'));
var Model = _interopDefault(require('./Model'));
var libphonenumberJs = require('libphonenumber-js');
var inflection = require('inflection');

var globalConfig = {
  dbModel: Constants.DB_MODELS.NOSQL,
  dbCaseStyle: utils.CASE_STYLES.CAMEL_CASE
};

var Handler =
/*#__PURE__*/
function () {
  _createClass(Handler, null, [{
    key: "setDBModel",

    /**
     * supported database models
     */

    /**
     * supported database field case styles
     */

    /**
     * globally sets the database model to use
     * @param dbModel
     */
    value: function setDBModel(dbModel) {
      globalConfig.dbModel = dbModel;
    }
    /**
     * globally sets the database model field case style to use
     * @param dbCaseStyle
     */

  }, {
    key: "setDBCaseStyle",
    value: function setDBCaseStyle(dbCaseStyle) {
      globalConfig.dbCaseStyle = dbCaseStyle;
    }
  }]);

  function Handler(dataSource, filesSource, rules, validator, dbChecker) {
    _classCallCheck(this, Handler);

    _defineProperty(this, "dataSource", null);

    _defineProperty(this, "filesSource", null);

    _defineProperty(this, "addedFields", {});

    _defineProperty(this, "rules", null);

    _defineProperty(this, "resolvedRules", {});

    _defineProperty(this, "executed", false);

    _defineProperty(this, "requiredFields", []);

    _defineProperty(this, "optionalFields", []);

    _defineProperty(this, "validator", void 0);

    _defineProperty(this, "dbChecker", void 0);

    _defineProperty(this, "dbModel", globalConfig.dbModel);

    _defineProperty(this, "dbCaseStyle", globalConfig.dbCaseStyle);

    _defineProperty(this, "dataTypeToMethod", {
      text: 'validateText',
      title: 'validateText',
      date: 'validateDate',
      //integer validation methods
      int: 'validateInt',
      pInt: 'validatePInt',
      nInt: 'validateNInt',
      //number validation methods
      number: 'validateNumber',
      pNumber: 'validatePNumber',
      nNumber: 'validateNNumber',
      money: 'validateNumber',
      //boolean validation
      boolean: '',
      checkbox: '',
      email: 'validateEmail',
      url: 'validateURL',
      choice: 'validateChoice',
      range: 'validateRange',
      //file related validations
      file: 'validateFile',
      image: 'validateImage',
      audio: 'validateAudio',
      video: 'validateVideo',
      media: 'validateMedia',
      document: 'validateDocument',
      archive: 'validateArchive',
      //password validation
      password: 'validatePassword',
      //phone number validation
      phoneNumber: 'validatePhoneNumber'
    });

    _defineProperty(this, "DBCheckTypesToMethod", {
      //check if exist method map
      exists: 'checkIfExists',
      //check if not exists method map
      notExists: 'checkIfNotExists'
    });

    _defineProperty(this, "customData", {});

    _defineProperty(this, "data", new Proxy({}, DataProxy));

    _defineProperty(this, "errors", {});

    this.setDataSource(dataSource).setFilesSource(filesSource).setRules(rules).setValidator(validator || new Validator()).setDBChecker(dbChecker || new DBChecker());
  }
  /**
   * returns true if value is falsy
   */


  _createClass(Handler, [{
    key: "valueIsFalsy",
    value: function valueIsFalsy(value) {
      return value === '' || /(false|off|0|nil|null|none|undefined|no)/i.test(value);
    }
    /**
     * returns true if the given data type is a file data type
     */

  }, {
    key: "isFileDataType",
    value: function isFileDataType(dataType) {
      return ['file', 'image', 'audio', 'video', 'media', 'document', 'archive'].includes(dataType);
    }
    /**
     * returns true if the given field's type is a file data type
     */

  }, {
    key: "isFileField",
    value: function isFileField(field) {
      return this.isFileDataType(this.resolvedRules[field].type);
    }
    /**
     * return empty file collection
     */

  }, {
    key: "createEmptyFileEntryCollection",
    value: function createEmptyFileEntryCollection() {
      return {
        name: [],
        key: [],
        path: [],
        size: [],
        type: []
      };
    }
    /**
     * picks out a fileEntry out of the collection
     * @param fileCollection
     */

  }, {
    key: "makeFileEntry",
    value: function makeFileEntry(fileCollection, index) {
      return Object.keys(fileCollection).reduce(function (result, key) {
        result[key] = fileCollection[key][index];
        return result;
      }, {});
    }
    /**
     * turns the file into a file collection
     * @param file
     */

  }, {
    key: "makeFileCollection",
    value: function makeFileCollection(file) {
      if (utils.isObject(file)) {
        return Object.keys(file).reduce(function (result, key) {
          result[key] = utils.makeArray(file[key]);
          return result;
        }, {});
      } else {
        return this.createEmptyFileEntryCollection();
      }
    }
    /**
     * run post processes
     */

  }, {
    key: "runPostProcesses",
    value: function () {
      var _runPostProcesses = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee() {
        var _i, _Object$keys, _field, rule, result;

        return _regeneratorRuntime.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                _i = 0, _Object$keys = Object.keys(this.resolvedRules);

              case 1:
                if (!(_i < _Object$keys.length)) {
                  _context.next = 16;
                  break;
                }

                _field = _Object$keys[_i];
                rule = this.resolvedRules[_field];

                if (!utils.isCallable(rule.postCompute)) {
                  _context.next = 8;
                  break;
                }

                _context.next = 7;
                return rule.postCompute(this.data[_field], this.data, this);

              case 7:
                this.data[_field] = _context.sent;

              case 8:
                if (!utils.isCallable(rule.postValidate)) {
                  _context.next = 13;
                  break;
                }

                _context.next = 11;
                return rule.postValidate(this.data[_field], this.data, this);

              case 11:
                result = _context.sent;

                if (result !== true) {
                  this.setError(_field, result);
                }

              case 13:
                _i++;
                _context.next = 1;
                break;

              case 16:
              case "end":
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

      function runPostProcesses() {
        return _runPostProcesses.apply(this, arguments);
      }

      return runPostProcesses;
    }()
    /**
     * runs db check
     */

  }, {
    key: "runDBCheck",
    value: function () {
      var _runDBCheck = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee2(required, field, value, checks, index) {
        var _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, check, method;

        return _regeneratorRuntime.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                _iteratorNormalCompletion = true;
                _didIteratorError = false;
                _iteratorError = undefined;
                _context2.prev = 3;
                _iterator = checks[Symbol.iterator]();

              case 5:
                if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
                  _context2.next = 22;
                  break;
                }

                check = _step.value;

                if (!utils.isTypeOf('callback', check)) {
                  _context2.next = 14;
                  break;
                }

                _context2.next = 10;
                return check.callback(field, value, index);

              case 10:
                if (!_context2.sent) {
                  _context2.next = 12;
                  break;
                }

                return _context2.abrupt("return", this.setError(field, utils.pickValue('err', check, 'condition not satisfied')));

              case 12:
                _context2.next = 19;
                break;

              case 14:
                method = this.DBCheckTypesToMethod[check.if];
                _context2.next = 17;
                return this.dbChecker[method](required, field, value, check, index);

              case 17:
                if (!this.dbChecker.fails()) {
                  _context2.next = 19;
                  break;
                }

                return _context2.abrupt("return", false);

              case 19:
                _iteratorNormalCompletion = true;
                _context2.next = 5;
                break;

              case 22:
                _context2.next = 28;
                break;

              case 24:
                _context2.prev = 24;
                _context2.t0 = _context2["catch"](3);
                _didIteratorError = true;
                _iteratorError = _context2.t0;

              case 28:
                _context2.prev = 28;
                _context2.prev = 29;

                if (!_iteratorNormalCompletion && _iterator.return != null) {
                  _iterator.return();
                }

              case 31:
                _context2.prev = 31;

                if (!_didIteratorError) {
                  _context2.next = 34;
                  break;
                }

                throw _iteratorError;

              case 34:
                return _context2.finish(31);

              case 35:
                return _context2.finish(28);

              case 36:
                return _context2.abrupt("return", true);

              case 37:
              case "end":
                return _context2.stop();
            }
          }
        }, _callee2, this, [[3, 24, 28, 36], [29,, 31, 35]]);
      }));

      function runDBCheck(_x, _x2, _x3, _x4, _x5) {
        return _runDBCheck.apply(this, arguments);
      }

      return runDBCheck;
    }()
    /**
     * performs database integrity checks
     *
     * @param fields array of fields
     * @param required boolean indicating if fields are required
     */

  }, {
    key: "validateDBChecks",
    value: function () {
      var _validateDBChecks = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee3(fields, required) {
        var _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, _field2, checks, values, len, i, value;

        return _regeneratorRuntime.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                _iteratorNormalCompletion2 = true;
                _didIteratorError2 = false;
                _iteratorError2 = undefined;
                _context3.prev = 3;
                _iterator2 = fields[Symbol.iterator]();

              case 5:
                if (_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done) {
                  _context3.next = 21;
                  break;
                }

                _field2 = _step2.value;
                checks = this.resolvedRules[_field2].checks;

                if (!(checks.length > 0)) {
                  _context3.next = 18;
                  break;
                }

                values = utils.makeArray(this.data[_field2]);
                len = values.length;
                i = -1;

              case 12:
                if (!(++i < len)) {
                  _context3.next = 18;
                  break;
                }

                value = values[i];
                _context3.next = 16;
                return this.runDBCheck(required, _field2, value === null ? '' : value.toString(), checks, i);

              case 16:
                _context3.next = 12;
                break;

              case 18:
                _iteratorNormalCompletion2 = true;
                _context3.next = 5;
                break;

              case 21:
                _context3.next = 27;
                break;

              case 23:
                _context3.prev = 23;
                _context3.t0 = _context3["catch"](3);
                _didIteratorError2 = true;
                _iteratorError2 = _context3.t0;

              case 27:
                _context3.prev = 27;
                _context3.prev = 28;

                if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
                  _iterator2.return();
                }

              case 30:
                _context3.prev = 30;

                if (!_didIteratorError2) {
                  _context3.next = 33;
                  break;
                }

                throw _iteratorError2;

              case 33:
                return _context3.finish(30);

              case 34:
                return _context3.finish(27);

              case 35:
              case "end":
                return _context3.stop();
            }
          }
        }, _callee3, this, [[3, 23, 27, 35], [28,, 30, 34]]);
      }));

      function validateDBChecks(_x6, _x7) {
        return _validateDBChecks.apply(this, arguments);
      }

      return validateDBChecks;
    }()
    /**
     * carries out data validation
     */

  }, {
    key: "runValidation",
    value: function () {
      var _runValidation = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee4(required, field, type, value, options, index) {
        var validator, method, data, name;
        return _regeneratorRuntime.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                validator = this.validator;
                method = this.dataTypeToMethod[type];

                if (!(method !== '')) {
                  _context4.next = 11;
                  break;
                }

                _context4.next = 5;
                return validator[method](required, field, value, options, index);

              case 5:
                if (!utils.isObject(value)) {
                  _context4.next = 11;
                  break;
                }

                data = this.data[field];
                name = data.name;

                if (typeof name === 'string') {
                  data = value;
                  this.data[field] = value;
                } else {
                  Object.keys(value).forEach(function (key) {
                    data[key][index] = value[key];
                  });
                }

                this.data[field] = data;
                return _context4.abrupt("return", validator.succeeds());

              case 11:
              case "end":
                return _context4.stop();
            }
          }
        }, _callee4, this);
      }));

      function runValidation(_x8, _x9, _x10, _x11, _x12, _x13) {
        return _runValidation.apply(this, arguments);
      }

      return runValidation;
    }()
    /**
     * performs data validation
     *
     * @param fields array of fields
     * @param required boolean indicating if fields are required
     */

  }, {
    key: "validateFields",
    value: function () {
      var _validateFields = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee5(fields, required) {
        var _iteratorNormalCompletion3, _didIteratorError3, _iteratorError3, _iterator3, _step3, _field3, _this$resolvedRules$_, options, type, isFileDataType, fileCollection, len, i, value, values, _len, _i2, _value;

        return _regeneratorRuntime.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                _iteratorNormalCompletion3 = true;
                _didIteratorError3 = false;
                _iteratorError3 = undefined;
                _context5.prev = 3;
                _iterator3 = fields[Symbol.iterator]();

              case 5:
                if (_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done) {
                  _context5.next = 37;
                  break;
                }

                _field3 = _step3.value;
                _this$resolvedRules$_ = this.resolvedRules[_field3], options = _this$resolvedRules$_.options, type = _this$resolvedRules$_.type;
                isFileDataType = this.isFileDataType(type);

                if (!(this.data[_field3] !== null && isFileDataType)) {
                  _context5.next = 23;
                  break;
                }

                fileCollection = this.makeFileCollection(this.data[_field3]);
                len = fileCollection.name.length;
                i = -1;

              case 13:
                if (!(++i < len)) {
                  _context5.next = 21;
                  break;
                }

                value = this.makeFileEntry(fileCollection, i);
                _context5.next = 17;
                return this.runValidation(required, _field3, type, value, options, i);

              case 17:
                if (_context5.sent) {
                  _context5.next = 19;
                  break;
                }

                return _context5.abrupt("break", 21);

              case 19:
                _context5.next = 13;
                break;

              case 21:
                _context5.next = 34;
                break;

              case 23:
                values = utils.makeArray(this.data[_field3], true);
                _len = values.length;
                _i2 = -1;

              case 26:
                if (!(++_i2 < _len)) {
                  _context5.next = 34;
                  break;
                }

                _value = values[_i2];
                _context5.next = 30;
                return this.runValidation(required, _field3, type, _value === null ? '' : _value.toString(), options, _i2);

              case 30:
                if (_context5.sent) {
                  _context5.next = 32;
                  break;
                }

                return _context5.abrupt("break", 34);

              case 32:
                _context5.next = 26;
                break;

              case 34:
                _iteratorNormalCompletion3 = true;
                _context5.next = 5;
                break;

              case 37:
                _context5.next = 43;
                break;

              case 39:
                _context5.prev = 39;
                _context5.t0 = _context5["catch"](3);
                _didIteratorError3 = true;
                _iteratorError3 = _context5.t0;

              case 43:
                _context5.prev = 43;
                _context5.prev = 44;

                if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
                  _iterator3.return();
                }

              case 46:
                _context5.prev = 46;

                if (!_didIteratorError3) {
                  _context5.next = 49;
                  break;
                }

                throw _iteratorError3;

              case 49:
                return _context5.finish(46);

              case 50:
                return _context5.finish(43);

              case 51:
              case "end":
                return _context5.stop();
            }
          }
        }, _callee5, this, [[3, 39, 43, 51], [44,, 46, 50]]);
      }));

      function validateFields(_x14, _x15) {
        return _validateFields.apply(this, arguments);
      }

      return validateFields;
    }()
    /**
     * flag error if certain data values are multiple when the field does not accept multiple values
     */

  }, {
    key: "checkArrays",
    value: function checkArrays() {
      var rules = this.resolvedRules;

      for (var _i3 = 0, _Object$keys2 = Object.keys(this.resolvedRules); _i3 < _Object$keys2.length; _i3++) {
        var _field4 = _Object$keys2[_i3];
        var _rules = this.resolvedRules[_field4];
        var data = this.data[_field4];
        var isFileField = this.isFileField(_field4);

        if (!_rules.array) {
          if (isFileField && utils.isObject(data) && utils.isArray(data.key) || !isFileField && utils.isArray(data)) {
            this.setError(_field4, '{_this} does not accept multiple values');
          }
        }
      }

      return this.succeeds();
    }
    /**
     * retrieves fields' data
     */

  }, {
    key: "getFields",
    value: function getFields(fields) {
      var _this = this;

      fields.forEach(function (field) {
        var rules = _this.resolvedRules[field];

        var isFileField = _this.isFileField(field);

        var fieldIsMissing = _this.fieldIsMissing(field);

        var filesSource = _this.filesSource;
        var dataSource = _this.dataSource;
        var value = isFileField ? filesSource[field] : dataSource[field];

        if (fieldIsMissing) {
          value = rules.defaultValue;
        }

        if (rules.array) {
          value = isFileField ? _this.makeFileCollection(value) : utils.makeArray(value).filter(function (value) {
            return value !== '';
          });
        }

        _this.data[field] = _this.filterValue(value, field);
      });
    }
    /**
     * returns true if field is missing
     */

  }, {
    key: "fieldIsMissing",
    value: function fieldIsMissing(field) {
      var isFileField = this.isFileField(field);
      var filesSource = this.filesSource;
      var dataSource = this.dataSource;

      if (isFileField) {
        return utils.isUndefined(filesSource[field]);
      } else if (utils.isUndefined(dataSource[field])) {
        return true;
      }

      var value = this.dataSource[field];

      if (utils.isString(value)) {
        return value === '';
      } else {
        dataSource[field] = value = value.filter(function (current) {
          return current !== '';
        });
        return value.length === 0;
      }
    }
    /**
     * checks for missing required fields
     */

  }, {
    key: "checkMissingFields",
    value: function checkMissingFields() {
      var _this2 = this;

      this.requiredFields.forEach(function (field) {
        if (_this2.fieldIsMissing(field)) {
          _this2.setError(field, _this2.resolvedRules[field].hint);
        }
      });
      return this.succeeds();
    }
    /**
     * strips html tags from the given value
     */

  }, {
    key: "stripTags",
    value: function stripTags(value, stripTagsIgnore) {
      if (utils.isString(stripTagsIgnore)) {
        stripTagsIgnore = stripTagsIgnore.split(/[,\s>]/);
      }

      stripTagsIgnore = stripTagsIgnore.map(function (tag) {
        return tag.toLowerCase().replace(/[/<>]/g, '');
      }).filter(function (tag) {
        return tag !== '';
      });
      var matchName = '[_a-z][-\\w]*';
      var regex$$1 = new RegExp( //capture tagName
      '<\\s*\\/?(' + matchName + ')' + //followed by zero or more attributes, with the attribute value optional
      '(?:\\s+' + matchName + '(?:=(?:"[^"]*"|\'[^\']*\'))?)*' + //then ends with zero or more spaces followed by the right angle bracket
      '\\s*>', 'i');
      return regex.replaceCallback(regex$$1, function (matches) {
        if (stripTagsIgnore.includes(matches[1].toLowerCase())) {
          return matches[0];
        } else {
          return '';
        }
      }, value);
    }
    /**
     * runs defined filters on the given value and returns the result
     */

  }, {
    key: "filterValue",
    value: function filterValue(value, field) {
      var _this3 = this;

      var resolvedRules = this.resolvedRules;
      var type = resolvedRules[field].type;
      var filters = resolvedRules[field].filters;
      /**
       * performs the filter
       * @param value the current value
       */

      var performFilter = function performFilter(value) {
        var result = value;

        if (type === 'checkbox' || type === 'boolean') {
          return !_this3.valueIsFalsy(value);
        } //decode value


        if (utils.keyNotSetOrTrue('decode', filters)) {
          result = decodeURIComponent(result);
        } //strip tags before doing any trim operations


        if (utils.keyNotSetOrTrue('stripTags', filters)) {
          result = _this3.stripTags(result, utils.pickValue('stripTagsIgnore', filters, []));
        } //minimize data by triming and removing empty lines, suitable when handling
        //computer proprams such as html, xml, json, etc


        if (filters.minimize) {
          result = result.split(/\r\n|\r|\n/).map(function (value) {
            return value.trim();
          }).filter(function (value) {
            return !/^\s*$/.test(value);
          }).join(' ');
        } // trim value


        if (utils.keyNotSetOrTrue('trim', filters)) {
          result = result.trim();
        } // title


        if (filters.titleize || type == 'title') {
          result = inflection.titleize(result);
        } // upper case
        else if (filters.uppercase) {
            result = result.toUpperCase();
          } //lower case
          else if (filters.lowercase) {
              result = result.toLowerCase();
            } //capitalize
            else if (filters.capitalize) {
                result = inflection.capitalize(result);
              }

        if (filters.pluralize) {
          result = inflection.pluralize(result);
        }

        if (filters.singularize) {
          result = inflection.singularize(result);
        }

        if (filters.ordinalize) {
          result = inflection.ordinalize(result);
        } //cast to float


        if (filters.toNumeric) {
          result = utils.isNumeric(result) ? parseFloat(result) : 0;
        }

        switch (type) {
          case 'email':
            result = regex.replace(/[^-\w!#$%&'*+/=?^`{|}~.@]/, '', result);
            break;

          case 'url':
            result = regex.replace(/[^-\w!#$%&'*+/=?^`:?{|}()~.@]/, '', result);
            break;

          case 'int':
          case 'nInt':
          case 'pInt':
            if (utils.isNumeric(result)) {
              result = parseInt(result);
            }

            break;

          case 'number':
          case 'nNumber':
          case 'pNumber':
          case 'money':
            if (utils.isNumeric(result)) {
              result = parseFloat(result);
            }

            break;
        } //run callback on the value if given


        if (filters.callback) {
          result = filters.callback(result.toString());
        }

        return result;
      };

      if (value === '') {
        return null;
      }

      if (utils.isObject(value)) {
        var name = value.name;
        value.name = utils.isArray(name) ? name.map(function (current) {
          return performFilter(current);
        }) : performFilter(name);
        return value;
      } else {
        if (utils.isArray(value)) {
          return utils.uniqueArray(value).map(function (current) {
            return performFilter(current.toString());
          });
        } else {
          return performFilter(value.toString());
        }
      }
    }
    /**
     * resolves an option
     */

  }, {
    key: "resolveOption",
    value: function resolveOption(field, options) {
      var _this4 = this;

      var data = this.data;

      var resolve = function resolve(field, value) {
        return regex.replaceCallback(/\{\s*([^}]+)\s*\}/, function (matches) {
          var capture = matches[1];
          var result = utils.pickValue(capture, data, matches[0]); //while resolving, leave out this and _index, as they are runtime values

          switch (capture.toLowerCase()) {
            case '_this':
              result = field;
              break;

            case 'current_date':
              result = '' + new CustomDate();
              break;

            case 'current_year':
              result = new CustomDate().getFullYear();
              break;

            case 'current_time':
              result = new CustomDate().getTime() * 1000;
              break;
          }

          return result.toString();
        }, value);
      };

      if (utils.isObject(options)) {
        for (var _i4 = 0, _Object$entries = Object.entries(options); _i4 < _Object$entries.length; _i4++) {
          var _Object$entries$_i = _slicedToArray(_Object$entries[_i4], 2),
              key = _Object$entries$_i[0],
              value = _Object$entries$_i[1];

          options[key] = this.resolveOption(field, value);
        }

        return options;
      }

      if (utils.isArray(options)) {
        return options.map(function (value) {
          return _this4.resolveOption(field, value);
        });
      }

      if (utils.isString(options)) {
        return resolve(field, options);
      }

      return options;
    }
    /**
     * resolves the specific target objects within all resolved rules
     */

  }, {
    key: "resolveOptions",
    value: function resolveOptions(target) {
      var _this5 = this;

      Object.keys(this.resolvedRules).forEach(function (field) {
        var rule = _this5.resolvedRules[field];
        rule[target] = _this5.resolveOption(field, rule[target]);
      });
    }
    /**
     * extracts required fields and optional fields.
     * @param rules
     */

  }, {
    key: "categorizeRules",
    value: function categorizeRules() {
      var _this6 = this;

      Object.keys(this.resolvedRules).forEach(function (field) {
        var rule = _this6.resolvedRules[field];

        if (rule.required) {
          _this6.requiredFields.push(field);
        } else {
          _this6.optionalFields.push(field);
        }
      });
    }
    /**
     * filter rules to be validated.
     * @param requiredFields
     */

  }, {
    key: "filterRules",
    value: function filterRules(requiredFields) {
      var _this7 = this;

      requiredFields = utils.makeArray(requiredFields);
      this.resolvedRules = Object.keys(this.resolvedRules).reduce(function (result, field) {
        var rule = _this7.resolvedRules[field];

        var isFileField = _this7.isFileField(field);

        if (requiredFields.includes(field)) {
          result[field] = rule;
        } else if (isFileField && !utils.isUndefined(_this7.filesSource[field])) {
          result[field] = rule;
        } else if (!isFileField && !utils.isUndefined(_this7.dataSource[field])) {
          result[field] = rule;
        }

        return result;
      }, {});
    }
    /**
     * performs the conditional if resolution
     * @param conditionalIf
     */

  }, {
    key: "resolveConditionalIf",
    value: function resolveConditionalIf(conditionalIf) {
      var targetField = conditionalIf.field;
      var status = false;
      var filteredValue = this.filterValue(utils.pickValue(targetField, this.dataSource, ''), targetField);

      switch (conditionalIf.if) {
        case 'checked':
          status = !!filteredValue;
          break;

        case 'notChecked':
          status = !filteredValue;
          break;

        case 'equals':
          status = filteredValue === conditionalIf.value;
          break;

        case 'notEquals':
          status = filteredValue !== conditionalIf.value;
          break;

        case 'in':
          status = utils.makeArray(filteredValue).includes(conditionalIf.value);
          break;

        case 'notIn':
          status = !utils.makeArray(filteredValue).includes(conditionalIf.value);
          break;
      }

      return status;
    }
    /**
     * resolves all requiredIf conditional rule
     */

  }, {
    key: "resolveRequiredIf",
    value: function resolveRequiredIf() {
      for (var _i5 = 0, _Object$entries2 = Object.entries(this.resolvedRules); _i5 < _Object$entries2.length; _i5++) {
        var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i5], 2),
            _field5 = _Object$entries2$_i[0],
            rule = _Object$entries2$_i[1];

        var requiredIf = rule.requiredIf;

        if (utils.isObject(requiredIf)) {
          rule.required = false;

          if (this.resolveConditionalIf(requiredIf)) {
            rule.required = true;
          } else if (requiredIf.drop !== false) {
            if (this.isFileField(_field5)) {
              delete this.filesSource[_field5];
            } else {
              this.dataSource[_field5] = '';
            }
          }
        }
      }
    }
    /**
     * resolves the rule and returns the result
     */

  }, {
    key: "resolveRule",
    value: function resolveRule(field, rule) {
      var _this8 = this;

      if (utils.isString(rule)) {
        rule = {
          type: rule
        };
      }

      var result = {
        type: utils.pickValue('type', rule, 'text'),
        required: utils.pickValue('required', rule, true),
        array: utils.pickValue('array', rule, inflection.pluralize(field) === field),
        defaultValue: utils.pickValue('defaultValue', rule, ''),
        hint: utils.pickValue('hint', rule, "".concat(field, " is required")),
        requiredIf: utils.pickValue('requiredIf', rule, undefined),
        options: utils.pickObject('options', rule),
        filters: utils.pickObject('filters', rule),
        checks: utils.makeArray(rule.checks),
        postCompute: utils.pickValue('postCompute', rule, undefined),
        postValidate: utils.pickValue('postValidate', rule, undefined)
      }; //enclose the target field

      if (!utils.isUndefined(result.options.shouldMatch)) {
        if (utils.isString(result.options.shouldMatch)) {
          result.options.shouldMatch = {
            target: result.options.shouldMatch
          };
        }

        result.options.shouldMatch.target = "{".concat(result.options.shouldMatch.target, "}");
      }

      if (result.type === 'checkbox' || typeof rule.defaultValue !== 'undefined') {
        result.required = false;
      }

      if (result.type === 'phoneNumber') {
        var postCompute = result.postCompute;
        var options = result.options;

        result.postCompute = function (value, data) {
          var newValue = libphonenumberJs.parsePhoneNumberFromString(value, options.country ? options.country.substring(0, 2).toUpperCase() : undefined).number;

          if (postCompute) {
            return postCompute(newValue, data, _this8);
          } else {
            return newValue;
          }
        };
      }

      return result;
    }
    /**
     * resolves the given rules
     */

  }, {
    key: "resolveRules",
    value: function resolveRules() {
      var _this9 = this;

      var rules = this.rules;
      var fields = Object.keys(rules);
      this.resolvedRules = fields.reduce(function (result, field) {
        var rule = rules[field];
        result[field] = _this9.resolveRule(field, rule);
        return result;
      }, {}); //if there is a file field, and the user did not set the files object, throw

      if (utils.isNull(this.filesSource) && fields.some(function (field) {
        return _this9.isFileField(field);
      })) {
        throw new FilesSourceNotSetException();
      }
    }
    /**
     * checks if the handler should be executed
     */

  }, {
    key: "shouldExecute",
    value: function shouldExecute() {
      if (this.executed) {
        throw new StateException('A handler can only be executed once');
      }

      if (this.dataSource === null) {
        throw new DataSourceNotSetException();
      }

      if (this.rules === null) {
        throw new RulesNotSetException();
      }
    }
    /**
     * sets the data source object if given
     */

  }, {
    key: "setDataSource",
    value: function setDataSource(dataSource) {
      var resolveData = function resolveData(arg) {
        if (utils.isArray(arg)) {
          return arg.map(function (current) {
            return resolveData(current);
          });
        } else {
          return arg.toString();
        }
      };

      if (dataSource) {
        this.dataSource = Object.keys(dataSource).reduce(function (result, key) {
          result[key] = resolveData(dataSource[key]);
          return result;
        }, {});
      }

      return this;
    }
    /**
     * sets the files source object if given
     */

  }, {
    key: "setFilesSource",
    value: function setFilesSource(filesSource) {
      if (filesSource) {
        this.filesSource = filesSource;
      }

      return this;
    }
    /**
     * set rules
     */

  }, {
    key: "setRules",
    value: function setRules(rules) {
      if (rules) {
        this.rules = rules;
      }

      return this;
    }
    /**
     * sets the validator instance to use
     */

  }, {
    key: "setValidator",
    value: function setValidator(validator) {
      validator.setErrorBag(this.errors);
      this.validator = validator;
      return this;
    }
    /**
     * sets the db checker instance to use
     * @param dbChecker
     */

  }, {
    key: "setDBChecker",
    value: function setDBChecker(dbChecker) {
      dbChecker.setErrorBag(this.errors);
      this.dbChecker = dbChecker;
      return this;
    }
    /**
     * sets the instance database model to use
     * @param dbModel
     */

  }, {
    key: "setDBModel",
    value: function setDBModel(dbModel) {
      this.dbModel = dbModel;
    }
    /**
     * sets the instance database model field case style to use
     * @param dbCaseStyle
     */

  }, {
    key: "setDBCaseStyle",
    value: function setDBCaseStyle(dbCaseStyle) {
      this.dbCaseStyle = dbCaseStyle;
    }
    /**
     * sets the error message
     */

  }, {
    key: "setError",
    value: function setError(field, errorMessage) {
      this.errors[field] = errorMessage ? errorMessage : 'error occured';
      return this;
    }
    /**
     *
     * @param field
     * @param value
     */

  }, {
    key: "addField",
    value: function addField(field, value) {
      this.addedFields[field] = value;
      return this;
    }
    /**
     * add extra fields to the data to be validated
     * @param fields object of field value pairs
     */

  }, {
    key: "addFields",
    value: function addFields(fields) {
      var _this10 = this;

      Object.keys(fields).forEach(function (field) {
        _this10.addField(field, fields[field]);
      });
      return this;
    }
    /**
     * asynchronously executes the handler instance
     *
     * @param validateOnDemand boolean value indicating if it should only pick and validate
     * fields that were sent and whose rules are defined. perfect when performing data updates
     * @param requiredFields field or array of fields that must be included when validating on
     * demand, even if they were not sent.
     */

  }, {
    key: "execute",
    value: function () {
      var _execute = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee6() {
        var validateOnDemand,
            requiredFields,
            _args6 = arguments;
        return _regeneratorRuntime.wrap(function _callee6$(_context6) {
          while (1) {
            switch (_context6.prev = _context6.next) {
              case 0:
                validateOnDemand = _args6.length > 0 && _args6[0] !== undefined ? _args6[0] : false;
                requiredFields = _args6.length > 1 && _args6[1] !== undefined ? _args6[1] : [];
                this.shouldExecute();
                this.executed = true; // copy fields

                this.dataSource = utils.copy({}, this.dataSource, this.addedFields);
                this.filesSource = this.filesSource ? utils.copy({}, this.filesSource) : this.filesSource;
                this.resolveRules();
                this.resolveRequiredIf();

                if (validateOnDemand) {
                  this.filterRules(requiredFields);
                }

                this.categorizeRules(); //resolve hints

                this.resolveOptions('hint');

                if (!this.checkMissingFields()) {
                  _context6.next = 30;
                  break;
                }

                this.getFields(this.requiredFields);
                this.getFields(this.optionalFields);

                if (!this.checkArrays()) {
                  _context6.next = 30;
                  break;
                }

                //resolve options.
                this.resolveOptions('options');
                this.resolveOptions('checks');
                _context6.next = 19;
                return this.validateFields(this.requiredFields, true);

              case 19:
                _context6.next = 21;
                return this.validateFields(this.optionalFields, false);

              case 21:
                if (!this.succeeds()) {
                  _context6.next = 27;
                  break;
                }

                this.dbChecker.setDBModel(this.dbModel).setDBCaseStyle(this.dbCaseStyle);
                _context6.next = 25;
                return this.validateDBChecks(this.requiredFields, true);

              case 25:
                _context6.next = 27;
                return this.validateDBChecks(this.optionalFields, false);

              case 27:
                if (!this.succeeds()) {
                  _context6.next = 30;
                  break;
                }

                _context6.next = 30;
                return this.runPostProcesses();

              case 30:
                return _context6.abrupt("return", this.succeeds());

              case 31:
              case "end":
                return _context6.stop();
            }
          }
        }, _callee6, this);
      }));

      function execute() {
        return _execute.apply(this, arguments);
      }

      return execute;
    }()
    /**
     * returns true if the handler has been executed and no errors were found
     */

  }, {
    key: "succeeds",
    value: function succeeds() {
      return this.executed && Object.keys(this.errors).length === 0;
    }
    /**
     * return true if handler has not been executed or if it has been executed but there are
     * some errors found
     */

  }, {
    key: "fails",
    value: function fails() {
      return !this.succeeds();
    }
    /**
     * returns the resolved rule, only useful for testing
     */

  }, {
    key: "getResolvedRules",
    value: function getResolvedRules() {
      return this.resolvedRules;
    }
    /**
     * gets the instance current database model in use
     */

  }, {
    key: "getDBModel",
    value: function getDBModel() {
      return this.dbModel;
    }
    /**
     * gets the instance current database model field case style in use
     */

  }, {
    key: "getDBCaseStyle",
    value: function getDBCaseStyle() {
      return this.dbCaseStyle;
    }
    /**
     * creates and returns a model instance, that can be exported
     */

  }, {
    key: "model",
    value: function model() {
      return new Model(this);
    }
    /**
     * sets custom data by the given name
     * @param name custom data name
     * @param value custom data value
     */

  }, {
    key: "setCustomData",
    value: function setCustomData(name, value) {
      this.customData[name] = value;
      return this;
    }
    /**
     * gets an already set custom data
     * @param name custom data name
     */

  }, {
    key: "getCustomData",
    value: function getCustomData(name) {
      return this.customData[name];
    }
  }]);

  return Handler;
}();

_defineProperty(Handler, "DB_MODELS", Constants.DB_MODELS);

_defineProperty(Handler, "DB_MODEL_CASE_STYLES", utils.CASE_STYLES);

module.exports = Handler;
//# sourceMappingURL=Handler.js.map
