HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/RImmers2/portal.photomenu.nl/wwwroot/node_modules/total.js/index.js
// Copyright 2012-2016 (c) Peter Širka <petersirka@gmail.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

/**
 * @module Framework
 * @version 2.3.0
 */

'use strict';

const Qs = require('querystring');
const Os = require('os');
const Fs = require('fs');
const Zlib = require('zlib');
const Path = require('path');
const Crypto = require('crypto');
const Parser = require('url');
const Child = require('child_process');
const Util = require('util');
const Events = require('events');
const http = require('http');

const ENCODING = 'utf8';
const RESPONSE_HEADER_CACHECONTROL = 'Cache-Control';
const RESPONSE_HEADER_CONTENTTYPE = 'Content-Type';
const RESPONSE_HEADER_CONTENTLENGTH = 'Content-Length';
const CONTENTTYPE_TEXTPLAIN = 'text/plain';
const CONTENTTYPE_TEXTHTML = 'text/html';
const REQUEST_COMPRESS_CONTENTTYPE = { 'text/plain': true, 'text/javascript': true, 'text/css': true, 'text/jsx': true, 'application/x-javascript': true, 'application/json': true, 'text/xml': true, 'image/svg+xml': true, 'text/x-markdown': true, 'text/html': true };
const TEMPORARY_KEY_REGEX = /\//g;
const REG_MOBILE = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|Tablet/i;
const REG_ROBOT = /search|agent|bot|crawler|spider/i;
const REG_VERSIONS = /(href|src)="[a-zA-Z0-9\/\:\-\.]+\.(jpg|js|css|png|gif|svg|html|ico|json|less|sass|scss|swf|txt|webp|woff|woff2|xls|xlsx|xml|xsl|xslt|zip|rar|csv|doc|docx|eps|gzip|jpe|jpeg|manifest|mov|mp3|mp4|ogg|package|pdf)"/gi;
const REG_MULTIPART = /\/form\-data$/i;
const REG_COMPILECSS = /url\(.*?\)/g;
const REG_ROUTESTATIC = /^(\/\/|https\:|http\:)+/;
const REG_EMPTY = /\s/g;
const REG_SANITIZE_BACKSLASH = /\/\//g;
const REG_WEBSOCKET_ERROR = /ECONNRESET|EHOSTUNREACH|EPIPE|is closed/i;
const REG_WINDOWSPATH = /\\/g;
const REG_SCRIPTCONTENT = /\<|\>|;/;
const REG_HTTPHTTPS = /^(\/)?(http|https)\:\/\//i;
const REG_NOCOMPRESS = /[\.|-]+min\.(css|js)$/i;
const REG_TEXTAPPLICATION = /text|application/;
const REG_ENCODINGCLEANER = /[\;\s]charset=utf\-8/g;
const REQUEST_PROXY_FLAGS = ['post', 'json'];
const QUERYPARSEROPTIONS = { maxKeys: 69 };
const EMPTYARRAY = [];
const EMPTYOBJECT = {};
const EMPTYREQUEST = { uri: {} };
const SINGLETONS = {};
const REPOSITORY_HEAD = '$head';
const REPOSITORY_META = '$meta';
const REPOSITORY_META_TITLE = '$title';
const REPOSITORY_META_DESCRIPTION = '$description';
const REPOSITORY_META_KEYWORDS = '$keywords';
const REPOSITORY_META_AUTHOR = '$author';
const REPOSITORY_META_IMAGE = '$image';
const REPOSITORY_PLACE = '$place';
const REPOSITORY_SITEMAP = '$sitemap';
const ATTR_END = '"';

Object.freeze(EMPTYOBJECT);
Object.freeze(EMPTYARRAY);
Object.freeze(EMPTYREQUEST);

global.EMPTYOBJECT = EMPTYOBJECT;
global.EMPTYARRAY = EMPTYARRAY;

var RANGE = { start: 0, end: 0 };
var HEADERS = {};
var SUCCESSHELPER = { success: true };

// Cached headers for repeated usage
HEADERS['responseCode'] = {};
HEADERS['responseCode'][RESPONSE_HEADER_CONTENTTYPE] = CONTENTTYPE_TEXTPLAIN;
HEADERS['responseRedirect'] = {};
HEADERS['responseRedirect'][RESPONSE_HEADER_CONTENTTYPE] = CONTENTTYPE_TEXTHTML + '; charset=utf-8';
HEADERS['responseRedirect'][RESPONSE_HEADER_CONTENTLENGTH] = '0';
HEADERS['sse'] = {};
HEADERS['sse'][RESPONSE_HEADER_CACHECONTROL] = 'no-cache, no-store, must-revalidate';
HEADERS['sse']['Pragma'] = 'no-cache';
HEADERS['sse']['Expires'] = '0';
HEADERS['sse'][RESPONSE_HEADER_CONTENTTYPE] = 'text/event-stream';
HEADERS['mmr'] = {};
HEADERS['mmr'][RESPONSE_HEADER_CACHECONTROL] = 'no-cache, no-store, must-revalidate';
HEADERS['mmr']['Pragma'] = 'no-cache';
HEADERS['mmr']['Expires'] = '0';
HEADERS['proxy'] = {};
HEADERS['proxy']['X-Proxy'] = 'total.js';
HEADERS['responseFile.etag'] = {};
HEADERS['responseFile.etag']['Last-Modified'] = 'Mon, 01 Jan 2001 08:00:00 GMT';
HEADERS['responseFile.etag']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.etag'][RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=11111111';
HEADERS['responseFile.release.compress'] = {};
HEADERS['responseFile.release.compress'][RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=11111111';
HEADERS['responseFile.release.compress']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.release.compress']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.release.compress']['Last-Modified'] = 'Mon, 01 Jan 2001 08:00:00 GMT';
HEADERS['responseFile.release.compress']['Content-Encoding'] = 'gzip';
HEADERS['responseFile.release.compress.range'] = {};
HEADERS['responseFile.release.compress.range']['Accept-Ranges'] = 'bytes';
HEADERS['responseFile.release.compress.range'][RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=11111111';
HEADERS['responseFile.release.compress.range']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.release.compress.range']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.release.compress.range']['Last-Modified'] = 'Mon, 01 Jan 2001 08:00:00 GMT';
HEADERS['responseFile.release.compress.range']['Content-Encoding'] = 'gzip';
HEADERS['responseFile.release.compress.range'][RESPONSE_HEADER_CONTENTLENGTH] = '0';
HEADERS['responseFile.release.compress.range']['Content-Range'] = '';
HEADERS['responseFile.release'] = {};
HEADERS['responseFile.release'][RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=11111111';
HEADERS['responseFile.release']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.release']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.release']['Last-Modified'] = 'Mon, 01 Jan 2001 08:00:00 GMT';
HEADERS['responseFile.release.range'] = {};
HEADERS['responseFile.release.range']['Accept-Ranges'] = 'bytes';
HEADERS['responseFile.release.range'][RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=11111111';
HEADERS['responseFile.release.range']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.release.range']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.release.range']['Last-Modified'] = 'Mon, 01 Jan 2001 08:00:00 GMT';
HEADERS['responseFile.release.range'][RESPONSE_HEADER_CONTENTLENGTH] = '0';
HEADERS['responseFile.release.range']['Content-Range'] = '';
HEADERS['responseFile.debug.compress'] = {};
HEADERS['responseFile.debug.compress'][RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
HEADERS['responseFile.debug.compress']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.debug.compress']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.debug.compress']['Pragma'] = 'no-cache';
HEADERS['responseFile.debug.compress']['Expires'] = '0';
HEADERS['responseFile.debug.compress']['Content-Encoding'] = 'gzip';
HEADERS['responseFile.debug.compress.range'] = {};
HEADERS['responseFile.debug.compress.range']['Accept-Ranges'] = 'bytes';
HEADERS['responseFile.debug.compress.range'][RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
HEADERS['responseFile.debug.compress.range']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.debug.compress.range']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.debug.compress.range']['Content-Encoding'] = 'gzip';
HEADERS['responseFile.debug.compress.range']['Pragma'] = 'no-cache';
HEADERS['responseFile.debug.compress.range']['Expires'] = '0';
HEADERS['responseFile.debug.compress.range'][RESPONSE_HEADER_CONTENTLENGTH] = '0';
HEADERS['responseFile.debug.compress.range']['Content-Range'] = '';
HEADERS['responseFile.debug'] = {};
HEADERS['responseFile.debug'][RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
HEADERS['responseFile.debug']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.debug']['Pragma'] = 'no-cache';
HEADERS['responseFile.debug']['Expires'] = '0';
HEADERS['responseFile.debug']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.debug.range'] = {};
HEADERS['responseFile.debug.range']['Accept-Ranges'] = 'bytes';
HEADERS['responseFile.debug.range'][RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
HEADERS['responseFile.debug.range']['Vary'] = 'Accept-Encoding';
HEADERS['responseFile.debug.range']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseFile.debug.range']['Pragma'] = 'no-cache';
HEADERS['responseFile.debug.range']['Expires'] = '0';
HEADERS['responseFile.debug.range'][RESPONSE_HEADER_CONTENTLENGTH] = '0';
HEADERS['responseFile.debug.range']['Content-Range'] = '';
HEADERS['responseContent.mobile.compress'] = {};
HEADERS['responseContent.mobile.compress']['Vary'] = 'Accept-Encoding, User-Agent';
HEADERS['responseContent.mobile.compress']['Content-Encoding'] = 'gzip';
HEADERS['responseContent.mobile'] = {};
HEADERS['responseContent.mobile']['Vary'] = 'Accept-Encoding, User-Agent';
HEADERS['responseContent.compress'] = {};
HEADERS['responseContent.compress'][RESPONSE_HEADER_CACHECONTROL] = 'private';
HEADERS['responseContent.compress']['Vary'] = 'Accept-Encoding';
HEADERS['responseContent.compress']['Content-Encoding'] = 'gzip';
HEADERS['responseContent'] = {};
HEADERS['responseContent']['Vary'] = 'Accept-Encoding';
HEADERS['responseStream.release.compress'] = {};
HEADERS['responseStream.release.compress'][RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=11111111';
HEADERS['responseStream.release.compress']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseStream.release.compress']['Content-Encoding'] = 'gzip';
HEADERS['responseStream.release'] = {};
HEADERS['responseStream.release'][RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=11111111';
HEADERS['responseStream.release']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseStream.debug.compress'] = {};
HEADERS['responseStream.debug.compress'][RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
HEADERS['responseStream.debug.compress']['Pragma'] = 'no-cache';
HEADERS['responseStream.debug.compress']['Expires'] = '0';
HEADERS['responseStream.debug.compress']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseStream.debug.compress']['Content-Encoding'] = 'gzip';
HEADERS['responseStream.debug'] = {};
HEADERS['responseStream.debug'][RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
HEADERS['responseStream.debug']['Pragma'] = 'no-cache';
HEADERS['responseStream.debug']['Expires'] = '0';
HEADERS['responseStream.debug']['Access-Control-Allow-Origin'] = '*';
HEADERS['responseBinary.compress'] = {};
HEADERS['responseBinary.compress'][RESPONSE_HEADER_CACHECONTROL] = 'public';
HEADERS['responseBinary.compress']['Content-Encoding'] = 'gzip';
HEADERS['responseBinary'] = {};
HEADERS['responseBinary'][RESPONSE_HEADER_CACHECONTROL] = 'public';
HEADERS.redirect = { 'Location': '' };
HEADERS.authorization = { user: '', password: '', empty: true };
HEADERS.fsStreamRead = { flags: 'r', mode: '0666', autoClose: true }
HEADERS.fsStreamReadRange = { flags: 'r', mode: '0666', autoClose: true, start: 0, end: 0 };
HEADERS.workers = { cwd: '' };
HEADERS.mmrpipe = { end: false };

var _controller = '';
var _owner = '';
var _test;
var _flags;

// GO ONLINE MODE
if (!global.framework_internal)
	global.framework_internal = require('./internal');

if (!global.framework_builders)
	global.framework_builders = require('./builders');

if (!global.framework_utils)
	global.framework_utils = require('./utils');

if (!global.framework_mail)
	global.framework_mail = require('./mail');

if (!global.framework_image)
	global.framework_image = require('./image');

if (!global.framework_nosql)
	global.framework_nosql = require('./nosql');

global.Builders = framework_builders;
var utils = global.Utils = global.utils = global.U = framework_utils;
global.Mail = framework_mail;

global.WTF = function(message, name, uri) {
	return framework.problem(message, name, uri);
};

global.INCLUDE = global.SOURCE = function(name, options) {
	return framework.source(name, options);
};

global.MODULE = function(name) {
	return framework.module(name);
};

global.NOSQL = function(name) {
	return framework.nosql(name);
};

global.NOBIN = function(name) {
	return framework.nosql(name).binary;
};

global.NOCOUNTER = function(name) {
	return framework.nosql(name).counter;
};

global.NOMEM = global.NOSQLMEMORY = function(name, view) {
	return global.framework_nosql.inmemory(name, view);
};

global.DB = global.DATABASE = function() {
	if (typeof(framework.database) === 'object')
		return framework.database;
	return framework.database.apply(framework, arguments);
};

global.CONFIG = function(name) {
	return framework.config[name];
};

global.INSTALL = function(type, name, declaration, options, callback) {
	return framework.install(type, name, declaration, options, callback);
};

global.UNINSTALL = function(type, name, options) {
	return framework.uninstall(type, name, options);
};

global.RESOURCE = function(name, key) {
	return framework.resource(name, key);
};

global.TRANSLATE = function(name, key) {
	return framework.translate(name, key);
};

global.TRANSLATOR = function(name, text) {
	return framework.translator(name, text);
};

global.LOG = function() {
	return framework.log.apply(framework, arguments);
};

global.LOGGER = function() {
	return framework.logger.apply(framework, arguments);
};

global.MODEL = function(name) {
	return framework.model(name);
};

global.GETSCHEMA = function(group, name, fn, timeout) {
	return framework_builders.getschema(group, name, fn, timeout);
};

global.CREATE = function(group, name) {
	return framework_builders.getschema(group, name).default();
};

global.SCRIPT = function(body, value, callback) {
	return F.script(body, value, callback);
};

global.UID = function() {
	var plus = UIDGENERATOR.index % 2 ? 1 : 0;
	return UIDGENERATOR.date + (UIDGENERATOR.index++).padLeft(4, '0') + UIDGENERATOR.instance + plus;
};

global.MAKE = global.TRANSFORM = function(transform, fn) {

	if (typeof(transform) === 'function') {
		var tmp = fn;
		fn = transform;
		transform = tmp;
	}

	var obj;

	if (typeof(fn) === 'function') {
		obj = {};
		fn.call(obj, obj);
	} else
		obj = fn;

	return transform ? TransformBuilder.transform.apply(obj, arguments) : obj;
};

global.SINGLETON = function(name, def) {
	return SINGLETONS[name] || (SINGLETONS[name] = (new Function('return ' + (def || '{}')))());
};

global.NEWTRANSFORM = function(name, fn, isDefault) {
	return TransformBuilder.addTransform.apply(this, arguments);
};

/**
 * Creates schema in the specific group
 * @param {String} [group=default]
 * @param {String} name
 * @return {SchemaBuilderEntity}
 */
global.NEWSCHEMA = function(group, name) {
	if (!name) {
		name = group;
		group = 'default';
	}
	return framework_builders.newschema(group, name);
};

global.EACHSCHEMA = function(group, fn) {
	return framework_builders.eachschema(group, fn);
};

global.FUNCTION = function(name) {
	return framework.functions[name];
};

global.ROUTING = function(name) {
	return framework.routing(name);
};

global.SCHEDULE = function(date, each, fn) {
	return framework.schedule(date, each, fn);
};

global.FINISHED = function(stream, callback) {
	framework_internal.onFinished(stream, callback);
};

global.DESTROY = function(stream) {
	framework_internal.destroyStream(stream);
};

global.CLEANUP = function(stream, callback) {

	var fn = function() {
		FINISHED(stream, function() {
			DESTROY(stream);
			if (callback) {
				callback();
				callback = null;
			}
		});
	};

	stream.on('error', fn);

	if (stream.readable)
		stream.on('end', fn);
	else
		stream.on('finish', fn);
};

global.SUCCESS = function(success, value) {

	if (typeof(success) === 'function') {
		return function(err, value) {
			success(err, SUCCESS(err, value));
		};
	}

	var err;

	if (success instanceof Error) {
		err = success;
		success = false;
	} else if (success instanceof framework_builders.ErrorBuilder) {
		if (success.hasError()) {
			err = success.output();
			success = false;
		} else
			success = true;
	} else if (success == null)
		success = true;

	SUCCESSHELPER.success = success ? true : false;
	SUCCESSHELPER.value = value == null ? undefined : value;
	SUCCESSHELPER.error = err ? err : undefined;
	return SUCCESSHELPER;
};

global.TRY = function(fn, err) {
	try {
		fn();
		return true;
	} catch (e) {
		err && err(e);
		return false;
	}
};

global.OBSOLETE = function(name, message) {
	console.log(':: OBSOLETE / IMPORTANT ---> "' + name + '"', message);
	if (global.framework)
		framework.stats.other.obsolete++;
};

global.DEBUG = false;
global.TEST = false;
global.RELEASE = false;
global.is_client = false;
global.is_server = true;

var directory = framework_utils.$normalize(require.main ? Path.dirname(require.main.filename) : process.cwd());

// F._service() changes the values below:
var DATE_EXPIRES = new Date().add('y', 1).toUTCString();

const UIDGENERATOR = { date: new Date().format('yyMMddHHmm'), instance: 'abcdefghijklmnoprstuwxy'.split('').random().join('').substring(0, 3), index: 1 };
const controller_error_status = function(controller, status, problem) {

	if (status !== 500 && problem)
		controller.problem(problem);

	if (controller.res.success || controller.res.headersSent || !controller.isConnected)
		return controller;

	controller.precache && controller.precache(null, null, null);
	controller.req.path = EMPTYARRAY;
	controller.subscribe.success();
	controller.subscribe.route = framework.lookup(controller.req, '#' + status, EMPTYARRAY, 0);
	controller.subscribe.exception = problem;
	controller.subscribe.execute(status, true);
	return controller;
};

function Framework() {

	this.id = null;
	this.version = 2300;
	this.version_header = '2.3.0';
	this.version_node = process.version.toString().replace('v', '').replace(/\./g, '').parseFloat();

	this.config = {

		debug: false,
		trace: true,

		name: 'Total.js',
		version: '1.0.0',
		author: '',
		secret: Os.hostname() + '-' + Os.platform() + '-' + Os.arch(),

		'etag-version': '',
		'directory-controllers': '/controllers/',
		'directory-views': '/views/',
		'directory-definitions': '/definitions/',
		'directory-temp': '/tmp/',
		'directory-models': '/models/',
		'directory-resources': '/resources/',
		'directory-public': '/public/',
		'directory-public-virtual': '/app/',
		'directory-modules': '/modules/',
		'directory-source': '/source/',
		'directory-logs': '/logs/',
		'directory-tests': '/tests/',
		'directory-databases': '/databases/',
		'directory-workers': '/workers/',
		'directory-packages': '/packages/',
		'directory-private': '/private/',
		'directory-isomorphic': '/isomorphic/',
		'directory-configs': '/configs/',
		'directory-services': '/services/',
		'directory-themes': '/themes/',

		// all HTTP static request are routed to directory-public
		'static-url': '',
		'static-url-script': '/js/',
		'static-url-style': '/css/',
		'static-url-image': '/img/',
		'static-url-video': '/video/',
		'static-url-font': '/fonts/',
		'static-url-download': '/download/',
		'static-accepts': { '.jpg': true, '.png': true, '.gif': true, '.ico': true, '.js': true, '.css': true, '.txt': true, '.xml': true, '.woff': true, '.woff2': true, '.otf': true, '.ttf': true, '.eot': true, '.svg': true, '.zip': true, '.rar': true, '.pdf': true, '.docx': true, '.xlsx': true, '.doc': true, '.xls': true, '.html': true, '.htm': true, '.appcache': true, '.manifest': true, '.map': true, '.ogv': true, '.ogg': true, '.mp4': true, '.mp3': true, '.webp': true, '.webm': true, '.swf': true, '.package': true, '.json': true, '.md': true, '.m4v': true, '.jsx': true },

		// 'static-accepts-custom': [],

		'default-layout': 'layout',
		'default-theme': '',

		// default maximum request size / length
		// default 10 kB
		'default-request-length': 10,
		'default-websocket-request-length': 2,
		'default-websocket-encodedecode': true,
		'default-maximum-file-descriptors': 0,
		'default-timezone': '',
		'default-root': '',
		'default-response-maxage': '11111111',

		// Seconds (2 minutes)
		'default-cors-maxage': 120,

		// in milliseconds
		'default-request-timeout': 5000,

		// otherwise is used ImageMagick (Heroku supports ImageMagick)
		// gm = graphicsmagick or im = imagemagick
		'default-image-converter': 'gm',
		'default-image-quality': 93,

		'allow-handle-static-files': true,
		'allow-gzip': true,
		'allow-websocket': true,
		'allow-compile-script': true,
		'allow-compile-style': true,
		'allow-compile-html': true,
		'allow-performance': false,
		'allow-custom-titles': false,
		'allow-compatibility': false,
		'allow-cache-snapshot': false,
		'disable-strict-server-certificate-validation': true,
		'disable-clear-temporary-directory': false,

		// Used in framework._service()
		// All values are in minutes
		'default-interval-clear-resources': 20,
		'default-interval-clear-cache': 7,
		'default-interval-precompile-views': 61,
		'default-interval-websocket-ping': 3,
		'default-interval-clear-dnscache': 120
	};

	this.global = {};
	this.resources = {};
	this.connections = {};
	this.functions = {};
	this.themes = {};
	this.versions = null;
	this.workflows = {};
	this.schedules = [];

	this.isDebug = true;
	this.isTest = false;
	this.isLoaded = false;
	this.isWorker = true;
	this.isCluster = require('cluster').isWorker;

	this.routes = {
		sitemap: null,
		web: [],
		files: [],
		cors: [],
		websockets: [],
		middleware: {},
		redirects: {},
		resize: {},
		request: [],
		views: {},
		merge: {},
		mapping: {},
		packages: {},
		blocks: {},
		resources: {},
		mmr: {}
	};

	this.behaviours = null;
	this.modificators = null;
	this.helpers = {};
	this.modules = {};
	this.models = {};
	this.sources = {};
	this.controllers = {};
	this.dependencies = {};
	this.isomorphic = {};
	this.tests = [];
	this.errors = [];
	this.problems = [];
	this.changes = [];
	this.server = null;
	this.port = 0;
	this.ip = '';

	this.validators = {
		email: new RegExp('^[a-zA-Z0-9-_.+]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'),
		url: /^(http|https):\/\/(?:(?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)*(?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@)?(?:(?:[a-z0-9\-\.]|%[0-9a-f]{2})+|(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]))(?::[0-9]+)?(?:[\/|\?](?:[\w#!:\.\?\+=&@!$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})*)?$/i,
		phone: /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im,
		zip: /^\d{5}(?:[-\s]\d{4})?$/,
		uid: /^\d{14,}[a-z]{3}[01]{1}$/
	};

	this.workers = {};
	this.databases = {};
	this.directory = HEADERS.workers.cwd = directory;
	this.isLE = Os.endianness ? Os.endianness() === 'LE' : true;
	this.isHTTPS = false;
	this.datetime = new Date();

	// It's hidden
	// this.waits = {};

	this.temporary = {
		path: {},
		processing: {},
		range: {},
		views: {},
		dependencies: {}, // temporary for module dependencies
		other: {},
		internal: {} // internal controllers/modules names for the routing
	};

	this.stats = {

		other: {
			websocketPing: 0,
			websocketCleaner: 0,
			obsolete: 0,
			restart: 0
		},

		request: {
			request: 0,
			pending: 0,
			web: 0,
			xhr: 0,
			file: 0,
			websocket: 0,
			get: 0,
			options: 0,
			head: 0,
			post: 0,
			put: 0,
			path: 0,
			upload: 0,
			mmr: 0,
			blocked: 0,
			'delete': 0,
			mobile: 0,
			desktop: 0
		},
		response: {
			view: 0,
			json: 0,
			websocket: 0,
			timeout: 0,
			custom: 0,
			binary: 0,
			pipe: 0,
			file: 0,
			destroy: 0,
			stream: 0,
			streaming: 0,
			plain: 0,
			empty: 0,
			redirect: 0,
			forward: 0,
			restriction: 0,
			notModified: 0,
			sse: 0,
			mmr: 0,
			error400: 0,
			error401: 0,
			error403: 0,
			error404: 0,
			error408: 0,
			error431: 0,
			error500: 0,
			error501: 0
		}
	};

	// intialize cache
	this.cache = new FrameworkCache();
	this.path = new FrameworkPath();
	this.restrictions = new FrameworkRestrictions();

	this._request_check_redirect = false;
	this._request_check_referer = false;
	this._request_check_POST = false;
	this._request_check_robot = false;
	this._length_middleware = 0;
	this._length_request_middleware = 0;
	this._length_files = 0;
	this._length_wait = 0;
	this._length_themes = 0;
	this._length_cors = 0;
	this._length_subdomain_web = 0;
	this._length_subdomain_websocket = 0;

	this.isVirtualDirectory = false;
	this.isTheme = false;
	this.isWindows = Os.platform().substring(0, 3).toLowerCase() === 'win';
}

// ======================================================
// PROTOTYPES
// ======================================================

Framework.prototype = {
	get onLocate() {
		return this.onLocale;
	},
	set onLocate(value) {
		OBSOLETE('F.onLocate', 'Rename "F.onLocate" method for "F.onLocale".');
		this.onLocale = value;
	}
}

Framework.prototype.__proto__ = Object.create(Events.EventEmitter.prototype, {
	constructor: {
		value: WebSocket,
		enumberable: false
	}
});

/**
 * Internal function
 * @return {String} Returns current (dependency type and name) owner.
 */
Framework.prototype.$owner = function() {
	return _owner;
};

/**
 * Adds a new behaviour
 * @param {String} url A relative URL address.
 * @param {String Array} flags
 * @return {Framework}
 */
Framework.prototype.behaviour = function(url, flags) {
	var self = this;

	if (!self.behaviours)
		self.behaviours = {};

	if (typeof(flags) === 'string')
		flags = [flags];

	url = framework_internal.preparePath(url);

	if (!self.behaviours[url])
		self.behaviours[url] = {};

	for (var i = 0; i < flags.length; i++)
		self.behaviours[url][flags[i]] = true;

	return self;
};

Framework.prototype.isSuccess = function(obj) {
	return obj === SUCCESSHELPER;
};

/**
 * Get a controller
 * @param {String} name
 * @return {Object}
 */
Framework.prototype.controller = function(name) {
	return this.controllers[name] || null;
};

/**
 * Use configuration
 * @param {String} filename
 * @return {Framework}
 */
Framework.prototype.useConfig = function(name) {
	return this._configure(name, true);
};

/**
 * Sort all routes
 * @return {Framework}
 */
Framework.prototype._routesSort = function(type) {

	var self = this;

	self.routes.web.sort(function(a, b) {
		if (a.priority > b.priority)
			return -1;
		if (a.priority < b.priority)
			return 1;
		return 0;
	});

	self.routes.websockets.sort(function(a, b) {
		if (a.priority > b.priority)
			return -1;
		if (a.priority < b.priority)
			return 1;
		return 0;
	});

	var cache = {};
	var length = self.routes.web.length;
	var url;

	for (var i = 0; i < length; i++) {
		var route = self.routes.web[i];
		var name = self.temporary.internal[route.controller];
		if (name)
			route.controller = name;
		if (!route.isMOBILE || route.isUPLOAD || route.isXHR || route.isJSON || route.isSYSTEM || route.isXML || route.flags.indexOf('get') === -1)
			continue;
		url = route.url.join('/');
		cache[url] = true;
	}

	for (var i = 0; i < length; i++) {
		var route = self.routes.web[i];
		if (route.isMOBILE || route.isUPLOAD || route.isXHR || route.isJSON || route.isSYSTEM || route.isXML || route.flags.indexOf('get') === -1)
			continue;
		url = route.url.join('/');
		route.isMOBILE_VARY = cache[url] === true;
	}

	(!type || type === 1) && self.routes.web.forEach(function(route) {
		var tmp = self.routes.web.findItem(function(item) {
			return item.hash === route.hash && item !== route;
		});
		route.isUNIQUE = tmp == null;
	});

	return self;
};

Framework.prototype.script = function(body, value, callback) {

	var fn;

	try {
		fn = new Function('next', 'value', 'now', 'var model=value;var global,require,process,GLOBAL,root,clearImmediate,clearInterval,clearTimeout,setImmediate,setInterval,setTimeout,console,$STRING,$VIEWCACHE,framework_internal,TransformBuilder,Pagination,Page,URLBuilder,UrlBuilder,SchemaBuilder,framework_builders,framework_utils,framework_mail,Image,framework_image,framework_nosql,Builders,U,utils,Utils,Mail,WTF,SOURCE,INCLUDE,MODULE,NOSQL,NOBIN,NOCOUNTER,NOSQLMEMORY,NOMEM,DATABASE,DB,CONFIG,INSTALL,UNINSTALL,RESOURCE,TRANSLATOR,LOG,LOGGER,MODEL,GETSCHEMA,CREATE,UID,TRANSFORM,MAKE,SINGLETON,NEWTRANSFORM,NEWSCHEMA,EACHSCHEMA,FUNCTION,ROUTING,SCHEDULE,OBSOLETE,DEBUG,TEST,RELEASE,is_client,is_server,F,framework,Controller,setTimeout2,clearTimeout2,String,Number,Boolean,Object,Function,Date,isomorphic,I,eval;try{' + body + '}catch(e){next(e)}');
	} catch(e) {
		callback && callback(e);
		return this;
	}

	fn.call(EMPTYOBJECT, function(value) {

		if (!callback)
			return;

		if (value instanceof Error)
			callback(value);
		else
			callback(null, value);

	}, value, this.datetime);

	return this;
};

/**
 * Get a database instance
 * @param {String} name Database name (optional)
 * @return {Database}
 */
Framework.prototype.database = function(name) {
	return this.nosql(name);
};

/**
 * Get a database instance (NoSQL embedded)
 * @param {String} name Database name (optional)
 * @return {Database}
 */
Framework.prototype.nosql = function(name) {
	var self = this;
	var db = self.databases[name];
	if (db)
		return db;
	self.path.verify('databases');
	db = framework_nosql.load(name, self.path.databases(name));
	self.databases[name] = db;
	return db;
};

/**
 * Stop application
 * @param {String} signal
 * @return {Framework}
 */
Framework.prototype.stop = Framework.prototype.kill = function(signal) {

	var self = this;

	for (var m in framework.workers) {
		var worker = framework.workers[m];
		TRY(() => worker && worker.kill && worker.kill(signal || 'SIGTERM'));
	}

	framework.emit('exit', signal);

	if (!self.isWorker && typeof(process.send) === 'function')
		TRY(() => process.send('stop'));

	self.cache.stop();
	self.server && self.server.close();

	setTimeout(() => process.exit(signal || 'SIGTERM'), TEST ? 2000 : 100);
	return self;
};

/**
 * Add a route redirect
 * @param {String} host Domain with protocol.
 * @param {String} newHost Domain with protocol.
 * @param {Boolean} withPath Copy path (default: true).
 * @param {Boolean} permanent Is permanent redirect (302)? (default: false)
 * @return {Framework}
 */
Framework.prototype.redirect = function(host, newHost, withPath, permanent) {

	var self = this;
	var external = host.startsWith('http://') || host.startsWith('https');

	if (external) {

		if (host[host.length - 1] === '/')
			host = host.substring(0, host.length - 1);

		if (newHost[newHost.length - 1] === '/')
			newHost = newHost.substring(0, newHost.length - 1);

		self.routes.redirects[host] = {
			url: newHost,
			path: withPath,
			permanent: permanent
		};

		self._request_check_redirect = true;
		return self;
	}

	if (host[0] !== '/')
		host = '/' + host;

	var flags;

	if (withPath instanceof Array) {
		flags = withPath;
		withPath = permanent === true;
	} else if (permanent instanceof Array) {
		flags = permanent;
		withPath = withPath === true;
	} else
		withPath = withPath === true;

	permanent = withPath;

	if (framework_utils.isStaticFile(host)) {
		framework.file(host, function(req, res) {
			if (newHost.startsWith('http://') || newHost.startsWith('https://'))
				res.redirect(newHost, permanent);
			else
				res.redirect(newHost[0] !== '/' ? '/' + newHost : newHost, permanent);
		});
		return;
	}

	framework.route(host, function() {

		if (newHost.startsWith('http://') || newHost.startsWith('https://')) {
			this.redirect(newHost + this.href(), permanent);
			return;
		}

		if (newHost[0] !== '/')
			newHost = '/' + newHost;

		this.redirect(newHost + this.href(), permanent);
	}, flags);

	return self;
};

/**
 * Schedule job
 * @param {Date or String} date
 * @param {Boolean} repeat Repeat schedule
 * @param {Function} fn
 * @return {Framework}
 */
Framework.prototype.schedule = function(date, repeat, fn) {

	if (fn === undefined) {
		fn = repeat;
		repeat = false;
	}

	var self = this;
	var type = typeof(date);

	if (type === 'string')
		date = date.parseDate();
	else if (type === 'number')
		date = new Date(date);

	var sum = date.getTime();
	var id = framework_utils.GUID(5) + framework_utils.random(10000);

	if (repeat)
		repeat = repeat.replace('each', '1');

	self.schedules.push({ expire: sum, fn: fn, repeat: repeat });
	return self;
};

/**
 * Auto resize picture according the path
 * @param {String} url Relative path.
 * @param {Function(image)} fn Processing.
 * @param {String Array} flags Optional, can contains extensions `.jpg`, `.gif' or watching path `/img/gallery/`
 * @return {Framework}
 */
Framework.prototype.resize = function(url, fn, flags) {

	var self = this;
	var extensions = {};
	var cache = true;

	if (typeof(flags) === 'function') {
		var tmp = flags;
		flags = fn;
		fn = tmp;
	}

	var ext = url.match(/\*.\*$|\*?\.(jpg|png|gif|jpeg)$/gi);
	if (ext) {
		url = url.replace(ext, '');
		switch (ext.toString().toLowerCase()) {
			case '*.*':
				extensions['*'] = true;
				break;
			case '*.jpg':
			case '*.gif':
			case '*.png':
			case '*.jpeg':
				extensions[ext.toString().toLowerCase().replace(/\*/g, '')] = true;
				break;
		}
	}

	var path = url;

	if (flags && flags.length) {
		for (var i = 0, length = flags.length; i < length; i++) {
			var flag = flags[i];

			if (flag[0] === '.') {
				extensions[flag] = true;
				continue;
			}

			if (flag[0] === '~' || flag[0] === '/' || flag.match(/^http\:|https\:/gi)) {
				path = flag;
				continue;
			}

			if (flag === 'nocache')
				cache = false;
		}
	}

	if (!extensions.length) {
		extensions['.jpg'] = true;
		extensions['.jpeg'] = true;
		extensions['.png'] = true;
		extensions['.gif'] = true;
	}

	if (extensions['.jpg'] && !extensions['.jpeg'])
		extensions['.jpeg'] = true;
	else if (extensions['.jpeg'] && !extensions['.jpg'])
		extensions['.jpg'] = true;

	self.routes.resize[url] = {
		fn: fn,
		path: framework_utils.path(path || url),
		ishttp: path.match(/http\:|https\:/gi) ? true : false,
		extension: extensions,
		cache: cache
	};

	return self;
};

/**
 * RESTful routing
 * @param {String} url A relative url.
 * @param {String Array} flags
 * @param {Function} onQuery
 * @param {Function(id)} onGet
 * @param {Function([id])} onSave
 * @param {Function(id)} onDelete
 * @return {Framework}
 */
Framework.prototype.restful = function(url, flags, onQuery, onGet, onSave, onDelete) {

	var self = this;
	var tmp;

	if (onQuery) {
		tmp = [];
		flags && tmp.push.apply(tmp, flags);
		self.route(url, tmp, onQuery);
	}

	var restful = framework_utils.path(url) + '{id}';

	if (onGet) {
		tmp = [];
		flags && tmp.push.apply(tmp, flags);
		self.route(restful, tmp, onGet);
	}

	if (onSave) {
		tmp = ['post'];
		flags && tmp.push.apply(tmp, flags);
		self.route(url, tmp, onSave);
		tmp = ['put'];
		flags && tmp.push.apply(tmp, flags);
		self.route(restful, tmp, onSave);
	}

	if (onDelete) {
		tmp = ['delete'];
		flags && tmp.push.apply(tmp, flags);
		self.route(restful, tmp, onDelete);
	}

	return self;
};

/**
 * Register cors
 * @param {String} url
 * @param {String Array or String} origin
 * @param {String Array or String} methods
 * @param {String Array or String} headers
 * @param {Boolean} credentials
 * @return {Framework}
 */
Framework.prototype.cors = function(url, flags, credentials) {

	var self = this;
	var route = {};
	var tmp;

	var origins = [];
	var methods = [];
	var headers = [];
	var age;

	if (flags instanceof Array) {
		for (var i = 0, length = flags.length; i < length; i++) {
			var flag = flags[i];
			var type = typeof(flag);

			if (type === 'string')
				flag = flag.toLowerCase();
			else if (type === 'number') {
				age = flag;
				continue;
			}

			if (type === 'boolean' || flag.startsWith('credential')) {
				credentials = true;
				continue;
			}

			if (flag.startsWith('http://') || flag.startsWith('https://')) {
				origins.push(flag);
				continue;
			}

			switch (flag) {
				case 'post':
				case 'put':
				case 'delete':
				case 'options':
				case 'patch':
				case 'head':
				case 'get':
					methods.push(flag.toUpperCase());
					break;
				default:
					headers.push(flags[i]);
					break;
			}
		}
	}

	route.isASTERIX = url.lastIndexOf('*') !== -1;

	if (route.isASTERIX)
		url = url.replace('*', '');

	url = framework_internal.preparePath(framework_internal.encodeUnicodeURL(url.trim()));

	route.hash = url.hash();
	route.url = framework_internal.routeSplitCreate(url);
	route.origins = origins.length ? origins : null;
	route.methods = methods.length ? methods : null;
	route.headers = headers.length ? headers : null;
	route.credentials = credentials;
	route.age = age || framework.config['default-cors-maxage'];

	self.routes.cors.push(route);
	self._length_cors = self.routes.cors.length;

	self.routes.cors.sort(function(a, b) {
		var al = a.url.length;
		var bl = b.url.length;
		if (al > bl)
			return -1;
		if (al < bl)
			return 1;
		return a.isASTERIX && b.isASTERIX ? 1 : 0;
	});

	return self;
};

Framework.prototype.group = function(flags, fn) {
	_flags = flags;
	fn.call(this);
	_flags = undefined;
	return this;
};

/**
 * Add a route
 * @param {String} url
 * @param {Function} funcExecute Action.
 * @param {String Array} flags
 * @param {Number} length Maximum length of request data.
 * @param {String Array} middleware Loads custom middleware.
 * @param {Number timeout Response timeout.
 * @return {Framework}
 */
Framework.prototype.web = Framework.prototype.route = function(url, funcExecute, flags, length, language) {

	var name;
	var tmp;
	var viewname;
	var self = this;
	var skip = true;
	var sitemap;
	var sitemap_language = language !== undefined;

	if (url instanceof Array) {
		url.forEach(url => self.route(url, funcExecute, flags, length));
		return self;
	}

	var CUSTOM = typeof(url) === 'function' ? url : null;
	if (CUSTOM)
		url = '/';

	if (url[0] === '#') {
		url = url.substring(1);
		if (url !== '400' && url !== '401' && url !== '403' && url !== '404' && url !== '408' && url !== '431' && url !== '500' && url !== '501') {

			var sitemapflags = funcExecute instanceof Array ? funcExecute : flags;
			if (!(sitemapflags instanceof Array))
				sitemapflags = EMPTYARRAY;

			sitemap = self.sitemap(url, true, language);
			if (sitemap) {

				name = url;
				url = sitemap.url;
				if (sitemap.wildcard)
					url += '*';

				if (sitemap.localizeUrl) {
					if (!F.temporary.internal.resources) {
						try {
							F.temporary.internal.resources = Fs.readdirSync(self.path.resources()).map(n => n.substring(0, n.lastIndexOf('.')));
						} catch (e) {
							F.temporary.internal.resources = [];
						}
					}

					if (language === undefined) {
						sitemap_language = true;
						var sitemaproutes = {};

						F.temporary.internal.resources.forEach(function(language) {
							var item = self.sitemap(sitemap.id, true, language);
							if (item.url && item.url !== url)
								sitemaproutes[item.url] = { name: sitemap.id, language: language };
						});

						Object.keys(sitemaproutes).forEach(key => self.route('#' + sitemap.id, funcExecute, flags, length, sitemaproutes[key].language));
					}
				}

			} else
				throw new Error('Sitemap item "' + url + '" not found.');
		} else
			url = '#' + url;
	}

	if (!url)
		url = '/';

	if (url[0] !== '[' && url[0] !== '/')
		url = '/' + url;

	if (url.endsWith('/'))
		url = url.substring(0, url.length - 1);

	url = framework_internal.encodeUnicodeURL(url);

	var type = typeof(funcExecute);
	var index = 0;
	var urlcache = url;

	if (!name)
		name = url;

	if (type === 'object' || funcExecute instanceof Array) {
		tmp = funcExecute;
		funcExecute = flags;
		flags = tmp;
	}

	var priority = 0;
	var subdomain = null;

	priority = url.count('/');

	if (url[0] === '[') {
		index = url.indexOf(']');
		if (index > 0) {
			subdomain = url.substring(1, index).trim().toLowerCase().split(',');
			url = url.substring(index + 1);
			priority += subdomain.indexOf('*') !== -1 ? 50 : 100;
		}
	}

	var isASTERIX = url.indexOf('*') !== -1;
	if (isASTERIX) {
		url = url.replace('*', '').replace('//', '/');
		priority = priority - 100;
	}

	var isRaw = false;
	var isNOXHR = false;
	var method = '';
	var schema;
	var workflow;
	var isMOBILE = false;
	var isJSON = false;
	var isDELAY = false;
	var isROBOT = false;
	var isBINARY = false;
	var isCORS = false;
	var isROLE = false;
	var middleware = null;
	var timeout;
	var options;
	var corsflags = [];
	var membertype = 0;
	var isGENERATOR = false;

	if (_flags) {
		if (!flags)
			flags = [];
		_flags.forEach(function(flag) {
			if (flags.indexOf(flag) === -1)
				flags.push(flag);
		});
	}

	if (flags) {

		tmp = [];
		var count = 0;

		for (var i = 0; i < flags.length; i++) {

			var tt = typeof(flags[i]);

			if (tt === 'number') {
				timeout = flags[i];
				continue;
			}

			if (tt === 'object') {
				options = flags[i];
				continue;
			}

			var first = flags[i][0];

			if (first === '&') {
				// resource (sitemap localization)
				// doesn't used now
				continue;
			}

			if (first === '%') {
				self.behaviour(url === '' ? '/' : url, flags[i].substring(1));
				continue;
			}

			if (first === '#') {
				if (!middleware)
					middleware = [];
				middleware.push(flags[i].substring(1));
				continue;
			}

			if (first === '*') {

				workflow = flags[i].trim().substring(1);
				index = workflow.indexOf('-->');

				if (index !== -1) {
					schema = workflow.substring(0, index).trim();
					workflow = workflow.substring(index + 3).trim();
				} else {
					schema = workflow;
					workflow = null;
				}
				schema = schema.replace(/\\/g, '/').split('/');

				if (schema.length === 1) {
					schema[1] = schema[0];
					schema[0] = 'default';
				}

				index = schema[1].indexOf('#');
				if (index !== -1) {
					schema[2] = schema[1].substring(index + 1).trim();
					schema[1] = schema[1].substring(0, index).trim();
				}

				continue;
			}

			var flag = flags[i].toString().toLowerCase();

			if (flag.startsWith('http://') || flag.startsWith('https://')) {
				corsflags.push(flag);
				continue;
			}

			count++;

			switch (flag) {

				case 'json':
					isJSON = true;
					continue;

				case 'delay':
					count--;
					isDELAY = true;
					continue;

				case 'binary':
					isBINARY = true;
					continue;

				case 'cors':
					isCORS = true;
					count--;
					continue;

				case 'credential':
				case 'credentials':
					corsflags.push(flag);
					count--;
					continue;

				case 'sync':
				case 'yield':
				case 'synchronize':
					isGENERATOR = true;
					count--;
					continue;

				case 'noxhr':
				case '-xhr':
					isNOXHR = true;
					continue;
				case 'raw':
					isRaw = true;
					tmp.push(flag);
					break;
				case 'mobile':
					isMOBILE = true;
					break;
				case 'robot':
					isROBOT = true;
					self._request_check_robot = true;
					break;
				case 'authorize':
				case 'authorized':
				case 'logged':
					membertype = 1;
					priority += 2;
					tmp.push('authorize');
					break;
				case 'unauthorize':
				case 'unauthorized':
				case 'unlogged':
					membertype = 2;
					priority += 2;
					tmp.push('unauthorize');
					break;
				case 'referer':
				case 'referrer':
					tmp.push('referer');
					break;
				case 'delete':
				case 'get':
				case 'head':
				case 'options':
				case 'patch':
				case 'post':
				case 'propfind':
				case 'put':
				case 'trace':
					tmp.push(flag);
					method += (method ? ',' : '') + flag;
					corsflags.push(flag);
					break;
				default:
					if (flag[0] === '@')
						isROLE = true;
					tmp.push(flag);
					break;
			}
		}
		flags = tmp;
		priority += (count * 2);
	} else {
		flags = ['get'];
		method = 'get';
	}

	if (type === 'string') {
		viewname = funcExecute;
		funcExecute = (function(name, sitemap, language) {
			if (language && !this.language)
				this.language = language;
			var themeName = framework_utils.parseTheme(name);
			if (themeName)
				name = prepare_viewname(name);
			return function() {
				sitemap && this.sitemap(sitemap.id, language);
				if (name[0] === '~')
					this.themeName = '';
				else if (themeName)
					this.themeName = themeName;

				if (!this.route.workflow)
					return this.view(name);
				var self = this;
				this.$exec(this.route.workflow, this, function(err, response) {
					if (err)
						self.content(err);
					else
						self.view(name, response);
				});
			};
		})(viewname, sitemap, language);
	} else if (typeof(funcExecute) !== 'function') {

		viewname = (sitemap && sitemap.url !== '/' ? sitemap.id : workflow ? '' : url) || '';

		if (!workflow || (!viewname && !workflow)) {
			if (viewname.endsWith('/'))
				viewname = viewname.substring(0, viewname.length - 1);

			index = viewname.lastIndexOf('/');
			if (index !== -1)
				viewname = viewname.substring(index + 1);

			if (!viewname || viewname === '/')
				viewname = 'index';

			funcExecute = (function(name, sitemap, language) {
				return function() {
					if (language && !this.language)
						this.language = language;
					sitemap && this.sitemap(sitemap.id, language);
					name[0] === '~' && this.theme('');
					if (!this.route.workflow)
						return this.view(name);
					var self = this;
					this.$exec(this.route.workflow, this, function(err, response) {
						if (err)
							self.content(err);
						else
							self.view(name, response);
					});
				};
			})(viewname, sitemap, language);
		} else if (workflow)
			funcExecute = controller_json_workflow;
	}

	if (!isGENERATOR)
		isGENERATOR = (funcExecute.constructor.name === 'GeneratorFunction' || funcExecute.toString().indexOf('function*') === 0);

	var url2 = framework_internal.preparePath(url.trim());
	var hash = url2.hash();
	var routeURL = framework_internal.routeSplitCreate(url2);
	var arr = [];
	var reg = null;
	var regIndex = null;

	if (url.indexOf('{') !== -1) {
		routeURL.forEach(function(o, i) {
			if (o.substring(0, 1) !== '{')
				return;

			arr.push(i);

			var sub = o.substring(1, o.length - 1);

			if (sub[0] !== '/')
				return;

			var index = sub.lastIndexOf('/');
			if (index === -1)
				return;

			if (!reg) {
				reg = {};
				regIndex = [];
			}

			reg[i] = new RegExp(sub.substring(1, index), sub.substring(index + 1));
			regIndex.push(i);
		});

		priority -= arr.length;
	}

	if (url.indexOf('#') !== -1)
		priority -= 100;

	if (flags.indexOf('proxy') !== -1) {
		isJSON = true;
		priority++;
	}

	if ((isJSON || flags.indexOf('xml') !== -1 || isRaw) && (flags.indexOf('delete') === -1 && flags.indexOf('post') === -1 && flags.indexOf('put') === -1) && flags.indexOf('patch') === -1) {
		flags.push('post');
		method += (method ? ',' : '') + 'post';
		priority++;
	}

	if (flags.indexOf('upload') !== -1) {
		if (flags.indexOf('post') === -1 && flags.indexOf('put') === -1) {
			flags.push('post');
			method += (method ? ',' : '') + 'post';
		}
	}

	if (flags.indexOf('get') === -1 &&
		flags.indexOf('options') === -1 &&
		flags.indexOf('post') === -1 &&
		flags.indexOf('delete') === -1 &&
		flags.indexOf('put') === -1 &&
		flags.indexOf('upload') === -1 &&
		flags.indexOf('head') === -1 &&
		flags.indexOf('trace') === -1 &&
		flags.indexOf('patch') === -1 &&
		flags.indexOf('propfind') === -1) {
			flags.push('get');
			method += (method ? ',' : '') + 'get';
		}

	if (flags.indexOf('referer') !== -1)
		self._request_check_referer = true;

	if (!self._request_check_POST && (flags.indexOf('delete') !== -1 || flags.indexOf('post') !== -1 || flags.indexOf('put') !== -1 || flags.indexOf('upload') !== -1 || flags.indexOf('json') !== -1 || flags.indexOf('patch') !== -1 || flags.indexOf('options') !== -1))
		self._request_check_POST = true;

	var isMULTIPLE = false;

	if (method.indexOf(',') !== -1)
		isMULTIPLE = true;

	if (method.indexOf(',') !== -1 || method === '')
		method = undefined;
	else
		method = method.toUpperCase();

	if (name[1] === '#')
		name = name.substring(1);

	if (isBINARY && !isRaw) {
		isBINARY = false;
		console.warn('framework.route() skips "binary" flag because the "raw" flag is not defined.');
	}

	if (subdomain)
		self._length_subdomain_web++;

	self.routes.web.push({
		hash: hash,
		name: name,
		priority: priority,
		schema: schema,
		workflow: workflow,
		subdomain: subdomain,
		controller: _controller ? _controller : 'unknown',
		url: routeURL,
		param: arr,
		flags: flags || EMPTYARRAY,
		flags2: flags_to_object(flags),
		method: method,
		execute: funcExecute,
		length: (length || self.config['default-request-length']) * 1024,
		middleware: middleware,
		timeout: timeout === undefined ? (isDELAY ? 0 : self.config['default-request-timeout']) : timeout,
		isGET: flags.indexOf('get') !== -1,
		isMULTIPLE: isMULTIPLE,
		isJSON: isJSON,
		isXML: flags.indexOf('xml') !== -1,
		isRAW: isRaw,
		isBINARY: isBINARY,
		isMOBILE: isMOBILE,
		isROBOT: isROBOT,
		isMOBILE_VARY: isMOBILE,
		isGENERATOR: isGENERATOR,
		MEMBER: membertype,
		isASTERIX: isASTERIX,
		isROLE: isROLE,
		isREFERER: flags.indexOf('referer') !== -1,
		isHTTPS: flags.indexOf('https') !== -1,
		isHTTP: flags.indexOf('http') !== -1,
		isDEBUG: flags.indexOf('debug') !== -1,
		isRELEASE: flags.indexOf('release') !== -1,
		isPROXY: flags.indexOf('proxy') !== -1,
		isBOTH: isNOXHR ? false : true,
		isXHR: flags.indexOf('xhr') !== -1,
		isUPLOAD: flags.indexOf('upload') !== -1,
		isSYSTEM: url.startsWith('/#'),
		isCACHE: !url.startsWith('/#') && !CUSTOM && !arr.length && !isASTERIX,
		isPARAM: arr.length > 0,
		isDELAY: isDELAY,
		CUSTOM: CUSTOM,
		options: options,
		regexp: reg,
		regexpIndexer: regIndex
	});

	self.emit('route', 'web', self.routes.web[self.routes.web.length - 1]);

	// Appends cors route
	isCORS && F.cors(urlcache, corsflags);
	!_controller && self._routesSort(1);

	return self;
};

function flags_to_object(flags) {
	var obj = {};
	flags.forEach(function(flag) {
		obj[flag] = true;
	});
	return obj;
}

Framework.prototype.mmr = function(url, process) {
	var self = this;
	url = framework_internal.preparePath(framework_utils.path(url));
	self.routes.mmr[url] = { exec: process };
	self._request_check_POST = true;
	return self;
};

/**
 * Get routing by name
 * @param {String} name
 * @return {Object}
 */
Framework.prototype.routing = function(name) {
	var self = this;
	for (var i = 0, length = self.routes.web.length; i < length; i++) {
		var route = self.routes.web[i];
		if (route.name === name) {
			var url = framework_utils.path(route.url.join('/'));
			if (url[0] !== '/')
				url = '/' + url;
			return { controller: route.controller, url: url, id: route.id, flags: route.flags, middleware: route.middleware, execute: route.execute, timeout: route.timeout, options: route.options, length: route.length };
		}
	}
};

/**
 * Merge files
 * @param {String} url Relative URL.
 * @param {String/String Array} file1 Filename or URL.
 * @param {String/String Array} file2 Filename or URL.
 * @param {String/String Array} file3 Filename or URL.
 * @param {String/String Array} fileN Filename or URL.
 * @return {Framework}
 */
Framework.prototype.merge = function(url) {

	var arr = [];
	var self = this;

	for (var i = 1, length = arguments.length; i < length; i++) {

		var items = arguments[i];
		if (!(items instanceof Array))
			items = [items];

		for (var j = 0, lengthsub = items.length; j < lengthsub; j++) {
			var fn = items[j];
			var c = fn[0];
			if (c === '@')
				fn = '~' + framework.path.package(fn.substring(1));
			else if (c === '=')
				fn = '~' + framework.path.themes(fn.substring(1));
			arr.push(fn);
		}
	}

	url = framework_internal.preparePath(self._version(url));

	if (url[0] !== '/')
		url = '/' + url;

	var filename = self.path.temp((self.id ? 'i-' + self.id + '_' : '') + 'merge-' + createTemporaryKey(url));
	self.routes.merge[url] = { filename: filename, files: arr };
	return self;
};

Framework.prototype.mapping = function(url, path) {
	return this.map.apply(this, arguments);
};

/**
 * Send message
 * @param  {Object} message
 * @param  {Object} handle
 * @return {Framework}
 */
Framework.prototype.send = function(message, handle) {
	process.send(message, handle);
	return this;
}

/**
 * Mapping of static file
 * @param {String} url
 * @param {String} filename	Filename or Directory.
 * @param {Function(filename) or String Array} filter
 * @return {Framework}
 */
Framework.prototype.map = function(url, filename, filter) {

	if (url[0] !== '/')
		url = '/' + url;

	var isPackage = false;
	var self = this;

	filename = framework_utils.$normalize(filename);
	url = framework_internal.preparePath(self._version(url));

	// isomorphic
	if (filename[0] === '#') {
		self.routes.mapping[url] = filename;
		return self;
	}

	var index = filename.indexOf('#');
	var block;

	if (index !== -1) {
		var tmp = filename.split('#');
		filename = tmp[0];
		block = tmp[1];
	}

	var c = filename[0];

	// package
	if (c === '@') {
		filename = self.path.package(filename.substring(1));
		isPackage = true;
	} else if (c === '=') {
		if (framework.isWindows)
			filename = framework_utils.combine(framework.config['directory-themes'], filename.substring(1));
		else
			filename = self.path.themes(filename.substring(1));
		isPackage = true;
	}

	var isFile = framework_utils.getExtension(filename).length > 0;

	// Checks if the directory exists
	if (!isPackage && !filename.startsWith(directory)) {
		var tmp = filename[0] === '~' ? self.path.root(filename.substring(1)) : self.path.public(filename);
		if (existsSync(tmp))
			filename = tmp;
	}

	if (isFile) {
		self.routes.mapping[url] = filename;
		if (block)
			self.routes.blocks[url] = block;
		return self;
	}

	url = framework_utils.path(url);
	filename = framework_utils.path(filename);

	var replace = filename;
	var plus = '';
	var isRoot = false;

	if (replace[0] === '/')
		isRoot = true;

	if (replace[0] === '~') {
		plus += '~';
		replace = replace.substring(1);
	}

	if (replace[0] === '.') {
		plus += '.';
		replace = replace.substring(1);
	}

	if (!isRoot && replace[0] === '/') {
		plus += '/';
		replace = replace.substring(1);
	}

	if (filter instanceof Array) {
		for (var i = 0, length = filter.length; i < length; i++) {
			if (filter[i][0] === '.')
				filter[i] = filter[i].substring(1);
			filter[i] = filter[i].toLowerCase();
		}
	}

	setTimeout(function() {
		framework_utils.ls(framework.isWindows ? filename.replace(/\//g, '\\') : filename, function(files) {
			for (var i = 0, length = files.length; i < length; i++) {

				if (framework.isWindows)
					files[i] = files[i].replace(filename, '').replace(/\\/g, '/');

				var file = files[i].replace(replace, '');

				if (filter) {
					if (typeof(filter) === 'function') {
						if (!filter(file))
							continue;
					} else {
						if (filter.indexOf(framework_utils.getExtension(file).toLowerCase()) === -1)
							continue;
					}
				}

				if (file[0] === '/')
					file = file.substring(1);

				var key = url + file;
				self.routes.mapping[key] = plus + files[i];

				if (block)
					self.routes.blocks[key] = block;
			}

		});
	}, isPackage ? 500 : 1);

	return this;
};

/**
 * Add a middleware
 * @param {String} name
 * @param {Function(req, res, next, options)} funcExecute
 * @return {Framework}
 */
Framework.prototype.middleware = function(name, funcExecute) {
	var self = this;
	self.install('middleware', name, funcExecute);
	return self;
};

/**
 * Uses middleware
 * @name {String or String Array} name
 * @url {String} url A url address (optional)
 * @types {String Array} types It can be `web`, `file` or `websocket`
 * @return {Framework}
 */
Framework.prototype.use = function(name, url, types, first) {
	var self = this;

	if (!url && !types) {
		if (name instanceof Array) {
			for (var i = 0; i < name.length; i++)
				self.routes.request.push(name[i]);
		} else
			self.routes.request.push(name);
		self._length_request_middleware = self.routes.request.length;
		return self;
	}

	if (url instanceof Array) {
		types = url;
		url = null;
	}

	if (url === '*')
		url = null;

	var route;

	if (url)
		url = framework_internal.routeSplitCreate(framework_internal.preparePath(url.trim())).join('/');

	if (!types || types.indexOf('web') !== -1) {
		for (var i = 0, length = self.routes.web.length; i < length; i++) {
			route = self.routes.web[i];
			if (url && !route.url.join('/').startsWith(url))
				continue;
			if (!route.middleware)
				route.middleware = [];
			merge_middleware(route.middleware, name, first);
		}
	}

	if (!types || types.indexOf('file') !== -1 || types.indexOf('files') !== -1) {
		for (var i = 0, length = self.routes.files.length; i < length; i++) {
			route = self.routes.files[i];
			if (url && !route.url.join('/').startsWith(url))
				continue;
			if (!route.middleware)
				route.middleware = [];
			merge_middleware(route.middleware, name, first);
		}
	}

	if (!types || types.indexOf('websocket') !== -1 || types.indexOf('websockets') !== -1) {
		for (var i = 0, length = self.routes.websockets.length; i < length; i++) {
			route = self.routes.websockets[i];
			if (url && !route.url.join('/').startsWith(url))
				continue;
			if (!route.middleware)
				route.middleware = [];
			merge_middleware(route.middleware, name, first);
		}
	}

	return self;
};

function merge_middleware(a, b, first) {

	if (typeof(b) === 'string')
		b = [b];

	for (var i = 0, length = b.length; i < length; i++) {
		var index = a.indexOf(b[i]);
		if (index === -1) {
			if (first)
				a.unshift(b[i]);
			else
				a.push(b[i]);
		}
	}

	return a;
}

/**
 * Add a new websocket route
 * @param {String} url
 * @param {Function()} funcInitialize
 * @param {String Array} flags Optional.
 * @param {String Array} protocols Optional, framework compares this array with request protocol (http or https)
 * @param {String Array} allow Optional, framework compares this array with "origin" request header
 * @param {Number} length Optional, maximum message length.
 * @param {String Array} middleware Optional, middlewares.
 * @param {Object} options Optional, additional options for middleware.
 * @return {Framework}
 */
Framework.prototype.websocket = function(url, funcInitialize, flags, length) {

	var tmp;

	var CUSTOM = typeof(url) === 'function' ? url : null;
	if (CUSTOM)
		url = '/';

	if (url[0] === '#') {
		url = url.substring(1);
		var sitemap = self.sitemap(url, true);
		if (sitemap) {
			url = sitemap.url;
			if (sitemap.wildcard)
				url += '*';
		} else
			throw new Error('Sitemap item "' + url + '" not found.');
	}

	if (url === '')
		url = '/';

	// Unicode encoding
	url = framework_internal.encodeUnicodeURL(url);

	var self = this;
	var priority = 0;
	var index = url.indexOf(']');
	var subdomain = null;
	var middleware;
	var allow;
	var options;
	var protocols;

	priority = url.count('/');

	if (index > 0) {
		subdomain = url.substring(1, index).trim().toLowerCase().split(',');
		url = url.substring(index + 1);
		priority += subdomain.indexOf('*') !== -1 ? 50 : 100;
	}

	var isASTERIX = url.indexOf('*') !== -1;
	if (isASTERIX) {
		url = url.replace('*', '').replace('//', '/');
		priority = (-10) - priority;
	}

	var url2 = framework_internal.preparePath(url.trim());
	var routeURL = framework_internal.routeSplitCreate(url2);
	var arr = [];
	var reg = null;
	var regIndex = null;
	var hash = url2.hash();

	if (url.indexOf('{') !== -1) {

		routeURL.forEach(function(o, i) {
			if (o.substring(0, 1) !== '{')
				return;

			arr.push(i);

			var sub = o.substring(1, o.length - 1);

			if (sub[0] !== '/')
				return;

			var index = sub.lastIndexOf('/');
			if (index === -1)
				return;

			if (!reg) {
				reg = {};
				regIndex = [];
			}

			reg[i] = new RegExp(sub.substring(1, index), sub.substring(index + 1));
			regIndex.push(i);
		});

		priority -= arr.length;
	}

	if (typeof(allow) === 'string')
		allow = allow[allow];

	if (typeof(protocols) === 'string')
		protocols = protocols[protocols];

	tmp = [];

	var isJSON = false;
	var isBINARY = false;
	var isROLE = false;
	var count = 0;
	var membertype = 0;

	if (!flags)
		flags = [];

	_flags && _flags.forEach(function(flag) {
		if (flags.indexOf(flag) === -1)
			flags.push(flag);
	});

	for (var i = 0; i < flags.length; i++) {

		var flag = flags[i];
		var type = typeof(flag);

		// Middleware options
		if (type === 'object') {
			options = flags[i];
			continue;
		}

		// Length
		if (type === 'number') {
			length = flag;
			continue;
		}

		// Middleware
		if (flag[0] === '#') {
			if (!middleware)
				middleware = [];
			middleware.push(flags[i].substring(1));
			continue;
		}

		flag = flag.toString().toLowerCase();

		// Origins
		if (flag.startsWith('http://') || flag.startsWith('https://')) {
			if (!allow)
				allow = [];
			allow.push(flag);
			continue;
		}

		count++;

		if (flag === 'json')
			isJSON = true;

		if (flag === 'binary')
			isBINARY = true;

		if (flag === 'raw') {
			isBINARY = false;
			isJSON = false;
		}


		if (flag[0] === '@') {
			isROLE = true;
			tmp.push(flag);
			continue;
		}

		if (flag === 'json' || flag === 'binary' || flag === 'raw')
			continue;

		switch (flag) {
			case 'authorize':
			case 'authorized':
			case 'logged':
				membertype = 1;
				priority++;
				tmp.push('authorize');
				break;
			case 'unauthorize':
			case 'unauthorized':
			case 'unlogged':
				membertype = 2;
				priority++;
				tmp.push('unauthorize');
				break;
			case 'get':
			case 'http':
			case 'https':
			case 'debug':
			case 'release':
				tmp.push(flag);
				break;
			default:
				if (!protocols)
					protocols = [];
				protocols.push(flag);
				break;
		}
	}

	flags = tmp;

	flags.indexOf('get') === -1 && flags.unshift('get');
	priority += (count * 2);

	if (subdomain)
		self._length_subdomain_websocket++;

	self.routes.websockets.push({
		hash: hash,
		controller: !_controller ? 'unknown' : _controller,
		url: routeURL,
		param: arr,
		subdomain: subdomain,
		priority: priority,
		flags: flags || EMPTYARRAY,
		flags2: flags_to_object(flags),
		onInitialize: funcInitialize,
		protocols: protocols || EMPTYARRAY,
		allow: allow || [],
		length: (length || self.config['default-websocket-request-length']) * 1024,
		isWEBSOCKET: true,
		MEMBER: membertype,
		isJSON: isJSON,
		isBINARY: isBINARY,
		isROLE: isROLE,
		isASTERIX: isASTERIX,
		isHTTPS: flags.indexOf('https'),
		isHTTP: flags.indexOf('http'),
		isDEBUG: flags.indexOf('debug'),
		isRELEASE: flags.indexOf('release'),
		CUSTOM: CUSTOM,
		middleware: middleware ? middleware : null,
		options: options,
		isPARAM: arr.length > 0,
		regexp: reg,
		regexpIndexer: regIndex
	});

	self.emit('route', 'websocket', self.routes.websockets[self.routes.websockets.length - 1]);
	!_controller && self._routesSort(2);
	return self;
};

/**
 * Create a file route
 * @param {String} name
 * @param {Function} funcValidation
 * @param {Function} fnExecute
 * @param {String Array} middleware
 * @return {Framework}
 */
Framework.prototype.file = function(fnValidation, fnExecute, flags) {

	var self = this;
	var a;

	if (fnValidation instanceof Array) {
		a = fnExecute;
		var b = flags;
		flags = fnValidation;
		fnValidation = a;
		fnExecute = b;
	} else if (fnExecute instanceof Array) {
		a = fnExecute;
		fnExecute = flags;
		flags = a;
	}

	if (!fnExecute && fnValidation) {
		fnExecute = fnValidation;
		fnValidation = undefined;
	}

	var extensions;
	var middleware;
	var options;
	var url;
	var wildcard = false;
	var fixedfile = false;

	if (_flags) {
		if (!flags)
			flags = [];
		_flags.forEach(function(flag) {
			if (flags.indexOf(flag) === -1)
				flags.push(flag);
		});
	}

	if (flags) {
		for (var i = 0, length = flags.length; i < length; i++) {
			var flag = flags[i];

			if (typeof(flag) === 'object') {
				options = flag;
				continue;
			}

			if (flag[0] === '#') {
				if (!middleware)
					middleware = [];
				middleware.push(flag.substring(1));
			}

			if (flag[0] === '.') {
				flag = flag.substring(1).toLowerCase();
				if (!extensions)
					extensions = {};
				extensions[flag] = true;
			}
		}
	}

	if (typeof(fnValidation) === 'string') {

		if (fnValidation === '/')
			fnValidation = '';

		url = fnValidation ? framework_internal.routeSplitCreate(fnValidation) : EMPTYARRAY;
		fnValidation = undefined;
		a = url.last();
		if (a === '*.*') {
			wildcard = true;
			url.splice(url.length - 1, 1);
		} else if (a) {
			var index = a.indexOf('*.');
			if (index !== -1) {
				extensions = {};
				extensions[a.substring(index + 2).trim()] = true;
				wildcard = false;
				url.splice(url.length - 1, 1);
			} else if (a === '*') {
				wildcard = true;
				url.splice(url.length - 1, 1);
			} else if (framework_utils.getExtension(a)) {
				fixedfile = true;
				wildcard = false;
			}
		}
	} else if (!extensions && !fnValidation)
		fnValidation = fnExecute;


	self.routes.files.push({
		controller: !_controller ? 'unknown' : _controller,
		url: url,
		fixedfile: fixedfile,
		wildcard: wildcard,
		extensions: extensions,
		onValidate: fnValidation,
		execute: fnExecute,
		middleware: middleware,
		options: options
	});

	self.routes.files.sort(function(a, b) {
		if (!a.url)
			return -1;
		if (!b.url)
			return 1;
		return a.url.length > b.url.length ? -1 : 1;
	});

	self.emit('route', 'file', self.routes.files[self.routes.files.length - 1]);
	self._length_files++;
	return self;
};

Framework.prototype.localize = function(url, flags, minify) {

	url = url.replace('*', '');

	var self = this;

	if (flags === true) {
		flags = [];
		minify = true;
	} else if (!flags)
		flags = [];

	var index;

	if (!minify) {
		index = flags.indexOf('minify');
		if (index === -1)
			index = flags.indexOf('compress');
		minify = index !== -1;
		if (index !== -1)
			flags.splice(index, 1);
	}

	var index = url.lastIndexOf('.');

	if (index !== -1) {
		flags.push(url.substring(index).toLowerCase());
		url = url.substring(0, index);
	} else
		flags.push('.html', '.htm', '.md', '.txt');

	url = framework_internal.preparePath(url);
	self.file(url, function(req, res, is) {

		var key = 'locate_' + (req.$language ? req.$language : 'default') + '_' + req.url;
		var output = framework.temporary.other[key];

		if (output) {
			framework.responseContent(req, res, 200, output, framework_utils.getContentType(req.extension), true);
			return;
		}

		var name = req.uri.pathname;
		var filename = self.onMapping(name, name, true, true);

		Fs.readFile(filename, function(err, content) {

			if (err)
				return res.throw404();

			content = framework.translator(req.$language, framework_internal.modificators(content.toString(ENCODING), filename, 'static'));

			if (minify && (req.extension === 'html' || req.extension === 'htm'))
				content = framework_internal.compile_html(content, filename);

			if (RELEASE)
				framework.temporary.other[key] = content;

			framework.responseContent(req, res, 200, content, framework_utils.getContentType(req.extension), true);
		});

	}, flags);
	return self;
};

/**
 * Error caller
 * @param {Error} err
 * @param {String} name Controller or Script name.
 * @param {Object} uri
 * @return {Framework}
 */
Framework.prototype.error = function(err, name, uri) {

	if (!arguments.length) {
		return function(err) {
			err && framework.error(err, name, uri);
		};
	}

	if (!err)
		return this;

	var self = this;

	if (self.errors) {
		self.errors.push({ error: err.stack, name: name, url: uri ? Parser.format(uri) : null, date: new Date() });
		self.errors.length > 50 && self.errors.shift();
	}

	self.onError(err, name, uri);
	return self;
};

/**
 * Registers a new problem
 * @param {String} message
 * @param {String} name A controller name.
 * @param {String} uri
 * @param {String} ip
 * @return {Framework}
 */
Framework.prototype.problem = Framework.prototype.wtf = function(message, name, uri, ip) {
	var self = this;
	self.emit('problem', message, name, uri, ip);

	if (message instanceof framework_builders.ErrorBuilder)
		message = message.plain();
	else if (typeof(message) === 'object')
		message = JSON.stringify(message);

	var obj = { message: message, name: name, url: uri ? Parser.format(uri) : null, ip: ip };
	self.logger('problems', obj.message, 'url: ' + obj.url, 'controller: ' + obj.name, 'ip: ' + obj.ip);

	if (!self.problems)
		return self;

	self.problems.push(obj);
	self.problems.length > 50 && self.problems.shift();
	return self;
};

/**
 * Registers a new change
 * @param {String} message
 * @param {String} name A controller name.
 * @param {String} uri
 * @param {String} ip
 * @return {Framework}
 */
Framework.prototype.change = function(message, name, uri, ip) {
	var self = this;

	self.emit('change', message, name, uri, ip);

	if (message instanceof framework_builders.ErrorBuilder)
		message = message.plain();
	else if (typeof(message) === 'object')
		message = JSON.stringify(message);

	var obj = { message: message, name: name, url: uri ? Parser.format(uri) : null, ip: ip };
	self.logger('changes', obj.message, 'url: ' + obj.url, 'controller: ' + obj.name, 'ip: ' + obj.ip);

	if (!self.changes)
		return self;

	self.changes.push(obj);
	self.changes.length > 50 && self.changes.shift();
	return self;
};

/**
 * Trace
 * @param {String} message
 * @param {String} name A controller name.
 * @param {String} uri
 * @param {String} ip
 * @return {Framework}
 */
Framework.prototype.trace = function(message, name, uri, ip) {
	var self = this;

	if (!self.config.trace)
		return self;

	self.emit('trace', message, name, uri, ip);

	if (message instanceof framework_builders.ErrorBuilder)
		message = message.plain();
	else if (typeof(message) === 'object')
		message = JSON.stringify(message);

	var obj = { message: message, name: name, url: uri ? Parser.format(uri) : null, ip: ip };
	self.logger('traces', obj.message, 'url: ' + obj.url, 'controller: ' + obj.name, 'ip: ' + obj.ip);

	if (!self.traces)
		return self;

	self.traces.push(obj);
	self.traces.length > 50 && self.traces.shift();
	return self;
};

/**
 * Get a module
 * @param {String} name
 * @return {Object}
 */
Framework.prototype.module = function(name) {
	return this.modules[name] || null;
};

/**
 * Add a new modificator
 * @param {Function(type, filename, content)} fn The `fn` must return modified value.
 * @return {String}
 */
Framework.prototype.modify = function(fn) {
	var self = this;
	if (!self.modificators)
		self.modificators = [];
	self.modificators.push(fn);
	return self;
};

/**
 * Load framework
 * @return {Framework}
 */
Framework.prototype.$load = function(types, targetdirectory) {

	var self = this;
	var arr = [];
	var dir = '';

	if (!targetdirectory)
		targetdirectory = directory;

	targetdirectory = '~' + targetdirectory;

	function listing(directory, level, output, extension, isTheme) {

		if (!existsSync(dir))
			return;

		if (!extension)
			extension = '.js';

		Fs.readdirSync(directory).forEach(function(o) {
			var isDirectory = Fs.statSync(Path.join(directory, o)).isDirectory();

			if (isDirectory && isTheme) {
				output.push({ name: o });
				return;
			}

			if (isDirectory) {

				if (extension === '.package' && o.endsWith(extension)) {
					var name = o.substring(0, o.length - extension.length);
					output.push({ name: name[0] === '/' ? name.substring(1) : name, filename: Path.join(dir, o), is: true });
					return;
				}

				level++;
				listing(Path.join(directory, o), level, output, extension);
				return;
			}

			var ext = framework_utils.getExtension(o).toLowerCase();
			if (ext)
				ext = '.' + ext;
			if (ext !== extension)
				return;
			var name = (level ? framework_utils.$normalize(directory).replace(dir, '') + '/' : '') + o.substring(0, o.length - ext.length);
			output.push({ name: name[0] === '/' ? name.substring(1) : name, filename: Path.join(dir, name) + extension });
		});
	}

	if (!types || types.indexOf('modules') !== -1) {
		dir = framework_utils.combine(targetdirectory, self.config['directory-modules']);
		arr = [];
		listing(dir, 0, arr, '.js');
		arr.forEach((item) => self.install('module', item.name, item.filename, undefined, undefined, undefined, true));
	}

	if (!types || types.indexOf('isomorphic') !== -1) {
		dir = framework_utils.combine(targetdirectory, self.config['directory-isomorphic']);
		arr = [];
		listing(dir, 0, arr, '.js');
		arr.forEach((item) => self.install('isomorphic', item.name, item.filename, undefined, undefined, undefined, true));
	}

	if (!types || types.indexOf('packages') !== -1) {
		dir = framework_utils.combine(targetdirectory, self.config['directory-packages']);
		arr = [];
		listing(dir, 0, arr, '.package');

		var dirtmp = framework_utils.$normalize(dir);

		arr.forEach(function(item) {

			if (item.is) {
				framework_utils.ls(item.filename, function(files, directories) {

					var dir = framework.path.temp(item.name) + '.package';

					if (!existsSync(dir))
						Fs.mkdirSync(dir);

					for (var i = 0, length = directories.length; i < length; i++) {
						var target = framework.path.temp(framework_utils.$normalize(directories[i]).replace(dirtmp, '') + '/');
						if (!existsSync(target))
							Fs.mkdirSync(target);
					}

					files.wait(function(filename, next) {
						var stream = Fs.createReadStream(filename);
						stream.pipe(Fs.createWriteStream(Path.join(dir, filename.replace(item.filename, '').replace(/\.package$/i, ''))));
						stream.on('end', next);
					}, function() {
						// Windows sometimes doesn't load package and delay solves the problem.
						setTimeout(() => self.install('package2', item.name, item.filename, undefined, undefined, undefined, true), 50);
					});
				});
				return;
			}

			self.install('package', item.name, item.filename, undefined, undefined, undefined, true);
		});
	}

	if (!types || types.indexOf('models') !== -1) {
		dir = framework_utils.combine(targetdirectory, self.config['directory-models']);
		arr = [];
		listing(dir, 0, arr);
		arr.forEach((item) => self.install('model', item.name, item.filename, undefined, undefined, undefined, true));
	}

	if (!types || types.indexOf('themes') !== -1) {
		arr = [];
		dir = framework_utils.combine(targetdirectory, self.config['directory-themes']);
		listing(dir, 0, arr, undefined, true);
		arr.forEach(function(item) {
			var themeName = item.name;
			var themeDirectory = Path.join(dir, themeName);
			var filename = Path.join(themeDirectory, 'index.js');
			self.themes[item.name] = framework_utils.path(themeDirectory);
			self._length_themes++;
			if (existsSync(filename))
				self.install('theme', item.name, filename, undefined, undefined, undefined, true);
		});
	}

	if (!types || types.indexOf('definitions') !== -1) {
		dir = framework_utils.combine(targetdirectory, self.config['directory-definitions']);
		arr = [];
		listing(dir, 0, arr);
		arr.forEach((item) => self.install('definition', item.name, item.filename, undefined, undefined, undefined, true));
	}

	if (!types || types.indexOf('controllers') !== -1) {
		arr = [];
		dir = framework_utils.combine(targetdirectory, self.config['directory-controllers']);
		listing(dir, 0, arr);
		arr.forEach((item) => self.install('controller', item.name, item.filename, undefined, undefined, undefined, true));
	}

	self._routesSort();

	if (!types || types.indexOf('dependencies') !== -1)
		self._configure_dependencies();

	return self;
};

Framework.prototype.$startup = function(callback) {

	var dir = Path.join(directory, '/startup/');

	if (!existsSync(dir))
		return callback();

	var run = [];

	Fs.readdirSync(dir).forEach(function(o) {
		var extension = framework_utils.getExtension(o).toLowerCase();
		if (extension === 'js')
			run.push(o);
	});

	if (!run.length)
		return callback();

	run.wait(function(filename, next) {
		var fn = dir + filename + new Date().format('yyMMdd_HHmmss');
		Fs.renameSync(dir + filename, fn);
		var fork = Child.fork(fn, [], { cwd: directory });
		fork.on('exit', function() {
			fork = null;
			next();
		});
	}, callback);

	return this;
};

/**
 * Install type with its declaration
 * @param {String} type Available types: model, module, controller, source.
 * @param {String} name Default name (optional).
 * @param {String or Function} declaration
 * @param {Object} options Custom options, optional.
 * @param {Object} internal Internal/Temporary options, optional.
 * @param {Boolean} useRequired Internal, optional.
 * @param {Boolean} skipEmit Internal, optional.
 * @return {Framework}
 */
Framework.prototype.install = function(type, name, declaration, options, callback, internal, useRequired, skipEmit) {

	var self = this;
	var obj = null;

	if (type !== 'config' && type !== 'version' && typeof(name) === 'string') {
		if (name.startsWith('http://') || name.startsWith('https://')) {
			if (typeof(declaration) === 'object') {
				callback = options;
				options = declaration;
				declaration = name;
				name = '';
			}
		} else if (name[0] === '@') {
			declaration = framework.path.package(name.substring(1));
			name = Path.basename(name).replace(/\.js$/i, '');
			if (useRequired === undefined)
				useRequired = true;
		}
	}

	var t = typeof(declaration);
	var key = '';
	var tmp;

	if (t === 'object') {
		t = typeof(options);
		if (t === 'function')
			callback = options;
		options = declaration;
		declaration = undefined;
	}

	if (declaration === undefined) {
		declaration = name;
		name = '';
	}

	if (typeof(options) === 'function') {
		callback = options;
		options = undefined;
	}

	// Check if declaration is a valid URL address
	if (type !== 'eval' && typeof(declaration) === 'string') {

		if (declaration.startsWith('http://') || declaration.startsWith('https://')) {

			if (type === 'package') {
				framework_utils.download(declaration, ['get'], function(err, response) {

					if (err) {
						self.error(err, 'framework.install(\'{0}\', \'{1}\')'.format(type, declaration), null);
						callback && callback(err);
						return;
					}

					var id = Path.basename(declaration, '.package');
					var filename = framework.path.temp(id + '.download');
					var stream = Fs.createWriteStream(filename);
					response.pipe(stream);
					stream.on('finish', () => self.install(type, id, filename, options, undefined, undefined, true));
				});

				return self;
			}

			framework_utils.request(declaration, ['get'], function(err, data, code) {

				if (code !== 200 && !err)
					err = new Error(data);

				if (err) {
					self.error(err, 'framework.install(\'{0}\', \'{1}\')'.format(type, declaration), null);
					callback && callback(err);
				} else
					self.install(type, name, data, options, callback, declaration);

			});
			return self;
		} else {

			if (declaration[0] === '~')
				declaration = declaration.substring(1);
			if (type !== 'config' && type !== 'resource' && type !== 'package' && !REG_SCRIPTCONTENT.test(declaration)) {
				if (!existsSync(declaration))
					throw new Error('The ' + type + ': ' + declaration + ' doesn\'t exist.');
				useRequired = true;
			}
		}
	}

	if (type === 'middleware') {

		self.routes.middleware[name] = typeof(declaration) === 'function' ? declaration : eval(declaration);
		self._length_middleware = Object.keys(self.routes.middleware).length;

		callback && callback(null);

		key = type + '.' + name;

		if (self.dependencies[key]) {
			self.dependencies[key].updated = new Date();
		} else {
			self.dependencies[key] = { name: name, type: type, installed: new Date(), updated: null, count: 0 };
			if (internal)
				self.dependencies[key].url = internal;
		}

		self.dependencies[key].count++;

		setTimeout(function() {
			self.emit(type + '#' + name);
			self.emit('install', type, name);
		}, 500);

		return self;
	}

	if (type === 'config' || type === 'configuration' || type === 'settings') {

		self._configure(declaration instanceof Array ? declaration : declaration.toString().split('\n'), true);

		setTimeout(function() {
			delete self.temporary['mail-settings'];
			self.emit(type + '#' + name, framework.config);
			self.emit('install', type, name);
		}, 500);

		callback && callback(null);
		return self;
	}

	if (type === 'version' || type === 'versions') {

		self._configure_versions(declaration.toString().split('\n'));
		setTimeout(function() {
			self.emit(type + '#' + name);
			self.emit('install', type, name);
		}, 500);

		callback && callback(null);
		return self;
	}

	if (type === 'workflow' || type === 'workflows') {

		self._configure_workflows(declaration.toString().split('\n'));
		setTimeout(function() {
			self.emit(type + '#' + name);
			self.emit('install', type, name);
		}, 500);

		callback && callback(null);
		return self;
	}

	if (type === 'sitemap') {

		self._configure_sitemap(declaration.toString().split('\n'));
		setTimeout(function() {
			self.emit(type + '#' + name);
			self.emit('install', type, name);
		}, 500);

		callback && callback(null);
		return self;
	}

	if (type === 'package') {

		var backup = new Backup();
		var id = Path.basename(declaration, '.' + framework_utils.getExtension(declaration));
		var dir = framework.config['directory-temp'][0] === '~' ? Path.join(framework.config['directory-temp'].substring(1), id + '.package') : Path.join(framework.path.root(), framework.config['directory-temp'], id + '.package');

		self.routes.packages[id] = dir;
		backup.restore(declaration, dir, function() {

			var filename = Path.join(dir, 'index.js');
			if (!existsSync(filename))
				return;

			self.install('module', id, filename, options, function(err) {

				setTimeout(function() {
					self.emit(type + '#' + name);
					self.emit('install', type, name);
				}, 500);

				callback && callback(err);
			}, internal, useRequired, true);
		});

		return self;
	}

	if (type === 'theme') {

		_owner = type + '#' + name;
		obj = require(declaration);
		obj.$owner = _owner;

		if (typeof(obj.install) === 'function')
			obj.install(options, name);

		if (!skipEmit) {
			setTimeout(function() {
				self.emit(type + '#' + name);
				self.emit('install', type, name);
			}, 500);
		}

		callback && callback(null);

		(function(name, filename) {
			setTimeout(function() {
				delete require.cache[name];
			}, 1000);
		})(require.resolve(declaration), declaration);
		return self;
	}

	if (type === 'package2') {
		var id = framework_utils.getName(declaration, '.package');
		var dir = framework.config['directory-temp'][0] === '~' ? Path.join(framework.config['directory-temp'].substring(1), id) : Path.join(framework.path.root(), framework.config['directory-temp'], id);
		var filename = Path.join(dir, 'index.js');
		self.install('module', id, filename, options, function(err) {
			setTimeout(function() {
				self.emit(type + '#' + name);
				self.emit('install', type, name);
			}, 500);
			callback && callback(err);
		}, internal, useRequired, true);
		return self;
	}

	var plus = self.id ? 'i-' + self.id + '_' : '';

	if (type === 'view') {

		var item = self.routes.views[name];
		key = type + '.' + name;

		if (item === undefined) {
			item = {};
			item.filename = self.path.temporary(plus + 'installed-view-' + framework_utils.GUID(10) + '.tmp');
			item.url = internal;
			item.count = 0;
			self.routes.views[name] = item;
		}

		item.count++;
		Fs.writeFileSync(item.filename, framework_internal.modificators(declaration, name));

		setTimeout(function() {
			self.emit(type + '#' + name);
			self.emit('install', type, name);
		}, 500);

		callback && callback(null);
		return self;
	}

	if (type === 'definition' || type === 'eval') {

		_controller = '';
		_owner = type + '#' + name;

		try {

			if (useRequired) {
				delete require.cache[require.resolve(declaration)];
				obj = require(declaration);

				(function(name) {
					setTimeout(() => delete require.cache[name], 1000);
				})(require.resolve(declaration));
			}
			else
				obj = typeof(declaration) === 'function' ? eval('(' + declaration.toString() + ')()') : eval(declaration);

		} catch (ex) {
			self.error(ex, 'framework.install(\'' + type + '\')', null);
			callback && callback(ex);
			return self;
		}

		callback && callback(null);

		setTimeout(function() {
			self.emit(type + '#' + name);
			self.emit('install', type, name);
		}, 500);

		return self;
	}

	if (type === 'isomorphic') {

		var content = '';

		try {

			if (!name && typeof(internal) === 'string') {
				var tmp = internal.match(/[a-z0-9]+\.js$/i);
				if (tmp)
					name = tmp.toString().replace(/\.js/i, '');
			}

			if (useRequired) {
				delete require.cache[require.resolve(declaration)];
				obj = require(declaration);
				content = Fs.readFileSync(declaration).toString(ENCODING);
				(function(name) {
					setTimeout(() => delete require.cache[name], 1000);
				})(require.resolve(declaration));
			}
			else {
				obj = typeof(declaration) === 'function' ? eval('(' + declaration.toString() + ')()') : eval(declaration);
				content = declaration.toString();
			}

		} catch (ex) {
			self.error(ex, 'framework.install(\'' + type + '\')', null);
			callback && callback(ex);
			return self;
		}

		if (typeof(obj.id) === 'string')
			name = obj.id;
		else if (typeof(obj.name) === 'string')
			name = obj.name;

		if (obj.url) {
			if (obj.url[0] !== '/')
				obj.url = '/' + obj.url;
		} else
			obj.url = '/' + name + '.js';

		framework.map(framework_internal.preparePath(obj.url), '#' + name);
		framework.isomorphic[name] = obj;
		framework.isomorphic[name].$$output = framework_internal.compile_javascript(content, '#' + name);

		callback && callback(null);

		setTimeout(function() {
			self.emit(type + '#' + name, obj);
			self.emit('install', type, name, obj);
		}, 500);

		return self;
	}

	if (type === 'model' || type === 'source') {

		_controller = '';
		_owner = type + '#' + name;

		try {

			if (useRequired) {
				obj = require(declaration);
				(function(name) {
					setTimeout(() => delete require.cache[name], 1000);
				})(require.resolve(declaration));
			}
			else {

				if (typeof(declaration) !== 'string')
					declaration = declaration.toString();

				if (!name && typeof(internal) === 'string') {
					var tmp = internal.match(/[a-z0-9]+\.js$/i);
					if (tmp)
						name = tmp.toString().replace(/\.js/i, '');
				}

				var filename = self.path.temporary(plus + 'installed-' + type + '-' + framework_utils.GUID(10) + '.js');
				Fs.writeFileSync(filename, declaration);
				obj = require(filename);

				(function(name, filename) {
					setTimeout(function() {
						Fs.unlinkSync(filename);
						delete require.cache[name];
					}, 1000);
				})(require.resolve(filename), filename);
			}

		} catch (ex) {
			self.error(ex, 'framework.install(\'' + type + '\', \'' + name + '\')', null);
			callback && callback(ex);
			return self;
		}

		if (typeof(obj.id) === 'string')
			name = obj.id;
		else if (typeof(obj.name) === 'string')
			name = obj.name;

		obj.$owner = _owner;

		if (!name)
			name = (Math.random() * 10000) >> 0;

		key = type + '.' + name;
		tmp = self.dependencies[key];

		self.uninstall(type, name);

		if (tmp) {
			self.dependencies[key] = tmp;
			self.dependencies[key].updated = new Date();
		}
		else {
			self.dependencies[key] = { name: name, type: type, installed: new Date(), updated: null, count: 0 };
			if (internal)
				self.dependencies[key].url = internal;
		}

		self.dependencies[key].count++;

		if (obj.reinstall)
			self.dependencies[key].reinstall = obj.reinstall.toString().parseDateExpiration();
		else
			delete self.dependencies[key];

		if (type === 'model')
			self.models[name] = obj;
		else
			self.sources[name] = obj;

		if (typeof(obj.install) === 'function')
			obj.install(options, name);

		if (!skipEmit) {
			setTimeout(function() {
				self.emit(type + '#' + name, obj);
				self.emit('install', type, name, obj);
			}, 500);
		}

		callback && callback(null);
		return self;
	}

	if (type === 'module' || type === 'controller') {

		// for inline routes
		var _ID = _controller = 'TMP' + framework_utils.random(10000);
		_owner = type + '#' + name;

		try {
			if (useRequired) {
				obj = require(declaration);
				(function(name) {
					setTimeout(function() {
						delete require.cache[name];
					}, 1000);
				})(require.resolve(declaration));
			} else {

				if (typeof(declaration) !== 'string')
					declaration = declaration.toString();

				if (!name && typeof(internal) === 'string') {
					var tmp = internal.match(/[a-z0-9]+\.js$/i);
					if (tmp)
						name = tmp.toString().replace(/\.js/i, '');
				}

				filename = self.path.temporary(plus + 'installed-' + type + '-' + framework_utils.GUID(10) + '.js');
				Fs.writeFileSync(filename, declaration);
				obj = require(filename);
				(function(name, filename) {
					setTimeout(function() {
						Fs.unlinkSync(filename);
						delete require.cache[name];
					}, 1000);
				})(require.resolve(filename), filename);
			}

		} catch (ex) {
			self.error(ex, 'framework.install(\'' + type + '\', \'' + (name ? '' : internal) + '\')', null);
			callback && callback(ex);
			return self;
		}

		if (typeof(obj.id) === 'string')
			name = obj.id;
		else if (typeof(obj.name) === 'string')
			name = obj.name;

		if (!name)
			name = (Math.random() * 10000) >> 0;

		obj.$owner = _owner;

		if (obj.booting) {
			setTimeout(function() {

				var tmpdir = framework.path.temp(name + '.package/');
				if (obj.booting === 'root') {
					framework.directory = directory = tmpdir;
					framework.temporary.path = {};
					framework._configure();
					framework._configure_versions();
					framework._configure_dependencies();
					framework._configure_sitemap();
					framework._configure_workflows();
				} else {

					framework._configure('@' + name + '/config');

					if (framework.config.debug)
						framework._configure('@' + name + '/config-debug');
					else
						framework._configure('@' + name + '/config-release');

					framework.isTest && framework._configure('@' + name + '/config-test');
					framework._configure_versions('@' + name + '/versions');
					framework._configure_dependencies('@' + name + '/dependencies');
					framework._configure_sitemap('@' + name + '/sitemap');
					framework._configure_workflows('@' + name + '/workflows');
				}

				framework.$load(undefined, tmpdir);
			}, 100);
		}

		key = type + '.' + name;
		tmp = self.dependencies[key];

		self.uninstall(type, name);

		if (tmp) {
			self.dependencies[key] = tmp;
			self.dependencies[key].updated = new Date();
		}
		else {
			self.dependencies[key] = { name: name, type: type, installed: new Date(), updated: null, count: 0, _id: _ID };
			if (internal)
				self.dependencies[key].url = internal;
		}

		self.dependencies[key].dependencies = obj.dependencies;
		self.dependencies[key].count++;
		self.dependencies[key].processed = false;

		if (obj.reinstall)
			self.dependencies[key].reinstall = obj.reinstall.toString().parseDateExpiration();
		else
			delete self.dependencies[key].reinstall;

		_controller = _ID;

		if (obj.dependencies instanceof Array) {
			for (var i = 0, length = obj.dependencies.length; i < length; i++) {
				if (!self.dependencies[type + '.' + obj.dependencies[i]]) {
					self.temporary.dependencies[key] = { obj: obj, options: options, callback: callback, skipEmit: skipEmit };
					return self;
				}
			}
		}

		self.install_make(key, name, obj, options, callback, skipEmit);

		if (type === 'module')
			self.modules[name] = obj;
		else
			self.controllers[name] = obj;

		self.install_prepare();
		return self;
	}

	return self;
};

Framework.prototype.restart = function() {
	var self = this;

	if (self.isRestart)
		return self;

	F.emit('restart');
	setTimeout(() => self.$restart(), 1000);
	return self;
};

Framework.prototype.$restart = function() {
	var self = this;

	console.log('----------------------------------------------------> RESTART ' + new Date().format('yyyy-MM-dd HH:mm:ss'));

	self.server.setTimeout(0);
	self.server.timeout = 0;
	self.server.close(function() {

		Object.keys(self.modules).forEach(function(key) {
			var item = self.modules[key];
			item && item.uninstall && item.uninstall();
		});

		Object.keys(self.models).forEach(function(key) {
			var item = self.models[key];
			item && item.uninstall && item.uninstall();
		});

		Object.keys(self.controllers).forEach(function(key) {
			var item = self.controllers[key];
			item && item.uninstall && item.uninstall();
		});

		Object.keys(self.workers).forEach(function(key) {
			var item = self.workers[key];
			if (item && item.kill) {
				item.removeAllListeners();
				item.kill('SIGTERM');
			}
		});

		Object.keys(self.connections).forEach(function(key) {
			var item = self.connections[key];
			if (item) {
				item.removeAllListeners();
				item.close();
			}
		});

		framework_builders.restart();
		framework_image.restart();
		framework_mail.restart();
		framework_utils.restart();

		self.cache.clear();
		self.cache.stop();
		self.global = {};
		self.resources = {};
		self.connections = {};
		self.functions = {};
		self.themes = {};
		self.versions = null;
		self.schedules = [];
		self.isLoaded = false;
		self.isRestart = false;

		self.routes = {
			sitemap: null,
			web: [],
			files: [],
			cors: [],
			websockets: [],
			middleware: {},
			redirects: {},
			resize: {},
			request: [],
			views: {},
			merge: {},
			mapping: {},
			packages: {},
			blocks: {},
			resources: {},
			mmr: {}
		};

		self.behaviours = null;
		self.modificators = null;
		self.helpers = {};
		self.modules = {};
		self.models = {};
		self.sources = {};
		self.controllers = {};
		self.dependencies = {};
		self.isomorphic = {};
		self.tests = [];
		self.errors = [];
		self.problems = [];
		self.changes = [];
		self.workers = {};
		self.databases = {};

		self._request_check_redirect = false;
		self._request_check_referer = false;
		self._request_check_POST = false;
		self._request_check_robot = false;
		self._length_middleware = 0;
		self._length_request_middleware = 0;
		self._length_files = 0;
		self._length_wait = 0;
		self._length_themes = 0;
		self._length_cors = 0;
		self._length_subdomain_web = 0;
		self._length_subdomain_websocket = 0;
		self.isVirtualDirectory = false;
		self.isTheme = false;
		self.stats.other.restart++;

		setTimeout(() => self.removeAllListeners(), 2000);
		setTimeout(function() {
			var init = self.temporary.init;
			self.mode(init.isHTTPS ? require('https') : http, init.name, init.options);
		}, 1000);
	});
	return self;
};

Framework.prototype.install_prepare = function(noRecursive) {

	var self = this;
	var keys = Object.keys(self.temporary.dependencies);

	if (!keys.length)
		return;

	// check dependencies
	for (var i = 0, length = keys.length; i < length; i++) {

		var k = keys[i];
		var a = self.temporary.dependencies[k];
		var b = self.dependencies[k];
		var skip = false;

		if (b.processed)
			continue;

		for (var j = 0, jl = b.dependencies.length; j < jl; j++) {
			var d = self.dependencies['module.' + b.dependencies[j]];
			if (!d || !d.processed) {
				skip = true;
				break;
			}
		}

		if (skip)
			continue;

		delete self.temporary.dependencies[k];

		if (b.type === 'module')
			self.modules[b.name] = a.obj;
		else
			self.controllers[b.name] = a.obj;

		self.install_make(k, b.name, a.obj, a.options, a.callback, a.skipEmit);
	}

	keys = Object.keys(self.temporary.dependencies);

	clearTimeout(self.temporary.other.dependencies);
	self.temporary.other.dependencies = setTimeout(function() {
		var keys = Object.keys(framework.temporary.dependencies);
		if (keys.length)
			throw new Error('Dependency exception (module): missing dependencies for: ' + keys.join(', ').trim());
		delete self.temporary.other.dependencies;
	}, 1500);

	if (!keys.length)
		return self;

	if (noRecursive)
		return self;

	self.install_prepare(true);
	return self;
};

Framework.prototype.install_make = function(key, name, obj, options, callback, skipEmit) {

	var self = this;
	var me = self.dependencies[key];
	var routeID = me._id;
	var type = me.type;

	self.temporary.internal[me._id] = name;
	_controller = routeID;

	if (typeof(obj.install) === 'function')
		obj.install(options, name);

	me.processed = true;

	var id = (type === 'module' ? '#' : '') + name;
	var length = self.routes.web.length;

	for (var i = 0; i < length; i++) {
		if (self.routes.web[i].controller === routeID)
			self.routes.web[i].controller = id;
	}

	length = self.routes.websockets.length;
	for (var i = 0; i < length; i++) {
		if (self.routes.websockets[i].controller === routeID)
			self.routes.websockets[i].controller = id;
	}

	length = self.routes.files.length;
	for (var i = 0; i < length; i++) {
		if (self.routes.files[i].controller === routeID)
			self.routes.files[i].controller = id;
	}

	self._routesSort();
	_controller = '';

	if (!skipEmit) {
		setTimeout(function() {
			self.emit(type + '#' + name, obj);
			self.emit('install', type, name, obj);
		}, 500);
	}

	callback && callback(null);
	return self;
};

/**
 * Uninstall type
 * @param {String} type Available types: model, module, controller, source.
 * @param {String} name
 * @param {Object} options Custom options, optional.
 * @param {Object} skipEmit Internal, optional.
 * @return {Framework}
 */
Framework.prototype.uninstall = function(type, name, options, skipEmit) {

	var self = this;
	var obj = null;

	if (type === 'schema') {
		framework_builders.remove(name);
		self.emit('uninstall', type, name);
		return self;
	}

	if (type === 'mapping') {
		delete self.routes.mapping[name];
		self.emit('uninstall', type, name);
		return self;
	}

	if (type === 'isomorphic') {
		var obj = self.isomorphic[name];
		if (obj.url)
			delete self.routes.mapping[self._version(obj.url)];
		delete self.isomorphic[name];
		self.emit('uninstall', type, name);
		return self;
	}

	if (type === 'middleware') {

		if (!self.routes.middleware[name])
			return self;

		delete self.routes.middleware[name];
		delete self.dependencies[type + '.' + name];
		self._length_middleware = Object.keys(self.routes.middleware).length;

		var tmp;

		for (var i = 0, length = self.routes.web.length; i < length; i++) {
			tmp = self.routes.web[i];
			if (tmp.middleware && tmp.middleware.length)
				tmp.middleware = tmp.middleware.remove(name);
		}

		for (var i = 0, length = self.routes.websocket.length; i < length; i++) {
			tmp = self.routes.websocket[i];
			if (tmp.middleware && tmp.middleware.length)
				tmp.middleware = tmp.middleware.remove(name);
		}

		for (var i = 0, length = self.routes.web.length; i < length; i++) {
			tmp = self.routes.files[i];
			if (tmp.middleware && tmp.middleware.length)
				tmp.middleware = tmp.middleware.remove(name);
		}

		self.emit('uninstall', type, name);
		return self;
	}

	if (type === 'package') {
		delete self.routes.packages[name];
		self.uninstall('module', name, options, true);
		return self;
	}

	if (type === 'view' || type === 'precompile') {

		obj = self.routes.views[name];

		if (!obj)
			return self;

		delete self.routes.views[name];
		delete self.dependencies[type + '.' + name];

		fsFileExists(obj.filename, function(e) {
			e && Fs.unlink(obj.filename, NOOP);
		});

		self.emit('uninstall', type, name);
		return self;
	}

	if (type === 'model' || type === 'source') {

		obj = type === 'model' ? self.models[name] : self.sources[name];

		if (!obj)
			return self;

		if (obj.id)
			delete require.cache[require.resolve(obj.id)];

		if (typeof(obj.uninstall) === 'function') {
			if (framework.config['allow-compatibility'])
				obj.uninstall(self, options, name);
			else
				obj.uninstall(options, name);
		}

		if (type === 'model')
			delete self.models[name];
		else
			delete self.sources[name];

		delete self.dependencies[type + '.' + name];

		self._routesSort();
		self.emit('uninstall', type, name);
		return self;
	}

	if (type === 'module' || type === 'controller') {

		var isModule = type === 'module';
		obj = isModule ? self.modules[name] : self.controllers[name];

		if (!obj)
			return self;

		if (obj.id)
			delete require.cache[require.resolve(obj.id)];

		var id = (isModule ? '#' : '') + name;

		self.routes.web = self.routes.web.remove('controller', id);
		self.routes.files = self.routes.files.remove('controller', id);
		self.routes.websockets = self.routes.websockets.remove('controller', id);

		if (obj) {
			if (obj.uninstall) {
				if (framework.config['allow-compatibility'])
					obj.uninstall(self, options, name);
				else
					obj.uninstall(options, name);
			}

			if (isModule)
				delete self.modules[name];
			else
				delete self.controllers[name];
		}

		self._routesSort();
		delete self.dependencies[type + '.' + name];

		if (!skipEmit)
			self.emit('uninstall', type, name);

		return self;
	}

	return self;
};

/**
 * Register internal mapping (e.g. Resource)
 * @param {String} path
 * @return {Framework}
 */
Framework.prototype.register = function(path) {

	var extension = '.' + framework_utils.getExtension(path);
	var self = this;
	var name = framework_utils.getName(path);
	var key;
	var c = path[0];

	if (c === '@')
		path = framework.path.package(path.substring(1));
	else if (c === '=') {
		if (path[1] === '?')
			framework.path.themes(framework.config['default-theme'] + path.substring(2));
		else
			path = framework.path.themes(path.substring(1));
	}

	switch (extension) {
		case '.resource':
			key = name.replace(extension, '');
			if (!self.routes.resources[key])
				self.routes.resources[key] = [path];
			else
				self.routes.resources[key].push(path);

			// clears cache
			delete self.resources[key];
			break;

		default:
			throw new Error('Not supported registration type "' + extension + '".');
	}

	return self;
};

/**
 * Run code
 * @param {String or Function} script Function to eval or Code or URL address.
 * @return {Framework}
 */
Framework.prototype.eval = function(script) {
	return this.install('eval', script);
};

/**
 * Error handler
 * @param {Error} err
 * @param {String} name
 * @param {Object} uri URI address, optional.
 * @return {Framework}
 */
Framework.prototype.onError = function(err, name, uri) {
	console.log('======= ' + (new Date().format('yyyy-MM-dd HH:mm:ss')) + ': ' + (name ? name + ' ---> ' : '') + err.toString() + (uri ? ' (' + Parser.format(uri) + ')' : ''), err.stack);
	return this;
};

/*
	Authorization handler
	@req {Request}
	@res {Response} OR {WebSocketClient}
	@flags {String array}
	@callback {Function} - @callback(Boolean), true is [authorize]d and false is [unauthorize]d
*/
Framework.prototype.onAuthorize = null;

/*
	Sets the current language for the current request
	@req {Request}
	@res {Response} OR {WebSocketClient}
	@return {String}
*/
Framework.prototype.onLocale = null;
// OLD: Framework.prototype.onLocate = null;

/**
 * Sets theme to controlller
 * @controller {Controller}
 * @return {String}
 */
Framework.prototype.onTheme = null;

/*
	Versioning static files (this delegate call LESS CSS by the background property)
	@name {String} :: name of static file (style.css or script.js)
	return {String} :: return new name of static file (style-new.css or script-new.js)
*/
Framework.prototype.onVersion = null;

/**
 * On mapping static files
 * @param {String} url
 * @param {String} def Default value.
 * @return {String}
 */
Framework.prototype.onMapping = function(url, def, ispublic, encode) {

	if (url[0] !== '/')
		url = '/' + url;

	if (this._length_themes) {
		var index = url.indexOf('/', 1);
		if (index !== -1) {
			var themeName = url.substring(1, index);
			if (this.themes[themeName])
				return this.themes[themeName] + 'public' + url.substring(index);
		}
	}

	if (this.routes.mapping[url])
		return this.routes.mapping[url];

	def = framework_internal.preparePath(def, true);

	if (encode)
		def = $decodeURIComponent(def);

	if (ispublic)
		def = framework.path.public_cache(def);
	else
		def = def[0] === '~' ? def.substring(1) : def[0] === '.' ? def : framework.path.public_cache(def);

	return def;
};

/**
 * Snapshot
 * @param {String} url Relative URL.
 * @param {String} filename Filename to save output.
 * @param {Function} callback
 * @return {Framework}
 */
Framework.prototype.snapshot = function(url, filename, callback) {
	var self = this;

	url = framework_internal.preparePath(url);

	if (!url.match(/^http:|https:/gi)) {
		if (url[0] !== '/')
			url = '/' + url;
		var ip = self.ip === 'auto' ? '0.0.0.0' : self.ip;
		url = 'http://' + ip + ':' + self.port + url;
	}

	framework_utils.download(url, ['get'], function(error, response) {
		var stream = Fs.createWriteStream(filename);
		response.pipe(stream);
		FINISHED(stream, function() {
			DESTROY(stream);
			callback && callback();
		});
	});

	return self;
};

/**
 * Find WebSocket connection
 * @param {String/RegExp} path
 * @return {WebSocket}
 */
Framework.prototype.findConnection = function(path) {
	var self = this;
	var arr = Object.keys(self.connections);
	var is = framework_utils.isRegExp(path);
	for (var i = 0, length = arr.length; i < length; i++) {
		var key = arr[i];
		if (is) {
			if (path.test(key))
				return self.connections[key];
		} else {
			if (key.indexOf(path) !== -1)
				return self.connections[key];
		}
	}
};

/**
 * Find WebSocket connections
 * @param {String/RegExp} path
 * @return {WebSocket Array}
 */
Framework.prototype.findConnections = function(path) {
	var self = this;
	var arr = Object.keys(self.connections);
	var is = framework_utils.isRegExp(path);
	var output = [];
	for (var i = 0, length = arr.length; i < length; i++) {
		var key = arr[i];
		if (is) {
			if (path.test(key))
				output.push(self.connections[key]);
		} else {
			if (key.indexOf(path) !== -1)
				output.push(self.connections[key]);
		}
	}
	return output;
};

/**
 * Global validation
 * @param {Function(name, value)} delegate
 * @type {Boolean or StringErrorMessage}
 */
Framework.prototype.onValidate = null;

/**
 * Global XML parsing
 * @param {String} value
 * @return {Object}
 */
Framework.prototype.onParseXML = function(value) {
	return framework_utils.parseXML(value);
};

/**
 * Global JSON parsing
 * @param {String} value
 * @return {Object}
 */
Framework.prototype.onParseJSON = function(value) {
	return JSON.parse(value);
};

/**
 * Global JSON parsing
 * @param {String} value
 * @return {Object}
 */
Framework.prototype.onParseQuery = function(value) {
	return value ? Qs.parse(value, null, null, QUERYPARSEROPTIONS) : {};
};

/**
 * Schema parser delegate
 * @param {Request} req
 * @param {String} group
 * @param {String} name
 * @param {Function(err, body)} callback
 */
Framework.prototype.onSchema = function(req, group, name, callback, filter) {
	var schema = GETSCHEMA(group, name);
	if (schema)
		schema.make(req.body, (err, res) => err ? callback(err) : callback(null, res), filter);
	else
		callback(new Error('Schema "' + group + '/' + name + '" not found.'));
};

/**
 * Mail delegate
 * @param {String or Array String} address
 * @param {String} subject
 * @param {String} body
 * @param {Function(err)} callback
 * @param {String} replyTo
 * @return {MailMessage}
 */
Framework.prototype.onMail = function(address, subject, body, callback, replyTo) {

	var tmp;

	if (typeof(callback) === 'string') {
		tmp = replyTo;
		replyTo = callback;
		callback = tmp;
	}

	var message = Mail.create(subject, body);

	if (address instanceof Array) {
		for (var i = 0, length = address.length; i < length; i++)
			message.to(address[i]);
	} else
		message.to(address);

	var self = this;

	message.from(self.config['mail.address.from'] || '', self.config.name);
	tmp = self.config['mail.address.reply'];

	if (replyTo)
		message.reply(replyTo);
	else if (tmp && tmp.isEmail())
		message.reply(self.config['mail.address.reply']);

	tmp = self.config['mail.address.copy'];

	if (tmp && tmp.isEmail())
		message.bcc(tmp);

	var opt = self.temporary['mail-settings'];

	if (!opt) {
		var config = self.config['mail.smtp.options'];
		if (config) {
			var type = typeof(config);
			if (type === 'string')
				opt = config.parseJSON();
			else if (type === 'object')
				opt = config;
		}

		if (!opt)
			opt = {};

		self.temporary['mail-settings'] = opt;
	}

	message.$sending = setTimeout(() => message.send(self.config['mail.smtp'], opt, callback), 5);
	return message;
};

Framework.prototype.onMeta = function() {

	var self = this;
	var builder = '';
	var length = arguments.length;

	for (var i = 0; i < length; i++) {

		var arg = framework_utils.encode(arguments[i]);
		if (arg == null || !arg.length)
			continue;

		switch (i) {
			case 0:
				builder += '<title>' + (arg + (self.url !== '/' && !self.config['allow-custom-titles'] ? ' - ' + self.config.name : '')) + '</title>';
				break;
			case 1:
				builder += '<meta name="description" content="' + arg + '" />';
				break;
			case 2:
				builder += '<meta name="keywords" content="' + arg + '" />';
				break;
			case 3:
				var tmp = arg.substring(0, 6);
				var img = tmp === 'http:/' || tmp === 'https:' || arg.substring(0, 2) === '//' ? arg : self.hostname(self.routeImage(arg));
				builder += '<meta property="og:image" content="' + img + '" /><meta name="twitter:image" content="' + img + '" />';
				break;
		}
	}

	return builder;
};

// @arguments {Object params}
Framework.prototype.log = function() {

	var self = this;
	var now = new Date();
	var filename = now.getFullYear() + '-' + (now.getMonth() + 1).toString().padLeft(2, '0') + '-' + now.getDate().toString().padLeft(2, '0');
	var time = now.getHours().toString().padLeft(2, '0') + ':' + now.getMinutes().toString().padLeft(2, '0') + ':' + now.getSeconds().toString().padLeft(2, '0');
	var str = '';
	var length = arguments.length;

	for (var i = 0; i < length; i++) {
		var val = arguments[i];
		if (val === undefined)
			val = 'undefined';
		else if (val === null)
			val = 'null';
		else if (typeof(val) === 'object')
			val = Util.inspect(val);
		str += (str ? ' ' : '') + val;
	}

	self.path.verify('logs');
	framework_utils.queue('framework.log', 5, (next) => Fs.appendFile(framework_utils.combine(self.config['directory-logs'], filename + '.log'), time + ' | ' + str + '\n', next));
	return self;
};

Framework.prototype.logger = function() {
	var self = this;
	var now = new Date();
	var dt = now.getFullYear() + '-' + (now.getMonth() + 1).toString().padLeft(2, '0') + '-' + now.getDate().toString().padLeft(2, '0') + ' ' + now.getHours().toString().padLeft(2, '0') + ':' + now.getMinutes().toString().padLeft(2, '0') + ':' + now.getSeconds().toString().padLeft(2, '0');
	var str = '';
	var length = arguments.length;

	for (var i = 1; i < length; i++) {
		var val = arguments[i];
		if (val === undefined)
			val = 'undefined';
		else if (val === null)
			val = 'null';
		else if (typeof(val) === 'object')
			val = Util.inspect(val);
		str += (str ? ' ' : '') + val;
	}

	self.path.verify('logs');
	framework_utils.queue('framework.logger', 5, (next) => Fs.appendFile(framework_utils.combine(self.config['directory-logs'], arguments[0] + '.log'), dt + ' | ' + str + '\n', next));
	return self;
};

Framework.prototype.logmail = function(address, subject, body, callback) {

	if (typeof(body) === FUNCTION) {
		callback = body;
		body = subject;
		subject = null;
	} else if (body === undefined) {
		body = subject;
		subject = null;
	}

	if (!subject)
		subject = framework.config.name + ' v' + framework.config.version;

	var self = this;
	var body = '<!DOCTYPE html><html><head><title>' + subject + '</title><meta charset="utf-8" /></head><body><pre style="max-width:600px;font-size:13px;line-height:16px">' + (typeof(body) === 'object' ? JSON.stringify(body).escape() : body) + '</pre></body></html>';
	return framework.onMail(address, subject, body, callback);
};

Framework.prototype.usage = function(detailed) {
	var self = this;
	var memory = process.memoryUsage();
	var cache = Object.keys(self.cache.items);
	var resources = Object.keys(self.resources);
	var controllers = Object.keys(self.controllers);
	var connections = Object.keys(self.connections);
	var workers = Object.keys(self.workers);
	var modules = Object.keys(self.modules);
	var isomorphic = Object.keys(self.isomorphic);
	var models = Object.keys(self.models);
	var helpers = Object.keys(self.helpers);
	var staticFiles = Object.keys(self.temporary.path);
	var staticRange = Object.keys(self.temporary.range);
	var redirects = Object.keys(self.routes.redirects);
	var output = {};

	output.framework = {
		pid: process.pid,
		node: process.version,
		version: 'v' + self.version_header,
		platform: process.platform,
		processor: process.arch,
		uptime: Math.floor(process.uptime() / 60),
		memoryTotal: (memory.heapTotal / 1024 / 1024).floor(2),
		memoryUsage: (memory.heapUsed / 1024 / 1024).floor(2),
		mode: self.config.debug ? 'debug' : 'release',
		port: self.port,
		ip: self.ip,
		directory: process.cwd()
	};

	var keys = Object.keys(framework_utils.queuecache);
	var pending = 0;
	for (var i = 0, length = keys.length; i < length; i++)
		pending += framework_utils.queuecache[keys[i]].pending.length;

	output.counter = {
		resource: resources.length,
		controller: controllers.length,
		module: modules.length,
		isomorphic: isomorphic.length,
		cache: cache.length,
		worker: workers.length,
		connection: connections.length,
		schedule: self.schedules.length,
		helpers: helpers.length,
		error: self.errors.length,
		problem: self.problems.length,
		queue: pending,
		files: staticFiles.length,
		streaming: staticRange.length,
		modificator:  self.modificators ? self.modificators.length : 0,
		viewphrases: $VIEWCACHE.length
	};

	output.routing = {
		webpage: self.routes.web.length,
		sitemap: self.routes.sitemap ? Object.keys(self.routes.sitemap).length : 0,
		websocket: self.routes.websockets.length,
		file: self.routes.files.length,
		middleware: Object.keys(self.routes.middleware).length,
		redirect: redirects.length,
		mmr: Object.keys(self.routes.mmr).length
	};

	output.stats = self.stats;
	output.redirects = redirects;

	if (self.restrictions.is) {
		output.restrictions = {
			allowed: [],
			blocked: [],
			allowedHeaders: self.restrictions.allowedCustomKeys,
			blockedHeaders: self.restrictions.blockedCustomKeys
		};
	}

	if (!detailed)
		return output;

	output.controllers = [];

	controllers.forEach(function(o) {
		var item = self.controllers[o];
		output.controllers.push({
			name: o,
			usage: item.usage === undefined ? null : item.usage()
		});
	});

	output.connections = [];

	connections.forEach(function(o) {
		output.connections.push({
			name: o,
			online: self.connections[o].online
		});
	});

	output.modules = [];

	modules.forEach(function(o) {
		var item = self.modules[o];
		output.modules.push({
			name: o,
			usage: item.usage === undefined ? null : item.usage()
		});
	});

	output.models = [];

	models.forEach(function(o) {
		var item = self.models[o];
		output.models.push({
			name: o,
			usage: item.usage === undefined ? null : item.usage()
		});
	});

	output.helpers = helpers;
	output.cache = cache;
	output.resources = resources;
	output.errors = self.errors;
	output.problems = self.problems;
	output.changes = self.changes;
	output.traces = self.traces;
	output.files = staticFiles;
	output.streaming = staticRange;
	output.other = Object.keys(self.temporary.other);
	return output;
};

/**
 * Compiles content in the view @{compile}...@{end}. The function has controller context, this === controler.
 * @param {String} name
 * @param {String} html HTML content to compile
 * @param {Object} model
 * @return {String}
 */
Framework.prototype.onCompileView = function(name, html, model) {
	return html;
};

/*
	3rd CSS compiler (Sync)
	@filename {String}
	@content {String} :: Content of CSS file
	return {String}
*/
Framework.prototype.onCompileStyle = null;

/*
	3rd JavaScript compiler (Sync)
	@filename {String}
	@content {String} :: Content of JavaScript file
	return {String}
*/
Framework.prototype.onCompileScript = null;

/**
 * Compile content (JS, CSS, HTML)
 * @param {String} extension File extension.
 * @param {String} content File content.
 * @param {String} filename
 * @return {String}
 */
Framework.prototype.compileContent = function(extension, content, filename) {

	var self = this;

	if (filename && REG_NOCOMPRESS.test(filename))
		return content;

	switch (extension) {
		case 'js':
			return self.config['allow-compile-script'] ? framework_internal.compile_javascript(content, filename) : content;

		case 'css':
			content = self.config['allow-compile-style'] ? framework_internal.compile_css(content, filename) : content;
			var matches = content.match(REG_COMPILECSS);
			if (matches) {
				for (var i = 0, length = matches.length; i < length; i++) {
					var key = matches[i];
					var url = key.substring(4, key.length - 1);
					content = content.replace(key, 'url(' + self._version(url) + ')');
				}
			}
			return content;
	}

	return content;
};

/**
 * Compile static file
 * @param {URI} uri
 * @param {String} key Temporary key.
 * @param {String} filename
 * @param {String} extension File extension.
 * @param {Function()} callback
 * @return {Framework}
 */
Framework.prototype.compileFile = function(uri, key, filename, extension, callback) {

	var self = this;

	fsFileRead(filename, function(err, buffer) {

		if (err) {
			self.error(err, filename, uri);
			self.temporary.path[key] = null;
			callback();
			return;
		}

		var file = self.path.temp((self.id ? 'i-' + self.id + '_' : '') + createTemporaryKey(uri.pathname));
		self.path.verify('temp');
		Fs.writeFileSync(file, self.compileContent(extension, framework_internal.parseBlock(self.routes.blocks[uri.pathname], buffer.toString(ENCODING)), filename), ENCODING);
		self.temporary.path[key] = file + ';' + Fs.statSync(file).size;
		callback();

	});

	return self;
};

/**
 * Merge static files (JS, CSS, HTML, TXT, JSON)
 * @param {URI} uri
 * @param {String} key Temporary key.
 * @param {String} extension File extension.
 * @param {Function()} callback
 * @return {Framework}
 */
Framework.prototype.compileMerge = function(uri, key, extension, callback) {

	var self = this;
	var merge = self.routes.merge[uri.pathname];
	var filename = merge.filename;

	if (!self.config.debug && existsSync(filename)) {
		self.temporary.path[key] = filename + ';' + Fs.statSync(filename).size;
		callback();
		return self;
	}

	var writer = Fs.createWriteStream(filename);

	writer.on('finish', function() {
		self.temporary.path[key] = filename + ';' + Fs.statSync(filename).size;
		callback();
	});

	var index = 0;
	var remove;

	merge.files.wait(function(filename, next) {

		var block;

		// Skip isomorphic
		if (filename[0] !== '#') {
			var blocks = filename.split('#');
			block = blocks[1];
			if (block)
				filename = blocks[0];
		}

		if (filename.startsWith('http://') || filename.startsWith('https://')) {
			framework_utils.request(filename, ['get'], function(err, data) {

				var output = self.compileContent(extension, framework_internal.parseBlock(block, data), filename);

				if (extension === 'js') {
					if (output[output.length - 1] !== ';')
						output += ';';
				} else if (extension === 'html') {
					if (output[output.length - 1] !== NEWLINE)
						output += NEWLINE;
				}

				framework.isDebug && merge_debug_writer(writer, filename, extension, index++, block);
				writer.write(output);
				next();
			});
			return;
		}

		if (filename[0] === '#') {
			framework.isDebug && merge_debug_writer(writer, filename, 'js', index++, block);
			writer.write(prepare_isomorphic(filename.substring(1)));
			next();
			return;
		}

		if (filename[0] !== '~') {
			var tmp = self.path.public(filename);
			if (self.isVirtualDirectory && !existsSync(tmp))
				tmp = self.path.virtual(filename);
			filename = tmp;
		}
		else
			filename = filename.substring(1);

		var indexer = filename.indexOf('*');
		if (indexer !== -1) {

			var tmp = filename.substring(indexer + 1).toLowerCase();
			var len = tmp.length;

			if (!remove)
				remove = [];

			// Remove directory for all future requests
			remove.push(arguments[0]);

			framework_utils.ls(filename.substring(0, indexer), function(files, directories) {
				for (var j = 0, l = files.length; j < l; j++)
					merge.files.push('~' + files[j]);
				next();
			}, function(path, isDirectory) {
				return isDirectory ? true : path.substring(path.length - len).toLowerCase() === tmp;
			});

			return;
		}

		fsFileRead(filename, function(err, buffer) {

			if (err) {
				self.error(err, merge.filename, uri);
				next();
				return;
			}

			var output = self.compileContent(extension, framework_internal.parseBlock(block, buffer.toString(ENCODING)), filename);

			if (extension === 'js') {
				if (output[output.length - 1] !== ';')
					output += ';';
			} else if (extension === 'html') {
				if (output[output.length - 1] !== NEWLINE)
					output += NEWLINE;
			}

			framework.isDebug && merge_debug_writer(writer, filename, extension, index++, block);
			writer.write(output);
			next();
		});

	}, function() {

		writer.end();

		// Removes all directories from merge list (because the files are added into the queue)
		if (remove) {
			for (var i = 0, length = remove.length; i < length; i++)
				merge.files.splice(merge.files.indexOf(remove[i]), 1);
		}
	});

	return self;
};

function merge_debug_writer(writer, filename, extension, index, block) {
	var plus = '===========================================================================================';
	var beg = extension === 'js' ? '/*\n' : extension === 'css' ? '/*!\n' : '<!--\n';
	var end = extension === 'js' || extension === 'css' ? '\n */' : '\n-->';
	var mid = extension !== 'html' ? ' * ' : ' ';
	writer.write((index > 0 ? '\n\n' : '') + beg + mid + plus + '\n' + mid + 'MERGED: ' + filename + '\n' + (block ? mid + 'BLOCKS: ' + block + '\n' : '') + mid + plus + end + '\n\n', ENCODING);
}

/**
 * Validating static file for compilation
 * @param {URI} uri
 * @param {String} key Temporary key.
 * @param {String} filename
 * @param {String} extension File extension.
 * @param {Function()} callback
 * @return {Framework}
 */
Framework.prototype.compileValidation = function(uri, key, filename, extension, callback, noCompress) {

	var self = this;

	if (self.routes.merge[uri.pathname]) {
		self.compileMerge(uri, key, extension, callback);
		return self;
	}

	fsFileExists(filename, function(e, size) {

		if (e) {

			if (!noCompress && (extension === 'js' || extension === 'css') && !REG_NOCOMPRESS.test(filename))
				return self.compileFile(uri, key, filename, extension, callback);

			self.temporary.path[key] = filename + ';' + size;
			callback();
			return;
		}

		if (self.isVirtualDirectory) {
			self.compileValidationVirtual(uri, key, filename, extension, callback, noCompress);
			return;
		}

		self.temporary.path[key] = null;
		callback();
	});

	return self;
};

Framework.prototype.compileValidationVirtual = function(uri, key, filename, extension, callback, noCompress) {

	var self = this;

	var tmpname = filename.replace(self.config['directory-public'], self.config['directory-public-virtual']);
	var notfound = true;

	if (tmpname === filename) {
		self.temporary.path[key] = null;
		callback();
		return;
	}

	filename = tmpname;
	fsFileExists(filename, function(e, size) {

		if (!e) {
			self.temporary.path[key] = null;
			callback();
			return;
		}

		if (!noCompress && (extension === 'js' || extension === 'css') && !REG_NOCOMPRESS.test(filename))
			return self.compileFile(uri, key, filename, extension, callback);

		self.temporary.path[key] = filename + ';' + size;
		callback();
	});

	return;
};

/**
 * Server all static files
 * @param {Request} req
 * @param {Response} res
 * @return {Framework}
 */
Framework.prototype.responseStatic = function(req, res, done) {

	var self = this;

	if (res.success || res.headersSent) {
		done && done();
		return self;
	}

	if (!self.config['static-accepts']['.' + req.extension]) {
		self.response404(req, res);
		done && done();
		return self;
	}

	var name = req.uri.pathname;
	var index = name.lastIndexOf('/');
	var resizer = self.routes.resize[name.substring(0, index + 1)] || null;
	var isResize = false;
	var filename;

	if (resizer) {
		name = name.substring(index + 1);
		index = name.lastIndexOf('.');
		isResize = resizer.extension['*'] || resizer.extension[name.substring(index).toLowerCase()];
		if (isResize) {
			name = resizer.path + $decodeURIComponent(name);
			filename = self.onMapping(name, name, false, false);
		} else
			filename = self.onMapping(name, name, true, true);
	} else
		filename = self.onMapping(name, name, true, true);

	if (!isResize) {

		// is isomorphic?
		if (filename[0] !== '#') {
			self.responseFile(req, res, filename, undefined, undefined, done);
			return self;
		}

		var key = filename.substring(1);
		var iso = self.isomorphic[key];

		if (!iso) {
			self.response404(req, res);
			done && done();
			return;
		}

		var etag = framework_utils.etag(filename, (iso.version || '') + '-' + (self.config['etag-version'] || ''));
		if (RELEASE && self.notModified(req, res, etag)) {
			done && done();
			return;
		}

		// isomorphic
		var headers = {};

		if (RELEASE) {
			headers['Etag'] = etag;
			headers['Expires'] = DATE_EXPIRES;
			headers[RESPONSE_HEADER_CACHECONTROL] = 'public, max-age=' + self.config['default-response-maxage'];
		}

		self.responseContent(req, res, 200, prepare_isomorphic(key), 'text/javascript', true, headers);
		done && done();
		return self;
	}

	if (!resizer.ishttp) {
		var method = resizer.cache ? self.responseImage : self.responseImageWithoutCache;
		method.call(self, req, res, filename, (image) => resizer.fn.call(image, image), undefined, done);
		return;
	}

	if (self.temporary.processing[req.uri.pathname]) {
		setTimeout(() => self.responseStatic(req, res, done), 500);
		return;
	}

	var key = createTemporaryKey(req);
	var tmp = self.path.temp(key);

	if (self.temporary.path[key]) {
		self.responseFile(req, res, req.uri.pathname, undefined, undefined, done);
		return self;
	}

	self.temporary.processing[req.uri.pathname] = true;

	framework_utils.download(name, ['get', 'dnscache'], function(err, response) {
		var writer = Fs.createWriteStream(tmp);
		response.pipe(writer);
		CLEANUP(writer, function() {

			delete self.temporary.processing[req.uri.pathname];
			var contentType = response.headers['content-type'];

			if (response.statusCode !== 200 || !contentType || !contentType.startsWith('image/')) {
				self.response404(req, res);
				done && done();
				return;
			}

			var method = resizer.cache ? self.responseImage : self.responseImageWithoutCache;
			method.call(self, req, res, tmp, (image) => resizer.fn.call(image, image), undefined, done);
		});
	});

	return self;
};

Framework.prototype.restore = function(filename, target, callback, filter) {
	var backup = new Backup();
	backup.restore(filename, target, callback, filter);
};

Framework.prototype.backup = function(filename, path, callback, filter) {

	var length = path.length;
	var padding = 120;

	framework_utils.ls(path, function(files, directories) {
		directories.wait(function(item, next) {
			var dir = item.substring(length).replace(/\\/g, '/') + '/';
			if (filter && !filter(dir))
				return next();
			Fs.appendFile(filename, dir.padRight(padding) + ':#\n', next);
		}, function() {
			files.wait(function(item, next) {
				var fil = item.substring(length).replace(/\\/g, '/');
				if (filter && !filter(fil))
					return next();
				Fs.readFile(item, function(err, data) {
					Zlib.gzip(data, function(err, data) {
						if (err) {
							framework.error(err, 'framework.backup()', filename);
							return next();
						}
						Fs.appendFile(filename, fil.padRight(padding) + ':' + data.toString('base64') + '\n', next);
					});
				});
			}, callback);
		});
	});

	return this;
};

Framework.prototype.exists = function(req, res, max, callback) {

	if (typeof(max) === 'function') {
		callback = max;
		max = 10;
	}

	var self = this;
	var name = createTemporaryKey(req);
	var filename = self.path.temp(name);
	var httpcachevalid = false;

	if (RELEASE) {
		var etag = framework_utils.etag(req.url, self.config['etag-version']);
		if (req.headers['if-none-match'] === etag)
			httpcachevalid = true;
	}

	if (self.isProcessed(name) || httpcachevalid) {
		self.responseFile(req, res, filename);
		return self;
	}

	framework_utils.queue('framework.exists', max, function(next) {
		fsFileExists(filename, function(e) {
			if (e)
				framework.responseFile(req, res, filename, undefined, undefined, next);
			else
				callback(next, filename);
		});
	});

	return self;
};

/**
 * Is processed static file?
 * @param {String / Request} filename Filename or Request object.
 * @return {Boolean}
 */
Framework.prototype.isProcessed = function(filename) {

	var self = this;

	if (filename.url) {
		var name = filename.url;
		var index = name.indexOf('?');
		if (index !== -1)
			name = name.substring(0, index);
		filename = framework.path.public($decodeURIComponent(name));
	}

	return self.temporary.path[filename] !== undefined;
};

/**
 * Processing
 * @param {String / Request} filename Filename or Request object.
 * @return {Boolean}
 */
Framework.prototype.isProcessing = function(filename) {

	var self = this;
	if (!filename.url)
		return self.temporary.processing[filename] ? true : false;

	var name = filename.url;
	var index = name.indexOf('?');

	if (index !== -1)
		name = name.substring(0, index);

	filename = framework_utils.combine(self.config['directory-public'], $decodeURIComponent(name));
	return self.temporary.processing[filename] ? true : false;
};

/**
 * Disable HTTP cache for current request/response
 * @param  {Request}  req Request
 * @param  {Response} res (optional) Response
 * @return {Framework}
 */
Framework.prototype.noCache = function(req, res) {
	req.noCache();
	res && res.noCache();
	return this;
};

/**
 * Response file
 * @param {Request} req
 * @param {Response} res
 * @param {String} filename
 * @param {String} downloadName Optional
 * @param {Object} headers Optional
 * @param {Function} done Optional, callback.
 * @param {String} key Path to file, INTERNAL.
 * @return {Framework}
 */
Framework.prototype.responseFile = function(req, res, filename, downloadName, headers, done, key) {

	var self = this;

	if (res.success || res.headersSent) {
		done && done();
		return self;
	}

	// Is package?
	if (filename[0] === '@')
		filename = framework.path.package(filename.substring(1));

	if (!key)
		key = createTemporaryKey(req);

	var name = self.temporary.path[key];
	if (name === null) {

		if (self.config.debug)
			self.temporary.path[key] = undefined;

		self.response404(req, res);
		done && done();
		return self;
	}

	var etag = framework_utils.etag(req.url, self.config['etag-version']);
	var extension = req.extension;
	var returnHeaders;
	var index;

	if (!extension) {
		if (key)
			extension = framework_utils.getExtension(key);
		if (!extension && name) {
			extension = framework_utils.getExtension(name);
			index = extension.lastIndexOf(';');
			if (index !== -1)
				extension = extension.substring(0, index);
		}
		if (!extension && filename)
			extension = framework_utils.getExtension(filename);
	}

	if (!self.config.debug && req.headers['if-none-match'] === etag) {

		returnHeaders = HEADERS['responseFile.etag'];

		if (!res.getHeader('ETag') && etag)
			returnHeaders.ETag = etag;
		else if (returnHeaders.ETag)
			delete returnHeaders.ETag;

		if (!res.getHeader('Expires'))
			returnHeaders.Expires = DATE_EXPIRES;
		else if (returnHeaders.Expires)
			delete returnHeaders.Expires;

		returnHeaders[RESPONSE_HEADER_CONTENTTYPE] = framework_utils.getContentType(extension);

		res.success = true;
		res.writeHead(304, returnHeaders);
		res.end();
		self.stats.response.notModified++;
		self._request_stats(false, req.isStaticFile);

		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		req.clear(true);
		return self;
	}

	// JS, CSS
	if (name === undefined) {

		if (self.isProcessing(key)) {

			if (req.processing > self.config['default-request-timeout']) {
				self.response408(req, res);
				return;
			}

			req.processing += 500;
			setTimeout(() => framework.responseFile(req, res, filename, downloadName, headers, done, key), 500);
			return self;
		}

		// waiting
		self.temporary.processing[key] = true;

		// checks if the file exists and counts the file size
		self.compileValidation(req.uri, key, filename, extension, function() {
			delete self.temporary.processing[key];
			framework.responseFile(req, res, filename, downloadName, headers, done, key);
		}, res.noCompress);

		return self;
	}

	index = name.lastIndexOf(';');
	var size = null;

	if (index === -1)
		index = name.length;
	else
		size = name.substring(index + 1);

	name = name.substring(0, index);

	var contentType = framework_utils.getContentType(extension);
	var accept = req.headers['accept-encoding'] || '';

	if (!accept && isGZIP(req))
		accept = 'gzip';

	var compress = self.config['allow-gzip'] && REQUEST_COMPRESS_CONTENTTYPE[contentType] && accept.indexOf('gzip') !== -1;
	var range = req.headers['range'] || '';
	var canCache = RELEASE && contentType !== 'text/cache-manifest';

	if (canCache) {
		if (compress)
			returnHeaders = range ? HEADERS['responseFile.release.compress.range'] : HEADERS['responseFile.release.compress'];
		else
			returnHeaders = range ? HEADERS['responseFile.release.range'] : HEADERS['responseFile.release'];
	} else {
		if (compress)
			returnHeaders = range ? HEADERS['responseFile.debug.compress.range'] : HEADERS['responseFile.debug.compress'];
		else
			returnHeaders = range ? HEADERS['responseFile.debug.range'] : HEADERS['responseFile.debug'];
	}

	if (req.$mobile)
		returnHeaders.Vary = 'Accept-Encoding, User-Agent';
	else
		returnHeaders.Vary = 'Accept-Encoding';

	returnHeaders[RESPONSE_HEADER_CONTENTTYPE] = contentType;
	if (REG_TEXTAPPLICATION.test(contentType))
		returnHeaders[RESPONSE_HEADER_CONTENTTYPE] += '; charset=utf-8';

	if (canCache && !res.getHeader('Expires'))
		returnHeaders.Expires = DATE_EXPIRES;
	else if (returnHeaders.Expires)
		delete returnHeaders.Expires;

	if (headers) {
		returnHeaders = framework_utils.extend({}, returnHeaders, true);
		framework_utils.extend(returnHeaders, headers, true);
	}

	if (downloadName)
		returnHeaders['Content-Disposition'] = 'attachment; filename="' + encodeURIComponent(downloadName) + '"';
	else if (returnHeaders['Content-Disposition'])
		delete returnHeaders['Content-Disposition'];

	if (canCache && etag && !res.getHeader('ETag'))
		returnHeaders.Etag = etag;
	else if (returnHeaders.Etag)
		delete returnHeaders.Etag;

	res.success = true;

	if (range) {
		self.responseRange(name, range, returnHeaders, req, res, done);
		return self;
	}

	if (self.config.debug && self.isProcessed(key))
		self.temporary.path[key] = undefined;

	if (size && size !== '0' && !compress)
		returnHeaders[RESPONSE_HEADER_CONTENTLENGTH] = size;
	else if (returnHeaders[RESPONSE_HEADER_CONTENTLENGTH])
		delete returnHeaders[RESPONSE_HEADER_CONTENTLENGTH];

	self.stats.response.file++;
	self._request_stats(false, req.isStaticFile);

	if (req.method === 'HEAD') {
		res.writeHead(200, returnHeaders);
		res.end();
		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		req.clear(true);
		return self;
	}

	if (compress) {
		res.writeHead(200, returnHeaders);
		fsStreamRead(name, undefined, function(stream, next) {
			framework_internal.onFinished(res, function(err) {
				framework_internal.destroyStream(stream);
				next();
			});

			stream.pipe(Zlib.createGzip()).pipe(res);
			done && done();
			!req.isStaticFile && self.emit('request-end', req, res);
			req.clear(true);
		});
		return self;
	}

	res.writeHead(200, returnHeaders);
	fsStreamRead(name, undefined, function(stream, next) {
		stream.pipe(res);
		framework_internal.onFinished(res, function(err) {
			framework_internal.destroyStream(stream);
			next();
		});

		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		req.clear(true);
	});

	return self;
};

/**
 * Clears file information in release mode
 * @param {String/Request} url
 * @return {Framework}
 */
Framework.prototype.touch = function(url) {

	if (url)
		delete this.temporary.path[createTemporaryKey(url)];
	else
		this.temporary.path = {};

	return this;
};

/**
 * Creates a pipe between the current request and target URL
 * @param {Request} req
 * @param {Response} res
 * @param {String} url
 * @param {Object} headers Additional headers, optional.
 * @param {Number} timeout
 * @param {Function(err)} callback
 * @return {Framework}
 */
Framework.prototype.responsePipe = function(req, res, url, headers, timeout, callback) {

	var self = this;

	if (res.success || res.headersSent)
		return self;

	framework_utils.resolve(url, function(err, uri) {
		var h = {};

		h[RESPONSE_HEADER_CACHECONTROL] = 'private';
		headers && framework_utils.extend(h, headers, true);

		var options = { protocol: uri.protocol, auth: uri.auth, method: 'GET', hostname: uri.hostname, port: uri.port, path: uri.path, agent: false, headers: h };
		var connection = options.protocol === 'https:' ? require('https') : http;
		var supportsGZIP = (req.headers['accept-encoding'] || '').lastIndexOf('gzip') !== -1;

		var client = connection.get(options, function(response) {

			if (res.success || res.headersSent)
				return;

			var contentType = response.headers['content-type'];
			var isGZIP = (response.headers['content-encoding'] || '').lastIndexOf('gzip') !== -1;
			var compress = !isGZIP && supportsGZIP && (contentType.indexOf('text/') !== -1 || contentType.lastIndexOf('javascript') !== -1 || contentType.lastIndexOf('json') !== -1);
			var attachment = response.headers['content-disposition'] || '';

			attachment && res.setHeader('Content-Disposition', attachment);
			res.setHeader(RESPONSE_HEADER_CONTENTTYPE, contentType);
			res.setHeader('Vary', 'Accept-Encoding' + (req.$mobile ? ', User-Agent' : ''));

			res.on('error', function(err) {
				response.close();
				callback && callback(err);
				callback = null;
			});

			if (compress) {
				res.setHeader('Content-Encoding', 'gzip');
				response.pipe(Zlib.createGzip()).pipe(res);
				return;
			}

			if (isGZIP && !supportsGZIP)
				response.pipe(Zlib.createGunzip()).pipe(res);
			else
				response.pipe(res);
		});

		timeout && client.setTimeout(timeout, function() {
			self.response408(req, res);
			callback && callback(new Error(framework_utils.httpStatus(408)));
			callback = null;
		});

		client.on('close', function() {

			if (res.success || res.headersSent)
				return;

			res.success = true;
			self.stats.response.pipe++;
			self._request_stats(false, req.isStaticFile);
			res.success = true;
			!req.isStaticFile && self.emit('request-end', req, res);
			req.clear(true);
			callback && callback();
		});
	});

	return self;
};

/**
 * Enables a custom respoding for the current response
 * @param {Request} req
 * @param {Response} res
 * @return {Framework}
 */
Framework.prototype.responseCustom = function(req, res) {

	var self = this;

	if (res.success || res.headersSent)
		return;

	res.success = true;
	self.stats.response.custom++;
	self._request_stats(false, req.isStaticFile);
	!req.isStaticFile && self.emit('request-end', req, res);
	req.clear(true);
	return self;
};

/**
 * Responds with an image
 * @param {Request} req
 * @param {Response} res
 * @param {String or Stream} filename
 * @param {Function(image)} fnProcess
 * @param {Object} headers Optional, additional headers.
 * @param {Function} done Optional, callback function.
 * @return {Framework}
 */
Framework.prototype.responseImage = function(req, res, filename, fnProcess, headers, done) {

	var self = this;
	var key = createTemporaryKey(req);

	var name = self.temporary.path[key];
	if (name === null) {
		self.response404(req, res);
		done && done();
		return self;
	}

	var stream = null;

	if (typeof(filename) === 'object')
		stream = filename;
	else if (filename[0] === '@')
		filename = self.path.package(filename.substring(1));

	if (name !== undefined) {
		self.responseFile(req, res, '', undefined, headers, done, key);
		return self;
	}

	var im = self.config['default-image-converter'] === 'im';

	if (self.isProcessing(key)) {

		if (req.processing > self.config['default-request-timeout']) {
			self.response408(req, res);
			done && done();
			return;
		}

		req.processing += 500;
		setTimeout(() => self.responseImage(req, res, filename, fnProcess, headers, done), 500);
		return;
	}

	var plus = self.id ? 'i-' + self.id + '_' : '';

	name = self.path.temp(plus + key);
	self.temporary.processing[key] = true;

	// STREAM
	if (stream) {
		fsFileExists(name, function(exist) {

			if (exist) {
				delete self.temporary.processing[key];
				self.temporary.path[key] = name;
				self.responseFile(req, res, name, undefined, headers, done, key);
				if (self.isDebug)
					self.temporary.path[key] = undefined;
				return;
			}

			self.path.verify('temp');
			var image = framework_image.load(stream, im);

			fnProcess(image);

			var extension = framework_utils.getExtension(name);
			if (extension !== image.outputType) {
				var index = name.lastIndexOf('.' + extension);
				if (index !== -1)
					name = name.substring(0, index) + '.' + image.outputType;
				else
					name += '.' + image.outputType;
			}

			image.save(name, function(err) {

				delete self.temporary.processing[key];

				if (err) {

					self.temporary.path[key] = null;
					self.response500(req, res, err);
					done && done();

					if (self.isDebug)
						self.temporary.path[key] = undefined;

					return;
				}

				self.temporary.path[key] = name + ';' + Fs.statSync(name).size;
				self.responseFile(req, res, name, undefined, headers, done, key);
			});
		});

		return self;
	}

	// FILENAME
	fsFileExists(filename, function(exist) {

		if (!exist) {

			delete self.temporary.processing[key];
			self.temporary.path[key] = null;
			self.response404(req, res);
			done && done();

			if (self.isDebug)
				self.temporary.path[key] = undefined;

			return;
		}

		self.path.verify('temp');

		var image = framework_image.load(filename, im);

		fnProcess(image);

		var extension = framework_utils.getExtension(name);
		if (extension !== image.outputType) {
			var index = name.lastIndexOf('.' + extension);
			if (index === -1)
				name +=  '.' + image.outputType;
			else
				name = name.substring(0, index) + '.' + image.outputType;
		}

		image.save(name, function(err) {

			delete self.temporary.processing[key];

			if (err) {
				self.temporary.path[key] = null;
				self.response500(req, res, err);
				done && done();

				if (self.isDebug)
					self.temporary.path[key] = undefined;

				return;
			}

			self.temporary.path[key] = name + ';' + Fs.statSync(name).size;
			self.responseFile(req, res, name, undefined, headers, done, key);
		});

	});

	return self;
};

Framework.prototype.responseImagePrepare = function(req, res, fnPrepare, fnProcess, headers, done) {

	var self = this;
	var key = createTemporaryKey(req);

	var name = self.temporary.path[key];
	if (name === null) {
		self.response404(req, res);
		done && done();
		return self;
	}

	if (name !== undefined) {
		self.responseFile(req, res, '', undefined, headers, done, key);
		return self;
	}

	if (self.isProcessing(key)) {
		if (req.processing > self.config['default-request-timeout']) {
			self.response408(req, res);
			done && done();
			return;
		}

		req.processing += 500;
		setTimeout(() => self.responseImage(req, res, filename, fnProcess, headers, done), 500);
		return;
	}

	fnPrepare.call(self, function(filename) {
		if (filename) {
			self.responseImage(req, res, filename, fnProcess, headers, done);
		} else {
			self.response404(req, res);
			done && done();
		}
	});

	return self;
};

/**
 * Responds with an image (not cached)
 * @param {Request} req
 * @param {Response} res
 * @param {String or Stream} filename
 * @param {Function(image)} fnProcess
 * @param {Object} headers Optional, additional headers
 * @param {Function} done Optional, callback.
 * @return {Framework}
 */
Framework.prototype.responseImageWithoutCache = function(req, res, filename, fnProcess, headers, done) {

	var self = this;
	var stream = null;

	if (typeof(filename) === 'object')
		stream = filename;
	else if (filename[0] === '@')
		filename = framework.path.package(filename.substring(1));

	var im = self.config['default-image-converter'] === 'im';

	// STREAM
	if (stream) {
		var image = framework_image.load(stream, im);
		fnProcess(image);
		self.responseStream(req, res, framework_utils.getContentType(image.outputType), image.stream(), null, headers, done);
		return self;
	}

	// FILENAME
	fsFileExists(filename, function(exist) {

		if (!exist) {
			self.response404(req, res);
			done && done();
			return;
		}

		self.path.verify('temp');
		var image = framework_image.load(filename, im);
		fnProcess(image);
		self.responseStream(req, res, framework_utils.getContentType(image.outputType), image.stream(), null, headers, done);
	});
	return self;
};

/**
 * Responds with a stream
 * @param {Request} req
 * @param {Response} res
 * @param {String} contentType
 * @param {ReadStream} stream
 * @param {String} download Optional, download name.
 * @param {Object} headers Optional
 * @return {Framework}
 */
Framework.prototype.responseStream = function(req, res, contentType, stream, download, headers, done, nocompress) {

	var self = this;

	if (res.success || res.headersSent) {
		done && done();
		return self;
	}

	if (contentType.lastIndexOf('/') === -1)
		contentType = framework_utils.getContentType(contentType);

	var accept = req.headers['accept-encoding'] || '';

	if (!accept && isGZIP(req))
		accept = 'gzip';

	var compress = nocompress === false && self.config['allow-gzip'] && REQUEST_COMPRESS_CONTENTTYPE[contentType] && accept.indexOf('gzip') !== -1;
	var returnHeaders;

	if (RELEASE) {
		if (compress)
			returnHeaders = HEADERS['responseStream.release.compress'];
		else
			returnHeaders = HEADERS['responseStream.release'];
	} else {
		if (compress)
			returnHeaders = HEADERS['responseStream.debug.compress'];
		else
			returnHeaders = HEADERS['responseStream.debug'];
	}

	returnHeaders.Vary = 'Accept-Encoding' + (req.$mobile ? ', User-Agent' : '');

	if (RELEASE) {
		returnHeaders.Expires = DATE_EXPIRES;
		returnHeaders['Last-Modified'] = 'Mon, 01 Jan 2001 08:00:00 GMT';
	}

	if (headers) {
		returnHeaders = framework_utils.extend({}, returnHeaders, true);
		framework_utils.extend(returnHeaders, headers, true);
	}

	if (download)
		returnHeaders['Content-Disposition'] = 'attachment; filename=' + encodeURIComponent(download);
	else if (returnHeaders['Content-Disposition'])
		delete returnHeaders['Content-Disposition'];

	returnHeaders[RESPONSE_HEADER_CONTENTTYPE] = contentType;

	self.stats.response.stream++;
	self._request_stats(false, req.isStaticFile);

	if (req.method === 'HEAD') {
		res.writeHead(200, returnHeaders);
		res.end();
		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		req.clear(true);
		return self;
	}

	if (compress) {
		res.writeHead(200, returnHeaders);
		res.on('error', () => stream.close());
		stream.pipe(Zlib.createGzip()).pipe(res);
		framework_internal.onFinished(res, () => framework_internal.destroyStream(stream));
		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		req.clear(true);
		return self;
	}

	res.writeHead(200, returnHeaders);
	framework_internal.onFinished(res, (err) => framework_internal.destroyStream(stream));
	stream.pipe(res);

	done && done();
	!req.isStaticFile && self.emit('request-end', req, res);
	req.clear(true);
	return self;
};

/**
 * INTERNAL: Response range (streaming)
 * @param {String} name Temporary name.
 * @param {String} range
 * @param {Object} headers Optional, additional headers.
 * @param {Request} req
 * @param {Response} res
 * @param {Function} done Optional, callback.
 * @return {Framework}
 */
Framework.prototype.responseRange = function(name, range, headers, req, res, done) {

	var self = this;
	var arr = range.replace(/bytes=/, '').split('-');
	var beg = +arr[0] || 0;
	var end = +arr[1] || 0;
	var total = self.temporary.range[name];

	if (!total) {
		total = Fs.statSync(name).size;
		self.temporary.range[name] = total;
	}

	if (end === 0)
		end = total - 1;

	if (beg > end) {
		beg = 0;
		end = total - 1;
	}

	var length = (end - beg) + 1;

	headers[RESPONSE_HEADER_CONTENTLENGTH] = length;
	headers['Content-Range'] = 'bytes ' + beg + '-' + end + '/' + total;

	if (req.method === 'HEAD') {
		res.writeHead(206, headers);
		res.end();
		self.stats.response.streaming++;
		self._request_stats(false, req.isStaticFile);
		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		return self;
	}

	res.writeHead(206, headers);

	RANGE.start = beg;
	RANGE.end = end;

	fsStreamRead(name, RANGE, function(stream, next) {

		framework_internal.onFinished(res, function() {
			framework_internal.destroyStream(stream);
			next();
		});

		stream.pipe(res);
		self.stats.response.streaming++;
		self._request_stats(false, req.isStaticFile);
		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
	});

	return self;
};

/**
 * Responds with a binary
 * @param {Request} req
 * @param {Response} res
 * @param {String} contentType
 * @param {Buffer} buffer
 * @param {Encoding} type Default: "binary", optioanl
 * @param {String} download Optional, download name.
 * @param {Object} headers Optional
 * @return {Framework}
 */
Framework.prototype.responseBinary = function(req, res, contentType, buffer, encoding, download, headers, done) {

	var self = this;

	if (res.success || res.headersSent) {
		done && done();
		return self;
	}

	if (!encoding)
		encoding = 'binary';

	if (contentType.lastIndexOf('/') === -1)
		contentType = framework_utils.getContentType(contentType);

	var accept = req.headers['accept-encoding'] || '';

	if (!accept && isGZIP(req))
		accept = 'gzip';

	var compress = self.config['allow-gzip'] && REQUEST_COMPRESS_CONTENTTYPE[contentType] && accept.indexOf('gzip') !== -1;
	var returnHeaders = compress ? HEADERS['responseBinary.compress'] : HEADERS['responseBinary'];

	returnHeaders['Vary'] = 'Accept-Encoding' + (req.$mobile ? ', User-Agent' : '');

	if (headers) {
		returnHeaders = framework_utils.extend({}, returnHeaders, true);
		framework_utils.extend(returnHeaders, headers, true);
	}

	if (download)
		returnHeaders['Content-Disposition'] = 'attachment; filename=' + encodeURIComponent(download);
	else if (returnHeaders['Content-Disposition'])
		delete returnHeaders['Content-Disposition'];

	returnHeaders[RESPONSE_HEADER_CONTENTTYPE] = contentType;

	self.stats.response.binary++;
	self._request_stats(false, req.isStaticFile);

	if (req.method === 'HEAD') {
		res.writeHead(200, returnHeaders);
		res.end();
		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		req.clear(true);
		return self;
	}

	if (compress) {
		res.writeHead(200, returnHeaders);
		Zlib.gzip(encoding === 'binary' ? buffer : buffer.toString(encoding), (err, buffer) => res.end(buffer));
		done && done();
		!req.isStaticFile && self.emit('request-end', req, res);
		return self;
	}

	res.writeHead(200, returnHeaders);
	res.end(encoding === 'binary' ? buffer : buffer.toString(encoding));

	done && done();
	!req.isStaticFile && self.emit('request-end', req, res);
	req.clear(true);
	return self;
};

/*
	Sets the last modified header or Etag
	@req {Request}
	@res {Response}
	@value {String or Date}

	if @value === {String} set ETag
	if @value === {Date} set LastModified

	return {Controller};
*/
Framework.prototype.setModified = function(req, res, value) {
	if (typeof(value) === 'string')
		res.setHeader('Etag', value + ':' + this.config['etag-version']);
	else
		res.setHeader('Last-Modified', value.toUTCString());
	return this;
};

/*
	Checks if ETag or Last Modified has modified
	@req {Request}
	@res {Response}
	@compare {String or Date}
	@strict {Boolean} :: if strict then use equal date else use great than date (default: false)

	if @compare === {String} compare if-none-match
	if @compare === {Date} compare if-modified-since

	this method automatically flushes response (if it's not modified)
	--> response 304

	return {Boolean};
*/
Framework.prototype.notModified = function(req, res, compare, strict) {

	var self = this;
	var type = typeof(compare);

	if (type === 'boolean') {
		var tmp = compare;
		compare = strict;
		strict = tmp;
		type = typeof(compare);
	}

	var isEtag = type === 'string';
	var val = req.headers[isEtag ? 'if-none-match' : 'if-modified-since'];

	if (isEtag) {

		if (!val)
			return false;

		var myetag = compare + ':' + self.config['etag-version'];
		if (val !== myetag)
			return false;

	} else {

		if (!val)
			return false;

		var date = compare === undefined ? new Date().toUTCString() : compare.toUTCString();
		if (strict) {
			if (new Date(Date.parse(val)) === new Date(date))
				return false;
		} else {
			if (new Date(Date.parse(val)) < new Date(date))
				return false;
		}
	}

	res.success = true;
	res.writeHead(304);
	res.end();

	self.stats.response.notModified++;
	self._request_stats(false, req.isStaticFile);
	!req.isStaticFile && self.emit('request-end', req, res);
	return true;
};

Framework.prototype.responseCode = function(req, res, code, problem) {
	var self = this;

	problem && self.problem(problem, 'response' + code + '()', req.uri, req.ip);

	if (res.success || res.headersSent)
		return self;

	self._request_stats(false, req.isStaticFile);

	res.success = true;
	res.writeHead(code, HEADERS['responseCode']);

	if (req.method === 'HEAD')
		res.end();
	else
		res.end(framework_utils.httpStatus(code));

	!req.isStaticFile && self.emit('request-end', req, res);
	req.clear(true);
	var key = 'error' + code;
	self.emit(key, req, res, problem);
	self.stats.response[key]++;
	return self;
};

Framework.prototype.response400 = function(req, res, problem) {
	return this.responseCode(req, res, 400, problem);
};

Framework.prototype.response401 = function(req, res, problem) {
	return this.responseCode(req, res, 401, problem);
};

Framework.prototype.response403 = function(req, res, problem) {
	return this.responseCode(req, res, 403, problem);
};

Framework.prototype.response404 = function(req, res, problem) {
	return this.responseCode(req, res, 404, problem);
};

Framework.prototype.response408 = function(req, res, problem) {
	return this.responseCode(req, res, 408, problem);
};

Framework.prototype.response431 = function(req, res, problem) {
	return this.responseCode(req, res, 431, problem);
};

Framework.prototype.response500 = function(req, res, error) {
	var self = this;
	error && self.error(error, null, req.uri);

	if (res.success || res.headersSent)
		return self;

	self._request_stats(false, req.isStaticFile);

	res.success = true;
	res.writeHead(500, HEADERS['responseCode']);

	if (req.method === 'HEAD')
		res.end();
	else
		res.end(framework_utils.httpStatus(500) + prepare_error(error));

	!req.isStaticFile && self.emit('request-end', req, res);
	req.clear(true);
	self.stats.response.error500++;
	return self;
};

Framework.prototype.response501 = function(req, res, problem) {
	return this.responseCode(req, res, 501, problem);
};

Framework.prototype.response503 = function(req, res) {
	var self = this;
	var keys = '';
	var headers = {};
	headers[RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
	headers[RESPONSE_HEADER_CONTENTTYPE] = CONTENTTYPE_TEXTHTML;
	res.writeHead(503, headers);
	for (var m in self.waits)
		keys += (keys ? ', ' : '') + '<u>' + m + '</u>';
	res.end('<html><head><meta charset="utf-8" /></head><body style="font:normal normal 11px Arial;color:gray;line-height:16px;padding:10px;background-color:white"><div style="font-size:14px;color:#505050">Please wait (<span id="time">10</span>) for <b>' + (self.config.name + ' v' + self.config.version) + '</b> application.</div>The application is waiting for: ' + keys + '.<script>var i=10;setInterval(function(){i--;if(i<0)return;document.getElementById("time").innerHTML=(i===0?"refreshing":i);if(i===0)window.location.reload();},1000);</script></body></html>', ENCODING);
	return self;
};

/**
 * Response content
 * @param {Request} req
 * @param {Response} res
 * @param {Number} code Status code.
 * @param {String} contentBody Content body.
 * @param {String} contentType Content type.
 * @param {Boolean} compress GZIP compression.
 * @param {Object} headers Custom headers.
 * @return {Framework}
 */
Framework.prototype.responseContent = function(req, res, code, contentBody, contentType, compress, headers) {
	var self = this;

	if (res.success || res.headersSent)
		return self;

	res.success = true;

	var accept = req.headers['accept-encoding'] || '';

	if (!accept && isGZIP(req))
		accept = 'gzip';

	var gzip = compress ? accept.indexOf('gzip') !== -1 : false;
	var returnHeaders;

	if (req.$mobile) {
		if (gzip)
			returnHeaders = HEADERS['responseContent.mobile.compress'];
		else
			returnHeaders = HEADERS['responseContent.mobile'];
	} else {
		if (gzip)
			returnHeaders = HEADERS['responseContent.compress'];
		else
			returnHeaders = HEADERS['responseContent'];
	}

	if (headers) {
		returnHeaders = framework_utils.extend({}, returnHeaders, true);
		framework_utils.extend(returnHeaders, headers, true);
	}

	// Safari resolve
	if (contentType === 'application/json')
		returnHeaders[RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';
	else
		returnHeaders[RESPONSE_HEADER_CACHECONTROL] = 'private';

	if (REG_TEXTAPPLICATION.test(contentType))
		contentType += '; charset=utf-8';

	returnHeaders[RESPONSE_HEADER_CONTENTTYPE] = contentType;

	if (req.method === 'HEAD') {
		res.writeHead(code, returnHeaders);
		res.end();
	} else {
		if (gzip) {
			res.writeHead(code, returnHeaders);
			Zlib.gzip(new Buffer(contentBody || ''), (err, data) => res.end(data, ENCODING));
		} else {
			res.writeHead(code, returnHeaders);
			res.end(contentBody || '', ENCODING);
		}
	}

	self._request_stats(false, req.isStaticFile);
	!req.isStaticFile && self.emit('request-end', req, res);
	req.clear(true);
	return self;
};

/**
 * Response Redirect
 * @param {Request} req
 * @param {Response} res
 * @param {String} url
 * @param {Boolean} permanent Optional.
 * @return {Framework}
 */
Framework.prototype.responseRedirect = function(req, res, url, permanent) {

	var self = this;

	if (res.success || res.headersSent)
		return;

	self._request_stats(false, req.isStaticFile);
	res.success = true;
	var headers = HEADERS.responseRedirect;
	headers.Location = url;
	res.writeHead(permanent ? 301 : 302, headers);
	res.end();
	!req.isStaticFile && self.emit('request-end', req, res);
	req.clear(true);
	return self;
};

Framework.prototype.load = function(debug, types, pwd) {

	var self = this;
	if (pwd && pwd[0] === '.' && pwd.length < 4)
		self.directory = directory = framework_utils.$normalize(Path.normalize(directory + '/..'));
	else if (pwd)
		self.directory = directory = framework_utils.$normalize(pwd);

	self.isWorker = true;
	self.config.debug = debug;
	self.isDebug = debug;

	global.DEBUG = debug;
	global.RELEASE = !debug;
	global.I = global.isomorphic = self.isomorphic;

	self.$startup(function() {

		self._configure();

		if (!types || types.indexOf('versions') !== -1)
			self._configure_versions();

		if (!types || types.indexOf('workflows') !== -1)
			self._configure_workflows();

		if (!types || types.indexOf('sitemap') !== -1)
			self._configure_sitemap();

		self.cache.init();
		self.emit('init');
		self.isLoaded = true;

		setTimeout(function() {

			try {
				self.emit('load', self);
				self.emit('ready', self);
			} catch (err) {
				self.error(err, 'framework.on("load/ready")');
			}

			self.removeAllListeners('load');
			self.removeAllListeners('ready');

			// clear unnecessary items
			delete framework.tests;
			delete framework.test;
			delete framework.testing;
			delete framework.assert;
		}, 500);

		self.$load(types, directory);
	});

	return self;
};

/**
 * Initialize framework
 * @param  {Object} http
 * @param  {Boolean} debug
 * @param  {Object} options
 * @return {Framework}
 */
Framework.prototype.initialize = function(http, debug, options, restart) {

	var self = this;

	if (!options)
		options = {};

	var port = options.port;
	var ip = options.ip;

	if (options.config)
		framework_utils.copy(options.config, self.config);

	self.isHTTPS = typeof(http.STATUS_CODES) === 'undefined';
	if (isNaN(port) && typeof(port) !== 'string')
		port = null;

	self.config.debug = debug;
	self.isDebug = debug;

	global.DEBUG = debug;
	global.RELEASE = !debug;
	global.isomorphic = self.isomorphic;

	self._configure();
	self._configure_versions();
	self._configure_workflows();
	self._configure_sitemap();
	self.isTest && self._configure('config-test', false);
	self.cache.init();
	self.emit('init');

	// clears static files
	self.clear(function() {

		self.$load(undefined, directory);

		if (!port) {
			if (self.config['default-port'] === 'auto') {
				var envPort = +(process.env.PORT || '');
				if (!isNaN(envPort))
					port = envPort;
			} else
				port = self.config['default-port'];
		}

		self.port = port || 8000;

		if (ip !== null) {
			self.ip = ip || self.config['default-ip'] || '127.0.0.1';
			if (self.ip === 'null' || self.ip === 'undefined' || self.ip === 'auto')
				self.ip = undefined;
		} else
			self.ip = undefined;

		if (self.ip == null)
			self.ip = 'auto';

		if (self.server) {
			self.server.removeAllListeners();

			Object.keys(self.connections).forEach(function(key) {
				var item = self.connections[key];
				if (!item)
					return;
				item.removeAllListeners();
				item.close();
			});

			self.server.close();
		}

		if (options.https)
			self.server = http.createServer(options.https, self.listener);
		else
			self.server = http.createServer(self.listener);

		self.config['allow-performance'] && self.server.on('connection', function(socket) {
			socket.setNoDelay(true);
			socket.setKeepAlive(true, 10);
		});

		self.config['allow-websocket'] && self.server.on('upgrade', framework._upgrade);
		self.server.listen(self.port, self.ip === 'auto' ? undefined : self.ip);
		self.isLoaded = true;

		if (!process.connected || restart)
			self.console();

		setTimeout(function() {
			try {
				self.emit('load', self);
				self.emit('ready', self);
			} catch (err) {
				self.error(err, 'framework.on("load/ready")');
			}

			self.removeAllListeners('load');
			self.removeAllListeners('ready');
			options.package && INSTALL('package', options.package);
		}, 500);

		if (self.isTest) {
			var sleep = options.sleep || options.delay || 1000;
			global.TEST = true;
			global.assert = require('assert');
			setTimeout(() => self.test(true, options.tests || options.test), sleep);
			return self;
		}

		setTimeout(function() {
			if (framework.isTest)
				return;
			delete framework.tests;
			delete framework.test;
			delete framework.testing;
			delete framework.assert;
		}, 5000);
	}, true);

	return self;
};

/**
 * Run framework –> HTTP
 * @param  {String} mode Framework mode.
 * @param  {Object} options Framework settings.
 * @return {Framework}
 */
Framework.prototype.http = function(mode, options) {

	if (options === undefined)
		options = {};

	if (!options.port)
		options.port = +process.argv[2];

	return this.mode(require('http'), mode, options);
};

/**
 * Run framework –> HTTPS
 * @param {String} mode Framework mode.
 * @param {Object} options Framework settings.
 * @return {Framework}
 */
Framework.prototype.https = function(mode, options) {
	return this.mode(require('https'), mode, options || {});
};

/**
 * Changes the framework mode
 * @param {String} mode New mode (e.g. debug or release)
 * @return {Framework}
 */
Framework.prototype.mode = function(http, name, options) {

	var self = this;
	var test = false;
	var debug = false;

	if (options.directory)
		self.directory = directory = options.directory;

	if (typeof(http) === 'string') {
		switch (http) {
			case 'debug':
			case 'development':
				debug = true;
				break;
		}
		self.config.debug = debug;
		self.config.trace = debug;
		self.isDebug = debug;
		global.DEBUG = debug;
		global.RELEASE = !debug;
		return self;
	}

	self.isWorker = false;

	switch (name.toLowerCase().replace(/\.|\s/g, '-')) {
		case 'release':
		case 'production':
			break;

		case 'debug':
		case 'develop':
		case 'development':
			debug = true;
			break;

		case 'test':
		case 'testing':
		case 'test-debug':
		case 'debug-test':
		case 'testing-debug':
			test = true;
			debug = true;
			self.isTest = true;
			break;

		case 'test-release':
		case 'release-test':
		case 'testing-release':
		case 'test-production':
		case 'testing-production':
			test = true;
			debug = false;
			break;
	}

	var restart = false;
	if (self.temporary.init)
		restart = true;
	else
		self.temporary.init = { name: name, isHTTPS: typeof(http.STATUS_CODES) === 'undefined', options: options };

	self.config.trace = debug;
	self.$startup(n => self.initialize(http, debug, options, restart));
	return self;
};

Framework.prototype.console = function() {
	console.log('====================================================');
	console.log('PID         : ' + process.pid);
	console.log('Node.js     : ' + process.version);
	console.log('Total.js    : v' + F.version_header);
	console.log('OS          : ' + Os.platform() + ' ' + Os.release());
	console.log('====================================================');
	console.log('Name        : ' + F.config.name);
	console.log('Version     : ' + F.config.version);
	console.log('Author      : ' + F.config.author);
	console.log('Date        : ' + new Date().format('yyyy-MM-dd HH:mm:ss'));
	console.log('Mode        : ' + (F.config.debug ? 'debug' : 'release'));
	console.log('====================================================\n');
	console.log('{2}://{0}:{1}/'.format(F.ip, F.port, F.isHTTPS ? 'https' : 'http'));
	console.log('');
};

/**
 * Re-connect server
 * @return {Framework}
 */
Framework.prototype.reconnect = function() {
	var self = this;

	if (self.config['default-port'] !== undefined)
		self.port = self.config['default-port'];

	if (self.config['default-ip'] !== undefined)
		self.ip = self.config['default-ip'];

	self.server.close(() => self.server.listen(self.port));
	return self;
};

/**
 * Internal service
 * @private
 * @param {Number} count Run count.
 * @return {Framework}
 */
Framework.prototype._service = function(count) {

	var self = this;

	UIDGENERATOR.date = self.datetime.format('yyMMddHHmm');
	UIDGENERATOR.index = 1;

	if (self.config.debug)
		self.resources = {};

	// every 7 minutes (default) service clears static cache
	if (count % self.config['default-interval-clear-cache'] === 0) {

		self.emit('clear', 'temporary', self.temporary);
		self.temporary.path = {};
		self.temporary.range = {};
		self.temporary.views = {};
		self.temporary.other = {};

		if (global.$VIEWCACHE && global.$VIEWCACHE.length)
			global.$VIEWCACHE = [];

		// Clears command cache
		Image.clear();

		var dt = F.datetime.add('-5 minutes');
		for (var key in self.databases)
			self.databases[key] && self.databases[key].inmemorylastusage < dt && self.databases[key].release();
	}

	// every 61 minutes (default) services precompile all (installed) views
	if (count % self.config['default-interval-precompile-views'] === 0) {
		for (var key in self.routes.views) {
			var item = self.routes.views[key];
			self.install('view', key, item.url, null);
		}
	}

	if (count % self.config['default-interval-clear-dnscache'] === 0) {
		self.emit('clear', 'dns');
		framework_utils.clearDNS();
	}

	var ping = self.config['default-interval-websocket-ping'];
	if (ping > 0 && count % ping === 0) {
		for (var item in self.connections) {
			var conn = self.connections[item];
			if (conn) {
				conn.check();
				conn.ping();
			}
		}
	}

	// every 20 minutes (default) service clears resources
	if (count % self.config['default-interval-clear-resources'] === 0) {
		self.emit('clear', 'resources');
		self.resources = {};
		global.gc && setTimeout(global.gc, 1000);
	}

	// Update expires date
	if (count % 1000 === 0)
		DATE_EXPIRES = self.datetime.add('y', 1).toUTCString();

	self.emit('service', count);

	var length = self.schedules.length;

	// Run schedules
	if (!length)
		return self;

	var expire = self.datetime.getTime();
	var index = 0;

	while (true) {
		var schedule = self.schedules[index++];
		if (!schedule)
			break;
		if (schedule.expire > expire)
			continue;

		index--;

		if (schedule.repeat)
			schedule.expire = self.datetime.add(schedule.repeat);
		else
			self.schedules.splice(index, 1);

		schedule.fn.call(self);
	}

	return self;
};

/**
 * Request processing
 * @private
 * @param {Request} req
 * @param {Response} res
 */
Framework.prototype.listener = function(req, res) {

	if (!req.host) {
		res.writeHead(403);
		res.end();
		return;
	}

	var self = framework;

	if (self._length_wait)
		return self.response503(req, res);

	var headers = req.headers;
	var protocol = req.connection.encrypted || headers['x-forwarded-protocol'] === 'https' ? 'https' : 'http';

	res.req = req;
	req.res = res;
	req.uri = framework_internal.parseURI(protocol, req);

	self.stats.request.request++;
	self.emit('request', req, res);

	if (self._request_check_redirect) {
		var redirect = self.routes.redirects[protocol + '://' + req.host];
		if (redirect) {
			self.stats.response.forward++;
			self.responseRedirect(req, res, redirect.url + (redirect.path ? req.url : ''), redirect.permanent);
			return;
		}
	}

	if (self.restrictions.is && self._request_restriction(req, res, headers))
		return;

	req.path = framework_internal.routeSplit(req.uri.pathname);
	req.processing = 0;
	req.isAuthorized = true;
	req.xhr = headers['x-requested-with'] === 'XMLHttpRequest';
	res.success = false;
	req.session = null;
	req.user = null;
	req.isStaticFile = framework.config['allow-handle-static-files'] && framework_utils.isStaticFile(req.uri.pathname);

	var can = true;

	if (req.isStaticFile) {
		req.extension = framework_utils.getExtension(req.uri.pathname);
		switch (req.extension) {
			case 'html':
			case 'htm':
			case 'txt':
			case 'md':
				can = true;
				break;
			default:
				can = false;
				break;
		}
	}

	if (can && self.onLocale)
		req.$language = self.onLocale(req, res, req.isStaticFile);

	self._request_stats(true, true);

	if (self._length_request_middleware && !req.behaviour('disable-middleware'))
		async_middleware(0, req, res, self.routes.request, () => self._request_continue(res.req, res, res.req.headers, protocol));
	else
		self._request_continue(req, res, headers, protocol);
};

Framework.prototype._request_restriction = function(req, res, headers) {

	var self = this;

	if (self.restrictions.isAllowedIP) {
		for (var i = 0, length = self.restrictions.allowedIP.length; i < length; i++) {
			var ip = self.restrictions.allowedIP[i];
			if (req.ip.indexOf(ip) !== -1)
				continue;
			self.stats.response.restriction++;
			res.writeHead(403);
			res.end();
			return true;
		}
	}

	if (self.restrictions.isBlockedIP) {
		for (var i = 0, length = self.restrictions.blockedIP.length; i < length; i++) {
			var ip = self.restrictions.blockedIP[i];
			if (req.ip.indexOf(ip) === -1)
				continue;
			self.stats.response.restriction++;
			res.writeHead(403);
			res.end();
			return true;
		}
	}

	if (self.restrictions.isAllowedCustom) {
		if (!self.restrictions._allowedCustom(headers)) {
			self.stats.response.restriction++;
			res.writeHead(403);
			res.end();
			return true;
		}
	}

	if (self.restrictions.isBlockedCustom) {
		if (self.restrictions._blockedCustom(headers)) {
			self.stats.response.restriction++;
			res.writeHead(403);
			res.end();
			return true;
		}
	}

	return false;
};

/**
 * Continue to process
 * @private
 * @param {Request} req
 * @param {Response} res
 * @param {Object} headers
 * @param {String} protocol [description]
 * @return {Framework}
 */
Framework.prototype._request_continue = function(req, res, headers, protocol) {

	if (!req || !res || res.headersSent || res.success)
		return;

	var self = this;

	// Validates if this request is the file (static file)
	if (req.isStaticFile) {
		self.stats.request.file++;
		if (!self._length_files)
			return self.responseStatic(req, res);
		new Subscribe(self, req, res, 3).file();
		return self;
	}

	req.body = EMPTYOBJECT;
	req.files = EMPTYARRAY;
	req.isProxy = headers['x-proxy'] === 'total.js';
	req.buffer_exceeded = false;
	req.buffer_has = false;
	req.$flags = req.method[0] + req.method[1];
	self.stats.request.web++;

	var flags = [req.method.toLowerCase()];
	var multipart = req.headers['content-type'] || '';

	if (req.mobile) {
		req.$flags += 'a';
		self.stats.request.mobile++;
	} else
		self.stats.request.desktop++;

	if (protocol[5])
		req.$flags += protocol[5];

	req.$type = 0;
	flags.push(protocol);

	var method = req.method;
	var first = method[0];
	if (first === 'P' || first === 'D') {
		req.buffer_data = new Buffer('');
		var index = multipart.lastIndexOf(';');
		var tmp = multipart;
		if (index !== -1)
			tmp = tmp.substring(0, index);
		switch (tmp.substring(tmp.length - 4)) {
			case 'json':
				req.$flags += 'b';
				flags.push('json');
				req.$type = 1;
				multipart = '';
				break;
			case 'oded':
				req.$type = 3;
				multipart = '';
				break;
			case 'data':
				req.$flags += 'c';
				flags.push('upload');
				break;
			case '/xml':
				req.$flags += 'd';
				flags.push('xml');
				req.$type = 2;
				multipart = '';
				break;
			case 'lace':
				req.$type = 4;
				flags.push('mmr');
				req.$flags += 'e';
				break;
			default:
				if (multipart) {
					// 'undefined' DATA
					multipart = '';
					flags.push('raw');
				} else {
					req.$type = 3;
					multipart = '';
				}
				break;
		}
	}

	if (req.isProxy) {
		req.$flags += 'f';
		flags.push('proxy');
	}

	if (headers.accept === 'text/event-stream') {
		req.$flags += 'g';
		flags.push('sse');
	}

	if (self.config.debug) {
		req.$flags += 'h';
		flags.push('debug');
	}

	if (req.xhr) {
		self.stats.request.xhr++;
		req.$flags += 'i';
		flags.push('xhr');
	}

	if (self._request_check_robot && req.robot)
		req.$flags += 'j';

	if (self._request_check_referer) {
		var referer = headers['referer'];
		if (referer && referer.indexOf(headers['host']) !== -1) {
			req.$flags += 'k';
			flags.push('referer');
		}
	}

	req.flags = flags;
	self.emit('request-begin', req, res);

	var isCORS = req.headers['origin'] && self._length_cors;

	switch (first) {
		case 'G':
			self.stats.request.get++;
			if (isCORS)
				self._cors(req, res, (req, res) => new Subscribe(framework, req, res, 0).end());
			else
				new Subscribe(self, req, res, 0).end();
			return self;

		case 'O':
			self.stats.request.options++;
			if (isCORS)
				self._cors(req, res, (req, res) => new Subscribe(framework, req, res, 0).end());
			else
				new Subscribe(framework, req, res, 0).end();
			return self;

		case 'H':
			self.stats.request.head++;
			if (isCORS)
				self._cors(req, res, (req, res) => new Subscribe(framework, req, res, 0).end());
			else
				new Subscribe(self, req, res, 0).end();
			return self;

		case 'D':
			self.stats.request['delete']++;
			if (isCORS)
				self._cors(req, res, (req, res) => new Subscribe(framework, req, res, 1).urlencoded());
			else
				new Subscribe(self, req, res, 1).urlencoded();
			return self;

		case 'P':
			if (self._request_check_POST) {

				if (multipart) {

					if (!isCORS) {

						if (req.$type === 4)
							self._request_mmr(req, res, multipart);
						else
							new Subscribe(self, req, res, 2).multipart(multipart);

						return self;
					}

					self._cors(req, res, (req, res, multipart) => req.$type === 4 ? self._request_mmr(req, res, multipart) : new Subscribe(self, req, res, 2).multipart(multipart), multipart);
					return self;

				} else {
					if (method === 'PUT')
						self.stats.request.put++;
					else if (method === 'PATCH')
						self.stats.request.path++;
					else
						self.stats.request.post++;
					if (isCORS)
						self._cors(req, res, (req, res) => new Subscribe(self, req, res, 1).urlencoded());
					else
						new Subscribe(self, req, res, 1).urlencoded();
				}

				return self;
			}
			break;
	}

	self.emit('request-end', req, res);
	self._request_stats(false, false);
	self.stats.request.blocked++;
	res.writeHead(403);
	res.end();
	return self;
};

Framework.prototype._request_mmr = function(req, res, header) {
	var self = this;
	var route = self.routes.mmr[req.url];
	self.stats.request.mmr++;

	if (route) {
		self.path.verify('temp');
		framework_internal.parseMULTIPART_MIXED(req, header, self.config['directory-temp'], route.exec);
		return;
	}

	self.emit('request-end', req, res);
	self._request_stats(false, false);
	self.stats.request.blocked++;
	res.writeHead(403);
	res.end();
};

Framework.prototype._cors = function(req, res, fn, arg) {

	var self = this;
	var isAllowed = false;
	var cors;

	for (var i = 0; i < self._length_cors; i++) {
		cors = self.routes.cors[i];
		if (framework_internal.routeCompare(req.path, cors.url, false, cors.isASTERIX)) {
			isAllowed = true;
			break;
		}
	}

	if (!isAllowed)
		return fn(req, res, arg);

	var stop = false;
	var headers = req.headers;

	if (!isAllowed)
		stop = true;

	isAllowed = false;

	if (!stop && cors.headers) {
		isAllowed = false;
		for (var i = 0, length = cors.headers.length; i < length; i++) {
			if (headers[cors.headers[i]]) {
				isAllowed = true;
				break;
			}
		}
		if (!isAllowed)
			stop = true;
	}

	if (!stop && cors.methods) {
		isAllowed = false;
		var current = headers['access-control-request-method'] || req.method;
		if (current !== 'OPTIONS') {
			for (var i = 0, length = cors.methods.length; i < length; i++) {
				if (current.indexOf(cors.methods[i]) !== -1)
					isAllowed = true;
			}

			if (!isAllowed)
				stop = true;
		}
	}

	var origin = headers['origin'].toLowerCase();
	if (!stop && cors.origins) {
		isAllowed = false;
		for (var i = 0, length = cors.origins.length; i < length; i++) {
			if (cors.origins[i].indexOf(origin) !== -1) {
				isAllowed = true;
				break;
			}
		}
		if (!isAllowed)
			stop = true;
	}

	var tmp;
	var name
	var isOPTIONS = req.method === 'OPTIONS';

	res.setHeader('Access-Control-Allow-Origin', cors.origins ? cors.origins : cors.credentials ? isAllowed ? origin : cors.origins ? cors.origins : origin : headers['origin']);
	cors.credentials && res.setHeader('Access-Control-Allow-Credentials', 'true');

	name = 'Access-Control-Allow-Methods';

	if (cors.methods)
		res.setHeader(name, cors.methods.join(', '));
	else
		res.setHeader(name, isOPTIONS ? headers['access-control-request-method'] || '*' : req.method);

	name = 'Access-Control-Allow-Headers';

	if (cors.headers)
		res.setHeader(name, cors.headers.join(', '));
	else
		res.setHeader(name, headers['access-control-request-headers'] || '*');

	cors.age && res.setHeader('Access-Control-Max-Age', cors.age);

	if (stop) {
		fn = null;
		self.emit('request-end', req, res);
		self._request_stats(false, false);
		self.stats.request.blocked++;
		res.writeHead(404);
		res.end();
		return;
	}

	if (!isOPTIONS)
		return fn(req, res, arg);

	fn = null;
	self.emit('request-end', req, res);
	self._request_stats(false, false);
	res.writeHead(200);
	res.end();
	return self;
};

/**
 * Upgrade HTTP (WebSocket)
 * @param {HttpRequest} req
 * @param {Socket} socket
 * @param {Buffer} head
 */
Framework.prototype._upgrade = function(req, socket, head) {

	if ((req.headers.upgrade || '').toLowerCase() !== 'websocket')
		return;

	// disables timeout
	socket.setTimeout(0);
	socket.on('error', NOOP);

	var self = framework;
	var headers = req.headers;
	var protocol = req.connection.encrypted || headers['x-forwarded-protocol'] === 'https' ? 'https' : 'http';

	req.uri = framework_internal.parseURI(protocol, req);

	self.emit('websocket', req, socket, head);
	self.stats.request.websocket++;

	if (self.restrictions.is && self._request_restriction(req, res, headers))
		return;

	req.session = null;
	req.user = null;
	req.flags = [req.secured ? 'https' : 'http', 'get'];

	var path = framework_utils.path(req.uri.pathname);
	var websocket = new WebSocketClient(req, socket, head);

	req.path = framework_internal.routeSplit(req.uri.pathname);
	req.websocket = websocket;

	if (self.onLocale)
		req.$language = self.onLocale(req, socket);

	if (self._length_request_middleware && !req.behaviour('disable-middleware'))
		async_middleware(0, req, req.websocket, self.routes.request, () => self._upgrade_prepare(req, path, req.headers));
	else
		self._upgrade_prepare(req, path, headers);
};

/**
 * Prepare WebSocket
 * @private
 * @param {HttpRequest} req
 * @param {WebSocketClient} websocket
 * @param {String} path
 * @param {Object} headers
 */
Framework.prototype._upgrade_prepare = function(req, path, headers) {

	var self = this;
	var auth = self.onAuthorize;

	if (!auth) {
		var route = self.lookup_websocket(req, req.websocket.uri.pathname, true);
		if (route) {
			self._upgrade_continue(route, req, path);
		} else {
			req.websocket.close();
			req.connection.destroy();
		}
		return;
	}

	auth.call(self, req, req.websocket, req.flags, function(isLogged, user) {

		if (user)
			req.user = user;

		req.flags.push(isLogged ? 'authorize' : 'unauthorize');

		var route = self.lookup_websocket(req, req.websocket.uri.pathname, false);
		if (route) {
			self._upgrade_continue(route, req, path);
		} else {
			req.websocket.close();
			req.connection.destroy();
		}
	});
};

/**
 * Prepare WebSocket
 * @private
 * @param {HttpRequest} req
 * @param {WebSocketClient} websocket
 * @param {String} path
 * @param {Object} headers
 */
Framework.prototype._upgrade_continue = function(route, req, path) {

	var self = this;
	var socket = req.websocket;

	if (!socket.prepare(route.flags, route.protocols, route.allow, route.length, self.version_header)) {
		socket.close();
		req.connection.destroy();
		return;
	}

	var id = path + (route.flags.length ? '#' + route.flags.join('-') : '');

	if (route.isBINARY)
		socket.type = 1;
	else if (route.isJSON)
		socket.type = 3;

	var next = function() {

		if (self.connections[id]) {
			socket.upgrade(self.connections[id]);
			return;
		}

		var connection = new WebSocket(path, route.controller, id);
		connection.route = route;
		connection.options = route.options;
		self.connections[id] = connection;
		route.onInitialize.apply(connection, framework_internal.routeParam(route.param.length ? req.split : req.path, route));
		setImmediate(() => socket.upgrade(connection));
	};

	if (route.middleware)
		async_middleware(0, req, req.websocket, route.middleware, next, route.options);
	else
		next();
};

/**
 * Request statistics writer
 * @private
 * @param {Boolean} beg
 * @param {Boolean} isStaticFile
 * @return {Framework}
 */
Framework.prototype._request_stats = function(beg, isStaticFile) {

	var self = this;

	if (beg)
		self.stats.request.pending++;
	else
		self.stats.request.pending--;

	if (self.stats.request.pending < 0)
		self.stats.request.pending = 0;

	return self;
};

/**
 * Get a model
 * @param {String} name
 * @return {Object}
 */
Framework.prototype.model = function(name) {
	var self = this;
	var model = self.models[name];
	if (model || model === null)
		return model;
	var filename = framework_utils.combine(self.config['directory-models'], name + '.js');
	existsSync(filename) && self.install('model', name, filename, undefined, undefined, undefined, true);
	return self.models[name] || null;
};

/**
 * Load a source code
 * @param {String} name
 * @param {Object} options Custom initial options, optional.
 * @return {Object}
 */
Framework.prototype.source = function(name, options, callback) {

	var self = this;
	var model = self.sources[name];

	if (model || model === null)
		return model;

	var filename = framework_utils.combine(self.config['directory-source'], name + '.js');
	existsSync(filename) && self.install('source', name, filename, options, callback, undefined, true);
	return self.sources[name] || null;
};

/**
 * Load a source code (alias for framework.source())
 * @param {String} name
 * @param {Object} options Custom initial options, optional.
 * @return {Object}
 */
Framework.prototype.include = function(name, options, callback) {
	return this.source(name, options, callback);
};

/**
 * Internal logger
 * @private
 * @param {String} message
 * @return {Framework}
 */
Framework.prototype._log = function(a, b, c, d) {
	var self = this;

	if (!self.isDebug)
		return false;

	var length = arguments.length;
	var params = ['---->'];

	for (var i = 0; i < length; i++)
		params.push(arguments[i]);

	setTimeout(() => console.log.apply(console, params), 1000);
};

/**
 * Send e-mail
 * @param {String or Array} address E-mail address.
 * @param {String} subject E-mail subject.
 * @param {String} view View name.
 * @param {Object} model Optional.
 * @param {Function(err)} callback Optional.
 * @param {String} language Optional.
 * @return {MailMessage}
 */
Framework.prototype.mail = function(address, subject, view, model, callback, language) {

	if (typeof(callback) === 'string') {
		var tmp = language;
		language = callback;
		callback = tmp;
	}

	var controller = new Controller('', null, null, null, '');
	controller.layoutName = '';
	controller.themeName = framework_utils.parseTheme(view);

	if (controller.themeName)
		view = prepare_viewname(view);
	else if (this.onTheme)
		controller.themeName = this.onTheme(controller);

	var replyTo;

	// Translation
	if (typeof(language) === 'string') {
		subject = subject.indexOf('@(') === -1 ? framework.translate(language, subject) : framework.translator(language, subject);
		controller.language = language;
	}

	if (typeof(repository) === 'object' && repository)
		controller.repository = repository;

	return controller.mail(address, subject, view, model, callback, replyTo);
};

/**
 * Renders view
 * @param {String} name View name.
 * @param {Object} model Model.
 * @param {String} layout Layout for the view, optional. Default without layout.
 * @param {Object} repository A repository object, optional. Default empty.
 * @param {String} language Optional.
 * @return {String}
 */
Framework.prototype.view = function(name, model, layout, repository, language) {

	var controller = EMPTYCONTROLLER;

	if (typeof(layout) === 'object') {
		var tmp = repository;
		repository = layout;
		layout = tmp;
	}

	controller.layoutName = layout || '';
	controller.language = language || '';
	controller.repository = typeof(repository) === 'object' && repository ? repository : EMPTYOBJECT;

	var theme = framework_utils.parseTheme(name);
	if (theme) {
		controller.themeName = theme;
		name = prepare_viewname(name);
	} else if (this.onTheme)
		controller.themeName = this.onTheme(controller);

	return controller.view(name, model, true);
};

/**
 * Compiles and renders view
 * @param {String} body HTML body.
 * @param {Object} model Model.
 * @param {String} layout Layout for the view, optional. Default without layout.
 * @param {Object} repository A repository object, optional. Default empty.
 * @param {String} language Optional.
 * @return {String}
 */
Framework.prototype.viewCompile = function(body, model, layout, repository, language) {

	var controller = EMPTYCONTROLLER;

	if (typeof(layout) === 'object') {
		var tmp = repository;
		repository = layout;
		layout = tmp;
	}

	controller.layoutName = layout || '';
	controller.language = language || '';
	controller.themeName = '';
	controller.repository = typeof(repository) === 'object' && repository ? repository : EMPTYOBJECT;

	return controller.viewCompile(body, model, true);
};

/**
 * Add a test function or test request
 * @param {String} name Test name.
 * @param {Url or Function} url Url or Callback function(next, name) {}.
 * @param {Array} flags Routed flags (GET, POST, PUT, XHR, JSON ...).
 * @param {Function} callback Callback.
 * @param {Object or String} data Request data.
 * @param {Object} cookies Request cookies.
 * @param {Object} headers Additional headers.
 * @return {Framework}
 */
Framework.prototype.assert = function(name, url, flags, callback, data, cookies, headers) {

	var self = this;

	// !IMPORTANT! framework.testsPriority is created dynamically in framework.test()
	if (typeof(url) === 'function') {
		self.tests.push({ name: _test + ': ' + name, priority: framework.testsPriority, index: self.tests.length, run: url });
		return self;
	}

	var method = 'GET';
	var length = 0;
	var type = 0;

	if (headers)
		headers = framework_utils.extend({}, headers);
	else
		headers = {};

	if (flags instanceof Array) {
		length = flags.length;
		for (var i = 0; i < length; i++) {

			switch (flags[i].toLowerCase()) {

				case 'xhr':
					headers['X-Requested-With'] = 'XMLHttpRequest';
					break;

				case 'referer':
				case 'referrer':
					headers['Referer'] = url;
					break;

				case 'json':
					headers['Content-Type'] = 'application/json';
					type = 1;
					break;

				case 'xml':
					headers['Content-Type'] = 'text/xml';
					type = 2;
					break;

				case 'get':
				case 'head':
				case 'options':
					method = flags[i].toUpperCase();

					if (data) {
						if (typeof(data) === 'object')
							url += '?' + Qs.stringify(data);
						else
							url += data[0] === '?' ? data : '?' + data;
						data = '';
					}

					break;

				case 'upload':
					headers['Content-Type'] = 'multipart/form-data';
					break;

				case 'robot':
					if (headers['User-Agent'])
						headers['User-Agent'] += ' Bot';
					else
						headers['User-Agent'] = 'Bot';
					break;

				case 'mobile':
					if (headers['User-Agent'])
						headers['User-Agent'] += ' iPhone';
					else
						headers['User-Agent'] = 'iPhone';
					break;

				case 'post':
				case 'put':
				case 'delete':

					method = flags[i].toUpperCase();

					if (!headers['Content-Type'])
						headers['Content-Type'] = 'application/x-www-form-urlencoded';

					break;

				case 'raw':
					headers['Content-Type'] = 'application/octet-stream';
					break;

			}
		}
	}

	headers['X-Assertion-Testing'] = '1';

	if (cookies) {
		var builder = [];
		var keys = Object.keys(cookies);

		length = keys.length;

		for (var i = 0; i < length; i++)
			builder.push(keys[i] + '=' + encodeURIComponent(cookies[keys[i]]));

		if (builder.length)
			headers['Cookie'] = builder.join('; ');
	}

	var obj = {
		name: _test + ': ' + name,
		priority: framework.testsPriority,
		index: self.tests.length,
		url: url,
		callback: callback,
		method: method,
		data: data,
		headers: headers
	};

	self.tests.push(obj);
	return self;
};

/**
 * Test in progress
 * @private
 * @param {Boolean} stop Stop application.
 * @param {Function} callback Callback.
 * @return {Framework}
 */
Framework.prototype.testing = function(stop, callback) {

	if (stop === undefined)
		stop = true;

	var self = this;

	// !IMPORTANT! framework.isTestError is created dynamically
	//             framework.testsFiles too

	if (!self.tests.length) {

		if (!self.testsFiles.length) {
			callback && callback(framework.isTestError === true);
			stop && self.stop(framework.isTestError ? 1 : 0);
			return self;
		}

		var file = self.testsFiles.shift();
		file && file.fn.call(self, self);
		self.testing(stop, callback);
		return self;
	}

	var logger = function(name, start, err) {

		var time = Math.floor(new Date() - start) + ' ms';

		if (err) {
			framework.isTestError = true;
			console.error('Failed [x] '.padRight(20, '.') + ' ' + name + ' <' + (err.name.toLowerCase().indexOf('assert') !== -1 ? err.toString() : err.stack) + '> [' + time + ']');
			return;
		}

		console.info('Passed '.padRight(20, '.') + ' ' + name + ' [' + time + ']');
	};

	var test = self.tests.shift();
	var key = test.name;
	var beg = new Date();

	if (test.run) {

		// Is used in: process.on('uncaughtException')
		framework.testContinue = function(err) {
			logger(key, beg, err);
			if (err)
				framework.testsNO++;
			else
				framework.testsOK++;
			self.testing(stop, callback);
		};

		test.run.call(self, function() {
			logger(key, beg);
			framework.testsOK++;
			self.testing(stop, callback);
		}, key);

		return self;
	}

	var response = function(res) {

		res.on('data', function(chunk) {
			if (this._buffer)
				this._buffer = Buffer.concat([this._buffer, chunk]);
			else
				this._buffer = chunk;
		});

		res.on('end', function() {

			res.removeAllListeners();

			var cookie = res.headers['cookie'] || '';
			var cookies = {};

			if (cookie.length) {

				var arr = cookie.split(';');
				var length = arr.length;

				for (var i = 0; i < length; i++) {
					var c = arr[i].trim().split('=');
					cookies[c.shift()] = unescape(c.join('='));
				}
			}

			try {
				test.callback(null, this._buffer ? this._buffer.toString(ENCODING) : '', res.statusCode, res.headers, cookies, key);
				logger(key, beg);
				framework.testsOK++;
			} catch (e) {
				framework.testsNO++;
				logger(key, beg, e);
			}

			self.testing(stop, callback);
		});

		res.resume();
	};

	var options = Parser.parse((test.url.startsWith('http://', true) || test.url.startsWith('https://', true) ? '' : 'http://' + self.ip + ':' + self.port) + test.url);
	if (typeof(test.data) === 'function')
		test.data = test.data();

	if (typeof(test.data) !== 'string')
		test.data = (test.headers[RESPONSE_HEADER_CONTENTTYPE] || '').indexOf('json') !== -1 ? JSON.stringify(test.data) : Qs.stringify(test.data);

	var buf;

	if (test.data && test.data.length) {
		buf = new Buffer(test.data, ENCODING);
		test.headers[RESPONSE_HEADER_CONTENTLENGTH] = buf.length;
	}

	options.method = test.method;
	options.headers = test.headers;

	var con = options.protocol === 'https:' ? require('https') : http;
	var req = test.method === 'POST' || test.method === 'PUT' || test.method === 'DELETE' || test.method === 'PATCH' ? con.request(options, response) : con.get(options, response);

	req.on('error', function(e) {
		req.removeAllListeners();
		logger(key, beg, e);
		self.testsNO++;
		self.testing(stop, callback);
	});

	req.end(buf);
	return self;
};

/**
 * Load tests
 * @private
 * @param {Boolean} stop Stop framework after end.
 * @param {String Array} names Test names, optional.
 * @param {Function()} cb
 * @return {Framework}
 */
Framework.prototype.test = function(stop, names, cb) {

	var self = this;

	if (stop === undefined)
		stop = true;

	if (typeof(names) === 'function') {
		cb = names;
		names = [];
	} else
		names = names || [];

	var counter = 0;
	self.isTest = true;

	var dir = self.config['directory-tests'];
	var is = false;

	self._configure('config-test', true);

	var logger = function(name, start, err) {

		var time = Math.floor(new Date() - start) + ' ms';

		if (err) {
			framework.isTestError = true;
			console.error('Failed [x] '.padRight(20, '.') + ' ' + name + ' <' + (err.name.toLowerCase().indexOf('assert') !== -1 ? err.toString() : err.stack) + '> [' + time + ']');
			return;
		}

		console.info('Passed '.padRight(20, '.') + ' ' + name + ' [' + time + ']');
	};

	var results = function() {
		if (!framework.testsResults.length)
			return;
		console.log('');
		console.log('===================== RESULTS ======================');
		console.log('');
		framework.testsResults.forEach((fn) => fn());
	};

	framework.testsFiles = [];

	if (!framework.testsResults)
		framework.testsResults = [];

	if (!framework.testsOK)
		framework.testsOK = 0;

	if (!framework.testsNO)
		framework.testsNO = 0;

	framework_utils.ls(framework_utils.combine(dir), function(files) {
		files.forEach(function(filePath) {
			var name = Path.relative(framework_utils.combine(dir), filePath);
			var filename = filePath;
			var ext = framework_utils.getExtension(filename).toLowerCase();
			if (ext !== 'js')
				return;

			if (names.length && names.indexOf(name.substring(0, name.length - 3)) === -1)
				return;

			var test = require(filename);
			var beg = new Date();

			try {
				var isRun = test.run !== undefined;
				var isInstall = test.isInstall !== undefined;
				var isInit = test.init !== undefined;
				var isLoad = test.load !== undefined;

				_test = name;

				if (test.disabled === true)
					return;

				framework.testsPriority = test.priority === undefined ? self.testsFiles.length : test.priority;
				var fn = null;

				if (isRun)
					fn = test.run;
				else if (isInstall)
					fn = test.install;
				else if (isInit)
					fn = test.init;
				else if (isLoad)
					fn = test.loadname;

				if (fn === null)
					return;

				self.testsFiles.push({ name: name, index: self.testsFiles.length, fn: fn, priority: framework.testsPriority });

				test.usage && (function(test) {
					framework.testsResults.push(() => test.usage(name));
				})(test);

				counter++;

			} catch (ex) {
				logger('Failed', beg, ex);
			}
		});

		_test = '';

		self.testsFiles.sort(function(a, b) {

			if (a.priority > b.priority)
				return 1;

			if (a.priority < b.priority)
				return -1;

			if (a.index > b.index)
				return 1;

			if (a.index < b.index)
				return -1;

			return 0;
		});

		setTimeout(function() {
			console.log('===================== TESTING ======================');

			if (counter)
				console.log('');

			self.testing(stop, function() {

				console.log('');
				console.log('Passed ...', framework.testsOK);
				console.log('Failed ...', framework.testsNO);
				console.log('');

				results();
				self.isTest = false;
				console.log('');
				cb && cb();
			});
		}, 100);
	});

	return self;
};

/**
 * Clear temporary directory
 * @param {Function} callback
 * @param {Boolean} isInit Private argument.
 * @return {Framework}
 */
Framework.prototype.clear = function(callback, isInit) {

	var self = this;
	var dir = self.path.temp();
	var plus = self.id ? 'i-' + self.id + '_' : '';

	if (isInit) {
		if (self.config['disable-clear-temporary-directory']) {
			// clears only JS and CSS files
			framework_utils.ls(dir, function(files, directories) {
				self.unlink(files);
				callback && callback();
			}, function(filename, folder) {
				if (folder || (plus && !filename.substring(dir.length).startsWith(plus)))
					return false;
				var ext = framework_utils.getExtension(filename);
				return ext === 'js' || ext === 'css' || ext === 'tmp' || ext === 'upload' || ext === 'html' || ext === 'htm';
			});

			return self;
		}
	}

	if (!existsSync(dir)) {
		callback && callback();
		return self;
	}

	framework_utils.ls(dir, function(files, directories) {

		if (isInit) {
			var arr = [];
			for (var i = 0, length = files.length; i < length; i++) {
				var filename = files[i].substring(dir.length);
				if (plus && !filename.startsWith(plus))
					continue;
				filename.indexOf('/') === -1 && !filename.endsWith('.jsoncache') && arr.push(files[i]);
			}
			files = arr;
			directories = [];
		}

		self.unlink(files, () => self.rmdir(directories, callback));
	});

	if (!isInit) {
		// clear static cache
		self.temporary.path = {};
		self.temporary.range = {};
	}

	return this;
};

/**
 * Remove files in array
 * @param {String Array} arr File list.
 * @param {Function} callback
 * @return {Framework}
 */
Framework.prototype.unlink = function(arr, callback) {
	var self = this;

	if (typeof(arr) === 'string')
		arr = [arr];

	if (!arr.length) {
		callback && callback();
		return self;
	}

	var filename = arr.shift();

	if (filename)
		Fs.unlink(filename, (err) => self.unlink(arr, callback));
	else
		callback && callback();

	return self;
};

/**
 * Remove directories in array
 * @param {String Array} arr
 * @param {Function} callback
 * @return {Framework}
 */
Framework.prototype.rmdir = function(arr, callback) {
	var self = this;

	if (typeof(arr) === 'string')
		arr = [arr];

	if (!arr.length) {
		callback && callback();
		return self;
	}

	var path = arr.shift();

	if (path)
		Fs.rmdir(path, () => self.rmdir(arr, callback));
	else
		callback && callback();

	return self;
};

/**
 * Cryptography (encrypt)
 * @param {String} value
 * @param {String} key Encrypt key.
 * @param {Boolean} isUnique Optional, default true.
 * @return {String}
 */
Framework.prototype.encrypt = function(value, key, isUnique) {

	var self = this;

	if (value === undefined)
		return '';

	var type = typeof(value);

	if (typeof(key) === 'boolean') {
		var tmp = isUnique;
		isUnique = key;
		key = tmp;
	}

	if (type === 'function')
		value = value();

	if (type === 'number')
		value = value.toString();

	if (type === 'object')
		value = JSON.stringify(value);

	return value.encrypt(self.config.secret + '=' + key, isUnique);
};

/**
 * Cryptography (decrypt)
 * @param {String} value
 * @param {String} key Decrypt key.
 * @param {Boolean} jsonConvert Optional, default true.
 * @return {Object or String}
 */
Framework.prototype.decrypt = function(value, key, jsonConvert) {

	if (typeof(key) === 'boolean') {
		var tmp = jsonConvert;
		jsonConvert = key;
		key = tmp;
	}

	if (typeof(jsonConvert) !== 'boolean')
		jsonConvert = true;

	var self = this;
	var result = (value || '').decrypt(self.config.secret + '=' + key);

	if (result === null)
		return null;

	if (jsonConvert) {
		if (result.isJSON()) {
			try {
				return JSON.parse(result);
			} catch (ex) {}
		}
		return null;
	}

	return result;
};

/**
 * Create hash
 * @param {String} type Type (md5, sha1, sha256, etc.)
 * @param {String} value
 * @param {String} salt Optional, default false.
 * @return {String}
 */
Framework.prototype.hash = function(type, value, salt) {
	var hash = Crypto.createHash(type);
	var plus = '';

	if (typeof(salt) === 'string')
		plus = salt;
	else if (salt !== false)
		plus = (this.config.secret || '');

	hash.update(value.toString() + plus, ENCODING);
	return hash.digest('hex');
};

/**
 * Resource reader
 * @param {String} name Optional, resource file name. Default: "default".
 * @param {String} key Resource key.
 * @return {String} String
 */
Framework.prototype.resource = function(name, key) {

	if (!key) {
		key = name;
		name = null;
	}

	if (!name)
		name = 'default';

	var self = this;
	var res = self.resources[name];

	if (res)
		return res[key] || '';

	var routes = self.routes.resources[name];
	var body = '';
	var filename;

	if (routes) {
		for (var i = 0, length = routes.length; i < length; i++) {
			filename = routes[i];
			if (existsSync(filename))
				body += (body ? '\n' : '') + Fs.readFileSync(filename).toString(ENCODING);
		}
	}

	var filename = framework_utils.combine(self.config['directory-resources'], name + '.resource');
	if (existsSync(filename))
		body += (body ? '\n' : '') + Fs.readFileSync(filename).toString(ENCODING);

	var obj = body.parseConfig();
	self.resources[name] = obj;
	return obj[key] || '';
};

/**
 * Translates text
 * @param {String} language A resource filename, optional.
 * @param {String} text
 * @return {String}
 */
Framework.prototype.translate = function(language, text) {

	if (!text) {
		text = language;
		language = undefined;
	}

	if (text[0] === '#' && text[1] !== ' ')
		return this.resource(language, text.substring(1));

	var value = this.resource(language, 'T' + text.hash());
	return value ? value : text;
};

/**
 * The translator for the text from the View Engine @(TEXT TO TRANSLATE)
 * @param {String} language A resource filename, optional.
 * @param {String} text
 * @return {String}
 */
Framework.prototype.translator = function(language, text) {
	return framework_internal.parseLocalization(text, language);
};

Framework.prototype._configure_sitemap = function(arr, clean) {

	if (!arr || typeof(arr) === 'string') {
		var filename = prepare_filename(arr || 'sitemap');
		if (existsSync(filename, true))
			arr = Fs.readFileSync(filename).toString(ENCODING).split('\n');
		else
			arr = null;
	}

	var self = this;
	if (!arr || !arr.length)
		return self;

	if (clean || !self.routes.sitemap)
		self.routes.sitemap = {};

	for (var i = 0, length = arr.length; i < length; i++) {

		var str = arr[i];
		if (!str || str[0] === '#' || str.substring(0, 3) === '// ')
			continue;

		var index = str.indexOf(' :');
		if (index === -1) {
			index = str.indexOf('\t:');
			if (index === -1)
				continue;
		}

		var key = str.substring(0, index).trim();
		var val = str.substring(index + 2).trim();
		var a = val.split('-->');
		var url = a[1].trim();
		var wildcard = false;

		if (url.endsWith('*')) {
			wildcard = true;
			url = url.substring(0, url.length - 1);
		}

		var name = a[0].trim();
		var localizeName = name.startsWith('@(');
		var localizeUrl = url.startsWith('@(');

		if (localizeName)
			name = name.substring(2, name.length - 1).trim();

		if (localizeUrl)
			url = url.substring(2, url.length - 1).trim();

		self.routes.sitemap[key] = { name: name, url: url, parent: a[2] ? a[2].trim() : null, wildcard: wildcard, formatName: name.indexOf('{') !== -1, formatUrl: url.indexOf('{') !== -1, localizeName: localizeName, localizeUrl: localizeUrl };
	}

	return self;
};

Framework.prototype.sitemap = function(name, me, language) {

	var self = this;
	if (!self.routes.sitemap)
		return EMPTYARRAY;

	if (typeof(me) === 'string') {
		language = me;
		me = false;
	}

	var key = REPOSITORY_SITEMAP + name + '$' + (me ? '1' : '0') + '$' + (language || '');

	if (self.temporary.other[key])
		return self.temporary.other[key];

	var sitemap;
	var id = name;
	var url;
	var title;

	if (me === true) {
		sitemap = self.routes.sitemap[name];
		var item = { sitemap: id, id: '', name: '', url: '', last: true, selected: true, index: 0, wildcard: false, formatName: false, formatUrl: false };
		if (!sitemap)
			return item;

		title = sitemap.name;
		if (sitemap.localizeName)
			title = self.translate(language, title);

		url = sitemap.url;
		if (sitemap.localizeUrl)
			url = self.translate(language, url);

		item.sitemap = id;
		item.id = name;
		item.formatName = sitemap.formatName;
		item.formatUrl = sitemap.formatUrl;
		item.localizeUrl = sitemap.localizeUrl;
		item.localizeName = sitemap.localizeName;
		item.name = title;
		item.url = url;
		item.wildcard = sitemap.wildcard;
		self.temporary.other[key] = item;
		return item;
	}

	var arr = [];
	var index = 0;

	while (true) {
		sitemap = self.routes.sitemap[name];
		if (!sitemap)
			break;

		title = sitemap.name;
		url = sitemap.url;

		if (sitemap.localizeName)
			title = self.translate(language, sitemap.name);

		if (sitemap.localizeUrl)
			url = self.translate(language, url);

		arr.push({ sitemap: id, id: name, name: title, url: url, last: index === 0, first: sitemap.parent ? false : true, selected: index === 0, index: index, wildcard: sitemap.wildcard, formatName: sitemap.formatName, formatUrl: sitemap.formatUrl, localizeName: sitemap.localizeName, localizeUrl: sitemap.localizeUrl });
		index++;
		name = sitemap.parent;
		if (!name)
			break;
	}

	arr.reverse();
	self.temporary.other[key] = arr;
	return arr;
};

/**
 * Gets a list of all items in sitemap
 * @param {String} parent
 * @param {String} language Optional, language
 * @return {Array}
 */
Framework.prototype.sitemap_navigation = function(parent, language) {

	var self = this;
	var key = REPOSITORY_SITEMAP + '_n_' + (parent || '') + '$' + (language || '');;
	if (self.temporary.other[key])
		return self.temporary.other[key];

	var keys = Object.keys(self.routes.sitemap);
	var arr = [];
	var index = 0;

	for (var i = 0, length = keys.length; i < length; i++) {
		var item = self.routes.sitemap[keys[i]];
		if ((parent && item.parent !== parent) || (!parent && item.parent))
			continue;

		var title = item.name;
		var url = item.url;

		if (item.localizeName)
			title = self.translate(language, title);

		if (item.localizeUrl)
			url = self.translate(language, url);

		arr.push({ id: parent || '', name: title, url: url, last: index === 0, first: item.parent ? false : true, selected: index === 0, index: index, wildcard: item.wildcard, formatName: item.formatName, formatUrl: item.formatUrl });
		index++;
	}

	arr.quicksort('name');
	self.temporary.other[key] = arr;
	return arr;
};

/**
 * Adds an item(s) to sitemap
 * @param {String|Array} obj - 'ID : Title ---> URL --> [Parent]' parent is optional
 * @return {framework}
 */
Framework.prototype.sitemap_add = function (obj) {
    var self = this;
    self._configure_sitemap(typeof(obj) === 'array' ? obj : [obj]);
    return self;
};

Framework.prototype._configure_dependencies = function(arr) {

	if (!arr || typeof(arr) === 'string') {
		var filename = prepare_filename(arr || 'dependencies');
		if (existsSync(filename, true))
			arr = Fs.readFileSync(filename).toString(ENCODING).split('\n');
		else
			arr = null;
	}

	var self = this;

	if (!arr)
		return self;

	for (var i = 0, length = arr.length; i < length; i++) {

		var str = arr[i];

		if (!str || str[0] === '#' || str.substring(0, 3) === '// ')
			continue;

		var index = str.indexOf(' :');
		if (index === -1) {
			index = str.indexOf('\t:');
			if (index === -1)
				continue;
		}

		var key = str.substring(0, index).trim();
		var url = str.substring(index + 2).trim();
		var options = EMPTYOBJECT;

		index = url.indexOf('-->');

		if (index !== -1) {
			var opt = url.substring(index + 3).trim();
			if (opt.isJSON())
				options = JSON.parse(opt);
			url = url.substring(0, index).trim();
		}

		switch (key) {
			case 'package':
			case 'packages':
			case 'pkg':
				self.install('package', url, options);
				break;
			case 'module':
			case 'modules':
				self.install('module', url, options);
				break;
			case 'model':
			case 'models':
				self.install('model', url, options);
				break;
			case 'source':
			case 'sources':
				self.install('source', url, options);
				break;
			case 'controller':
			case 'controllers':
				self.install('controller', url, options);
				break;
			case 'view':
			case 'views':
				self.install('view', url, options);
				break;
			case 'version':
			case 'versions':
				self.install('version', url, options);
				break;
			case 'config':
			case 'configuration':
				self.install('config', url, options);
				break;
			case 'isomorphic':
			case 'isomorphics':
				self.install('isomorphic', url, options);
				break;
			case 'definition':
			case 'definitions':
				self.install('definition', url, options);
				break;
			case 'middleware':
			case 'middlewares':
				self.install('middleware', url, options);
				break;
		}
	}

	return self;
};

Framework.prototype._configure_workflows = function(arr, clean) {

	var self = this;

	if (arr === undefined || typeof(arr) === 'string') {
		var filename = prepare_filename(arr || 'workflows');
		if (existsSync(filename, true))
			arr = Fs.readFileSync(filename).toString(ENCODING).split('\n');
		else
			arr = null;
	}

	if (clean)
		self.workflows = {};

	if (!arr || !arr.length)
		return self;

	arr.forEach(function(line) {
		line = line.trim();
		if (line.startsWith('//'))
			return;
		var index = line.indexOf(':');
		if (index === -1)
			return;

		var key = line.substring(0, index).trim();
		var response = -1;
		var builder = [];

		// sub-type
		var subindex = key.indexOf('(');
		if (subindex !== -1) {
			var type = key.substring(subindex + 1, key.indexOf(')', subindex + 1)).trim();
			key = key.substring(0, subindex).trim();
			type = type.replace(/^default\//gi, '');
			key = type + '#' + key;
		}

		line.substring(index + 1).split('-->').forEach(function(operation, index) {
			operation = operation.trim().replace(/\"/g, '\'');

			if (operation.endsWith('(response)')) {
				response = index;
				operation = operation.replace('(response)', '').trim();
			}

			var what = operation.split(':');
			if (what.length === 2)
				builder.push('$' + what[0].trim() + '(' + what[1].trim() + ', options)');
			else
				builder.push('$' + what[0] + '(options)');
		});

		self.workflows[key] = new Function('model', 'options', 'callback', 'return model.$async(callback' + (response === -1 ? '' : ', ' + response) + ').' + builder.join('.') + ';');
	});

	return this;
};

Framework.prototype._configure_versions = function(arr, clean) {

	var self = this;

	if (arr === undefined || typeof(arr) === 'string') {
		var filename = prepare_filename(arr || 'versions');
		if (existsSync(filename, true))
			arr = Fs.readFileSync(filename).toString(ENCODING).split('\n');
		else
			arr = null;
	}

	if (!arr) {
		if (clean)
			self.versions = null;
		return self;
	}

	if (!clean)
		self.versions = {};

	if (!self.versions)
		self.versions = {};

	for (var i = 0, length = arr.length; i < length; i++) {

		var str = arr[i];

		if (!str || str[0] === '#' || str.substring(0, 3) === '// ')
			continue;

		if (str[0] !== '/')
			str = '/' + str;

		var index = str.indexOf(' :');
		var ismap = false;

		if (index === -1) {
			index = str.indexOf('\t:');
			if (index === -1) {
				index = str.indexOf('-->');
				if (index === -1)
					continue;
				ismap = true;
			}
		}

		var len = ismap ? 3 : 2;
		var key = str.substring(0, index).trim();
		var filename = str.substring(index + len).trim();
		self.versions[key] = filename;
		ismap && self.map(filename, self.path.public(key));
	}

	return self;
};

Framework.prototype._configure = function(arr, rewrite) {

	var self = this;
	var type = typeof(arr);

	if (type === 'string') {
		var filename = prepare_filename(arr);
		if (!existsSync(filename, true))
			return self;
		arr = Fs.readFileSync(filename).toString(ENCODING).split('\n');
	}

	if (!arr) {

		var filenameA = framework_utils.combine('/', 'config');
		var filenameB = framework_utils.combine('/', 'config-' + (self.config.debug ? 'debug' : 'release'));

		arr = [];

		// read all files from "configs" directory
		var configs = self.path.configs();
		if (existsSync(configs)) {
			var tmp = Fs.readdirSync(configs);
			for (var i = 0, length = tmp.length; i < length; i++) {
				var skip = tmp[i].match(/\-(debug|release|test)$/i);
				if (skip) {
					skip = skip[0].toString().toLowerCase();
					if (skip === '-debug' && !self.isDebug)
						continue;
					if (skip === '-release' && self.isDebug)
						continue;
					if (skip === '-test' && !self.isTest)
						continue;
				}
				arr = arr.concat(Fs.readFileSync(configs + tmp[i]).toString(ENCODING).split('\n'));
			}
		}

		if (existsSync(filenameA) && Fs.lstatSync(filenameA).isFile())
			arr = arr.concat(Fs.readFileSync(filenameA).toString(ENCODING).split('\n'));

		if (existsSync(filenameB) && Fs.lstatSync(filenameB).isFile())
			arr = arr.concat(Fs.readFileSync(filenameB).toString(ENCODING).split('\n'));
	}

	var done = function() {
		process.title = 'total: ' + self.config.name.removeDiacritics().toLowerCase().replace(REG_EMPTY, '-').substring(0, 8);
		self.isVirtualDirectory = existsSync(framework_utils.combine(self.config['directory-public-virtual']));
	};

	if (!arr instanceof Array || !arr.length) {
		done();
		return self;
	}

	if (rewrite === undefined)
		rewrite = true;

	var obj = {};
	var accepts = null;
	var length = arr.length;
	var resources = false;
	var tmp;
	var subtype;
	var value;

	for (var i = 0; i < length; i++) {
		var str = arr[i];

		if (!str || str[0] === '#' || (str[0] === '/' || str[1] === '/'))
			continue;

		var index = str.indexOf(':');
		if (index === -1)
			continue;

		var name = str.substring(0, index).trim();
		if (name === 'debug' || name === 'resources')
			continue;

		value = str.substring(index + 1).trim();
		index = name.indexOf('(');

		if (index !== -1) {
			subtype = name.substring(index + 1, name.indexOf(')')).trim().toLowerCase();
			name = name.substring(0, index).trim();
		} else
			subtype = '';

		switch (name) {
			case 'default-cors-maxage':
			case 'default-request-length':
			case 'default-websocket-request-length':
			case 'default-request-timeout':
			case 'default-interval-clear-cache':
			case 'default-interval-clear-resources':
			case 'default-interval-precompile-views':
			case 'default-interval-websocket-ping':
			case 'default-maximum-file-descriptors':
			case 'default-interval-clear-dnscache':
				obj[name] = framework_utils.parseInt(value);
				break;

			case 'static-accepts-custom':
				accepts = value.replace(REG_EMPTY, '').split(',');
				break;

			case 'default-root':
				if (value)
					obj[name] = framework_utils.path(value);
				break;

			case 'static-accepts':
				obj[name] = {};
				tmp = value.replace(REG_EMPTY, '').split(',');
				for (var j = 0; j < tmp.length; j++)
					obj[name][tmp[j]] = true;
				break;

			case 'allow-gzip':
			case 'allow-websocket':
			case 'allow-performance':
			case 'allow-compile-html':
			case 'allow-compile-style':
			case 'allow-compile-script':
			case 'disable-strict-server-certificate-validation':
			case 'disable-clear-temporary-directory':
			case 'trace':
			case 'allow-cache-snapshot':
				obj[name] = value.toLowerCase() === 'true' || value === '1' || value === 'on';
				break;

			case 'allow-compress-html':
				obj['allow-compile-html'] = value.toLowerCase() === 'true' || value === '1' || value === 'on';
				break;

			case 'version':
				obj[name] = value;
				break;

			default:

				if (subtype === 'string')
					obj[name] = value;
				else if (subtype === 'number' || subtype === 'currency' || subtype === 'float' || subtype === 'double')
					obj[name] = value.isNumber(true) ? value.parseFloat() : value.parseInt();
				else if (subtype === 'boolean' || subtype === 'bool')
					obj[name] = value.parseBoolean();
				else if (subtype === 'eval' || subtype === 'object' || subtype === 'array') {
					try {
						obj[name] = new Function('return ' + value)();
					} catch (e) {
						F.error(e, 'F.configure(' + name + ')');
					}
				} else if (subtype === 'json')
					obj[name] = value.parseJSON();
				else if (subtype === 'date' || subtype === 'datetime' || subtype === 'time')
					obj[name] = value.parseDate();
				else if (subtype === 'env' || subtype === 'environment')
					obj[name] = process.env[value];
				else
					obj[name] = value.isNumber() ? framework_utils.parseInt(value) : value.isNumber(true) ? framework_utils.parseFloat(value) : value.isBoolean() ? value.toLowerCase() === 'true' : value;
				break;
		}
	}

	framework_utils.extend(self.config, obj, rewrite);

	if (!self.config['directory-temp'])
		self.config['directory-temp'] = '~' + framework_utils.path(Path.join(Os.tmpdir(), 'totaljs' + self.directory.hash()));

	if (!self.config['etag-version'])
		self.config['etag-version'] = self.config.version.replace(/\.|\s/g, '');

	if (self.config['default-timezone'])
		process.env.TZ = self.config['default-timezone'];

	if (accepts && accepts.length)
		accepts.forEach(accept => self.config['static-accepts'][accept] = true);

	if (self.config['disable-strict-server-certificate-validation'] === true)
		process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

	if (self.config['allow-performance'])
		http.globalAgent.maxSockets = 9999;

	Object.keys(HEADERS).forEach(function(key) {
		Object.keys(HEADERS[key]).forEach(function(subkey) {
			if (subkey === 'Cache-Control')
				HEADERS[key][subkey] = HEADERS[key][subkey].replace(/max-age=\d+/, 'max-age=' + self.config['default-response-maxage']);
		});
	});

	done();
	self.emit('configure', self.config);
	return self;
};

/**
 * Create URL: JavaScript (according to config['static-url-script'])
 * @param {String} name
 * @return {String}
 */
Framework.prototype.routeScript = function(name, theme) {
	var self = this;
	if (!name.endsWith('.js'))
		name += '.js';
	return self._routeStatic(name, self.config['static-url-script'], theme);
};

/**
 * Create URL: CSS (according to config['static-url-style'])
 * @param {String} name
 * @return {String}
 */
Framework.prototype.routeStyle = function(name, theme) {
	var self = this;
	if (!name.endsWith('.css'))
		name += '.css';
	return self._routeStatic(name, self.config['static-url-style'], theme);
};

Framework.prototype.routeImage = function(name, theme) {
	var self = this;
	return self._routeStatic(name, self.config['static-url-image'], theme);
};

Framework.prototype.routeVideo = function(name, theme) {
	var self = this;
	return self._routeStatic(name, self.config['static-url-video'], theme);
};

Framework.prototype.routeFont = function(name, theme) {
	var self = this;
	return self._routeStatic(name, self.config['static-url-font'], theme);
};

Framework.prototype.routeDownload = function(name, theme) {
	var self = this;
	return self._routeStatic(name, self.config['static-url-download'], theme);
};

Framework.prototype.routeStatic = function(name, theme) {
	var self = this;
	return self._routeStatic(name, self.config['static-url'], theme);
};

Framework.prototype._routeStatic = function(name, directory, theme) {
	var key = name + directory + '$' + theme;
	var val = framework.temporary.other[key];
	if (RELEASE && val)
		return val;

	if (name[0] === '~') {
		name = name.substring(name[1] === '~' ? 2 : 1);
		theme = '';
	} else if (name[0] === '=') {
		// theme
		var index = name.indexOf('/');
		if (index !== -1) {
			theme = name.substring(1, index);
			name = name.substring(index + 1);
			if (theme === '?')
				theme = framework.config['default-theme'];
		}
	}

	var filename;

	if (REG_ROUTESTATIC.test(name))
		filename = name;
	else if (name[0] === '/')
		filename = framework_utils.join(theme, this._version(name));
	else {
		filename = framework_utils.join(theme, directory, this._version(name));
		if (REG_HTTPHTTPS.test(filename))
			filename = filename.substring(1);
	}

	return framework.temporary.other[key] = framework_internal.preparePath(this._version(filename));
};

Framework.prototype._version = function(name) {
	var self = this;
	if (self.versions)
		name = self.versions[name] || name;
	if (self.onVersion)
		name = self.onVersion(name) || name;
	return name;
};

Framework.prototype._version_prepare = function(html) {
	var self = this;

	var match = html.match(REG_VERSIONS);
	if (!match)
		return html;

	for (var i = 0, length = match.length; i < length; i++) {

		var src = match[i].toString();
		var end = 5;

		// href
		if (src[0] === 'h')
			end = 6;

		var name = src.substring(end, src.length - 1);
		html = html.replace(match[i], src.substring(0, end) + self._version(name) + '"');
	}

	return html;
};

/**
 * Lookup for the route
 * @param {HttpRequest} req
 * @param {String} url URL address.
 * @param {String Array} flags
 * @param {Boolean} membertype Not defined = 0, Authorized = 1, Unauthorized = 2
 * @return {Object}
 */
Framework.prototype.lookup = function(req, url, flags, membertype) {

	var self = this;
	var isSystem = url[0] === '#';
	var subdomain = self._length_subdomain_web && req.subdomain ? req.subdomain.join('.') : null;

	if (isSystem)
		req.path = [url];

	// helper for 401 http status
	req.$isAuthorized = true;

	var key;

	if (!isSystem) {
		key = '#' + url + '$' + membertype + req.$flags + (subdomain ? '$' + subdomain : '');
		if (framework.temporary.other[key])
			return framework.temporary.other[key];
	}

	var length = self.routes.web.length;
	for (var i = 0; i < length; i++) {

		var route = self.routes.web[i];
		if (route.CUSTOM) {
			if (!route.CUSTOM(url, req, flags))
				continue;
		} else {
			if (self._length_subdomain_web && !framework_internal.routeCompareSubdomain(subdomain, route.subdomain))
				continue;
			if (route.isASTERIX) {
				if (!framework_internal.routeCompare(req.path, route.url, isSystem, true))
					continue;
			} else {
				if (!framework_internal.routeCompare(req.path, route.url, isSystem))
					continue;
			}
		}

		if (isSystem) {
			if (route.isSYSTEM)
				return route;
			continue;
		}

		if (route.isPARAM && route.regexp) {
			var skip = false;
			for (var j = 0, l = route.regexpIndexer.length; j < l; j++) {

				var p = req.path[route.regexpIndexer[j]];
				if (p === undefined) {
					skip = true;
					break;
				}

				if (!route.regexp[route.regexpIndexer[j]].test(p)) {
					skip = true;
					break;
				}
			}

			if (skip)
				continue;
		}

		if (route.flags && route.flags.length) {
			var result = framework_internal.routeCompareFlags2(req, route, membertype);
			if (result === -1)
				req.$isAuthorized = false; // request is not authorized
			if (result < 1)
				continue;
		}

		if (key && route.isCACHE && req.$isAuthorized)
			framework.temporary.other[key] = route;

		return route;
	}

	return null;
};

Framework.prototype.lookup_websocket = function(req, url, membertype) {

	var self = this;
	var subdomain = self._length_subdomain_websocket && req.subdomain ? req.subdomain.join('.') : null;
	var length = self.routes.websockets.length;

	req.$isAuthorized = true;

	for (var i = 0; i < length; i++) {

		var route = self.routes.websockets[i];

		if (route.CUSTOM) {
			if (!route.CUSTOM(url, req))
				continue;
		} else {

			if (self._length_subdomain_websocket && !framework_internal.routeCompareSubdomain(subdomain, route.subdomain))
				continue;
			if (route.isASTERIX) {
				if (!framework_internal.routeCompare(req.path, route.url, false, true))
					continue;
			} else {
				if (!framework_internal.routeCompare(req.path, route.url, false))
					continue;
			}
		}

		if (route.isPARAM && route.regexp) {
			var skip = false;
			for (var j = 0, l = route.regexpIndexer.length; j < l; j++) {

				var p = req.path[route.regexpIndexer[j]];
				if (p === undefined) {
					skip = true;
					break;
				}

				if (!route.regexp[route.regexpIndexer[j]].test(p)) {
					skip = true;
					break;
				}
			}

			if (skip)
				continue;
		}

		if (route.flags && route.flags.length) {
			var result = framework_internal.routeCompareFlags2(req, route, membertype);
			if (result === -1)
				req.$isAuthorized = false;
			if (result < 1)
				continue;
		}

		return route;
	}

	return null;
};

/**
 * Accept file type
 * @param {String} extension
 * @param {String} contentType Content-Type for file extension, optional.
 * @return {Framework}
 */
Framework.prototype.accept = function(extension, contentType) {

	var self = this;

	if (extension[0] !== '.')
		extension = '.' + extension;

	self.config['static-accepts'][extension] = true;
	contentType && framework_utils.setContentType(extension, contentType);
	return self;
};

/**
 * Run worker
 * @param {String} name
 * @param {String} id Worker id, optional.
 * @param {Number} timeout Timeout, optional.
 * @param {Array} args Additional arguments, optional.
 * @return {ChildProcess}
 */
Framework.prototype.worker = function(name, id, timeout, args) {

	var self = this;
	var fork = null;
	var type = typeof(id);

	if (type === 'number' && timeout === undefined) {
		timeout = id;
		id = null;
		type = 'undefined';
	}

	if (type === 'string')
		fork = self.workers[id];

	if (id instanceof Array) {
		args = id;
		id = null;
		timeout = undefined;
	}

	if (timeout instanceof Array) {
		args = timeout;
		timeout = undefined;
	}

	if (fork)
		return fork;

	// @TODO: dokončiť
	var filename = name[0] === '@' ? framework.path.package(name.substring(1)) : framework_utils.combine(self.config['directory-workers'], name);

	if (!args)
		args = [];

	fork = Child.fork(filename[filename.length - 3] === '.' ? filename : filename + '.js', args, HEADERS.workers);

	if (!id)
		id = name + '_' + new Date().getTime();

	fork.__id = id;
	self.workers[id] = fork;

	fork.on('exit', function() {
		var self = this;
		self.__timeout && clearTimeout(self.__timeout);
		delete framework.workers[self.__id];
		fork.removeAllListeners();
		fork = null;
	});

	if (typeof(timeout) !== 'number')
		return fork;

	fork.__timeout = setTimeout(function() {
		fork.kill();
		fork = null;
	}, timeout);

	return fork;
};

Framework.prototype.worker2 = function(name, args, callback, timeout) {

	var self = this;

	if (typeof(args) === 'function') {
		timeout = callback;
		callback = args;
		args = undefined;
	} else if (typeof(callback) === 'number') {
		var tmp = timeout;
		timeout = callback;
		callback = tmp;
	}

	if (args && !(args instanceof Array))
		args = [args];

	var fork = self.worker(name, name, timeout, args);
	if (fork.__worker2)
		return fork;

	fork.__worker2 = true;
	fork.on('error', function(e) {
		callback && callback(e);
		callback = null;
	});

	fork.on('exit', function() {
		callback && callback();
		callback = null;
	});

	return fork;
};

/**
 * This method suspends
 * @param {String} name Operation name.
 * @param {Boolean} enable Enable waiting (optional, default: by the current state).
 * @return {Boolean}
 */
Framework.prototype.wait = function(name, enable) {
	var self = this;

	if (!self.waits)
		self.waits = {};

	if (enable !== undefined) {
		if (enable)
			self.waits[name] = true;
		else
			delete self.waits[name];
		self._length_wait = Object.keys(self.waits).length;
		return enable;
	}

	if (self.waits[name])
		delete self.waits[name];
	else {
		self.waits[name] = true;
		enable = true;
	}

	self._length_wait = Object.keys(self.waits).length;
	return enable === true;
};

// *********************************************************************************
// =================================================================================
// Framework Restrictions
// 1.01
// =================================================================================
// *********************************************************************************

function FrameworkRestrictions() {
	this.is = false;
	this.isAllowedIP = false;
	this.isBlockedIP = false;
	this.isAllowedCustom = false;
	this.isBlockedCustom = false;
	this.allowedIP = [];
	this.blockedIP = [];
	this.allowedCustom = {};
	this.blockedCustom = {};
	this.allowedCustomKeys = [];
	this.blockedCustomKeys = [];
};

FrameworkRestrictions.prototype.allow = function(name, value) {

	var self = this;

	// IP address
	if (!value) {
		self.allowedIP.push(name);
		self.refresh();
		return framework;
	}

	// Custom header
	if (self.allowedCustom[name])
		self.allowedCustom[name].push(value);
	else
		self.allowedCustom[name] = [value];

	self.refresh();
	return framework;
};

FrameworkRestrictions.prototype.disallow = function(name, value) {

	var self = this;

	// IP address
	if (value === undefined) {
		self.blockedIP.push(name);
		self.refresh();
		return framework;
	}

	// Custom header
	if (self.blockedCustom[name])
		self.blockedCustom[name].push(value);
	else
		self.blockedCustom[name] = [value];

	self.refresh();
	return framework;
};

FrameworkRestrictions.prototype.refresh = function() {

	var self = this;

	self.isAllowedIP = self.allowedIP.length > 0;
	self.isBlockedIP = self.blockedIP.length > 0;

	self.isAllowedCustom = !framework_utils.isEmpty(self.allowedCustom);
	self.isBlockedCustom = !framework_utils.isEmpty(self.blockedCustom);

	self.allowedCustomKeys = Object.keys(self.allowedCustom);
	self.blockedCustomKeys = Object.keys(self.blockedCustom);

	self.is = self.isAllowedIP || self.isBlockedIP || self.isAllowedCustom || self.isBlockedCustom;
	return framework;
};

FrameworkRestrictions.prototype.clearIP = function() {
	var self = this;
	self.allowedIP = [];
	self.blockedIP = [];
	self.refresh();
	return framework;
}

FrameworkRestrictions.prototype.clearHeaders = function() {
	var self = this;
	self.allowedCustom = {};
	self.blockedCustom = {};
	self.allowedCustomKeys = [];
	self.blockedCustomKeys = [];
	self.refresh();
	return framework;
}

FrameworkRestrictions.prototype._allowedCustom = function(headers) {

	var self = this;
	var length = self.allowedCustomKeys.length;

	for (var i = 0; i < length; i++) {

		var key = self.allowedCustomKeys[i];
		var value = headers[key];
		if (value === undefined)
			return false;

		var arr = self.allowedCustom[key];
		var max = arr.length;
		for (var j = 0; j < max; j++) {
			if (value.search(arr[j]) === -1)
				return false;
		}
	}

	return true;
};

FrameworkRestrictions.prototype._blockedCustom = function(headers) {

	var self = this;
	var length = self.blockedCustomKeys.length;

	for (var i = 0; i < length; i++) {

		var key = self.blockedCustomKeys[i];
		var value = headers[key];

		if (value === undefined)
			return false;

		var arr = self.blockedCustom[key];
		var max = arr.length;

		for (var j = 0; j < max; j++) {
			if (value.search(arr[j]) !== -1)
				return true;
		}

	}

	return false;
};

// *********************************************************************************
// =================================================================================
// Framework path
// =================================================================================
// *********************************************************************************

function FrameworkPath() {}

FrameworkPath.prototype.verify = function(name) {
	var prop = '$directory-' + name;
	if (framework.temporary.path[prop])
		return framework;
	var directory = framework.config['directory-' + name] || name;
	var dir = framework_utils.combine(directory);
	!existsSync(dir) && Fs.mkdirSync(dir);
	framework.temporary.path[prop] = true;
	return framework;
};

FrameworkPath.prototype.exists = function(path, callback) {
	Fs.lstat(path, (err, stats) => callback(err ? false : true, stats ? stats.size : 0, stats ? stats.isFile() : false));
	return framework;
};

FrameworkPath.prototype.public = function(filename) {
	return framework_utils.combine(framework.config['directory-public'], filename);
};

FrameworkPath.prototype.public_cache = function(filename) {
	var key = 'public_' + filename;
	var item = framework.temporary.other[key];
	if (item)
		return item;
	return framework.temporary.other[key] = framework_utils.combine(framework.config['directory-public'], filename);
};

FrameworkPath.prototype.private = function(filename) {
	return framework_utils.combine(framework.config['directory-private'], filename);
};

FrameworkPath.prototype.isomorphic = function(filename) {
	return framework_utils.combine(framework.config['directory-isomorphic'], filename);
};

FrameworkPath.prototype.configs = function(filename) {
	return framework_utils.combine(framework.config['directory-configs'], filename);
};

FrameworkPath.prototype.virtual = function(filename) {
	return framework_utils.combine(framework.config['directory-public-virtual'], filename);
};

FrameworkPath.prototype.logs = function(filename) {
	this.verify('logs');
	return framework_utils.combine(framework.config['directory-logs'], filename);
};

FrameworkPath.prototype.models = function(filename) {
	return framework_utils.combine(framework.config['directory-models'], filename);
};

FrameworkPath.prototype.temp = function(filename) {
	this.verify('temp');
	return framework_utils.combine(framework.config['directory-temp'], filename);
};

FrameworkPath.prototype.temporary = function(filename) {
	return this.temp(filename);
};

FrameworkPath.prototype.views = function(filename) {
	return framework_utils.combine(framework.config['directory-views'], filename);
};

FrameworkPath.prototype.workers = function(filename) {
	return framework_utils.combine(framework.config['directory-workers'], filename);
};

FrameworkPath.prototype.databases = function(filename) {
	this.verify('databases');
	return framework_utils.combine(framework.config['directory-databases'], filename);
};

FrameworkPath.prototype.modules = function(filename) {
	return framework_utils.combine(framework.config['directory-modules'], filename);
};

FrameworkPath.prototype.controllers = function(filename) {
	return framework_utils.combine(framework.config['directory-controllers'], filename);
};

FrameworkPath.prototype.definitions = function(filename) {
	return framework_utils.combine(framework.config['directory-definitions'], filename);
};

FrameworkPath.prototype.tests = function(filename) {
	return framework_utils.combine(framework.config['directory-tests'], filename);
};

FrameworkPath.prototype.resources = function(filename) {
	return framework_utils.combine(framework.config['directory-resources'], filename);
};

FrameworkPath.prototype.services = function(filename) {
	return framework_utils.combine(framework.config['directory-services'], filename);
};

FrameworkPath.prototype.packages = function(filename) {
	return framework_utils.combine(framework.config['directory-packages'], filename);
};

FrameworkPath.prototype.themes = function(filename) {
	return framework_utils.combine(framework.config['directory-themes'], filename);
};

FrameworkPath.prototype.root = function(filename) {
	var p = Path.join(directory, filename || '');
	return framework.isWindows ? p.replace(/\\/g, '/') : p;
};

FrameworkPath.prototype.package = function(name, filename) {

	if (filename === undefined) {
		var index = name.indexOf('/');
		if (index !== -1) {
			filename = name.substring(index + 1);
			name = name.substring(0, index);
		}
	}

	var tmp = framework.config['directory-temp'];
	var p = tmp[0] === '~' ? Path.join(tmp.substring(1), name + '.package', filename || '') : Path.join(directory, tmp, name + '.package', filename || '');
	return framework.isWindows ? p.replace(REG_WINDOWSPATH, '/') : p;
};

// *********************************************************************************
// =================================================================================
// Cache declaration
// =================================================================================
// *********************************************************************************

function FrameworkCache() {
	this.items = {};
	this.count = 1;
	this.interval;
}

FrameworkCache.prototype.init = function() {
	clearInterval(this.interval);
	this.interval = setInterval(() => framework.cache.recycle(), 1000 * 60);
	framework.config['allow-cache-snapshot'] && this.load();
	return this;
};

FrameworkCache.prototype.save = function() {
	var self = this;
	Fs.writeFile(framework.path.temp((framework.id ? 'i-' + framework.id + '_' : '') + 'framework.jsoncache'), JSON.stringify(this.items), NOOP);
	return self;
};

FrameworkCache.prototype.load = function() {
	var self = this;
	Fs.readFile(framework.path.temp((framework.id ? 'i-' + framework.id + '_' : '') + 'framework.jsoncache'), function(err, data) {
		if (err)
			return;

		try {
			data = JSON.parse(data.toString('utf8'), (key, value) => typeof(value) === 'string' && value.isJSONDate() ? new Date(value) : value);
			self.items = data;
		} catch (e) {}
	});
	return self;
};

FrameworkCache.prototype.stop = function() {
	var self = this;
	clearInterval(self.interval);
	return self;
};

FrameworkCache.prototype.clear = function() {
	var self = this;
	self.items = {};
	return self;
};

FrameworkCache.prototype.recycle = function() {

	var self = this;
	var items = self.items;
	var expire = framework.datetime = new Date();

	self.count++;

	for (var o in items) {
		var value = items[o];
		if (!value)
			delete items[o];
		else if (value.expire < expire) {
			framework.emit('cache-expire', o, value.value);
			delete items[o];
		}
	}

	framework.config['allow-cache-snapshot'] && self.save();
	framework._service(self.count);
	return self;
};

FrameworkCache.prototype.set = FrameworkCache.prototype.add = function(name, value, expire, sync) {
	var self = this;
	var type = typeof(expire);

	switch (type) {
		case 'string':
			expire = expire.parseDateExpiration();
			break;

		case 'undefined':
			expire = framework.datetime.add('m', 5);
			break;
	}

	self.items[name] = { value: value, expire: expire };
	framework.emit('cache-set', name, value, expire, sync);
	return value;
};

FrameworkCache.prototype.read = FrameworkCache.prototype.get = function(key, def) {
	var self = this;
	var value = self.items[key];

	if (!value)
		return def;

	if (value.expire < new Date()) {
		self.items[key] = undefined;
		framework.emit('cache-expire', key, value.value);
		return def;
	}

	return value.value;
};

FrameworkCache.prototype.read2 = FrameworkCache.prototype.get2 = function(key, def) {
	var self = this;
	var value = self.items[key];

	if (!value)
		return def;

	if (value.expire < F.datetime) {
		self.items[key] = undefined;
		framework.emit('cache-expire', key, value.value);
		return def;
	}

	return value.value;
};

FrameworkCache.prototype.setExpire = function(name, expire) {
	var self = this;
	var obj = self.items[name];

	if (obj)
		obj.expire = typeof(expire) === 'string' ? expire.parseDateExpiration() : expire;

	return self;
};

FrameworkCache.prototype.remove = function(name) {
	var self = this;
	var value = self.items[name];
	if (value)
		self.items[name] = undefined;
	return value;
};

FrameworkCache.prototype.removeAll = function(search) {
	var self = this;
	var count = 0;
	var isReg = framework_utils.isRegExp(search);

	for (var key in self.items) {

		if (isReg) {
			if (!search.test(key))
				continue;
		} else {
			if (key.indexOf(search) === -1)
				continue;
		}

		self.remove(key);
		count++;
	}

	return count;
};

FrameworkCache.prototype.fn = function(name, fnCache, fnCallback) {

	var self = this;
	var value = self.read2(name);

	if (value) {
		fnCallback && fnCallback(value, true);
		return self;
	}

	fnCache(function(value, expire) {
		self.add(name, value, expire);
		fnCallback && fnCallback(value, false);
	});

	return self;
};

// *********************************************************************************
// =================================================================================
// Framework.Subscribe
// =================================================================================
// *********************************************************************************

function Subscribe(framework, req, res, type) {

	// type = 0 - GET, DELETE
	// type = 1 - POST, PUT
	// type = 2 - POST MULTIPART
	// type = 3 - file routing

	// this.controller;
	this.req = req;
	this.res = res;

	// Because of performance
	// this.route = null;
	// this.timeout = null;
	// this.isCanceled = false;
	// this.isTransfer = false;
	// this.header = '';
	// this.error = null;
}

Subscribe.prototype.success = function() {
	var self = this;

	self.timeout && clearTimeout(self.timeout);
	self.timeout = null;
	self.isCanceled = true;

	if (self.controller && self.controller.res) {
		self.controller.res.controller = null;
		self.controller = null;
	}

	return self;
};

Subscribe.prototype.file = function() {
	var self = this;
	self.req.on('end', () => self.doEndfile(this));
	self.req.resume();
	return self;
};

/**
 * Process MULTIPART (uploaded files)
 * @param {String} header Content-Type header.
 * @return {FrameworkSubscribe}
 */
Subscribe.prototype.multipart = function(header) {

	var self = this;
	var req = self.req;

	framework.stats.request.upload++;
	self.route = framework.lookup(req, req.uri.pathname, req.flags, 0);
	self.header = header;

	if (self.route) {
		framework.path.verify('temp');
		framework_internal.parseMULTIPART(req, header, self.route, framework.config['directory-temp'], self);
		return self;
	}

	framework._request_stats(false, false);
	framework.stats.request.blocked++;
	self.res.writeHead(403);
	self.res.end();
	return self;
};

Subscribe.prototype.urlencoded = function() {

	var self = this;
	self.route = framework.lookup(self.req, self.req.uri.pathname, self.req.flags, 0);

	if (self.route) {
		self.req.buffer_has = true;
		self.req.buffer_exceeded = false;
		self.req.on('data', (chunk) => self.doParsepost(chunk));
		self.end();
		return self;
	}

	framework.stats.request.blocked++;
	framework._request_stats(false, false);
	self.res.writeHead(403);
	self.res.end();
	framework.emit('request-end', self.req, self.res);
	self.req.clear(true);

	return self;
};

Subscribe.prototype.end = function() {
	var self = this;
	self.req.on('end', () => self.doEnd());
	self.req.resume();
};

/**
 * Execute controller
 * @private
 * @param {Number} status Default HTTP status.
 * @return {FrameworkSubscribe}
 */
Subscribe.prototype.execute = function(status, isError) {

	var self = this;
	var route = self.route;
	var req = self.req;
	var res = self.res;

	if (isError || !route) {
		framework.stats.response['error' + status]++;
		status !== 500 && framework.emit('error' + status, req, res, self.exception);
	}

	if (!route) {

		if (!status)
			status = 404;

		if (status === 400 && self.exception instanceof framework_builders.ErrorBuilder) {
			req.$language && self.exception.setResource(req.$language);
			framework.responseContent(req, res, 200, self.exception.output(), self.exception.contentType, framework.config['allow-gzip']);
			return self;
		}

		framework.responseContent(req, res, status, framework_utils.httpStatus(status) + prepare_error(self.exception), CONTENTTYPE_TEXTPLAIN, framework.config['allow-gzip']);
		return self;
	}

	var name = route.controller;

	if (route.isMOBILE_VARY)
		req.$mobile = true;

	if (route.currentViewDirectory === undefined)
		route.currentViewDirectory = name && name[0] !== '#' && name !== 'default' && name !== 'unknown' ? '/' + name + '/' : '';

	var controller = new Controller(name, req, res, self, route.currentViewDirectory);

	controller.isTransfer = self.isTransfer;
	controller.exception = self.exception;
	self.controller = controller;

	if (!self.isCanceled && route.timeout) {
		self.timeout && clearTimeout(self.timeout);
		self.timeout = setTimeout(function() {
			self.controller && self.controller.precache && self.controller.precache(null, null, null);
			self.doCancel();
		}, route.timeout);
	}

	route.isDELAY && self.res.writeContinue();
	if (self.isSchema)
		req.body.$$controller = controller;

	if (!framework._length_middleware || !route.middleware)
		self.doExecute();
	else
		async_middleware(0, req, res, route.middleware, () => self.doExecute(), route.options, controller);
};

Subscribe.prototype.prepare = function(flags, url) {

	var self = this;
	var req = self.req;
	var res = self.res;
	var auth = framework.onAuthorize;

	if (auth) {
		var length = flags.length;
		auth(req, res, flags, function(isAuthorized, user) {

			var hasRoles = length !== flags.length;

			if (hasRoles)
				req.$flags += flags.slice(length).join('');

			if (typeof(isAuthorized) !== 'boolean') {
				user = isAuthorized;
				isAuthorized = !user;
			}

			req.isAuthorized = isAuthorized;
			self.doAuthorization(isAuthorized, user, hasRoles);
		});
		return self;
	}

	if (!self.route)
		self.route = framework.lookup(req, req.buffer_exceeded ? '#431' : url || req.uri.pathname, req.flags, 0);

	if (!self.route)
		self.route = framework.lookup(req, '#404', EMPTYARRAY, 0);

	var code = req.buffer_exceeded ? 431 : 404;

	if (!self.schema || !self.route)
		self.execute(code);
	else
		self.validate(self.route, () => self.execute(code));

	return self;
};

Subscribe.prototype.doExecute = function() {

	var self = this;
	var name = self.route.controller;
	var controller = self.controller;
	var req = self.req;

	try {

		if (framework.onTheme)
			controller.themeName = framework.onTheme(controller);

		if (controller.isCanceled)
			return self;

		framework.emit('controller', controller, name, self.route.options);

		if (controller.isCanceled)
			return self;

		if (self.route.isCACHE && !framework.temporary.other[req.uri.pathname])
			framework.temporary.other[req.uri.pathname] = req.path;

		if (self.route.isGENERATOR)
			async.call(controller, self.route.execute, true)(controller, framework_internal.routeParam(self.route.param.length ? req.split : req.path, self.route));
		else
			self.route.execute.apply(controller, framework_internal.routeParam(self.route.param.length ? req.split : req.path, self.route));

	} catch (err) {
		controller = null;
		framework.error(err, name, req.uri);
		self.exception = err;
		self.route = framework.lookup(req, '#500', EMPTYARRAY, 0);
		self.execute(500, true);
	}

	return self;
};

Subscribe.prototype.doAuthorization = function(isLogged, user, roles) {

	var self = this;
	var req = self.req;
	var membertype = isLogged ? 1 : 2;

	req.$flags += membertype

	if (user)
		req.user = user;

	if (self.route && self.route.isUNIQUE && !roles && (!self.route.MEMBER || self.route.MEMBER === membertype)) {
		if (self.schema)
			self.validate(self.route, () => self.execute(code));
		else
			self.execute(req.buffer_exceeded ? 431 : 401, true);
		return;
	}

	var route = framework.lookup(req, req.buffer_exceeded ? '#431' : req.uri.pathname, req.flags, req.buffer_exceeded ? 0 : membertype);
	var status = req.$isAuthorized ? 404 : 401;
	var code = req.buffer_exceeded ? 431 : status;

	if (!route)
		route = framework.lookup(req, '#' + status, EMPTYARRAY, 0);

	self.route = route;

	if (!self.route || !self.schema)
		self.execute(code);
	else
		self.validate(self.route, () => self.execute(code));

	return self;
};

Subscribe.prototype.doEnd = function() {

	var self = this;
	var req = self.req;
	var res = self.res;
	var route = self.route;

	if (req.buffer_exceeded) {
		route = framework.lookup(req, '#431', EMPTYARRAY, 0);
		req.buffer_data = null;

		if (!route) {
			framework.response431(req, res);
			return self;
		}

		self.route = route;
		self.execute(431, true);
		return self;
	}

	if (req.buffer_data && (!route || !route.isBINARY))
		req.buffer_data = req.buffer_data.toString(ENCODING);

	var schema;

	if (!req.buffer_data) {
		if (route && route.schema)
			self.schema = true;
		req.buffer_data = null;
		self.prepare(req.flags, req.uri.pathname);
		return self;
	}

	if (route.isXML) {

		if (req.$type !== 2) {
			self.route400('Invalid "Content-Type".');
			req.buffer_data = null;
			return self;
		}

		try {
			req.body = framework.onParseXML(req.buffer_data.trim());
			req.buffer_data = null;
			self.prepare(req.flags, req.uri.pathname);
		} catch (err) {
			F.error(err, null, req.uri);
			self.route500(err);
		}
		return self;
	}

	if (self.route.isRAW) {
		req.body = req.buffer_data;
		req.buffer_data = null;
		self.prepare(req.flags, req.uri.pathname);
		return self;
	}

	if (!req.$type) {
		req.buffer_data = null;
		self.route400('Invalid "Content-Type".');
		return self;
	}

	if (req.$type === 1) {
		try {
			req.body = framework.onParseJSON(req.buffer_data);
			req.buffer_data = null;
		} catch (e) {
			self.route400('Invalid JSON data.');
			return self;
		}
	} else
		req.body = framework.onParseQuery(req.buffer_data);

	if (self.route.schema)
		self.schema = true;

	req.buffer_data = null;
	self.prepare(req.flags, req.uri.pathname);
	return self;
};

Subscribe.prototype.validate = function(route, next) {
	var self = this;
	var req = self.req;
	self.schema = false;

	if (!route.schema || req.method === 'DELETE')
		return next();

	framework.onSchema(req, route.schema[0], route.schema[1], function(err, body) {

		if (err) {
			self.route400(err);
			next = null;
			return self;
		}

		req.body = body;
		self.isSchema = true;
		next();
	}, route.schema[2]);
};

Subscribe.prototype.route400 = function(problem) {
	var self = this;
	self.route = framework.lookup(self.req, '#400', EMPTYARRAY, 0);
	self.exception = problem;
	self.execute(400, true);
	return self;
};

Subscribe.prototype.route500 = function(problem) {
	var self = this;
	self.route = framework.lookup(self.req, '#500', EMPTYARRAY, 0);
	self.exception = problem;
	self.execute(500, true);
	return self;
};

Subscribe.prototype.doEndfile = function() {

	var self = this;
	var req = self.req;
	var res = self.res;

	if (!framework._length_files) {
		framework.responseStatic(self.req, self.res);
		return;
	}

	for (var i = 0; i < framework._length_files; i++) {

		var file = framework.routes.files[i];
		try {

			if (file.extensions && !file.extensions[self.req.extension])
				continue;

			if (file.url) {
				var skip = false;
				var length = file.url.length;

				if (!file.wildcard && !file.fixedfile && length !== req.path.length - 1)
					continue;

				for (var j = 0; j < length; j++) {
					if (file.url[j] === req.path[j])
						continue;
					skip = true;
					break;
				}

				if (skip)
					continue;

			} else if (file.onValidate && !file.onValidate.call(framework, req, res, true))
				continue;

			if (file.middleware)
				self.doEndfile_middleware(file);
			else
				file.execute.call(framework, req, res, false);

			return self;

		} catch (err) {
			framework.error(err, file.controller, req.uri);
			framework.responseContent(req, res, 500, '500 - internal server error', CONTENTTYPE_TEXTPLAIN, framework.config['allow-gzip']);
			return self;
		}
	}

	framework.responseStatic(self.req, self.res);
	return self;
};

/**
 * Executes a file middleware
 * @param {FileRoute} file
 * @return {Subscribe}
 */
Subscribe.prototype.doEndfile_middleware = function(file) {
	var self = this;
	async_middleware(0, self.req, self.res, file.middleware, function() {
		try {
			file.execute.call(framework, self.req, self.res, false);
		} catch (err) {
			framework.error(err, file.controller + ' :: ' + file.name, self.req.uri);
			framework.responseContent(self.req, self.res, 500, '500 - internal server error', CONTENTTYPE_TEXTPLAIN, framework.config['allow-gzip']);
		}
	}, file.options);
};

/**
 * Parse data from CHUNK
 * @param {Buffer} chunk
 * @return {FrameworkSubscribe}
 */
Subscribe.prototype.doParsepost = function(chunk) {

	var self = this;
	var req = self.req;

	if (req.buffer_exceeded)
		return self;

	if (!req.buffer_exceeded)
		req.buffer_data = Buffer.concat([req.buffer_data, chunk]);

	if (req.buffer_data.length < self.route.length)
		return self;

	req.buffer_exceeded = true;
	req.buffer_data = new Buffer('');

	return self;
};

Subscribe.prototype.doCancel = function() {
	var self = this;

	framework.stats.response.timeout++;
	clearTimeout(self.timeout);
	self.timeout = null;

	if (!self.controller)
		return;

	self.controller.isTimeout = true;
	self.controller.isCanceled = true;
	self.route = framework.lookup(self.req, '#408', EMPTYARRAY, 0);
	self.execute(408, true);
};

// *********************************************************************************
// =================================================================================
// Framework.Controller
// =================================================================================
// *********************************************************************************

/**
 * FrameworkController
 * @class
 * @param {String} name Controller name.
 * @param {Request} req
 * @param {Response} res
 * @param {FrameworkSubscribe} subscribe
 */
function Controller(name, req, res, subscribe, currentView) {

	this.subscribe = subscribe;
	this.name = name;
	// this.exception;

	// Sets the default language
	if (req) {
		this.language = req.$language;
		this.req = req;
	} else
		this.req = EMPTYREQUEST;

	// controller.type === 0 - classic
	// controller.type === 1 - server sent events
	this.type = 0;

	// this.layoutName = framework.config['default-layout'];
	// this.themeName = framework.config['default-theme'];

	this.status = 200;

	// this.isLayout = false;
	// this.isCanceled = false;
	// this.isTimeout = false;
	// this.isTransfer = false;

	this.isConnected = true;
	this.isController = true;
	this.repository = {};

	// render output
	// this.output = null;
	// this.outputPartial = null;
	// this.$model = null;

	this._currentView = currentView;

	if (res) {
		this.res = res;
		this.res.controller = this;
	} else
		this.res = EMPTYOBJECT;
}

Controller.prototype = {

	get schema() {
		return this.route.schema[0] === 'default' ? this.route.schema[1] : this.route.schema.join('/');
	},

	get workflow() {
		return this.route.schema_workflow;
	},

	get sseID() {
		return this.req.headers['last-event-id'] || null;
	},

	get route() {
		return this.subscribe.route;
	},

	get options() {
		return this.subscribe.route.options;
	},

	get flags() {
		return this.subscribe.route.flags;
	},

	get path() {
		return framework.path;
	},

	get get() {
		OBSOLETE('controller.get', 'Instead of controller.get use controller.query');
		return this.req.query;
	},

	get query() {
		return this.req.query;
	},

	get post() {
		OBSOLETE('controller.post', 'Instead of controller.post use controller.body');
		return this.req.body;
	},

	get body() {
		return this.req.body;
	},

	get files() {
		return this.req.files;
	},

	get subdomain() {
		return this.req.subdomain;
	},

	get ip() {
		return this.req.ip;
	},

	get xhr() {
		return this.req.xhr;
	},

	get url() {
		return framework_utils.path(this.req.uri.pathname);
	},

	get uri() {
		return this.req.uri;
	},

	get cache() {
		return framework.cache;
	},

	get config() {
		return framework.config;
	},

	get controllers() {
		return framework.controllers;
	},

	get isProxy() {
		return this.req.isProxy;
	},

	get isDebug() {
		return framework.config.debug;
	},

	get isTest() {
		return this.req.headers['x-assertion-testing'] === '1';
	},

	get isSecure() {
		return this.req.isSecure;
	},

	get secured() {
		return this.req.secured;
	},

	get session() {
		return this.req.session;
	},

	set session(value) {
		this.req.session = value;
	},

	get user() {
		return this.req.user;
	},

	get referrer() {
		return this.req.headers['referer'] || '';
	},

	set user(value) {
		this.req.user = value;
	},

	get mobile() {
		return this.req.mobile;
	},

	get robot() {
		return this.req.robot;
	},

	get viewname() {
		var name = this.req.path[this.req.path.length - 1];
		if (!name || name === '/')
			name = 'index';
		return name;
	}
};

// ======================================================
// PROTOTYPES
// ======================================================

// Schema operations

Controller.prototype.$get = Controller.prototype.$read = function(helper, callback) {
	this.getSchema().get(helper, callback, this);
	return this;
};

Controller.prototype.$query = function(helper, callback) {
	this.getSchema().query(helper, callback, this);
	return this;
};

Controller.prototype.$save = function(helper, callback) {
	var self = this;
	if (framework_builders.isSchema(self.body)) {
		self.body.$$controller = self;
		self.body.$save(helper, callback);
	} else {
		var model = self.getSchema().default();
		model.$$controller = self;
		model.$save(helper, callback);
	}
	return self;
};

Controller.prototype.$remove = function(helper, callback) {
	var self = this;
	self.getSchema().remove(helper, callback, self);
	return this;
};

Controller.prototype.$workflow = function(name, helper, callback) {
	var self = this;
	if (framework_builders.isSchema(self.body)) {
		self.body.$$controller = self;
		self.body.$workflow(name, helper, callback);
	} else
		self.getSchema().workflow2(name, helper, callback, self);
	return self;
};

Controller.prototype.$workflow2 = function(name, helper, callback) {
	var self = this;
	self.getSchema().workflow2(name, helper, callback, self);
	return self;
};

Controller.prototype.$hook = function(name, helper, callback) {
	var self = this;
	if (framework_builders.isSchema(self.body)) {
		self.body.$$controller = self;
		self.body.$hook(name, helper, callback);
	} else
		self.getSchema().hook2(name, helper, callback, self);
	return self;
};

Controller.prototype.$hook2 = function(name, helper, callback) {
	var self = this;
	self.getSchema().hook2(name, helper, callback, self);
	return self;
};

Controller.prototype.$transform = function(name, helper, callback) {
	var self = this;
	if (framework_builders.isSchema(self.body)) {
		self.body.$$controller = self;
		self.body.$transform(name, helper, callback);
	} else
		self.getSchema().transform2(name, helper, callback, self);
	return self;
};

Controller.prototype.$transform2 = function(name, helper, callback) {
	var self = this;
	self.getSchema().transform2(name, helper, callback, self);
	return self;
};

Controller.prototype.$operation = function(name, helper, callback) {
	var self = this;
	if (framework_builders.isSchema(self.body)) {
		self.body.$$controller = self;
		self.body.$operation(name, helper, callback);
	} else
		self.getSchema().operation2(name, helper, callback, self);
	return self;
};

Controller.prototype.$operation2 = function(name, helper, callback) {
	var self = this;
	self.getSchema().operation2(name, helper, callback, self);
	return self;
};

Controller.prototype.$exec = function(name, helper, callback) {
	var self = this;

	if (framework_builders.isSchema(self.body)) {
		self.body.$$controller = self;
		self.body.$exec(name, helper, callback);
		return self;
	}

	var tmp = self.getSchema().create();
	tmp.$$controller = self;
	tmp.$exec(name, helper, callback);
	return self;
};

Controller.prototype.$async = function(callback, index) {
	var self = this;

	if (framework_builders.isSchema(self.body)) {
		self.body.$$controller = self;
		return self.body.$async(callback, index);
	}

	var model = self.getSchema().default();
	model.$$controller = self;
	return model.$async(callback, index);
};

Controller.prototype.getSchema = function() {
	var route = this.subscribe.route;
	if (!route.schema || !route.schema[1])
		throw new Error('The controller\'s route does not define any schema.');
	var schema = GETSCHEMA(route.schema[0], route.schema[1]);
	if (!schema)
		throw new Error('Schema "{0}" does not exist.'.format(route.schema[1]));
	return schema;
};

/**
 * Reads / Writes cookie
 * @param {String} name
 * @param {String} value
 * @param {String/Date} expires
 * @param {Object} options
 * @return {String/Controller}
 */
Controller.prototype.cookie = function(name, value, expires, options) {
	var self = this;
	if (value === undefined)
		return self.req.cookie(name);
	self.res.cookie(name, value, expires, options);
	return self;
};

/**
 * Clears uploaded files
 * @return {Controller}
 */
Controller.prototype.clear = function() {
	var self = this;
	self.req.clear();
	return self;
};

/**
 * Translates text
 * @param {String} text
 * @return {String}
 */
Controller.prototype.translate = function(language, text) {

	if (!text) {
		text = language;
		language = this.language;
	}

	return framework.translate(language, text);
};

/**
 * Exec middleware
 * @param {String Array} names Middleware name.
 * @param {Object} options Custom options for middleware.
 * @param {Function} callback
 * @return {Controller}
 */
Controller.prototype.middleware = function(names, options, callback) {

	if (typeof(names) === 'string')
		names = [names];

	if (typeof(options) === 'function') {
		var tmp = callback;
		callback = options;
		options = tmp;
	}

	if (!options)
		options = EMPTYOBJECT;

	var self = this;
	async_middleware(0, self.req, self.res, names, () => callback && callback(), options, self);
	return self;
};

/**
 * Creates a pipe between the current request and target URL
 * @param {String} url
 * @param {Object} headers Optional, custom headers.
 * @param {Function(err)} callback Optional.
 * @return {Controller}
 */
Controller.prototype.pipe = function(url, headers, callback) {

	var self = this;

	if (typeof(headers) === 'function') {
		var tmp = callback;
		callback = headers;
		headers = tmp;
	}

	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	framework.responsePipe(self.req, self.res, url, headers, null, function() {
		self.subscribe.success();
		callback && callback();
	});

	return self;
};

Controller.prototype.encrypt = function() {
	return framework.encrypt.apply(framework, arguments);
};

Controller.prototype.decrypt = function() {
	return framework.decrypt.apply(framework, arguments);
};

/**
 * Creates a hash (alias for F.hash())
 * @return {Controller}
 */
Controller.prototype.hash = function() {
	return framework.hash.apply(framework, arguments);
};

/**
 * Compare DateTime
 * @param {String} type Compare type ('<', '>', '=', '>=', '<=')
 * @param {String or Date} d1 String (yyyy-MM-dd [HH:mm:ss]), (optional) - default current date
 * @param {String or Date} d2 String (yyyy-MM-dd [HH:mm:ss])
 * @return {Boolean}
 */
Controller.prototype.date = function(type, d1, d2) {

	if (d2 === undefined) {
		d2 = d1;
		d1 = framework.datetime;
	}

	var beg = typeof(d1) === 'string' ? d1.parseDate() : d1;
	var end = typeof(d2) === 'string' ? d2.parseDate() : d2;
	var r = beg.compare(end);

	switch (type) {
		case '>':
			return r === 1;
		case '>=':
		case '=>':
			return r === 1 || r === 0;
		case '<':
			return r === -1;
		case '<=':
		case '=<':
			return r === -1 || r === 0;
		case '=':
			return r === 0;
	}

	return true;
};

/**
 * Sets a response header
 * @param {String} name
 * @param {String} value
 * @return {Controller}
 */
Controller.prototype.header = function(name, value) {
	this.res.setHeader(name, value);
	return this;
};

/**
 * Gets a hostname
 * @param {String} path
 * @return {Controller}
 */
Controller.prototype.host = function(path) {
	return this.req.hostname(path);
};

Controller.prototype.hostname = function(path) {
	return this.req.hostname(path);
};

Controller.prototype.resource = function(name, key) {
	return framework.resource(name, key);
};

/**
 * Error caller
 * @param {Error/String} err
 * @return {Controller/Function}
 */
Controller.prototype.error = function(err) {
	var self = this;

	// Custom errors
	if (err instanceof ErrorBuilder) {
		self.content(err);
		return self;
	}

	var result = framework.error(typeof(err) === 'string' ? new Error(err) : err, self.name, self.uri);

	if (err === undefined)
		return result;

	if (self.subscribe) {
		self.subscribe.exception = err;
		self.exception = err;
	}

	return self;
};

Controller.prototype.invalid = function(status) {
	var self = this;

	if (status)
		self.status = status;

	var builder = new ErrorBuilder();
	setImmediate(n => self.content(builder));
	return builder;
};

/**
 * Registers a new problem
 * @param {String} message
 * @return {Controller}
 */
Controller.prototype.wtf = Controller.prototype.problem = function(message) {
	framework.problem(message, this.name, this.uri, this.ip);
	return this;
};

/**
 * Registers a new change
 * @param {String} message
 * @return {Controller}
 */
Controller.prototype.change = function(message) {
	framework.change(message, this.name, this.uri, this.ip);
	return this;
};

/**
 * Trace
 * @param {String} message
 * @return {Controller}
 */
Controller.prototype.trace = function(message) {
	framework.trace(message, this.name, this.uri, this.ip);
	return this;
};

/**
 * Transfer to new route
 * @param {String} url Relative URL.
 * @param {String Array} flags Route flags (optional).
 * @return {Boolean}
 */
Controller.prototype.transfer = function(url, flags) {

	var self = this;
	var length = framework.routes.web.length;
	var path = framework_internal.routeSplit(url.trim());

	var isSystem = url[0] === '#';
	var noFlag = !flags || flags.length === 0 ? true : false;
	var selected = null;

	self.req.$isAuthorized = true;

	for (var i = 0; i < length; i++) {

		var route = framework.routes.web[i];

		if (route.isASTERIX) {
			if (!framework_internal.routeCompare(path, route.url, isSystem, true))
				continue;
		} else {
			if (!framework_internal.routeCompare(path, route.url, isSystem))
				continue;
		}

		if (noFlag) {
			selected = route;
			break;
		}

		if (route.flags && route.flags.length) {
			var result = framework_internal.routeCompareFlags(route.flags, flags, true);
			if (result === -1)
				self.req.$isAuthorized = false;
			if (result < 1)
				continue;
		}

		selected = route;
		break;
	}


	if (!selected)
		return false;

	self.cancel();
	self.req.path = EMPTYARRAY;
	self.subscribe.isTransfer = true;
	self.subscribe.success();
	self.subscribe.route = selected;
	self.subscribe.execute(404);
	return true;

};

/**
 * Cancels controller executions
 * @return {Controller}
 */
Controller.prototype.cancel = function() {
	this.isCanceled = true;
	return this;
};

Controller.prototype.log = function() {
	framework.log.apply(framework, arguments);
	return this;
};

Controller.prototype.logger = function() {
	framework.logger.apply(framework, arguments);
	return this;
};

Controller.prototype.meta = function() {
	var self = this;

	if (arguments[0])
		self.repository[REPOSITORY_META_TITLE] = arguments[0];

	if (arguments[1])
		self.repository[REPOSITORY_META_DESCRIPTION] = arguments[1];

	if (arguments[2] && arguments[2].length)
		self.repository[REPOSITORY_META_KEYWORDS] = arguments[2] instanceof Array ? arguments[2].join(', ') : arguments[2];

	if (arguments[3])
		self.repository[REPOSITORY_META_IMAGE] = arguments[3];

	return self;
};

Controller.prototype.$dns = function(value) {

	var builder = '';
	var length = arguments.length;

	for (var i = 0; i < length; i++)
		builder += '<link rel="dns-prefetch" href="' + this._preparehostname(arguments[i]) + '" />';

	this.head(builder);
	return '';
};

Controller.prototype.$prefetch = function() {

	var builder = '';
	var length = arguments.length;

	for (var i = 0; i < length; i++)
		builder += '<link rel="prefetch" href="' + this._preparehostname(arguments[i]) + '" />';

	this.head(builder);
	return '';
};

Controller.prototype.$prerender = function(value) {

	var builder = '';
	var length = arguments.length;

	for (var i = 0; i < length; i++)
		builder += '<link rel="prerender" href="' + this._preparehostname(arguments[i]) + '" />';

	this.head(builder);
	return '';
};

Controller.prototype.$next = function(value) {
	this.head('<link rel="next" href="' + this._preparehostname(value) + '" />');
	return '';
};

Controller.prototype.$prev = function(value) {
	this.head('<link rel="prev" href="' + this._preparehostname(value) + '" />');
	return '';
};

Controller.prototype.$canonical = function(value) {
	this.head('<link rel="canonical" href="' + this._preparehostname(value) + '" />');
	return '';
};

Controller.prototype.$meta = function() {
	var self = this;

	if (arguments.length) {
		self.meta.apply(self, arguments);
		return '';
	}

	framework.emit('controller-render-meta', self);
	var repository = self.repository;
	return framework.onMeta.call(self, repository[REPOSITORY_META_TITLE], repository[REPOSITORY_META_DESCRIPTION], repository[REPOSITORY_META_KEYWORDS], repository[REPOSITORY_META_IMAGE]);
};

Controller.prototype.title = function(value) {
	this.$title(value);
	return this;
};

Controller.prototype.description = function(value) {
	this.$description(value);
	return this;
};

Controller.prototype.keywords = function(value) {
	this.$keywords(value);
	return this;
};

Controller.prototype.author = function(value) {
	this.$author(value);
	return this;
};

Controller.prototype.$title = function(value) {
	if (value)
		this.repository[REPOSITORY_META_TITLE] = value;
	return '';
};

Controller.prototype.$title2 = function(value) {
	var current = this.repository[REPOSITORY_META_TITLE];
	if (value)
		this.repository[REPOSITORY_META_TITLE] = (current ? current : '') + value;
	return '';
};

Controller.prototype.$description = function(value) {
	if (value)
		this.repository[REPOSITORY_META_DESCRIPTION] = value;
	return '';
};

Controller.prototype.$keywords = function(value) {
	if (value && value.length)
		this.repository[REPOSITORY_META_KEYWORDS] = value instanceof Array ? value.join(', ') : value;
	return '';
};

Controller.prototype.$author = function(value) {
	if (value)
		this.repository[REPOSITORY_META_AUTHOR] = value;
	return '';
};

Controller.prototype.sitemap_navigation = function(name, language) {
	return framework.sitemap_navigation(name, language || this.language);
};

Controller.prototype.sitemap_url = function(name, a, b, c, d, e, f) {
	if (!name)
		name = this.repository[REPOSITORY_SITEMAP];
	var item = F.sitemap(name, true, this.language);
	return item ? item.url.format(a, b, c, d, e, f) : '';
};

Controller.prototype.sitemap_name = function(name, a, b, c, d, e, f) {
	if (!name)
		name = this.repository[REPOSITORY_SITEMAP];
	var item = F.sitemap(name, true, this.language);
	return item ? item.name.format(a, b, c, d, e, f) : '';
};

Controller.prototype.sitemap_change = function(name, type, a, b, c, d, e, f) {

	var self = this;
	var sitemap = self.repository[REPOSITORY_SITEMAP];

	if (!sitemap)
		sitemap = self.sitemap(name);

	if (!sitemap.$cloned) {
		sitemap = framework_utils.clone(sitemap);
		sitemap.$cloned = true;
		self.repository[REPOSITORY_SITEMAP] = sitemap;
	}

	var isFn = typeof(a) === 'function';

	for (var i = 0, length = sitemap.length; i < length; i++) {

		var item = sitemap[i];
		if (item.id !== name)
			continue;

		var tmp = item[type];

		if (isFn)
			item[type] = a(item[type]);
		else if (type === 'name')
			item[type] = item.formatName ? item[type].format(a, b, c, d, e, f) : a;
		else if (type === 'url')
			item[type] = item.formatUrl ? item[type].format(a, b, c, d, e, f) : a;
		else
			item[type] = a;

		if (type === 'name' && self.repository[REPOSITORY_META_TITLE] === tmp)
			self.repository[REPOSITORY_META_TITLE] = item[type];

		return self;
	}

	return self;
};

Controller.prototype.sitemap_replace = function(name, title, url) {
	var self = this;
	var sitemap = self.repository[REPOSITORY_SITEMAP];
	if (!sitemap)
		sitemap = self.sitemap(name);

	if (!sitemap.$cloned) {
		sitemap = framework_utils.clone(sitemap);
		sitemap.$cloned = true;
		self.repository[REPOSITORY_SITEMAP] = sitemap;
	}

	for (var i = 0, length = sitemap.length; i < length; i++) {
		var item = sitemap[i];
		if (item.id !== name)
			continue;
		var is = self.repository[REPOSITORY_META_TITLE] === item.name;
		item.name = typeof(title) === 'function' ? title(item.name) : item.formatName ? item.name.format(title) : title;
		item.url = typeof(url) === 'function' ? url(item.url) : item.formatUrl ? item.url.format(url) : url;
		if (is)
			self.repository[REPOSITORY_META_TITLE] = item.name;
		return self;
	}

	return self;
};

Controller.prototype.$sitemap_change = function(name, type, value, format) {
	this.sitemap_change.apply(this, arguments);
	return '';
};

Controller.prototype.$sitemap_replace = function(name, title, url, format) {
	this.sitemap_replace.apply(this, arguments);
	return '';
};

Controller.prototype.sitemap = function(name) {
	var self = this;
	var sitemap;

	if (name instanceof Array) {
		self.repository[REPOSITORY_SITEMAP] = name;
		return self;
	}

	if (!name) {
		sitemap = self.repository[REPOSITORY_SITEMAP];
		return sitemap ? sitemap : self.repository.sitemap || EMPTYARRAY;
	}

	sitemap = framework.sitemap(name, false, self.language);
	self.repository[REPOSITORY_SITEMAP] = sitemap;
	if (!self.repository[REPOSITORY_META_TITLE]) {
		sitemap = sitemap.last();
		if (sitemap)
			self.repository[REPOSITORY_META_TITLE] = sitemap.name;
	}

	return self.repository[REPOSITORY_SITEMAP];
};

Controller.prototype.$sitemap = function(name) {
	var self = this;
	self.sitemap.apply(self, arguments);
	return '';
};

Controller.prototype.module = function(name) {
	return framework.module(name);
};

Controller.prototype.layout = function(name) {
	var self = this;
	self.layoutName = name;
	return self;
};

Controller.prototype.theme = function(name) {
	var self = this;
	self.themeName = name;
	return self;
};

/**
 * Layout setter for views
 * @param {String} name Layout name
 * @return {String}
 */
Controller.prototype.$layout = function(name) {
	var self = this;
	self.layoutName = name;
	return '';
};

Controller.prototype.model = function(name) {
	return framework.model(name);
};

/**
 * Send e-mail
 * @param {String or Array} address E-mail address.
 * @param {String} subject E-mail subject.
 * @param {String} view View name.
 * @param {Object} model Optional.
 * @param {Function(err)} callback Optional.
 * @return {MailMessage}
 */
Controller.prototype.mail = function(address, subject, view, model, callback) {

	if (typeof(model) === 'function') {
		callback = model;
		model = null;
	}

	var self = this;

	if (typeof(self.language) === 'string')
		subject = subject.indexOf('@(') === -1 ? framework.translate(self.language, subject) : framework.translator(self.language, subject);

	// Backup layout
	var layoutName = self.layoutName;
	var body = self.view(view, model, true);
	self.layoutName = layoutName;
	return framework.onMail(address, subject, body, callback);
};

/*
	Check if ETag or Last Modified has modified
	@compare {String or Date}
	@strict {Boolean} :: if strict then use equal date else use great than date (default: false)

	if @compare === {String} compare if-none-match
	if @compare === {Date} compare if-modified-since

	return {Boolean};
*/
Controller.prototype.notModified = function(compare, strict) {
	return framework.notModified(this.req, this.res, compare, strict);
};

/*
	Set last modified header or Etag
	@value {String or Date}

	if @value === {String} set ETag
	if @value === {Date} set LastModified

	return {Controller};
*/
Controller.prototype.setModified = function(value) {
	framework.setModified(this.req, this.res, value);
	return this;
};

/**
 * Sets expire headers
 * @param {Date} date
 */
Controller.prototype.setExpires = function(date) {
	date && this.res.setHeader('Expires', date.toUTCString());
	return this;
};

Controller.prototype.$template = function(name, model, expire, key) {
	return this.$viewToggle(true, name, model, expire, key);
};

Controller.prototype.$templateToggle = function(visible, name, model, expire, key) {
	return this.$viewToggle(visible, name, model, expire, key);
};

Controller.prototype.$view = function(name, model, expire, key) {
	return this.$viewToggle(true, name, model, expire, key);
};

Controller.prototype.$viewCompile = function(body, model) {
	var self = this;
	var layout = self.layoutName;
	self.layoutName = '';
	var value = self.viewCompile(body, model, null, true);
	self.layoutName = layout;
	return value || '';
};

Controller.prototype.$viewToggle = function(visible, name, model, expire, key) {

	if (!visible)
		return '';

	var self = this;
	var cache;

	if (expire) {
		cache = '$view.' + name + '.' + (key || '');
		var output = self.cache.read2(cache);
		if (output)
			return output;
	}

	var layout = self.layoutName;
	self.layoutName = '';
	var value = self.view(name, model, null, true);
	self.layoutName = layout;

	if (!value)
		return '';

	expire && self.cache.add(cache, value, expire);
	return value;
};

/**
 * Adds a place into the places.
 * @param {String} name A place name.
 * @param {String} arg1 A content 1, optional
 * @param {String} arg2 A content 2, optional
 * @param {String} argN A content 2, optional
 * @return {String/Controller} String is returned when the method contains only `name` argument
 */
Controller.prototype.place = function(name) {

	var key = REPOSITORY_PLACE + '_' + name;
	var length = arguments.length;

	if (length === 1)
		return this.repository[key] || '';

	var output = '';
	for (var i = 1; i < length; i++) {
		var val = arguments[i];

		if (val)
			val = val.toString();
		else
			val = '';

		if (val.endsWith('.js'))
			val = '<script src="' + val + '"></script>';

		output += val;
	}

	this.repository[key] = (this.repository[key] || '') + output;
	return this;
};

/**
 * Adds a content into the section
 * @param {String} name A section name.
 * @param {String} value A content.
 * @param {Boolean} replace Optional, default `false` otherwise concats contents.
 * @return {String/Controller} String is returned when the method contains only `name` argument
 */
Controller.prototype.section = function(name, value, replace) {

	var key = '$section_' + name;

	if (value === undefined)
		return this.repository[key];

	if (replace) {
		this.repository[key] = value;
		return this;
	}

	if (this.repository[key])
		this.repository[key] += value;
	else
		this.repository[key] = value;

	return this;
};

Controller.prototype.$place = function() {
	var self = this;
	if (arguments.length === 1)
		return self.place.apply(self, arguments);
	self.place.apply(self, arguments);
	return '';
};

Controller.prototype.$url = function(host) {
	return host ? this.req.hostname(this.url) : this.url;
};

Controller.prototype.$helper = function(name) {
	return this.helper.apply(this, arguments);
};

function querystring_encode(value, def) {
	return value != null ? value instanceof Date ? encodeURIComponent(value.format()) : typeof(value) === 'string' ? encodeURIComponent(value) : value.toString() : def || '';
}

// @{href({ key1: 1, key2: 2 })}
// @{href('key', 'value')}
Controller.prototype.href = function(key, value) {
	var self = this;

	if (!arguments.length) {
		var val = Qs.stringify(self.query);
		return val ? '?' + val : '';
	}

	var type = typeof(key);
	var obj;

	if (type === 'string') {

		var cachekey = '$href' + key;
		var str = self[cachekey] || '';

		if (!str) {

			obj = framework_utils.copy(self.query);
			for (var i = 2; i < arguments.length; i++)
				obj[arguments[i]] = undefined;

			obj[key] = '\0';

			var arr = Object.keys(obj);
			for (var i = 0, length = arr.length; i < length; i++) {
				var val = obj[arr[i]];
				if (val !== undefined)
					str += (str ? '&' : '') + arr[i] + '=' + (key === arr[i] ? '\0' : querystring_encode(val));
			}
			self[cachekey] = str;
		}

		str = str.replace('\0', querystring_encode(value, self.query[key]));

		for (var i = 2; i < arguments.length; i++) {
			var beg = str.indexOf(arguments[i] + '=');
			if (beg === -1)
				continue;
			var end = str.indexOf('&', beg);
			str = str.substring(0, beg) + str.substring(end === -1 ? str.length : end + 1);
		}

		return str ? '?' + str : '';
	}

	if (value) {
		obj = framework_utils.copy(self.query);
		framework_utils.extend(obj, value);
	}

	if (value != null)
		obj[key] = value;

	obj = Qs.stringify(obj);

	if (value === undefined && type === 'string')
		obj += (obj ? '&' : '') + key;

	return self.url + (obj ? '?' + obj : '');
};

Controller.prototype.$checked = function(bool, charBeg, charEnd) {
	return this.$isValue(bool, charBeg, charEnd, 'checked="checked"');
};

Controller.prototype.$disabled = function(bool, charBeg, charEnd) {
	return this.$isValue(bool, charBeg, charEnd, 'disabled="disabled"');
};

Controller.prototype.$selected = function(bool, charBeg, charEnd) {
	return this.$isValue(bool, charBeg, charEnd, 'selected="selected"');
};

/**
 * Fake function for assign value
 * @private
 * @param {Object} value Value to eval.
 * return {String} Returns empty string.
 */
Controller.prototype.$set = function(value) {
	return '';
};

Controller.prototype.$readonly = function(bool, charBeg, charEnd) {
	return this.$isValue(bool, charBeg, charEnd, 'readonly="readonly"');
};

Controller.prototype.$header = function(name, value) {
	this.header(name, value);
	return '';
};

Controller.prototype.$text = function(model, name, attr) {
	return this.$input(model, 'text', name, attr);
};

Controller.prototype.$password = function(model, name, attr) {
	return this.$input(model, 'password', name, attr);
};

Controller.prototype.$hidden = function(model, name, attr) {
	return this.$input(model, 'hidden', name, attr);
};

Controller.prototype.$radio = function(model, name, value, attr) {

	if (typeof(attr) === 'string') {
		var label = attr;
		attr = SINGLETON('!$radio');
		attr.label = label;
	}

	attr.value = value;
	return this.$input(model, 'radio', name, attr);
};

Controller.prototype.$checkbox = function(model, name, attr) {

	if (typeof(attr) === 'string') {
		var label = attr;
		attr = SINGLETON('!$checkbox');
		attr.label = label;
	}

	return this.$input(model, 'checkbox', name, attr);
};

Controller.prototype.$textarea = function(model, name, attr) {

	var builder = '<textarea';

	if (typeof(attr) !== 'object')
		attr = EMPTYOBJECT;

	builder += ' name="' + name + '" id="' + (attr.id || name) + ATTR_END;

	for (var key in attr) {
		switch (key) {
			case 'name':
			case 'id':
				break;
			case 'required':
			case 'disabled':
			case 'readonly':
			case 'value':
				builder += ' ' + key + '="' + key + ATTR_END;
				break;
			default:
				builder += ' ' + key + '="' + attr[key].toString().encode() + ATTR_END;
				break;
		}
	}

	if (model === undefined)
		return builder + '></textarea>';

	return builder + '>' + ((model[name] || attr.value) || '') + '</textarea>';
};

Controller.prototype.$input = function(model, type, name, attr) {

	var builder = ['<input'];

	if (typeof(attr) !== 'object')
		attr = EMPTYOBJECT;

	var val = attr.value || '';

	builder += ' type="' + type + ATTR_END;

	if (type === 'radio')
		builder += ' name="' + name + ATTR_END;
	else
		builder += ' name="' + name + '" id="' + (attr.id || name) + ATTR_END;

	if (attr.autocomplete) {
		if (attr.autocomplete === true || attr.autocomplete === 'on')
			builder += ' autocomplete="on"';
		else
			builder += ' autocomplete="off"';
	}

	for (var key in attr) {
		switch (key) {
			case 'name':
			case 'id':
			case 'type':
			case 'autocomplete':
			case 'checked':
			case 'value':
			case 'label':
				break;
			case 'required':
			case 'disabled':
			case 'readonly':
			case 'autofocus':
				builder += ' ' + key + '="' + key + ATTR_END;
				break;
			default:
				builder += ' ' + key + '="' + attr[key].toString().encode() + ATTR_END;
				break;
		}
	}

	var value = '';

	if (model !== undefined) {
		value = model[name];

		if (type === 'checkbox') {
			if (value == '1' || value === 'true' || value === true || value === 'on')
				builder += ' checked="checked"';
			value = val || '1';
		}

		if (type === 'radio') {

			val = (val || '').toString();

			if (value.toString() === val)
				builder += ' checked="checked"';

			value = val || '';
		}
	}

	if (value !== undefined)
		builder += ' value="' + (value || '').toString().encode() + ATTR_END;
	else
		builder += ' value="' + (attr.value || '').toString().encode() + ATTR_END;

	builder += ' />';
	return attr.label ? ('<label>' + builder + ' <span>' + attr.label + '</span></label>') : builder;
};

Controller.prototype._preparehostname = function(value) {
	if (!value)
		return value;
	var tmp = value.substring(0, 5);
	return tmp !== 'http:' && tmp !== 'https' && (tmp[0] !== '/' || tmp[1] !== '/') ? this.host(value) : value;
};

Controller.prototype.head = function() {

	var self = this;
	var length = arguments.length;

	if (!length) {
		framework.emit('controller-render-head', self);
		var author = self.repository[REPOSITORY_META_AUTHOR] || self.config.author;
		return (author ? '<meta name="author" content="' + author + '" />' : '') + (self.repository[REPOSITORY_HEAD] || '');
	}

	var header = (self.repository[REPOSITORY_HEAD] || '');
	var output = '';

	for (var i = 0; i < length; i++) {

		var val = arguments[i];
		if (val[0] === '<') {
			output += val;
			continue;
		}

		var tmp = val.substring(0, 7);
		var is = (tmp[0] !== '/' && tmp[1] !== '/') && tmp !== 'http://' && tmp !== 'https:/';

		if (val.endsWith('.css', true))
			output += '<link type="text/css" rel="stylesheet" href="' + (is ? self.routeStyle(val) : val) + '" />';
		else if (val.endsWith('.js', true) !== -1)
			output += '<script src="' + (is ? self.routeScript(val) : val) + '"></script>';
	}

	header += output;
	self.repository[REPOSITORY_HEAD] = header;
	return self;
};

Controller.prototype.$head = function() {
	this.head.apply(this, arguments);
	return '';
};

Controller.prototype.$isValue = function(bool, charBeg, charEnd, value) {
	if (!bool)
		return '';
	charBeg = charBeg || ' ';
	charEnd = charEnd || '';
	return charBeg + value + charEnd;
};

Controller.prototype.$modified = function(value) {

	var self = this;
	var type = typeof(value);
	var date;

	if (type === 'number') {
		date = new Date(value);
	} else if (type === 'string') {

		var d = value.split(' ');

		date = d[0].split('-');
		var time = (d[1] || '').split(':');

		var year = framework_utils.parseInt(date[0] || '');
		var month = framework_utils.parseInt(date[1] || '') - 1;
		var day = framework_utils.parseInt(date[2] || '') - 1;

		if (month < 0)
			month = 0;

		if (day < 0)
			day = 0;

		var hour = framework_utils.parseInt(time[0] || '');
		var minute = framework_utils.parseInt(time[1] || '');
		var second = framework_utils.parseInt(time[2] || '');

		date = new Date(year, month, day, hour, minute, second, 0);
	} else if (framework_utils.isDate(value))
		date = value;

	date && self.setModified(date);
	return '';
};

Controller.prototype.$etag = function(value) {
	this.setModified(value);
	return '';
};

Controller.prototype.$options = function(arr, selected, name, value) {

	var type = typeof(arr);
	if (!arr)
		return '';

	var isObject = false;
	var tmp = null;

	if (!(arr instanceof Array) && type === 'object') {
		isObject = true;
		tmp = arr;
		arr = Object.keys(arr);
	}

	if (!framework_utils.isArray(arr))
		arr = [arr];

	selected = selected || '';

	var options = '';

	if (!isObject) {
		if (value === undefined)
			value = value || name || 'value';
		if (name === undefined)
			name = name || 'name';
	}

	var isSelected = false;
	var length = 0;

	length = arr.length;

	for (var i = 0; i < length; i++) {

		var o = arr[i];
		var type = typeof(o);
		var text = '';
		var val = '';
		var sel = false;

		if (isObject) {
			if (name === true) {
				val = tmp[o];
				text = o;
				if (!value)
					value = '';
			} else {
				val = o;
				text = tmp[o];
				if (!text)
					text = '';
			}

		} else if (type === 'object') {

			text = (o[name] || '');
			val = (o[value] || '');

			if (typeof(text) === 'function')
				text = text(i);

			if (typeof(val) === 'function')
				val = val(i, text);

		} else {
			text = o;
			val = o;
		}

		if (!isSelected) {
			sel = val == selected;
			isSelected = sel;
		}

		options += '<option value="' + val.toString().encode() + '"' + (sel ? ' selected="selected"' : '') + '>' + text.toString().encode() + '</option>';
	}

	return options;
};

/**
 * Append <script> TAG
 * @private
 * @return {String}
 */
Controller.prototype.$script = function() {
	return arguments.length === 1 ? this.$js(arguments[0]) : this.$js.apply(this, arguments);
};

/**
 * Append <script> TAG
 * @private
 * @return {String}
 */
Controller.prototype.$js = function() {
	var self = this;
	var builder = '';
	for (var i = 0; i < arguments.length; i++)
		builder += self.routeScript(arguments[i], true);
	return builder;
};

/**
 * Append <script> or <style> TAG
 * @private
 * @return {String}
 */
Controller.prototype.$absolute = function(files, base) {

	var self = this;
	var builder;
	var ftype;

	if (!base)
		base = self.hostname();

	if (files instanceof Array) {

		ftype = framework_utils.getExtension(files[0]);
		builder = '';

		for (var i = 0, length = files.length; i < length; i++) {
			switch (ftype) {
				case 'js':
					builder += self.routeScript(files[i], true, base);
					break;
				case 'css':
					builder += self.routeStyle(files[i], true, base);
					break;
				default:
					builder += self.routeStatic(files[i], base);
					break;
			}
		}

		return builder;
	}

	ftype = framework_utils.getExtension(files);

	switch (ftype) {
		case 'js':
			return self.routeScript(files, true, base);
		case 'css':
			return self.routeStyle(files, true, base);
	}

	return self.routeStatic(files, base);
};

Controller.prototype.$import = function() {

	var self = this;
	var builder = '';

	for (var i = 0; i < arguments.length; i++) {
		var filename = arguments[i];

		if (filename === 'head') {
			builder += self.head();
			continue;
		}

		if (filename === 'meta') {
			builder += self.$meta();
			continue;
		}

		var extension = filename.substring(filename.lastIndexOf('.'));
		var tag = filename[0] !== '!';
		if (!tag)
			filename = filename.substring(1);

		if (filename[0] === '#')
			extension = '.js';

		switch (extension) {
			case '.js':
				builder += self.routeScript(filename, tag);
				break;
			case '.css':
				builder += self.routeStyle(filename, tag);
				break;
			case '.ico':
				builder += self.$favicon(filename);
				break;
			case '.mp4':
			case '.avi':
			case '.ogv':
			case '.webm':
			case '.mov':
			case '.mpg':
			case '.mpe':
			case '.mpeg':
			case '.m4v':
				builder += self.routeVideo(filename);
				break;
			case '.jpg':
			case '.gif':
			case '.png':
			case '.jpeg':
				builder += self.routeImage(filename);
				break;
			default:
				builder += self.routeStatic(filename);
				break;
		}
	}

	return builder;
};

/**
 * Append <link> TAG
 * @private
 * @return {String}
 */
Controller.prototype.$css = function() {

	var self = this;
	var builder = '';

	for (var i = 0; i < arguments.length; i++)
		builder += self.routeStyle(arguments[i], true);

	return builder;
};

Controller.prototype.$image = function(name, width, height, alt, className) {

	var style = '';

	if (typeof(width) === 'object') {
		height = width.height;
		alt = width.alt;
		className = width.class;
		style = width.style;
		width = width.width;
	}

	var builder = '<img src="' + this.routeImage(name) + ATTR_END;

	if (width > 0)
		builder += ' width="' + width + ATTR_END;

	if (height > 0)
		builder += ' height="' + height + ATTR_END;

	if (alt)
		builder += ' alt="' + alt.encode() + ATTR_END;

	if (className)
		builder += ' class="' + className + ATTR_END;

	if (style)
		builder += ' style="' + style + ATTR_END;

	return builder + ' border="0" />';
};

/**
 * Create URL: DOWNLOAD (<a href="..." download="...")
 * @private
 * @param {String} filename
 * @param {String} innerHTML
 * @param {String} downloadName Optional.
 * @param {String} className Optional.
 * @return {String}
 */
Controller.prototype.$download = function(filename, innerHTML, downloadName, className) {
	var builder = '<a href="' + framework.routeDownload(filename) + ATTR_END;

	if (downloadName)
		builder += ' download="' + downloadName + ATTR_END;

	if (className)
		builder += ' class="' + className + ATTR_END;

	return builder + '>' + (innerHTML || filename) + '</a>';
};

/**
 * Serialize object into the JSON
 * @private
 * @param {Object} obj
 * @param {String} id Optional.
 * @param {Boolean} beautify Optional.
 * @return {String}
 */
Controller.prototype.$json = function(obj, id, beautify) {

	if (typeof(id) === 'boolean') {
		var tmp = id;
		id = beautify;
		beautify = tmp;
	}

	var value = beautify ? JSON.stringify(obj, null, 4) : JSON.stringify(obj);
	return id ? ('<script type="application/json" id="' + id + '">' + value + '</script>') : value;
};

/**
 * Append FAVICON tag
 * @private
 * @param {String} name
 * @return {String}
 */
Controller.prototype.$favicon = function(name) {

	var contentType = 'image/x-icon';

	if (name == null)
		name = 'favicon.ico';

	var key = 'favicon#' + name;
	if (framework.temporary.other[key])
		return framework.temporary.other[key];

	if (name.lastIndexOf('.png') !== -1)
		contentType = 'image/png';
	else if (name.lastIndexOf('.gif') !== -1)
		contentType = 'image/gif';

	return framework.temporary.other[key] = '<link rel="shortcut icon" href="' + framework.routeStatic('/' + name) + '" type="' + contentType + '" />';
};

/**
 * Route static file helper
 * @private
 * @param {String} current
 * @param {String} name
 * @param {Function} fn
 * @return {String}
 */
Controller.prototype._routeHelper = function(name, fn) {
	return fn.call(framework, prepare_staticurl(name, false), this.themeName);
};

/**
 * Create URL: JavaScript
 * @param {String} name
 * @param {Boolean} tag Append tag?
 * @return {String}
 */
Controller.prototype.routeScript = function(name, tag, path) {

	if (name === undefined)
		name = 'default.js';

	var url;

	// isomorphic
	if (name[0] === '#') {
		var tmp = framework.isomorphic[name.substring(1)];
		if (tmp)
			url = tmp.url;
		else {
			F.error('Isomorphic library {0} doesn\'t exist.'.format(name.substring(1)));
			return '';
		}
	} else {
		url = this._routeHelper(name, framework.routeScript);
		if (path && framework_utils.isRelative(url))
			url = framework.isWindows ? framework_utils.join(path, url) : framework_utils.join(path, url).substring(1);
	}

	return tag ? '<script src="' + url + '"></script>' : url;
};

/**
 * Create URL: CSS
 * @param {String} name
 * @param {Boolean} tag Append tag?
 * @return {String}
 */
Controller.prototype.routeStyle = function(name, tag, path) {
	var self = this;

	if (name === undefined)
		name = 'default.css';

	var url = self._routeHelper(name, framework.routeStyle);
	if (path && framework_utils.isRelative(url))
		url = framework.isWindows ? framework_utils.join(path, url) : framework_utils.join(path, url).substring(1);

	return tag ? '<link type="text/css" rel="stylesheet" href="' + url + '" />' : url;
};

/**
 * Create URL: IMG
 * @param {String} name
 * @return {String}
 */
Controller.prototype.routeImage = function(name) {
	return this._routeHelper(name, framework.routeImage);
};

/**
 * Create URL: VIDEO
 * @param {String} name
 * @return {String}
 */
Controller.prototype.routeVideo = function(name) {
	return this._routeHelper(name, framework.routeVideo);
};

/**
 * Create URL: FONT
 * @param {String} name
 * @return {String}
 */
Controller.prototype.routeFont = function(name) {
	return framework.routeFont(name);
};

/**
 * Create URL: DOWNLOAD
 * @param {String} name
 * @return {String}
 */
Controller.prototype.routeDownload = function(name) {
	return this._routeHelper(name, framework.routeDownload);
};

/**
 * Create URL: static files (by the config['static-url'])
 * @param {String} name
 * @return {String}
 */
Controller.prototype.routeStatic = function(name, path) {
	var url = this._routeHelper(name, framework.routeStatic);
	if (path && framework_utils.isRelative(url))
		return framework.isWindows ? framework_utils.join(path, url) : framework_utils.join(path, url).substring(1);
	return url;
};

/**
 * Creates a string from the view
 * @param {String} name A view name without `.html` extension.
 * @param {Object} model A model, optional.
 * @return {String}
 */
Controller.prototype.template = function(name, model) {
	return this.view(name, model, true);
};

/**
 * Renders a custom helper to a string
 * @param {String} name A helper name.
 * @return {String}
 */
Controller.prototype.helper = function(name) {
	var helper = framework.helpers[name];
	if (!helper)
		return '';

	var params = [];
	for (var i = 1; i < arguments.length; i++)
		params.push(arguments[i]);

	return helper.apply(this, params);
};

/**
 * Response JSON
 * @param {Object} obj
 * @param {Object} headers Custom headers, optional.
 * @param {Boolean} beautify Beautify JSON.
 * @param {Function(key, value)} replacer JSON replacer.
 * @return {Controller}
 */
Controller.prototype.json = function(obj, headers, beautify, replacer) {
	var self = this;

	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	// Checks the HEAD method
	if (self.req.method === 'HEAD') {
		self.subscribe.success();
		framework.responseContent(self.req, self.res, self.status, '', 'application/json', self.config['allow-gzip'], headers);
		framework.stats.response.json++;
		return self;
	}

	if (typeof(headers) === 'boolean') {
		replacer = beautify;
		beautify = headers;
	}

	var type = 'application/json';

	if (obj instanceof framework_builders.ErrorBuilder) {
		self.req.$language && !obj.isResourceCustom && obj.setResource(self.req.$language);
		if (obj.contentType)
			type = obj.contentType;
		obj = obj.output();
	} else {

		if (framework_builders.isSchema(obj))
			obj = obj.$clean();

		if (beautify)
			obj = JSON.stringify(obj, replacer, 4);
		else
			obj = JSON.stringify(obj, replacer);
	}

	self.subscribe.success();
	framework.responseContent(self.req, self.res, self.status, obj, type, self.config['allow-gzip'], headers);
	framework.stats.response.json++;
	self.precache && self.precache(obj, 'application/json', headers);
	return self;
};

/**
 * Responds with JSONP
 * @param {String} name A method name.
 * @param {Object} obj Object to serialize.
 * @param {Object} headers A custom headers.
 * @param {Boolean} beautify Should be the JSON prettified? Optional, default `false`
 * @param {Function} replacer Optional, the JSON replacer.
 * @return {Controller}
 */
Controller.prototype.jsonp = function(name, obj, headers, beautify, replacer) {
	var self = this;

	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	// Checks the HEAD method
	if (self.req.method === 'HEAD') {
		self.subscribe.success();
		framework.responseContent(self.req, self.res, self.status, '', 'application/x-javascript', self.config['allow-gzip'], headers);
		framework.stats.response.json++;
		return self;
	}

	if (typeof(headers) === 'boolean') {
		replacer = beautify;
		beautify = headers;
	}

	if (!name)
		name = 'callback';

	if (obj instanceof framework_builders.ErrorBuilder) {
		self.req.$language && !obj.isResourceCustom && obj.setResource(self.req.$language);
		obj = obj.json(beautify);
	} else {

		if (framework_builders.isSchema(obj))
			obj = obj.$clean();

		if (beautify)
			obj = JSON.stringify(obj, replacer, 4);
		else
			obj = JSON.stringify(obj, replacer);
	}

	self.subscribe.success();
	framework.responseContent(self.req, self.res, self.status, name + '(' + obj + ')', 'application/x-javascript', self.config['allow-gzip'], headers);
	framework.stats.response.json++;
	self.precache && self.precache(name + '(' + obj + ')', 'application/x-javascript', headers);
	return self;
};

/**
 * Creates View or JSON callback
 * @param {String} viewName Optional, if is undefined or null then returns JSON.
 * @return {Function}
 */
Controller.prototype.callback = function(viewName) {
	var self = this;
	return function(err, data) {

		var is = err instanceof framework_builders.ErrorBuilder;

		// NoSQL embedded database
		if (data === undefined && !framework_utils.isError(err) && !is) {
			data = err;
			err = null;
		}

		if (err) {
			if (is && !viewName) {
				self.req.$language && !err.isResourceCustom && err.setResource(self.req.$language);
				return self.content(err);
			}
			return is && err.unexpected ? self.view500(err) : self.view404(err);
		}

		if (typeof(viewName) === 'string')
			return self.view(viewName, data);

		self.json(data);
	};
};

Controller.prototype.custom = function() {
	this.subscribe.success();
	if (this.res.success || this.res.headersSent || !this.isConnected)
		return false;
	framework.responseCustom(this.req, this.res);
	return true;
};

/**
 * Prevents cleaning uploaded files (need to call `controller.clear()` manually).
 * @param {Boolean} enable Optional, default `true`.
 * @return {Controller}
 */
Controller.prototype.noClear = function(enable) {
	this.req._manual = enable === undefined ? true : enable;
	return this;
};

Controller.prototype.content = function(contentBody, contentType, headers) {

	var self = this;
	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	if (contentBody instanceof ErrorBuilder) {
		var tmp = contentBody.output();
		if (!contentType)
			contentType = contentBody.contentType || 'application/json';
		contentBody = tmp;
	}

	self.subscribe.success();
	framework.responseContent(self.req, self.res, self.status, contentBody, contentType || CONTENTTYPE_TEXTPLAIN, self.config['allow-gzip'], headers);

	if (self.precache && self.status === 200) {
		self.layout('');
		self.precache(contentBody, contentType || CONTENTTYPE_TEXTPLAIN, headers, true);
	}

	return self;
};

/**
 * Responds with plain/text body
 * @param {String} body A response body (object is serialized into the JSON automatically).
 * @param {Boolean} headers A custom headers.
 * @return {Controller}
 */
Controller.prototype.plain = function(body, headers) {
	var self = this;

	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	// Checks the HEAD method
	if (self.req.method === 'HEAD') {
		self.subscribe.success();
		framework.responseContent(self.req, self.res, self.status, '', CONTENTTYPE_TEXTPLAIN, self.config['allow-gzip'], headers);
		framework.stats.response.plain++;
		return self;
	}

	var type = typeof(body);

	if (body == null)
		body = '';
	else if (type === 'object') {
		if (framework_builders.isSchema(body))
			body = body.$clean();
		body = body ? JSON.stringify(body, null, 4) : '';
	} else
		body = body ? body.toString() : '';

	self.subscribe.success();
	framework.responseContent(self.req, self.res, self.status, body, CONTENTTYPE_TEXTPLAIN, self.config['allow-gzip'], headers);
	framework.stats.response.plain++;
	self.precache && self.precache(body, CONTENTTYPE_TEXTPLAIN, headers);
	return self;
};

/**
 * Creates an empty response
 * @param {Object/Number} headers A custom headers or a custom HTTP status.
 * @return {Controller}
 */
Controller.prototype.empty = function(headers) {
	var self = this;

	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	var code = 200;

	if (typeof(headers) === 'number') {
		code = headers;
		headers = null;
	}

	self.subscribe.success();
	framework.responseContent(self.req, self.res, code, '', CONTENTTYPE_TEXTPLAIN, false, headers);
	framework.stats.response.empty++;
	return self;
};

/**
 * Destroys a request (closes it)
 * @param {String} problem Optional.
 * @return {Controller}
 */
Controller.prototype.destroy = function(problem) {
	var self = this;

	problem && self.problem(problem);
	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	self.subscribe.success();
	self.req.connection.destroy();
	framework.stats.response.destroy++;
	return self;
};

/**
 * Responds with a file
 * @param {String} filename
 * @param {String} download Optional, a download name.
 * @param {Object} headers Optional, additional headers.
 * @param {Function} done Optinoal, callback.
 * @return {Controller}
 */
Controller.prototype.file = function(filename, download, headers, done) {
	var self = this;

	if (self.res.success || self.res.headersSent || !self.isConnected) {
		done && done();
		return self;
	}

	if (filename[0] === '~')
		filename = filename.substring(1);
	else
		filename = framework.path.public_cache(filename);

	self.subscribe.success();
	framework.responseFile(self.req, self.res, filename, download, headers, done);
	return self;
};

/**
 * Responds with an image
 * @param {String or Stream} filename
 * @param {Function(image)} fnProcess
 * @param {Object} headers Optional, additional headers.
 * @param {Function} done Optional, callback.
 * @return {Controller}
 */
Controller.prototype.image = function(filename, fnProcess, headers, done) {
	var self = this;

	if (self.res.success || self.res.headersSent || !self.isConnected) {
		done && done();
		return self;
	}

	if (typeof(filename) === 'string') {
		if (filename[0] === '~')
			filename = filename.substring(1);
		else
			filename = framework.path.public_cache(filename);
	}

	self.subscribe.success();
	framework.responseImage(self.req, self.res, filename, fnProcess, headers, done);
	return self;
};

/**
 * Responds with a stream
 * @param {String} contentType
 * @param {Stream} stream
 * @param {String} download Optional, a download name.
 * @param {Object} headers Optional, additional headers.
 * @param {Function} done Optinoal, callback.
 * @return {Controller}
 */
Controller.prototype.stream = function(contentType, stream, download, headers, done, nocompress) {
	var self = this;

	if (self.res.success || self.res.headersSent || !self.isConnected) {
		done && done();
		return self;
	}

	self.subscribe.success();
	framework.responseStream(self.req, self.res, contentType, stream, download, headers, done, nocompress);
	return self;
};

/**
 * Throw 401 - Bad request.
 * @param  {String} problem Description of problem (optional)
 * @return {Controller}
 */
Controller.prototype.throw400 = Controller.prototype.view400 = function(problem) {
	return controller_error_status(this, 400, problem);
};

/**
 * Throw 401 - Unauthorized.
 * @param  {String} problem Description of problem (optional)
 * @return {Controller}
 */
Controller.prototype.throw401 = Controller.prototype.view401 = function(problem) {
	return controller_error_status(this, 401, problem);
};

/**
 * Throw 403 - Forbidden.
 * @param  {String} problem Description of problem (optional)
 * @return {Controller}
 */
Controller.prototype.throw403 = Controller.prototype.view403 = function(problem) {
	return controller_error_status(this, 403, problem);
};

/**
 * Throw 404 - Not found.
 * @param  {String} problem Description of problem (optional)
 * @return {Controller}
 */
Controller.prototype.throw404 = Controller.prototype.view404 = function(problem) {
	return controller_error_status(this, 404, problem);
};

/**
 * Throw 500 - Internal Server Error.
 * @param {Error} error
 * @return {Controller}
 */
Controller.prototype.throw500 = Controller.prototype.view500 = function(error) {
	var self = this;
	framework.error(error instanceof Error ? error : new Error((error || '').toString()), self.name, self.req.uri);
	return controller_error_status(self, 500, error);
};

/**
 * Throw 501 - Not implemented
 * @param  {String} problem Description of the problem (optional)
 * @return {Controller}
 */
Controller.prototype.throw501 = Controller.prototype.view501 = function(problem) {
	return controller_error_status(this, 501, problem);
};

/**
 * Creates a redirect
 * @param {String} url
 * @param {Boolean} permanent Is permanent? Default: `false`
 * @return {Controller}
 */
Controller.prototype.redirect = function(url, permanent) {
	var self = this;
	self.precache && self.precache(null, null, null);

	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	HEADERS.redirect.Location = url;
	self.subscribe.success();
	self.res.success = true;
	self.res.writeHead(permanent ? 301 : 302, HEADERS.redirect);
	self.res.end();
	framework._request_stats(false, false);
	framework.emit('request-end', self.req, self.res);
	self.req.clear(true);
	framework.stats.response.redirect++;
	return self;
};

/**
 * A binary response
 * @param {Buffer} buffer
 * @param {String} contentType
 * @param {String} type Transformation type: `binary`, `utf8`, `ascii`.
 * @param {String} download Optional, download name.
 * @param {Object} headers Optional, additional headers.
 * @return {Controller}
 */
Controller.prototype.binary = function(buffer, contentType, type, download, headers) {
	var self = this;
	var res = self.res;

	if (self.res.success || self.res.headersSent || !self.isConnected)
		return self;

	if (typeof(type) === 'object') {
		var tmp = type;
		type = download;
		download = headers;
		headers = tmp;
	}

	if (typeof(download) === 'object') {
		headers = download;
		download = headers;
	}

	self.subscribe.success();
	framework.responseBinary(self.req, res, contentType, buffer, type, download, headers);
	return self;
};

/**
 * Basic access authentication (baa)
 * @param {String} label
 * @return {Object}
 */
Controller.prototype.baa = function(label) {

	var self = this;
	self.precache && self.precache(null, null, null);

	if (label === undefined)
		return self.req.authorization();

	var headers = SINGLETON('!controller.baa');
	headers['WWW-Authenticate'] = 'Basic realm="' + (label || 'Administration') + '"';
	framework.responseContent(self.req, self.res, 401, '401: NOT AUTHORIZED', CONTENTTYPE_TEXTPLAIN, false, headers);
	self.subscribe.success();
	self.cancel();
	return null;
};

/**
 * Sends server-sent event message
 * @param {String/Object} data
 * @param {String} eventname Optional, an event name.
 * @param {String} id Optional, a custom ID.
 * @param {Number} retry A reconnection timeout in milliseconds when is an unexpected problem.
 * @return {Controller}
 */
Controller.prototype.sse = function(data, eventname, id, retry) {

	var self = this;
	var res = self.res;

	if (!self.isConnected)
		return self;

	if (!self.type && res.success)
		throw new Error('Response was sent.');

	if (self.type > 0 && self.type !== 1)
		throw new Error('Response was used.');

	if (!self.type) {

		self.type = 1;

		if (retry === undefined)
			retry = self.subscribe.route.timeout;

		self.subscribe.success();
		self.req.on('close', () => self.close());
		res.success = true;
		res.writeHead(self.status, HEADERS['sse']);
	}

	if (typeof(data) === 'object')
		data = JSON.stringify(data);
	else
		data = data.replace(/\n/g, '\\n').replace(/\r/g, '\\r');

	var newline = '\n';
	var builder = '';

	if (eventname)
		builder = 'event: ' + eventname + newline;

	builder += 'data: ' + data + newline;

	if (id)
		builder += 'id: ' + id + newline;

	if (retry > 0)
		builder += 'retry: ' + retry + newline;

	builder += newline;
	res.write(builder);
	framework.stats.response.sse++;
	return self;
};

Controller.prototype.mmr = function(name, stream, callback) {

	var self = this;
	var res = self.res;

	if (typeof(stream) === 'function') {
		var tmp = callback;
		callback = stream;
		stream = name;
	}

	if (!stream)
		stream = name;

	if (!self.isConnected || (!self.type && res.success) || (self.type && self.type !== 2)) {
		callback = null;
		return self;
	}

	if (!self.type) {
		self.type = 2;
		self.boundary = '----totaljs' + framework_utils.GUID(10);
		self.subscribe.success();
		self.req.on('close', () => self.close());
		res.success = true;
		HEADERS.mmr[RESPONSE_HEADER_CONTENTTYPE] = 'multipart/x-mixed-replace; boundary=' + self.boundary;
		res.writeHead(self.status, HEADERS.mmr);
	}

	res.write('--' + self.boundary + NEWLINE + RESPONSE_HEADER_CONTENTTYPE + ': ' + framework_utils.getContentType(framework_utils.getExtension(name)) + NEWLINE + NEWLINE);
	framework.stats.response.mmr++;

	if (typeof(stream) === 'string')
		stream = Fs.createReadStream(stream);

	stream.pipe(res, HEADERS.mmrpipe);
	CLEANUP(stream, () => callback && callback());
	return self;
};

/**
 * Close a response
 * @param {Boolean} end
 * @return {Controller}
 */
Controller.prototype.close = function(end) {
	var self = this;

	if (end === undefined)
		end = true;

	if (!self.isConnected)
		return self;

	if (self.type) {
		self.isConnected = false;
		self.res.success = true;
		framework._request_stats(false, false);
		framework.emit('request-end', self.req, self.res);
		self.type = 0;
		end && self.res.end();
		return self;
	}

	self.isConnected = false;

	if (self.res.success)
		return self;

	self.res.success = true;
	framework._request_stats(false, false);
	framework.emit('request-end', self.req, self.res);
	end && self.res.end();
	return self;
};

/**
 * Sends an object to another total.js application (POST + JSON)
 * @param {String} url
 * @param {Object} obj
 * @param {Funciton(err, data, code, headers)} callback
 * @param {Number} timeout Timeout, optional default 10 seconds.
 * @return {EventEmitter}
 */
Controller.prototype.proxy = function(url, obj, callback, timeout) {

	var self = this;
	var tmp;

	if (typeof(callback) === 'number') {
		tmp = timeout;
		timeout = callback;
		callback = tmp;
	}

	if (typeof(obj) === 'function') {
		tmp = callback;
		callback = obj;
		obj = tmp;
	}

	return framework_utils.request(url, REQUEST_PROXY_FLAGS, obj, function(err, data, code, headers) {
		if (!callback)
			return;
		if ((headers['content-type'] || '').lastIndexOf('/json') !== -1)
			data = framework.onParseJSON(data);
		callback.call(self, err, data, code, headers);
	}, null, HEADERS['proxy'], ENCODING, timeout || 10000);
};

/**
 * Creates a proxy between current request and new URL
 * @param {String} url
 * @param {Function(err, response, headers)} callback Optional.
 * @param {Object} headers Optional, additional headers.
 * @param {Number} timeout Optional, timeout (default: 10000)
 * @return {EventEmitter}
 */
Controller.prototype.proxy2 = function(url, callback, headers, timeout) {

	if (typeof(callback) === 'object') {
		timeout = headers;
		headers = callback;
		callback = undefined;
	}

	var self = this;
	var flags = [];
	var req = self.req;
	var type = req.headers['content-type'];
	var h = {};

	flags.push(req.method);
	flags.push('dnscache');

	if (type === 'application/json')
		flags.push('json');

	var c = req.method[0];
	var tmp;
	var keys;

	if (c === 'G' || c === 'H' || c === 'O') {
		if (url.indexOf('?') === -1) {
			tmp = Qs.stringify(self.query);
			if (tmp)
				url += '?' + tmp;
		}
	}

	keys = Object.keys(req.headers);
	for (var i = 0, length = keys.length; i < length; i++) {
		switch (keys[i]) {
			case 'x-forwarded-for':
			case 'x-forwarded-protocol':
			case 'x-nginx-proxy':
			case 'connection':
			case 'content-type':
			case 'host':
			case 'accept-encoding':
				break;
			default:
				h[keys[i]] = req.headers[keys[i]];
				break;
		}
	}

	if (headers) {
		keys = Object.keys(headers);
		for (var i = 0, length = keys.length; i < length; i++)
			h[keys[i]] = headers[keys[i]];
	}

	return framework_utils.request(url, flags, self.body, function(err, data, code, headers) {

		if (err) {
			callback && callback(err);
			self.invalid().push(err);
			return;
		}

		self.status = code;
		callback && callback(err, data, code, headers);
		self.content(data, (headers['content-type'] || 'text/plain').replace(REG_ENCODINGCLEANER, ''));
	}, null, h, ENCODING, timeout || 10000);
};

/**
 * Renders view to response
 * @param {String} name View name without `.html` extension.
 * @param {Object} model A model, optional default: `undefined`.
 * @param {Object} headers A custom headers, optional.
 * @param {Boolean} isPartial When is `true` the method returns rendered HTML as `String`
 * @return {Controller/String}
 */
Controller.prototype.view = function(name, model, headers, partial) {

	var self = this;

	if (typeof(name) !== 'string') {
		partial = headers;
		headers = model;
		model = name;
		name = self.viewname;
	} else if (partial === undefined && typeof(headers) === 'boolean') {
		partial = headers;
		headers = null;
	}

	if (!partial && self.res && self.res.success)
		return self;

	if (self.layoutName === undefined)
		self.layoutName = framework.config['default-layout'];
	if (self.themeName === undefined)
		self.themeName = framework.config['default-theme'];

	// theme root `~some_view`
	// views root `~~some_view`
	// package    `@some_view`
	// theme      `=theme/view`

	var key = 'view#=' + this.themeName + '/' + self._currentView + '/' + name;
	var filename = framework.temporary.other[key];
	var isLayout = self.isLayout;

	self.isLayout = false;

	// A small cache
	if (!filename) {

		// ~   --> routed into the root of views (if the controller uses a theme then is routed into the root views of the theme)
		// ~~  --> routed into the root of views (if the controller contains theme)
		// /   --> routed into the views (skipped)
		// @   --> routed into the packages
		// .   --> routed into the opened path
		// =   --> routed into the theme

		var c = name[0];
		var skip = c === '/' ? 1 : c === '~' && name[1] === '~' ? 4 : c === '~' ? 2 : c === '@' ? 3 : c === '.' ? 5 : c === '=' ? 6 : 0;
		var isTheme = false;

		filename = name;

		if (self.themeName && skip < 3) {
			filename = '.' + framework.path.themes(self.themeName + '/views/' + (isLayout || skip ? '' : self._currentView.substring(1)) + (skip ? name.substring(1) : name)).replace(REG_SANITIZE_BACKSLASH, '/');
			isTheme = true;
		}

		if (skip === 4) {
			filename = filename.substring(1);
			name = name.substring(1);
			skip = 2;
		}

		if (!isTheme && !isLayout && !skip)
			filename = self._currentView + name;

		if (!isTheme && (skip === 2 || skip === 3))
			filename = name.substring(1);

		if (skip === 3)
			filename = '.' + framework.path.package(filename);

		if (skip === 6) {
			c = framework_utils.parseTheme(filename);
			name = name.substring(name.indexOf('/') + 1);
			filename = '.' + framework.path.themes(c + '/views/' + name).replace(REG_SANITIZE_BACKSLASH, '/');
		}

		framework.temporary.other[key] = filename;
	}

	return self.$viewrender(filename, framework_internal.viewEngine(name, filename, self), model, headers, partial, isLayout);
};

Controller.prototype.viewCompile = function(body, model, headers, partial) {

	if (headers === true) {
		partial = true;
		headers = undefined;
	}

	return this.$viewrender('[dynamic view]', framework_internal.viewEngineCompile(body, this.language, this), model, headers, partial);
};

Controller.prototype.$viewrender = function(filename, generator, model, headers, partial, isLayout) {

	var self = this;
	var err;

	if (!generator) {

		err = new Error('View "' + filename + '" not found.');

		if (partial) {
			framework.error(err, self.name, self.uri);
			return self.outputPartial;
		}

		if (isLayout) {
			self.subscribe.success();
			framework.response500(self.req, self.res, err);
			return self;
		}

		self.view500(err);
		return self;
	}

	var value = '';
	self.$model = model;

	if (isLayout)
		self._currentView = self._defaultView || '';

	var helpers = framework.helpers;

	try {
		value = generator.call(self, self, self.repository, model, self.session, self.query, self.body, self.url, framework.global, helpers, self.user, self.config, framework.functions, 0, partial ? self.outputPartial : self.output, self.date, self.req.cookie, self.req.files, self.req.mobile);
	} catch (ex) {

		err = new Error('View "' + filename + '": ' + ex.message);

		if (!partial) {
			self.view500(err);
			return self;
		}

		self.error(err);

		if (self.partial)
			self.outputPartial = '';
		else
			self.output = '';

		isLayout = false;
		return value;
	}

	if (!isLayout && self.precache && self.status === 200 && !partial)
		self.precache(value, CONTENTTYPE_TEXTHTML, headers, true);

	if (isLayout || !self.layoutName) {

		self.outputPartial = '';
		self.output = '';
		isLayout = false;

		if (partial)
			return value;

		self.subscribe.success();

		if (!self.isConnected)
			return self;

		framework.responseContent(self.req, self.res, self.status, value, CONTENTTYPE_TEXTHTML, self.config['allow-gzip'], headers);
		framework.stats.response.view++;
		return self;
	}

	if (partial)
		self.outputPartial = value;
	else
		self.output = value;

	self.isLayout = true;
	value = self.view(self.layoutName, self.$model, headers, partial);

	if (partial) {
		self.outputPartial = '';
		self.isLayout = false;
		return value;
	}

	return self;
};

/**
 * Creates a cache for the response without caching layout
 * @param {String} key
 * @param {String} expires Expiration, e.g. `1 minute`
 * @param {Boolean} disabled Disables a caching, optinoal (e.g. for debug mode you can disable a cache), default: `false`
 * @param {Function()} fnTo This method is executed when the content is prepared for the cache.
 * @param {Function()} fnFrom This method is executed when the content is readed from the cache.
 * @return {Controller}
 */
Controller.prototype.memorize = function(key, expires, disabled, fnTo, fnFrom) {

	var self = this;

	if (disabled === true) {
		fnTo();
		return self;
	}

	var output = self.cache.read2(key);
	if (!output)
		return self.$memorize_prepare(key, expires, disabled, fnTo, fnFrom);

	if (typeof(disabled) === 'function') {
		var tmp = fnTo;
		fnTo = disabled;
		fnFrom = tmp;
	}

	self.layoutName = output.layout;
	self.themeName = output.theme;

	if (output.type !== CONTENTTYPE_TEXTHTML) {
		fnFrom && fnFrom();
		self.subscribe.success();
		framework.responseContent(self.req, self.res, self.status, output.content, output.type, self.config['allow-gzip'], output.headers);
		return;
	}

	switch (output.type) {
		case CONTENTTYPE_TEXTPLAIN:
			framework.stats.response.plain++;
			return self;
		case 'application/json':
			framework.stats.response.json++;
			return self;
		case CONTENTTYPE_TEXTHTML:
			framework.stats.response.view++;
			break;
	}

	var length = output.repository.length;
	for (var i = 0; i < length; i++) {
		var key = output.repository[i].key;
		if (self.repository[key] === undefined)
			self.repository[key] = output.repository[i].value;
	}

	fnFrom && fnFrom();

	if (!self.layoutName) {
		self.subscribe.success();
		self.isConnected && framework.responseContent(self.req, self.res, self.status, output.content, output.type, self.config['allow-gzip'], output.headers);
		return self;
	}

	self.output = output.content;
	self.isLayout = true;
	self.view(self.layoutName, null);
	return self;
};

Controller.prototype.$memorize_prepare = function(key, expires, disabled, fnTo, fnFrom) {

	var self = this;
	var pk = '$memorize' + key;

	if (framework.temporary.processing[pk]) {
		setTimeout(function() {
			!self.subscribe.isCanceled && self.memorize(key, expires, disabled, fnTo, fnFrom);
		}, 500);
		return self;
	}

	self.precache = function(value, contentType, headers, isView) {

		if (!value && !contentType && !headers) {
			delete framework.temporary.processing[pk];
			self.precache = null;
			return;
		}

		var options = { content: value, type: contentType, layout: self.layoutName, theme: self.themeName };
		if (headers)
			options.headers = headers;

		if (isView) {
			options.repository = [];
			for (var name in self.repository) {
				var value = self.repository[name];
				value !== undefined && options.repository.push({ key: name, value: value });
			}
		}

		self.cache.add(key, options, expires);
		self.precache = null;
		delete framework.temporary.processing[pk];
	};

	if (typeof(disabled) === 'function')
		fnTo = disabled;

	framework.temporary.processing[pk] = true;
	fnTo();
	return self;
};

// *********************************************************************************
// =================================================================================
// Framework.WebSocket
// =================================================================================
// *********************************************************************************

const NEWLINE = '\r\n';
const SOCKET_RESPONSE = 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {0}\r\n\r\n';
const SOCKET_RESPONSE_PROTOCOL = 'HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {0}\r\nSec-WebSocket-Protocol: {1}\r\n\r\n';
const SOCKET_RESPONSE_ERROR = 'HTTP/1.1 403 Forbidden\r\nConnection: close\r\nX-WebSocket-Reject-Reason: 403 Forbidden\r\n\r\n';
const SOCKET_HASH = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const SOCKET_ALLOW_VERSION = [13];

function WebSocket(path, name, id) {
	this._keys = [];
	this.id = id;
	this.online = 0;
	this.connections = {};
	this.repository = {};
	this.name = name;
	this.isController = true;
	this.url = framework_utils.path(path);
	this.route = null;

	// on('open', function(client) {});
	// on('close', function(client) {});
	// on('message', function(client, message) {});
	// on('error', function(error, client) {});
	// Events.EventEmitter.call(this);
}

WebSocket.prototype = {

	get global() {
		return framework.global;
	},

	get config() {
		return framework.config;
	},

	get cache() {
		return framework.cache;
	},

	get isDebug() {
		return framework.config.debug;
	},

	get path() {
		return framework.path;
	},

	get fs() {
		return framework.fs;
	},

	get isSecure() {
		return this.req.isSecure;
	},

	get secured() {
		return this.req.secured;
	},
}

WebSocket.prototype.__proto__ = Object.create(Events.EventEmitter.prototype, {
	constructor: {
		value: WebSocket,
		enumberable: false
	}
});

/**
 * Compare Date/Time
 * @param {String} type Compare type ('<', '>', '=', '>=', '<=')
 * @param {String or Date} d1 String (yyyy-MM-dd [HH:mm:ss]), (optional) - default current date
 * @param {String or Date} d2 String (yyyy-MM-dd [HH:mm:ss])
 * @return {Boolean}
 */
WebSocket.prototype.date = function(type, d1, d2) {

	if (d2 === undefined) {
		d2 = d1;
		d1 = new Date();
	}

	var beg = typeof(d1) === 'string' ? d1.parseDate() : d1;
	var end = typeof(d2) === 'string' ? d2.parseDate() : d2;
	var r = beg.compare(end);

	switch (type) {
		case '>':
			return r === 1;
		case '>=':
		case '=>':
			return r === 1 || r === 0;
		case '<':
			return r === -1;
		case '<=':
		case '=<':
			return r === -1 || r === 0;
		case '=':
			return r === 0;
	}

	return true;
};

/**
 * Sends a message
 * @param {String} message
 * @param {String Array or Function(id, client)} id
 * @param {String Array or Function(id, client)} blacklist
 * @param {String} raw internal
 * @return {WebSocket}
 */
WebSocket.prototype.send = function(message, id, blacklist) {

	var self = this;
	var keys = self._keys;

	if (!keys || !keys.length)
		return self;

	var data;
	var raw = false;

	for (var i = 0, length = keys.length; i < length; i++) {

		var _id = keys[i];
		var conn = self.connections[_id];

		if (id) {
			if (id instanceof Array) {
				if (!websocket_valid_array(_id, id))
					continue;
			} else if (id instanceof Function) {
				if (!websocket_valid_fn(_id, conn, id))
					continue;
			} else
				throw new Error('Invalid "id" argument.');
		}

		if (blacklist) {
			if (blacklist instanceof Array) {
				if (websocket_valid_array(_id, blacklist))
					continue;
			} else if (blacklist instanceof Function) {
				if (websocket_valid_fn(_id, conn, blacklist))
					continue;
			} else
				throw new Error('Invalid "blacklist" argument.');
		}

		if (data === undefined) {
			if (conn.type === 3) {
				raw = true;
				data = JSON.stringify(message);
			} else
				data = message;
		}

		conn.send(data, raw);
		framework.stats.response.websocket++;
	}

	return self;
};

function websocket_valid_array(id, arr) {
	return arr.indexOf(id) !== -1;
}

function websocket_valid_fn(id, client, fn) {
	return fn && fn(id, client) ? true : false;
}

/**
 * Sends a ping message
 * @return {WebSocket}
 */
WebSocket.prototype.ping = function() {

	var self = this;
	var keys = self._keys;

	if (!keys)
		return self;

	var length = keys.length;

	if (length === 0)
		return self;

	self.$ping = true;
	framework.stats.other.websocketPing++;

	for (var i = 0; i < length; i++)
		self.connections[keys[i]].ping();

	return self;
};

/**
 * Closes a connection
 * @param {String Array} id Client id, optional, default `null`.
 * @param {String} message A message for the browser.
 * @param {Number} code Optional default 1000.
 * @return {Websocket}
 */
WebSocket.prototype.close = function(id, message, code) {

	var self = this;
	var keys = self._keys;

	if (!keys)
		return self;

	if (typeof(id) === 'string') {
		code = message;
		message = id;
		id = null;
	}

	var length = keys.length;
	if (!length)
		return self;

	if (!id || !id.length) {
		for (var i = 0; i < length; i++) {
			var _id = keys[i];
			self.connections[_id].close(message, code);
			self._remove(_id);
		}
		self._refresh();
		return self;
	}

	var is = id instanceof Array;
	var fn = typeof(id) === 'function' ? id : null;

	for (var i = 0; i < length; i++) {

		var _id = keys[i];
		if (is && id.indexOf(_id) === -1)
			continue;

		var conn = self.connections[_id];
		if (fn && !fn.call(self, _id, conn))
			continue;

		conn.close(message, code);
		self._remove(_id);
	}

	self._refresh();
	return self;
};

/**
 * Error caller
 * @param {Error/String} err
 * @return {WebSocket/Function}
 */
WebSocket.prototype.error = function(err) {
	var self = this;
	var result = framework.error(typeof(err) === 'string' ? new Error(err) : err, self.name, self.path);
	if (err === undefined)
		return result;
	return self;
};

/**
 * Creates a problem
 * @param {String} message
 * @return {WebSocket}
 */
WebSocket.prototype.wtf = WebSocket.prototype.problem = function(message) {
	var self = this;
	framework.problem(message, self.name, self.uri);
	return self;
};

/**
 * Creates a change
 * @param {String} message
 * @return {WebSocket}
 */
WebSocket.prototype.change = function(message) {
	var self = this;
	framework.change(message, self.name, self.uri, self.ip);
	return self;
};

/**
 * The method executes a provided function once per client.
 * @param {Function(connection, index)} fn
 * @return {WebSocket}
 */
WebSocket.prototype.all = function(fn) {

	var self = this;
	if (!self._keys)
		return self;

	for (var i = 0, length = self._keys.length; i < length; i++)
		fn(self.connections[self._keys[i]], i);

	return self;
};

/**
 * Finds a connection
 * @param {String} id
 * @return {WebSocketClient}
 */
WebSocket.prototype.find = function(id) {
	var self = this;

	if (!self._keys)
		return self;

	var length = self._keys.length;
	var isFn = typeof(id) === 'function';

	for (var i = 0; i < length; i++) {
		var connection = self.connections[self._keys[i]];

		if (!isFn) {
			if (connection.id === id)
				return connection;
			continue;
		}

		if (id(connection, connection.id))
			return connection;
	}

	return null;
};

/**
 * Destroyes a WebSocket controller
 * @param {String} problem Optional.
 * @return {WebSocket}
 */
WebSocket.prototype.destroy = function(problem) {
	var self = this;

	problem && self.problem(problem);
	if (!self.connections && !self._keys)
		return self;

	self.close();
	self.emit('destroy');

	setTimeout(function() {

		self._keys.forEach(function(key) {
			var conn = self.connections[key];
			if (conn) {
				conn._isClosed = true;
				conn.socket.removeAllListeners();
				conn.removeAllListeners();
			}
		});

		self.connections = null;
		self._keys = null;
		self.route = null;
		self.buffer = null;
		delete framework.connections[self.id];
		self.removeAllListeners();
	}, 1000);

	return self;
};

/**
 * Enables auto-destroy websocket controller when any user is not online
 * @param {Function} callback
 * @return {WebSocket]
 */
WebSocket.prototype.autodestroy = function(callback) {
	var self = this;
	var key = 'websocket:' + self.id;
	self.on('open', () => clearTimeout2(key));
	self.on('close', function() {
		!self.online && setTimeout2(key, function() {
			callback && callback.call(self);
			self.destroy();
		}, 5000);
	});
	return self;
};

/**
 * Sends an object to another total.js application (POST + JSON)
 * @param {String} url
 * @param {Object} obj
 * @param {Funciton(err, data, code, headers)} callback
 * @param {Number} timeout Timeout, optional default 10 seconds.
 * @return {EventEmitter}
 */
WebSocket.prototype.proxy = function(url, obj, callback, timeout) {

	var self = this;

	if (typeof(callback) === 'number') {
		tmp = timeout;
		timeout = callback;
		callback = tmp;
	}

	if (typeof(obj) === 'function') {
		tmp = callback;
		callback = obj;
		obj = tmp;
	}

	return framework_utils.request(url, REQUEST_PROXY_FLAGS, obj, function(error, data, code, headers) {
		if (!callback)
			return;
		if ((headers['content-type'] || '').lastIndexOf('/json') !== -1)
			data = framework.onParseJSON(data);
		callback.call(self, error, data, code, headers);
	}, null, HEADERS['proxy'], ENCODING, timeout || 10000);
};

/**
 * Internal function
 * @return {WebSocket}
 */
WebSocket.prototype._refresh = function() {
	var self = this;

	if (!self.connections) {
		self.online = 0;
		return self;
	}

	self._keys = Object.keys(self.connections);
	self.online = self._keys.length;
	return self;
};

/**
 * Internal function
 * @param {String} id
 * @return {WebSocket}
 */
WebSocket.prototype._remove = function(id) {
	var self = this;
	if (self.connections)
		delete self.connections[id];
	return self;
};

/**
 * Internal function
 * @param {WebSocketClient} client
 * @return {WebSocket}
 */
WebSocket.prototype._add = function(client) {
	var self = this;
	self.connections[client._id] = client;
	return self;
};

/**
 * Gets a module instance
 * @param {String} name
 * @return {Object}
 */
WebSocket.prototype.module = function(name) {
	return framework.module(name);
};

/**
 * Gets a model instance
 * @param {String} name
 * @return {Object}
 */
WebSocket.prototype.model = function(name) {
	return framework.model(name);
};

/**
 * Render helper to string
 * @param {String} name
 * @return {String}
 */
WebSocket.prototype.helper = function(name) {
	var self = this;
	var helper = framework.helpers[name];

	if (!helper)
		return '';

	var length = arguments.length;
	var params = [];

	for (var i = 1; i < length; i++)
		params.push(arguments[i]);

	return helper.apply(self, params);
};

/**
 * A resource header
 * @param {String} name A resource name.
 * @param {String} key A resource key.
 * @return {String}
 */
WebSocket.prototype.resource = function(name, key) {
	return framework.resource(name, key);
};

WebSocket.prototype.log = function() {
	var self = this;
	framework.log.apply(framework, arguments);
	return self;
};

WebSocket.prototype.logger = function() {
	var self = this;
	framework.logger.apply(framework, arguments);
	return self;
};

WebSocket.prototype.check = function() {
	var self = this;

	if (!self.$ping)
		return self;

	self.all(function(client) {
		if (client.$ping)
			return;
		client.close();
		framework.stats.other.websocketCleaner++;
	});

	return self;
};

/**
 * WebSocket controller
 * @param {Request} req
 * @param {Socket} socket
 * @param {String} head
 */
function WebSocketClient(req, socket, head) {
	this.$ping = true;
	this.container;
	this._id;
	this.id = '';
	this.socket = socket;
	this.req = req;
	// this.isClosed = false;
	this.errors = 0;
	this.buffer = new Buffer(0);
	this.length = 0;

	// 1 = raw - not implemented
	// 2 = plain
	// 3 = JSON

	this.type = 2;
	// this._isClosed = false;
}

WebSocketClient.prototype = {

	get protocol() {
		return (this.req.headers['sec-websocket-protocol'] || '').replace(REG_EMPTY, '').split(',');
	},

	get ip() {
		return this.req.ip;
	},

	get get() {
		return this.req.query;
	},

	get query() {
		return this.req.query;
	},

	get uri() {
		return this.req.uri;
	},

	get config() {
		return this.container.config;
	},

	get global() {
		return this.container.global;
	},

	get session() {
		return this.req.session;
	},

	set session(value) {
		this.req.session = value;
	},

	get user() {
		return this.req.user;
	},

	set user(value) {
		this.req.user = value;
	}
};

WebSocketClient.prototype.__proto__ = Object.create(Events.EventEmitter.prototype, {
	constructor: {
		value: WebSocketClient,
		enumberable: false
	}
});

WebSocketClient.prototype.isWebSocket = true;

WebSocketClient.prototype.cookie = function(name) {
	return this.req.cookie(name);
};

WebSocketClient.prototype.prepare = function(flags, protocols, allow, length, version) {

	var self = this;

	flags = flags || [];
	protocols = protocols || [];
	allow = allow || [];

	self.length = length;

	var origin = self.req.headers['origin'] || '';
	var length = allow.length;

	if (length) {
		if (allow.indexOf('*') === -1) {
			for (var i = 0; i < length; i++) {
				if (origin.indexOf(allow[i]) === -1)
					return false;
			}
		}
	}

	length = protocols.length;
	if (length) {
		for (var i = 0; i < length; i++) {
			if (self.protocol.indexOf(protocols[i]) === -1)
				return false;
		}
	}

	if (SOCKET_ALLOW_VERSION.indexOf(framework_utils.parseInt(self.req.headers['sec-websocket-version'])) === -1)
		return false;

	var header = protocols.length ? SOCKET_RESPONSE_PROTOCOL.format(self._request_accept_key(self.req), protocols.join(', ')) : SOCKET_RESPONSE.format(self._request_accept_key(self.req));
	self.socket.write(new Buffer(header, 'binary'));

	self._id = (self.ip || '').replace(/\./g, '') + framework_utils.GUID(20);
	self.id = self._id;
	return true;
};

/**
 * Add a container to client
 * @param {WebSocket} container
 * @return {WebSocketClient}
 */
WebSocketClient.prototype.upgrade = function(container) {

	var self = this;
	self.container = container;

	//self.socket.setTimeout(0);
	//self.socket.setNoDelay(true);
	//self.socket.setKeepAlive(true, 0);

	self.socket.on('data', n => self._ondata(n));
	self.socket.on('error', n => self._onerror(n));
	self.socket.on('close', () => self._onclose());
	self.socket.on('end', () => self._onclose());
	self.container._add(self);
	self.container._refresh();

	framework.emit('websocket-begin', self.container, self);
	self.container.emit('open', self);
	return self;
};

/**
 * Internal handler written by Jozef Gula
 * @param {Buffer} data
 * @return {Framework}
 */
WebSocketClient.prototype._ondata = function(data) {

	var self = this;

	if (data)
		self.buffer = Buffer.concat([self.buffer, data]);

	if (self.buffer.length > self.length) {
		self.errors++;
		self.container.emit('error', new Error('Maximum request length exceeded.'), self);
		return;
	}

	switch (self.buffer[0] & 0x0f) {
		case 0x01:
			// text message or JSON message
			self.type !== 1 && self.parse();
			break;
		case 0x02:
			// binary message
			self.type === 1 && self.parse();
			break;
		case 0x08:
			// close
			self.close();
			break;
		case 0x09:
			// ping, response pong
			self.socket.write(framework_utils.getWebSocketFrame(0, '', 0x0A));
			self.buffer = new Buffer(0);
			self.$ping = true;
			break;
		case 0x0a:
			// pong
			self.$ping = true;
			self.buffer = new Buffer(0);
			break;
	}
};

// MIT
// Written by Jozef Gula
WebSocketClient.prototype.parse = function() {

	var self = this;
	var bLength = self.buffer[1];

	if (((bLength & 0x80) >> 7) !== 1)
		return self;

	var length = framework_utils.getMessageLength(self.buffer, framework.isLE);
	var index = (self.buffer[1] & 0x7f);

	index = (index == 126) ? 4 : (index == 127 ? 10 : 2);

	if ((index + length + 4) > (self.buffer.length))
		return self;

	var mask = new Buffer(4);
	self.buffer.copy(mask, 0, index, index + 4);

	// TEXT
	if (self.type !== 1) {
		var output = '';
		for (var i = 0; i < length; i++)
			output += String.fromCharCode(self.buffer[index + 4 + i] ^ mask[i % 4]);

		// JSON
		if (self.type === 3) {
			try {
				output = self.container.config['default-websocket-encodedecode'] === true ? $decodeURIComponent(output) : output;
				output.isJSON() && self.container.emit('message', self, framework.onParseJSON(output));
			} catch (ex) {
				if (DEBUG) {
					self.errors++;
					self.container.emit('error', new Error('JSON parser: ' + ex.toString()), self);
				}
			}
		} else
			self.container.emit('message', self, self.container.config['default-websocket-encodedecode'] === true ? $decodeURIComponent(output) : output);
	} else {
		var binary = new Buffer(length);
		for (var i = 0; i < length; i++)
			binary[i] = self.buffer[index + 4 + i] ^ mask[i % 4];
		self.container.emit('message', self, new Uint8Array(binary).buffer);
	}

	self.buffer = self.buffer.slice(index + length + 4, self.buffer.length);

	if (self.buffer.length >= 2 && framework_utils.getMessageLength(self.buffer, framework.isLE))
		self.parse();

	return self;
};

WebSocketClient.prototype._onerror = function(err) {
	var self = this;

	if (!self || self.isClosed)
		return;

	if (REG_WEBSOCKET_ERROR.test(err.stack)) {
		self.isClosed = true;
		self._onclose();
		return;
	}

	self.container.emit('error', err, self);
};

WebSocketClient.prototype._onclose = function() {
	var self = this;
	if (!self || self._isClosed)
		return;
	self.isClosed = true;
	self._isClosed = true;
	self.container._remove(self._id);
	self.container._refresh();
	self.container.emit('close', self);
	self.socket.removeAllListeners();
	self.removeAllListeners();
	framework.emit('websocket-end', self.container, self);
};

/**
 * Sends a message
 * @param {String/Object} message
 * @param {Boolean} raw The message won't be converted e.g. to JSON.
 * @return {WebSocketClient}
 */
WebSocketClient.prototype.send = function(message, raw) {

	var self = this;
	if (!self || self.isClosed)
		return self;

	if (self.type !== 1) {
		var data = self.type === 3 ? (raw ? message : JSON.stringify(message)) : (message || '').toString();
		if (self.container.config['default-websocket-encodedecode'] === true && data)
			data = encodeURIComponent(data);
		self.socket.write(framework_utils.getWebSocketFrame(0, data, 0x01));
	} else
		message && self.socket.write(framework_utils.getWebSocketFrame(0, new Int8Array(message), 0x02));

	return self;
};

/**
 * Ping message
 * @return {WebSocketClient}
 */
WebSocketClient.prototype.ping = function() {

	var self = this;

	if (!self || self.isClosed)
		return self;

	self.socket.write(framework_utils.getWebSocketFrame(0, '', 0x09));
	self.$ping = false;
	return self;
};

/**
 * Close connection
 * @param {String} message Message.
 * @param {Number} code WebSocket code.
 * @return {WebSocketClient}
 */
WebSocketClient.prototype.close = function(message, code) {
	var self = this;

	if (!self || self.isClosed)
		return self;

	if (message)
		message = encodeURIComponent(message);
	else
		message = '';

	self.isClosed = true;
	self.socket.end(framework_utils.getWebSocketFrame(code || 1000, message, 0x08));
	return self;
};

/**
 * Create a signature for the WebSocket
 * @param {Request} req
 * @return {String}
 */
WebSocketClient.prototype._request_accept_key = function(req) {
	var sha1 = Crypto.createHash('sha1');
	sha1.update((req.headers['sec-websocket-key'] || '') + SOCKET_HASH);
	return sha1.digest('base64');
};

function Backup() {
	this.file = [];
	this.directory = [];
	this.path = '';
	this.read = { key: new Buffer(0), value: new Buffer(0), status: 0 };
	this.pending = 0;
	this.cache = {};
	this.complete = NOOP;
	this.filter = () => true;
	this.bufKey = new Buffer(':');
	this.bufNew = new Buffer('\n');
}

Backup.prototype.restoreKey = function(data) {

	var self = this;
	var read = self.read;

	if (read.status === 1) {
		self.restoreValue(data);
		return;
	}

	var index = -1;
	var tmp = data;

	if (read.status === 2) {
		tmp = Buffer.concat([read.key, tmp]);
		index = tmp.indexOf(self.bufKey);
	} else
		index = tmp.indexOf(self.bufKey);

	if (index === -1) {
		read.key = Buffer.concat([read.key, data]);
		read.status = 2;
		return;
	}

	read.status = 1;
	read.key = tmp.slice(0, index);
	self.restoreValue(tmp.slice(index + 1));
	tmp = null;
};

Backup.prototype.restoreValue = function(data) {

	var self = this;
	var read = self.read;

	if (read.status !== 1) {
		self.restoreKey(data);
		return;
	}

	var index = data.indexOf(self.bufNew);
	if (index === -1) {
		read.value = Buffer.concat([read.value, data]);
		return;
	}

	read.value = Buffer.concat([read.value, data.slice(0, index)]);
	self.restoreFile(read.key.toString('utf8').replace(REG_EMPTY, ''), read.value.toString('utf8').replace(REG_EMPTY, ''));

	read.status = 0;
	read.value = new Buffer(0);
	read.key = new Buffer(0);

	self.restoreKey(data.slice(index + 1));
};

Backup.prototype.restore = function(filename, path, callback, filter) {

	if (!existsSync(filename)) {
		callback && callback(new Error('Package not found.'), path);
		return;
	}

	var self = this;

	self.filter = filter;
	self.cache = {};
	self.createDirectory(path, true);
	self.path = path;

	var stream = Fs.createReadStream(filename);
	stream.on('data', buffer => self.restoreKey(buffer));

	if (!callback) {
		stream.resume();
		return;
	}

	callback.path = path;

	stream.on('end', function() {
		self.callback(callback);
		stream = null;
	});

	stream.resume();
};

Backup.prototype.callback = function(cb) {
	var self = this;
	if (self.pending <= 0)
		return cb(null, cb.path);
	setTimeout(() => self.callback(cb), 100);
};

Backup.prototype.restoreFile = function(key, value) {
	var self = this;

	if (typeof(self.filter) === 'function' && !self.filter(key))
		return;

	if (value === '#') {
		self.createDirectory(key);
		return;
	}

	var p = key;
	var index = key.lastIndexOf('/');

	if (index !== -1) {
		p = key.substring(0, index).trim();
		p && self.createDirectory(p);
	}

	var buffer = new Buffer(value, 'base64');
	self.pending++;

	Zlib.gunzip(buffer, function(err, data) {
		Fs.writeFile(Path.join(self.path, key), data, () => self.pending--);
		buffer = null;
	});
};

Backup.prototype.createDirectory = function(p, root) {

	var self = this;
	if (self.cache[p])
		return;

	self.cache[p] = true;

	if (p[0] === '/')
		p = p.substring(1);

	var is = framework.isWindows;

	if (is) {
		if (p[p.length - 1] === '\\')
			p = p.substring(0, p.length - 1);
	} else {
		if (p[p.length - 1] === '/')
			p = p.substring(0, p.length - 1);
	}

	var arr = is ? p.replace(/\//g, '\\').split('\\') : p.split('/');
	var directory = '';

	if (is && arr[0].indexOf(':') !== -1)
		arr.shift();

	for (var i = 0, length = arr.length; i < length; i++) {
		var name = arr[i];
		if (is)
			directory += (directory ? '\\' : '') + name;
		else
			directory += (directory ? '/' : '') + name;

		var dir = Path.join(self.path, directory);
		if (root)
			dir = (is ? '\\' : '/') + dir;

		!existsSync(dir) && Fs.mkdirSync(dir);
	}
};

// *********************************************************************************
// =================================================================================
// Prototypes
// =================================================================================
// *********************************************************************************

/**
 * Add a cookie into the response
 * @param {String} name
 * @param {Object} value
 * @param {Date/String} expires
 * @param {Object} options Additional options.
 * @return {Response}
 */
http.ServerResponse.prototype.cookie = function(name, value, expires, options) {

	var self = this;

	if (self.headersSent || self.success)
		return;

	var cookieHeaderStart = name + '=';
	var builder = [cookieHeaderStart + value];
	var type = typeof(expires);

	if (expires && !framework_utils.isDate(expires) && type === 'object') {
		options = expires;
		expires = options.expires || options.expire || null;
	}

	if (type === 'string')
		expires = expires.parseDateExpiration();

	if (!options)
		options = {};

	options.path = options.path || '/';
	expires &&  builder.push('Expires=' + expires.toUTCString());
	options.domain && builder.push('Domain=' + options.domain);
	options.path && builder.push('Path=' + options.path);
	options.secure && builder.push('Secure');

	if (options.httpOnly || options.httponly || options.HttpOnly)
		builder.push('HttpOnly');

	var arr = self.getHeader('set-cookie') || [];

	// Cookie, already, can be in array, resulting in duplicate 'set-cookie' header
	var idx = arr.findIndex(cookieStr => cookieStr.startsWith(cookieHeaderStart));
	idx !== -1 && arr.splice(idx, 1);
	arr.push(builder.join('; '));
	self.setHeader('Set-Cookie', arr);
	return self;
};

/**
 * Disable HTTP cache for current response
 * @return {Response}
 */
http.ServerResponse.prototype.noCache = function() {
	var self = this;
	self.removeHeader(RESPONSE_HEADER_CACHECONTROL);
	self.removeHeader('Etag');
	self.removeHeader('Last-Modified');
	return self;
};

/**
 * Send
 * @param {Number} code Response status code, optional
 * @param {Object} body Body
 * @param {String} type Content-Type, optional
 * @return {Response}
 */
http.ServerResponse.prototype.send = function(code, body, type) {

	var self = this;

	if (self.headersSent)
		return self;

	self.controller && self.controller.subscribe.success();

	var res = self;
	var req = self.req;
	var contentType = type;
	var isHEAD = req.method === 'HEAD';

	if (body === undefined) {
		body = code;
		code = 200;
	}

	switch (typeof(body)) {
		case 'string':
			if (!contentType)
				contentType = 'text/html';
			break;

		case 'number':
			if (!contentType)
				contentType = 'text/plain';
			body = framework_utils.httpStatus(body);
			break;

		case 'boolean':
		case 'object':
			if (!contentType)
				contentType = 'application/json';
			if (!isHEAD) {
				if (body instanceof framework_builders.ErrorBuilder)
					body = obj.output();
				body = JSON.stringify(body);
			}
			break;
	}

	var accept = req.headers['accept-encoding'] || '';
	var headers = {};

	headers[RESPONSE_HEADER_CACHECONTROL] = 'private';
	headers['Vary'] = 'Accept-Encoding' + (req.$mobile ? ', User-Agent' : '');

	// Safari resolve
	if (contentType === 'application/json')
		headers[RESPONSE_HEADER_CACHECONTROL] = 'private, no-cache, no-store, must-revalidate';

	if ((/text|application/).test(contentType))
		contentType += '; charset=utf-8';

	headers[RESPONSE_HEADER_CONTENTTYPE] = contentType;
	framework.responseCustom(req, res);

	if (!accept && isGZIP(req))
		accept = 'gzip';

	var compress = accept.indexOf('gzip') !== -1;

	if (isHEAD) {
		if (compress)
			headers['Content-Encoding'] = 'gzip';
		res.writeHead(200, headers);
		res.end();
		return self;
	}

	if (!compress) {
		res.writeHead(code, headers);
		res.end(body, ENCODING);
		return self;
	}

	var buffer = new Buffer(body);
	Zlib.gzip(buffer, function(err, data) {

		if (err) {
			res.writeHead(code, headers);
			res.end(body, ENCODING);
			return;
		}

		headers['Content-Encoding'] = 'gzip';
		res.writeHead(code, headers);
		res.end(data, ENCODING);
	});

	return self;
};

http.ServerResponse.prototype.throw400 = function(problem) {
	this.controller && this.controller.subscribe.success();
	framework.response400(this.req, this, problem);
};

http.ServerResponse.prototype.throw401 = function(problem) {
	this.controller && this.controller.subscribe.success();
	framework.response401(this.req, this, problem);
};

http.ServerResponse.prototype.throw403 = function(problem) {
	this.controller && this.controller.subscribe.success();
	framework.response403(this.req, this, problem);
};

http.ServerResponse.prototype.throw404 = function(problem) {
	this.controller && this.controller.subscribe.success();
	framework.response404(this.req, this, problem);
};

http.ServerResponse.prototype.throw408 = function(problem) {
	this.controller && this.controller.subscribe.success();
	framework.response408(this.req, this, problem);
};

http.ServerResponse.prototype.throw431 = function(problem) {
	this.controller && this.controller.subscribe.success();
	framework.response431(this.req, this, problem);
};

http.ServerResponse.prototype.throw500 = function(error) {
	this.controller && this.controller.subscribe.success();
	framework.response500(this.req, this, error);
};

http.ServerResponse.prototype.throw501 = function(problem) {
	this.controller && this.controller.subscribe.success();
	framework.response501(this.req, this, problem);
};

/**
 * Responds with a static file
 * @param {Function} done Optional, callback.
 * @return {Response}
 */
http.ServerResponse.prototype.continue = function(done) {
	var self = this;
	if (self.headersSent) {
		done && done();
		return self;
	}
	self.controller && self.controller.subscribe.success();
	framework.responseStatic(self.req, self, done);
	return self;
};

/**
 * Response custom content
 * @param {Number} code
 * @param {String} body
 * @param {String} type
 * @param {Boolean} compress Disallows GZIP compression. Optional, default: true.
 * @param {Object} headers Optional, additional headers.
 * @return {Response}
 */
http.ServerResponse.prototype.content = function(code, body, type, compress, headers) {
	var self = this;
	if (self.headersSent)
		return self;
	self.controller && self.controller.subscribe.success();
	framework.responseContent(self.req, self, code, body, type, compress, headers);
	return self;
};

/**
 * Response redirect
 * @param {String} url
 * @param {Boolean} permanent Optional, default: false.
 * @return {Framework}
 */
http.ServerResponse.prototype.redirect = function(url, permanent) {
	var self = this;
	if (self.headersSent)
		return self;
	self.controller && self.controller.subscribe.success();
	framework.responseRedirect(self.req, self, url, permanent);
	return self;
};

/**
 * Responds with a file
 * @param {String} filename
 * @param {String} download Optional, a download name.
 * @param {Object} headers Optional, additional headers.
 * @param {Function} done Optional, callback.
 * @return {Framework}
 */
http.ServerResponse.prototype.file = function(filename, download, headers, done) {
	var self = this;
	if (self.headersSent) {
		done && done();
		return self;
	}
	self.controller && self.controller.subscribe.success();
	framework.responseFile(self.req, self, filename, download, headers, done);
	return self;
};

/**
 * Responds with a stream
 * @param {String} contentType
 * @param {Stream} stream
 * @param {String} download Optional, a download name.
 * @param {Object} headers Optional, additional headers.
 * @param {Function} done Optional, callback.
 * @return {Framework}
 */
http.ServerResponse.prototype.stream = function(contentType, stream, download, headers, done, nocompress) {
	var self = this;
	if (self.headersSent) {
		done && done();
		return self;
	}

	self.controller && self.controller.subscribe.success();
	framework.responseStream(self.req, self, contentType, stream, download, headers, done, nocompress);
	return self;
};

/**
 * Responds with an image
 * @param {String or Stream} filename
 * @param {String} fnProcess
 * @param {Object} headers Optional, additional headers.
 * @param {Function} done Optional, callback.
 * @return {Framework}
 */
http.ServerResponse.prototype.image = function(filename, fnProcess, headers, done) {
	var self = this;
	if (self.headersSent) {
		done && done();
		return self;
	}
	self.controller && self.controller.subscribe.success();
	framework.responseImage(self.req, self, filename, fnProcess, headers, done);
	return self;
};

/**
 * Response JSON
 * @param {Object} obj
 * @return {Response}
 */
http.ServerResponse.prototype.json = function(obj) {
	var self = this;
	return self.send(200, obj, 'application/json');
};

var _tmp = http.IncomingMessage.prototype;

http.IncomingMessage.prototype = {

	get ip() {

		var self = this;
		if (self._ip)
			return self._ip;

		//  x-forwarded-for: client, proxy1, proxy2, ...
		var proxy = self.headers['x-forwarded-for'];
		if (proxy)
			self._ip = proxy.split(',', 1)[0] || self.connection.remoteAddress;
		else if (!self._ip)
			self._ip = self.connection.remoteAddress;

		return self._ip;
	},

	get query() {
		var self = this;
		if (self._dataGET)
			return self._dataGET;
		self._dataGET = framework.onParseQuery(self.uri.query);
		return self._dataGET;
	},

	set query(value) {
		this._dataGET = value;
	},

	get subdomain() {

		var self = this;

		if (self._subdomain)
			return self._subdomain;

		var subdomain = self.uri.host.toLowerCase().replace(/^www\./i, '').split('.');
		if (subdomain.length > 2)
			self._subdomain = subdomain.slice(0, subdomain.length - 2); // example: [subdomain].domain.com
		else
			self._subdomain = null;

		return self._subdomain;
	},

	get host() {
		return this.headers['host'];
	},

	get split() {
		if (this.$path)
			return this.$path;
		return this.$path = framework_internal.routeSplit(this.uri.pathname, true);
	},

	get secured() {
		return this.uri.protocol === 'https:' || this.uri.protocol === 'wss:';
	},

	get language() {
		if (!this.$language)
			this.$language = (((this.headers['accept-language'] || '').split(';')[0] || '').split(',')[0] || '').toLowerCase();
		return this.$language;
	},

	get mobile() {
		if (this.$mobile === undefined)
			this.$mobile = REG_MOBILE.test(this.headers['user-agent']);
		return this.$mobile;
	},

	get robot() {
		if (this.$robot === undefined)
			this.$robot = REG_ROBOT.test(this.headers['user-agent']);
		return this.$robot;
	},

	set language(value) {
		this.$language = value;
	}
};

// Handle errors of decodeURIComponent
function $decodeURIComponent(value) {
	try
	{
		return decodeURIComponent(value);
	} catch (e) {
		return value;
	}
};

http.IncomingMessage.prototype.__proto__ = _tmp;

/**
 * Signature request (user-agent + ip + referer + current URL + custom key)
 * @param {String} key Custom key.
 * @return {Request}
 */
http.IncomingMessage.prototype.signature = function(key) {
	var self = this;
	return framework.encrypt((self.headers['user-agent'] || '') + '#' + self.ip + '#' + self.url + '#' + (key || ''), 'request-signature', false);
};

/**
 * Disable HTTP cache for current request
 * @return {Request}
 */
http.IncomingMessage.prototype.noCache = function() {
	var self = this;
	delete self.headers['if-none-match'];
	delete self.headers['if-modified-since'];
	return self;
};

http.IncomingMessage.prototype.behaviour = function(type) {

	if (!framework.behaviours)
		return false;

	var url = this.url;

	if (!this.isStaticFile && url[url.length - 1] !== '/')
		url += '/';

	var current = framework.behaviours['*'];
	var value = false;

	// global
	if (current !== undefined) {
		current = current[type];
		if (current !== undefined)
			value = current;
	}

	// by specific
	current = framework.behaviours[url];
	if (current === undefined)
		return value; // responds with global

	current = current[type];

	if (current === undefined)
		return value; // responds with global

	return current;
};

/**
 * Read a cookie from current request
 * @param {String} name Cookie name.
 * @return {String} Cookie value (default: '')
 */
http.IncomingMessage.prototype.cookie = function(name) {

	var self = this;
	if (self.cookies)
		return $decodeURIComponent(self.cookies[name] || '');

	var cookie = self.headers['cookie'];
	if (!cookie)
		return '';

	self.cookies = {};

	var arr = cookie.split(';');

	for (var i = 0, length = arr.length; i < length; i++) {
		var line = arr[i].trim();
		var index = line.indexOf('=');
		if (index !== -1)
			self.cookies[line.substring(0, index)] = line.substring(index + 1);
	}

	return $decodeURIComponent(self.cookies[name] || '');
};

/**
 * Read authorization header
 * @return {Object}
 */
http.IncomingMessage.prototype.authorization = function() {

	var self = this;
	var authorization = self.headers['authorization'];

	if (!authorization)
		return HEADERS.authorization;

	var result = { user: '', password: '', empty: true };

	try {
		var arr = new Buffer(authorization.replace('Basic ', '').trim(), 'base64').toString(ENCODING).split(':');
		result.user = arr[0] || '';
		result.password = arr[1] || '';
		result.empty = !result.user || !result.password;
	} catch (e) {}

	return result;
};

/**
 * Authorization for custom delegates
 * @param  {Function(err, userprofile, isAuthorized)} callback
 * @return {Request}
 */
http.IncomingMessage.prototype.authorize = function(callback) {

	var auth = framework.onAuthorize;

	if (!auth) {
		callback(null, null, false);
		return this;
	}

	var req = this;

	auth(req, req.res, req.flags, function(isAuthorized, user) {
		if (typeof(isAuthorized) !== 'boolean') {
			user = isAuthorized;
			isAuthorized = !user;
		}
		req.isAuthorized = isAuthorized;
		callback(null, user, isAuthorized);
	});

	return this;
};

/**
 * Clear all uplaoded files
 * @private
 * @param {Boolean} isAuto
 * @return {Request}
 */
http.IncomingMessage.prototype.clear = function(isAuto) {

	var self = this;
	var files = self.files;

	if (!files || (isAuto && self._manual))
		return self;

	self.body = null;
	self.query = null;
	self.cookies = null;

	var length = files.length;
	if (!length)
		return self;

	var arr = [];
	for (var i = 0; i < length; i++)
		files[i].rem && arr.push(files[i].path);

	framework.unlink(arr);
	self.files = null;
	return self;
};

/**
 * Get host name from URL
 * @param {String} path Additional path.
 * @return {String}
 */
http.IncomingMessage.prototype.hostname = function(path) {

	var self = this;
	var uri = self.uri;

	if (path && path[0] !== '/')
		path = '/' + path;

	return uri.protocol + '//' + uri.hostname + (uri.port && uri.port !== 80 ? ':' + uri.port : '') + (path || '');
};

var framework = new Framework();
global.framework = global.F = module.exports = framework;
global.Controller = Controller;

process.on('uncaughtException', function(e) {

	if (e.toString().indexOf('listen EADDRINUSE') !== -1) {
		if (typeof(process.send) === 'function')
			process.send('eaddrinuse');
		console.log('\nThe IP address and the PORT is already in use.\nYou must change the PORT\'s number or IP address.\n');
		process.exit('SIGTERM');
		return;
	}

	if (framework.isTest) {
		// HACK: this method is created dynamically in framework.testing();
		framework.testContinue && framework.testContinue(e);
		return;
	}

	framework.error(e, '', null);
});

function fsFileRead(filename, callback) {
	U.queue('framework.files', F.config['default-maximum-file-descriptors'], function(next) {
		Fs.readFile(filename, function(err, result) {
			next();
			callback(err, result);
		});
	});
};

function fsFileExists(filename, callback) {
	U.queue('framework.files', F.config['default-maximum-file-descriptors'], function(next) {
		Fs.lstat(filename, function(err, stats) {
			next();
			callback(!err && stats.isFile(), stats ? stats.size : 0, stats ? stats.isFile() : false);
		});
	});
};

function fsStreamRead(filename, options, callback) {

	if (!callback) {
		callback = options;
		options = undefined;
	}

	var opt;

	if (options) {
		opt = HEADERS.fsStreamReadRange
		opt.start = options.start;
		opt.end = options.end;
	} else
		opt = HEADERS.fsStreamRead;

	U.queue('framework.files', F.config['default-maximum-file-descriptors'], function(next) {
		var stream = Fs.createReadStream(filename, opt);
		stream.on('error', noop);
		callback(stream, next);
	});
}

/**
 * Prepare URL address to temporary key (for caching)
 * @param {ServerRequest or String} req
 * @return {String}
 */
function createTemporaryKey(req) {
	return (req.uri ? req.uri.pathname : req).replace(TEMPORARY_KEY_REGEX, '-').substring(1);
}

process.on('SIGTERM', () => framework.stop());
process.on('SIGINT', () => framework.stop());
process.on('exit', () => framework.stop());

process.on('message', function(msg, h) {

	if (typeof(msg) !== 'string') {
		framework.emit('message', msg, h);
		return;
	}

	if (msg === 'debugging') {
		framework_utils.wait(() => framework.isLoaded, function() {
			framework.isLoaded = undefined;
			framework.console();
		}, 10000, 500);
		return;
	}

	if (msg === 'reconnect') {
		framework.reconnect();
		return;
	}

	if (msg === 'reconfigure') {
		framework._configure();
		framework._configure_versions();
		framework._configure_workflows();
		framework._configure_sitemap();
		framework.emit(msg);
		return;
	}

	if (msg === 'reset') {
		// framework.clear();
		framework.cache.clear();
		return;
	}

	if (msg === 'stop' || msg === 'exit') {
		framework.stop();
		return;
	}

	framework.emit('message', msg, h);
});

function prepare_error(e) {
	if (!framework.isDebug || !e)
		return '';
	if (e instanceof ErrorBuilder)
		return ' :: ' + e.plain();
	if (e.stack)
		return ' :: ' + e.stack.toString();
	return ' :: ' + e.toString();
}

function prepare_filename(name) {
	if (name[0] === '@')
		return framework.isWindows ? framework_utils.combine(framework.config['directory-temp'], name.substring(1)) : framework.path.package(name.substring(1));
	return framework_utils.combine('/', name);
}

function prepare_staticurl(url, isDirectory) {
	if (!url)
		return url;
	if (url[0] === '~') {
		if (isDirectory)
			return framework_utils.path(url.substring(1));
	} else if (url.substring(0, 2) === '//' || url.substring(0, 6) === 'http:/' || url.substring(0, 7) === 'https:/')
		return url;
	return url;
}

function prepare_isomorphic(name) {
	name = name.replace(/\.js$/i, '');

	var content = framework.isomorphic[name];
	if (content)
		content = content.$$output;
	else
		content = '';

	return 'if(window["isomorphic"]===undefined)window.isomorphic=window.I={};isomorphic["' + name + '"]=(function(framework,F,U,utils,Utils,is_client,is_server){var module={},exports=module.exports={};' + content + ';return exports;})(null,null,null,null,null,true,false)';
}

function isGZIP(req) {
	var ua = req.headers['user-agent'];
	return ua && ua.lastIndexOf('Firefox') !== -1;
}

function prepare_viewname(value) {
	// Cleans theme name
	return value.substring(value.indexOf('/', 2) + 1);
}

function existsSync(filename, file) {
	try {
		var val = Fs.statSync(filename);
		return val ? (file ? val.isFile() : true) : false;
	} catch (e) {
		return false;
	}
}

function async_middleware(index, req, res, middleware, callback, options, controller) {

	if (res.success || res.headersSent) {
		// Prevents timeout
		controller && controller.subscribe.success();
		callback = null;
		return;
	}

	var name = middleware[index++];
	if (!name)
		return callback && callback();

	var item = framework.routes.middleware[name];
	if (!item) {
		framework.error('Middleware not found: ' + name, null, req.uri);
		return async_middleware(index, req, res, middleware, callback, options, controller);
	}

	var output = item.call(framework, req, res, function(err) {

		if (err) {
			res.throw500(err);
			callback = null;
			return;
		}

		async_middleware(index, req, res, middleware, callback, options, controller);
	}, options, controller);

	if (output !== false)
		return;

	callback = null;
};

global.setTimeout2 = function(name, fn, timeout) {
	var key = ':' + name;
	framework.temporary.internal[key] && clearTimeout(framework.temporary.internal[key]);
	return framework.temporary.internal[key] = setTimeout(fn, timeout);
};

global.clearTimeout2 = function(name) {
	var key = ':' + name;

	if (framework.temporary.internal[key]) {
		clearTimeout(framework.temporary.internal[key]);
		framework.temporary.internal[key] = undefined;
		return true;
	}

	return false;
};

// Default action for workflow routing
function controller_json_workflow(id) {
	var self = this;
	self.id = id;
	self.$exec(self.route.workflow, self, self.callback());
}

// Because of controller prototypes
// It's used in F.view() and F.viewCompile()
const EMPTYCONTROLLER = new Controller('', null, null, null, '');
EMPTYCONTROLLER.isConnected = false;