/**
 * Module dependencies.
 */

var debug = require('debug')('express:router:route');
var methods = require('methods');
var utils = require('../utils');

/**
 * Expose `Route`.
 */

module.exports = Route;

/**
 * Initialize `Route` with the given `path`,
 *
 * @param {String} path
 * @api private
 */

function Route(path) {
  debug('new %s', path);
  this.path = path;
  this.stack = undefined;

  // route handlers for various http methods
  this.methods = {};
}

/**
 * @return {Array} supported HTTP methods
 * @api private
 */

Route.prototype._options = function(){
  return Object.keys(this.methods).map(function(method) {
    return method.toUpperCase();
  });
};

/**
 * dispatch req, res into this route
 *
 * @api private
 */

Route.prototype.dispatch = function(req, res, done){
  var self = this;
  var method = req.method.toLowerCase();

  if (method === 'head' && !this.methods['head']) {
    method = 'get';
  }

  req.route = self;

  // single middleware route case
  if (typeof this.stack === 'function') {
    this.stack(req, res, done);
    return;
  }

  var stack = self.stack;
  if (!stack) {
    return done();
  }

  var idx = 0;
  (function next_layer(err) {
    if (err && err === 'route') {
      return done();
    }

    var layer = stack[idx++];
    if (!layer) {
      return done(err);
    }

    if (layer.method && layer.method !== method) {
      return next_layer(err);
    }

    var arity = layer.handle.length;
    if (err) {
      if (arity < 4) {
        return next_layer(err);
      }

      try {
        layer.handle(err, req, res, next_layer);
      } catch (err) {
        next_layer(err);
      }
      return;
    }

    if (arity > 3) {
      return next_layer();
    }

    try {
      layer.handle(req, res, next_layer);
    } catch (err) {
      next_layer(err);
    }
  })();
};

/**
 * Add a handler for all HTTP verbs to this route.
 *
 * Behaves just like middleware and can respond or call `next`
 * to continue processing.
 *
 * You can use multiple `.all` call to add multiple handlers.
 *
 *   function check_something(req, res, next){
 *     next();
 *   };
 *
 *   function validate_user(req, res, next){
 *     next();
 *   };
 *
 *   route
 *   .all(validate_user)
 *   .all(check_something)
 *   .get(function(req, res, next){
 *     res.send('hello world');
 *   });
 *
 * @param {function} handler
 * @return {Route} for chaining
 * @api public
 */

Route.prototype.all = function(){
  var self = this;
  var callbacks = utils.flatten([].slice.call(arguments));
  callbacks.forEach(function(fn) {
    if (typeof fn !== 'function') {
      var type = {}.toString.call(fn);
      var msg = 'Route.all() requires callback functions but got a ' + type;
      throw new Error(msg);
    }

    if (!self.stack) {
      self.stack = fn;
    }
    else if (typeof self.stack === 'function') {
      self.stack = [{ handle: self.stack }, { handle: fn }];
    }
    else {
      self.stack.push({ handle: fn });
    }
  });

  return self;
};

methods.forEach(function(method){
  Route.prototype[method] = function(){
    var self = this;
    var callbacks = utils.flatten([].slice.call(arguments));

    callbacks.forEach(function(fn) {
      if (typeof fn !== 'function') {
        var type = {}.toString.call(fn);
        var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
        throw new Error(msg);
      }

      debug('%s %s', method, self.path);

      if (!self.methods[method]) {
        self.methods[method] = true;
      }

      if (!self.stack) {
        self.stack = [];
      }
      else if (typeof self.stack === 'function') {
        self.stack = [{ handle: self.stack }];
      }

      self.stack.push({ method: method, handle: fn });
    });
    return self;
  };
});
