"use strict";

const Promise = require("bluebird");
const pathToRegexp = require("path-to-regexp");
const defaultValue = require("default-value");
const url = require("url");

const objectToFormData = require("../util/form-data/object-to-formdata");
const objectToURLSearchParams = require("../util/form-data/object-to-urlsearchparams");

module.exports = function createRouter(options = {}) {
	let baseUrl = defaultValue(options.baseBackendUrl, "");

	let router = {
		_routes: [],
		_getRoute: function getRoute(method, path) {
			let matches;
			let matchingRoute = router._routes.find((route) => route.method === method && (matches = route.regex.exec(path)));

			if (matchingRoute == null) {
				throw new Error(`No matching routes found for ${method.toUpperCase()} ${path}`);
			} else {
				let params = {};

				matchingRoute.keys.forEach((key, i) => {
					params[key] = matches[i + 1];
				});

				return {
					handler: matchingRoute.handler,
					params: params
				};
			}
		},
		get: function (...args) {
			return this.addRoute("get", ...args);
		},
		post: function (...args) {
			return this.addRoute("post", ...args);
		},
		put: function (...args) {
			return this.addRoute("put", ...args);
		},
		delete: function (...args) {
			return this.addRoute("delete", ...args);
		},
		head: function (...args) {
			return this.addRoute("head", ...args);
		},
		patch: function (...args) {
			return this.addRoute("patch", ...args);
		},
		addRoute: function addRoute(method, path, handler) {
			/* Mutable arguments? WTF. */
			let keys = [];
			let regex = pathToRegexp(path, keys);

			router._routes.push({ method, path, regex, keys, handler });
		},
		handle: function handleRequest(method, uri, data, handleOptions = {}) {
			return Promise.try(() => {
				/* TODO: Support relative paths? */
				let {pathname, query} = url.parse(uri, true);
				let route = this._getRoute(method, pathname);
				let tasks = [];
				let renderTaskAdded = false;

				let req = {
					path: pathname,
					query: query,
					body: data,
					params: route.params,
					pass: function(options = {}) {
						return Promise.try(() => {
							let body;

							if (handleOptions.multipart) {
								body = objectToFormData(this.body);
							} else {
								body = objectToURLSearchParams(this.body);
							}

							return window.fetch(url.resolve(baseUrl, uri), Object.assign({ // FIXME: Override URI but maintain query?
								method: method,
								credentials: "same-origin", /* FIXME: Allow sending them elsewhere as well? */
								body: body
							}, options));
						}).then((response) => {
							if (!response.ok) {
								/* TODO: Is this what we want? */
								throw new Error(`Got a non-200 response: ${response.status}`, {response: response});
							} else {
								return Promise.try(() => {
									return response.json();
								}).then((json) => {
									return {
										status: response.status,
										body: json
									};
								});
							}
						});
					},
					/* TODO: passActions? */
					passRender: function(viewName, options = {}) {
						return Promise.try(() => {
							return this.pass(options.requestOptions);
						}).then((response) => {
							let locals = defaultValue(options.locals, {});
							let combinedLocals = Object.assign({}, locals, response.body);

							res.render(viewName, combinedLocals, options.renderOptions);
						});
					}
				};

				let res = {
					render: function(view, locals = {}, options = {}) {
						if (renderTaskAdded === false) {
							renderTaskAdded = true;

							tasks.push({
								type: "render",
								view, locals, options
							});
						} else {
							throw new Error("Can only render a view once per response");
						}
					},
					redirect: function(path, redirectMethod) {
						tasks.push({
							type: "redirect",
							path: path,
							method: defaultValue(redirectMethod, method)
						});
					},
					open: function(path, options = {}) {
						tasks.push({
							type: "open",
							path, options
						});
					},
					close: function(options = {}) {
						tasks.push({
							type: "close",
							options
						});
					},
					notify: function(message, options = {}) {
						tasks.push({
							type: "notify",
							message: message,
							options: {
								type: defaultValue(options.type, "info"),
								timeout: options.timeout,
								title: options.title
							}
						});
					},
					error: function(error, context = {}) {
						tasks.push({
							type: "error",
							error, context
						});
					}
				};

				return Promise.try(() => {
					return route.handler(req, res);
				}).then((result) => {
					return {
						result: result,
						actions: tasks
					};
				});
			});
		}
	};

	return router;
};
