'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var _regeneratorRuntime = require('@babel/runtime/regenerator');
var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator');
var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var _createClass = require('@babel/runtime/helpers/createClass');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var utils = require('@teclone/utils');
var regex = require('@teclone/regex');
var Constants = require('./Constants');
var Utils = require('./Utils');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

var _regeneratorRuntime__default = /*#__PURE__*/_interopDefaultLegacy(_regeneratorRuntime);
var _asyncToGenerator__default = /*#__PURE__*/_interopDefaultLegacy(_asyncToGenerator);
var _slicedToArray__default = /*#__PURE__*/_interopDefaultLegacy(_slicedToArray);
var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallCheck);
var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass);
var _defineProperty__default = /*#__PURE__*/_interopDefaultLegacy(_defineProperty);

function _createForOfIteratorHelper(o, allowArrayLike) { var it; if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }

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__default['default'](this, Engine);

    _defineProperty__default['default'](this, "resolved", false);

    _defineProperty__default['default'](this, "request", void 0);

    _defineProperty__default['default'](this, "response", void 0);

    _defineProperty__default['default'](this, "middlewares", void 0);

    _defineProperty__default['default'](this, "method", void 0);

    _defineProperty__default['default'](this, "url", void 0);

    _defineProperty__default['default'](this, "logger", void 0);

    _defineProperty__default['default'](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();
  }
  /**
   * capture route parameters
   */


  _createClass__default['default'](Engine, [{
    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__default['default'](_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;
    }
    /**
     * reduce parameters to object value
     * @param parameters
     */

  }, {
    key: "reduceParams",
    value: function reduceParams(parameters) {
      return parameters.reduce(function (acc, current) {
        acc[current.name] = current.value;
        return acc;
      }, {});
    }
    /**
     * runs through the route, and captures parameters
     */

  }, {
    key: "captureParameters",
    value: function captureParameters(routeUrl) {
      var parameters = [];

      var removeEmpty = function removeEmpty(arg) {
        return arg !== '';
      }; //split the tokens.


      var routeTokens = routeUrl.split('/').filter(removeEmpty);
      var urlTokens = this.url.split('/').filter(removeEmpty); // if the route tokens is greater than the url tokens, fill with empty string

      var len = routeTokens.length;
      utils.fillArray(urlTokens, len, '');
      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: 'rest',
            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 + '/');
    }
    /**
     * asynchronously runs the middleware
     */

  }, {
    key: "runMiddlewares",
    value: function () {
      var _runMiddlewares = _asyncToGenerator__default['default']( /*#__PURE__*/_regeneratorRuntime__default['default'].mark(function _callee(middlewares, parameters) {
        var next, _iterator, _step, middleware;

        return _regeneratorRuntime__default['default'].wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                next = generateNext();
                _iterator = _createForOfIteratorHelper(middlewares);
                _context.prev = 2;

                _iterator.s();

              case 4:
                if ((_step = _iterator.n()).done) {
                  _context.next = 13;
                  break;
                }

                middleware = _step.value;
                next.reset();
                _context.next = 9;
                return middleware(this.request, this.response, next, {
                  params: this.reduceParams(parameters)
                });

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

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

              case 11:
                _context.next = 4;
                break;

              case 13:
                _context.next = 18;
                break;

              case 15:
                _context.prev = 15;
                _context.t0 = _context["catch"](2);

                _iterator.e(_context.t0);

              case 18:
                _context.prev = 18;

                _iterator.f();

                return _context.finish(18);

              case 21:
                if (next.status() === false && (!this.response.writableFinished || !this.response.finished)) {
                  this.response.end();
                }

                return _context.abrupt("return", next.status());

              case 23:
              case "end":
                return _context.stop();
            }
          }
        }, _callee, this, [[2, 15, 18, 21]]);
      }));

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

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

  }, {
    key: "runRoute",
    value: function () {
      var _runRoute = _asyncToGenerator__default['default']( /*#__PURE__*/_regeneratorRuntime__default['default'].mark(function _callee2(route, parameters) {
        var _iterator2, _step2, middlewareInstance, _middlewareInstance, url, middlewares, options, middlewareUrl, methods, middlewareParameters, shouldContinue, _route, callback, routeOptions;

        return _regeneratorRuntime__default['default'].wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                _iterator2 = _createForOfIteratorHelper(this.middlewares);
                _context2.prev = 1;

                _iterator2.s();

              case 3:
                if ((_step2 = _iterator2.n()).done) {
                  _context2.next = 18;
                  break;
                }

                middlewareInstance = _step2.value;
                _middlewareInstance = _slicedToArray__default['default'](middlewareInstance, 4), url = _middlewareInstance[1], middlewares = _middlewareInstance[2], options = _middlewareInstance[3];
                middlewareUrl = utils.stripSlashes(url);
                methods = options.method;

                if (!(methods.length === 0 || !methods.includes(this.method) || !this.matchUrl(middlewareUrl))) {
                  _context2.next = 10;
                  break;
                }

                return _context2.abrupt("continue", 16);

              case 10:
                middlewareParameters = this.captureParameters(middlewareUrl);
                _context2.next = 13;
                return this.runMiddlewares(middlewares, middlewareParameters);

              case 13:
                shouldContinue = _context2.sent;

                if (shouldContinue) {
                  _context2.next = 16;
                  break;
                }

                return _context2.abrupt("return");

              case 16:
                _context2.next = 3;
                break;

              case 18:
                _context2.next = 23;
                break;

              case 20:
                _context2.prev = 20;
                _context2.t0 = _context2["catch"](1);

                _iterator2.e(_context2.t0);

              case 23:
                _context2.prev = 23;

                _iterator2.f();

                return _context2.finish(23);

              case 26:
                _route = _slicedToArray__default['default'](route, 4), callback = _route[2], routeOptions = _route[3]; //run localised middlewares if any

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

                _context2.next = 30;
                return this.runMiddlewares(routeOptions.use, parameters);

              case 30:
                if (_context2.sent) {
                  _context2.next = 32;
                  break;
                }

                return _context2.abrupt("return");

              case 32:
                _context2.next = 34;
                return callback(this.request, this.response, {
                  params: this.reduceParams(parameters)
                });

              case 34:
                return _context2.abrupt("return", _context2.sent);

              case 35:
              case "end":
                return _context2.stop();
            }
          }
        }, _callee2, this, [[1, 20, 23, 26]]);
      }));

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

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

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

                if (!this.resolved) {
                  _context3.next = 3;
                  break;
                }

                return _context3.abrupt("return", true);

              case 3:
                if (this.matchUrl(routeUrl)) {
                  _context3.next = 5;
                  break;
                }

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

              case 5:
                this.resolved = true;
                parameters = this.captureParameters(routeUrl);
                _context3.prev = 7;
                _context3.next = 10;
                return this.runRoute(route, parameters);

              case 10:
                _context3.next = 15;
                break;

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

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

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

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

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

  }, {
    key: "use",
    value: function use(middlewares) {
      this.middlewares = middlewares;
      return this;
    }
  }]);

  return Engine;
}();

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