"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.createType = createType;
exports.determine = determine;
exports.equal = equal;
exports.ifexist = ifexist;
exports.ifmatch = ifmatch;
exports.ifnotmatch = ifnotmatch;
exports.instance = instance;
exports.lambda = lambda;
exports.lazy = lazy;
exports.match = match;
exports.nonable = nonable;
exports.nullable = nullable;
exports.shouldexist = shouldexist;
exports.shouldmatch = shouldmatch;
exports.shouldnotexist = shouldnotexist;
exports.shouldnotmatch = shouldnotmatch;

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

var _type = require("./type.js");

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

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

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

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

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

var _prototypes = require("./prototypes.js");

function createType(type, strict, loose) {
  var checker = null;

  if ((0, _tsFns.isInstanceOf)(type, _type.Type)) {
    checker = type.clone();
  } else if ((0, _tsFns.isObject)(type)) {
    checker = new _dict.Dict(type);
  } else if ((0, _tsFns.isArray)(type)) {
    checker = new _list.List(type);
  } else {
    checker = new _type.Type(type);
  }

  if (strict) {
    checker.toBeStrict();
  }

  if (loose) {
    checker.toBeLoose();
  }

  return checker;
}

function createRule(type) {
  if ((0, _tsFns.isInstanceOf)(type, _rule.Rule)) {
    return type;
  } else {
    return createType(type);
  }
}
/**
 * lazy rule
 * @param {Function} fn which can be an async function and should return a pattern
 */


function lazy(fn) {
  var pattern = _prototypes.Any;
  var rule = new _rule.Rule({
    name: 'lazy',
    pattern: pattern,
    use: function use() {
      return pattern;
    }
  });

  var create = function create(res) {
    pattern = createRule(res);
    rule.pattern = pattern;
  };

  var res = fn();

  if ((0, _tsFns.isInstanceOf)(res, Promise)) {
    res.then(create);
  } else {
    create(res);
  }

  return rule;
}
/**
 * the passed value should match all passed patterns
 * @param {Array} patterns
 */


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

  if (args.length === 2) {
    return shouldmatch.apply(void 0, args);
  }

  var patterns = args[0];
  var rule = new _rule.Rule({
    name: 'match',
    pattern: patterns,
    validate: function validate(data, key) {
      for (var i = 0, len = patterns.length; i < len; i++) {
        var pattern = createRule(patterns[i]);
        var error = this.validate(data, key, pattern);

        if (error) {
          return error;
        }
      }

      return true;
    }
  });
  return rule;
}
/**
 * determine which pattern to use.
 * @param {Function} determine a function to receive parent node of current key, and return a pattern
 */


function determine(determine, A, B) {
  var rule = new _rule.Rule({
    name: 'determine',
    pattern: [A, B],
    use: function use(data) {
      var bool = determine(data);
      var choice = bool ? A : B;
      var pattern = createRule(choice);
      return pattern;
    }
  });
  rule.determine = determine;
  return rule;
}
/**
 * Verify a rule by using custom error message
 * @param {Rule|Type|Function} pattern
 * @param {String|Function} message
 */


function shouldmatch(pattern, message) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'shouldmatch',
    pattern: pattern,
    message: message,
    validate: function validate(data, key) {
      var error = this.validate(data, key, type);
      return !error;
    }
  });
  return rule;
}
/**
 * the passed value should not match patterns
 * @param {Pattern} pattern
 */


function shouldnotmatch(pattern, message) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'shouldnotmatch',
    pattern: pattern,
    message: message,
    validate: function validate(data, key) {
      var error = this.validate(data, key, type);
      return !!error;
    }
  });
  return rule;
}
/**
 * If the value exists, use rule to validate.
 * If not exists, ignore this rule.
 * @param {Pattern} pattern
 */


function ifexist(pattern) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'ifexist',
    pattern: pattern,
    shouldcheck: function shouldcheck(data, key) {
      return key in data;
    },
    use: function use() {
      return type;
    }
  });
  return rule;
}
/**
 * If the value not match pattern, use callback as value.
 * Notice, this will modify original data, which may cause error, so be careful.
 * @param {Pattern} pattern
 * @param {Function|Any} callback a function to return new value with origin old value
 */


function ifnotmatch(pattern, callback) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'ifnotmatch',
    pattern: pattern,
    use: function use() {
      return type;
    },
    override: function override(data, key) {
      data[key] = (0, _tsFns.isFunction)(callback) ? callback(data, key) : callback;
    }
  });
  return rule;
}
/**
 * If the value match pattern, use callback as value.
 * @param {*} pattern
 * @param {*} callback
 */


function ifmatch(pattern, callback) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'ifmatch',
    pattern: pattern,
    shouldcheck: function shouldcheck(data, key) {
      return key in data;
    },
    use: function use() {
      return type;
    },
    decorate: function decorate(data, key) {
      data[key] = (0, _tsFns.isFunction)(callback) ? callback(data, key) : callback;
    }
  });
  return rule;
}
/**
 * Advance version of ifexist, determine whether a key need to exist with a determine function.
 * @param {Function} determine the function to return true or false,
 * if true, it means the key should MUST exist and will use the second parameter to check data type,
 * if false:
 *  a) when exist, will use the second parameter to check data type.
 *  b) when not exit, ignore
 * @param {Pattern} pattern when the determine function return true, use this to check data type
 */


function shouldexist(determine, pattern) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'shouldexist',
    pattern: pattern,
    message: 'missing',
    shouldcheck: function shouldcheck(data, key) {
      var bool = determine(data);

      if (bool) {
        return true;
      } else {
        return key in data;
      }
    },
    use: function use() {
      return type;
    }
  });
  return rule;
}
/**
 * Advance version of ifexist, determine whether a key can not exist with a determine function.
 * @param {Function} determine the function to return true or false,
 * if true, it means the key should NOT exists,
 * if false:
 *  a) when exist, will use the second parameter to check data type.
 *  b) when not exit, ignore
 * @param {Pattern} pattern when the determine function return true, use this to check data type
 */


function shouldnotexist(determine, pattern) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'shouldnotexist',
    pattern: pattern,
    shouldcheck: function shouldcheck(data, key) {
      var bool = determine(data);

      if (bool) {
        return true;
      } else {
        return key in data;
      }
    },
    validate: function validate(data, key) {
      var bool = determine(data);

      if (bool) {
        return new Error('overflow');
      }

      var error = this.validate(data, key, type);
      return error;
    }
  });
  return rule;
}
/**
 * Whether the value is an instance of given class
 * @param {Constructor} Cons should be a class constructor
 */


function instance(pattern) {
  var rule = new _rule.Rule({
    name: 'instance',
    pattern: pattern,
    validate: function validate(data, key) {
      var value = data[key];
      return (0, _tsFns.isInstanceOf)(value, pattern, true) ? null : new _tyError.TyError({
        type: 'exception',
        value: value,
        pattern: pattern,
        name: 'instance'
      });
    }
  });
  return rule;
}
/**
 * Whether the value is eqaul to the given value
 * @param {Any} target
 */


function equal(pattern) {
  var rule = new _rule.Rule({
    name: 'equal',
    pattern: pattern,
    validate: function validate(data, key) {
      var value = data[key];
      return (0, _tsFns.isEqual)(value, pattern) ? null : new _tyError.TyError({
        type: 'exception',
        value: value,
        pattern: pattern,
        name: 'equal'
      });
    }
  });
  return rule;
}
/**
 * Can be null, or match the passed pattern
 * @param {*} pattern
 */


function nullable(pattern) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'nullable',
    pattern: pattern,
    shouldcheck: function shouldcheck(data, key) {
      return !(0, _tsFns.isNull)(data[key]);
    },
    use: function use() {
      return type;
    }
  });
  return rule;
}

function nonable(pattern) {
  var type = createRule(pattern);
  var rule = new _rule.Rule({
    name: 'nonable',
    pattern: pattern,
    shouldcheck: function shouldcheck(data, key) {
      return !(0, _tsFns.isNone)(data[key]);
    },
    use: function use() {
      return type;
    }
  });
  return rule;
}
/**
 * Wether the value is a function
 * @param {Tupl} InputType
 * @param {Any} OutputType
 */


function lambda(_InputType, _OutputType) {
  var InputType = (0, _tsFns.isArray)(_InputType) ? new _tuple.Tupl(_InputType) : _InputType;

  if (!(0, _tsFns.isInstanceOf)(InputType, _tuple.Tupl)) {
    throw new Error('lambda InputType should be a Tupl');
  }

  var OutputType = !(0, _tsFns.isInstanceOf)(_OutputType, _type.Type) ? createType(_OutputType) : _OutputType;
  var rule = new _rule.Rule({
    name: 'lambda',
    pattern: [InputType, OutputType],
    use: function use() {
      return Function;
    },
    decorate: function decorate(data, key) {
      data[key] = function () {
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }

        InputType.assert(args);
        var result = data[key].apply(this, args);
        OutputType.assert(result);
        return result;
      };
    }
  });
  return rule;
}