'use strict';

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

var _regeneratorRuntime = _interopDefault(require('@babel/runtime/regenerator'));
var _toConsumableArray = _interopDefault(require('@babel/runtime/helpers/toConsumableArray'));
var _asyncToGenerator = _interopDefault(require('@babel/runtime/helpers/asyncToGenerator'));
var _slicedToArray = _interopDefault(require('@babel/runtime/helpers/slicedToArray'));
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 utils = require('@forensic-js/utils');
var regex = require('@forensic-js/regex');
var Constants = require('./Constants');
var Utils = require('./Utils');

var generateNext = function generateNext() {
  var status = false;

  var next = function next() {
    return status = true;
  };

  next.status = function () {
    return status;
  };

  next.reset = function () {
    return !(status = false);
  };

  return next;
};

var Engine =
/*#__PURE__*/
function () {
  function Engine(url, method, request, response, logger, errorCallback) {
    _classCallCheck(this, Engine);

    _defineProperty(this, "resolved", false);

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

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

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

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

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

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

    _defineProperty(this, "errorCallback", null);

    this.resolved = false;
    this.request = request;
    this.response = response;
    this.middlewares = [];
    this.logger = logger;
    this.errorCallback = errorCallback;
    this.url = utils.stripSlashes(url.replace(/[#?].*$/, ''));
    this.method = method.toLowerCase();
  }
  /**
   * returns the parameter value
   */


  _createClass(Engine, [{
    key: "getParameterValue",
    value: function getParameterValue(parameter) {
      return parameter.value;
    }
    /**
     * capture route parameters
     */

  }, {
    key: "captureParameter",
    value: function captureParameter(routeToken, urlToken, parameters) {
      var processToken = function processToken(token, value) {
        var _ref = token.indexOf(':') > -1 ? token.split(':') : ['string', token],
            _ref2 = _slicedToArray(_ref, 2),
            dataType = _ref2[0],
            name = _ref2[1];

        var result = value;

        switch (dataType.toLowerCase()) {
          case 'int':
            result = Number.parseInt(value);
            break;

          case 'float':
          case 'number':
          case 'numeric':
            result = Number.parseFloat(value);
            break;

          case 'bool':
          case 'boolean':
            result = result.toLowerCase();
            result = !['0', 'false', '', 'null', 'nil', 'undefined', 'no', 'none'].includes(value);
            break;
        }

        if (Number.isNaN(result)) {
          result = 0;
        }

        return {
          name: name,
          dataType: dataType,
          value: result
        };
      };

      if (Constants.DOUBLE_TOKEN_REGEX.test(routeToken)) {
        var token1 = RegExp.$1;
        var separator = RegExp.$2;
        var token2 = RegExp.$3;
        var values = urlToken ? urlToken.split(separator) : ['', ''];
        parameters.push(processToken(token1, values[0]));
        parameters.push(processToken(token2, values[1]));
      } else if (Constants.SINGLE_TOKEN_REGEX.test(routeToken)) {
        var token = RegExp.$1;
        parameters.push(processToken(token, urlToken));
      }

      return parameters;
    }
    /**
     * runs through the route, and captures parameters
     */

  }, {
    key: "captureParameters",
    value: function captureParameters(routeUrl) {
      var parameters = []; //split the tokens.

      var routeTokens = routeUrl !== '' ? routeUrl.split('/') : [];
      var urlTokens = this.url !== '' ? this.url.split('/') : []; // if the route tokens is greater than the url tokens, fill with empty string

      var len = routeTokens.length;
      var difference = len - urlTokens.length;

      if (difference > 0) {
        Array(difference).fill('').forEach(function (item) {
          urlTokens.push(item);
        });
      }

      var i = -1;

      while (++i < len) {
        var urlToken = urlTokens[i];
        var routeToken = routeTokens[i]; // if it is final route token, store remaining url

        if (routeToken === '*' && i + 1 === len) {
          parameters.push({
            name: '*',
            dataType: 'string',
            value: urlTokens.slice(i).join('/')
          });
          break;
        } else {
          this.captureParameter(routeToken, urlToken, parameters);
        }
      }

      return parameters;
    }
    /**
     * check if route url matches request url
     */

  }, {
    key: "matchUrl",
    value: function matchUrl(routeUrl) {
      /* create matching regular expression */
      var tokens = routeUrl ? routeUrl.split('/') : [];
      var max = tokens.length - 1;
      var pattern = tokens.map(function (token, index) {
        if (index === max && /[^)]\?$/.test(token)) {
          token = "(".concat(token.substring(0, token.length - 1), ")?");
        }

        if (token === '*') {
          return '.*';
        } else {
          token = regex.replace(Constants.DOUBLE_TOKEN_REGEX, '[^/]+$:2[^/]+', token);
          token = regex.replace(Constants.SINGLE_TOKEN_REGEX, '[^/]+', token);
        }

        return token;
      }).join('/');
      var regex$$1 = new RegExp('^' + pattern + '$', 'i'); //regex is case insensitive

      return regex$$1.test(this.url) || regex$$1.test(this.url + '/');
    }
    /**
     * validate that the middleware method matches request method
     */

  }, {
    key: "validateMiddleware",
    value: function validateMiddleware(options) {
      if (utils.isObject(options)) {
        return options.method.includes(this.method);
      } else {
        return true;
      }
    }
    /**
     * validate that the route method matches the request method
     */

  }, {
    key: "validateRoute",
    value: function validateRoute(overrideMethod) {
      if (overrideMethod && this.method !== overrideMethod) {
        return false;
      } else {
        return true;
      }
    }
    /**
     * asynchronously runs the middleware
     */

  }, {
    key: "runMiddlewares",
    value: function () {
      var _runMiddlewares = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee(middlewares, parameters) {
        var next, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, middleware;

        return _regeneratorRuntime.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                next = generateNext();
                _iteratorNormalCompletion = true;
                _didIteratorError = false;
                _iteratorError = undefined;
                _context.prev = 4;
                _iterator = middlewares[Symbol.iterator]();

              case 6:
                if (_iteratorNormalCompletion = (_step = _iterator.next()).done) {
                  _context.next = 17;
                  break;
                }

                middleware = _step.value;
                next.reset();
                _context.next = 11;
                return middleware.apply(void 0, [this.request, this.response, next].concat(_toConsumableArray(parameters)));

              case 11:
                if (!(next.status() === false)) {
                  _context.next = 14;
                  break;
                }

                if (!this.response.finished) {
                  this.response.end();
                }

                return _context.abrupt("break", 17);

              case 14:
                _iteratorNormalCompletion = true;
                _context.next = 6;
                break;

              case 17:
                _context.next = 23;
                break;

              case 19:
                _context.prev = 19;
                _context.t0 = _context["catch"](4);
                _didIteratorError = true;
                _iteratorError = _context.t0;

              case 23:
                _context.prev = 23;
                _context.prev = 24;

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

              case 26:
                _context.prev = 26;

                if (!_didIteratorError) {
                  _context.next = 29;
                  break;
                }

                throw _iteratorError;

              case 29:
                return _context.finish(26);

              case 30:
                return _context.finish(23);

              case 31:
                return _context.abrupt("return", next.status());

              case 32:
              case "end":
                return _context.stop();
            }
          }
        }, _callee, this, [[4, 19, 23, 31], [24,, 26, 30]]);
      }));

      function runMiddlewares(_x, _x2) {
        return _runMiddlewares.apply(this, arguments);
      }

      return runMiddlewares;
    }()
    /**
     * asynchronously runs the matching route
     */

  }, {
    key: "runRoute",
    value: function () {
      var _runRoute = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee2(route, parameters) {
        var _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, _step2$value, url, middlewares, options, middlewareUrl, middlewareParameters, _route, callback, routeOptions, parameterValues;

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

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

                _step2$value = _slicedToArray(_step2.value, 4), url = _step2$value[1], middlewares = _step2$value[2], options = _step2$value[3];
                middlewareUrl = utils.stripSlashes(url);

                if (!(this.validateMiddleware(options) && this.matchUrl(middlewareUrl))) {
                  _context2.next = 14;
                  break;
                }

                middlewareParameters = this.captureParameters(middlewareUrl);
                _context2.next = 12;
                return this.runMiddlewares(middlewares, middlewareParameters.map(this.getParameterValue));

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

                return _context2.abrupt("return");

              case 14:
                _iteratorNormalCompletion2 = true;
                _context2.next = 5;
                break;

              case 17:
                _context2.next = 23;
                break;

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

              case 23:
                _context2.prev = 23;
                _context2.prev = 24;

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

              case 26:
                _context2.prev = 26;

                if (!_didIteratorError2) {
                  _context2.next = 29;
                  break;
                }

                throw _iteratorError2;

              case 29:
                return _context2.finish(26);

              case 30:
                return _context2.finish(23);

              case 31:
                _route = _slicedToArray(route, 4), callback = _route[2], routeOptions = _route[3];
                parameterValues = parameters.map(this.getParameterValue); //run localised middlewares if any

                if (!utils.isObject(routeOptions)) {
                  _context2.next = 38;
                  break;
                }

                _context2.next = 36;
                return this.runMiddlewares(routeOptions.middleware, parameterValues);

              case 36:
                if (_context2.sent) {
                  _context2.next = 38;
                  break;
                }

                return _context2.abrupt("return");

              case 38:
                _context2.next = 40;
                return callback.apply(void 0, [this.request, this.response].concat(_toConsumableArray(parameterValues)));

              case 40:
              case "end":
                return _context2.stop();
            }
          }
        }, _callee2, this, [[3, 19, 23, 31], [24,, 26, 30]]);
      }));

      function runRoute(_x3, _x4) {
        return _runRoute.apply(this, arguments);
      }

      return runRoute;
    }()
    /**
     * processes the route
     */

  }, {
    key: "process",
    value: function () {
      var _process = _asyncToGenerator(
      /*#__PURE__*/
      _regeneratorRuntime.mark(function _callee3(route, overrideMethod) {
        var routeUrl, parameters;
        return _regeneratorRuntime.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                routeUrl = utils.stripSlashes(route[1]);

                if (!(!this.resolved && this.validateRoute(overrideMethod) && this.matchUrl(routeUrl))) {
                  _context3.next = 15;
                  break;
                }

                this.resolved = true;
                parameters = this.captureParameters(routeUrl);
                _context3.prev = 4;
                _context3.next = 7;
                return this.runRoute(route, parameters);

              case 7:
                _context3.next = 12;
                break;

              case 9:
                _context3.prev = 9;
                _context3.t0 = _context3["catch"](4);
                Utils.handleError(_context3.t0, this.errorCallback, this.logger, this.request, this.response);

              case 12:
                return _context3.abrupt("return", true);

              case 15:
                return _context3.abrupt("return", false);

              case 16:
              case "end":
                return _context3.stop();
            }
          }
        }, _callee3, this, [[4, 9]]);
      }));

      function process(_x5, _x6) {
        return _process.apply(this, arguments);
      }

      return process;
    }()
    /**
     * sets or overrides the existing middlewares
     */

  }, {
    key: "use",
    value: function use(middlewares) {
      this.middlewares = middlewares;
      return this;
    }
    /**
     * stores route rules for http OPTIONS method
     */

  }, {
    key: "options",
    value: function options(route) {
      return this.process(route, 'options');
    }
    /**
     * stores route rules for http HEAD method
     */

  }, {
    key: "head",
    value: function head(route) {
      return this.process(route, 'head');
    }
    /**
     * stores route rules for http GET method
     */

  }, {
    key: "get",
    value: function get(route) {
      return this.process(route, 'get');
    }
    /**
     * stores route rules for http POST method
     */

  }, {
    key: "post",
    value: function post(route) {
      return this.process(route, 'post');
    }
    /**
     * stores route rules for http PUT method
     */

  }, {
    key: "put",
    value: function put(route) {
      return this.process(route, 'put');
    }
    /**
     * stores route rules for http DELETE method
     */

  }, {
    key: "delete",
    value: function _delete(route) {
      return this.process(route, 'delete');
    }
    /**
     * stores route rules for all http methods
     */

  }, {
    key: "all",
    value: function all(route) {
      return this.process(route);
    }
  }]);

  return Engine;
}();

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