'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 FieldRuleNotFoundException = _interopDefault(require('./Exceptions/FieldRuleNotFoundException'));
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 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',
      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);
    }
    /**
     * 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);

              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);

              case 11:
                result = _context.sent;

                if (utils.isString(result)) {
                  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 = 23;
                  break;
                }

                check = _step.value;

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

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

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

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

              case 13:
                _context2.next = 20;
                break;

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

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

                return _context2.abrupt("break", 23);

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

              case 23:
                _context2.next = 29;
                break;

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

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

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

              case 32:
                _context2.prev = 32;

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

                throw _iteratorError;

              case 35:
                return _context2.finish(32);

              case 36:
                return _context2.finish(29);

              case 37:
              case "end":
                return _context2.stop();
            }
          }
        }, _callee2, this, [[3, 25, 29, 37], [30,, 32, 36]]);
      }));

      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;

        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 = 24;
                  break;
                }

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

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

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

              case 12:
                _context3.t0 = ++i < len;

                if (!_context3.t0) {
                  _context3.next = 17;
                  break;
                }

                _context3.next = 16;
                return this.runDBCheck(required, _field2, values[i], checks, i);

              case 16:
                _context3.t0 = _context3.sent;

              case 17:
                if (!_context3.t0) {
                  _context3.next = 21;
                  break;
                }
                _context3.next = 12;
                break;

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

              case 24:
                _context3.next = 30;
                break;

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

              case 30:
                _context3.prev = 30;
                _context3.prev = 31;

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

              case 33:
                _context3.prev = 33;

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

                throw _iteratorError2;

              case 36:
                return _context3.finish(33);

              case 37:
                return _context3.finish(30);

              case 38:
              case "end":
                return _context3.stop();
            }
          }
        }, _callee3, this, [[3, 26, 30, 38], [31,, 33, 37]]);
      }));

      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, newFileName, data;
        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 = 6;
                  break;
                }

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

              case 5:
                if (this.isFileField(field)) {
                  newFileName = validator.getFileName();
                  data = this.data[field];

                  if (utils.isArray(data)) {
                    data.splice(index, 1, newFileName);
                  } else {
                    this.data[field] = newFileName;
                  }
                }

              case 6:
              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, values, len, i;

        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 = 23;
                  break;
                }

                _field3 = _step3.value;
                _this$resolvedRules$_ = this.resolvedRules[_field3], options = _this$resolvedRules$_.options, type = _this$resolvedRules$_.type;
                values = utils.makeArray(this.data[_field3]);
                len = values.length;
                i = -1;

              case 11:
                _context5.t0 = ++i < len;

                if (!_context5.t0) {
                  _context5.next = 16;
                  break;
                }

                _context5.next = 15;
                return this.runValidation(required, _field3, type, values[i].toString(), options, i);

              case 15:
                _context5.t0 = _context5.sent;

              case 16:
                if (!_context5.t0) {
                  _context5.next = 20;
                  break;
                }
                _context5.next = 11;
                break;

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

              case 23:
                _context5.next = 29;
                break;

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

              case 29:
                _context5.prev = 29;
                _context5.prev = 30;

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

              case 32:
                _context5.prev = 32;

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

                throw _iteratorError3;

              case 35:
                return _context5.finish(32);

              case 36:
                return _context5.finish(29);

              case 37:
              case "end":
                return _context5.stop();
            }
          }
        }, _callee5, this, [[3, 25, 29, 37], [30,, 32, 36]]);
      }));

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

      return validateFields;
    }()
    /**
     * retrieves fields' data
     */

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

      fields.forEach(function (field) {
        var filters = _this.resolvedRules[field].filters;
        var defaultValue = _this.resolvedRules[field].defaultValue || '';

        var isFileField = _this.isFileField(field);

        var fieldIsMissing = _this.fieldIsMissing(field);

        var value = '';

        if (isFileField) {
          value = fieldIsMissing ? defaultValue : _this.filesSource[field].name;
        } else {
          value = fieldIsMissing ? defaultValue : _this.dataSource[field];
        }

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

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

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

      var value = this.dataSource[field];

      if (utils.isString(value)) {
        return value === '';
      } else {
        value = value.filter(function (current) {
          return current !== '';
        });
        this.dataSource[field] = value;
        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();
    }
    /**
     * resolves an option
     */

  }, {
    key: "resolveOption",
    value: function resolveOption(field, options) {
      var _this3 = 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 _i2 = 0, _Object$entries = Object.entries(options); _i2 < _Object$entries.length; _i2++) {
          var _Object$entries$_i = _slicedToArray(_Object$entries[_i2], 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 _this3.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 _this4 = this;

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

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

      Object.keys(rules).forEach(function (field) {
        var rule = rules[field];

        if (rule.required) {
          _this5.requiredFields.push(field);
        } else {
          _this5.optionalFields.push(field);
        }
      });
    }
  }, {
    key: "filterRules",
    value: function filterRules(rules, requredFields) {
      var _this6 = this;

      requredFields = utils.makeArray(requredFields);
      return Object.keys(rules).reduce(function (result, field) {
        var rule = rules[field];

        var isFileDataType = _this6.isFileDataType(rule.type);

        if (requredFields.includes(field)) {
          result[field] = rule;
        } else if (isFileDataType && !utils.isUndefined(_this6.filesSource[field])) {
          result[field] = rule;
        } else if (!isFileDataType && !utils.isUndefined(_this6.dataSource[field])) {
          result[field] = rule;
        }

        return result;
      }, {});
    }
    /**
     * 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, type, filters) {
      var _this7 = this;

      var performFilter = function performFilter(value, type, filters) {
        var result = value;

        if (type === 'checkbox' || type === 'boolean') {
          return !_this7.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 = _this7.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();
        } //cast to float


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


        if (filters.toUpper) {
          result = result.toUpperCase();
        } //lower case
        else if (filters.toLower) {
            result = result.toLowerCase();
          } //capitalize
          else if (filters.capitalize) {
              result = result.charAt(0).toUpperCase() + result.substring(1).toLowerCase();
            }

        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 (filters.toArray) {
        value = utils.makeArray(value);
      }

      if (utils.isArray(value)) {
        return utils.uniqueArray(value).map(function (current) {
          return performFilter(current.toString(), type, filters);
        });
      } else {
        return performFilter(value.toString(), type, filters);
      }
    }
    /**
     * performs the conditional if resolution
     * @param conditionalIf
     */

  }, {
    key: "resolveConditionalIf",
    value: function resolveConditionalIf(conditionalIf) {
      var targetField = conditionalIf.field;
      var targetFieldRule = this.resolvedRules[targetField];

      if (utils.isUndefined(targetFieldRule)) {
        throw new FieldRuleNotFoundException(targetField);
      }

      var status = false;

      switch (conditionalIf.if) {
        case 'checked':
          status = !!this.filterValue(utils.pickValue(targetField, this.dataSource, ''), targetFieldRule.type, targetFieldRule.filters);
          break;

        case 'notChecked':
          status = !this.filterValue(utils.pickValue(targetField, this.dataSource, ''), targetFieldRule.type, targetFieldRule.filters);
          break;

        case 'equals':
          status = this.filterValue(utils.pickValue(targetField, this.dataSource, ''), targetFieldRule.type, targetFieldRule.filters) === conditionalIf.value;
          break;

        case 'notEquals':
          status = this.filterValue(utils.pickValue(targetField, this.dataSource, ''), targetFieldRule.type, targetFieldRule.filters) !== conditionalIf.value;
          break;

        case 'in':
          status = utils.makeArray(this.filterValue(utils.pickValue(targetField, this.dataSource, ''), targetFieldRule.type, targetFieldRule.filters)).includes(conditionalIf.value);
          break;

        case 'notIn':
          status = !utils.makeArray(this.filterValue(utils.pickValue(targetField, this.dataSource, ''), targetFieldRule.type, targetFieldRule.filters)).includes(conditionalIf.value);
          break;
      }

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

  }, {
    key: "resolveOverrideIf",
    value: function resolveOverrideIf(rules) {
      for (var _i3 = 0, _Object$entries2 = Object.entries(rules); _i3 < _Object$entries2.length; _i3++) {
        var _Object$entries2$_i = _slicedToArray(_Object$entries2[_i3], 2),
            _field4 = _Object$entries2$_i[0],
            rule = _Object$entries2$_i[1];

        var overrideIf = rule.overrideIf;

        if (utils.isObject(overrideIf)) {
          if (this.resolveConditionalIf(overrideIf)) {
            this.dataSource[_field4] = overrideIf.with;
          }
        }
      }

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

  }, {
    key: "resolveRequiredIf",
    value: function resolveRequiredIf(rules) {
      for (var _i4 = 0, _Object$entries3 = Object.entries(rules); _i4 < _Object$entries3.length; _i4++) {
        var _Object$entries3$_i = _slicedToArray(_Object$entries3[_i4], 2),
            rule = _Object$entries3$_i[1];

        var requiredIf = rule.requiredIf;

        if (utils.isObject(requiredIf)) {
          rule.required = this.resolveConditionalIf(requiredIf);
        }
      }

      return rules;
    }
    /**
     * resolves the rule and returns the result
     */

  }, {
    key: "resolveRule",
    value: function resolveRule(field, rule) {
      if (utils.isString(rule)) {
        rule = {
          type: rule
        };
      }

      var result = {
        type: utils.pickValue('type', rule, 'text'),
        required: utils.pickValue('required', rule, true),
        defaultValue: utils.pickValue('defaultValue', rule, undefined),
        hint: utils.pickValue('hint', rule, "".concat(field, " is required")),
        requiredIf: utils.pickValue('requiredIf', rule, undefined),
        overrideIf: utils.pickValue('overrideIf', 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 it is not enclosed

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

        if (result.options.shouldMatch.target.charAt(0) !== '{') {
          result.options.shouldMatch.target = "{".concat(result.options.shouldMatch.target, "}");
        }
      }

      if (result.type === 'checkbox' || typeof result.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);
          } else {
            return newValue;
          }
        };
      }

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

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

      var fields = Object.keys(rules);
      var result = fields.reduce(function (result, field) {
        var rule = rules[field];
        result[field] = _this8.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 _this8.isFileDataType(result[field].type);
      })) {
        throw new FilesSourceNotSetException();
      }

      return result;
    }
    /**
     * 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) {
        var resolvedDataSource = Object.keys(dataSource).reduce(function (result, key) {
          result[key] = resolveData(dataSource[key]);
          return result;
        }, {});
        this.dataSource = resolvedDataSource;
      }

      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;
      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 _this9 = this;

      Object.keys(fields).forEach(function (field) {
        _this9.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;
                this.dataSource = utils.copy({}, this.dataSource, this.addedFields);
                this.resolvedRules = this.resolveRules(this.rules);
                this.resolvedRules = this.resolveRequiredIf(this.resolvedRules);
                this.resolveOverrideIf(this.resolvedRules);

                if (validateOnDemand) {
                  this.resolvedRules = this.filterRules(this.resolvedRules, requiredFields);
                }

                this.categorizeRules(this.resolvedRules); //resolve hints

                this.resolveOptions('hint');

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

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

              case 13:
                this.getFields(this.requiredFields);
                this.getFields(this.optionalFields); //resolve options.

                this.resolveOptions('options');
                this.resolveOptions('checks');
                this.validator.setFiles(this.filesSource || {});
                _context6.next = 20;
                return this.validateFields(this.requiredFields, true);

              case 20:
                _context6.next = 22;
                return this.validateFields(this.optionalFields, false);

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

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

              case 26:
                _context6.next = 28;
                return this.validateDBChecks(this.optionalFields, false);

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

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

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

              case 32:
              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
