/**
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|
 * |#       #|  Copyright (c) 1999-2010 CardContact Software & System Consulting
 * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
 *  ---------
 *
 *  This file is part of OpenSCDP.
 *
 *  OpenSCDP is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  OpenSCDP is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenSCDP; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * @fileoverview A class that routes URLs to handlers
 */


/**
 * Create a router.
 *
 * @constructor
 */
function Router() {
	this.map = {};
}

exports.Router = Router;



/**
 * Add a route for an URL.
 *
 * The URL pattern may contain wildcards, start with ":". A wildcard decoded from the URL passed
 * into the route() function is added with the same name as element in req.router.params.
 *
 * @param {String} the name of the route, passed to the handler in req.router.name
 * @param {String} the URL pattern
 * @param {Object} the handler object with method handleRoutedRequest(req, res)
 */
Router.prototype.add = function(name, pattern, handler) {
	if (pattern.startsWith("/")) {
		pattern = pattern.substring(1);
	}

	if (pattern.endsWith("/")) {
		pattern = pattern.substring(0, pattern.length - 1);
	}

	var parts = pattern.split("/");

	var map = this.map;
	for (var i = 0; i < parts.length; i++) {
		var elem = parts[i];
		if (elem.startsWith(":")) {
			var varid = elem.substring(1);
			if (typeof(map.sub) != "undefined") {
				throw new GPError(module.id, GPError.INVALID_DATA, 0, "Redefinition of fixed resource as variable in " + pattern);
			}

			if ((typeof(map.wildcard) != "undefined") && (map.wildcard != varid)) {
				throw new GPError(module.id, GPError.INVALID_DATA, 0, "Redefinition of variable " + map.wildcard + " in " + pattern);
			}

			map.wildcard = elem.substring(1);
		} else {
			if (typeof(map.wildcard) != "undefined") {
				throw new GPError(module.id, GPError.INVALID_DATA, 0, "Redefinition of wildcard element " + elem + " in " + pattern);
			}

			if (typeof(map.sub) == "undefined") {
				map.sub = {};
			}

			var n = map.sub[elem];
			if (typeof(n) == "undefined") {
				var n = {};
				map.sub[elem] = n;
			}
			map = n;
		}

		if (i == parts.length - 1) {
			if (typeof(map.handler) != "undefined") {
				throw new GPError(module.id, GPError.INVALID_DATA, 0, "Duplicate path " + pattern);
			}
			map.handler = handler;
			map.name = name;
		} else if (typeof(map.nxt) == "undefined") {
			map.nxt = {};
		}
		map = map.nxt;
	}
}



/**
 * Route a URL to an registered handler.
 *
 * @param {HttpRequest} req the request object
 * @param {HttpResponse} req the response object
 */
Router.prototype.route = function(req, res) {
	var pathInfo = req.pathInfo;
	if (pathInfo.startsWith("/")) {
		pathInfo = pathInfo.substring(1);
	}

	if (pathInfo.endsWith("/")) {
		pathInfo = pathInfo.substring(0, pathInfo.length - 1);
	}

	var parts = pathInfo.split("/");

	var params = {};
	var map = this.map;
	for (var i = 0; i < parts.length; i++) {
		var elem = parts[i];
//		print("Routing part " + elem);
		if (map == undefined) {
			res.setStatus(HttpResponse.SC_NOT_FOUND);
			return;
		}
		if (map.wildcard) {
//			print("Add as parameter " + map.wildcard);
			params[map.wildcard] = elem;
		} else {
			map = map.sub[elem];
			if (map == undefined) {
				res.setStatus(HttpResponse.SC_NOT_FOUND);
				return;
			}
//			print("Traversing");
		}
		if (i < parts.length - 1) {
			map = map.nxt;
		}
	}

	if (map.handler == undefined) {
		res.setStatus(HttpResponse.SC_NOT_FOUND);
	} else {
		req.router = {
			name: map.name,
			params: params
		};
		map.handler.handleRoutedRequest(req, res);
	}
}



/**
 * Dump the internal routing map.
 */
Router.prototype.dump = function(obj, indent) {
	if (typeof(obj) == "undefined") {
		obj = this.map;
	}
	if (typeof(indent) == "undefined") {
		indent = "";
	}

	for (var i in obj) {
		var o = obj[i];

		if ((typeof(o) == "object") && (i != "handler")) {
			print(indent + i + ":" );
			this.dump(o, indent + " ");
		} else {
			print(indent + i + "=" + o);
		}
	}
}


/*
var HttpResponse = {
	SC_NOT_FOUND: 404
};


Router.test = function() {

	var handler = {
		handleRequest: function(req, res) {
			print("name: " + req.router.name);
			for (var i in req.router.params) {
				print(i + "=" + req.router.params[i]);
			}
		}
	};

	var res = {
		setStatus: function(sc) {
			print("sc: " + sc);
		}
	};

	var router = new Router();
	router.add("route0", "/keys/id/test/action", handler);
	router.add("route1", "/keys/id2/:id", handler);
	router.add("route2", "/keys/id2/:id/action", handler);
	router.add("route3", "/keys/id2/:id/action/:task", handler);
	router.dump();

	var req = { pathInfo: "/keys/id/test/action" };
	router.route(req, res);

	var req = { pathInfo: "/keys/id2/7" };
	router.route(req, res);

	var req = { pathInfo: "/keys/id2/2/action" };
	router.route(req, res);

	var req = { pathInfo: "/keys/id2/2/action/abc" };
	router.route(req, res);
}
*/
