"use strict";

import EventEmitter from 'event-emitter-es6';

import pathspec_to_regex from '../Shared/Pathspec.mjs';

/**
 * Client-side request router.
 * You should use this in a browser. If you're not in a browser, you probably want ServerRouter instead.
 * @extends		EventEmitter
 * @param		{object}	options	The options object to use when creating the router.
 * @property	{Boolean}	verbose	Whether to be verbose and log debugging information to the console.
 * @property	{Boolean}	listen_pushstate	Whether to listen to the browser's `pushstate` event and automatically navigate on recieving it.
 */
class ClientRouter extends EventEmitter {
	constructor(options) {
		super();
		
		/***** Settings *****/
		this.listen_pushstate = true;
		this.verbose = false;
		
		/********************/
		
		for(let key in options) {
			this[key] = options[key];
		}
		
		/********************/
		
		/** Whether we should handle popstate events or not. @type {Boolean} */
		this.handle_popstates = true;
		this.routes = [];
		
		window.addEventListener("popstate", ((event) => {
			if(!this.handle_popstates) return;
			
		    console.info(`[ClientRouter] Handling popstate - path: ${window.location.hash.substr(1)} (state`, event.state, `)`);
			this.navigate(window.location.hash.substr(1));
		}).bind(this));
	}
	
	/**
	 * Sets the function to execute when no other route could be matched.
	 * @param	{Function}	callback The callback to execute.
	 * @example router.add_404((path) => console.log(`Oops! Couldn't find a route to handle '${path}'.`));
	 */
	add_404(callback) {
		this.callback_404 = callback;
	}
	
	/**
	 * Adds a route to the router.
	 * @param	{string|RegExp}	routespec	The route specification that the route should match against. May contain regular expression syntax, and the domain-specific :param syntax. A raw regular expression may also be passed, if you need the flexibility.
	 * @param	{Function}	callback	The callback to execute when the route is matched.
	 * @example router.add_page("/add/vegetable/:name/:weight", (params) => console.log(`We added a ${params.name} with a weight of ${params.weight}g.`));
	 */
	add_page(routespec, callback) {
		this.routes.push({
			spec: routespec,
			match: routespec instanceof RegExp ? { regex: routespec, tokens: [] } : pathspec_to_regex(routespec),
			callback
		});
	}
	
	/**
	 * Manually navigate to a given path.
	 * @param	{string}	path	The path to navigate to.
	 * @example router.navigate("/add/carrot/frederick/10001");
	 */
	navigate(path) {
		for(let route_info of this.routes) {
			const matches = path.match(route_info.match.regex);
			if(matches) {
				if(this.verbose) console.log(`%c[Router] Matched against ${route_info.match.regex}!`, "color: hsl(331, 76%, 40%); font-weight: bold;");
				
				// Build the parameters object
				let params = {};
				for(let i = 1; i < matches.length; i++) { // Skip the top-level group match
					params[route_info.match.tokens[i-1]] = matches[i];
				}
				
				// Don't handle any popstates potentially generated by changing the hash
				this.handle_popstates = false;
				window.location.hash = `#${path}`;
				this.handle_popstates = true;
				
				route_info.callback(params);
				return;
			}
			
			if(this.verbose) console.debug(`%c[Router] No match against ${route_info.match.regex} - moving on.`, "color: hsl(331, 76%, 40%)");
		}
		
		if(this.verbose) console.warn(`Couldn't find a match for '${path}'.`);
		if(typeof this.callback_404 == "function")
			this.callback_404(path);
	}
	
	/**
	 * Navigate to the current URL's hash value - i.e. the bit after the '#' symbol in the URL.
	 * @example router.navigate_current_hash();
	 */
	navigate_current_hash() {
		this.navigate(window.location.hash.substr(1));
	}
}

export default ClientRouter;