"use strict";

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

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

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

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

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 _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

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

var _ty = require("../ty/ty.js");

var _prototypes2 = require("../ty/prototypes.js");

var _type2 = require("../ty/type.js");

var _dict = require("../ty/dict.js");

var _list = require("../ty/list.js");

var _tuple = require("../ty/tuple.js");

var _enum = require("../ty/enum.js");

var _range = require("../ty/range.js");

var _mapping = require("../ty/mapping.js");

var _selfRef = require("../ty/self-ref.js");

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

var _rules = require("../ty/rules.js");

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }

var RULES = {
  '&': _rules.nonable,
  '?': _rules.ifexist,
  '=': _rules.equal,
  '!': _rules.shouldnotmatch
};

var Parser = /*#__PURE__*/function () {
  function Parser(types) {
    (0, _classCallCheck2.default)(this, Parser);
    this.init(types);
  }

  (0, _createClass2.default)(Parser, [{
    key: "init",
    value: function init() {
      var types = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      this.types = _objectSpread(_objectSpread({}, Parser.defaultTypes), types);
    }
  }, {
    key: "define",
    value: function define(text, target) {
      this.types[text] = target;
      return this;
    }
    /**
     * description -> type
     * @param {*} description should must be an object
     * {
     *   __def__: [
     *     {
     *       name: 'book',
     *       def: { name: 'string', price: 'float' },
     *     },
     *   ],
     *   name: 'string',
     *   age: 'number',
     *   has_football: '?boolean', // ifexist
     *   sex: 'F|M', // enum
     *   dot: '=xxxxx', // equal
     *   '#belong': 'comments of `belong`',
     *   belong: '?=animal', // ifexist equal
     *   vioce: '!number', // should not match
     *   num: 'string,numeric', // match multiple
     *   '#parents': [
     *     'first line comment',
     *     'second line comment',
     *   ],
     *   parents: '[string,string]', // tuple
     *   books: 'book[]', // list, use defined 'book', recommended
     *   books2: ['book'], // list
     *   '#books3[0]': 'comment of first child of books3',
     *   books3: ['book1', 'book2'], // = 'book1[]|book2[]'
     *   '#body.neck': 'comment for body.neck',
     *   body: {
     *     head: 'boolean',
     *     '#neck': 'comment of neck',
     *     neck: 'boolean',
     *   },
     * }
     */

  }, {
    key: "parse",
    value: function parse(description) {
      var _this = this;

      var types = this.types;

      var parse = function parse(text) {
        var exp = [];

        var checkRule = function checkRule() {
          var firstChar = text.charAt(0);

          if (RULES[firstChar]) {
            exp.push(RULES[firstChar]);
            text = text.substr(1);
            checkRule();
          }
        };

        checkRule(); // prepare for '(a,b)' and '[a|b]'

        text = text.replace(/\[.*\]/g, function (item) {
          return item.replace(/\|/g, ':@:');
        });
        text = text.replace(/\(.*\)/g, function (item) {
          return item.replace(/,/g, ':@:');
        });
        var items = text.split(',').map(function (word) {
          var words = word.split('|').map(function (item) {
            // tuple (a,b)
            if (item.charAt(0) === '(' && item.substr(-1) === ')') {
              item = item.substr(1, item.length - 2);
              var texts = item.split(':@:');
              var prototypes = texts.map(function (item) {
                return parse(item);
              });
              return new _tuple.Tupl(prototypes);
            } // list [a|b]
            else if (item.charAt(0) === '[' && item.substr(-1) === ']') {
              item = item.substr(1, item.length - 2); // empty list []

              if (!item) {
                return Array;
              }

              var _texts = item.split(':@:');

              var _prototypes = _texts.map(function (item) {
                return parse(item);
              });

              return new _list.List(_prototypes);
            } // list a[]
            else if (item.substr(-2) === '[]') {
              item = item.substr(0, item.length - 2);

              var _type = types[item] || item;

              return _type ? new _list.List([_type]) : Array;
            } // range 10<-20
            else if (/^-?\d+<?->?-?\d+$/.test(item)) {
              var _item$match = item.match(/^(-?\d+)<?->?(-?\d+)$/),
                  _item$match2 = (0, _slicedToArray2.default)(_item$match, 3),
                  _ = _item$match2[0],
                  minStr = _item$match2[1],
                  maxStr = _item$match2[2];

              var min = +minStr;
              var max = +maxStr;
              var minBound = item.indexOf('<-') > 0;
              var maxBound = item.indexOf('->') > 0;
              return new _range.Range({
                min: min,
                max: max,
                minBound: minBound,
                maxBound: maxBound
              });
            } // mapping {string:array}
            else if (/^\{\w+?:\w+?\}$/.test(item)) {
              var _item$split$filter = item.split(/\{:\}/).filter(function (item) {
                return !!item;
              }),
                  _item$split$filter2 = (0, _slicedToArray2.default)(_item$split$filter, 2),
                  k = _item$split$filter2[0],
                  v = _item$split$filter2[1];

              var kp = types[k] || k;
              var vp = types[v] || v;
              var t = new _mapping.Mapping([kp, vp]);
              return t;
            } // other
            else if (types[item]) {
              return types[item];
            } // normal
            else {
              try {
                return JSON.parse(item);
              } catch (e) {
                return item;
              }
            }
          });
          return words.length > 1 ? new _enum.Enum(words) : words[0];
        });
        var type = items.length > 1 ? (0, _rules.match)(items) : items[0];
        exp.reverse();
        var pattern = type;
        exp.forEach(function (rule) {
          pattern = rule(pattern);
        });
        return pattern;
      };

      var target = (0, _tsFns.isObject)(description) ? _objectSpread({}, description) : (0, _tsFns.isArray)(description) ? (0, _toConsumableArray2.default)(description) : description;
      var parser = this;

      var __def__;

      if ((0, _tsFns.isObject)(target)) {
        __def__ = target.__def__;
        delete target.__def__;
      }

      if (__def__) {
        __def__.forEach(function (_ref) {
          var name = _ref.name,
              def = _ref.def,
              origin = _ref.origin;

          var hasSelf = function hasSelf(target) {
            if ((0, _tsFns.isObject)(target)) {
              var keys = Object.keys(target);

              for (var i = 0, len = keys.length; i < len; i++) {
                var key = keys[i];
                var value = target[key];

                if (hasSelf(value)) {
                  return true;
                }
              }

              return false;
            } else if ((0, _tsFns.isArray)(target)) {
              for (var _i = 0, _len = target.length; _i < _len; _i++) {
                var _value = target[_i];

                if (hasSelf(_value)) {
                  return true;
                }
              }

              return false;
            } else if ((0, _tsFns.isString)(target)) {
              return target.indexOf('__self__') > -1;
            } else {
              return false;
            }
          };

          var type = null;

          if (origin) {
            type = def;
          } else if (hasSelf(def)) {
            type = new _selfRef.SelfRef(function (type) {
              var parser = new Parser(_objectSpread(_objectSpread({}, _this.types), {}, {
                __self__: type
              }));
              var t = parser.parse(def);
              return t;
            });
          } else {
            type = parser.parse(def);
          }

          types = _objectSpread(_objectSpread({}, types), {}, (0, _defineProperty2.default)({}, name, type));
          parser = new Parser(types);
        });
      }

      var comments = {};

      var build = function build(value, key) {
        if (key && key.indexOf('#') === 0) {
          comments[key.substr(1)] = value;
        } else if ((0, _tsFns.isObject)(value)) {
          var subtype = parser.parse(value);
          var subcomments = subtype.__comments__;

          if (key && subcomments) {
            (0, _tsFns.each)(subcomments, function (m, k) {
              comments[key + '.' + k] = m;
            });
          }

          return subtype;
        } else if ((0, _tsFns.isArray)(value)) {
          return new _list.List(value.map(function (item, i) {
            var subtype = build(item);

            if ((0, _typeof2.default)(subtype) !== 'object') {
              return subtype;
            }

            var subcomments = subtype.__comments__;

            if (key && subcomments) {
              (0, _tsFns.each)(subcomments, function (m, k) {
                comments[key + '[' + i + ']' + '.' + k] = m;
              });
            }

            return subtype;
          }));
        } else if ((0, _tsFns.isString)(value)) {
          return parse(value);
        } else {
          return value;
        }
      };

      var pattern = (0, _tsFns.decideby)(function () {
        if ((0, _tsFns.isObject)(target)) {
          var res = {};

          var customRules = _objectSpread(_objectSpread({}, RULES), {}, {
            '|': _enum.enumerate,
            '*': _tuple.tupl
          });

          (0, _tsFns.each)(target, function (value, key) {
            if (key.indexOf('#') === 0) {
              comments[key.substr(1)] = value;
              return;
            }

            var chars = key.split('');
            var use = [];

            for (var i = chars.length - 1; i > 0; i--) {
              var char = chars[i];

              if (!customRules[char]) {
                break;
              }

              use.push(chars.pop());
            }

            var prop = chars.join('');
            var type = (0, _tsFns.decideby)(function () {
              // deal with |,: type should be an array
              if (use.indexOf('|') > -1 || use.indexOf('*') > -1) {
                if (!(0, _tsFns.isArray)(value)) {
                  throw new Error("".concat(key, " should be an array, but receive ").concat((0, _typeof2.default)(value)));
                }

                var items = value.map(function (item, i) {
                  var subtype = build(item);

                  if ((0, _typeof2.default)(subtype) !== 'object') {
                    return subtype;
                  }

                  var subcomments = subtype.__comments__;

                  if (subcomments) {
                    (0, _tsFns.each)(subcomments, function (m, k) {
                      comments[prop + '[' + i + ']' + '.' + k] = m;
                    });
                  }

                  return subtype;
                });
                return items;
              }

              return build(value, prop);
            });
            var t = use.length ? use.reverse().map(function (char) {
              return customRules[char];
            }).reduce(function (t, fn) {
              return fn(t);
            }, type) : type;
            res[prop] = t;
          });
          return res;
        }

        return build(target);
      });

      var type = _ty.Ty.create(pattern);

      type.__comments__ = comments;
      return type;
    }
    /**
     * type -> description
     * @param {object} dict
     * @param {object} options
     * @param {number} [options.arrayStyle] 1: "[string|number]" 2: ["string", "number"] default: "string[]|number[]"
     * @param {number} [options.ruleStyle] 1: { "name?": "string" } default: { "name": "?string" }
     * @returns
     */

  }, {
    key: "describe",
    value: function describe(dict) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      var __def__ = [];
      var arrayStyle = options.arrayStyle,
          ruleStyle = options.ruleStyle;
      var types = Object.entries(this.types);

      var getProto = function getProto(value) {
        var type = types.find(function (item) {
          return item[1] === value;
        });

        if (type) {
          return type[0];
        }

        if ((0, _tsFns.isString)(value)) {
          return value;
        }
      };

      var define = function define(v) {
        var origin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

        if (v && (0, _typeof2.default)(v) === 'object') {
          var existing = __def__.find(function (item) {
            return (0, _tsFns.isEqual)(item.def, v);
          });

          if (existing) {
            return existing.name;
          }

          var i = __def__.length + 1;
          var name = '$' + i;
          var def = {
            name: name,
            def: v
          };

          if (origin) {
            def.origin = true;
          }

          __def__.push(def);

          return name;
        } else {
          return v;
        }
      };

      var buildList = function buildList(items) {
        var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
        return type === 2 ? items.map(function (item) {
          return define(item) + '[]';
        }).join('|') : type === 1 ? '[' + items.map(function (item) {
          return define(item);
        }).join('|') + ']' : items.map(function (item) {
          return build(item);
        });
      };

      var create = function create(value) {
        var rules = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
        var proto = getProto(value);

        if (proto) {
          return proto;
        }

        var sign = value;

        if ((0, _tsFns.isInstanceOf)(value, _dict.Dict)) {
          sign = build(value.pattern);
        } else if ((0, _tsFns.isInstanceOf)(value, _tuple.Tupl)) {
          var pattern = value.pattern;
          var items = pattern.map(build);

          if (ruleStyle) {
            rules.push('*');
            sign = items;
          } else {
            sign = '(' + items.map(function (item) {
              return define(item);
            }).join(',') + ')';
          }
        } else if ((0, _tsFns.isInstanceOf)(value, _list.List)) {
          var _pattern = value.pattern;

          var _items = _pattern.map(build);

          if (ruleStyle) {
            rules.push('|');
            sign = _items;
          } else {
            sign = buildList(_items, arrayStyle);
          }
        } else if ((0, _tsFns.isInstanceOf)(value, _enum.Enum)) {
          var _pattern2 = value.pattern;

          var _items2 = _pattern2.map(build);

          var desc = _items2.join('|');

          sign = desc;
        } else if ((0, _tsFns.isInstanceOf)(value, _range.Range)) {
          var _pattern3 = value.pattern;
          var min = _pattern3.min,
              max = _pattern3.max,
              minBound = _pattern3.minBound,
              maxBound = _pattern3.maxBound;

          var _desc = "".concat(min).concat(minBound ? '<' : '', "-").concat(maxBound ? '>' : '').concat(max);

          sign = _desc;
        } else if ((0, _tsFns.isInstanceOf)(value, _mapping.Mapping)) {
          var _pattern4 = value.pattern;

          var _pattern5 = (0, _slicedToArray2.default)(_pattern4, 2),
              k = _pattern5[0],
              v = _pattern5[1];

          var kp = build(k);
          var vp = build(v);

          var _desc2 = "{".concat(kp, ":").concat(vp, "}");

          sign = _desc2;
        } else if ((0, _tsFns.isInheritedOf)(value, _selfRef.SelfRef)) {
          var type = value.fn('__self__');
          sign = create(type);
        } else if ((0, _tsFns.isInstanceOf)(value, _rule.Rule)) {
          var name = value.name,
              _pattern6 = value.pattern;

          if (name === 'ifexist') {
            if (ruleStyle) {
              rules.push('?');
              sign = create(_pattern6, rules);
            } else {
              var inner = create(_pattern6);
              sign = '?' + define(inner);
            }
          } else if (name === 'equal') {
            if (ruleStyle) {
              rules.push('=');
              sign = create(_pattern6, rules);
            } else {
              var _inner = create(_pattern6);

              sign = '=' + define(_inner, true);
            }
          } else if (name === 'shouldnotmatch') {
            if (ruleStyle) {
              rules.push('!');
              sign = create(_pattern6, rules);
            } else {
              var _inner2 = create(_pattern6);

              sign = '!' + define(_inner2);
            }
          } else if (name === 'nullable' || name === 'nonable') {
            if (ruleStyle) {
              rules.push('*');
              sign = create(_pattern6, rules);
            } else {
              var _inner3 = create(_pattern6);

              sign = '*' + define(_inner3);
            }
          } else if (name === 'match') {
            var _items3 = build(_pattern6);

            sign = _items3.join(',');
          } else if (name === 'asynchronous') {
            sign = create(_pattern6);
          } else if (name === 'shouldmatch') {
            sign = create(_pattern6);
          } else if (name === 'ifnotmatch') {
            sign = create(_pattern6);
          } else if (name === 'shouldexist') {
            var _inner4 = create(_pattern6);

            sign = '?' + define(_inner4);
          } else if (name === 'shouldnotexist') {
            var _inner5 = create(_pattern6);

            sign = '?' + define(_inner5);
          } else if (name === 'beof') {
            sign = create(_pattern6);
          } else if (name === 'lambda') {
            sign = 'function';
          } else {
            var _items4 = build(_pattern6);

            sign = (0, _tsFns.isArray)(_items4) ? _items4.join('|') : _items4;
          }
        } else if ((0, _tsFns.isObject)(value)) {
          sign = build(value);
        } else if ((0, _tsFns.isArray)(value)) {
          var _items5 = build(value);

          var _desc3 = buildList(_items5);

          sign = _desc3;
        }

        return sign;
      };

      var build = function build(type) {
        var proto = getProto(type);

        if (proto) {
          return proto;
        }

        var pattern = (0, _tsFns.isInstanceOf)(type, _type2.Type) || (0, _tsFns.isInstanceOf)(type, _rule.Rule) ? type.pattern : type;
        var desc = (0, _tsFns.isArray)(pattern) ? [] : {};
        (0, _tsFns.each)(pattern, function (value, key) {
          var rules = [];
          var sign = create(value, rules);
          var symb = rules.join('');
          desc[key + symb] = sign;
        });
        return desc;
      };

      var description = build(dict);

      if (__def__.length) {
        return _objectSpread({
          __def__: __def__
        }, description);
      } else {
        return description;
      }
    }
  }, {
    key: "guess",
    value: function guess(data) {
      var _this2 = this;

      var res = (0, _tsFns.map)(data, function (value) {
        return _this2.getType(value);
      });
      return res;
    }
  }, {
    key: "merge",
    value: function merge(exist, data) {
      var _this3 = this;

      var res = {};
      var checkedKeys = {};
      var existKeys = Object.keys(exist);

      var findKey = function findKey(key) {
        var existKey = existKeys.find(function (item) {
          return new RegExp("^".concat((0, _tsFns.createSafeExp)(key), "[?!&=|*]*$")).test(item);
        });
        return existKey;
      };

      (0, _tsFns.each)(data, function (value, key) {
        var prevKey = findKey(key);
        var hasKey = !!prevKey;

        var next = _this3.getType(value);

        if (hasKey) {
          checkedKeys[prevKey] = 1; // TODO 基于权重决定是应该替换部分，还是整体作为enum
          // TODO list -> tuple

          var prev = exist[prevKey];
          var prevRules = prevKey.replace(key, '');
          var isEnum = prevRules.indexOf('|') > -1;

          if (isEnum && !(0, _tsFns.isArray)(prev)) {
            throw new Error("".concat(prevKey, " in previous type description should be an array, but receive ").concat((0, _typeof2.default)(prev)));
          }

          var prevItems = isEnum ? prev : [].concat(prev);
          var inPrev = prevItems.some(function (item) {
            return (0, _tsFns.isEqual)(item, next);
          });

          if (inPrev) {
            res[prevKey] = prev;
            return;
          }

          if (next === 'null' || next === 'undefined') {
            var isNonable = prevKey.indexOf('&') > -1;

            var _nextKey = isNonable ? prevKey : prevKey + '&';

            res[_nextKey] = prev;
            return;
          }

          var nextKey = isEnum ? prevKey : prevKey + '|';
          prevItems.push(next);
          res[nextKey] = prevItems;
        } else {
          checkedKeys[key] = 1;
          res[key] = next;
        }
      });
      (0, _tsFns.each)(exist, function (value, key) {
        if (checkedKeys[key]) {
          return;
        }

        var isOptional = key.indexOf('?') > -1;
        var nextKey = isOptional ? nextKey : key + '?';
        res[nextKey] = value;
      });
      return res;
    }
  }, {
    key: "getType",
    value: function getType(value) {
      var _this4 = this;

      if ((0, _tsFns.isObject)(value)) {
        return this.guess(value);
      } else if ((0, _tsFns.isArray)(value)) {
        return value.map(function (item) {
          return _this4.guess(item);
        });
      } else if ((0, _tsFns.isNull)(value)) {
        return 'null';
      } else if ((0, _tsFns.isUndefined)(value)) {
        return 'undefined';
      } else if ((0, _tsFns.isNaN)(value)) {
        return 'nan';
      } else if ((0, _tsFns.isNumeric)(value)) {
        return 'numeric';
      } else {
        return (0, _typeof2.default)(value);
      }
    }
  }]);
  return Parser;
}();

exports.Parser = Parser;
(0, _defineProperty2.default)(Parser, "defaultTypes", {
  string: String,
  string8: _prototypes2.String8,
  string16: _prototypes2.String16,
  string32: _prototypes2.String32,
  string64: _prototypes2.String64,
  string128: _prototypes2.String128,
  number: Number,
  boolean: Boolean,
  null: _prototypes2.Null,
  undefined: _prototypes2.Undefined,
  none: _prototypes2.None,
  symbol: Symbol,
  function: Function,
  array: Array,
  object: Object,
  numeric: _prototypes2.Numeric,
  int: _prototypes2.Int,
  float: _prototypes2.Float,
  negative: _prototypes2.Negative,
  positive: _prototypes2.Positive,
  zero: _prototypes2.Zero,
  natural: _prototypes2.Natural,
  any: _prototypes2.Any,
  nan: NaN,
  infinity: Infinity,
  finity: _prototypes2.Finity,
  date: Date,
  promise: Promise,
  error: Error,
  regexp: RegExp
});