'use strict';

var forEach      = require('es5-ext/object/for-each')
  , includes     = require('es5-ext/string/#/contains')
  , randomUniq   = require('es5-ext/string/random-uniq')
  , d            = require('d')
  , lazy         = require('d/lazy')
  , memoize      = require('memoizee')
  , deferred     = require('deferred')
  , debug        = require('debug-ext')('html-renderer')
  , resolve      = require('path').resolve
  , parse        = require('querystring').parse
  , req          = require('http').IncomingMessage.prototype
  , fork         = require('child_process').fork
  , inspect      = require('util').inspect
  , getHtmlIndex = require('./utils/get-html-index')
  , Client       = require('./client')
  , appAccessMap = require('./user-app-access-map')
  , mano         = require('../../')

  , now = Date.now, create = Object.create
  , poolScriptPath = resolve(__dirname, '../../bin/legacy-pool')
  , staticHost = '/'
  , legacyRe = /src="([^"]+)\.legacy\.js">/
  , waiting = Object.create(null);

if (mano.env.static && mano.env.static.host) staticHost = mano.env.static.host;

var resolveCookies = function (req, res) {
	var cookies, id;
	cookies = req.headers.cookie ? parse(req.headers.cookie, ';') : {};
	forEach(cookies, function (value, key) {
		var trimmedKey;
		if (!value) delete cookies[key];
		trimmedKey = key.trim();
		if (trimmedKey === key) return;
		delete cookies[key];
		cookies[trimmedKey] = value;
	});
	if (res.getHeader('Set-Cookie')) {
		res.getHeader('Set-Cookie').forEach(function (cookie) {
			var data, name, value;
			cookie = cookie.slice(0, cookie.indexOf(';')).trim();
			data = cookie.split('=');
			name = data[0];
			value = data[1];
			if (!value) {
				delete cookies[name];
				return;
			}
			cookies[name] = value;
		});
	}
	cookies.legacyPool = '1';
	delete cookies.legacy;
	id = 'legacy_' + req.$appName;
	if (!req.$user) {
		cookies.id = id;
		return deferred(cookies);
	}
	return appAccessMap.get(req.$user)(function (accessId) {
		id += '_' + accessId.slice(1);
		cookies.id = id;
		return cookies;
	});
};

var getIndex = memoize(function (appName, isFullRender) {
	if (isFullRender) return deferred('<!DOCTYPE html>\n');
	return getHtmlIndex(mano.appRoutes[appName])(function (html) {
		var legacyMatch, insert, index;
		html = String(html);
		legacyMatch = html.match(legacyRe);
		insert = legacyMatch ? ('<script src="' + legacyMatch[1] + '.legacy.js"></script>') : '';
		index = html.indexOf('<noscript>');
		if (index !== -1) {
			html = html.slice(0, index) + html.slice(html.lastIndexOf('</noscript>') +
				'</noscript>'.length);
		}
		html = html.slice(0, html.indexOf('<script data-spa')) + insert +
			html.slice(html.lastIndexOf('</script>') + '</script>'.length);
		html = html.slice(0, html.indexOf('<body'));
		if (includes.call(html, 'href="' + staticHost + appName + '.css"')) {
			html += '<link href="' + staticHost + appName + '-legacy.css" rel="stylesheet" />\n';
		}
		return html;
	});
}, { primitive: true, length: 1 });

Object.defineProperties(req, lazy({
	isHtmlServerRendered: d(function () {
		return Boolean(mano.legacyPool && this.cookies.get('legacy'));
	})
}));

module.exports = function (env) {
	var pool = fork(poolScriptPath, null, { cwd: env.root, env: process.env })
	  , middleware, postMessages, send;
	pool.on('error', function (err) {
		console.error("Legacy Master: Could not initialize pool");
		throw err;
	});
	pool.on('message', function (message) {
		var data;
		if (message.browserError) {
			pool.kill();
			console.log(message.browserError);
			throw new TypeError('Legacy browser error');
		}
		if (!message.id) {
			console.log("Legacy Master: data: ", message);
			console.error("Legacy Master: Invalid Response");
			return;
		}
		data = waiting[message.id];
		if (!data) {
			console.log("Legacy Master: data: ", message);
			console.error("Legacy Master: Unrecognized id");
			return;
		}
		delete waiting[message.id];
		if (message.error) {
			console.error("Legacy Master: Could not generate HTML! " + message.error);
			data.res.statusCode = 500;
			data.res.end("Server error");
			return;
		}
		debug("received html %s %ss", message.id, ((now() - data.time) / 1000).toFixed(2));
		getIndex(data.appName || data.req.$appName || 'main', data.req.$forceLegacyFullRender)
			.done(function (head) {
				data.res.setHeader('X-UA-Compatible', 'IE=edge,chrome=1');
				data.res.setHeader('Content-Type', 'text/html; charset=utf-8');
				data.res.setHeader('Cache-Control', 'no-cache');
				data.res.end(head + message.html);
			});
	});
	process.on('exit', function () { pool.kill(); });
	send = function (req, res, message/*, options*/) {
		var id = randomUniq(), postMessage = postMessages[req.$clientId]
		  , options = Object(arguments[3]);
		delete postMessages[req.$clientId];
		debug("request html %s %o %s", id, message.cookies, inspect(postMessage, { depth: 10 }));
		waiting[id] = { req: req, res: res, time: now(), appName: options.appName };
		message.id = id;
		message.postMessage = postMessage;
		pool.send(message);
	};
	middleware = function (req, res/*, options*/) {
		var options = Object(arguments[2]), message;
		resolveCookies(req, res).done(function (cookies) {
			message = { cookies: cookies, url: options.url || req._parsedUrl.path,
				fullRender: req.$forceLegacyFullRender };
			if (!Client.hasOwnProperty(cookies.id) || Client[cookies.id].isSync) send(req, res, message);
			else Client[cookies.id].once('sync', function () { send(req, res, message); });
		});
	};
	middleware.pool = pool;
	middleware.send = send;
	middleware.postMessages = postMessages = create(null);
	return middleware;
};
