"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Type = void 0;

var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _tsFns = require("ts-fns");

var _prototype = require("./prototype.js");

var _rule = require("./rule.js");

var _tyError = require("./ty-error.js");

var Type = /*#__PURE__*/function () {
  /**
   * create a Type instance
   * @param  {Any} pattern should be native prototypes or a Rule instance, i.e. String, Number, Boolean... Null, Any, Float...
   */
  function Type(pattern) {
    (0, _classCallCheck2.default)(this, Type);
    this.isStrict = false;
    this.isLoose = false;
    this.pattern = pattern;
    this.name = 'Type';
    this.$msg = null;
  }
  /**
   * validate whether the argument match the pattern
   * @param {*} value
   * @param {*} pattern
   */


  (0, _createClass2.default)(Type, [{
    key: "validate",
    value: function validate() {
      var _this = this;

      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }

      var _decideby = (0, _tsFns.decideby)(function () {
        if (args.length === 1) {
          return [args[0], _this.pattern];
        } else {
          return args;
        }
      }),
          _decideby2 = (0, _slicedToArray2.default)(_decideby, 2),
          value = _decideby2[0],
          pattern = _decideby2[1];

      var tyerr = new _tyError.TyError(); // check array

      if ((0, _tsFns.isArray)(pattern)) {
        if (!(0, _tsFns.isArray)(value)) {
          tyerr.replace({
            type: 'exception',
            value: value,
            name: 'List',
            pattern: pattern
          });
        } // can be empty array
        else if (value.length) {
          var patterns = pattern;
          var values = value;
          var count = values.length;

          var enumerate = function enumerate(value, patterns) {
            var errors = [];

            for (var i = 0, len = patterns.length; i < len; i++) {
              var _pattern = patterns[i]; // nested Type

              if ((0, _tsFns.isInstanceOf)(_pattern, Type)) {
                if (_this.isStrict && !_pattern.isStrict) {
                  _pattern = _pattern.strict;
                } else if (!_this.isStrict && _this.isLoose && !_pattern.isStrict && _pattern.isLoose) {
                  _pattern = _pattern.loose;
                }

                var error = _pattern.catch(value);

                if (!error) {
                  return [];
                } else {
                  errors.push(error);
                }
              } // normal validate
              else {
                var _error = _this.validate(value, _pattern);

                if (!_error) {
                  return [];
                } else {
                  errors.push(_error);
                }
              }
            }

            return errors;
          };

          for (var i = 0; i < count; i++) {
            var _value = values[i];
            var errors = enumerate(_value, patterns);

            if (errors.length) {
              tyerr.add({
                type: 'notin',
                value: _value,
                name: this.name,
                pattern: pattern,
                errors: errors,
                index: i
              });
            }
          }
        }
      } // check object
      else if ((0, _tsFns.isObject)(pattern)) {
        if (!(0, _tsFns.isObject)(value)) {
          tyerr.replace({
            type: 'exception',
            value: value,
            name: 'Dict',
            pattern: pattern
          });
        } else {
          var _patterns = pattern;
          var data = value;
          var patternKeys = Object.keys(_patterns);
          var dataKeys = Object.keys(data); // in strict mode, keys should absolutely equal
          // properties should be absolutely same

          if (this.isStrict) {
            for (var _i = 0, len = dataKeys.length; _i < len; _i++) {
              var key = dataKeys[_i];

              if (!(0, _tsFns.inArray)(key, patternKeys)) {
                tyerr.add({
                  type: 'overflow',
                  key: key
                });
              }
            }
          }

          for (var _i2 = 0, _len2 = patternKeys.length; _i2 < _len2; _i2++) {
            var _key2 = patternKeys[_i2]; // skip in loose mode when has no property

            if (!this.isStrict && this.isLoose && !(0, _tsFns.hasOwnKey)(data, _key2)) {
              continue;
            }

            var _value2 = data[_key2];
            var _pattern2 = _patterns[_key2];
            var isRule = (0, _tsFns.isInstanceOf)(_pattern2, _rule.Rule);

            if (isRule) {
              var rule = this.isStrict && !_pattern2.isStrict ? _pattern2.strict : !this.isStrict && this.isLoose && !_pattern2.isStrict && _pattern2.isLoose ? _pattern2.loose : _pattern2;
              var error = rule.catch(data, _key2);

              if (!error) {
                continue;
              } // after validate, the property may create by rule


              if (!(0, _tsFns.inObject)(_key2, data)) {
                tyerr.add({
                  type: 'missing',
                  key: _key2,
                  value: _value2,
                  pattern: _pattern2
                });
              } else {
                tyerr.add({
                  error: error,
                  key: _key2
                });
              }
            } // not found some key in data
            // i.e. should be { name: String, age: Number } but give { name: 'tomy' }, 'age' is missing
            else if (!(0, _tsFns.inObject)(_key2, data)) {
              tyerr.add({
                type: 'missing',
                key: _key2,
                value: _value2,
                pattern: _pattern2
              });
            } // nested Type
            else if ((0, _tsFns.isInstanceOf)(_pattern2, Type)) {
              if (this.isStrict && !_pattern2.isStrict) {
                _pattern2 = _pattern2.strict;
              } else if (!this.isStrict && this.isLoose && !_pattern2.isStrict && _pattern2.isLoose) {
                _pattern2 = _pattern2.loose;
              }

              var _error2 = _pattern2.catch(_value2);

              if (_error2) {
                tyerr.add({
                  error: _error2,
                  key: _key2
                });
              }
            } // normal validate
            else {
              var _error3 = this.validate(_value2, _pattern2);

              if (_error3) {
                tyerr.add({
                  error: _error3,
                  key: _key2
                });
              }
            }
          }
        }
      } // fix: new Mapping({ key: String, value: new Dict(...) })
      else if ((0, _tsFns.isInstanceOf)(pattern, Type)) {
        if (this.isStrict && !pattern.isStrict) {
          pattern = pattern.strict;
        } else if (!this.isStrict && this.isLoose && !pattern.isStrict && pattern.isLoose) {
          pattern = pattern.loose;
        }

        var _error4 = pattern.catch(value);

        if (_error4) {
          tyerr.add({
            error: _error4,
            value: value,
            name: pattern.name,
            pattern: pattern
          });
        }
      } // check prototypes
      else if (_prototype.Prototype.is(pattern).existing()) {
        var res = _prototype.Prototype.is(pattern).typeof(value);

        if (res !== true) {
          tyerr.replace({
            type: 'exception',
            value: value,
            pattern: pattern
          });
        }
      } // check single value
      else if (!_prototype.Prototype.is(pattern).equal(value)) {
        tyerr.replace({
          type: 'exception',
          value: value,
          name: 'equal',
          pattern: pattern
        });
      }

      tyerr.commit();
      return tyerr.error();
    }
  }, {
    key: "_decide",
    value: function _decide(value) {
      var pattern = this.pattern;
      var error = this.validate(value, pattern);
      return error;
    }
  }, {
    key: "catch",
    value: function _catch(value) {
      var error = this._decide(value);

      if (error && this.$msg) {
        error.translate(this.$msg.message, this.$msg.prefix, this.$msg.suffix);
      }

      return error;
    }
  }, {
    key: "assert",
    value: function assert(value) {
      var error = this.catch(value);

      if (error) {
        throw error;
      }
    }
  }, {
    key: "test",
    value: function test(value) {
      var error = this.catch(value);
      return !error;
    }
    /**
     * track value with type sync
     * @param {*} value
     */

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

      return new Promise(function (resolve, reject) {
        var error = _this2.catch(value);

        if (error) {
          reject(error);
        } else {
          resolve(null);
        }
      });
    }
    /**
     * track value with type async
     * @param {*} value
     */

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

      return new Promise(function (resolve, reject) {
        Promise.resolve().then(function () {
          var error = _this3.catch(value);

          if (error) {
            reject(error);
          } else {
            resolve(null);
          }
        });
      });
    }
  }, {
    key: "clone",
    value: function clone() {
      var Constructor = (0, _tsFns.getConstructorOf)(this);
      var ins = new Constructor(this.pattern);
      ins.isStrict = this.isStrict;
      ins.isLoose = this.isStrict ? false : this.isLoose;
      ins.name = this.name;
      ins.$msg = this.$msg;
      return ins;
    }
  }, {
    key: "toBeStrict",
    value: function toBeStrict() {
      var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
      this.isStrict = !!mode;

      if (mode) {
        this.isLoose = false;
      }

      return this;
    }
  }, {
    key: "strict",
    get: function get() {
      var ins = this.clone().toBeStrict();
      return ins;
    }
  }, {
    key: "Strict",
    get: function get() {
      return this.strict;
    }
  }, {
    key: "toBeLoose",
    value: function toBeLoose() {
      var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;

      if (this.isStrict) {
        console.error('TySheMo: strict Type can not change to be loose.');
        return this;
      }

      this.isLoose = !!mode;
      return this;
    }
  }, {
    key: "loose",
    get: function get() {
      var ins = this.clone().toBeLoose();
      return ins;
    }
  }, {
    key: "Loose",
    get: function get() {
      return this.loose;
    }
  }, {
    key: "with",
    value: function _with(_ref) {
      var name = _ref.name,
          strict = _ref.strict,
          message = _ref.message,
          prefix = _ref.prefix,
          suffix = _ref.suffix;
      this.$msg = message || prefix || suffix ? Object.assign({}, {
        message: message,
        prefix: prefix,
        suffix: suffix
      }) : null;

      if ((0, _tsFns.isString)(name)) {
        this.name = name;
      }

      if (!(0, _tsFns.isUndefined)(strict)) {
        this.isStrict = !!strict;
      }

      return this;
    } // use name when convert to string

  }, {
    key: "toString",
    value: function toString() {
      return this.name;
    }
  }]);
  return Type;
}();

exports.Type = Type;