text:
// Все заварачиваем в анонимную функцию, чтобы сложно было редактировать внутренние переменные из консоли и т.п.
(function(){
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  SHA-256 implementation in JavaScript                              (c) Chris Veness 2002-2014  */
/*                                                                                */
/*  - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html                              */
/*        http://csrc.nist.gov/groups/ST/toolkit/examples.html                                    */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/* jshint node:true *//* global define, escape, unescape */
//'use strict';

/**
 * SHA-256 hash function reference implementation.
 *
 * @namespace
 */

 var Sha256 = {};

/**
 * Generates SHA-256 hash of string.
 *
 * @param   {string} msg - String to be hashed
 * @returns {string} Hash of msg as hex character string
 */
Sha256.hash = function(msg) {
    // convert string to UTF-8, as SHA only deals with byte-streams
    msg = msg.utf8Encode();

    // constants [§4.2.2]
    var K = [
        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
        0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
        0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
        0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
        0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
        0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
        0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 ];
    // initial hash value [§5.3.1]
    var H = [
        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 ];

    // PREPROCESSING

    msg += String.fromCharCode(0x80);  // add trailing '1' bit (+ 0's padding) to string [§5.1.1]

    // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
    var l = msg.length/4 + 2; // length (in 32-bit integers) of msg + '1' + appended length
    var N = Math.ceil(l/16);  // number of 16-integer-blocks required to hold 'l' ints
    var M = new Array(N);
    var i;

    for (i=0; i<N; i++) {
        M[i] = new Array(16);
        for (var j=0; j<16; j++) {  // encode 4 chars per integer, big-endian encoding
            M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
                      (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
        } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
    }
    // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
    // note: most significant word would be (len-1)*8 >>> 32, but since JS converts
    // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
    M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14]);
    M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;

    // HASH COMPUTATION [§6.1.2]

    var W = new Array(64); var a, b, c, d, e, f, g, h;
    for(i=0; i<N; i++) {

        // 1 - prepare message schedule 'W'
        var t;
        for(t = 0;  t<16; t++) W[t] = M[i][t];
        for(t = 16; t<64; t++) W[t] = (Sha256.σ1(W[t-2]) + W[t-7] + Sha256.σ0(W[t-15]) + W[t-16]) & 0xffffffff;

        // 2 - initialise working variables a, b, c, d, e, f, g, h with previous hash value
        a = H[0]; b = H[1]; c = H[2]; d = H[3]; e = H[4]; f = H[5]; g = H[6]; h = H[7];

        // 3 - main loop (note 'addition modulo 2^32')
        for(t = 0; t<64; t++) {
            var T1 = h + Sha256.Σ1(e) + Sha256.Ch(e, f, g) + K[t] + W[t];
            var T2 =     Sha256.Σ0(a) + Sha256.Maj(a, b, c);
            h = g;
            g = f;
            f = e;
            e = (d + T1) & 0xffffffff;
            d = c;
            c = b;
            b = a;
            a = (T1 + T2) & 0xffffffff;
        }
         // 4 - compute the new intermediate hash value (note 'addition modulo 2^32')
        H[0] = (H[0]+a) & 0xffffffff;
        H[1] = (H[1]+b) & 0xffffffff;
        H[2] = (H[2]+c) & 0xffffffff;
        H[3] = (H[3]+d) & 0xffffffff;
        H[4] = (H[4]+e) & 0xffffffff;
        H[5] = (H[5]+f) & 0xffffffff;
        H[6] = (H[6]+g) & 0xffffffff;
        H[7] = (H[7]+h) & 0xffffffff;
    }

    return Sha256.toHexStr(H[0]) + Sha256.toHexStr(H[1]) + Sha256.toHexStr(H[2]) + Sha256.toHexStr(H[3]) +
           Sha256.toHexStr(H[4]) + Sha256.toHexStr(H[5]) + Sha256.toHexStr(H[6]) + Sha256.toHexStr(H[7]);
};

/**
 * Rotates right (circular right shift) value x by n positions [§3.2.4].
 * @private
 */
Sha256.ROTR = function(n, x) {
    return (x >>> n) | (x << (32-n));
};

/**
 * Logical functions [§4.1.2].
 * @private
 */
Sha256.Σ0  = function(x) { return Sha256.ROTR(2,  x) ^ Sha256.ROTR(13, x) ^ Sha256.ROTR(22, x); };
Sha256.Σ1  = function(x) { return Sha256.ROTR(6,  x) ^ Sha256.ROTR(11, x) ^ Sha256.ROTR(25, x); };
Sha256.σ0  = function(x) { return Sha256.ROTR(7,  x) ^ Sha256.ROTR(18, x) ^ (x>>>3);  };
Sha256.σ1  = function(x) { return Sha256.ROTR(17, x) ^ Sha256.ROTR(19, x) ^ (x>>>10); };
Sha256.Ch  = function(x, y, z) { return (x & y) ^ (~x & z); };
Sha256.Maj = function(x, y, z) { return (x & y) ^ (x & z) ^ (y & z); };

/**
 * Hexadecimal representation of a number.
 * @private
 */
Sha256.toHexStr = function(n) {
    // note can't use toString(16) as it is implementation-dependant,
    // and in IE returns signed numbers when used on full words
    var s='', v;
    for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
    return s;
};

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

/** Extend String object with method to encode multi-byte string to utf8
 *  - monsur.hossa.in/2012/07/20/utf-8-in-javascript.html */
if (typeof String.prototype.utf8Encode == 'undefined') {
    String.prototype.utf8Encode = function() {
        return unescape( encodeURIComponent( this ) );
    };
}

/** Extend String object with method to decode utf8 string to multi-byte */
if (typeof String.prototype.utf8Decode == 'undefined') {
    String.prototype.utf8Decode = function() {
        try {
            return decodeURIComponent( escape( this ) );
        } catch (e) {
            return this; // invalid UTF-8? return as-is
        }
    };
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
if (typeof module != 'undefined' && module.exports) module.exports = Sha256; // CommonJs export
if (typeof define == 'function' && define.amd) define([], function() { return Sha256; }); // AMD

// Не пинать, писал без образца, не подглядывая в литературу и, как говорится, ... ... и в продакшн.
var BTS = function() {
	this.root = undefined;
	this.vals = [];
};
BTS.prototype.get = function(val, nod, type) {
	if(!nod) {
		nod = this.root;
		if(!nod) return;
	}
	if(nod.v === val) {
		return nod;
	} else if(nod.v > val && nod.l) {
		return this.get(val, nod.l, type);
	} else if(nod.v < val && nod.r) {
		return this.get(val, nod.r, type);
	}
	if(type) {
		return nod;
	}
	return;
};
BTS.prototype.searchOrAutocomplete = function(val, count) {
	var nod = this.get(val, 0, 1);
	if(nod && nod.v == val) return [nod];
	var endVal = val.substr(0, val.length - 1) + String.fromCharCode(val.charCodeAt(val.length - 1) + 1);
	var ret = [];
	while(nod && nod.v < endVal && (!count || ret.length < count)) {
		if(nod.v > val) {
			ret.push(nod);
		}
		nod = this.next(nod);
	}
	return ret;
};
BTS.prototype.set = function(val, o) {
	var nod = {};
	nod.v = val;
	nod.os = [o];
	if(this.vals.indexOf(val) == -1) {
		this.vals.push(val);
	}
	if(!this.root) {
		this.root = nod;
		return  nod;
	}
	var p = this.get(val, undefined, 1);
	nod.p = p;
	if(val
 p.v) {
		p.r = nod;
	} else if(val === p.v) {
		if(p.os.indexOf(o) == -1) {
			p.os.push(o);
		}
		return p;
	}
	return nod;
};

BTS.prototype.rm = function(nod, o) {
	if(!isObject(nod)) {
		nod = this.get(nod);
		if(!nod) {
			return;
		}
	}
	if(o !== undefined) {
		var ind;
		while(-1 !== (ind = nod.os.indexOf(o))) {
			nod.os.splice(ind, 1);
		}
		if(nod.os.length) return;
	}
	this.vals.splice(this.vals.indexOf(nod.v), 1);
	if(!nod.l && !nod.r) {
		if(nod.p) {
			if(nod.p.r === nod) {
				nod.p.r = undefined;
			}
			if(nod.p.l === nod) {
				nod.p.l = undefined;
			}
		}
		if(nod === this.root) {
			this.root = undefined;
		}
	} else if(nod.l && nod.r) {
		var replacer = this.get(nod.v, nod.r, 1);
		if(replacer.p !== nod) {
			replacer.p.l = replacer.r;
			if(replacer.r) replacer.r.p = replacer.p;
		}
		replacer.p = nod.p;
		if(nod.p) {
			if(nod.p.l === nod) {
				nod.p.l = replacer;
			} else if(nod.p.r === nod) {
				nod.p.r = replacer;
			}
		}
		replacer.l = nod.l;
		replacer.l.p = replacer;
		var oldR = nod.r;
		if(nod.r !== replacer) {
			replacer.r = nod.r;
			replacer.r.p = replacer;
		}
		if(nod === this.root) {
			this.root = replacer;
		}
	} else if(nod.l || nod.r) {
		var ch = 0;
		if(nod.l) {
			ch = nod.l;
		} else {
			ch = nod.r;
		}
		if(nod.p) {
			if(nod.p.l === nod) {
				nod.p.l = ch;
			} else if(nod.p.r === nod) {
				nod.p.r = ch;
			}
		}
		ch.p = nod.p;
		if(nod === this.root) {
			this.root = ch;
		}
	}
};
BTS.prototype.next = function(nod) {
	if(!isObject(nod)) {
		nod = this.get(nod);
		if(!nod) {
			return;
		}
	}
	if(nod.r) {
		par = nod.r;
		return this.get(nod.v, nod.r, 1);
	} else {
		var ps = [];
		var par = nod;
		do {
			ps.push(par);
			if(par.p) {
				if(par.p.l && par.p.l === par) {
					return par.p;
				} else if(par.p.r && par.p.r !== par) {
					var nextInRi = this.get(nod.v, par.p.r, 1);
					/*if(par.p.v < nextInRi.v) {
						return par.p;
					}*/
					return nextInRi;
				}
			}
			if(par === this.root) {
				return;
			}
			par = par.p;
		} while (par);
	}

};
BTS.prototype.first = function() {
	if(this.root && isString(this.root.v)) {
		return this.get('', undefined, 1);
	} else {
		return this.get(0, undefined, 1);
	}
};
BTS.prototype.random = function() {
	return this.get(getRandomElementFromArray(this.vals));
};
BTS.prototype.clear = function() {
	this.root = undefined;
	this.vals = [];
};

BTS.getMixArrForBTS = function(arr) {
	// перемешивает упорядоченный по возрастанию массив, так чтобы в дерево создалось оптимизированным.
	var ret = [];
	var len = arr.length;
	var iters = Math.ceil(Math.log(len));
	var step = Math.pow(2, iters);
	for(var iter = 0; iter <= iters; iter++) {
		for(var i = 0; i <= len; i += step) {
			if(i % (step*2)) {
				ret.push(arr[i - 1]);
			}
		}
		step /= 2;
	}
	return ret;
};

BTS.prototype.html = function(node) {
  if(!node) {
    node = this.root;
  }
  var ret = '

'+node.v+'	';
  if(node.l) {
    ret += this.html(node.l);
  }
  ret += '

';
  if(node.r) {
    ret += this.html(node.r);
  }
  ret += '

';
  return ret;
};

var myBts = new BTS();

myBts.set('my', {text:'my'});
myBts.set('first', {text:'first'});
myBts.set('hello', {text:'hello'});
myBts.set('world', {text:'world'});
var productName = 'learnen.ru'; // название данного сайта.
var productVersion = '0.7.6'; // версия.
var devMode;
// Следующие переменные преимущественно для укорачивания минимизированного кода.
var win = window; // если кто додумается подменить window .
var javascriptUrl = 'javascrip';
javascriptUrl += 't:void(0)'; // иначе jshint ругается.
var windowSetTimeout = function(func, time) {
	return win.setTimeout(func, time);
};
var windowClearTimeout = function(id) {
	return win.clearTimeout(id);
};
var windowSetInterval = function(func, time) {
	return win.setInterval(func, time);
};
var windowClearInterval = function(id) {
	return win.clearInterval(id);
};
var getRandomElementFromArray = function(obj) {
	var rand = Math.round(Math.random()*(obj.length - 1));
	return obj[rand];
};
var isNumber = function(obj) {
	if(obj === undefined) return false;
	if(typeof obj == 'number') return true;
};
var isString = function(obj) {
	if(obj === undefined) return false;
	if(typeof obj == 'string') return true;
};
var isObject = function(obj) {
	if(!obj) return false;
	if(typeof obj == 'object') return true;
};
var isArray = function(obj) {
	if(!obj) return false;
	return Array.isArray(obj);
};
var isFunction = function(obj) {
	if(obj === undefined) return false;
	if(typeof obj == 'function') return true;
};
var isDomNode = function(obj) {
	return obj instanceof HTMLElement;
};
var removeFromArrayByValue = function(arr, val, once) {
	// вырезает из переданного arr все значения === val
	// Осторожно, новый массив не создается, изменения вносятся в переданый массив.
	for(var i = arr.length; i--;) {
		if(arr[i] === val) {
			arr.splice(i, 1);
			if(once) return arr;
		}
	}
	return arr;
};
var uniqueArray = function(arr) {
	// Осторожно, новый массив не создается, изменения вносятся в переданый массив.
	var a = [];
	for(var i = arr.length; i--;) {
		if(a.indexOf(arr[i]) == -1) {
			a.push(arr[i]);
		} else {
			arr.splice(i, 1);
		}
	}
	return arr;
};
var shuffleArray = function(arr) {
	// Перемешивает массив. Работает в среднем в 100 раз дольше чем простой перебор, или в 10 раз дольше чем concat().
	for(var i = arr.length - 1; i > 0; i--) {
		var j = Math.floor(Math.random() * (i + 1));
		var tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}
	return arr;
};
var pushAll = function(arr, elems) {
	// Вставляет в ам все элементы из elems.
	// Не использовал concat по соображениям упрощения кода.
	for(var i = 0; i < elems.length; i++) {
		arr.push(elems[i]);
	}
	return arr;
};
var encodeToJSON = function(obj) {
	// Очень упрощенный сериализатор в json - строку.
	//return win.JSON.stringify(obj);
	var ret = '';
	if(isObject(obj)) {
		var i;
		if(isArray(obj)) {
			for(i = 0; i < obj.length; i++) {
				if(ret) ret += ',';
				ret += encodeToJSON(obj[i]);
			}
			ret = '[' + ret + ']';
		} else {
			for(i in obj) {
				if(i !== '__proto__' && !isFunction(obj[i])) {
					if(ret) ret += ',';
					ret += '"' + i + '":' + encodeToJSON(obj[i]);
				}
			}
			ret = '{' + ret + '}';
		}
	} else if(isString(obj)) {
		ret = '"' + obj + '"';
	} else {
		ret = obj;
		if(!ret) ret = 0;
	}
	return ret;
};
var decodeJSON = function(str) {
	return win.JSON.parse(str);
};

var ls = win.localStorage;
var isLocalStorage; // чтобы не путать настоящий win.localStorage и его заглушку.
if(ls) {
	isLocalStorage = 1;
	try {
		ls['lastVisit' || 0] = new Date().getTime();
	} catch(e) {
		ls = {}; // так как нет прав на запись в localStorage.
	}
} else {
	ls = {}; // устанавливаем заглушку вместо localStorage.
}

var cloneStruct = function(obj) {
	// возвращает другой объект, у которого точно такие же значения свойств(/элементов).
	if(!isObject(obj)) return obj;
	var ret;
	if(isArray(obj)) {
		ret = [];
		for(var i = 0; i < obj.length; i++) {
			ret[i] = cloneStruct(obj[i]);
		}
	} else {
		ret = {};
		for(var attr in obj) {
			if(obj.hasOwnProperty(attr)) ret[attr] = cloneStruct(obj[attr]);
		}
	}
	return ret;
};
var isEquals = function(a, b) {
	if(a == b) return 1;
	if(isObject(a) && b) {
		var key;
		for(key in a) {
			if(!isEquals(a[key], b[key])) return 0;
		}
		for(key in b) {
			if(!isEquals(a[key], b[key])) return 0;
		}
		return 1;
	}
	return 0;
};
var randomInt = function(n) {
	if(n === undefined) n = 1;
	var ret = Math.floor(Math.random() * (n + 1));
	if(ret > n) ret = n; // на случай, если Math.random вернет 1 .
	return ret;
};
var randomBool = function(d) {
	if(!d) return false;
	if(d === 1 || Math.random() < d) {
		return true;
	}
};
var hash = function(data) {
	// такой хеш можно использовать только на 32битных целых
	var key = data;
	key += ~(key << 16);
	key ^=  (key >>  5);
	key +=  (key <<  3);
	key ^=  (key >> 13);
	key += ~(key <<  9);
	key ^=  (key >> 17);
	return key;
};
var trim = function(str) {
	// Удаляет пробелы из начала, из конца, перед знаками припинания, сокращает повторяющиеся пробелы до одного.
	var ret = '';
	var prevSpace = false;
	for(var i = 0; i < str.length; i++) {
		if(str[i] !== ' ') {
			if(prevSpace && (str[i] !== '.' && str[i] !== ',' && str[i] !== '!' && str[i] !== '?') && ret) ret += ' ';
			ret += str[i];
			prevSpace = false;
		} else {
			prevSpace = true;
		}
	}
	return ret;
};
var joins = function() {
	// Составляет из кусочков текста строку, обрабатывая запятые.
	var commaNext, commaStart, commaEnd, str, a, ret = '';
	for(var i = 0; i < arguments.length; i++) {
		a = arguments[i];
		str = 0;
		if(isString(a) || isNumber(a)) {
			if(a === ',') {
				commaNext = 1; // значит следующую непустую строку нужно отделить запятой.
			} else if(a) {
				str = a;
			}
		} else if(isArray(a)) { // если внутри есть массив, то он обрабатывается отдельно.
			if(a[0] === ',' && a[a.length - 1] === ',' ) {
				a = a.slice(1, a.length - 1);
				commaStart = i;
				commaEnd = i;
			} else if(a[0] === ',') {
				a = a.slice(1);
				commaStart = i;
			}
			str = joins.apply(win, a);
		}
		if(str) {
			if(commaNext || commaStart === i) {
				if(ret && ret[ret.length - 1] !== ',') ret += ',';
				commaNext = 0;
			}
			if(ret) ret += ' ';
			ret += str;
			if(commaEnd === i) {
				commaNext = 1;
			}
		}
	}
	return ret;
};
var addEventsMixin;
(function() {
	var mixin = {};
	mixin.addEventListener = function(eventName, cb, group) { // в group, например можно передавать вызывающий объект, а после удалить всех слушателей для этого вызывающего объекта.
		if(isArray(eventName)) {
			for(var i = 0; i < eventName.length; i++) {
				this.addEventListener(eventName[i], cb, group);
			}
		} else {
			if(!this._events) this._events = {};
			if(!this._events[eventName]) this._events[eventName] = [];
			this._events[eventName].push(cb);
			this._events[eventName].push(group);
		}
	};
	mixin.removeEventListener = function(eventName, cb, group) {
		if(this._events) {
			if(!eventName) eventName = Object.keys(this._events);
			var i;
			if(isArray(eventName)) {
				for(i = 0; i < eventName.length; i++) {
					this.removeEventListener(eventName[i], cb, group);
				}
			} else if(this._events[eventName]) {
				if(cb === undefined && group === undefined) {
					this._events[eventName] = 0;
				} else {
					var evs = this._events[eventName];
					for(i = evs.length - 2; i >= 0; i -= 2) {
						if(group !== undefined && evs[i + 1] !== group || cb && evs[i] !== cb) continue;
						evs.splice(i, 2);
					}
				}
			}
		}
	};
	mixin.trigger = function(nam) {
		var eventName = nam;
		if(this._events && this._events[eventName]) {
			var firstArg;
			for(var i = 0; i < this._events[eventName].length; i += 2) {
				if(isFunction(this._events[eventName][i])) {
					var func = this._events[eventName][i];
					var ev = this._events[eventName][i];
					firstArg = {}; // В коллбеки передаем первым параметром объект с данными о событии.
					firstArg.name = eventName;
					firstArg.group = this._events[eventName][i + 1];
					firstArg.caller = this;
					var args = arguments;
					args[0] = firstArg;
					func.apply(window, args);
				}
			}
		}
	};
	mixin.oneTriggerInNextTick = function(eventName) {
		// Позволяет единожды вызвать триггер с задержкой.
		if(this._eventsPause) {
			if(!(eventName in this._deferredTriggers)) this._deferredTriggers[eventName] = arguments;
			return;
		} else if(!this._eventsTimeout) {
			var th = this;
			var args = arguments;
			this._eventsTimeout = windowSetTimeout(function() {
				th._eventsTimeout = 0;
				th.trigger.apply(th, args);
			}, 0);
		}
	};
	mixin.pauseNextTickTriggers = function(time) {
		// Откладывает на заданное время вызовы oneTriggerInNextTick .
		var th = this;
		if(this._eventsPause) {
			windowClearTimeout(this._eventsPause);
		} else {
			this._deferredTriggers = {};
		}
		this._eventsPause = windowSetTimeout(function() {
			th.playNextTickTriggers();
		}, time || 2000);
	};
	mixin.playNextTickTriggers = function() {
		if(this._eventsPause) {
			windowClearTimeout(this._eventsPause);
			this._eventsPause = 0;
			var triggers = this._deferredTriggers;
			this._deferredTriggers = 0;
			for(var k in triggers) {
				this.oneTriggerInNextTick.apply(this, triggers[k]);
			}
		}
	};
	addEventsMixin = function(obj) {
		// Вставляет в obj методы для добавления и выполнения событий.
		if(obj.oneTriggerInNextTick) {
			// значит уже примиксирован.
		} else if(isFunction(obj)) { // Можно применять к класс-функции, в прототип добавятся необходимые методы.
			for(var m in mixin) {
				obj.prototype[k] = mixin[m];
			}
		} else {
			var f = function(k) {
				obj[k] = function() {
					return mixin[k].apply(obj, arguments);
				};
			};
			for(var k in mixin) {
				f(k);
			}
		}
	};
})();
var nextSID;
(function() {
	var ind = 1;
	nextSID = function() {
		return ind++;
	};
})();
var warning = function(obj) {
	var str;
	if(isObject(obj)) {
		if(obj instanceof Error) {
			throw obj;
		}
		str = obj.toString();
	} else if(isString(obj)) {
		str = obj;
	}
	var err;
	if(str) {
		err = new Error(str);
	} else {
		err = new Error();
	}
	console.log(obj, err.stack);
	var mes = 'warning: ' + (str || '') + '\n' + err.stack.toString();
	//alert(mes);
	// Должно писать в localStorage и отправлять сообщение на сервер, но не останавливать выполнение.
};

var addToOnloadBody;
var onloadBody;
(function() {
	var onloaded = 0;
	var onLoadFuncs = [];
	addToOnloadBody = function(f) {
		if(!onloaded) {
			if(onLoadFuncs.indexOf(f) == -1) {
				onLoadFuncs.push(f);
			}
		} else {
			f();
		}
	};
	onloadBody = function() {
		if(onloaded) return;
		onloaded = 1;
		//try {
			for(var i = 0; i < onLoadFuncs.length; i++) {
				onLoadFuncs[i]();
			}
		/*} catch(err) {
			warning(err);
		}*/
		onLoadFuncs = 0;
	};
})();
window['onloadBody' || 0] = onloadBody;

var getJSON = function(url, cb) {
	// аналог одноименной функции jquery.
	if(cb) {
		var sid = nextSID();
		window['getJSON_' + sid] = function(data) {
			var json = decodeJSON(data);
			if(cb) cb(json);
		};
		url += '&callback=getJSON_' + sid;
	}
	var resp_script = domCreater('script');
	resp_script.setAttribute('src', url);
	domAppendChild(document['head' || 0], resp_script);
	/*windowSetTimeout(function() {
		domRemove(resp_script);
	}, 0);*/
};
var autocompleteCallbacks = {};
window['ac' || 0] = function(text) {
	if(autocompleteCallbacks[text]) {
		autocompleteCallbacks[text](arguments);
		autocompleteCallbacks[text] = 0;
	}
};
var loadWordCallbacks = {};
window['lw' || 0] = function(json) {
	var text = json[1][0];
	if(loadWordCallbacks[json[1][0]]) {
		loadWordCallbacks[text](json);
		loadWordCallbacks[text] = 0;
	}
};
var getXmlHttp = function() {
	var xmlhttp;
	try {
		xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
	} catch(e) {
		try {
			xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
		} catch(E) {
			xmlhttp = false;
		}
	}
	if(!xmlhttp && typeof XMLHttpRequest != 'undefined') {
		xmlhttp = new XMLHttpRequest();
	}
	return xmlhttp;
};
var sendRequest = function(uri, obj, cb, typ) {
	// uri - адрес, на который посылать запрос.
	// obj - либо объект параметров, либо строка данных для POST запроса.
	if(!typ) typ = 'GET';
	if(typ === 'GET' && obj) {
		uri += '?';
		for(var k in obj) {
			uri += encodeURI(k) + '=' + encodeURI(obj[k]) + '&';
		}
	}
	var xmlhttp = getXmlHttp();
	xmlhttp.open(typ, uri, true);
	xmlhttp.onreadystatechange = function() {
		if (xmlhttp.readyState == 4) {
			if(xmlhttp.status == 200) {
				if(cb) cb(xmlhttp.responseText);
			}
		}
	};
	if(typ === 'POST' && obj) {
		if(isObject(obj)) {
			obj = encodeToJSON(obj);
		}
		xmlhttp.send(obj);
	} else {
		xmlhttp.send(null);
	}
};
var getCookie = function(name) {
	var matches = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=([^;]*)'));
	return matches ? decodeURIComponent(matches[1]) : undefined;
};
if(window.localStorage) {
	try {
		window.localStorage['lastVisit' || 0] = new Date().getTime();
	} catch (e) {//there was an error so...
		alert('Вы зашли в приватном режиме, многие функции сайта в этом режиме недоступны.');
	}
}
addToOnloadBody(function() {
	if(navigator.userAgent.indexOf('Opera Mini') > -1) {
		alert('Ваш браузер не поддерживается сайтом!');
	}
});
var isOnline = function() {
	return false;
};
// Общие данные о структурах языка
var enLang = {};
enLang.vowel = 'aeiou'; // гласные
enLang.getCountSyllables = function(str) {
	var ret = 0;
	for(var i = 0; i < str.length; i++) {
		for(var n = 0; n < enLang.vowel.length; n++) {
			if(str[i] == enLang.vowel[n]) {
				ret++;
				break;
			}
		}
	}
	return ret || 1;
};
// Общие данные о структурах языка
var ruLang = {};
ruLang.vowel = 'ауоыиэяюёе';
ruLang.fixWord = function(str) {
	// производит исправления по общим "понятиям" русского языка.
	if(str.substr(-2) === 'тю') {
		str = str.substr(0, str.length - 2) + 'чу';
	}
	str = str.replace('чю', 'чу');
	str = str.replace('щю', 'щу');
	str = str.replace('чя', 'ча');
	str = str.replace('щя', 'ща');
	str = str.replace('шя', 'ша'); // Удивительно, кажется все слова с таким сочитанием заменяются, остаются только иностранные фамилии, псевдонимы и подобное.
	str = str.replace('жы', 'ши');
	return str;
};
ruLang.getCountSyllables = function(str) {
	var ret = 0;
	for(var i = 0; i < str.length; i++) {
		for(var n = 0; n < ruLang.vowel.length; n++) {
			if(str[i] === ruLang.vowel[n]) {
				ret++;
				break;
			}
		}
	}
	return ret || 1;
};
var wordsTypes = {}; // Служит для регистрации типов слов со всеми необходимыми связаными функциями и коллекциями. Потом удобней итерироваться по этой коллекции.
var wordsTypesByNum = [];
var registerWordsType = function(name, collection, constructorEn, constructorRu, userCollection, userEmphasisCollection, inputConstructorEn, inputConstructorRu, baseCollection) {
	var obj = {};
	wordsTypes[name] = obj;
	obj.name = name;
	obj.collection = collection;
	obj.constructors = [constructorEn, constructorRu];
	obj.constructorEn = constructorEn;
	obj.constructorRu = constructorRu;
	obj.userCollection = userCollection;
	obj.userEmphasisCollection = userEmphasisCollection;
	obj.inputConstructors = [inputConstructorEn, inputConstructorRu];
	obj.inputConstructorEn = inputConstructorEn;
	obj.inputConstructorRu = inputConstructorRu;
	obj.baseCollection = baseCollection;
	wordsTypesByNum[obj.constructorEn.prototype.wordType] = obj;
};

addToOnloadBody(function() {
	registerWordsType(Word.prototype.wordTypeName, words, Word, Word, user.words, user.emphasis.words, createWordInput, createWordInput, baseWords);
	registerWordsType(Noun.prototype.wordTypeName, nouns, NounEn, NounRu, user.nouns, user.emphasis.nouns, createNounEnInput, createNounRuInput, baseNouns);
	registerWordsType(Verb.prototype.wordTypeName, verbs, VerbEn, VerbRu, user.verbs, user.emphasis.verbs, createVerbEnInput, createVerbRuInput, baseVerbs);
	registerWordsType(Adjective.prototype.wordTypeName, adjectives, AdjectiveEn, AdjectiveRu, user.adjectives, user.emphasis.adjectives, createAdjectiveEnInput, createAdjectiveRuInput, baseAdjectives);
	registerWordsType(Adverb.prototype.wordTypeName, adverbs, AdverbEn, AdverbRu, user.adverbs, user.emphasis.adverbs, createAdverbEnInput, createAdverbRuInput, baseAdverbs);
});

var loadWord = function(str, cb) {
	var word = words.searchByStr(str);
	if(word) {
		cb(word);
	} else {
		getJSON('http://' + productName + productSubfold + '/backend/lw.php?str=' + encodeURI(str));
		loadWordCallbacks[str] = function(json) {
			var lang = json[2];
			var word = new wordsTypesByNum[json[0]].constructors[lang](json[1]);
			wordsTypesByNum[json[0]].collection.addWords([word]);
			if(!lang) word.commonTranslations = json[3];
			cb(word);
		};
	}
};
var loadCommonTranslations = function(word, cb) {
	var arr = [];
	if(word.commonTranslations) {
		var loadTranslation = function(w) {
			arr.push(w);
			if(arr.length === word.commonTranslations.length) {
				wordsTypesByNum[word.wordType].collection.addWords(arr.concat(word));
				cb(arr);
			}
		};
		for(var i = 0; i < word.commonTranslations.length; i++) {
			loadWord(word.commonTranslations[i], loadTranslation);
		}
	}
	if(!word.commonTranslations || arr.length >= word.commonTranslations.length) {
		if(arr.length) wordsTypesByNum[word.wordType].collection.addWords(arr.concat(word));
		cb(arr);
	}
};
//var prepositions = [['in', 'в'], ['for', 'для'], ['on', 'на'], ['to', 'к'], ['from', 'из'], ['across', 'через'], ['below', 'внизу'], ['into', 'внутрь'], ['upon', 'над'], ['without', 'без'], ['according to', 'согласно'], ['as for', 'что касается'], ['behind', 'за'], ['over', 'над'], ['under', 'под'], ['in front of', 'перед'], ['by', 'около']];
var constructPrepositionsCollection = function() {
	var prepositions = {};

	prepositions.independents = [ // Все частицы, не нуждающиеся в дополнительном слове
	];

	prepositions.where = [
		['here', 'здесь'],
		['there', 'там'],

		['at', 'у'], // у, в, возле, за, на
		['in', 'в'], // в(внутри), на
		['on', 'на'], // на поверхности

		['inside', 'внутри'], //  внутри, в

		['over', 'выше'], // над, свыше, на той стороне, Используем когда один из предметов движется горизонтально, или объекты соприкосаются.
		['above', 'над'], // над, Используем когда один из предметов движется вертикально, или объекты не соприкосаются.

		['under', 'ниже'], // под, ниже, Используем когда один из предметов движется горизонтально, или объекты соприкосаются.
		['below', 'под'], // под, Используем когда один из предметов движется вертикально, или объекты не соприкосаются.

		['by', 'около'], // около, синоним beside
		['beside', 'возле'], // рядом с, около, возле, синоним by
		['near', 'вблизи'], // возле, у, около, вблизи, Используем когда находятся на значительном расстоянии.

		['against', 'напротив'], // напротив; около
		['in front of', 'впереди'], // впереди, напротив
		['before', 'перед'], // перед
		['behind', 'позади'], // за, позади
		['beyond', 'за'], // за, по ту сторону
		['outside', 'вне'], // вне, за пределами
		['round', 'вокруг'], // вокруг, кругом

		['between', 'между'], // между, среди
		['among', 'среди'], // между, среди

		['across', 'поперек'], // через; поперек; на той стороне
		['through', 'через'], // от одного конца чего то к другому
		['past', 'мимо'],
		['along', 'вдоль'],
	];
	prepositions.independents = prepositions.independents.concat([prepositions.where[0], prepositions.where[1]]);

	prepositions.from = [
		['from here', 'отсюда'],
		['from there', 'оттуда'],

		['from', 'от'], // от, из, с. Я иду из ванной(ведь важно не сам факт выхода тела из контейнера)
		['out of', 'из'], // из(наружу), Я иду из комнаты(важен факт выхода тела из контейнера)

		['away', 'отдаляясь от'], // Важно что отдаляемся(расстояние увеличивается)
	];
	prepositions.independents = prepositions.independents.concat([prepositions.from[0], prepositions.from[1]]);

	prepositions.to = [
		['to here', 'сюда'],
		['to there', 'туда'],
		['up', 'вверх'],
		['down', 'вниз'],

		['to', 'к'], // к, в, на. Я иду в ванную(ведь важен не сам факт помещения тела в контейнер)
		['into', 'в'], // в(внутрь), Я иду в комнату(важен факт помещения тела в контейнер)

		['towards', 'в направлении'],
		['out', 'наружу'],
	];
	prepositions.independents = prepositions.independents.concat([prepositions.to[0], prepositions.to[1], prepositions.to[2], prepositions.to[3]]);

	prepositions.for = [
		['for', 'для'],
		['against', 'против'],
	];

	return prepositions;
};

var prepositions = constructPrepositionsCollection(); // Основная коллекция слов, может использоваться в любых частях программы.
// Классы написаны примерно так, как если бы писались на ES6, но с ущербностями ES3.
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.
// Во избежание утечек памяти - не храним в словах ссылки на другие сложные объекты.
var Word = function(str) {
	this.construct();
	if(str) this.parse(str);
};

(function() {

	Word.prototype.wordTypeName = Word.wordTypeName = 'Word';
	Word.prototype.constructorFunc = Word;

	Word.prototype.construct = function() {
		this.sid = nextSID();
	};

	Word.prototype.parse = function(obj) {
		// устанавливает из ['любить'];
		if(isString(obj)) {
			if(this.str && this.str != obj) throw new Error('Change base word value.');
			this.str = obj;
		} else if(isArray(obj)) {
			if(this.str && this.str != obj[0]) throw new Error('Change base word value.');
			this.str = obj[0];
			if(obj[5]) { // хранит прогресс изучения этого слова.
				this.progress = {};
				this.progress.attempts = obj[5][0]; // означает колличество раз, когда это слово было использовано в экзаменах X4.
				this.progress.succesfull = obj[5][1]; // означает колличество раз, когда это слово было просмотренно в тренеровках X1 или верно угадано в экзаменах X4.
				this.progress.emphasis = obj[5][2]; // означает, что слово помечено как премиальное, и должно добавляться в премиальную коллекцию. Да, при парсинге не нужно пользоваться функцией setEmphasis().
			}
		}

		return obj;
	};

	Word.prototype.setByInputValues = function(obj) {
		// wordInput обычно передает данные о слове в формате: ['я',[111],['я','меня',...]], что не всегда соответствует хитромy JSON формату данного типа. Для установки из wordInput существует этот метод.
		this.parse(obj);
		return obj;
	};

	Word.prototype.addAttempt = function(num) {
		if(!this.progress) this.progress = {};
		this.progress.attempts = (this.progress.attempts || 0) + num;
	};
	Word.prototype.addSuccesfull = function(num) {
		if(!this.progress) this.progress = {};
		this.progress.succesfull = (this.progress.succesfull || 0) + num;
	};
	Word.prototype.setEmphasis = function(val) {
		if(!this.progress) this.progress = {};
		if(val !== this.progress.emphasis) {
			if(val) {
				user.addEmphasisWord(this);
			} else {
				user.removeEmphasisWord(this);
			}
		}
		this.progress.emphasis = val;
		if(!val) {
			for(var k in this.progress) {
				if(this.progress[k]) return;
			}
			this.progress = undefined;
		}
	};
	Word.prototype.isEmphasis = function() {
		if(this.progress && this.progress.emphasis) return 1;
	};

	Word.prototype.toJSON = function() {
		var ret = [this.str];
		if(this.progress) {
			ret[5] = [0,0];
			if(this.progress.attempts) ret[5][0] = this.progress.attempts;
			if(this.progress.succesfull) ret[5][1] = this.progress.succesfull;
			if(this.progress.emphasis) ret[5][2] = 1;
		}
		return ret;
	};

	Word.prototype.getFormed = function() {
		return this.str;
	};

	Word.prototype.combine = function(word) {
		// Должна добавить в слово все возможные данные из переданного слова.
		// Не забываем, результат getFormed() не должен измениться.
	};

	Word.prototype.setOnlyValues = function(inputValues) {
		// Устанавливает данные в inputValues, остальное очищает.
		this.construct();
		this.parse(inputValues);
	};

	Word.getFormed = function(word, lang) {
		if(!isArray(word)) {
			word = [word];
		} else if(word.os) {
			word = word.os;
		}
		for(var i = 0; i < word.length; i++) {
			if(lang !== undefined && word[i].wordLang == lang) {
				return w.getFormed();
			}
		}
	};

	Word.prototype.toString = function() {
		ret = this.wordTypeName + ' : ' + this.toJSON();
	};

	Word.prototype.setTitle = function(str) {
		this.title = str;
	};

})();
// У большинства слов есть флаги, указывающие смысл, категорию слова или сочитаемую категорию слов.
var WordWithFlags = function() {
	this.construct();
};
(function(){

	WordWithFlags.prototype = new Word();
	WordWithFlags.prototype.wordTypeName = 'WordWithFlags';

	WordWithFlags.prototype.construct = function() {
		Word.prototype.construct.call(this);
		this.flags = []; // о категориях к которым относится слово и о том, как можно работать с этим словом. В виде строк из "0" и "1".
	};

	WordWithFlags.prototype.parseFlags = function(obj, ind, collect) {
		// устанавливает флаги из ['любить',[249,145]];
		// ind - индекс нужного массива флагов в JSON.
		// collect - свойство(массив) this в которое нужно установить значения.
		// Значения флагов преобразуются в строки из нулей и единиц.
		if(ind === undefined) ind = 1;
		if(collect === undefined) collect = this.flags;
		if(isArray(obj)) {
			if(obj[ind] !== undefined) {
				if(isArray(obj[ind])) {
					for(var i = 0; i < obj[ind].length; i++) {
						this.addFlags(flagIntToBytesstr(obj[ind][i]), collect);
					}
				} else if(obj[ind]) {
					this.addFlags(flagIntToBytesstr(obj[ind]), collect);
				}
			}
		}
		return obj;
	};

	WordWithFlags.prototype.parse = function(obj) {
		// устанавливает из ['любить',[249,145]];
		// Значения флагов преобразуются в строки из нулей и единиц.
		obj = Word.prototype.parse.call(this, obj);
		return this.parseFlags(obj, 1, this.flags);
	};

	var flagIntToBytesstr = function(summ) {
		return summ.toString(2).substr(1);
	};
	var flagBytesstrToInt = function(summ) {
		return parseInt('1' + summ, 2);
	};

	WordWithFlags.prototype.addFlags = function(flagStr, collect) {
		// Прослойка для, возможно, более хитрой установки флагов
		if(!collect) collect = this.flags;
		if(flagStr !== undefined && collect.indexOf(flagStr) == -1) {
			collect.push(flagStr);
			this.apportionedFlags = 0; // убираем закешированное значение.
			return true;
		}
	};

	var apportionFlagToTreeRadioAndCheckboxes = function(str, subMap) {
		// Возвращает биты в виде ['10101110000000000000', ['большое']]. То есть разделяет значение в дереве и checkboxы.
		var k;
		var countCheckboxes = 0;
		var radios = [];
		var checkedCheckboxes = [];
		var ret = '';
		for(k in subMap) {
			if(k === 'checkboxes') {
				countCheckboxes += subMap[k].length;
				for(var i = 0; i < subMap[k].length; i++) {
					if(str[i] === '1') {
						if(isObject(subMap[k][i])) {
							checkedCheckboxes.push(subMap[k][i].name);
						} else {
							checkedCheckboxes.push(subMap[k][i]);
						}
					}
				}
			} else if(k !== 'title') {
				radios.push(subMap[k]);
			}
		}
		ret = str.substr(countCheckboxes); // пропускаем биты чекбоксов
		if(ret && radios.length) {
			var len = Math.ceil(Math.log(radios.length + 1) / Math.log(2)); // колличетсво битов необходимое для записи выбранного(или пустого) radio
			var selectedRadio = parseInt(ret.substr(0, len), 2);
			var subRet = apportionFlagToTreeRadioAndCheckboxes(ret.substr(len), radios[selectedRadio - 1]);
			if(selectedRadio) {
				ret = ret.substr(0, len) + subRet[0];
			} else {
				ret = '';
			}
			checkedCheckboxes = checkedCheckboxes.concat(subRet[1]);
		}
		return [ret, checkedCheckboxes];
	};
	WordWithFlags.prototype.apportionFlagToTreeRadioAndCheckboxes = function(str, map, onlyFirstChars, filtrFlags) {
		if(isNumber(str)) str = flagIntToBytesstr(str);
		var ret = apportionFlagToTreeRadioAndCheckboxes(str, map || this.flagsMap);
		if(!onlyFirstChars) {
			ret[0] += new Array(31 - ret[0].length).join('0'); // Дополняем биты по radioTree нулями до 20 символов для удобного сравнения значений.
		}
		var ind = ret[1].indexOf('filter');
		if(ind !== -1) {
			ret[2] = 1; // указывает, что флаг 'filter' установлен
			ret[1].splice(ind, 1);
		}
		return ret;
	};

	WordWithFlags.prototype.getApportionedFlags = function() {
		// возвращает флаги в виде разделенных на категории и чекбоксы, не дополненные нулями(лучше потом дополнить)
		if(!this.apportionedFlags) {
			this.apportionedFlags = [];
			if(this.flags) {
				for(var i = 0; i < this.flags.length; i++) {
					this.apportionedFlags.push(this.apportionFlagToTreeRadioAndCheckboxes(this.flags[i], 0, 1));
				}
			}
		}
		return this.apportionedFlags;
	};

	var disapportionFlag = function(radiosStr, checkboxes, subMap) {
		// объединяет ранее apportioned флаги в строку из 1 и 0.
		if(!subMap) return '';
		var k;
		var nameCheckbox;
		var checkboxesStr = '';
		var radios = [];
		var ret = '';
		for(k in subMap) {
			if(k === 'checkboxes') {
				for(var i = 0; i < subMap[k].length; i++) {
					if(isObject(subMap[k][i])) {
						nameCheckbox = subMap[k][i].name;
					} else {
						nameCheckbox = subMap[k][i];
					}
					if(checkboxes && checkboxes.indexOf(nameCheckbox) !== -1) {
						checkboxesStr += '1';
					} else {
						checkboxesStr += '0';
					}
				}
			} else if(k !== 'title') {
				radios.push(subMap[k]);
			}
		}
		var len = Math.ceil(Math.log(radios.length + 1) / Math.log(2)); // колличетсво битов необходимое для записи выбранного(или пустого) radio
		var radioStr = radiosStr.substr(0, len);
		return checkboxesStr + radioStr + disapportionFlag(radiosStr.substr(len), checkboxes, radios[parseInt(radioStr, 2) - 1]);
	};
	WordWithFlags.prototype.disapportionFlag = function(flag, map) {
		var checkboxes = flag[1] || [];
		if(flag[2]) checkboxes = checkboxes.concat('filter');
		return disapportionFlag(flag[0], checkboxes, map || this.flagsMap);
	};
	WordWithFlags.prototype.disapportionFlags = function(flags, map) {
		var th = this;
		return flags.map(function(flag) {return th.disapportionFlag(flag, map);});
	};

	WordWithFlags.prototype.toJSON = function() {
		if(!this.str) return;
		var ret = Word.prototype.toJSON.call(this);
		if(this.flags) {
			var flags = [];
			for(var i = 0; i < this.flags.length; i++) {
				var val = flagBytesstrToInt(this.flags[i]);
				if(val > 1) {
					flags.push(val);
				}
			}
			if(flags.length) ret[1] = flags;
		}
		return ret;
	};

	WordWithFlags.prototype.combine = function(word) {
		// Дополняет данными из переданого word.
		Word.prototype.combine.call(this, word);
		if(word.flags) {
			for(var i = 0; i < word.flags.length; i++) {
				this.addFlags(word.flags[i]);
			}
		}
	};

	WordWithFlags.flags = WordWithFlags.prototype.flagsMap = {
		'checkboxes': ['filter'],
		'материальное': {
			'title': 'объект, ',
			'checkboxes': [
				{'name': 'возможно маленькое', 'title': 'муравей, коробка, капля'},
				{'name': 'возможно большое', 'title': 'планета, коробка, столб'},
			],
			'одушевленное': {
				'title': 'существо, ',
				'человек': {
					'title': 'сосед, противник, больной, лидер, зритель, клиент, дама, субъект, полковник, житель, князь, судья, еврей, баба, коллега, ученик, подруга, знакомый, дурак, кандидат, партнер, владелец, старуха, ',
					'родня': 'родственник, тетя, ',
					'профессия': 'мастер, заместитель, артист, журналист, секретарь, чиновник, царь, лейтенант, водитель, губернатор, эксперт, рабочий, инженер, следователь, редактор, ',
					'национальность': 'американец, русский, ',
				},
				'животное': {
					'title': 'а так же насекомые, и тд (животное, )',
					'воздушное животное': 'птица, ',
					'наземное животное': 'лошадь, лев, ',
					'подводное животное': 'рыба, ',
					'подземное животное':0,
				},
			},
			'неодушевленное': {
				'title': 'энергия, ',
				'растение': {
					'title': 'а так же грибы, корралы и водоросли (растение, )',
					'трава': 'трава, ',
					'цветок': 'цветок, ',
					'куст': 'куст, ',
					'дерево':0,
					'подводное':0,
					'подземное':0,
				},
				'механизм': {
					'title': 'механизм, оборудование, аппарат, инструмент, устройство, двигатель, ',
					'человекообразный':0,
					'средство передвижения': {
						'checkboxes':[
							'движется строго по маршруту', // тогда используется on а не in
						],
						'подземный транспорт':0,
						'подводный транспорт':0,
						'наводный транспорт': 'лодка, ',
						'наземный транспорт': 'поезд, вагон, автобус, ',
						'воздушный транспорт': 'ракета, ',
					},
					'гаджет': {
						'title': 'замок, ',
						'разборный':0,
						'носят с собой': 'нож, компьютер, пистолет,',
						'стационарный': 'блок, часы, телевизор, ',
					},
				},
				'место': {
					'космическое место':0,
					'воздушное место':0,
					'наземное место': {
						'title': 'округ, дача, площадка, парк, штат, ',
						'checkboxes': [
							'длинное', // на участках, которые воспринимаются как прямая можно говорить on и across
						],
						'возвышенность': 'Я поднимаюсь на * (остров, вершина, )',
						'впадина':0,
					},
					'наводное место':0,
					'подводное место':0,
					'подземное место':0,
					'воображаемое место':0,
				},
				'путь': {
					'title': 'путь, ',
					'космический путь':0,
					'воздушный путь':0,
					'наземный путь': 'дорога, ',
					'наводный путь': 'канал, ',
					'подводный путь':0,
					'подземный путь':0,
					'воображаемый путь':0,
				},
				'селение': 'Я иду в *3, я иду к *2 (столица, лагерь, )',
				'строение': 'мост, памятник, ворота, ',
				'здание': {
					'title': 'храм, университет, милиция, музей, учреждение, тюрьма, академия, больница, гостиница, здание, дворец, ',
					'checkboxes': [
						'имеет специальное назначение', // тогда следует говорить to вместо into.
					],
				},
				'помещение': {
					'title': 'помещение, ресторан, палата, камера, штаб, жилье, ',
					'checkboxes': [
						'имеет специальное назначение', // тогда следует говорить to вместо into.
					],
				},
				'емкость': {
					'title': 'ящик, пакет, мешок, ',
					'checkboxes': ['посуда'],
					'для воды': 'стакан, ',
					'для животных': 'вагон, ',
				},
				'еда': {
					'title': 'мясо, ',
					'checkboxes':[
						'не готово к употреблению',
						'яд',
					],
					'напиток': 'водка, вино, кофе, пиво, ',
					'фрукт/овощ':0,
					'добавка': 'масло, ',
				},
				'твердое вещество': 'лед, вещество, ',
				'жидкость': 'ресурс, нефть, вещество, ',
				'газ': 'газ, ресурс, дым, ',
				'излучение': 'ресурс, ',
				'предмет': {
					'title': 'трубка, труба, ключ, подарок, инструмент, собственность, имущество, доска, ',
					'одежда': 'одежда, костюм, часы, платье, сапог, ',
					'содержит информацию': {
						'логическую~логическая': 'страница, ',
						'числовую~числовая':0,
						'текстовую~текстовая': 'страница, том, билет, книжка, ',
						'звуковую~звуковая':0,
						'графическую~графическая':0,
						'видео~видео': 'телевизор, экран, ',
					},
				},
				'мебель': {
					'title': 'кровать, стул, телевизор, постель, диван, столик, ',
					'checkboxes': [
						'можно сидеть',
						'можно лежать',
						'можно открывать',
						'можно любоваться',
						'можно ставить предметы',
						'можно складывать предметы',
					],
				},
				'интерьер': {
					'title': 'крыша, лестница, зеркало, порог, ',
					'checkboxes': [
						'можно открывать',
						'можно проходить',
						'можно любоваться',
						'может преграждать',
						'можно ходить по этому',
					],
				},
				'сопутствующее предмета': 'тень, ',
			},
			'часть материальная': {
				'материального~материальное': {
					'title': 'Это является *5 любого материального ()',
					'одушевленного~одушевленное': {
						'title': 'организм, ',
						'checkboxes':['внешний орган'],
						'человека~человек': 'Меня ударило в * (ладонь, шея, мозг, лоб, щека, живот, кулак, )',
						'животного~животное': {
							'подводного животного~подводное животное':0,
							'воздушного животного~воздушное животное': 'крыло, ',
							'наземного животного~наземное животное':0,
							'подземного животного~подземное животное':0,
						},
					},
					'неодушевленного~неодушевленное': {
						'растения~растение':'корень',
						'механизма~механизм':{
							'title': 'Это является *5 любого механизма (деталь, )',
							'средства передвижения~средство передвижения': 'колесо, двигатель, корпус, ',
						},
						'места~место':0,
						'здания~здание':0,
						'помещения~помещение':0,
						'емкости~емкость': 'дно, ',
						'еды~еда':0,
						'жидкости~жидкость':0,
						'газа~газ':0,
						'излучения~излучение':0,
						'предмета~предмет': 'деталь, ',
						'мебели~мебель': 'деталь, ',
						'интерьера~интерьер':0,
					},
					'части материальной~часть материальная':0,
				},
				'абстрактного~абстрактное': 0,
				'группы~группа':0,
			},
		},
		'абстрактное': {
			'title': 'уровень, мера, номер, доллар, память, соответствие, продукт, отсутствие, бюджет, впечатление, целое, налог, большинство, процент, волна, наличие, тишина, зарплата, запас, серия, выпуск, профессия, добро, услуга, следствие, взгляд, ',
			'время': {
				'checkboxes': ['не перечислимое',],
				'календарное понятие': {
					'title': 'Встретимся в следующий/ую * (лето, май, декабрь, октябрь, сентябрь, январь, весна, август, март, апрель, июнь, осень, июль, ноябрь, февраль, сезон, )',
					'день': 'on monday',
					'месяц': 'in may',
				},
				'длительность времени': 'Встретимся через *4 (секунда, час, день, период, век, эпоха, сутки, )',
				'момент времени': 'до *1 осталось... (милениум, конец, момент, срок, обед, )',
				'характер времени': 'будущее, детство, прошлое, пора, ',
			},
			'событие': {
				'title': 'Произойдет * (событие, беда, конфликт, потеря, столкновение, попытка, ход, чудо, прием, грех, случай, )',
				'продолжительное': '* идет 2 часа (война, дождь, реклама, урок, процедура, бой, мероприятие, конкурс, бизнес, деятельность, концерт, выставка, переговоры, акция, преступление, поездка, звонок, поток, спектакль, )',
			},
			'действие': {
				'title': 'Если есть возможность - отнести к Verb. Я совершил * (кульбит, терракт, )',
				/*'отглагольное/герундий': {
					'title': 'Автоматически обладают отглагольные(печать, труд, падение) и герундии(doing, singing). Я в процессе *1 (печать, участие, поиск, занятие, переход, оборона, наблюдение, формирование, воздействие, ...)',
					'материального~материальное': {
						'одушевленного~одушевленное': {
							'человека~человек': 0,
						},
					},
					'абстрактного~абстрактное': {
					},
					'группы~группа': {
					},
				},*/
			},
			'состояние': {
				'title': 'Он находится в состоянии *1, Оно в *6 (зависимость, положение, ситуация, обстановка, явление, )',
				'материального~материальное': {
					'одушевленного~одушевленное': {
						'человека~человек': {
							'title': 'Я в состоянии *1, я в *6 (брак, )',
							'с другим человеком': 'У меня с ним * (роман, )',
						},
						'животного~животное':0,
					},
					'неодушевленного~неодушевленное':0,
					'части материальной~часть материальная':0,
				},
				'абстрактного~абстрактное':0,
			},
			'эмоция': 'улыбка, смех, ',
			'чувство': 'Я чувствую * (радость, горе, любовь, интерес, свобода, надежда, счастье, страх, радость, безопасность, необходимость, воля, удовольствие, боль, ощущение, сожаление, ужас, сомнение, потребность, опасность, страсть, покой, опасность, )',
			'институт': 'Я занялся *5 (наука, культура, искусство, образование, политика, музыка, экономика, литература, безопасность, технология, традиция, законодательство, промышленность, творчество, психология, )',
			'организация': 'Я работаю в *6 (фонд, фирма, рынок, армия, )',
			'единица измерения': 'метр, километр, ',
			'фактор': {
				'title': '* подтолкнул(а/о/и) к действию (проблема, причина, закон, бог, возможность, результат, условие, связь, цель, задача, причина, судьба, принцип, повод, обстоятельство, ошибка, долг, традиция, фактор, заключение, польза, последствие, темнота, следствие, забота, риск, успех, внимание, )',
			},
			'качество': {
				'title': 'У этого есть * (образ, состояние, опыт, тип, оценка, название, особенность, стоимость, свойство, отличие, ценность, недостаток, )',
				'материального~материальное': {
					'title': 'вид, запах, цвет, форма, цена, размер, возраст, масса, высота, способность, красота, запах, черта, температура, длина, ',
					'одушевленного~одушевленное': {
						'title': 'Она обладает *5 (сила, скорость, голос, рост, характер, зрение, поведение, слух, )',
						'человека~человек': 'Я обладаю *5 (имя, душа, власть, вера, дух, ум, сознание, воля, слава, фамилия, личность, ответственность, стиль, настроение, должность, честь, перспектива, вкус, достоинство, талант, воспитание, )',
						'животного~животное':0,
					},
					'неодушевленного~неодушевленное': {
						'растения~растение':0,
						'механизма~механизм':0,
						'места~место':0,
						'пути~путь':0,
						'селения~селение':0,
						'строения~строение':0,
						'здания~здание':0,
						'помещения~помещение':0,
						'емкости~емкость': 'объем, глубина, ',
						'еды~еда':0,
						'твердого вещества~твердое вещество':0,
						'жидкости~жидкость': 'давление, ',
						'газа~газ': 'давление, ',
						'излучения~излучение':0,
						'предмета~предмет':0,
						'мебели~мебель':0,
						'интерьера~интерьер':0,
					},
					'части материальной~часть материальная':0,
				},
				'абстрактного~абстрактное': {
					'времени~время':0,
					'события~событие':0,
					'действия~действие':0,
					'состояния~состояние':0,
					'эмоции~эмоция':0,
					'чувства~чувство':0,
					'института~институт':0,
					'организации~организация':0,
					'единицы измерения~единица измерения':0,
					'фактора~фактор':0,
					'качества~качество':0,
					'роли~роль':0,
					'информации~информация': {
						'разговорной~разговорная':0,
						'логической~логическая':0,
						'числовой~числовая':0,
						'текстовой~текстовая':0,
						'звуковой~звуковая': 'тон, ',
						'графической~графическая':0,
						'видео~видео':0
					},
					'структуры объекта~структура объекта':0,
					'части абстрактной~часть абстрактная':0,
					'места абстрактного~место абстрактное':0,
				},
				'группы~группа': {
					'материальных~материальных': {
						'title':0,
						'одушевленных~одушевленное': {
							'людей~людей':0,
							'животных~животных':0,
						},
						'неодушевленных~неодушевленных':0,
					},
					'абстрактных~абстрактных':0,
				},
			},
			'роль': {
				'title': 'Это является *5 любого предмета (роль, основа, класс, основание, модель, название, содержание, стоимость, свойство, признак, сущность, основное, )',
				'материального~материальное': {
					'title': 'Это * материального (позиция, образец, )',
					'одушевленного~одушевленное': {
						'title': 'Это ее * (след, жертва, )',
						'человека~человек': 'Это мой * (роль, обязанность, характеристика, перспектива, капитал, )',
						'животного~животное':0,
					},
					'неодушевленного~неодушевленное': 'Это является *4 предмета ()',
					'части материальной~часть материальная':0,
				},
				'абстрактного~абстрактное': {
					'title': 'вариант, ',
					'времени~время': 'предел, ',
					'события~событие': 'эффект, ',
					'действия~действие': 'Это * процесса/действия (пример, метод, способ, итог, норма, режим, ошибка, подход, эффект, )',
					'состояния~состояние':0,
					'эмоции~эмоция':0,
					'чувства~чувство':0,
					'института~институт': 'субъект, эффект, ',
					'организации~организация':0,
					'единицы измерения~единица измерения':0,
					'фактора~фактор':0,
					'качества~качество': 'Это * качества (показатель, )',
					'роли~роль': 'эффект, ',
					'информации~информация': {
						'разговорной~разговорная':0,
						'логической~логическая': 'Это * данных (тема, суть, главное, )',
						'числовой~числовая':0,
						'текстовой~текстовая':0,
						'звуковой~звуковая': 'тон, ',
						'графической~графическая': 'фон, ',
						'видео~видео':0
					},
					'структуры объекта~структура объекта':0,
					'части абстрактной~часть абстрактная':0,
					'места абстрактного~место абстрактное':0,
				},
				'группы~группа': {
					'материальных~материальных': {
						'title': 0,
						'одушевленных~одушевленное': {
							'людей~людей': 'Это * людей (глава, лидер, смена, )',
							'животных~животных':0,
						},
						'неодушевленных~неодушевленных':0,
					},
					'абстрактных~абстрактных':0,
				},
			},
			'информация': {
				'разговорная': 'Я сказал * (вопрос, совет, ответ, речь, правда, требование, предложение, заявление, выражение, фраза, известие, тайна, просьба, сообщение, новость, обращение, шутка, описание, требование, команда, )',
				'логическая': {
					'title': 'данные, знание, понятие, адрес, сведение, ',
					'смысловая': 'Я понял * (программа, правило, идея, план, мнение, проект, мысль, значение, договор, факт, предложение, определение, теория, вывод, тайна, постановление, соглашение, концепция, разница, новость, доказательство, )',
					'математическая': 'формула, сумма, функция, расчет',
				},
				'числовая': {
					'относительная': '* океана (половина, )',
					'количественная': '* людей (количество, пара, миллион, десяток, сотня, )',
					'цифровая': 'Я вычислил * (номер, цифра, )',
					'разы': 'раз, ',
				},
				'память': 'Я вспомнил * (воспоминание, )',
				'текстовая': 'Я написал *3 (роман, стихи, литература, текст, рассказ, фраза, кодекс, пьеса, таблица, постановление, соглашение, сообщение, запись, издание, разрешение, новость, доклад, буква, признание, описание, )',
				'звуковая': 'Я слушаю *3 (музыка, песня, стихи, звук, крик, запись, шум, )',
				'графическая': 'Я рассматриваю *3, Я рисую *3 (точка, угол, круг, линия, сфера, фигура, фотография, схема, таблица, портрет, знак, кадр, полоса, )',
				'видео': 'Я смотрю *3 (фильм, передача, кино, запись, )',
			},
			'структура объекта': {
				'title': 'состав, структура, ',
				'материального~материальное': {
					'одушевленного~одушевленное': {
						'человека~человек': 0,
						'животного~животное':0,
					},
					'неодушевленного~неодушевленное': 0,
					'части материальной~часть материальная':0,
				},
				'абстрактного~абстрактное':0,
				'группы~группа':0,
			},
			'часть абстрактная': {
				'title': 'Это является *5 того (элемент, )',
				'материального~материальное': {
					'title': 'Это является *5 того (зад, центр, край, отдел, поверхность, середина, бок, кусок, )',
					'одушевленного~одушевленное': {
						'человека~человек': 0,
						'животного~животное':0,
					},
					'неодушевленного~неодушевленное': 'слой, ',
					'части материальной~часть материальная':0,
				},
				'абстрактного~абстрактное': {
					'title': 'сторона, половина, зона, частность, доля, ',
					'времени~время': 'конец, середина, заключение, окончание, ',
					'события~событие':'окончание, ',
					'действия~действие': 'акт, конец, этап, окончание, ',
					'состояния~состояние':0,
					'эмоции~эмоция':0,
					'чувства~чувство':'окончание, ',
					'института~институт': 'отрасль, ',
					'организации~организация':0,
					'единицы измерения~единица измерения':0,
					'фактора~фактор':0,
					'качества~качество':0,
					'роли~роль':0,
					'информации~информация': 'середина, окончание, ',
					'структуры объекта~структура объекта':0,
					'части абстрактной~часть абстрактная':0,
					'места абстрактного~место абстрактное':0,
				},
				'группы~группа': 'категория, ',
			},
			'место абстрактное': {
				'title': '',
				'указание': 'Оно двигается по *2, я указал * (направление, курс, )',
				'направление': 'Оно двигается на * (запад, восток, )',
				'точка': 'пункт, ',
				'значение места для объекта': {
					'материального~материальное': {
						'одушевленного~одушевленное': {
							'title': 'Это место - его * (родина, )',
							'человека~человек': 0,
							'животного~животное': 0,
						},
						'неодушевленного~неодушевленное': {

							'растения~растение':0,
							'механизма~механизм':0,
							'места~место': {
								'возвышенности~возвышенность': 'Это * возвышенности (вершина, )',
								'впадины~впадина':0,
							},
							'пути~путь':0,
							'селения~селение':0,
							'строения~строение':0,
							'здания~здание':0,
							'помещения~помещение':0,
							'емкости~емкость':0,
							'еды~еда':0,
							'твердого вещества~твердое вещество':0,
							'жидкости~жидкость':0,
							'газа~газ': 'давление, ',
							'излучения~излучение':0,
							'предмета~предмет':0,
							'мебели~мебель':0,
							'интерьера~интерьер':0,
						},
						'части материальной~часть материальная':0,

					},
					'абстрактного~абстрактное':0,
					'группы~группа': {
						'материальных~материальных': {
							'title': 0,
							'одушевленных~одушевленное': {
								'людей~людей': 'Он двигается к *2 (фронт, )',
								'животных~животных':0,
							},
							'неодушевленных~неодушевленных':0,
						},
						'абстрактных~абстрактных':0,
					},
					'title': 'Это * объекта (среда, источник, фронт, )',
				},
			},
			'воздействие': 'усилие, ',

		},
		'группа': {
			'title': 'ряд, множество, сеть, список, ',
			'материальных~материальное': {
				'одушевленных~одушевленное': {
					'животных~животное': 'Этих животных объединяет * (стадо, )',
					'людей~человек': 'Нас объединяет * (организация, семья, очередь, команда, род, комитет, администрация, толпа, поколение, публика, человечество, коллектив, молодежь, начальство, )',
				},
				'неодушевленных': 0,
			},
			'абстрактных~абстрактное': {
				'title': 'система, ',
				'времен~время': 0,
				'событий~событие':0,
				'действий~действие': 'комплекс, ',
				'состояний~состояние':0,
				'эмоций~эмоция':0,
				'чувств~чувство':0,
				'институтов~институт':9,
				'организаций~организация':0,
				'единиц измерения~единица измерения':0,
				'факторов~фактор':0,
				'качеств~качество':0,
				'ролей~роль':0,
				'информаций~информация': 'середина, окончание, ',
				'структур объектов~структура объекта': 'союз, ',
				'частей абстрактных~часть абстрактная':0,
				'мест абстрактных~место абстрактное':0,
			},
		},
	};

	WordWithFlags.prototype.subFlagsRanges = function(subMap, bits, objCont, clas) {
		// Устанавливает flagsRanges и flagNameByBites.
		if(!objCont || !objCont.flagsRanges) {
			if(!objCont) {
				if(clas) {
					objCont = clas.prototype;
				} else {
					objCont = this;
				}
			}
			objCont.flagsRanges = {}; // сюда записываются начальные и конечные значения для каждого поименованого пункта в WordWithFlags.flags. чекбоксы игнорируются.
			objCont.flagNameByBites = {}; // позволяет по строке битов определить название флага.
			objCont.checkboxesParFlags = {}; // указывается какие кратчайшие флаги(без checkboxes) могут быть установлены, чтобы добраться до этого checkboxа.
		}
		var radios = {};
		var len = 0;
		var k;
		for(k in subMap) {
			if(k === 'checkboxes') {
				for(var i = 0; i < subMap[k].length; i++) {
					var name = subMap[k][i];
					if(name.name) name = name.name;
					if(!objCont.checkboxesParFlags[name]) objCont.checkboxesParFlags[name] = [];
					objCont.checkboxesParFlags[name].push(bits);
				}
			} else if(k !== 'title') {
				radios[k] = subMap[k];
				len++;
			}
		}
		len = Math.ceil(Math.log(len + 1) / Math.log(2)); // колличетсво битов необходимое для записи выбранного(или пустого) radio.
		var bit = 0;
		for(k in radios) {
			bit++;
			var newBits = bit.toString(2);
			if(!objCont.flagsRanges[k]) objCont.flagsRanges[k] = [];
			var ob = [];
			objCont.flagsRanges[k].push(ob);
			newBits = new Array(len + 1 - newBits.length).join('0') + newBits;
			newBits = (bits || '') + newBits;
			ob[2] = newBits; // наименьшее без дополнения до 20 символов.
			objCont.flagNameByBites[newBits] = k;
			if(isObject(subMap[k])) {
				this.subFlagsRanges(subMap[k], newBits, objCont, clas);
			}
			var newBits0 = newBits + new Array(31 - newBits.length).join('0');
			ob[0] = newBits0; // наименьшее значение флага.
			var newBits1 = newBits + new Array(31 - newBits.length).join('1');
			ob[1] = newBits1; // наибольшее значение флага.
		}
	};
	WordWithFlags.prototype.subFlagsRanges(WordWithFlags.flags, '', 0, WordWithFlags);

	WordWithFlags.prototype.flagValTest = function(flag, valStr, strict, flagsRanges) {
		// Определяет, совпадает ли заначение битов флага с названием valStr.
		if(isNumber(flag)) {
			flag = flagIntToBytesstr(flag);
		}
		if(isString(flag)) {
			flag = this.apportionFlagToTreeRadioAndCheckboxes(flag, 0, 1);
		}
		var valBits, i, n, count;
		if(isArray(valStr)) {
			// иногда есть смысл указывать valStr например ['активное действие', 'кинетическое'] чтобы не принимать во внимание пассивные кинетические действия.
			valBits = (flagsRanges || this.flagsRanges)[valStr[valStr.length - 1]].concat();
			for(i = valBits.length - 1; i--; ) {
				count = 1;
				for(n = valBits[i][2].length - 1; n > 0; n--) {
					if(this.flagNameByBites[valBits[i][2].substr(0, i)] === valStr[valStr.length - 1 - count]) {
						count++;
					}
				}
				if(count < valStr.length) valBits.splice(i, 1);
			}
		} else {
			valBits = (flagsRanges || this.flagsRanges)[valStr];
		}
		if(valBits && valBits.length) {
			var bits = flag[0];
			if(flag[3]) bits = flag[3] + bits;
			for(i = 0; i < valBits.length; i++) {
				if(bits === valBits[i][2] || !strict && bits >= valBits[i][0] && bits <= valBits[i][1]) {
					if(flag[2]) return 0;
					return 1;
				}
			}
		} else {
			warning(valStr);
		}
	};

	WordWithFlags.prototype.flagsValTest = function(valStr, strict, flags, retArr) {
		if(!flags) flags = this.getApportionedFlags();
		// Определяет, совпадает ли заначение битов флага с названием valStr.
		var bool;
		for(var i = 0; i < flags.length; i++) {
			bool = this.flagValTest(flags[i], valStr, strict);
			if(bool) {
				if(retArr) {
					retArr.push(flags[i]);
				} else {
					return 1;
				}
			}
			if(bool === 0) return 0;
		}
		return retArr;
	};

	WordWithFlags.prototype.checkboxesValTest = function(valStr, flags) {
		if(!flags) flags = this.getApportionedFlags();
		for(var i = 0; i < flags.length; i++) {
			if(flags[i] && flags[i][1] && flags[i][1].indexOf(valStr) !== -1) return true;
		}
	};

	WordWithFlags.prototype.reduceFlag = function(flag) { // не очень логичное место для этой функции, но другие еще менее логичны.
		// Убирает у флага один наиболее вложеный уровень.
		var i, c, n, cbFlgs, t;
		var radios = flag[0];
		var checkboxes;
		if(flag[1]) checkboxes = flag[1].concat();
		for(i = radios.length - 1; i--; ) {
			radios = radios.substr(0, i);
			if(this.flagNameByBites[radios]) {
				if(checkboxes) {
					testChBx:
					for(c = checkboxes.length; c--; ) {
						if(checkboxes[c]) {
							cbFlgs = this.checkboxesParFlags[checkboxes[c]];
							if(cbFlgs) {
								for(t = 0; t < cbFlgs.length; t++) {
									if(cbFlgs[t] === radios || cbFlgs[t].length < radios.length && radios.substr(0, cbFlgs[t].length) === cbFlgs[t]) {
										break testChBx;
									}
								}
							}
						}
						checkboxes.splice(c, 1);
					}
				}
				return [radios, checkboxes, flag[2], flag[3], flag[4]];
			}
		}
		if(flag[0].length > 1) { // на всякий случай.
			return [flag[0].substr(0, flag[0].length - 1), checkboxes, flag[2], flag[3], flag[4]];
		}
	};

	WordWithFlags.prototype.genFlags = function(str, checkboxes, filtr) {
		// По переданому названию генерирует массив флагов.
		return this.flagsRanges[str].concat().map(function(r) {return [r[2], checkboxes, filtr];});
	};

	WordWithFlags.prototype.toTildaFlags = function(flags) {
		// проверяет названия переданных флагов, и если в них есть ~ возвращает флаги с названиями после тильды.
		var fullName, name, ret = [];
		for(var i = 0; i < flags.length; i++) {
			fullName = this.flagNameByBites[flags[i][0]];
			if(fullName.indexOf('~') !== -1) {
				name = fullName.substr(fullName.indexOf('~') + 1);
				if(name) {
					pushAll(ret, this.genFlags(name));
				}
			}
		}
		return ret;
	};

})();
// У большинства слов есть флаги, указывающие смысл, категорию слова или сочитаемую категорию слов.
var WordWithPadejs = function() {
	this.padejsCount = 0;
	this.construct();
};

(function() {

	WordWithPadejs.prototype = new WordWithFlags();
	WordWithPadejs.prototype.wordTypeName = 'WordWithPadejs';

	WordWithPadejs.prototype.construct = function() {
		WordWithFlags.prototype.construct.call(this);
		this.padejs = 0; // сжатые данные, о том, к как можно работать с этим глаголом.
	};

	WordWithPadejs.prototype.parse = function(obj) {
		obj = WordWithFlags.prototype.parse.call(this, obj);
		this.padejs = 0;
		if(isArray(obj) && isArray(obj[2])) {
			var padejs = [];
			for(var i = 0; i < obj[2].length; i++) {
				if(obj[2][i]) {
					padejs[i] = obj[2][i];
				} else {
					padejs[i] = 0;
				}
			}
			this.padejs = padejs;
		}
		return obj;
	};

	WordWithPadejs.prototype.toJSON = function() {
		if(!this.str) return;
		var ret = WordWithFlags.prototype.toJSON.call(this);
		if(this.padejs && this.padejs.length) {
			var padejs = [];
			var progressPadejs = []; // постепенно добавлять сюда уже пройденые падежи/формы.
			var i;
			for(i = 0; i < this.padejsCount; i++) {
				// Убираем лишние падежи, которые можно восстановить на основе нормальной формы и предшествующих падежей/форм.
				var tempPadejs = this.padejs;
				this.padejs = progressPadejs;
				var v1 = this.getFormed(i);
				this.padejs = tempPadejs;
				progressPadejs.push(this.padejs[i]);
				var v2 = this.getFormed(i);
				if(v1 != v2) {
					padejs[i] = v2;
					if(this.kor && v2.substr(0, this.kor.length) === this.kor) {
						padejs[i] = '*' + v2.substr(this.kor.length);
					}
				}
			}
			var len = -1;
			for(i = 0; i < padejs.length; i++) {
				if(!padejs[i]) {
					padejs[i] = 0;
				} else {
					len = i;
				}
			}
			if(len > -1) {
				ret[2] = padejs;
				if(!ret[1]) ret[1] = 0;
			}
		}
		return ret;
	};

	WordWithPadejs.prototype.combine = function(word) {
		// Дополняет данными из переданого word.
		WordWithFlags.prototype.combine.call(this, word);
		if(word.padejs) {
			for(var i = 0; i < word.padejs.length; i++) {
				if(word.padejs[i]) {
					if(!this.padejs) this.padejs = [];
					this.padejs[i] = word.padejs[i];
				}
			}
		}
	};

})();
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.
var Noun = function(str) {
	this.construct();
	if(str) this.parse(str);
};

(function() {

	Noun.prototype = new WordWithPadejs();
	Noun.prototype.wordTypeName = 'Noun';
	Noun.prototype.wordType = 1; // 1 - существительное, 2 - глагол, ...
	Noun.prototype.gender = 0; // 0 - мужской, 1 - женский, 2 - средний, 3 - только множественное.

	Noun.prototype.construct = function() {
		WordWithPadejs.prototype.construct.call(this);
	};

	Noun.prototype.parse = function(obj) {
		this.parseGenderFlag(obj[3]);
		if(obj[4]) {
			this.protoVerb = obj[4]; // строка глагола в нормальной форме, на который ссылаются отглагольные и герундии.
		} else if(this.protoVerb) {
			this.protoVerb = undefined;
		}
		obj = WordWithPadejs.prototype.parse.call(this, obj);
		return obj;
	};

	Noun.prototype.parseGenderFlag = function(num) {
		this.gender = num || 0;
	};

	Noun.prototype.genderToBites = function() {
		return this.gender ? '1' : '0';
	};

	Noun.prototype.toJSON = function() {
		if(!this.str) return;
		var ret = WordWithPadejs.prototype.toJSON.call(this);
		if(this.gender) ret[3] = this.gender;
		if(this.protoVerb) ret[4] = this.protoVerb;
		return ret;
	};

	Noun.prototype.getApportionedFlags = function() {
		// возвращает флаги в виде разделенных на категории и чекбоксы, не дополненные нулями(лучше потом дополнить)
		var ret = WordWithPadejs.prototype.getApportionedFlags.call(this);
		if(this.protoVerb) { // если это отглагольное или герундий, то добавляем флаги от глагола.
			var verb = verbs.searchByStr(this.protoVerb);
			if(verb) {
				var verbFlags = verb.getApportionedFlags();
				for(var i = 0; i < verbFlags.length; i++) {
					if(verbFlags[i] && verbFlags[i][3] === '001') { // '001' отвечает в глаголе за who .
						ret.push(verbFlags[i]);
					}
				}
			}
		}
		return ret;
	};

	Noun.genderFlags = {};

	Noun.flags = WordWithFlags.flags;

})();
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.
var NounEn = function(minData) {
	this.construct();
	if(minData) this.parse(minData);
};

(function() {

	NounEn.prototype = new Noun();
	NounEn.prototype.wordLang = 0;
	NounEn.prototype.gender = 2; // по умолчанию средний род.

	NounEn.prototype.construct = function() {
		Noun.prototype.construct.call(this);
		this.str = '';
	};

	NounEn.prototype.genderToBites = function() {
		var ret = this.gender.toString(2);
		return new Array(3 - ret.length).join('0') + ret;
	};

	NounEn.prototype.parseGenderFlag = function(num) {
		if(num) {
			if(num === 1) {
				this.gender = 0;
			} else if(num === 2) {
				this.gender = 1;
			} else {
				this.gender = 3;
			}
		} else {
			this.gender = 2;
		}
	};

	NounEn.prototype.toJSON = function() {
		if(!this.str) return;
		var ret = Noun.prototype.toJSON.call(this);
		return ret;
	};

	NounEn.preHelpsForPadeji = ['', 'many'];
	NounEn.prototype.padejsCount = NounEn.preHelpsForPadeji.length;

	NounEn.prototype.parse = function(obj) {
		obj = Noun.prototype.parse.call(this, obj);
		return obj;
	};

	NounEn.prototype.getFormed = function(many) {
		if(!this.str) return '';
		var start = this.str.substr(0, this.str.length - 2);
		var end = this.str.substr(-2);
		var okon = '';

		if(many) {
			if(this.padejs && this.padejs[many]) {
				return this.padejs[many];
			}
			okon = 's';
			if(end[1] == 'o' || end[1] == 'x' || end[1] == 's' || end == 'sh' || end == 'ch') {
				okon = 'es';
			} else if(end[1] == 'y' && enLang.vowel.indexOf(end[0]) == -1) {
				okon = 'ies';
			} else if(end[1] == 'f') {
				end = end[0] + 'v';
				okon = 'es';
			} else if(end == 'fe') {
				end = 'v';
				okon = 'es';
			}
		}
		return start + end + okon;
	};
	NounEn.prototype.getFormedOf = function() {
		var ret;
		if(this.isPron && this.pronInd < 6) {
			ret = pronsOf[this.pronInd][0];
		} else {
			ret = this.getFormed();
			if(this.flagsValTest('одушевленное')) {
				if(ret.substr(-1) !== 's') {
					ret += '\'s';
				} else {
					ret += '\'';
				}
			}
		}
		return ret;
	};
	NounEn.prototype.getFormedWhom = function() {
		var ret;
		if(this.isPron && this.pronInd < 6) {
			ret = pronsObTo[this.pronInd][0];
		} else {
			ret = this.getFormed();
		}
		return ret;
	};

	NounEn.genderFlags = {'мужской род': 0, 'женский род': 0, 'только множественное': 0};

	NounEn.flags = Noun.flags;

})();
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.
var NounRu = function(minData) {
	this.construct();
	if(minData) this.parse(minData);
};

(function(){

	NounRu.prototype = new Noun();
	NounRu.prototype.wordLang = 1;

	NounRu.prototype.construct = function() {
		Noun.prototype.construct.call(this);
		this.str = '';
		this.odush = false; // указывает что это одушевленное.
	};

	Noun.prototype.genderToBites = function() {
		var ret = this.gender.toString(2);
		return new Array(3 - ret.length).join('0') + ret;
	};

	NounRu.prototype.addFlags = function(flagsStr) {
		// Устанавливает и парсит из численного значения флаги слова.
		// При склонении слова бывает необходимым знать некоторые флаги, их лучше записать в отдельные переменные.
		if(Noun.prototype.addFlags.call(this, flagsStr)) {
			if(flagsStr.substr(0, 4) === '0101') {
				this.odush = true;
			}
		}
	};

	NounRu.preHelpsForPadeji = [
		'',
		'           нет',
		'          дать',
		'        винить',
		'       крутить',
		'    говорить о',
		'мн.',
		'мн.        нет',
		'мн.       дать',
		'мн.     винить',
		'мн.    крутить',
		'мн. говорить о',
	];
	NounRu.prototype.padejsCount = NounRu.preHelpsForPadeji.length;
	NounRu.PadejsQuests = [
		['кто', 'что'],
		['кого', 'чего'],
		['кому', 'чему'],
		['кого', 'чего'],
		['кем, чем'],
		['ком', 'чём']
	];
	NounRu.okons = [[
		['а',	'я'],
		['ы',	'и'],
		'e',
		['у',	'ю'],
		['ой',	'ей'],
		'е'
	], [
		['о',	'е',	'ое',	'ее',	''],
		['а',	'я',	'ого',	'его'],
		['у',	'ю',	'ому',	'ему'],
		['а',	'я',	'ого',	'ее'],
		['ом',	'ем',	'ым',	'им'],
		['е',	'е',	'ом',	'ем']
	], [
		'', 'и', 'и', '', 'ю', 'и'
	], [
		['ы',	'и',	'а',	'я',	'ые',	'ие'],
		['ов',	'',		'',		'ей',	'ых',	'их'],
		['ам',	'ам',	'ам',	'ями',	'ым',	'им'],
		['ы',	'и',	'а',	'я',	'ые',	'ие'],
		['ами',	'ами',	'ами',	'ями',	'ыми',	'ими'],
		['ах',	'ах',	'ах',	'ях',	'ых',	'их'],
	]]; // таблица окончаний для [склонение][падеж]
	// якобы 4 склонение - это если в именительном используется множественное число.

	NounRu.prototype.getFormed = function(padej, many) {
		if(!this.str) return '';
		if(padej > 5) {
			padej = padej - 6;
			many = 1;
		}
		var imenit = this.str;
		if(!padej && !many) return imenit;
		var imOkon = imenit.substr(-1);
		var sklon = 2; // склонение слова 4 это если слово имеет только множественное число.
		if(this.gender === 3) {
			sklon = 4;
		} else if(this.gender && NounRu.okons[0][0].indexOf(imOkon) != -1) {
			sklon = 1;
		} else if(this.gender) {
			if(imOkon === 'ь') {
				sklon = 3;
			}
		}

		var kor;
		var okonInd;
		var imenitOkon = '';
		var imenitOkons = NounRu.okons[sklon - 1][0];
		if(sklon != 3) {
			for(var i = 0; i < imenitOkons.length; i++) {
				if(imenitOkons[i] === imenit.substr(-imenitOkons[i].length)) {
					kor = imenit.substr(0, imenit.length - imenitOkons[i].length);
					okonInd = i;
					imenitOkon = imenitOkons[i];
				}
				if(!imenitOkons[i] && !kor) {
					okonInd = i;
				}
			}
		}
		if(!kor) kor = imenit;

		var specPadej = 0;
		if(this.padejs) specPadej = this.padejs[padej + 6 * (many || 0)];
		if(specPadej) {
			if(specPadej.substr(0, 1) === '*') {
				return kor + okonInd + specPadej.substr(1);
			}
			return specPadej;
		}
		var okon = NounRu.okons[sklon-1][padej];
		if(sklon === 2 && padej === 3 && this.flags && !this.odush) okon = NounRu.okons[1][0];
		if(isArray(okon)) {
			if(okonInd >= okon.length) {
				okon = okon[0];
			} else {
				okon = okon[okonInd];
			}
		}
		// А теперь подправим по некоторым правилам и эвристикам словообразования
		var endKor = kor[kor.length - 1];
		var startKor = kor.substr(0, kor.length - 1);
		var writeThis = 0;
		if(many) {

			var specKor = '';
			var specOkon = '';

			if(this.padejs && this.padejs[6]) {
				specKor = this.padejs[6].substr(0, this.padejs[6].length - 1);
				specOkon = this.padejs[6].substr(-1);

			}
			var end;
			if(specKor) {
				kor = specKor;
				okon = specOkon;
				endKor = kor.substr(-1);
				startKor = kor.substr(0, kor.length - 1);
				if(padej === 1) {
					okon = 'ей';
					if(('аы').indexOf(specOkon) != -1) {
						okon = 'ов';
					}
					if(('ъь').indexOf(endKor) != -1) {
						okon = 'ев';
					}
					end = 1;
				}
				if(padej === 3) {
					end = 1;
				}
			}
			if(end) {

			} else if(padej === 0 || (padej === 3 && !this.odush)) {

				if(imenitOkon === 'ые') {
					okon = 'ые';
				} else {
					if(sklon === 4) {
						okon = imOkon;
					} else if(imenitOkon === 'о' && endKor != 'ь') {
						okon = 'а';
					} else if(imenitOkon === 'а' && ('гчкшщжх').indexOf(endKor) === -1) {
						okon = 'ы';
					} else if(endKor === 'я') {
						okon = 'ена'; // редко
					} else if(imenitOkon === '') {
						okon = 'и';
						if(endKor === 'ь') {
							kor = startKor;
						}
						if(sklon != 3) {
							if(('зсрдцнтялм').indexOf(endKor) != -1) {
								// 7% окончания а.
								// 5% не вырезаны гласные из суффикса.
								// 3% особые формы.
								okon = 'ы';
							} else if(('ьй').indexOf(endKor) != -1) {
								// 3% окончания я.
								// 2% особые формы.
								// 2% не вырезаны гласные из суффикса.
								kor = startKor;
								if(kor.substr(-2) === 'ен') {
									kor = kor.substr(0, kor.length - 2) + 'н';
								}
							} else if(('бвп').indexOf(endKor) != -1) {
								okon = 'ы';
								// 10% окончания а хлебы островы
							} else {
								if(kor.length > 4 && kor.substr(-2) === 'ок') {
									// потки
									kor = kor.substr(0, kor.length - 2) + 'к';
								} else if(kor.substr(-2) === 'ег') {
									okon = 'a';
								}
							}
						}
					} else if(endKor === 'и' && imenitOkon != 'я') {
						// изредка специальная форма или отсутствие множественного.
						okon = 'я';
					} else if(sklon === 2) {
						// 40% мора будуща прошла цела жилья кофа
						okon = 'а';
						if(endKor === 'ь') {
							okon = 'я';
						}
						if(endKor === 'н') {
							okon = 'ые';
						}
					} else {
						// без проблем
						okon = 'и';
					}
					if(okon === 'и' && kor.substr(-2, 2) === 'ок') {
						if(('ндсшмтлр').indexOf(kor.substr(-3, 1)) != -1) {
							kor = kor.substr(0, kor.length-2) + 'к';
						}
					}
				}

			} else if(padej === 1 || padej === 3) {

				if(sklon === 4) {
					okon = imOkon;
				} else if(okon === 'о' && endKor != 'ь') {
					okon = 'а';
				} else if(okon === 'а' && ('гчкшщжх').indexOf(endKor) === -1) {
					okon = 'ы';
				} else if(endKor === 'я') {
					okon = 'ена'; // редко
				} else if(okon === '') {
					if(sklon === 3) {
						okon = 'и';
						if(endKor === 'ь') {
							kor = startKor;
						}
					} else {
						okon = 'и';
						if(('зсрдцнтялм').indexOf(endKor) != -1) {
							// 7% окончания а.
							// 5% не вырезаны гласные из суффикса.
							// 3% особые формы.
							okon = 'ы';
						} else if(('ьй').indexOf(endKor) != -1) {
							// 3% окончания я.
							// 2% особые формы.
							// 2% не вырезаны гласные из суффикса.
							kor = startKor;
							if(kor.substr(-2) === 'ен') {
								kor = kor.substr(0, kor.length - 2) + 'н';
							}
						} else {
							if(('бвп').indexOf(endKor) != -1) {
								okon = 'ы';
								// 10% окончания а хлебы островы
							} else {
								if(kor.length > 4 && kor.substr(-2) === 'ок') {
									// потки
									kor = kor.substr(0, kor.length - 2) + 'к';
								} else if(kor.substr(-2) === 'ег') {
									okon = 'a';
								}
							}
						}
					}
				} else if(endKor === 'и' && imenit.substr(-1) != 'я') {
					// изредка специальная форма или отсутствие множественного.
					okon = 'я';
				} else if(sklon === 2) {
					// 40% мора будуща прошла цела жилья кофа
					okon = 'а';
					if(endKor === 'ь') {
						okon = 'я';
					}
					if(endKor === 'н') {
						okon = 'ые';
					}
				} else {
					// без проблем
					okon = 'и';
				}
				if(okon === 'и' && kor.substr(-2, 2) === 'ок') {
					if(('ндсшмтлр').indexOf(kor.substr(-3, 1)) != -1) {
						kor = kor.substr(0, kor.length-2) + 'к';
					}
				}
				if(imenitOkon === 'ые') {
					okon = 'ые';
				}

				// выше идентично именительному падежу

				if(okon === 'я') {
					okon = 'ий';
				} else if(okon === 'и') {
					okon = '';
					if(endKor === 'ь') {
						kor = startKor;
						okon = 'ей';
					} else if(endKor === 'и') {
						okon = 'й';
					} else if(imenit.substr(kor.length) === 'я') {
							if((endKor === 'н' || endKor === 'л') && ('ео').indexOf(kor.substr(kor.length - 2, 1)) === -1) {
								kor = kor.substr(0, kor.length - 1) + 'е' + kor.substr(-1);
							}
							if(('еоя').indexOf(kor.substr(kor.length - 2, 1) != -1)) {
								okon = 'ь';
								if(kor.length < 4 && kor.substr(-1) === 'е') {
									okon = 'й';
								}
							}
					} else if(endKor === 'к' && ('аеёиоуыэюя').indexOf(kor.substr(kor.length - 2, 1)) === -1) {
						if(('чхшщфжй').indexOf(kor.substr(kor.length - 2, 1)) != -1) {
							kor = kor.substr(0, kor.length - 1) + 'е' + 'к';
						} else {
							kor = kor.substr(0, kor.length - 1) + 'о' + 'к';
						}
						// сумерок
					} else {
						if(kor.substr(-2, 1) === 'ь') {
							// бореб, полез, просеб
							kor = kor.substr(0, kor.length - 2) + 'е' + kor.substr(-1);
						}
					}
				} else {
					if(imenit.substr(-1) === 'о') {
						// 10% особые формы.
						// 3% странные окончания.
						okon = '';
						kor = imenit.substr(0, imenit.length - 1);
						if(('лцнмр').indexOf(kor.substr(-1, 1)) != -1) {
							if(('аеёиоуыэюя').indexOf(kor.substr(-2, 1)) === -1) {
								if(kor.substr(-2, 1) === 'ь') {
									kor = kor.substr(0, kor.length - 2) + 'е' + kor.substr(-1);
								} else if(kor.substr(-2, 1) === 'с') {
									kor = kor.substr(0, kor.length - 1) + 'е' + kor.substr(-1);
								} else {
									kor = kor.substr(0, kor.length - 1) + 'о' + kor.substr(-1);
								}
							}
						}
						// дерев крыл
					} else {
						if(okon === 'а') {
							okon = 'ов';
							if(kor.substr(-2) === 'ок') {
								kor = kor.substr(0, kor.length - 2) + 'к';
							} else if(imenit.substr(-1) === 'е') {
								// А хрен бы знал что сделать, какое то очень разнородное
							} else if(('чжщш').indexOf(kor.substr(-1)) != -1) {
								okon = 'ей';
							} else {
								// почти нормально
							}

						} else if(endKor === 'ь') {
							kor = startKor;
							okon = 'ей';
							if(kor.substr(-2) === 'ен') {
								kor = kor.substr(0, kor.length - 2) + 'н';
							}
							// нормально
						} else {
							okon = 'ов';
							if(('й').indexOf(endKor) != -1) {
								kor = startKor;
								okon = 'ев';
							} else if(kor.substr(-2) === 'ец') {
								kor = kor.substr(0, kor.length-2) + 'ц';
								if(('лмн').indexOf(kor.substr(-2, 1)) != -1) {
									okon = 'ев';
									if(kor.substr(-2, 1) === 'л') {
										kor = kor.substr(0, kor.length - 1) + 'ь' + kor.substr(kor.length - 1);
									}
									// концев
								}
							} else if(endKor === 'я') {
								kor = startKor;
								okon = 'ен';
							} else if(imenit.substr(-1) === 'е') {
								okon = 'ых';
							} else {
								// месяцов
							}
						}
					}

				}

			} else if(padej === 2 || padej === 4) {

				if(specKor) {
					kor = specKor;
					endKor = kor.substr(-1);
					startKor = kor.substr(0, kor.length - 1);
					if(specOkon === 'а' || ('цчшщ').indexOf(endKor) != -1 || (specOkon != 'и' && ('бгзкмпстх').indexOf(endKor) != -1)) {
						okon = 'ам';
					} else {
						okon = 'ям';
					}
					if(padej === 4) {
						okon += 'и';
					}
					// 2 людями, 22 детями, 326 дочерями,
				} else {

					okon = 'ам';
					if(endKor === 'я') {
						kor = startKor;
						okon = 'енам';
					} else if(imenitOkon === 'я' || (imenitOkon === 'е' && endKor != 'ц')) {
						okon = 'ям';
					} else if(endKor === 'й' || endKor === 'ь') {
						kor = startKor;
						okon = 'ям';
						// семям
					} else if(imenit.substr(-2) === 'ое') {
						okon = 'ым';
					}
					if(endKor === 'ц' && kor.substr(-3) === 'лец') {
						kor = kor.substr(0, kor.length - 2) + 'ь' + endKor;
					}
					if(sklon === 2 && kor.substr(-2, 2) === 'ен') {
						kor = kor.substr(0, kor.length-2) + 'н';
					}
					if(kor.length > 5 && kor.substr(-2, 2) === 'ок') {
						if(('ндсшмтлр').indexOf(kor.substr(-3, 1)) != -1) {
							kor = kor.substr(0, kor.length-2) + 'к';
							// 681 восткам
						}
					}
					if(imenitOkon === 'ые') {
						okon = 'ым';
					}
					if(kor.substr(-2) === 'ец') {
						kor = kor.substr(0, kor.length-2) + 'ц';
					}
					// деревам 2925, сонам 3549, ротам(рот) 4293, будущам 4365, колнам(колено) 5913, соседам 6249, стулам 7293, чертам 7953, лобам 8157, блкам 8577, крылам 9309, левам 9801, ледам 10125, нефтям 10149, молодежям 10161, кофям 10257, добрам 10581, господям 10761, едам 10989, злам 11001, людам 11961, буднам 12105.
					// 178 члнам, 248 уголы, 286 господинами, 335 ветрей, 364 будущами, 493 колнам, 680 лобами, 681 востками, 691 кусоками, 817 левами, 844 ледами, 847 молодежями, 855 кофями, 863 мешоками, 867 замоками, 897 господями, 948 песоками, 997 людами, 1009 буднами,

					if(padej === 4) {
						okon += 'и';
					}

				}

			} else if(padej === 5) {
				if(('ъь').indexOf(endKor) != -1) {
					okon = 'ях';
					kor = startKor;
				} else if(okon === 'и' && ('аеёиоуыэюя').indexOf(endKor) === -1) {
					okon = 'ях';
				} else {
					okon = 'ах';
					if(endKor === 'й' || endKor === 'и') {
						okon = 'ях';
						kor = startKor;
					}
				}
			}

		} else {

			if(padej != 3) {
				if(okon === 'е' && endKor === 'и') okon = 'и';
			}
			if(endKor === 'ь' && sklon > 1) {
				if(padej != 3) {
					if(padej === 4) {
						if(sklon === 2) {
							kor = startKor;
							okon = NounRu.okons[sklon-1][padej][1];
						}
					} else if(sklon === 3 || (sklon === 2 && okonInd === 4)) {
						kor = startKor;
						if(sklon === 2 && padej < 5) {
							okon = NounRu.okons[sklon-1][padej][1];
						}
					}
					if(sklon === 2 && kor.substr(-2, 2) === 'ен') {
						kor = kor.substr(0, kor.length-2) + 'н';
					}
				}
			} else if(endKor === 'ц') {
				if(okon === 'я') {
					okon = 'а';
				}
				if(startKor.substr(-1) === 'е' && okon) {
					var mz = '';
					if(kor.length > 3 && kor.substr(-3, 1) === 'л') mz = 'ь';
					kor = kor.substr(0, kor.length - 2) + mz + endKor;
				}
			} else if(endKor === 'й') {
				if(okon === 'а' || okon === 'я') {
					kor = startKor;
					okon = 'я';
				} else if(okon === 'е') {
					kor = startKor;
				} else if(okon === 'у') {
					kor = startKor;
					okon = 'ю';
				} else if(okon === 'ом') {
					kor = startKor;
					okon = 'ем';
				}
			} else if(endKor === 'я') { // редко
				if(padej === 4) {
					kor = startKor;
					okon = 'енем';
				} else if(padej != 3) {
					kor = startKor;
					okon = 'ени';
				}
			} else if(okon === 'ы' && 'кжгчхш'.indexOf(endKor) != -1) {
				okon = 'и';
			}
		}

		if(okon && okon.substr(0, 1) === 'я' && ('чщ').indexOf(kor.substr(-1)) != -1) {
			okon = 'а' + okon.substr(1) || '';
		}

		if(writeThis) console.log(testCounter, nounRu.preHelpsForPadeji[padej], kor + okon);

		return kor + okon;
	};

	NounRu.prototype.getFormedOf = function(padej, gender) {
		// pronInd - индекс местоимения, которому соответствует целевое слово.
		if(this.isPron) {
			var ret = pronsOf[this.pronInd][1];
			if(isArray(ret)) {
				ret = ret[gender || 0];
				if(ret[padej] === 1) {
					ret = ret[1];
				} else if(ret[padej] === -1) {
					ret = pronsOf[this.pronInd][1][0][gender];
				} else {
					ret = ret[padej];
				}
			}
			return ret;
		} else {
			if(this.odush) return this.getFormed(1, gender > 2 ? 1 : 0);
			return this.getFormed(padej, gender > 2 ? 1 : 0);
		}
	};
	NounRu.prototype.getFormedWhom = function(padej, many) {
		return this.getFormed(padej, many);
	};

	NounRu.prototype.apportionFlagToTreeRadioAndCheckboxes = function(str, map, onlyFirstChars, filtrFlags) {
		// Возвращает ту часть флагов, что отвечает за категорию в WordWithFlags.flags
		return Noun.prototype.apportionFlagToTreeRadioAndCheckboxes.call(this, str, map || NounRu.flags, onlyFirstChars, filtrFlags);
	};

	NounRu.genderFlags = {'женский род': 0, 'средний род': 0, 'только множественное': 0};

	NounRu.flags = Noun.flags;

})();
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.
var Verb = function(str) {
	this.construct();
	if(str) this.parse(str);
};
(function() {
	Verb.prototype = new WordWithPadejs();
	Verb.prototype.wordTypeName = 'Verb';
	Verb.prototype.wordType = 2; // 1 - существительное, 2 - глагол.

	Verb.prototype.construct = function() {
		WordWithPadejs.prototype.construct.call(this);
		this.verbFlags = []; // дополнительные флаги специально для глаголов.
	};

	Verb.prototype.parse = function(obj) {
		// устанавливает из ['любить',[249,145], 0, [46, 12]];
		// Значения флагов преобразуются в строки из нулей и единиц.
		obj = WordWithPadejs.prototype.parse.call(this, obj);
		if(obj) {
			obj = this.parseFlags(obj, 3, this.verbFlags);
		}
		return obj;
	};

	Verb.prototype.toJSON = function() {
		var ret = WordWithPadejs.prototype.toJSON.call(this);
		// добавим verbFlags по индексу 3.
		if(ret) {
			if(this.verbFlags) {
				var flags = [];
				for(var i = 0; i < this.verbFlags.length; i++) {
					var val = parseInt('1' + this.verbFlags[i], 2);
					if(val > 1) {
						flags.push(val);
					}
				}
				if(flags.length) ret[3] = flags;
			}
		}
		return ret;
	};

	Verb.verbFlags = Verb.prototype.flagsMap = {
		'checkboxes': [
			'совершенное',
			'продолжительное',
			{'name': 'занимает', 'title': 'отнимает врямя у исполнителя действия'},
		],
		'активное действие': {
			'title': '(делать, смотреть, работать, следовать, начинать, становиться, использовать, пытаться, заниматься, продолжать, искать, составлять, действовать, служить, выступать, помогать, пользоваться, создавать, участвовать, платить, слушать, )',
			'кинетическое': {
				'title': '(играть, принимать, пить, глядеть, держать, показывать, ставить, хватать, звонить, бить, встречаться, рассматривать, мешать, снимать, )',
				'перемещаться': {
					'title': '(возвращаться, )',
					'горизонтальное': {
						'title': '(идти, ходить, вести, выходить, входить, приходить, уходить, проходить, проводить, бежать, )',
					},
					'вертикальное': {
						'в небо': 0,
						'под воду': 0,
					},
				},
				'перемещать': {
					'title': '(давать, брать. передать. возвращать. )',
					'горизонтальное': '(нести, носить, приводить, )',
					'вертикальное': {
						'в небо': 0,
						'под воду': 0,
					},
				},
				'удержание': '(схватить, )',
				'разрушение': '(ломать, )',
				'создание': '(строить, собирать, )',
			},
			'мимика': '(скалиться, )',
			'вербальное': {
				'разговор': '(говорить, называть, отвечать, рассказывать, просить, спрашивать, называть, требовать, кричать, предлагать, вызывать, звать, обещать, напоминать, обращаться, утверждать, )',
				'письменное': '(писать, )',
			},
			'умственное': {
				'title': '(думать, считать, вспоминать, учиться, судить, решать, определять, )',
				'созидание': '(придумать, посчитать, представлять, )',
				'считывание': '(читать, наблюдать, )',
			},
			'излучать нечто': 0,
		},
		'пассивное действие': {
			'title': '(быть, мочь, хотеть, иметь, жить, любить, ждать, оставаться, происходить, чувствовать, спать, позволять, уметь, начинаться, зависеть, слышать, )',
			'кинетическое': {
				'title': '(сидеть, стоять, лежать, получать, касаться, возникать, расти, плакать, )',
				'перемещаться': '(ехать, )',
				'перемещать': 0,
				'удержание': 0,
				'разрушение': 0,
				'создание': 0,
			},
			'мимика': '(смеяться, улыбаться, )',
			'вербальное': '(молчать, )',
			'умственное': '(знать, видеть, помнить, бояться, верить, надеяться, понимать, желать, ожидать, )',
			'навязаное окружением': '(являться, стоить, казаться, находиться, бывать, существовать, нравиться, оказываться, получаться, выглядеть, состоять, называться, значить, иметься, получаться, подходить, считаться, принадлежать, занимать, обладать, отличаться, соответствовать, означать, приходиться, )',
			'излучать нечто': 0,
			'обладать': 'иметь отношение к чему то',
		},
	};

	Verb.prototype.verbFlagsRangesObj = {};

	Verb.prototype.subFlagsRanges(Verb.verbFlags, '', Verb.prototype.verbFlagsRangesObj, Verb);

	Verb.subjects = ['who', 'whom', 'usingWhat']; // флаги глаголов делятся на эти группы и ниже.
	Verb.flags = {}; // указывает флаги, которые должны быть у существительного к которым применяется. Обратите внимание, достраивается ниже. Не смотря на то, что Verb.flags изменен, в verbs.btsByFlags будут добавляться по таким же ключам как и nouns.btsByFlags .
	var i;
	for(i = 0; i < Verb.subjects.length; i++) {
		Verb.flags[Verb.subjects[i]] = WordWithFlags.flags;
	}
	Verb.specialFlags = { // специальные категории для глаголов.
		'...делать':{
			'checkboxes': ['обязательно', 'совершенное', 'продолжительное', 'занимает'],
			'активное':0,
			'пассивное':0,
		}, // хотеть, начинать.
	};
	Verb.specialFlagsKeys = [];
	for(i in Verb.specialFlags) {
		Verb.flags[i] = Verb.specialFlags[i];
		Verb.specialFlagsKeys.push(i);
	}
	Verb.destinations = ['where', 'to', 'from', '_'];
	for(i = 0; i < Verb.destinations.length; i++) {
		Verb.flags[Verb.destinations[i]] = WordWithFlags.flags;
	}
	Verb.flagsGroups = Verb.subjects.concat(Verb.specialFlagsKeys).concat(Verb.destinations);
	Verb.bitesByFlagsGroupName = {};
	var bites;
	var bitesLen = Math.ceil(Math.log(Verb.flagsGroups.length + 1) / Math.log(2)); // колличетсво битов необходимое для записи выбранного(или пустого) radio.
	for(i = 0; i < Verb.flagsGroups.length; i++) {
		bites = (i + 1).toString(2);
		while(bites.length < bitesLen) {
			bites = 0 + bites;
		}
		Verb.bitesByFlagsGroupName[Verb.flagsGroups[i]] = bites;
	}
	Verb.prototype.subFlagsRanges(Verb.flags, 0, 0, Verb);

	Verb.prototype.flagsValTest = function(valStr, strict, flags) {
		// Определяет, совпадает ли заначение битов флага с названием valStr.
		/*if(valStr === '...делать') {
			var apportionedFlags = this.getApportionedFlags();
			for(var i = 0; i < apportionedFlags.length; i++) {
				if(apportionedFlags[i][3] === Verb.bitesByFlagsGroupName['...делать']) return 1;
			}
		} else {*/
			var fgs;
			for(i = 0; i < Verb.flagsGroups.length; i++) {
				//Verb.bitesByFlagsGroupName = fgs()
				if(WordWithPadejs.prototype.flagsValTest.call(this, valStr, strict, flags[Verb.flagsGroups[i]])) return 1;
			}
		//}
	};
	Verb.prototype.checkboxesValTest = function(valStr, flags, group) {
		var groups = Verb.flagsGroups;
		if(group) group = [group];
		for(i = 0; i < groups.length; i++) {
			//Verb.bitesByFlagsGroupName = fgs()
			if(WordWithPadejs.prototype.checkboxesValTest.call(this, valStr, flags[groups[i]])) return 1;
		}
	};

	Verb.prototype.apportionFlagToTreeRadioAndCheckboxes = function(str, map, onlyFirstChars, filtrFlags) {
		// Парсит флаг, разделяя его на категорию в дереве, checkboxы, и наличие/отсутствие флага 'filter'
		if(filtrFlags) {
			if(isString(filtrFlags)) {
				if(Verb.flagsGroups[parseInt(str.substr(0, 4), 2) - 1] !== filtrFlags) return;
			} else {
				if(!filtrFlags(str)) return;
			}
		}
		var ret = WordWithPadejs.prototype.apportionFlagToTreeRadioAndCheckboxes.call(this, str, map || Verb.flags, onlyFirstChars, filtrFlags);
		ret[3] = ret[0].substr(0, 4); // устанавливаем в отдельный элемент flagsGroup
		ret[0] = ret[0].substr(4); // убираем первые 4 бита на Verb.flagsGroups и получаем обычную категорию.
		return ret;
	};
	Verb.prototype.disapportionFlag = function(flag) {
		var onlyFirstChars = flag[3];
		var map = Verb.flags[Verb.subjects[parseInt(onlyFirstChars, 2) - 1]];
		return onlyFirstChars + WordWithFlags.prototype.disapportionFlag.call(this, flag, map);
	};
	Verb.prototype.apportionVerbFlagToTreeRadioAndCheckboxes = function(str, map, onlyFirstChars, filtrFlags) {
		// Парсит флаг, разделяя его на категорию в дереве, checkboxы, и наличие/отсутствие флага 'filter'
		var ret = WordWithPadejs.prototype.apportionFlagToTreeRadioAndCheckboxes.call(this, str, map || Verb.flags, onlyFirstChars, filtrFlags);
		return ret;
	};
	Verb.prototype.getApportionedVerbFlags = function() {
		// возвращает флаги в виде разделенных на категории и чекбоксы, не дополненные нулями(лучше потом дополнить)
		var ret = [];
		for(var i = 0; i < this.verbFlags.length; i++) {
			ret.push(this.apportionVerbFlagToTreeRadioAndCheckboxes(this.verbFlags[i], Verb.verbFlags, 1));
		}
		return ret;
	};

	Verb.prototype.verbFlagsValTest = function(valStr, strict, flags) {
		if(!flags) flags = this.getApportionedVerbFlags(); // Вот тут, когда вызывается через Verb.prototype, можно выстрелить в ногу!
		// Определяет, совпадает ли заначение битов флага с названием valStr.
		for(var i = 0; i < flags.length; i++) {
			if(this.flagValTest(flags[i], valStr, strict, this.verbFlagsRangesObj.flagsRanges)) return 1;
		}
		return false;
	};
	Verb.prototype.verbCheckboxesValTest = function(valStr, flags) {
		if(!flags) flags = this.getApportionedVerbFlags();
		for(var i = 0; i < flags.length; i++) {
			if(flags[i] && flags[i][1] && flags[i][1].indexOf(valStr) !== -1) return true;
		}
	};

})();
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.
var VerbEn = function(str) {
	this.construct();
	if(str) this.parse(str);
};

(function(){

	VerbEn.prototype = new Verb();
	VerbEn.prototype.wordLang = 0;
	VerbEn.preHelpsForPadeji = ['normal', 'past simple', 'past participle', '3-е л. ед. ч.'];
	VerbEn.prototype.padejsCount = VerbEn.preHelpsForPadeji.length;

	VerbEn.prototype.getFormed = function(formOrPron, t, f) {
		var ret = this.str;
		if((formOrPron === 1 && t === undefined) || (t === 0 && f === 1)) {
			if(this.padejs && this.padejs[1]) {
				return this.padejs[1];
			} else {
				return ret + (ret.substr(-1) != 'e' ? 'e' : '') + 'd';
			}
		} else if(formOrPron === 2 && t === undefined) {
			if(this.padejs && this.padejs[2]) {
				return this.padejs[2];
			} else {
				return ret + (ret.substr(-1) != 'e' ? 'e' : '') + 'd';
			}
		}
		if((formOrPron === 3 && t === undefined) || t === 1 && f === 1 && (formOrPron > 3)) {
			if(this.padejs && this.padejs[3]) {
				return this.padejs[3];
			} else {
				var end = this.str.substr(-2);
				var okon = 's';
				if(end[1] == 'o' || end[1] == 'x' || end[1] == 's' || end == 'sh' || end == 'ch') {
					okon = 'es';
				}
				ret += okon;
			}
		}
		return ret;
	};

	VerbEn.prototype.getFormedIng = function() {
		var ret = this.str;
		var end;
		if(ret.indexOf(' ') !== -1) { // например "look out".
			end = ret.substr(ret.indexOf(' '));
			ret = ret.substr(0, ret.indexOf(' '));
		}
		if(ret[ret.length-1] === 'e') {
			if(ret[ret.length - 2] === 'i') {
				ret = ret.substr(0, ret.length - 2) + 'y';
			} else {
				ret = ret.substr(0, ret.length - 1);
			}
		}
		// если глагол заканчивается на краткую гласную с последующей согласной, то конечная согласная удваивается (sit - sitting). Как определить краткость гласной - не знаю.
		ret += 'ing';
		if(end) ret += end;
		return ret;
	};

	VerbEn.prototype.getFormedParticiple = function(typ, passive) {
		// typ - означает какой вид причастия требуется, 0 - Present Participle Simple, 1 - Present Participle Perfect, 2 - Past Participle
		//                                                   открывающий, отдыхающая        открыв, отдохнув                открытый, отдохнувшая
		// passive: 0 - Действительный залог, 1 - Страдательный залог
		if(typ || passive) return; // Present Participle Perfect, Past Participle и страдательный залог пока не используем.
		var ret = this.getFormedIng();
		return ret;
	};

	VerbEn.flags = Verb.flags;

})();
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed() будет давать другой результат - bts-ы перестанут его находить, это очень плохо.
var VerbRu = function(arr) {
	this.construct();
	this.kor = '';
	if(arr) this.parse(arr);
};

(function() {

	VerbRu.prototype = new Verb();
	VerbRu.prototype.wordLang = 1;
	VerbRu.preHelpsForPadeji = [
		'они будут',		// 0
		'я сейчас',			// 1
		'вы сейчас',		// 2
		'оно раньше',		// 3
		'мы сейчас',		// 4
		'они сейчас',		// 5
		'он/она сейчас',	// 6
		'я/он раньше',		// 7
		'вы/мы/они раньше',	// 8
		'она раньше',		// 9
		'что делая?',		// 10
		'происходит...',	// 11
	];
	VerbRu.prototype.padejsCount = VerbRu.preHelpsForPadeji.length;

	// глаголы(без наречий) видоименяются в 9 разных форм для разных сочетаний местоимения и времени. 10-ый padej для deeprichastie
	//                                 0     1       2   3      4   5   6   7   8        9              - это номера форм
	VerbRu.formRuVerbByTimeAndPron = [[[0,4],[1,2,3],[5],[6]], [[0],[1],[2],[3],[4,5]], [[0,1,2,3,4,5]]]; // позиционирует, к какой пронумерованой форме принадлежит глагол. Цифры означают местоимения.
	//                                прошедшее                настоящее                будущее
	VerbRu.formRuVerbByPronAndTime = [[7,1], [8,2], [8,4], [8,5], [7,6], [9,6], [3,6]]; // двумерный массив [местоимение, время] дает значение индекса формы русского глагола.
	VerbRu.formRuVerbByPronAndTimeComplete = [[7,7,1], [8,8,2], [8,8,4], [8,8,5], [7,7,6], [9,9,6], [3,3,6]]; // двумерный массив [местоимение, время] дает значение индекса формы русского глагола.

	VerbRu.okons = [
	//0			1		2		3		4		5		6		7		8		9		10		11
	['овать',	'зать',	'ыть',	'ить',	'ать',	'еть',	'ять',	'уть',	'оть',	'ти',	'чь',	'ть'],		// они будут
	['ую',		'жу',	'ою',	'ю',	'аю',	'ю',	'яю',	'у',	'ю',	'у',	'гу',	'у'],		// я сейчас
	['уете',	'жете',	'оете',	'ите',	'аете',	'ите',	'яете',	'ите',	'ите',	'ете',	'жете',	'ете'],	// вы сейчас
	['овало',	'зало',	'ыло',	'ило',	'ало',	'ело',	'яло',	'уло',	'оло',	'ло',	'гло',	'ло'],		// оно раньше
	['уем',		'жем',	'оем',	'им',	'аем',	'им',	'яем',	'ем',	'им',	'ем',	'жем',	'ем'],		// мы сейчас
	['уют',		'жут',	'оют',	'ят',	'ают',	'ят',	'яют',	'ут',	'ют',	'ут',	'гут',	'ут'],		// они сейчас
	['ует',		'жет',	'оет',	'ит',	'ает',	'ит',	'яет',	'ет',	'ит',	'ет',	'жет',	'ет'],		// он/она сейчас
	['овал',	'зал',	'ыл',	'ил',	'ал',	'ел',	'ял',	'ул',	'ол',	'',		'г',	''],		// я/он раньше
	['овали',	'зали',	'ыли',	'или',	'али',	'ели',	'яли',	'ули',	'оли',	'ли',	'гли',	'ли'],		// вы/мы/они раньше
	['овала',	'зала',	'ыла',	'ила',	'ала',	'ела',	'яла',	'ула',	'оло',	'ла',	'гла',	'ла']		// она раньше
	//					редко
	];
	VerbRu.specOkons = {
		'жать':	['ать','у','ите','ало','им','ат','ет','ал','али','ала'],
		'ать':	['ать','у','ите','ло','им','ат','ит','ел','ли','ла']
	};

	VerbRu.prototype.generateCommonFormed = function(group, okonsArr, forParticiple) {
		// функция выполняет основную часть генерации склонения слова.
		var ret = '';
		var kor, korEnd, korStart, okonInd, okon;
		if(forParticiple && group === undefined) group = 5;
		if(!group) group = 0;
		if(group > 0 && this.padejs && this.padejs[group]) {
			ret = this.padejs[group];
			if(ret) {
				if(ret[0] === '*') {
					ret = this.kor + this.padejs[group].substr(1);
				}
				if(forParticiple) {
					var korAndOkonInd = detectAndCutOkon(ret, VerbRu.okons[group]);
					ret = korAndOkonInd[0] + okonsArr[group][korAndOkonInd[1]];
				}
				return ret;
			}
		}
		if(this.okonInd !== undefined || okonInd !== undefined) {
			// Не забываем, примерно в 10% склоняется не правильно.
			if(!kor) kor = this.kor;
			korEnd = kor.substr(-1);
			korStart = kor.substr(0, kor.length - 1);
			if(!okonInd) okonInd = this.okonInd;
			if(!okon) okon = okonsArr[group][okonInd];
			if(!okon) okon = '';
			if(group < 7 && group !== 3) {
				if(okonsArr[0][okonInd] === 'ать') {
					if(kor.length < 5 && ruLang.getCountSyllables(kor) === 1) {
						if(korEnd === 'к') {
							korEnd = 'ч';
							if(korStart.substr(-1) === 'с') {
								korStart = korStart.substr(0, korStart.length - 1);
								korEnd = 'щ';
							}
							okon = okon.substr(1);
						} else if(korEnd === 'ш' && korStart.substr(-1) === 'ы') {
							okon = VerbRu.specOkons['ать'][group];
						} else if(korEnd === 'ч') {
							okon = VerbRu.specOkons['ать'][group];
						}
					}
					if(korEnd === 'ж' && korStart.substr(-1) === 'р') {
						okon = VerbRu.specOkons['жать'][group];
					}
				} else if(!korStart.length) {
					if(okonsArr[0][okonInd] === 'ить') {
						korStart = korEnd;
						korEnd = 'ь';
						if(forParticiple) okon = 'ю';
					} else if(okon[0] !== 'о') {
						korStart = korEnd;
						korEnd = 'о';
					}
					if(okon[0] === 'и') {
						okon = 'е' + okon.substr(1);
					}
				} else if(okonsArr[0][okonInd] === 'ять' && okon[0] === 'я' && ruLang.vowel.indexOf(korEnd) !== -1) {
					okon = okon.substr(1);
					if(okon[0] === 'е' && korEnd !== 'е') okon = 'и' + okon.substr(1);
				}
			}
			if(okon && okon[0] === 'ю') {
				if(korEnd === 'д') {
					okon = 'у' + okon.substr(1);
					korEnd = 'ж';
				} else if(korEnd === 'с') {
					okon = 'у' + okon.substr(1);
					korEnd = 'ш';
				} else if(korEnd === 'п') {
					korStart += korEnd;
					korEnd = 'л';
				} else if(korEnd === 'м') {
					korStart += korEnd;
					korEnd = 'е';
					if(okonsArr[0][okonInd][0] === 'и') {
						korEnd = 'л';
					}
				} else if(korEnd === 'ш') {
					okon = 'у';
				} else if(okon === 'ю' && korEnd === 'в') {
					korStart += korEnd;
					korEnd = 'л';
				} else if(group > 1 && korEnd === 'о') {
					okon = 'я' + okon.substr(1);
				}
			}

			if(korEnd === 'в' && kor[kor.length - 2] === 'а') {
				if(group && okonsArr[0][okonInd] === 'ать') {
					korEnd = korStart.substr(-2, 1);
					korStart = korStart.substr(0, korStart.length - 2);
				} else if(group === 1 && !forParticiple) {
					korStart += korEnd;
					korEnd = 'л';
				}
			}
			if(korEnd === 'ж') {
				if(okon && okon[0] === 'а' && (okon[1] === 'ю' || okon[1] === 'е') && korStart.substr(-1) === 'е') {
					if(okon[1] === 'е') {
						okon = 'и' + okon.substr(2);
					} else if(okon[1] === 'ю') {
						if(group === 1) {
							if(forParticiple) {
								okon = 'а' + okon.substr(2);
							} else {
								okon = 'у' + okon.substr(2);
							}
						} else {
							okon = okon[0] + okon.substr(2);
						}
					}
				} else if(okon[0] === 'ю' || okon[0] === 'я') {
					if(okon[0] === 'ю') {
						okon = 'у' + okon.substr(1);
					} else {
						okon = 'а' + okon.substr(1);
					}
				}
			}
			if(korEnd === 'м' && okon[0] === 'и' && VerbRu.okons[0][okonInd][0] === 'е') {
				okon = 'ее' + okon.substr(1);
			}
			if(okon === 'ят' && kor.length < 3) {
				if(kor.length < 2) {
					okon = 'ют';
				} else if(korEnd === 'м') {
					okon = 'ют';
					korStart += korEnd;
					korEnd = 'е';
				}
			}
			if(forParticiple) {
				if(okon) {
					if(okon.substr(-1) === 'т') { // слышатщей, держатщей, кричатщей, молчатщей, ...
						okon = okon.substr(0, okon.length - 1);
					}
					if(this.okonInd === 4 && okon === 'ую') { // пишующий, ждующий, берующий, едующий, бегующий, зовующий
						okon = 'у';
					}
					if(this.okonInd === 5 && ('мл').indexOf(korEnd) !== -1 && korStart.substr(-1) !== 'е') { // имящей, умящей, имящейся
						okon = 'ею';
					}
					if(kor.length < 2) {
						if(okon === 'я') okon = 'ю'; // поящий
					}
					// радящий(радеть), фанарящий
				}
			}
			ret = korStart + korEnd + (okon || '');
		}
		if(ret === '') return undefined;
		return ret;
	};

	VerbRu.prototype.getFormed = function(groupOrPron, t, f) {
		// f принимается для аналогии с verbEn но не используется.
		if(groupOrPron === undefined || (groupOrPron === 0 && t === undefined)) return this.str || undefined; // Тут вышла небольшая заминка, при getFormed() - выдается неопределенная форма, хотя можно предположить мужской род прошедшее.
		if(groupOrPron === 10) return this.getFormedDeeprichastie();
		if(groupOrPron === 11) return this.getFormedNoun();
		var group = -1;
		if(t === undefined) {
			group = groupOrPron || 0;
		}
		if(group === -1) {
			group = (this.verbCheckboxesValTest('совершенное') ? VerbRu.formRuVerbByPronAndTimeComplete : VerbRu.formRuVerbByPronAndTime)[groupOrPron][t] || 0; // Коряво, это по тому, что изначально не собирался использовать совершенные глаголы.
		}
		var ret = this.generateCommonFormed(group, VerbRu.okons);
		if(ret === undefined) return undefined;
		if(this.sya && ret) {
			if(group === 0 || group > 3 && group < 8) {
				ret += 'ся';
			} else {
				ret += 'сь';
			}
		}
		ret = ruLang.fixWord(ret);
		if(!ret) return;
		return ret;
	};

	VerbRu.okonsVersionForParticiple = [ // Окончания немного меняются для причастий
	//0			1		2		3		4		5		6		7		8		9		10
	['овать',	'зать',	'ыть',	'ить',	'ать',	'еть',	'ять',	'уть',	'оть',	'ти',	'чь',	'ть'],		// они будут
	//['ую',		'жу',	'ою',	'ю',	'аю',	'ю',	'яю',	'у',	'ю',	'у',	'гу'],
	['ую',		'жу',	'ою',	'я',	'аю',	'я',	'яю',	'у',	'ю',	'у',	'гу',	'ю']
	];
	(function() {for(var i = 2; i < 10; i++) VerbRu.okonsVersionForParticiple[i] = VerbRu.okonsVersionForParticiple[1];})();
	VerbRu.okonsParticiple = [[
	//					нет				дать			винить			крутить			говорить о
		'щий',			'щего',			'щему',			'щего',			'щим',			'щем'	// мужской
	], [
		'щая',			'щей',			'щей',			'щую',			'щей',			'щей'	// женский
	], [
		'щее',			'щего',			'щему',			'щее',			'щим',			'щем'	// средний
	], [
		'щие',			'щих',			'щим',			'щих',			'щими',			'щих'	// множественное
	]];
	VerbRu.prototype.getFormedParticiple = function(typ, passive, gender, padej) {
		// Возвращает причастие, образованое от этого глагола.
		// typ - означает какой вид причастия требуется, 0 - Present Participle Simple, 1 - Present Participle Perfect, 2 - Past Participle
		//                                                   открывающий, отдыхающая        открыв, отдохнув                открытый, отдохнувшая
		// passive: 0 - Действительный залог, 1 - Страдательный залог
		if(typ || passive) return; // Present Participle Perfect, Past Participle и страдательный залог пока не используем.
		if(!gender) gender = 0;
		if(!padej) padej = 0;
		var ret = this.generateCommonFormed(undefined, VerbRu.okonsVersionForParticiple, 1);
		if(ret === undefined) return undefined;
		ret += VerbRu.okonsParticiple[gender][padej];
		if(this.sya) {
			if(gender !== 3) {
				ret += 'ся';
			} else {
				ret += 'сь';
			}
		}
		//if(this.okonInd === 4) writeThisTest = true;
		ret = ruLang.fixWord(ret);
		if(!ret) return;
		return ret;
	};
	VerbRu.okonsDeeprichastie = [ // Окончания немного меняются для причастий
	//0			1		2		3		4		5		6		7		8		9		10
	['овать',	'зать',	'ыть',	'ить',	'ать',	'еть',	'ять',	'уть',	'оть',	'ти',	'чь',	'ть'],		// они будут
	//['ую',		'жу',	'ою',	'ю',	'аю',	'ю',	'яю',	'у',	'ю',	'у',	'гу'],
	['уя',		'жа',	'оя',	'я',	'ая',	'я',	'яя',	'я',	'я',	'я',	'гя',	'я']
	];
	(function() {for(var i = 2; i < 10; i++) VerbRu.okonsDeeprichastie[i] = VerbRu.okonsDeeprichastie[1];})();
	VerbRu.prototype.getFormedDeeprichastie = function() {
		if(this.padejs && this.padejs[10]) {
			ret = this.padejs[10];
			if(ret) {
				if(ret[0] === '*') {
					ret = this.kor + this.padejs[10].substr(1);
				}
				return ret;
			}
		}
		var ret = this.generateCommonFormed(undefined, VerbRu.okonsDeeprichastie, 1);
		if(!ret) return undefined;
		var start = ret.substr(0, ret.length - 1);
		var end = ret.substr(-1);
		if(end === 'я' && start.substr(-3) === 'ежа') {
			end = '';
		} else if(end !== 'а' && this.okonInd >= 0 && VerbRu.okonsDeeprichastie[1][this.okonInd].substr(-1) !== end) {
			end = 'я'; // заменяем всякие у,ю в хитро подставленные generateCommonFormed
		} else if(end === 'я' && this.str.substr(ret.length - 2, 3) === 'ава') {
			start += 'ва';
		} else if(start.substr(-1) === 'н' && this.str.substr(start.length, 1) === 'е') {
			start += 'е';
		}
		ret = start + end;
		if(this.sya) {
			ret += 'сь';
		}
		ret = ruLang.fixWord(ret);
		if(!ret) return;
		return ret;
	};

	VerbRu.okonsNoun = [ // Окончания другие для отглагольных существительных.
	//0			1			2		3		4		5		6		7
	['дить',	'вить',		'сить',	'ать',	'еть',	'ять',	'езти',	'ести'],		// они будут
	['ждение',	'вление',	'шение','ание',	'ение',	'яние',	'озка',	'оска']			// процесс...
	];
	VerbRu.prototype.getFormedNoun = function() {
		if(this.padejs && this.padejs[11]) {
			var ret = this.padejs[11];
			if(ret) {
				if(ret[0] === '*') {
					ret = this.kor + this.padejs[11].substr(1);
				}
				return ret;
			}
		}
		var formed = this.getFormed();
		if(formed) {
			for(var i = 0; i < VerbRu.okonsNoun[0].length; i++) {
				if(formed.substr(-VerbRu.okonsNoun[0][i].length) === VerbRu.okonsNoun[0][i]) {
					return formed.substr(0, formed.length - VerbRu.okonsNoun[0][i].length) + VerbRu.okonsNoun[1][i];
				}
			}
		}
	};

	var detectAndCutOkon = function(str, okonsArr) {
		var kor = '';
		var okon = '';
		var okonInd = -1;
		for(var i = 0; i < okonsArr.length; i++) {
			if(str.substr(-okonsArr[i].length) === okonsArr[i]) {
				if(okonsArr[i] !== 'зать' || ruLang.vowel.indexOf(str[str.length - 5]) !== -1) {
					okonInd = i;
					kor = str.substr(0, str.length - okonsArr[i].length);
					return [kor, okonInd];
				}
			}
		}
		return [str, -1];
	};

	VerbRu.prototype.parse = function(obj) {
		obj = Verb.prototype.parse.call(this, obj);
		this.sya = false;
		this.kor = this.str;
		this.okonInd = undefined;
		if(this.kor) {
			if(this.kor.substr(-2) === 'ся') {
				this.kor = this.kor.substr(0, this.kor.length - 2);
				this.sya = true;
			}
			var korAndOkonInd = detectAndCutOkon(this.kor, VerbRu.okons[0]);
			this.kor = korAndOkonInd[0];
			this.okonInd = korAndOkonInd[1];
		}
		return obj;
	};

	VerbRu.flags = Verb.flags;

})();

// Качественные прилагательные. Остальные прилагательные должны образовываться из существительных(относительные и притяжательные) и программно(порядковые), но пользователь может добавлять любые.
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.

var Adjective = function(str) {
	this.construct();
	if(str) this.parse(str);
};

(function() {

	Adjective.prototype = new WordWithPadejs();
	Adjective.prototype.wordTypeName = 'Adjective';
	Adjective.prototype.wordType = 3; // 1 - существительное, 2 - глагол, 3 - прилагательное
	Adjective.prototype.padejs = undefined; // в английском это только множественное, в русском в разных падежах и для множественного.

	Adjective.prototype.construct = function() {
		WordWithPadejs.prototype.construct.call(this);
	};

	Adjective.prototype.apportionFlagToTreeRadioAndCheckboxes = function(str, map, onlyFirstChars, filtrFlags) {
		// Возвращает ту часть флагов, что отвечает за категорию в WordWithFlags.flags
		return Noun.prototype.apportionFlagToTreeRadioAndCheckboxes.call(this, str, map || Adjective.flags, onlyFirstChars, filtrFlags);
	};

	Adjective.flags = Adjective.prototype.flagsMap = cloneStruct(WordWithFlags.flags); // если качественное, то можно сделать из него наречие.
	Adjective.flags['checkboxes' || 0] = ['качественное', 'more/least', 'индивидуализирующее'].concat(Adjective.flags['checkboxes' || 0]); // TODO: раскомментировать.

	Adjective.stdVerbFlags = []; // TODO: Установить стандартный флаг для слов преобразованных из прилагательное в глагол.

	Adjective.prototype.getApportionedVerbFlags = function() {
		return Adjective.stdVerbFlags;
	};

})();

// Качественные прилагательные. Остальные прилагательные должны образовываться из существительных(относительные и притяжательные) и программно(порядковые), но пользователь может добавлять любые.
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.

var AdjectiveEn = function(str) {
	this.construct();
	if(str) this.parse(str);
};

(function() {

	AdjectiveEn.prototype = new Adjective();
	AdjectiveEn.prototype.wordLang = 0;

	AdjectiveEn.prototype.construct = function() {
		Adjective.prototype.construct.call(this);
	};

	AdjectiveEn.preHelpsForComparative = ['', 'more', 'most', 'adverb'];
	AdjectiveEn.comparativeStarts = ['least ', 'less ', '', 'more ', 'most ']; // как и в AdverbEn
	AdjectiveEn.prototype.padejsCount = AdjectiveEn.comparativeStarts.length;

	AdjectiveEn.prototype.getFormed = function(comparative) {
		if(!comparative) return this.str;
		if(comparative === 3) {
			return this.getFormedAdverb();
		}
		if(comparative > 0 && this.padejs && this.padejs[comparative]) {
			if(this.padejs[comparative]) {
				if(this.padejs[comparative] === 1) {
					return AdjectiveEn.preHelpsForComparative[comparative] + ' ' + this.str;
				} else if(this.padejs[comparative][0] === '*') {
					return kor + this.padejs[comparative].substr(1);
				}
				return this.padejs[comparative];
			}
		}
		comparative += 2;
		if(comparative < 2 || enLang.getCountSyllables(this.str) >= 3) {
			var end = this.str.substr(2);
			if(comparative < 2 || (end[1] !== 'y' && end !== 'er' && end !== 'ow' && end !== 'le')) {
				return AdjectiveEn.comparativeStarts[comparative] + this.str;
			}
		}
		var str = this.str;
		if(str.substr(-1) === 'e') str = str.substr(0, str.length - 1);
		if(comparative === 3) {
			return str + 'er';
		} else if(comparative === 4) {
			return str + 'est';
		}
	};

	AdjectiveEn.prototype.getFormedAdverb = function(comparative) {
		// Возвращает наречие, образованое из этого качественного(флаг) прилагательного.
		var endKor = this.str[this.str.length - 1];
		var preEndKor = this.str[this.str.length - 2];
		if(endKor === 'y') {
			if(preEndKor === 'l') {
				return this.str;
			} else {
				return this.str.substr(0, this.str.length - 1) + 'ily';
			}

		} else if(endKor === 'e' && preEndKor === 'l') {
			return this.str.substr(0, this.str.length - 1) + 'y';
		} else if(endKor === 'c' && preEndKor === 'i') {
			return this.str + 'ally';
		}
		return this.str + 'ly';
	};

	AdjectiveEn.prototype.toJSON = function() {
		var ret = Adjective.prototype.toJSON.call(this);
		if(ret[2]) {
			if(ret[2][1] === 'more ' + this.str) {
				ret[2][1] = 1;
			}
			if(ret[2][2] === 'most ' + this.str) {
				ret[2][2] = 1;
			}
		}
		return ret;
	};

	AdjectiveEn.flags = Adjective.flags;

	AdjectiveEn.adjectiveFlags = {
		'индивидуализирующее': 'перед ним ставится the',
	};

})();
// Качественные прилагательные. Остальные прилагательные должны образовываться из существительных(относительные и притяжательные) и программно(порядковые), но пользователь может добавлять любые.
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed() будет давать другой результат - bts-ы перестанут его находить, это очень плохо.

var AdjectiveRu = function(str) {
	this.kor = '';
	this.okonInd = 0;
	this.construct();
	if(str) this.parse(str);
};

(function() {

	AdjectiveRu.prototype = new Adjective();
	AdjectiveRu.prototype.wordLang = 1;

	AdjectiveRu.prototype.construct = function() {
		Adjective.prototype.construct.call(this);
	};

	// прилагательные можно склонять одновременно по двум параметрам (gender и падеж), и еще есть сравнительная степень.
	AdjectiveRu.preHelpsForGender = ['Он ', 'Она', 'Оно', 'Они'];
	AdjectiveRu.preHelpsForPadeji = [
		'',
		'           нет',
		'          дать',
		'        винить',
		'       крутить',
		'    говорить о',
	];

	AdjectiveRu.okons = [[ // это основное соответствие окончаний, но есть систематические искажения.
	//						нет					дать				винить				крутить				говорить о
		['ый', 'ий', 'ой'],	['ого', 'его'],		['ому', 'ему'],		['ого', 'его'],		['ым', 'им'],		['ом', 'ем']	// мужской
	], [
		['ая', 'яя'],		['ой', 'ей'],		['ой', 'ей'],		['ую', 'юю'],		['ой', 'ей'],		['ой', 'ей']	// женский
	], [
		['ое', 'ее'],		['ого', 'его'],		['ому', 'ему'],		['ое', 'ее'],		['ым', 'им'],		['ом', 'ем']	// средний
	], [
		['ые', 'ие'],		['ых', 'их'],		['ым', 'им'],		['ых', 'их'],		['ыми', 'ими'],		['ых', 'их']	// множественное
	]];
	AdjectiveRu.okonVerbs = [
		['еть'],
		['ел'],
		['еете'],
		['ело'],
		['еем'],
		['еет'],
		['ели'],
		['ела']
	];
	AdjectiveRu.okonParticiple = [
		['еющий'],
		['еющая'],
		['еющее'],
		['еющие'],
	];
	AdjectiveRu.okonsParticiple = [[ // это основное соответствие окончаний, но есть систематические искажения.
	//						нет					дать				винить				крутить				говорить о
		'еющий',			'еющего',			'еющему',			'еющего',			'еющим',			'еющем'	// мужской
	], [
		'еющая',			'еющей',			'еющей',			'еющую',			'еющей',			'еющей'	// женский
	], [
		'еющее',			'еющего',			'еющему',			'еющее',			'еющим',			'еющем'	// средний
	], [
		'еющие',			'еющих',			'еющим',			'еющих',			'еющими',			'еющих'	// множественное
	]];
	AdjectiveRu.okonAdverbs = ['о', 'е', 'о'];// это соответствие окончаний для Adverb(наречие) к AdjectiveRu.okons с аналогичным индексом.

	AdjectiveRu.preHelpsForComparative = ['', 'более', 'самый', 'наречие'];
	AdjectiveRu.prototype.padejsCount = AdjectiveRu.preHelpsForComparative.length; // это еще не значит, что нельзя склонять иначе.

	AdjectiveRu.comparativeStarts = [[['наименее ', 'наименее ', 'наименее ', 'наименее ']], [['менее ', 'менее ', 'менее ', 'менее ']], [['', '', '', '']], [['более ', 'более ', 'более ', 'более ']], [
	['самый ', 'самая ', 'самое ', 'самые '],
	['самого ', 'самой ', 'самого ', 'самых '],
	['самому ', 'самой ', 'самому ', 'самым '],
	['самого ', 'самую ', 'самое ', 'самых '],
	['самым ', 'самой ', 'самым ', 'самыми '],
	['самом ', 'самой ', 'самом ', 'самых '],
	]]; // повторение мать ее учения
	AdjectiveRu.comparativeStarts.forEach(function(gend) {
		if(gend.length === 1) {
			for(var i = 1; i < AdjectiveRu.preHelpsForPadeji.length; i++) {
				gend[i] = gend[0];
			}
		}
	});
	AdjectiveRu.prevVerbByGroup = ['становиться', 'становлюсь', 'становитесь', 'становилось', 'становимся',  'становятся',  'становится',  'становился',  'становились', 'становилась'];
	AdjectiveRu.superlativeOkons = [[ // Окончания для превосходной степени.
	//						нет					дать				винить				крутить				говорить о
		['ий'],				['его'],			['ему'],			['его'],			['им'],				['ем']	// мужской
	], [
		['ая'],				['ей'],				['ей'],				['ую'],				['ей'],				['ей']	// женский
	], [
		['ее'],				['его'],			['ему'],			['ее'],				['им'],				['ем']	// средний
	], [
		['ие'],				['их'],				['им'],				['их'],				['ими'],			['их']	// множественное
	]];

	AdjectiveRu.prototype.getFormed = function(comparative, gender, padej) {
		if(comparative === 3) {
			return this.getFormedAdverb();
		}
		if(!gender) {
			gender = 0;
			if(!padej && (comparative === undefined || comparative === 0)) return this.str || undefined;
		}
		if(padej > 5) {
			padej = padej - 6;
			if(gender === undefined) gender = 4;
		}
		if(!this.str || !this.kor) return undefined;
		if(!padej) padej = 0;
		if(!gender) gender = 0;
		if(!comparative) comparative = 0;
		if(padej > 5 || padej < 0) padej = 0;
		if(gender > 3 || gender < 0) gender = 0;
		comparative += 2; // Да, внутриклассовая нумировка не совпадает с внешней.
		if(comparative > 4 || comparative < 0) comparative = 0;
		var okonInd = this.okonInd;
		var kor;
		var endKor;
		var okon;
		if(comparative > 2 && this.padejs && this.padejs[comparative - 2]) {
			var ret = this.padejs[comparative - 2];
			if(ret) {
				if(ret[0] === '*') {
					ret = this.kor + this.padejs[comparative - 2].substr(1);
				}
				if((comparative === 3 && !padej) || (!gender && !padej)) return ret; // ведь оно не склоняется
				var supOkon = ret.substr(-5);
				if(supOkon.substr(1) === 'йший' && (supOkon[0] === 'е' || supOkon[0] === 'а')) {
					kor = ret.substr(0, ret.length - 6);
					endKor = ret.substr(ret.length - 5, 1);
					okon = supOkon.substr(0, 3);
				} else if(comparative === 4	) {
					console.log(comparative, gender, padej);
					return AdjectiveRu.comparativeStarts[comparative][padej][gender] + this.getFormed(0, gender, padej);
				}
			}
		}
		endKor = this.kor.substr(-1);
		if((gender < 3 || (!padej && comparative === 3)) && comparative < 4 && okonInd === AdjectiveRu.okons[0][0].indexOf('ий') && endKor === 'к') {
			if((padej || gender) && gender < 3) okonInd = 0;
			okon = AdjectiveRu.okons[gender][padej][okonInd];
			return AdjectiveRu.comparativeStarts[comparative][padej][gender] + this.kor + okon;
		}
		if(comparative === 1 || comparative === 3 || comparative === 0) {
			if(comparative == 3 && !padej) {
				var startKor = this.kor.substr(0, this.kor.length - 1);
				okon = 'ее';
				if(('гкхдт').indexOf(endKor) !== -1) {
					okon = 'е';
					if(endKor == 'к') endKor = 'ч';
					if(endKor == 'г') endKor = 'ж';
					if(endKor == 'х') endKor = 'ш';
					if(endKor == 'д') endKor = 'ж';
					if(endKor == 'т') endKor = 'щ';
					if(endKor == 'т' && startKor.substr(-1) == 'с') { // TODO: че за недостижимый разврат?
						startKor = startKor.substr(0, startKor.length - 1);
					}
				}
				return startKor + endKor + okon;
			}
			return AdjectiveRu.comparativeStarts[comparative][padej][gender] + this.getFormed(0, gender, padej);
		}
		if(comparative === 4) {
			if(!kor) {
				kor = this.kor;
				endKor = kor.substr(-1);
				okon = 'ейш';
				if(('гкх').indexOf(endKor) !== -1) {
					okon = 'айш';
					if(endKor === 'к') endKor = 'ч';
					if(endKor === 'г') endKor = 'ж';
					if(endKor === 'х') endKor = 'ш';
				}
			}
			okon += AdjectiveRu.superlativeOkons[gender][padej];
			return kor + okon;
		}
		if(comparative !== 2) warning('Какого хрена, разработчик думал что comparative !== 2 уже отсеяны ' + comparative + ' ' + gender + ' ' + padej);
		if(okonInd !== undefined) {
			if(this.kor) {
				var okons = AdjectiveRu.okons;
				okon = '';
				if(okons[gender][padej][okonInd]) {
					okon = okons[gender][padej][okonInd];
				} else {
					okon = okons[gender][padej][0];
				}
				if(okonInd === 1 && gender !== 3) {
					if(('нш').indexOf(this.kor.substr(-1)) === -1 && ('хчщжк').indexOf(this.kor.substr(-1)) === -1) {
						okon = okons[gender][padej][0];
					}
				}
				if(okon[0] === 'ы' && ('хчшж').indexOf(this.kor.substr(-1)) !== -1) {
					okon = 'и' + okon.substr(1);
				}
				return this.kor + okon;
			}
		}
		return this.str;
	};

	AdjectiveRu.prototype.getFormedParticiple = function() {
		var str = this.kor + AdjectiveRu.okonParticiple[0];
		var padejs = [];
		for(var i = 0; i < AdjectiveRu.okonParticiple.length; i++) {
			padejs.push(this.kor + AdjectiveRu.okonParticiple[i]);
		}
		return [str, [], padejs];
	};

	AdjectiveRu.prototype.getFormedVerb = function(comparative, pron, time) {
		if(!pron) pron = 0;
		if(!time) time = 0;
		var group = VerbRu.formRuVerbByPronAndTime[pron][time] || 0;
		if(comparative === 0 && this.okonInd !== 1) {
			return this.kor + AdjectiveRu.okonVerbs[0];
		} else {
			return AdjectiveRu.prevVerbByGroup[0] + ' ' + this.getFormed(comparative, pronsToGender[pron], 4);
		}
	};

	AdjectiveRu.prototype.getFormedAdverb = function(comparative) {
		var okonInd = this.okonInd || 0;
		var pre = '';
		var okon = AdjectiveRu.okonAdverbs[okonInd];
		var endKor;
		var preEndKor;
		if(this.kor) {
			endKor = this.kor[this.kor.length - 1];
			if(this.kor.length > 1) {
				preEndKor = this.kor[this.kor.length - 2];
			}
		}

		if(okon === 'е') {
			if(endKor === 'г') {
				okon = 'о';
			} else if(endKor === 'к' && preEndKor === 'с') {
				okon = 'и';
			} else if(endKor === 'ш' && preEndKor === 'й') {
				okon = 'е';
			} else if(endKor === 'ш' || endKor === 'х' || endKor === 'к') {
				if(preEndKor !== 'ч') {
					okon = 'о';
				}
			}
		}
		return pre + this.kor + okon;
	};

	AdjectiveRu.prototype.parse = function(obj) {
		obj = Adjective.prototype.parse.call(this, obj);
		this.kor = undefined;
		this.okonInd = undefined;
		for(var i = 0; i < AdjectiveRu.okons[0][0].length; i++) {
			var okon = AdjectiveRu.okons[0][0][i];
			if(this.str.length > okon.length && this.str.substr(-okon.length) === okon) {
				this.kor = this.str.substr(0, this.str.length - okon.length);
				this.okonInd = i;
			}
		}
		return obj;
	};

	AdjectiveRu.flags = Adjective.flags;

})();

// Наречия.
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.

var Adverb = function(str) {
	this.construct();
	if(str) this.parse(str);
};

(function() {

	Adverb.prototype = new WordWithPadejs();
	Adverb.prototype.wordTypeName = 'Adverb';
	Adverb.prototype.suitableVerbs = []; // всегда переопределять объект!
	Adverb.prototype.wordType = 4; // 1 - существительное, 2 - глагол, 3 - прилагательное, 4 наречие

	Adverb.prototype.construct = function() {
		WordWithPadejs.prototype.construct.call(this);
	};

	Adverb.prototype.parse = function(obj) {
		obj = WordWithPadejs.prototype.parse.call(this, obj);
		this.suitableVerbs = obj[3];
		return obj;
	};

	Adverb.prototype.toJSON = function() {
		if(!this.str) return;
		var ret = WordWithPadejs.prototype.toJSON.call(this);
		if(this.suitableVerbs) ret[3] = this.suitableVerbs;
		return ret;
	};

	Adverb.prototype.apportionFlagToTreeRadioAndCheckboxes = function(str, map, onlyFirstChars, filtrFlags) {
		// Возвращает ту часть флагов, что отвечает за категорию в WordWithFlags.flags
		return WordWithPadejs.prototype.apportionFlagToTreeRadioAndCheckboxes.call(this, str, map || Adverb.flags, onlyFirstChars, filtrFlags);
	};

	Adverb.flags = Adverb.prototype.flagsMap = {
		// видно, обязательно, примерно, соответственно, зря, следовательно, вернее, тоже, вообще, вплоть, словом, наряду, заодно, отчасти, совершенно, особенно, вряд, широко, больно, пешком, категорически, напрямую, приблизительно, бывало, единственно, сугубо, справедливо, вплотную, пополам, желательно, круто, равно, применительно, туго, глухо, дополнительно, яростно, щедро, бегом, насколько,
		'checkboxes': [
			'filter',
			'более-менее',
		],
		'где?': {
			'title': 'Это быдло не там а * (рядом, дома, высоко, вокруг, впереди, напротив, близко, сзади, внизу, внутри, справа, слева, снизу, кругом, недалеко, неподалеку, наверху, позади, вдали, низко, поблизости, )',
			'...от этого': '(далеко, )',
			'...этого': '(около, вблизи, )',
		},
		'куда?': 'Он двигался не туда а * (назад, домой, вперед, вниз, обратно, вверх, вслед, глубоко, навстречу, замуж, мимо, некуда, прочь, наружу, наверх, направо, налево, против, внутрь, ниже, вправо, влево, )',
		'откуда?': 'Он двигался не отсюда а * (сверху, справа, слева, снизу, изнутри, сбоку, снаружи, издали, издалека, )',
		'когда?': {
			'title': '(вдруг, сразу, однажды, одновременно, мгновенно, )',
			'только прошедшее': '(давно, недавно, вчера, давным-давно, )',
			'преим. прошедшее': '(прежде, сначала, ранее, заранее, сперва, поначалу, вначале, изначально, предварительно, накануне, незадолго, задолго, первоначально, )',
			'настоящее': '(сегодня, наконец, ныне, нынче, )',
			'преим. будующее': '(затем, далее, вскоре, немедленно, впоследствии, тотчас, следом, наутро, после, скоро, отныне, )',
			'только будующее': '(завтра, вот-вот, )',
			'продолжительность': '(пока, постоянно, долго, вечно, недолго, временно, подолгу, )',
			'периодичность': '(снова, часто, иногда, вновь, постоянно, редко, нередко, регулярно, зачастую, ежедневно, ежегодно, опять-таки, подчас, периодически, непрерывно, )',
			'указание на характер времени': '(впервые, рано, поздно, внезапно, вовремя, опять, )',
		},
		'на сколько времени?': '(надолго, ненадолго)',
		'насколько делает?': {
			'...делать': '(почти, практически, вовсе, едва, еле, нормально, вовсю, полностью, )',
			'...быстро, ...сухо': '(очень, более, почти, совсем, слишком, менее, довольно, наиболее, весьма, сильно, явно, настолько, абсолютно, особо, крайне, чрезвычайно, исключительно, по-настоящему, максимально, сравнительно, относительно, предельно, чересчур, необычайно, действительно, правда, вполне, )',
			'...более': '(слегка, почти, чуть, едва, слегка, значительно, гораздо, отнюдь, существенно, заметно, чуть-чуть, намного, максимально, частично, наполовину, изрядно, )',
			'...чем он делать': 0,
		},
		'колличество': {
			'сколько?': '(больше, достаточно, немного, мало, несколько, много, немножко, недостаточно, бесконечно, )',
			'всколькером': '(вдвоем, втроем, )', // Вот это пока ни как не используется
			'в сколько раз': 'вдвое', // TODO: нужно запретить выбирать, и генерировать "three times less" и т.д. в PhNumeral.
			'колличество раз': '(дважды, неоднократно, трижды, многократно, )',
			'в какой раз': '(во-первых, во-вторых, в-третьих, )', // Вот это пока ни как не используется
		},
		'вероятность': 'Это произойдет завтра или, *, сегодня. (возможно, точно, видимо, невозможно, наверное, пожалуй, вероятно, наверно, похоже, наверняка, верно, безусловно, непременно, по-видимому, несомненно, преимущественно, изредка, неизбежно, однозначно, определенно, )',
		'делает 2 дела...': '(разом, одинаково, совместно, подряд, последовательно, )',
		'они делают дело...': 'если только множество могут так делать (одинаково, совместно, дружно, )',
		'почему?': 'Он сделал это не из за того, а * (случайно, специально, невольно, охотно, принципиально, заново, попросту, недаром, традиционно, нарочно, добровольно, даром, )',
		'как выглядеть?': {
			'title': 'Большинство действий можно совершать выглядя так (замечательно, неважно, необыкновенно, )',
			'материальное': {
				'одушевленное': {
					'title': '(жадно, недовольно, устало, робко, привычно, напряженно, надежно, неуверенно, тревожно, )',
					'человек': '(торжественно, испуганно, удивленно, печально, сердито, гордо, растерянно, зло, мрачно, скромно, нехорошо, безнадежно, бодро, счастливо, страстно, )',
				},
			},
			'абстрактное':0,
			'группа':0,
		},
		'каково делать?': 'Большинство действий можно совершать так (трудно, легко, страшно, просто, странно, тяжело, приятно, отлично, радостно, удивительно, неплохо, холодно, здорово, свободно, сложно, ужасно, удобно, нормально, грустно, тесно, скучно, смешно, неудобно, мучительно, неприятно, выгодно, любопытно, лениво, нелегко, противно, нетрудно, жутко, )',
		'в каком смысле?': {
			'title': '* молча бежать (фактически, непосредственно, по-настоящему, внешне, впрямь, поистине, вправду, условно, теоретически, реально, )',
			'checkboxes': [
				'только делать', // значит нельзя использовать с другими глаголами, только с глаголом "делать" - "do".
			],
			'только человек': '(формально, экономически, исторически, )',
		},
		'каким образом делает?': {
			'title': 'Только если предыдущие категории не подошли. (легко, также, вместе, действительно, лично, буквально, весело, радостно, отдельно, независимо, незаметно, задумчиво, по-разному, ладно, смело, терпеливо, всячески, )',

			// TODO: нужно заменить структурой из Verb.js
			'активное действие': {
				// делать смотреть работать следовать начинать становиться использовать пытаться заниматься продолжать искать составлять действовать служить выступать помогать пользоваться создавать участвовать платить слушать
				'title': '(хорошо, быстро, плохо, медленно, спокойно, неожиданно, постепенно, осторожно, окончательно, активно, решительно, успешно, тщательно, отлично, срочно, самостоятельно, непонятно, аккуратно, уверенно, смешно, стремительно, бесплатно, торопливо, напрасно, потихоньку, удачно, упорно, автоматически, старательно, поспешно, эффективно, благополучно, сознательно, отчаянно, зло, запросто, неправильно, неторопливо, моментально, вяло, нервно, равнодушно, понемногу, послушно, умело, заведомо, покорно, упрямо, )',
				'кинетическое': { // играть принимать пить глядеть держать показывать ставить хватать звонить бить встречаться рассматривать мешать снимать
					'title': '(быстро, резко, слабо, ловко, неловко, тихонько, физически, небрежно, живо, шумно, машинально, тайно, )',
					'перемещаться': { // возвращаться
						'title': '(параллельно, насквозь, плавно, )',
						'горизонтальное': {
							'title': '(прямо, четко, )', // идти ходить вести выходить входить приходить уходить проходить проводить бежать
						},
						'вертикальное': {
							'title': '(круто, )',
							'в небо': 0,
							'под воду': '(мелко, )',
						},
					},
					'перемещать': { // давать брать передать возвращать
						'title': '(параллельно, бережно, плавно, )',
						'горизонтальное': '(прямо, ровно, ласково, нежно, )', // нести носить приводить
						'вертикальное': {
							'в небо': 0,
							'под воду': 0,
						},
					},
					'удержание': '(крепко, грубо, нежно, жестко, бережно, )', // схватить,
					'разрушение': 0, // ломать,
					'создание': '(красиво, )', // строить, собирать,
				},
				'мимика': '(откровенно, )', // скалиться,
				'вербальное': {
					'title': '(наоборот, правильно, понятно, очевидно, прекрасно, честно, четко, подробно, коротко, целиком, по-русски, официально, откровенно, отчетливо, красиво, скучно, конкретно, )',
					'разговор': '(тихо, ясно, громко, строго, серьезно, всерьез, вслух, слышно, тепло, ласково, открыто, обидно, вежливо, торжественно, тихонько, негромко, испуганно, толком, печально, нежно, сердито, настойчиво, публично, шепотом, по-английски, наизусть, убедительно, достойно, наедине, тревожно, )', // говорить называть отвечать рассказывать просить спрашивать называть требовать кричать предлагать вызывать звать обещать напоминать обращаться утверждать
					'письменное': '(чисто, ровно, )', // писать
				},
				'умственное': {
					'title': '(несмотря, интересно, правильно, внимательно, прекрасно, искренне, скучно, мысленно, удивленно, глупо, смутно, тревожно, )', // думать считать вспоминать учиться судить решать определять
					'созидание': '(красиво, конкретно, )', // придумать, посчитать, представлять,
					'считывание': '(пристально, тайно, )', // читать, наблюдать,
				},
				'навязаное окружением': 0,
				'излучать нечто': 0,
			},
			'пассивное действие': { // быть мочь хотеть иметь жить любить ждать оставаться происходить чувствовать спать позволять уметь начинаться зависеть слышать
				'title': '(естественно, по-прежнему, молча, неизменно, неподвижно, )',
				'кинетическое': { // сидеть стоять лежать получать касаться возникать расти плакать
					'title': '(смешно, )',
					'перемещаться': 0, // ехать
					'перемещать': 0,
					'удержание': 0,
					'разрушение': 0,
					'создание': 0,
				},
				'мимика': '(откровенно, )', // смеяться, улыбаться,
				'вербальное': '(важно, честно, жалко, тихонько, )', // молчать
				'умственное': '(внутренне, )', // знать видеть помнить бояться верить надеяться понимать желать ожидать
				'навязаное окружением': 0, // являться стоить казаться находиться бывать существовать нравиться оказываться получаться выглядеть состоять называться значить иметься получаться подходить считаться принадлежать занимать обладать отличаться соответствовать означать приходиться
				'излучать нечто': 0,
			},

		},
		'там все было...': '(пусто, сладко, дорого, мирно, сухо, невероятно, объективно, прочно, густо, сплошь, остро, жарко, темно, горячо, горько, твердо, мягко, здорово, ярко, плотно, полно, )',
	};
	Adverb.prototype.subFlagsRanges(Adverb.flags, '', 0, Adverb);

	Adverb.verbFlagToFlag = function(flag) {
		// приводит значение флага по структуре Verb.verbFlag к соответствующему значению флага по структуре Adverb.flags .
		flag = cloneStruct(flag);
		flag[0] = '11100' + flag[0];
		return flag;
	};

})();

// Наречия.
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed(0) будет давать другой результат - bts-ы перестанут его находить, это очень плохо.

var AdverbEn = function(str) {
	this.construct();
	if(str) this.parse(str);
};

(function(){

	AdverbEn.prototype = new Adverb();
	AdverbEn.prototype.wordLang = 0;

	AdverbEn.prototype.construct = function() {
		Adverb.prototype.construct.call(this);
	};

	AdverbEn.preHelpsForComparative = [''];
	AdverbEn.prototype.padejsCount = AdverbEn.preHelpsForComparative.length;

	AdverbEn.prototype.getFormed = function() {
		return this.str;
	};

	AdverbEn.flags = Adverb.flags;

})();
// Качественные прилагательные. Остальные прилагательные должны образовываться из существительных(относительные и притяжательные) и программно(порядковые), но пользователь может добавлять любые.
// Не забывайте о негласном правиле: слово можно изменять, но если getFormed() будет давать другой результат - bts-ы перестанут его находить, это очень плохо.

var AdverbRu = function(str) {
	this.okonInd = 0;
	this.construct();
	if(str) this.parse(str);
};

(function() {

	AdverbRu.prototype = new Adverb();
	AdverbRu.prototype.wordLang = 1;

	AdverbRu.preHelpsForComparative = [''];
	AdverbRu.prototype.padejsCount = AdverbRu.preHelpsForComparative.length; // это еще не значит, что нельзя склонять иначе.

	AdverbRu.prototype.getFormed = function() {
		return this.str;
	};

	AdverbRu.flags = Adverb.flags;

})();

// Коллекции реализованы без прототипов, но с наследованием.

var devideWordsByLangs = function(word, wordsByLang) {
	// Разделяет все английские слова в первый массив, русские во второй.
	if(!word) return;
	if(!wordsByLang) wordsByLang = [[],[]];
	if(isArray(word)) {
		for(var i = 0; i < word.length; i++) {
			devideWordsByLangs(word[i], wordsByLang);
		}
	} else {
		wordsByLang[word.wordLang].push(word);
	}
	return wordsByLang;
};

var constructWordsCollection = function() {
	var words = {};

	addEventsMixin(words);

	words.btsByLang = [new BTS(), new BTS()]; // На каждую коллекцию по паре деревьев поиска. 0 - английский, 1 - русский.
	words.btsCollections = [words.btsByLang]; // список коллекций(пар деревьев) в которые должны помещаться слова связаного типа.
	words.validTypes = [Word]; // Типы объектов, которые можно добавлять в коллекцию.

	words.clear = function() {
		words.btsByLang.forEach(function(bts) {bts.clear();});
	};

	words.searchAndCombineWord = function(word) {
		// Если в коллекции нет слова с идентичным getFormed() - то возвращается это слово.
		// Если аналогичное слово найдено - возвращается найденое.
		// Найденое слово просим внести в себя все возможные дополнения из word.
		var searchedWord = this.searchByStr(word.getFormed().toLowerCase());
		if(searchedWord) {
			searchedWord.combine(word);
			return searchedWord;
		}
		return word;
	};

	words.addWordsToBTSs = function(word, btss) {
		// Добавляет в переданные пары коллекций(деревъя) переданные слова.
		// word может быть массивом слов с одинаковым смысловым значением.
		// Если пытаемся добавить слова, аналоги которых уже имеются в коллекции - старые удаляются.
		word = word.map(function(w) {
			return words.searchAndCombineWord(w);
		});
		var wordsByLang = devideWordsByLangs(word);
		var formedWordsByLang = wordsByLang.map(function(words) {
			return words.map(function(word) {
				return word.getFormed().toLowerCase();
			});
		});
		for(var b = 0; b < btss.length; b++) {
			for(var i = 0; i < 2; i++) {
				for(var n = 0; n < wordsByLang[i].length; n++) {
					var str = formedWordsByLang[i][n];
					var inBts = btss[b][i].get(str);
					var found = false;
					var v;
					if(inBts) {
						// Если в bts уже присутствует схожее слово, оно заменяется на новое.
						for(v = 0; v < inBts.os.length; v++) {
							if(inBts.os[v] && inBts.os[v].getFormed().toLowerCase() == str) {
								inBts.os[v] = wordsByLang[i][n];
								found = true;
							}
						}
					}
					if(!found) {
						btss[b][i].set(str, wordsByLang[i][n]);
					}
					for(var p = 0; p < wordsByLang[1 - i].length; p++) {
						found = false;
						if(inBts) {
							// Если уже присутствует схожее слово, оно заменяется на новое.
							var str2 = formedWordsByLang[1 - i][p];
							for(v = inBts.os.length; v--;) {
								if(inBts.os[v] && inBts.os[v].getFormed().toLowerCase() == str2) {
									inBts.os[v] = wordsByLang[1 - i][p];
									found = true;
								}
							}
						}
						if(!found) {
							btss[b][i].set(str, wordsByLang[1 - i][p]);
						}
					}
				}
			}
		}
	};

	words.removeWordsLinkFromBTSs = function(word, btss) {
		// Разрывает связь между словами в bts-ах
		// Не забывайте, что слова в bts ищутся только по значению.
		// Нужно учитывать, что связь может быть разорвана и в случае совпадения объектов.
		var wordsByLang = devideWordsByLangs(word);
		var formedWordsByLang = wordsByLang.map(function(words) {
			return words.map(function(word) {
				return word.getFormed().toLowerCase();
			});
		});
		for(var b = 0; b < btss.length; b++) {
			for(var i = 0; i < 2; i++) {
				for(var n = 0; n < wordsByLang[i].length; n++) {
					var str = formedWordsByLang[i][n];
					var inBts = btss[b][i].get(str);
					if(inBts) {
						for(var v = 0; v < inBts.os.length; v++) {
							if(inBts.os[v] && (wordsByLang[1 - i].indexOf(inBts.os[v]) != -1 || formedWordsByLang[1 - i].indexOf(inBts.os[v].getFormed().toLowerCase()) != -1)) {
								inBts.os[v] = undefined;
							}
						}
					}
				}
			}
		}
	};

	words.removeOtherWordsLinks = function(word, linkWords) {
		// Удаляет связи со всеми словами кроме переданых в linkWords.
		var str = word.getFormed().toLowerCase();
		var lang = word.wordLang;
		for(var b = 0; b < words.btsCollections.length; b++) {
			var nod = words.btsCollections[b][lang].get(str);
			for(var n = nod.os.length; n--;) {
				var lWord = nod.os[n];
				if(lWord && linkWords.indexOf(lWord) == -1 && lWord !== word) {
					words.btsCollections[b][lWord.wordLang].rm(lWord.getFormed().toLowerCase(), word);
					/*var lNod = words.btsCollections[b][lWord.wordLang].get(lWord.getFormed());
					if(lNod) {
						removeFromArrayByValue(lNod.os, word);
					}*/
					nod.os[n] = undefined;
				}
			}
		}
	};

	words.addWords = function(group, strong, delOther) {
		// Добавляется или обновляется набор связанных слов.
		// strong означает, что вместо combine производится полная замена данных слова.
		// delOther означает, что нужно удалить связи английских слов вне группы.
		var i;
		for(i = 0; i < group.length; i++) {
			if(group[i]) {
				for(var n = 0; n < words.validTypes.length; n++) {
					if(group[i] instanceof words.validTypes[n]) {
						break;
					}
					if(n === words.validTypes.length - 1) {
						warning('Попытка добавить в коллекцию слово невалидного типа.');
						return;
					}
				}
				var found = words.searchByStr(group[i].getFormed().toLowerCase()); // Если в коллекции уже есть похожее слово, то просто добавим к нему новые данные
				if(found) {
					if(strong) {
						found.setOnlyValues(group[i].toJSON());
					} else {
						found.combine(group[i]);
					}
					group[i] = found;
				}
			}
		}
		words.addWordsToBTSs(group, words.btsCollections);
		words.callChangeForParents();
		if(words.changesLog) { // записываем в лог входные слова.
			var json = [[],[]];
			for(i = 0; i < group.length; i++) {
				if(group[i].wordLang) {
					json[1].push(group[i].toJSON());
				} else {
					json[0].push(group[i].toJSON());
				}
			}
			words.changesLog.push(['add', json]);
		}
		if(delOther) {
			var wordsByLang = devideWordsByLangs(group);
			for(i = 0; i < wordsByLang[0].length; i++) {
				words.removeOtherWordsLinks(wordsByLang[0][i], wordsByLang[1]);
			}
		}
		return true;
	};

	words.searchByStr = function(str, lang) {
		str = str.toLowerCase();
		var langsStart = 0;
		var langsEnd = 2;
		if(lang !== undefined) {
			if(lang) {
				langsStart = 1;
			} else {
				langsEnd = 1;
			}
		}
		for(var lng = langsStart; lng < langsEnd; lng++) {
			var nod = words.btsByLang[lng].get(str);
			if(nod) {
				for(var i = 0; i < nod.os.length; i++) {
					if(nod.os[i] && nod.os[i].getFormed().toLowerCase() == str) {
						return nod.os[i];
					}
				}
			}
		}
	};

	words.searchNodes = function(word, remove) {
		// Возвращает ноды, в которых находится данное слово.
		var ret = [];
		var lng = word.wordLang;
		var str = word.getFormed().toLowerCase();
		for(var i = 0; i < words.btsCollections.length; i++) {
			var nod = words.btsCollections[i][lng].get(str);
			if(nod) {
				for(var n = 0; n < nod.os.length; n++) {
					if(nod.os[i] != word && nod.os[i]) {
						var trNod = words.btsCollections[i][1 - lng].get(nod.os[i].getFormed().toLowerCase());
						if(trNod) {
							for(var p = 0; p < trNod.os.length; p++) {
								if(trNod.os[p] == word) {
									if(remove) {
										trNod.os[p] = undefined;
									} else {
										ret.push(trNod);
										break;
									}
								}
							}
						}
					} else if(nod.os[i] == word) {
						if(remove) {
							words.btsCollections[i][lng].rm(nod);
						} else {
							ret.push(nod);
						}
					}
				}
			}
		}
		return ret;
	};

	words.removeWord = function(word, lang) {
		// Вычищает из коллекций(деревьев) все легко находящиеся ссылки на word
		var str;
		if(isString(word)) {
			str = word.toLowerCase();
			word = words.searchByStr(str);
		} else {
			str = word.getFormed().toLowerCase();
			lang = word.wordLang;
		}
		for(var b = 0; b < words.btsCollections.length; b++) {
			var nod = words.btsCollections[b][lang].get(str);
			if(nod) {
				for(var i = 0; i < nod.os.length; i++) {
					if(nod.os[i] && nod.os[i].wordLang != lang) {
						words.btsCollections[b][1 - lang].rm(nod.os[i].getFormed().toLowerCase(), word);
						/*var concNod = words.btsCollections[b][1 - lang].get(nod.os[i].getFormed());
						if(concNod) {
							removeFromArrayByValue(concNod, word);
						}*/
					}
				}
				words.btsCollections[b][lang].rm(str);
				words.callChangeForParents();
			}
		}
		if(words.changesLog) {
			words.changesLog.push(['rm', word.getFormed()]); // записываем в лог входящее слово.
		}
	};

	words.autocomplete = function(str) {
		// Возвращает слова, подходящие по началу строки.
		str = str.toLowerCase();
		var ret = [];
		for(var lng = 0; lng < 2; lng++) {
			var nods = words.btsByLang[lng].searchOrAutocomplete(str);
			for(var n = 0; n < nods.length; n++) {
				var nod = nods[n];
				if(nod) {
					for(var w = 0; w < nod.os.length; w++) {
						if(nod.os[w]) {
							var formed = nod.os[w].getFormed().toLowerCase();
							if(formed && str == formed.substr(0, str.length)) {
								ret.push(nod.os[w]);
							}
						}
					}
				}
			}
		}
		return ret;
	};

	words.getRndWordsPairs = function(enF, ruF, bts, count) {
		// возвращает пары[en, ru] слов из bts, при которых enF и ruF возвращают истину.
		var ret = [];
		if(!bts) bts = words.btsByLang[0];
		var langF = [enF, ruF];
		var vals = shuffleArray(bts.vals.concat()); // TODO: Это не самый плохой рандомизатор, но для ускорения лучше итеративно выбирать случайный элемент и splice его из массива.
		for(var i = 0; i < vals.length; i++) {
			var nod = bts.get(vals[i]);
			if(nod) {
				var wordsByLangs = devideWordsByLangs(nod.os);
				if(wordsByLangs[0].length === 0 || wordsByLangs[1].length === 0) {
					break;
				} else {
					var pair = [];
					for(var l = 0; l < 2; l++) {
						if(wordsByLangs[l].length > 1) wordsByLangs[l] = shuffleArray(wordsByLangs[l]);
						for(var n = 0; n < wordsByLangs[l].length; n++) {
							if(!langF[l] || (isArray(langF[l]) && langF[l].indexOf(wordsByLangs[l][n]) === -1) || (isFunction(langF[l]) && langF[l](wordsByLangs[l][n]))) {
								pair[l] = wordsByLangs[l][n];
								break;
							}
						}
					}
					if(pair[0] && pair[1]) {
						ret.push(pair);
						if(--count < 1) return ret;
					}
				}
			}
		}
		if(ret.length) return ret;
	};
	words.getRndWordsPair = function(enF, ruF, bts) {
		// возвращает пару[en, ru] слов из bts, при которых enF и ruF возвращают истину.
		var ret = wordsWithFlags.getRndWordsPairs(enF, ruF, bts, 1);
		if(ret) return ret[0];
	};

	words.printCollection = function() {
		return words.btsByLang[0].getXml() + words.btsByLang[1].getXml();
	};

	words.callChangeForParents = function(th) {
		if(!th) th = words;
		th.oneTriggerInNextTick('change');
		if(th.parent) th.parent.callChangeForParents(th.parent);
	};

	words.getWordsArray = function() {
		var arr = [];
		var bts = words.btsByLang[0];
		var node = bts.first();
		while(node) {
			var wds = devideWordsByLangs(node.os);
			if(wds[0][0]) {
				var ruWords = [];
				for(var i = 0; i < wds[1].length; i++) {
					ruWords.push(wds[1][i].toJSON());
				}
				var obj = [wds[0][0].toJSON(), ruWords];
				arr.push(obj);
			}
			node = bts.next(node);
		}
		return arr;
	};

	return words;
};

var words = constructWordsCollection(); // Основная коллекция слов, может использоваться в любых частях программы.

var baseWords = constructWordsCollection(); // храним слова, которые нельзя изменять, удалять, добавлять в другие коллекции. Большинство описаны в контенте учебника.
var constructWordsWithFlagsCollection = function(words) {
	var wordsWithFlags = constructWordsCollection();
	wordsWithFlags.valideTypes = [WordWithFlags];
	wordsWithFlags.treeFlagsBts = new BTS();
	wordsWithFlags.wordsByCheckboxes = {}; // Тут храним для каждого флага в flags коллекцию слов, имеющих этот флаг.

	var oldClear = wordsWithFlags.clear;
	wordsWithFlags.clear = function() {
		oldClear();
		wordsWithFlags.treeFlagsBts.clear();
		wordsWithFlags.wordsByCheckboxes = {}; // Тут храним для каждого флага в flags коллекцию слов, имеющих этот флаг.
	};

	wordsWithFlags.changeWordFlags = function(word) {
		// После изменения флагов слова - необходимо вызвать этот метод для добавления/удаления в ноды дерева по флагам.
		// Метод должен быть достаточно умным и делать все необходимое.
		// Изначально планировалось не давать словам доступ к коллекциям, а про флагаги не подумал. По этому такое скверное решение.
		var oldTreeFlags = word.lastChangedTreeFlags; // Без спросу храним в словах собственный описыавающий состояние treeFlags на момент последнего вызова данного метода.
		var delTreeFlags; // будем тут хранить флаги, к которым это слово потеряло привязку, и это нужно отразить в BTS.
		if(oldTreeFlags !== undefined) {
			delTreeFlags = oldTreeFlags.concat();
		}
		var oldCheckedCheckboxes = word.lastChangedCheckedCheckboxes;
		var delCheckedCheckboxes;
		if(oldCheckedCheckboxes !== undefined) {
			delCheckedCheckboxes = oldCheckedCheckboxes.concat();
		}
		var newTreeFlags = [];
		var newCheckedCheckboxes = [];
		if(word.getApportionedFlags) {
			var apportionedFlags = word.getApportionedFlags();
			for(var f = 0; f < apportionedFlags.length; f++) {
				var treeFlag = apportionedFlags[f][0];
				if(newTreeFlags.indexOf(treeFlag) === -1) {
					if(delTreeFlags !== undefined) {
						var ind = delTreeFlags.indexOf(treeFlag);
						if(ind !== -1) {
							delTreeFlags[ind] = undefined;
						}
					}
					newTreeFlags.push(treeFlag);
					if(oldTreeFlags === undefined || oldTreeFlags.indexOf(treeFlag) !== -1) { // если в коллекции по radioTree отсутствовало это слово - добавляем его.
						wordsWithFlags.treeFlagsBts.set(treeFlag, word);
					}
				}
				var checkedCheckboxes = apportionedFlags[f][1];
				for(var i = 0; i < checkedCheckboxes.length; i++) {
					if(!oldCheckedCheckboxes || oldCheckedCheckboxes.indexOf(checkedCheckboxes[i]) === -1) { // если слово не было ранее установлено в коллекцию по данному checkbox-у - добавляем его.
						if(!wordsWithFlags.wordsByCheckboxes[checkedCheckboxes[i]]) wordsWithFlags.wordsByCheckboxes[checkedCheckboxes[i]] = [];
						if(wordsWithFlags.wordsByCheckboxes[checkedCheckboxes[i]].indexOf(word) === -1) wordsWithFlags.wordsByCheckboxes[checkedCheckboxes[i]].push(word);
					}
					if(delCheckedCheckboxes) removeFromArrayByValue(delCheckedCheckboxes, checkedCheckboxes[i]);
				}

			}
			word.lastChangedTreeFlags = newTreeFlags;
			if(delTreeFlags !== undefined) { // Удаляем слово из BTS по флагам, ктороые более не установлены.
				delTreeFlags.forEach(function(treeFlag) {
					wordsWithFlags.treeFlagsBts.rm(treeFlag, word);
				});
			}
			word.oldCheckedCheckboxes = newCheckedCheckboxes;
			if(delCheckedCheckboxes !== undefined) { // Удаляем слово из массивов по флагам, ктороые более не установлены.
				delCheckedCheckboxes.forEach(function(checkbox) {
					removeFromArrayByValue(wordsWithFlags.wordsByCheckboxes[checkbox], word);
				});
			}
		}
	};

	var oldAddWords = wordsWithFlags.addWords;
	wordsWithFlags.addWords = function(word, strong, delOther) {
		oldAddWords(word, strong, delOther);
	};

	var oldAddWordsToBTSs = wordsWithFlags.addWordsToBTSs;
	wordsWithFlags.addWordsToBTSs = function(words, btss) {
		oldAddWordsToBTSs(words, btss);
		for(var i = 0; i < words.length; i++) {
			wordsWithFlags.changeWordFlags(words[i]);
		}
	};

	var oldRemoveWord = wordsWithFlags.removeWord;
	wordsWithFlags.removeWord = function(word) {
		// При удалении слова из коллекции необходимо вызвать этот метод.
		// Удаляет ссылки на слово из коллекции.
		oldRemoveWord(word);
		word.lastChangedTreeFlags.forEach(function(str) {
			wordsWithFlags.treeFlagsBts.rm(str, word);
		});
		if(word.lastChangedCheckedCheckboxes) {
			word.lastChangedCheckedCheckboxes.forEach(function(str) {
				removeFromArrayByValue(wordsWithFlags.wordsByCheckboxes[str], word);
			});
		}
	};

	wordsWithFlags.getWordsPairByFlags = function(enF, ruF, bts, flags, count, noIn) {
		// Возвращает случайные пары слов, соответствующие flags.
		// flags - флаги, которым должны соответствовать словаz
		// count - желаемое количество слов на выходе, однако модет вернуть меньше.
		// noIn - указывает, что нужно искать только с идентичным флагом, исключая подфлаги.
		if(!bts) bts = wordsWithFlags.treeFlagsBts;
		var pairs = [];
		var i, n, w, word, nods, words, nod, j, wordsForPair;
		var addFlags = [];
		var filterFlags = [];
		flags.forEach(function(flag) {
			if(flag && flag[0]) (flag[2] ? filterFlags : addFlags).push(flag);
		});
		if(!addFlags.length) return pairs;
		if(count < 1) count = 1;
		shuffleArray(addFlags);
		var testedWords = {}; // сюда записываем кеш результатов уже провереных слов. 1 - testByFlag вернул true; 2 - testByFlag для всех вернул false; 4 - testByFilters возвращает true; 8 - testByFilters возвращает false

		var testByFlag = function(word, flag) {
			if(testedWords[word.sid] === undefined) {
				testedWords[word.sid] = 0;
			}
			if(!flag) {
				if(testedWords[word.sid] % 4 === 1) {
					return 1;
				} else if(testedWords[word.sid] % 4 === 2) {
					return;
				}
				for(var i = 0; i < addFlags.length; i++) {
					if(testByFlag(word, addFlags[i])) return 1;
				}
				if(testedWords[word.sid] === undefined) {
					testedWords[word.sid] = testedWords[word.sid] - testedWords[word.sid] % 4 + 2;
				}
				return;
			}
			var apportionedFlags, containAllFlags, f, c;
			apportionedFlags = word.getApportionedFlags();
			for(f = 0; f < apportionedFlags.length; f++) {
				if(apportionedFlags[f][0] === flag[0] || (apportionedFlags[f][0].length > flag[0].length && apportionedFlags[f][0].substr(0, flag[0].length) === flag[0])) {
					containAllFlags = 1;
					if(flag[1]) {
						for(c = 0; c < flag[1].length; c++) {
							if(apportionedFlags[f][1].indexOf(flag[1][c]) === -1) {
								containAllFlags = 0;
								break;
							}
						}
					}
				}
			}
			if(containAllFlags) {
				testedWords[word.sid] = testedWords[word.sid] - testedWords[word.sid] % 4 + 1;
				return 1;
			}
			return;
		};

		var testByFilters = function(word) {
			// проверяет слово на фильтры, если не попадает под фильтр - возвращает 1.
			if(testedWords[word.sid] === undefined) {
				testedWords[word.sid] = 0;
			} else if(testedWords[word.sid] >= 8) {
				return;
			} else if(testedWords[word.sid] >= 4) {
				return 1;
			}
			var apportionedFlags, f, c, ff;
			apportionedFlags = word.getApportionedFlags();
			var wordAddFlags = [];
			var wordFilterFlags = [];
			apportionedFlags.forEach(function(flag) {
				if(flag && flag[0]) (flag[2] ? wordFilterFlags : wordAddFlags).push(flag);
			});
			// Сначала проверяем флаги слова по фильтрам filterFlags.
			for(f = 0; f < wordAddFlags.length; f++) {
				nextFilterFlag:
				for(ff = 0; ff < filterFlags.length; ff++) {
					if(wordAddFlags[f][0] === filterFlags[ff][0] || (!noIn && wordAddFlags[f][0].length > filterFlags[ff][0].length && wordAddFlags[f][0].substr(0, filterFlags[ff][0].length) === filterFlags[ff][0])) {
						if(filterFlags[ff][1] && filterFlags[ff][1].length) {
							for(c = 0; c < filterFlags[ff][1].length; c++) {
								if(wordAddFlags[f][1].indexOf(filterFlags[ff][1][c]) === -1) {
									continue nextFilterFlag;
								}
								testedWords[word.sid] = 8 + testedWords[word.sid] % 4;
								return;
							}
						} else {
							testedWords[word.sid] = 8 + testedWords[word.sid] % 4;
							return;
						}
					}
				}
			}
			// А теперь проверим флаги по фильтрам самого слова.
			for(f = 0; f < wordFilterFlags.length; f++) {
				for(ff = 0; ff < addFlags.length; ff++) {
					if(wordFilterFlags[f][0] === addFlags[ff][0] || addFlags[ff][0].length > wordFilterFlags[f][0].length && addFlags[ff][0].substr(0, wordFilterFlags[f][0].length) === wordFilterFlags[f][0]) {
						if(wordFilterFlags[f][1] && wordFilterFlags[f][1].length) {
							for(c = 0; c < wordFilterFlags[f][1].length; c++) {
								if(addFlags[ff][1].indexOf(wordFilterFlags[f][1][c]) !== -1) {
									testedWords[word.sid] = 8 + testedWords[word.sid] % 4;
									return;
								}
							}
						}
					}
				}
			}
			testedWords[word.sid] = 4 + testedWords[word.sid] % 4;
			return 1;
		};

		for(i = 0; i < addFlags.length; i++) {
			if(noIn) {
				nods = bts.get(addFlags[i][0]);
				if(nods) {
					nods = [nods];
				} else {
					nods = [];
				}
			} else {
				nods = bts.searchOrAutocomplete(addFlags[i][0]);
			}
			nods = shuffleArray(nods.concat());
			for(n = 0; n < nods.length; n++) {
				words = shuffleArray(nods[n].os.concat()); // тут могут быть тысячи слов, но это займет в пределах милисекунды.
				for(w = 0; w < words.length; w++) {
					word = words[w];
					if(word && !word.wordLang && testByFlag(word, addFlags[i]) && testByFilters(word) && (!enF || (!isArray(enF) || enF.indexOf(word) === -1) && (!isFunction(enF) || enF(word, addFlags[i])))) {
						nod = wordsWithFlags.btsByLang[0].get(word.getFormed());
						if(nod) {
							wordsForPair = shuffleArray(nod.os.concat());
							for(j = 0; j < wordsForPair.length; j++) {
								if(wordsForPair[j] && wordsForPair[j].wordLang && testByFlag(wordsForPair[j]) && testByFilters(wordsForPair[j]) && (!ruF || (!isArray(ruF) || ruF.indexOf(wordsForPair[j]) === -1) && (!isFunction(ruF) || ruF(wordsForPair[j], addFlags[i])))) {
									pairs.push([word, wordsForPair[j]]);
									if(count && count <= pairs.length) {
										return pairs;
									}
								}
							}
						}
					}
				}
			}

		}
		return pairs;

	};

	wordsWithFlags.getWordsPairByFlagsOutInside = function(enF, ruF, bts, flags, count, objContainReduceFlagFunc) {
		if(!objContainReduceFlagFunc) { // этот параметр нужен для adverbs.getWordsPairByFlagsOutInside
			objContainReduceFlagFunc = WordWithFlags.prototype;
		}
		// Ищет слова с флагом как у flags или более широкой категории.
		var i, flag;
		var ret = [];
		var words = [];
		var addFlags = [];
		var filterFlags = [];
		flags.forEach(function(flag) {
			if(flag && flag[0]) (flag[2] ? filterFlags : addFlags).push(flag);
		});
		addFlags = shuffleArray(addFlags);
		for(i = 0; i < addFlags.length; i++) {
			flag = addFlags[i];
			while(flag) {
				var pairs = wordsWithFlags.getWordsPairByFlags(enF, ruF, bts, [flag].concat(filterFlags), count - ret.length, 1);
				for(var p = pairs.length; p--; ) {
					if(words.indexOf(pairs[p][0]) === -1 && words.indexOf(pairs[p][1]) === -1) {
						words.push(pairs[p][0]);
						words.push(pairs[p][1]);
					} else {
						pairs.splice(p, 1); // удаляем пары, слова которых уже встречались.
					}
				}
				if(pairs.length) {
					ret = ret.concat(pairs);
					if(ret.length >= count) {
						return ret;
					}
				}
				flag = objContainReduceFlagFunc.reduceFlag(flag);
			}
		}
		return ret;
	};

	var oldGetRndWordsPairs = wordsWithFlags.getRndWordsPairs;
	wordsWithFlags.getRndWordsPairs = function(enF, ruF, bts, flags, count, outInside) {
		// Возвращает пары[en, ru] слов из bts, при которых enF и ruF возвращают истину.
		// в данном случае может еще выбирать по флагам.
		if(isArray(flags)) {
			var ret;
			if(outInside) {
				ret = wordsWithFlags.getWordsPairByFlagsOutInside(enF, ruF, bts, flags, count || 0);
			} else {
				ret = wordsWithFlags.getWordsPairByFlags(enF, ruF, bts, flags, count || 0);
			}
			if(!ret || !ret.length) return;
			return ret;
		}
		return oldGetRndWordsPairs(enF, ruF, 0, count);
	};

	wordsWithFlags.getRndWordsPair = function(enF, ruF, bts, flags, outInside) {
		// Возвращает пару[en, ru] слов из bts, при которых enF и ruF возвращают истину.
		// в данном случае может еще выбирать по флагам.
		var ret = wordsWithFlags.getRndWordsPairs(enF, ruF, bts, flags, 1, outInside);
		if(ret) return ret[0];
	};

	wordsWithFlags.getCommonFlagsForWords = function(words, filtrFlags, getApportionedFlagsFunc) {
		// Возвращает общие для переданых words флаги. Только возвращает их в виде возвращаемом apportionFlagToTreeRadioAndCheckboxes() .
		var i, n, p, k, is;
		if(!getApportionedFlagsFunc) { // этот параметр присутствует для упрощения getCommonVerbFlagsForWords .
			getApportionedFlagsFunc = function(word) {
				return word.getApportionedFlags();
			};
		}
		var commonFlags = [];
		var apportionFlag;
		var filterFlags = {}; // общая коллекция флагов, у которых установлен checkbox "filter".
		for(i = 0; i < words.length; i++) {
			if(!i) {
				commonFlags = cloneStruct(getApportionedFlagsFunc(words[i])); // ! клонируем, т.к. getApportionedFlags кеширует отдаваемый экземпляр.
				for(n = commonFlags.length; n--; ) {
					if(filtrFlags && commonFlags[n][3] !== filtrFlags) { // в filtrFlags указываются первые цифры, которые должен содержать флаг категории.
						commonFlags.splice(n, 1); // вырезаем все отфильтрованные флаги.
					} else if(commonFlags[n][2]) {
						filterFlags[commonFlags[n][0]] = commonFlags[n];
						commonFlags.splice(n, 1); // выносим из commonFlags фильтрующие флаги.
					}
				}
			} else {
				var wordFlags = getApportionedFlagsFunc(words[i]).concat();
				if(filtrFlags) {
					for(n = wordFlags.length; n--; ) {
						if(filtrFlags && wordFlags[n][3] !== filtrFlags) { // в filtrFlags указываются первые цифры, которые должен содержать флаг категории.
							wordFlags.splice(n, 1); // вырезаем все отфильтрованные флаги.
						}
					}
				}
				for(n = commonFlags.length; n--; ) {
					is = false; // сюда записывается индекс совпадающего(без checkboxов) флага.
					for(p = 0; p < wordFlags.length; p++) { // проходим все флаги слова, ища совпадающий.
						if(wordFlags[p][2]) { // если это фильтрующий флаг.
							if(filterFlags[wordFlags[p][0]]) {
								for(k = filterFlags[wordFlags[p][0]][1].length; k--; ) {
									if(wordFlags[p][1].indexOf(filterFlags[wordFlags[p][0]][1][k]) === -1) {
										filterFlags[wordFlags[p][0]][1].splice(k, 1); // удаляем не найденые в слове checkboxы.
									}
								}
							} else {
								filterFlags[wordFlags[p][0]] = cloneStruct(wordFlags[p]); // клонируем, чтобы не нарушить переменную внутри words[i] .
							}
						} else {
							if(wordFlags[p][0] === commonFlags[n][0]) {
								is = true;
								for(k = commonFlags[n][1].length; k--; ) {
									if(!wordFlags[n] || wordFlags[n][1].indexOf(commonFlags[n][1][k]) === -1) {
										commonFlags[n][1].splice(k, 1); // удаляем не найденые в слове checkboxы.
									}
								}
								continue;
							}
						}
					}
					if(!is) {
						removeFromArrayByValue(commonFlags, commonFlags[n]);
					}
				}
			}
		}
		for(k in filterFlags) {
			commonFlags.push(filterFlags[k]);
		}
		return commonFlags;
	};

	wordsWithFlags.getCommonVerbFlagsForWords = function(words, filtrFlags, getApportionedFlagsFunc) {
		if(!getApportionedFlagsFunc) getApportionedFlagsFunc = function(word) {return word.getApportionedVerbFlags();};
		return wordsWithFlags.getCommonFlagsForWords(words, filtrFlags, getApportionedFlagsFunc);
	};

	return wordsWithFlags;
};
var constructNounsCollection = function(words) {
	var nouns = constructWordsWithFlagsCollection();
	nouns.parent = words;
	nouns.btsCollections = words.btsCollections.concat(nouns.btsCollections); // список коллекций(деревьев) в которые должны помещаться слова связаного типа.
	nouns.valideTypes = [Noun];

	return nouns;
};

var nouns = constructNounsCollection(words); // Основная коллекция существительных, может использоваться в любых частях программы.

var baseNouns = constructNounsCollection(baseWords);
// Не стал заморачиваться.
// На самом деле конечно pronouns, но сократил
//            0           1              2             3                4             5               6
var prons = [['I', 'я'], ['you', 'вы'], ['we', 'мы'], ['they', 'они'], ['he', 'он'], ['she', 'она'], ['it', 'оно']];
//var pronsByLang = [prons.map(function(item) {return item[0]}), prons.map(function(item) {return item[1]})]; // то же что и prons, но в другом порядке навигации.
var pronsToGender = [0, 3, 3, 3, 0, 1, 2];
var genderToProns = [4, 5, 6, 3];
var pronsToRu = ['меня', 'вас', 'нас', 'их', 'его', 'ее', 'этого'];
var pronsObTo = [['me', 'мне'], ['you', 'вам'], ['us', 'нам'], ['them', 'им'], ['him', 'ему'], ['her', 'ей']]; // местоимения отвечающие на вопрос кому?

var pronsOf = [ // местоимения, отвечающие на вопросы чей? чья? чьим? ...
['my', [
	['мой',		'моего',	'моему',	1,			'моим',		'моем'],
	['моя',		'моей',		1,			'мою',		1,			1],
	['мое',		-1,			-1,			-1,			-1,			-1],
	['мои',		'моих',		'моим',		1,			'моими',	1],
]],
['your', [
	['ваш',		'вашего',	'вашему',	1,			'вашим',	'вашем'],
	['ваша',	'вашей',	1,			'вашу',		1,			1],
	['ваше',	-1,			-1,			-1,			-1,			-1],
	['ваши',	'ваших',	'вашим',	1,			'вашими',	'ваших'],
]],
['our', [
	['наш',		'нашего',	'нашему',	1,			'нашим',	'нашем'],
	['наша',	'нашей',	1,			'нашу',		1,			1],
	['наше',	-1,			-1,			-1,			-1,			-1],
	['наши',	'наших',	'нашим',	1,			'нашими',	'наших'],
]],
['their', [
	['ихний',	'ихнего',	'ихнему',	1,			'ихним',	'ихнем'],
	['ихняя',	'ихней',	1,			'ихнию',	1,			1],
	['ихнее',	-1,			-1,			-1,			-1,			-1],
	['ихние',	'ихних',	'ихним',	1,			'ихними',	'ихних'],
]],
['his', 'его'],
['her', 'ее'],
['', 'этого'],
];

var pronsByPadej = [
[['I', 'we'],
['я', 'меня', 'мне', 'меня', 'мной', 'мне', 'мы', 'нас', 'нам', 'нас', 'нами', 'нас'], [581]],
[['you', 'we'],
['вы', 'вас', 'вам', 'вас', 'вами', 'вам', 'вы', 'вас', 'вам', 'вас', 'вами', 'вас'], [144,10,11], [3], 3],
[['we', 'we'],
['мы', 'нас', 'нам', 'нас', 'нами', 'нас', 'мы', 'нас', 'нам', 'нас', 'нами', 'нас'], [144,10,11], [3], 3],
[['they', 'they'],
['они', 'их', 'им', 'их', 'ими', 'них', 'они', 'их', 'им', 'их', 'ими', 'них'], [144,10,11], [3], 3],
[['he', 'they'],
['он', 'его', 'ему', 'его', 'им', 'нем', 'они', 'их', 'им', 'их', 'ими', 'них'], [581]],
[['she', 'they'],
['она', 'ее', 'ей', 'ее', 'ей', 'ней', 'они', 'их', 'им', 'их', 'ими', 'них'], [581]],
[['it', 'they'],
['оно', 'этого', 'этому', 'это', 'этим', 'этих', 'они', 'их', 'им', 'их', 'ими', 'них'], [146], 2],
];
var pronsAsNounsArray = [[],[]];
var pronsAsNounsPairs = [];
var pronsAsNounsCollection = constructNounsCollection(baseWords);
for(var i = 0; i < prons.length; i++) {
	var enWord = new NounEn([pronsByPadej[i][0][0],pronsByPadej[i][2],pronsByPadej[i][0]]);
	enWord.gender = pronsToGender[i];
	var ruWord = new NounRu([pronsByPadej[i][1][0],pronsByPadej[i][2],pronsByPadej[i][1],pronsByPadej[i][3]]);
	ruWord.gender = pronsToGender[i];
	enWord.isPron = true;
	ruWord.isPron = true;
	enWord.pronInd = i;
	ruWord.pronInd = i;
	pronsAsNounsArray[0].push(enWord);
	pronsAsNounsArray[1].push(ruWord);
	var pair = [enWord, ruWord];
	pronsAsNounsPairs.push(pair);
	pronsAsNounsCollection.addWords(pair);
	baseNouns.addWords(pair, 1);
}
var constructVerbsCollection = function(words) {
	var verbs = constructWordsWithFlagsCollection();
	verbs.parent = words;
	//var btsByFullTree = new BTS(); // специальная коллекция по флагам, отличается от treeFlagsBts тем, что включает в начало индекса 4 первых бита для filter и destination
	verbs.btsCollections = words.btsCollections.concat(verbs.btsCollections); // список коллекций(деревьев) в которые должны помещаться слова связаного типа.
	verbs.valideTypes = [Verb];
	// verbs.treeVerbFlagsBts - не нужно, ведь мы ни когда не подбираем глаголы для наречия.

	return verbs;
};

var verbs = constructVerbsCollection(words); // Основная коллекция существительных, может использоваться в любых частях программы.

var baseVerbs = constructVerbsCollection(baseWords); // Основная коллекция существительных, может использоваться в любых частях программы.

var constructAdjectivesCollection = function(words) {
	var adjectives = constructWordsWithFlagsCollection();
	adjectives.parent = words;
	adjectives.btsCollections = words.btsCollections.concat(adjectives.btsCollections); // список коллекций(деревьев) в которые должны помещаться слова связаного типа.
	adjectives.valideTypes = [Adjective];

	return adjectives;
};

var adjectives = constructAdjectivesCollection(words); // Основная коллекция существительных, может использоваться в любых частях программы.

var baseAdjectives = constructAdjectivesCollection(baseWords);

var constructAdverbsCollection = function(words) {
	var adverbs = constructWordsWithFlagsCollection();
	adverbs.parent = words;
	adverbs.btsCollections = words.btsCollections.concat(adverbs.btsCollections); // список коллекций(деревьев) в которые должны помещаться слова связаного типа.
	adverbs.valideTypes = [Adverb];

	var oldGetWordsPairByFlagsOutInside = adverbs.getWordsPairByFlagsOutInside;
	adverbs.getWordsPairByFlagsOutInside = function(enF, ruF, bts, flags, count, reduceFlagFunc) {
		return oldGetWordsPairByFlagsOutInside(enF, ruF, bts, flags, count, reduceFlagFunc || Adverb.prototype);
	};

	return adverbs;
};

var adverbs = constructAdverbsCollection(words); // Основная коллекция существительных, может использоваться в любых частях программы.

var baseAdverbs = constructAdverbsCollection(baseWords);

// Объявляем всякие особые слова обоих языков.
(function() {

	var enByStr = {};
	var ruByStr = {};
	var byStr = [enByStr, ruByStr];
	var lastConstructor = [];
	var i, n;
	var crW = function() {
		// Кратко создает слова.
		var json, ret;
		var len = arguments.length;
		var lang = 0;
		if(isNumber(arguments[len - 1])) {
			len --;
			lang = arguments[len];
		}
		var constructor = 0;
		if(isFunction(arguments[len - 1])) {
			len --;
			constructor = arguments[len];
		}
		if(constructor) {
			lastConstructor[lang] = constructor;
		}
		for(var i = 0; i < len; i++) {
			json = arguments[i];
			if(isString(json)) {
				json = [json];
			}
			var word = byStr[lang][json[0]];
			if(!word) {
				word = new lastConstructor[lang](json);
				byStr[lang][json[0]] = word;
			}
			if(!ret) {
				ret = word;
			} else {
				if(!isArray(ret)) ret = [ret];
				ret.push(word);
			}
		}
		return ret;
	};
	var crWRu = function(json, constructor) {
		var args = pushAll([], arguments);
		args.push(1);
		return crW.apply(this, args);
	};
	var crP = function(en, ru) {
		return [crW(en), crWRu(ru)];
	};

	baseWords.emptyWord = new NounRu('');
	baseWords.emptyWord.getFormed = function() {return '';};

	var toBeEnForms = ['be', 'was', 'been', 'is'];
	var toBeEn = new VerbEn(['be',[73,74,75,81,82,83],toBeEnForms,[53]]);
	toBeEn.getFormed = function(pron, time, form) {
		var ret;
		if(form === undefined && time === undefined) {
			ret = toBeEnForms[pron || 0];
		} else if(time > 1) {
			ret = 'be';
		} else if(!time) {
			if(pron > 0 && pron < 4) {
				ret = 'were';
			} else {
				ret = 'was';
			}
		} else if(!pron) {
			ret = 'am';
		} else if(pron > 0 && pron < 4) {
			ret = 'are';
		} else {
			ret = 'is';
		}
		if(form === 2 && ret !== 'be') {
			ret += ' not';
		}
		return ret;
	};
	var toBeRuForms = ['быть', 'есть', 'есть', 'было', 'есть', 'есть', 'есть', 'был', 'были', 'была', 'будучи'];
	var toBeRu = new VerbRu(['быть',[73,74,75,81,82,83],toBeRuForms,[53]]);
	toBeRu.getFormed = function(pron, time, form) {
		if(!time) {
			if(form === undefined && time === undefined) {
				return toBeRuForms[pron || 0];
			}
			return VerbRu.prototype.getFormed.apply(this, arguments);
		}
		if(time === 1) {
			return 'есть';
		}
		if(time === 2) {
			return ['буду', 'будете', 'будем', 'будут', 'будет', 'будет', 'будет'][pron];
		}
	};
	baseVerbs.toBePair = [toBeEn, toBeRu];
	baseVerbs.addWords(baseVerbs.toBePair, 1);

	lastConstructor = [NounEn, NounRu];

	var timeFlags = [5137];
	baseNouns.timeUnitsPairs = [
		crP(['minute',timeFlags], ['минута', timeFlags,0,1]),
		crP(['hour',timeFlags], ['час', timeFlags]),
		crP(['day',timeFlags], ['день', timeFlags, [0,0,0,0,0,0,0,0,0,0,0,'днях']]),
		crP(['week',timeFlags], ['неделя', timeFlags, [0,0,0,0,0,0,0,0,0,0,0,'неделях'], 1]),
		crP(['month',timeFlags], ['месяц', timeFlags]),
		crP(['year',timeFlags], ['год', timeFlags]),
	];
	baseNouns.timeUnitsPairs.forEach(function(p) {
		baseAdverbs.addWords(p);
	});

	var kotoriyWord = new AdjectiveRu('который');
	baseNouns.whoPair = crP(['who'], ['кто']);
	baseNouns.whoAttributivePair = [crW(['who']), kotoriyWord];
	baseNouns.whoAttributivePair[0].isPron = 1;
	baseNouns.whichPair = crP(['which'], ['какой']);
	baseNouns.whichAttributivePair = [crW(['which']), kotoriyWord];
	baseNouns.whichAttributivePair[0].isPron = 1;

	var numeral20 = [
		'null',
		'one',
		'two',
		'three',
		'four',
		'five',
		'six',
		'seven',
		'eight',
		'nine',
		'ten',
		'eleven',
		'twelve',
		'thirteen',
		'fourteen',
		'fifteen',
		'sixteen',
		'seventeen',
		'eighteen',
		'nineteen',
	];
	baseWords.numeral20Pairs = [];
	numeral20.forEach(function(str, i){
		var pair = crP(str, i + '');
		baseWords.numeral20Pairs.push(pair);
		baseWords.addWords(pair);
	});
	var numeralDegrees = [
		['teen', '10'],
		['hundred', 'сто'],
		['thousand', 'тысяча'],
		['million', 'миллион'],
	];
	baseWords.numeralDegreesPairs = [];
	numeralDegrees.forEach(function(ar, i){
		var pair = [crW(ar[0]), crWRu(ar[1])];
		baseWords.numeralDegreesPairs.push(pair);
		baseWords.addWords(pair);
	});
	var numeralTeens = [
		'twenty',
		'thirty',
		'forty',
		'fifty',
		'sixty',
		'seventy',
		'eighty',
		'ninety',
	];
	baseWords.numeralTeensPairs = [];
	numeralTeens.forEach(function(str, i){
		var pair = crP(str, '' + (i + 2) * 10);
		baseWords.numeralTeensPairs.push(pair);
		baseWords.addWords(pair);
	});
	baseWords.ordinalSpecPairs = [];
	var ordinalSpec = [0, 'first', 'second', 'third', 0, 'fifth'];
	ordinalSpec.forEach(function(str, i) {
		if(str) {
			var pair = crP(str, i + '-й');
			baseWords.ordinalSpecPairs[i] = pair;
			baseWords.addWords(pair);
		}
	});

	lastConstructor = [VerbEn, VerbRu];
	//var tohave_verb = crP(["have",[2192,138,20],0,[42]], ["иметь",[2192,138,20],0,[42]]); // используется только для связи с глаголом.
	var tohave_noun = crP(['have',[2192,138,2320,146,147],['have', 'had', 'has', 'has'],[42]], ['иметь',[2192,138,2320,146,147],0,[42]]); // используется для соединения с существительными

	lastConstructor = [AdverbEn, AdverbRu];

	baseAdverbs.almostPair = crP('almost', 'почти');
	baseAdverbs.addWords(baseAdverbs.almostPair);
	baseAdverbs.littlePair = crP('little', 'немного');
	baseAdverbs.addWords(baseAdverbs.littlePair);
	baseAdverbs.strongPair = crP('strong', 'сильно');
	baseAdverbs.addWords(baseAdverbs.strongPair);
	baseAdverbs.tooPair = crP('too', 'слишком');
	baseAdverbs.addWords(baseAdverbs.tooPair);

	baseAdverbs.thenPair = crP('then', 'чем');
	baseAdverbs.addWords(baseAdverbs.thenPair);
	baseAdverbs.lessPair = crP('less', 'менее');
	baseAdverbs.addWords(baseAdverbs.lessPair);
	baseAdverbs.morePair = crP('more', 'более');
	baseAdverbs.addWords(baseAdverbs.morePair);
	baseAdverbs.mostPair = crP('most', 'наиболее');
	baseAdverbs.addWords(baseAdverbs.mostPair);
	baseAdverbs.afterPair = crP('after', 'после');
	baseAdverbs.addWords(baseAdverbs.afterPair);
	baseAdverbs.beforePair = crP('before', 'перед');
	baseAdverbs.addWords(baseAdverbs.beforePair);

	baseAdverbs.inTimePair = crP('in', 'через');
	baseAdverbs.inDatePair = crP('in', 'в');
	baseAdverbs.inPair = baseAdverbs.inTimePair.concat(crWRu('в'));
	baseAdverbs.addWords(baseAdverbs.inPair);
	baseAdverbs.agoTimePair = crP('ago', 'тому назад');
	baseAdverbs.addWords(baseAdverbs.agoTimePair);

	baseAdverbs.atTimePair = crP('at', 'в');
	baseAdverbs.addWords(pushAll(crWRu('у', 'на', 'около'), baseAdverbs.atTimePair));

	baseAdverbs.onDatePair = crP('on', 'в');

	baseAdverbs.pastTimePair = crP('past', 'после');
	baseAdverbs.addWords(baseAdverbs.pastTimePair);

	baseAdverbs.toTimePair = crP('to', 'до');
	baseAdverbs.addWords(baseAdverbs.toTimePair.concat(crWRu('к', 'в', 'на', 'для')));

	lastConstructor = [AdjectiveEn, AdjectiveRu];

	baseAdjectives.badPair = crP(['bad',0,[0,'worse','worst']], ['плохой',0,[0,'хуже','худший']]);
	baseAdjectives.addWords(baseAdjectives.badPair);

	baseAdjectives.goodPair = crP(['good',0,[0,'better','best']], ['хороший',0,[0,'лучше','лучший']]);
	baseAdjectives.addWords(baseAdjectives.goodPair);

	baseAdjectives.timeNextPair = crP('next', 'следующий');
	baseAdjectives.addWords(baseAdjectives.timeNextPair);
	baseAdjectives.timeLastPair = crP('last', 'прошлый');
	baseAdjectives.addWords(baseAdjectives.timeLastPair);
	baseAdjectives.thisPair = crP('this', 'этот');
	baseAdjectives.thisPair[1].getFormed = function(comparative, gender, padej) {
		return (gender === 3) ? 'these' : 'this';
	};
	var thisPadejs = [
		['от',	'ого',	'ому',	'ого',	'им',	'ом'],
		['а',	'ой',	'ой',	'у',	'ой',	'ой'],
		['т',	'ого',	'ому',	'ого',	'им',	'ом'],
		['и',	'их',	'им',	'их',	'ими',	'их'],
	];
	baseAdjectives.thisPair[1].getFormed = function(comparative, gender, padej) {
		if(!gender) gender = 0;
		if(!padej) padej = 0;
		return 'эт' + thisPadejs[gender][padej];
	};
	baseAdjectives.addWords(baseAdjectives.thisPair);
	baseAdjectives.thatPair = crP('that', 'тот');
	baseAdjectives.thatPair[1].getFormed = function(comparative, gender, padej) {
		return (gender === 3) ? 'those' : 'that';
	};
	baseAdjectives.thatPair[1].getFormed = function(comparative, gender, padej) {
		if(!gender) gender = 0;
		if(!padej) padej = 0;
		var str = thisPadejs[gender][padej];
		if(str[0] === 'и') str = 'е' + str.substr(1);
		return 'т' + str;
	};
	baseAdjectives.addWords(baseAdjectives.thatPair);

	lastConstructor = [VerbEn, VerbRu];

	baseVerbs.willPair = [crW('will'), crWRu(['быть', 0, ['быть', 'буду', 'будете', 0, 'будем', 'будут', 0, 0, 0, 0, 'будучи']])];

	addToOnloadBody(function() {
		// Эти слова еще не внедрил, но собираюсь
		var specWords = {
			'Word': [
				['the', ['тот самый']],
				['a', ['один']],
				['an', ['один']],
				['such', ['такой']],

				['and', ['и', 'а', 'но']],
				['either', ['и']],
				['of', ['из', 'от', 'о', 'об']],
				['with', ['с', 'вместе с']],
				['without', ['без']],
				['for', ['в течение', 'на', 'для']],
				['on', ['на']],
				['into', ['в']],
				['toward', ['к']],
				['by', ['у ', 'около']],
				['about', ['о', 'около', 'об', 'у', 'кругом', 'насчет', 'по']],
				['through', ['через']],
				['upon', ['на']],
				['under', ['под', 'ниже']],
				['behind', ['за']],
				['among', ['среди']],
				['during', ['во время']],

				['not', ['не', 'нет', 'ни']],
				['nor', ['ни']],
				['like', ['похожий', 'как', 'подобно', 'любить', 'нравиться']],
				['as', ['как', 'когда']],
				['no', ['нет', 'не']],
				['if', ['если']],
				['else', ['иначе']],
				['or', ['или']],
				['than', ['чем', 'нежели']],
				['off', ['от', 'с']],
				['another', ['другой']],
				['same', ['тот же']],
				['every', ['каждый']],
				['each', ['каждый']],
				['both', ['оба']],

				['whether', ['ли']],

				['yes', ['да']],
				['no', ['нет']],
				['maybe', ['может быть']],
				['okay', ['ладно']],
			],
			'Noun': [
				['all', ['все', 'вся', 'всё']],
				['everything', ['все']],
				['what', ['что']],
				['their', ['их']],
				['thy', ['твой']],
				['other', ['другой']],
				['nothing', ['ничего']],
				['himself', ['себя']],
				['myself', ['себе', 'себя']],
				['herself', ['сама']],
				['themselves', ['сами']],
				['thee', ['тебя']],
				['itself', ['себя']],
				['thou', ['ты']],
			],
			'Verb': [
				[['can', 'could'], ['мочь'], 'Для отражения способности что-то сделать. Чтобы попросить/дать разрешение. Для запрещения.'],
				[['may', 'might'], ['можно', 'мочь'], 'Чтобы попросить/дать разрешение. При запрете. В упреке.'],
				['must', ['должен', 'надо'], 'Для выражения непосредственной необходимости или обязанности. В прямом приказе, запрете. Для выражения уверенности.'],
				['have (got) to', ['нужно', 'должен'], 'Для выражения необходимости что-то сделать из-за обстоятельств.'],
				['be to', ['необходимо', 'мочь'], 'Для описания действия, необходимого согласно некоему плану. При категорическом запрете. Обозначения неизбежности. Описания принципиальной невозможности.'],
				['need', ['нужно', 'необходимость'], 'Обычно заменяет "Must" и "Have (got) to" в отрицательных и вопросительных предложениях.'],
				['ought to', ['должен'], 'Для обозначения совета или морального долгаZ или обязательства. Для закономерного предположения.'],
				['should', ['нужно', 'должен'], 'Для выражения морального обязательства. В советах и инструкциях. В умозаключениях, совершенных на основе только что высказанной информации.'],
				['would', ['могли бы', 'будет'], 'В вежливых просьбах и предложении чего-либо. Для обозначения привычного действия в прошлом. В догадках и предположениях.'],
				['shall', ['буду', 'должен'], 'В предложении что-то сделать или помочь.'],
				['dare', ['сметь'], 'Иметь мужество или наглость что-то сделать, рискнуть.'],
				['used to', ['делал', 'был'], 'Для описания действия или состояния, регулярно имевшего место в прошлом.'],
				['being', ['быть']],
				['seem', ['казаться']],
				['reach', ['достигать']], // тут на русском для whom используется именительный падеж.

				//['had', ['имел', 'получал']],
				//['have', ['иметь', 'получать', 'быть должным']],
				//['has', ['имел']],
				['do', ['делать']],
				['go', ['идти', 'ехать ', 'уходить']],
			],
			'Adverb': [
				['but', ['только', 'лишь', 'кроме', 'но', 'а']],
				['there', ['там', 'туда', 'здесь']],
				['out', ['вне', 'снаружи', 'за']],
				['up', ['наверх(у)', 'выше', 'вверх по', 'вдоль по']],
				['from', ['от', 'из', 'с']],
				['were', ['были']],
				['when', ['когда']],
				['why', ['почему']],
				['whose', ['чей']],
				['whom', ['кого']],
				['back', ['спина', 'задний']],
				['down', ['вниз', 'внизу', 'вниз по', 'вдоль по']],
				['over', ['над', 'свыше']],
				['across', ['через']],
				['so', ['так', 'тоже', 'также']],
				['only', ['только']],
				['how', ['как']],
				['any', ['какой-нибудь']],
				['even', ['даже']],
				['too', ['также']],
				['never', ['никогда']],
				['seem', ['казаться']],
				['because', ['потому что']],
				['therefore', ['поэтому']],
				['while', ['в то время как']],
				['many', ['много']],
				['though', ['хотя', 'даже', 'тем не менее']],
				['also', ['тоже']],
				['against', ['против']],
				['yet', ['еще']],
				['between', ['между']],
				['until', ['до']],
				['however', ['однако']],
				['less', ['менее']],
			],
		};
		var i, n, p, typeName, wordType, constructorEn, constructorRu, collection, arr, ruWords, ruWord, enWord, val, json;
		for(typeName in specWords) {
			if(typeName === 'Word') {
				constructorEn = Word;
				constructorRu = Word;
				collection = baseWords;
			} else {
				wordType = wordsTypes[typeName];
				constructorEn = wordType.constructorEn;
				constructorRu = wordType.constructorRu;
				collection = wordType.baseCollection;
			}
			lastConstructor = [constructorEn, constructorRu];
			arr = specWords[typeName];
			for(i = 0; i < arr.length; i++) {
				ruWords = [];
				for(n = 0; n < arr[i][1].length; n++) {
					ruWord = crWRu(arr[i][1][n]);
					if(constructorRu === Word) ruWord.wordLang = 1;
					ruWords.push(ruWord);
				}
				if(isString(arr[i][0])) arr[i][0] = [arr[i][0]];
				for(n = 0; n < arr[i][0].length; n++) {
					enWord = crW(arr[i][0][n]);
					if(constructorEn === Word) enWord.wordLang = 0;
					if(arr[i][2]) enWord.title = arr[i][2];
					collection.addWords(ruWords.concat(enWord));
				}
			}
		}

	});

	baseVerbs.notContinuousVerbs = { // глаголы, не имеющие формы Continuous.
		'Мыслительной деятельности': [
			['agree','соглашаться'],
			['believe','верить'],
			['deny','отрицать'],
			['doubt','сомневаться'],
			['expect','полагать'],
			['forget','забывать'],
			['know','знать'],
			['recognize','узнавать'],
			['remember','помнить'],
			['respect','уважать'],
			['suppose','предполагать'],
			['think','полагать'],
			['understand','понимать']
		],
		'Эмоционального состояния': [
			['desire','желать'],
			['envy','завидовать'],
			['fear','бояться'],
			['hate','ненавидеть'],
			['like','нравиться'],
			['love','любить'],
			['need','нуждаться'],
			['prefer','предпочитать'],
			['satisfy','удовлетворять'],
			['want','хотеть'],
			['wish','желать']
		],
		'Ощущения': [
			['hear','слышать'],
			['see','видеть'],
			['smell','пахнуть'],
			['taste','иметь вкус']
		],
		'Обладания, отношения': [
			['be','быть'],
			['belong','принадлежать'],
			['consist','состоять'],
			['contain','содержать'],
			['have','иметь'],
			['include','заключать в себе'],
			['own','владеть'],
			['possess','обладать']
		]
	};
	baseVerbs.notContinuousVerbsMap = {};
	for(i = 0; i < baseVerbs.notContinuousVerbs.length; i++) {
		for(n = 0; n < baseVerbs.notContinuousVerbs[i].length; n++) {
			baseVerbs.notContinuousVerbsMap[baseVerbs.notContinuousVerbs[i][n][0]] = baseVerbs.notContinuousVerbs[i][n];
		}
	}
	baseVerbs.onlyGerundVerbs = [ // В качестве прямого дополнения используется только герундий, а не инфинитив.
['admit', 'допускать'],
['avoid', 'избегать'],
['delay–откладывать'],
['deny', 'отрицать'],
['dislike', 'не нравиться'],
['enjoy', 'наслаждаться'],
['escape', 'убегать'],
['excuse', 'извинять'],
['finish', 'закончить'],
['forgive', 'прощать'],
['imagine', 'воображать'],
['mention', 'упоминать'],
['mind', 'возражать'],
['miss', 'упустить'],
['postpone', 'откладывать'],
['risk', 'рисковать'],
['stop', 'прекращать'],
['suggest', 'предлагать'],
['understand', 'понимать'],
	];
	baseVerbs.onlyGerundVerbsMap = {};
	for(i = 0; i < baseVerbs.onlyGerundVerbs.length; i++) {
		baseVerbs.onlyGerundVerbsMap[baseVerbs.onlyGerundVerbs[i][0]] = baseVerbs.onlyGerundVerbsMap[i];
	}
})();
// Не будем сильно усложнять логику, просто отдельный общедоступный объект user, который в процессе выполнения не заменяется.
// временно, пока не стабильно, вносим тестовые слова в localStorage
/*var smallStr;
if(!ls[equal'_words_Noun']) {
	smallStr = '[[["snake",[4658]],[["змея",[4658],[0,0,0,0,0,0,0,0,0,"змей",0,"змеях"],1]]],[["sound",[5334]],[["звук",[5334]]]],[["unit",[42650,145]],[["единица",[42650,145],[0,0,0,0,"единицей"],1]]]]';
	ls[productName + '_words_Noun'] = smallStr;
}
if(!ls[productName + '_words_Verb']) {
	smallStr = '[[["break",[1169,41538,41542,41543,41552],0,[332]],[["ломать",[1169,41538,41542,41543,41552],0,[332]]]],[["change",[4677,1298,43587,43591],0,[5]],[["менять",[4677,1298,43587,43591],0,[5]]]],[["skip",[1169,1297,2626,2627,2637],0,[5,6]],[["пропускать",[1169,1297,2626,2627,2637],0,[5,6]]]]]';
	ls[productName + '_words_Verb'] = smallStr;
}
if(!ls[productName + '_words_Adjective']) ls[productName + '_words_Adjective'] = '[[["black",[57,17425]],[["черный",[57,17425]]]],[["dark",[57,29713]],[["темный",[57,29713]]]],[["white",[57]],[["белый",[57]]]]]';
if(!ls[productName + '_words_Adverb']) ls[productName + '_words_Adverb'] = '[[["cheerfully",[2246,2245]],[["весело",[2246,2245]]]],[["fast",[561]],[["быстро",[561]]]]]';
// TODO: Убрать вот это автодополнение слов.
*/
var user = {};
addEventsMixin(user);
win.user = user;
(function() {
	// Анонимная функция для сокрытия лишнего функционала/логики.
	user.login = 'myLogin';
	user.words = constructWordsCollection(); // коллекция существительных, находящихся на изучении юзером.
	user.nouns = constructNounsCollection(user.words); // коллекция существительных, находящихся на изучении юзером.
	user.verbs = constructVerbsCollection(user.words); // коллекция глаголов, находящихся на изучении юзером.
	user.adjectives = constructAdjectivesCollection(user.words); // коллекция прилагательных, находящихся на изучении юзером.
	user.adverbs = constructAdverbsCollection(user.words); // Коллекция наречий, находящихся на изучении юзером.
	user.emphasis = {}; // коллекции слов, отмеченных звездочками.
	user.emphasis.nouns = constructNounsCollection(user.nouns);
	user.emphasis.verbs = constructVerbsCollection(user.verbs);
	user.emphasis.adjectives = constructAdjectivesCollection(user.adjectives);
	user.emphasis.adverbs = constructAdverbsCollection(user.adverbs);
	user.commonWordsProgress = 0; // Юзеры могут включить автопополнение словоря. Тогда из words будут добавляться первые слова

	var settings = user.settings = {};

	user.addEmphasisWord = function(word) {
		var item = wordsTypes[word.wordTypeName];
		if(!item) return warning(encodeToJSON(word));
		var nodes = item.userCollection.searchNodes(word);
		for(var i = 0; i < nodes.length; i++) {
			item.userEmphasisCollection.addWords(nodes[i].os);
		}
	};
	user.removeEmphasisWord = function(word) {
		var item = wordsTypes[word.wordTypeName];
		if(!item) return warning(encodeToJSON(word));
		var nodes = item.userCollection.searchNodes(word);
		for(var i = 0; i < nodes.length; i++) {
			for(var n = 0; n < nodes[i].length; n++) {
				if(nodes[i]) item.userEmphasisCollection.remoweWord(nodes[i].os[0]);
			}
		}
	};

	var sendByCheck = function(content, cb) {
		// Отправляет на сервер данные для сохранения.
		var sessionId = ls[productName + '_session_id' || 0];
		var str = sessionId + ls[productName + '_seed' || 0] + ls[productName + '_action_ind' || 0];
		if(!str) return;
		var check = Sha256.hash(str);
		sendRequest('backend/sync.php', {'session_id': sessionId, 'check': check, 'content': content}, function(data) {
			if(data) {
				var json;
				try {
					json = decodeJSON(data);
					if(cb) cb(json);
					if(json['check' || 0] === 'ok' && json['status' || 0] === 'ok') {
						ls[productName + '_action_ind' || 0] = parseInt(ls[productName + '_action_ind' || 0]) + 1;
					} else if(json['check' || 0] === 'no') {
						ls[productName + '_action_ind' || 0] = '';
						domRemoveCssClass(domGetByClass('toLogInBtn'), 'unactive');
					}
				} catch(err) {
					ls[productName + '_action_ind' || 0] = '';
					notific('Сессия разорвана, авторизуйтесь заново');
					if(cb) cb();
					throw err;
				}
			}
		}, 'POST');
	};

	addToOnloadBody(function() {
		var centerPageId = ls[productName + '_centerPageId'];
		var centerPageOffset = ls[productName + '_centerPageOffset'];
		if(centerPageId) {
			windowSetTimeout(function() {
				pages.setCenterPage(ls[productName + '_centerPageId'], parseFloat(ls[productName + '_centerPageOffset']));
			}, 0);
		}
		pages.addEventListener('scrollCenterPage', function(actObj, id, offset) {
			ls[productName + '_centerPageId'] = id;
			ls[productName + '_centerPageOffset'] = offset;
		});

		// Работу с сессией вынесем в отдельную анонимную функцию.
		var autosetLogin = function() {
			var seed = ls[productName + '_seed' || 0];
			var sessionId = ls[productName + '_session_id' || 0];
			if(!win.localStorage) sessionId = getCookie(productName + '_session_id');
			if(sessionId) {
				if(ls[productName + '_action_ind' || 0]) {
					domAddCssClass(domGetByClass('toLogInBtn'), 'unactive');
				}
			}
			user.firstName = ls[productName + '_first_name' || 0];
			user.lastName = ls[productName + '_last_name' || 0];
			user.setBySettingsObj(ls[productName + '_user_settings' || 0]);
			domSetInnerText(domGetById('userName'), user.firstName + ' ' + user.lastName);
		};
		autosetLogin();

		win['uloginCallback' || 0] = function(token) { // специально используем win - window.
			// Клиент хранит только seed = sha3(front_seed, back_seed) ,sessionId и actionInd
			// при записи на сервер, передается check = sha256.hash(sessionId + actionInd + seed) внутренние переменные передавать запрещено.
			var front_seed = randomInt(2147483647);
			sendRequest('backend/ulogin.php', {'token':token, 'front_seed':front_seed}, function(data) {
				if(data) {
					var json = decodeJSON(data);
					ls[productName + '_seed' || 0] = ls[productName + '_seed' || 0] = Sha256.hash(front_seed + json['back_seed' || 0]);
					var sessionId = ls[productName + '_session_id' || 0] = json['session_id' || 0];
					ls[productName + '_action_ind' || 0] = 0;
					ls[productName + '_first_name' || 0] = user.firstName = json['first_name' || 0];
					ls[productName + '_last_name' || 0] = user.lastName = json['last_name' || 0];
					if(!isLocalStorage) document.cookie = productName + '_session_id=' + sessionId.replace(/%([^\d].)/, '%25$1'); // в куках храним только sessionId, которого достаточно для чтения данных пользователя.
					domSetInnerText(domGetByClass('userName'), user.firstName + ' ' + user.lastName);
					domAddCssClass(domGetByClass('toLogInBtn'), 'unactive');
					if(modalWindow.inside.firstChild === uloginPanel) { // если открыто модальное окно авторизации, то закроем его.
						syncToServerWithTimeeout();
						domSetInnerText(modalWindow.inside, 'Ok');
						windowSetTimeout(function() {
							modalWindow.close();
						}, 500);
					}
				}
			}, 'POST');
		};
		var uloginPanel;
		var uloginTextElem;
		domBindEvent(domGetByClass('toLogInBtn'), 0, function(e) {
			domCancelEvent(e);
			if(!uloginPanel) {
				uloginPanel = domCreater();
				var ulogin_script = domCreater('script');
				ulogin_script.setAttribute('src', 'https://ulogin.ru/js/ulogin.js');
				ulogin_script.setAttribute('async', 'async');
				document['head' || 0].appendChild(ulogin_script);
				uloginTextElem = domCreater('p', 0, uloginPanel);
				uloginPanel.insertAdjacentHTML('beforeend', '

');
			}
			var text = 'Авторизуйтесь при помощи сервиса, в котором уже зарегистрированны:';
			if(user.firstName || user.lastName) {
				if(ls[productName + '_session_id' || 0] && ls[productName + '_action_ind' || 0]) {
					text = 'Вы (' + user.firstName + ' ' + user.lastName + ') уже авторизованы.';
				} else {
					text = 'Вы (' + user.firstName + ' ' + user.lastName + ') были авторизованы, но сейчас подлиность авторизации утеряна, в связи с чем, Ваши изменения не будут сохраняться на сервер и синхронизироваться с другими устройствами. Авторизуйтесь заново:';
				}
			}
			domSetInnerHTML(uloginTextElem, text);
			modalWindow.createAndShowWindow(uloginPanel);
		});
	});

	user.saveSettings = function() {
		var json = encodeToJSON(settings);
		ls[productName + '_user_settings' || 0] = json;
	};

	user.setBySettingsObj = function(obj) {
		if(isString(obj)) obj = decodeJSON(obj);
		if(!obj) {
			obj = {'autoaddCW':true};
			user.autoaddCW();
			user.autoaddCW();
			user.autoaddCW();
			user.autoaddCW();
		}
		user.setAutoaddCW(obj['autoaddCW' || 0]);
	};

	user.setAutoaddCW = function(bool) {
		// CW - расшифровывается как common words.
		bool = !!bool;
		if(bool != settings.autoaddCmWds) {
			user.settings.autoaddCW = bool;
			user.trigger('cAutoaddCW', bool);
			user.saveSettings();
		}
	};

	var cWProgr = 0;
	user.setCWProgr = function(num) {
		// устанавливает, какое последнее по индексу слово было автодобавлено.
		num = parseInt(num);
		if(num != cWProgr) {
			cWProgr = num;
		}
	};
	user.getCWProgr = function() {
		return cWProgr || 0;
	};

	user.autoaddCW = function() {
		var ind = user.getCWProgr();
		var enWord, str, nod;
		if(words.commonArray) {
			for(ind++; ind < words.commonArray.length; ind++) {
				enWord = words.commonArray[ind];
				if(enWord) {
					str = enWord.getFormed();
					if(!user.words.searchByStr(str)) {
						nod = words.searchNodes(enWord)[0];
						if(nod && nod.os.length) {
							wordsTypesByNum[enWord.wordType].userCollection.addWords(nod.os);
							user.setCWProgr(ind);
							var ruStrs = [];
							for(var i = nod.os.length; i--; ) {
								if(nod.os[i] && nod.os[i].wordLang) {
									ruStrs.push(nod.os[i].getFormed());
								}
							}
							if(ind > 4) notific('Добавленно слово ' + str + ' → ' + ruStrs.join(', '));
							return;
						}
					}
				}
			}
		} else {
			words.addEventListener('load', user.autoaddCW);
		}
	};

	var clearLocalStorageSaves = function(end) {
		// Очищает из ls все поля хранившие словарь пользователя.
		if(!end) end = ls[productName + '_next_ind_save' || 0];
		var key;
		for(var i = parseInt(ls[productName + '_first_has_ind_save' || 0]); i < end; i++) {
			key = productName + '_save_' + i;
			if(ls.removeItem) {
				ls.removeItem(key);
			} else {
				ls[key] = undefined;
			}
			ls[productName + '_first_has_ind_save' || 0] = parseInt(i) + 1;
		}
	};
	var shortenLocalStorageSaves = function() {
		// Удаляет старые, ненужные сохранения в localStorage.
		var lastSnapshotInd = ls[productName + '_last_snapshot_ind_save' || 0];
		if(lastSnapshotInd) {
			clearLocalStorageSaves(lastSnapshotInd);
		}
	};
	var clearCollections = function() {
		// Делает коллекции слов пользователя пустыми.
		for(var name in wordsTypes) {
			wordsTypes[name].userCollection.clear();
		}
		clearChangesLogs();
	};
	var clearChangesLogs = function() {
		for(var name in wordsTypes) {
			if(wordsTypes[name].userCollection.changesLog) {
				wordsTypes[name].userCollection.changesLog = []; // создадим логи в нужных коллекциях.
			}
		}
	};
	var applyChanges = function(json) {
		// Выполняет переданые изменения.
		if(!json || json === 'undefined') return;
		if(isString(json)) json = decodeJSON(json);
		user.words.pauseNextTickTriggers(); // чтобы перерисовка словаря происходила только 1 раз.
		var name, item, i, n;
		for(name in wordsTypes) {
			item = wordsTypes[name];
			if(name != Word.wordTypeName) {
				if(json[name]) {
					var word;
					for(i = 0; i < json[name].length; i++) {
						if(json[name][i][0] === 'add') {
							var words = [];
							var wordsEn = json[name][i][1][0];
							var createdEnWords = [];
							for(n = 0; n < wordsEn.length; n++) {
								word = new item.constructorEn(wordsEn[n]);
								words.push(word);
								createdEnWords.push(word);
							}
							var wordsRu = json[name][i][1][1];
							var createdRuWords = [];
							for(n = 0; n < wordsRu.length; n++) {
								word = new item.constructorRu(wordsRu[n]);
								words.push(word);
								createdRuWords.push(word);
							}
							item.userCollection.addWords(words, 1, 1);
							for(n = 0; n < createdEnWords.length; n++) {
								//item.userCollection.removeOtherWordsLinks(createdEnWords[n], createdRuWords);
							}
						} else if(json[name][i][0] === 'rm') {
							word = item.userCollection.searchByStr(json[name][i][1]);
							if(word) item.userCollection.removeWord(word);
						}
					}
				}
			}
		}
		clearChangesLogs();
	};
	var setCollectionsBySnapshot = function(json) {
		// Записывает в коллекции юзера слова в соответствии с json snapshot-а.
		if(isString(json)) {
			json = decodeJSON(json);
		}
		var item, entries, i, entry, words, enWord, n, ruWord;
		user.words.pauseNextTickTriggers(); // чтобы перерисовка словаря происходила только 1 раз.
		clearCollections();
		for(var name in wordsTypes) {
			if(json[name]) {
				item = wordsTypes[name];
				entries = BTS.getMixArrForBTS(json[name]); // чтобы bts был оптимизированный.
				for(i = 0; i < entries.length; i++) {
					entry = entries[i];
					words = [];
					enWord = new item.constructorEn(entry[0]);
					if(enWord.wordLang === undefined) enWord.wordLang = 0;
					words.push(enWord);
					for(n = 0; n < entry[1].length; n++) {
						if(entry[1][n]) {
							ruWord = new item.constructorRu(entry[1][n]);
							if(ruWord.wordLang === undefined) ruWord.wordLang = 1;
							words.push(ruWord);
						}
					}
					item.userCollection.addWords(words);
					if(enWord.isEmphasis()) item.userEmphasisCollection.addWords(words);
				}
			}
		}
		clearChangesLogs();
	};
	var setCollectionsBySnapshotOrApplyChangesOrSetProgresses = function(json) {
		if(!json) return;
		if(isString(json)) {
			json = decodeJSON(json);
		}
		if(!json['typ' || 0]) {
			setCollectionsBySnapshot(json['data' || 0]);
		} else if(parseInt(json['typ' || 0]) === 1) {
			applyChanges(json['data' || 0]);
		}
		if(json['cWProgr' || 0] !== undefined) {
			user.setCWProgr(json['cWProgr' || 0]);
		}
	};
	var getSnapshotByCollections = function() {
		var name, item, snapshot = {};
		for(name in wordsTypes) {
			item = wordsTypes[name];
			if(name != Word.wordTypeName) {
				snapshot[name] = item.userCollection.getWordsArray();
			}
		}
		return encodeToJSON(snapshot);
	};
	var getChangesByCollections = function() {
		var changesLogs = {};
		var containChanges, name, item; // указывает, что changesLogs не пустой.
		for(name in wordsTypes) {
			item = wordsTypes[name];
			if(name != Word.wordTypeName && item.userCollection.changesLog && item.userCollection.changesLog.length) {
				changesLogs[name] = item.userCollection.changesLog;
				containChanges = 1;
			}
		}
		if(containChanges) {
			return changesLogs;
		}
	};

	var lastRealizedIndSaveInThisWindow; // возможно другая вкладка добавит новые сохранения в localStorage, тогда различия между этой переменной и колличеством записей означает необходимость произвести появившиеся изменения.
	var lastSyncIndSaveInThisWindow; // индекс последнего синхронизованого с сервером изменения, которое было реализовано в этом окне.

	var afterOnloadBody = function() {
		if(!ls[productName + '_last_sync_ind_save' || 0] && ls[productName + '_last_sync_ind_save' || 0] !== '0') {
			ls[productName + '_last_sync_ind_save' || 0] = -1;
		}
		var organizingLocalStorageVariables = [productName + '_first_has_ind_save', productName + '_next_ind_save', productName + '_last_snapshot_ind_save'];
		for(var i = 0; i < organizingLocalStorageVariables.length; i++) {
			if(!ls[organizingLocalStorageVariables[i]]) {
				ls[organizingLocalStorageVariables[i]] = 0;
			}
		}
		strongSetByLocalStorage();
		for(var name in wordsTypes) {
			if(name != Word.wordTypeName) {
				wordsTypes[name].userCollection.changesLog = []; // создаем логи изменений в нужных нам коллекциях.
			}
		}
		windowSetInterval(function() {
			syncToServerWithTimeeout();
		}, 60000);
		syncToServerWithTimeeout();
		windowSetInterval(function() {
			syncToLocalStorageWithTimeeout();
		}, 10000);
		window.addEventListener('storage', function(e) {
			if(e['key' || 0] === productName + '_next_ind_save') {
				syncToLocalStorage();
			}
		}, false);
	};
	addToOnloadBody(afterOnloadBody);

	var strongSetByLocalStorage = function() {
		clearCollections();
		var saves = {};
		var i = parseInt(ls[productName + '_first_has_ind_save' || 0]);
		if(ls[productName + '_last_snapshot_ind_save' || 0]) {
			i = parseInt(ls[productName + '_last_snapshot_ind_save' || 0]);
		}
		var end = ls[productName + '_next_ind_save' || 0];
		var key;
		for(i; i < end; i++) {
			key = productName + '_save_' + i;
			var json = ls[key];
			if(json) {
				json = decodeJSON(json);
				if(!json['typ' || 0]) {
					saves = {}; // если есть snapshot - предидущие изменения не нужны.
				}
				saves[i] = json;
			}
		}
		for(i in saves) {
			setCollectionsBySnapshotOrApplyChangesOrSetProgresses(saves[i]);
			if(ls[productName + '_last_sync_ind_save' || 0] && i <= ls[productName + '_last_sync_ind_save' || 0]) {
				lastSyncIndSaveInThisWindow = i;
			}
			lastRealizedIndSaveInThisWindow = i;
		}
		clearChangesLogs();
	};

	var syncToServer = function() {
		var sendedNextIndSave = parseInt(ls[productName + '_next_ind_save' || 0]);
		var sendedLastSyncSaveInd = parseInt(ls[productName + '_last_sync_ind_save' || 0]);
		var req = {'action': 'sync', 'last_sync_ind_save': sendedLastSyncSaveInd};
		var i = sendedLastSyncSaveInd + 1;
		var end = sendedNextIndSave;
		if(i < end) {
			var saves = {};
			for(i; i < end; i++) {
				if(ls[productName + '_save_' + i]) {
					saves[i] = decodeJSON(ls[productName + '_save_' + i]);
					req['saves' || 0] = saves;
				}
			}
		}
		sendByCheck(req, function(json) {
			if(json) {
				var k, ind, hasCollision, key, nextIndSave, i;
				if(json['saves' || 0]) {
					if(lastRealizedIndSaveInThisWindow >= sendedNextIndSave) {
						// TODO: лицемерно забываем о конфликтных изменениях.
					}
					var jsonSaves = {};
					for(k in json['saves' || 0]) {
						ind = parseInt(k);
						if(ind > (lastSyncIndSaveInThisWindow || 0)) {
							if((lastRealizedIndSaveInThisWindow || 0) >= ind) {
								// TODO: лицемерно затираем конфликтные сохранения.
								nextIndSave = ls[productName + '_next_ind_save' || 0];
								for(i = ind; i < nextIndSave; i++) {
									key = productName + '_save_' + i;
									if(ls.removeItem) {
										ls.removeItem(key);
									} else {
										ls[key] = undefined;
									}
								}
								hasCollision = 1;
							}
							ls[productName + '_save_' + ind] = json['saves' || 0][k];
							ls[productName + '_next_ind_save' || 0] = ind + 1;
							jsonSaves[i] = decodeJSON(json['saves' || 0][k]);
							if(!jsonSaves[i]['typ' || 0]) {
								ls[productName + '_last_snapshot_ind_save' || 0] = ind;
							}
						}
					}
					ls[productName + '_last_sync_ind_save' || 0] = ind;
					if(hasCollision) {
						if(!ls[productName + '_last_snapshot_ind_save' || 0]) {
							strongLoadByServer();
						} else {
							strongSetByLocalStorage();
						}
					} else {
						shortenLocalStorageSaves();
						var start = lastRealizedIndSaveInThisWindow || 0;
						if(ls[productName + '_first_has_ind_save' || 0] > start) start = parseInt(ls[productName + '_first_has_ind_save' || 0]);
						nextIndSave = ls[productName + '_next_ind_save' || 0];
						for(i = start; i < nextIndSave; i++) {
							setCollectionsBySnapshotOrApplyChangesOrSetProgresses(ls[productName + '_save_' + i]);
							lastRealizedIndSaveInThisWindow = i;
						}
					}
				}
				if(json['last_sync_ind_save' || 0] !== undefined) {
					ls[productName + '_last_sync_ind_save' || 0] = json['last_sync_ind_save' || 0];
				}
			}
			//shortenLocalStorageSaves();
		});
	};
	var syncToServerTimeout;
	var syncToServerWithTimeeout = function() {
		// Вызываем синхнонизацию через секунду, и не чаще чем раз в секунду.
		if(!syncToServerTimeout) {
			syncToServerTimeout = windowSetTimeout(function() {
				syncToServerTimeout = 0;
				syncToServer();
			}, 1000);
		}
	};

	var syncToLocalStorage = function() {
		// сохраняет словарь e localStorage, читает новые сохранения из localStorage.
		var nextIndSave = ls[productName + '_next_ind_save' || 0];
		var changes = getChangesByCollections();
		if(lastRealizedIndSaveInThisWindow < nextIndSave - 1) {
			strongSetByLocalStorage();
			if(changes) {
				applyChanges(changes);
			}
		}
		clearChangesLogs();
		if(changes && nextIndSave) {
			if(ls[productName + '_next_ind_save' || 0] !== nextIndSave && ls[productName + '_next_ind_save' || 0]) {
				warning();
			}
			ls[productName + '_save_' + nextIndSave] = encodeToJSON({'typ':1, 'data': changes, 'cWProgr': cWProgr});
			lastRealizedIndSaveInThisWindow = nextIndSave;
			nextIndSave++;
			ls[productName + '_next_ind_save' || 0] = nextIndSave;
		}
		if(changes && (!nextIndSave || parseInt(ls[productName + '_last_snapshot_ind_save' || 0]) + 9 < nextIndSave)) {
			shortenLocalStorageSaves();
			var snapshot = getSnapshotByCollections();
			ls[productName + '_save_' + nextIndSave] = '{"typ":0,"cWProgr": ' + cWProgr + ',"data":' + snapshot + '}';
			lastRealizedIndSaveInThisWindow = nextIndSave;
			ls[productName + '_last_snapshot_ind_save' || 0] = nextIndSave;
			nextIndSave++;
			ls[productName + '_next_ind_save' || 0] = nextIndSave;
		}
	};
	var syncToLocalStorageTimeout;
	var syncToLocalStorageWithTimeeout = function() {
		// Вызываем синхнонизацию через секунду, и не чаще чем раз в секунду.
		if(!syncToLocalStorageTimeout) {
			syncToLocalStorageTimeout = windowSetTimeout(function() {
				syncToLocalStorageTimeout = 0;
				syncToLocalStorage();
			}, 1000);
		}
	};

	var strongLoadByServer = function() {
		// TODO: ...
	};

	user.words.addEventListener('change', function() {
		syncToLocalStorageWithTimeeout();
		syncToServerWithTimeeout();
	});

	return user;
})();
var PhProtoWord = function() {
};
(function() {
	PhProtoWord.constructorsByBaseWordTypeName = {};
	PhProtoWord.constructorsByPhWordTypeName = {};
	PhProtoWord.registrPhTyp = function(PhTyp, PhProtoTyp, baseWordTyp, wordsCollection, phWordTypeName) {
		if(PhTyp) {
			if(PhProtoTyp) {
				PhTyp.prototype = new PhProtoTyp();
				PhTyp.prototype.protoClass = PhProtoTyp;
			}
			PhTyp.prototype.constructorFunc = PhTyp;
			if(baseWordTyp) {
				PhTyp.prototype.baseWordTyp = baseWordTyp;
				PhProtoWord.constructorsByBaseWordTypeName[baseWordTyp.prototype.wordTypeName] = PhTyp;
			}
			if(wordsCollection) PhTyp.prototype.wordsCollection = wordsCollection;
			if(phWordTypeName) {
				PhTyp.prototype.phWordTypeName = phWordTypeName;
				PhProtoWord.constructorsByPhWordTypeName[phWordTypeName] = PhTyp;
			}
		}
	};
	PhProtoWord.registrPhTyp(PhProtoWord, 0, Word, user.words, 'PhProtoWord');
	PhProtoWord.prototype.inListing = 0; // указываем, что это слово не включено в перечисление, то есть parent.words не используется.
	PhProtoWord.prototype.noChangeWords = 0; // специальный флаг для gen(). Указывает, что нельзя изменять уже установленные основные слова. Знаю что недальновидно, но так проще.
	PhProtoWord.prototype.noSubwords = 0; // спец флаг для gen(). Указывает, что дочерние слова не должны генерироваться. Числительные игнорируют это правило.
	PhProtoWord.prototype.construct = function(parent, dividePoint) {
		this.sid = nextSID();
		this.childs = []; // сюда добавляются ссылки на Ph-слова, находящиеся в прямом подчинении (те у которых parent указывает на это слово).
		this.setParent(parent);
		this.dividePoint = dividePoint; // место отпочкования от родной фразы/субфразы. разделение через words не является отпочкованием фразы, это просто перечисление.
		if(!dividePoint) {
			var th = this;
			while(th.parent) { // ищем ближайшую фразу среди предков.
				th = th.parent;
				if(th instanceof PhPhrase) {
					this.dividePoint = th;
					break;
				}
				if(th === this) break;
			}
		}
		this.en = {}; // хранятся переменные для английской фразы.
		this.ru = {}; // хранятся переменные для русской фразы.
		this.commonFlags = []; // сюда будем записывать общие для всех слов флаги.
	};
	PhProtoWord.prototype.destruct = function() {
		// Вычищаем все, что можно из этого объекта, удаляем максимум связей;
		for(var i = this.childs.length; i--; ) {
			this.childs[i].destruct();
		}
		this.setParent();
		this.dividePoint = this.word = this.words = this.childs = undefined;
	};
	PhProtoWord.prototype.setParent = function(parent) {
		if(this.parent !== parent) {
			if(this.parent) {
				this.parent.unregisterChild(this);
			}
			if(parent) {
				parent.registerChild(this);
			}
			this.parent = parent;
			this.setRoot(this.getRoot());
		}
	};
	PhProtoWord.prototype.registerChild = function(child) {
		if(this.childs.indexOf(child) === -1) this.childs.push(child);
	};
	PhProtoWord.prototype.unregisterChild = function(child) {
		removeFromArrayByValue(this.childs, child, 1);
	};
	PhProtoWord.prototype.setRoot = function(root) {
		if(root !== this.root) {
			if(this.root) {
				if(this.root.allPhWordsInside) removeFromArrayByValue(this.root.allPhWordsInside, this);
				if(this.root.counts) {
					if(!this.root.counts[this.phWordTypeName]) this.root.counts[this.phWordTypeName] = 0;
					if(!this.words) {
						this.root.counts[this.phWordTypeName]--;
					}
				}
				if(this.word) {
					this.removeWordFromRootArrs(this.word);
				}
			}
			if(root && root !== this) {
				if(root.allPhWordsInside) root.allPhWordsInside.push(this);
				if(root.counts) {
					if(!root.counts[this.phWordTypeName]) root.counts[this.phWordTypeName] = 0;
					if(!this.words) {
						root.counts[this.phWordTypeName]++;
					}
				}
				if(this.word && this.root.allWords) {
					this.root.allWords.push(word[0]);
					this.root.allWords.push(word[1]);
				}
			}
			this.root = root;
		}
		for(var i = this.childs.length; i--; ) {
			this.childs[i].setRoot(root);
		}
	};
	PhProtoWord.prototype.removeWordFromRootArrs = function(words) {
		if(this.root.allWords) {
			for(var i = words.length; i--; ) {
				removeFromArrayByValue(this.root.allWords, words[i], 1);
			}
		}
	};

	PhProtoWord.prototype.setBySettings = function(settings) {
		// Устанавливает некоторые переменные переданные по средствам settings.
		// Эта функция должна вызываться в самом начале всех gen() и getByFlags() !
		if(settings.noSubwords !== undefined) this.noSubwords = settings.noSubwords;
		if(settings.noChangeWords !== undefined) this.noChangeWords = settings.noChangeWords;
	};

	PhProtoWord.prototype.gen = function(settings, wordsCollection, flags, outInside) {
		// Подбирает случайные слова и обороты.
		var ret;
		this.setBySettings(settings);
		if(this.parent && this.parent.noSubwords) return;
		if(this.noChangeWords && (this.words || this.word)) {
			ret = 1;
		} else {
			var countWords = this.mathCountWords(settings);
			for(var i = 0; i < countWords; i++) {
				this.addWordPair.apply(this, pushAll([0], arguments));
			}
			if(!(this.word && this.word.length || this.words && this.words.length)) {
				return;
			}
			this.autosetCommonFlags();
			ret = 1;
		}
		this.announceSecondGens(settings);
		return ret;
	};

	PhProtoWord.prototype.addWordPair = function(pair, settings, wordsCollection, flags) {
		// добавляет в основные слова еще одно(пару англ-рус).
		if(!this.word && !this.words) {
			if(!pair) {
				pair = this.genRndPair.apply(this, pushAll([], arguments).slice(1));
			}
			if(pair) {
				this.word = pair;
				if(this.root && this.root.allWords) {
					this.root.allWords.push(pair[0]);
					this.root.allWords.push(pair[1]);
				}
			}
		} else {
			// тогда переходим к системе с вложенными подобъектами.
			var oldWordPair;
			if(this.word) {
				oldWordPair = this.word;
				var phWord = new this.constructorFunc(this, this.dividePoint);
				phWord.inListing = 1;
				phWord.word = oldWordPair;
				phWord.noChangeWords = 1;
				phWord.commonFlags = this.commonFlags;
				if(phWord.gen(settings, wordsCollection)) {
					this.commonFlags = 0;
					phWord.noChangeWords = 0;
					this.words = [phWord];
					this.word = undefined;
					this.root.counts[this.phWordTypeName]--;
				} else {
					warning(phWord);
					return;
				}
			}
			var newPhWord = new this.constructorFunc(this, this.dividePoint);
			newPhWord.inListing = 1;
			if(pair) {
				newPhWord.word = pair;
				newPhWord.autosetCommonFlags();
				newPhWord.noChangeWords = 1;
			}
			if(newPhWord.gen(settings, wordsCollection, flags)) {
				if(pair) {
					newPhWord.noChangeWords = 0;
				}
				this.words.push(newPhWord);
			}
		}
	};

	PhProtoWord.prototype.genRndPair = function(settings, wordsCollection, flags, outInside) {
		var pair;
		var wordsCollections = this.wordsCollections;
		if(wordsCollection) wordsCollections = [wordsCollection];
		var filter = this.root.allWords;
		if(this.wordsFilter) {
			var th = this;
			filter = function(word) {
				if((!th.root || th.root.allWords.indexOf(word) === -1) && th.wordsFilter(word)) {
					return 1;
				}
			};
		}
		for(var i = 0; i < wordsCollections.length; i++) {
			pair = wordsCollections[i].getRndWordsPair(filter, filter, 0, flags || settings.flagsArgs, outInside);
			if(pair) return pair;
		}
	};

	PhProtoWord.prototype.autosetCommonFlags = function() {
		// Устанавливает в переменную this.commonFlags те флаги, что сейчас актуальны.
		var words = this.getAllWords().concat();
		//for(var i = words.length; i--; ) if(words[i].isPron) words.splice(i, 1);
		this.commonFlags = this.wordsCollection.getCommonFlagsForWords(words);
	};

	PhProtoWord.prototype.genSecondGensQueue = function(settings) {
		return [];
	};
	PhProtoWord.prototype.shuffleSecondGensQueue = function(arr) {
		// Перемешиваем очередь второстепенных генераторов с учетом их рангов.
		arr.forEach(function(ar) {
			if(ar[1] === undefined) ar[1] = 0.5;
		});
		shuffleArray(arr); // перемешаем, чтобы внести элемент непредсказуемости.
		arr.sort(function(a, b) {
			return b[1] - a[1];
		}); // отсортировали, что сначала идут с найбольшим рангом.
		for(var i = 0; i < arr.length; i++) {
			var rank = arr[i][1];
			var j = randomInt((1 - rank) * (arr.length - 1) - i) + i;
			if(j > i) {
				var tmp = arr[i];
				arr[i] = arr[j];
				arr[j] = tmp;
			}
		}
	};
	PhProtoWord.prototype.announceSecondGens = function(settings) {
		if(!this.parent) {
			this.runSecondGens(settings);
		} else {
			this.root.addToSecondQueue(this);
		}
	};
	PhProtoWord.prototype.runSecondGens = function(settings, wordsCollection) {
		// Запускает в случайном порядке генерацию второстепенных подслов.
		var arr = this.genSecondGensQueue(settings);
		this.shuffleSecondGensQueue(arr);
		if(!arr) return;
		shuffleArray(arr);
		for(var i = 0; i < arr.length; i++) {
			if(arr[i] && arr[i][1]) {
				arr[i][0].call(this, settings, wordsCollection);
			}
		}
	};

	PhProtoWord.prototype.writeEn = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам на английском.
		// Пока просто записывает в переменные объекта, в финальных классах нужно конкатенировать эти обрывки и вернуть строку.
		var en = this.en;
		var i;
		en.wordsStr = '';
		if(this.word) {
			en.wordsStr = this.getFormedWordEn();
		} else if(this.words) {
			for(i = 0; i < this.words.length; i++) {
				if(en.wordsStr) {
					if(i === this.words.length - 1) {
						en.wordsStr += ' and ';
					} else {
						en.wordsStr += ', ';
					}
				}
				en.wordsStr += this.words[i].writeEn.apply(this.words[i], en.argumentsForSubwordsWrite || arguments); // надеюсь в en.argumentsForSubwordsWrite не будет ["0"]
			}
		}
		if(en.wordsStr === '-') {
			if(this.regenByWrite < 3) {
				this.regenByWrite = (this.regenByWrite || 0) + 1;
				this.gen(this.root.settings);
				this.writeEn();
			} else {
				this.root.writeError = 1;
			}
		}
	};

	PhProtoWord.prototype.writeRu = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам на русском.
		// Пока просто записывает в переменные объекта, в финальных классах нужно конкатенировать эти обрывки и вернуть строку.
		var ru = this.ru;
		var i;
		ru.wordsStr = '';
		if(this.word) {
			ru.wordsStr = this.getFormedWordRu();
		} else if(this.words) {
			for(i = 0; i < this.words.length; i++) {
				if(ru.wordsStr) {
					if(i === this.words.length - 1) {
						ru.wordsStr += ' и ';
					} else {
						ru.wordsStr += ', ';
					}
				}
				ru.wordsStr += this.words[i].writeRu.apply(this.words[i], ru.argumentsForSubwordsWrite || arguments); // надеюсь в ru.argumentsForSubwordsWrite не будет ["0"]
			}
		}
		if(ru.wordsStr === '-') {
			if(this.regenByWrite < 3) {
				this.regenByWrite = (this.regenByWrite || 0) + 1;
				this.gen(this.root.settings);
				this.writeRu();
			} else {
				this.root.writeError = 1;
			}
		}
	};

	PhProtoWord.prototype.getFormedWordRu = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		// Если у основных слов типа есть падежи и т.д. - метод нужно переопределить метод.
		return this.word[1].getFormed();
	};

	PhProtoWord.prototype.getFormedWordEn = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		// Если у основных слов типа есть падежи и т.д. - метод нужно переопределить метод.
		return this.word[0].getFormed();
	};

	PhProtoWord.prototype.getRoot = function() {
		// Возвращеет самый старший(внешний) объект (обычно phPhrase).
		var th = this;
		while(th.parent) {
			th = th.parent;
			if(th === this) return;
		}
		return th;
	};

	PhProtoWord.prototype.getAllWords = function() {
		// Возвращеет все слова одним массивом.
		if(this.word) {
			return this.word;
		}
		var ret = [];
		if(this.words) {
			for(var i = 0; i < this.words.length; i++) {
				ret.concat(this.words[i].getAllWords());
			}
		}
		return ret;
	};
	PhProtoWord.prototype.mathCountWords = function(settings) {
		// Устанавливает максимальное колличество пар Word в этом PhWord.
		if(this.noChangeWords) {
			if(this.word) {
				this.countWords = 1;
			} else {
				this.countWords = this.words.length;
			}
		}
		var rank;
		if(settings.mandateForOneWord) {
			this.countWords = 1;
		} else if(this.mandateForOneWord) { // в некоторых ситуациях край как надо установить слово - тогда используем этот флаг.
			this.countWords = 1;
		} else if(this.countWords === undefined) {
			rank = this.mathRank(settings);
			if(rank === undefined) rank = 0.5;
			if(this.root && this.root.allPhWordsInside) {
				if(this.root.allPhWordsInside.length > 3) {
					rank /= 1 + (this.root.allPhWordsInside.length - 3) / 2;
				}
				if(this.root.counts[this.phWordTypeName] > 1) { // counts обычно инкрементируется до вызова gen() .
					rank /= this.root.counts[this.phWordTypeName];
				}
			}
			if(randomBool(rank)) {
				this.countWords = 1;
			} else {
				this.countWords = 0;
			}
		}
		if(this.countWords >= 1 && (this.inListing || !randomBool(settings.sListing))) {
			this.countWords = 1;
		}
		//if(rank > 0) this.countWords = 1;
		return this.countWords;
	};
	PhProtoWord.prototype.mathRank = function(settings) {
		return 0.5;
	};
	PhProtoWord.prototype.reduceRank = function(old, plus) {
		return old * plus + plus;
	};
	PhProtoWord.prototype.flagsTest = function(str, strict, flags, retArr) {
		if(!flags) flags = this.commonFlags;
		if(flags) return this.baseWordTyp.prototype.flagsValTest(str, strict, flags, retArr);
	};
	PhProtoWord.prototype.checkboxesTest = function(str, flags) {
		if(!flags) flags = this.commonFlags;
		if(flags) return this.baseWordTyp.prototype.checkboxesValTest(str, flags);
	};
	PhProtoWord.prototype.createPh = function(constr, pair) {
		if(isArray(constr)) {
			pair = constr;
			constr = 0;
		}
		if(!constr) {
			constr = PhProtoWord.constructorsByBaseWordTypeName[pair[0].wordTypeName] || this.constructorFunc;
		}
		var ret = new constr(this, this.dividePoint);
		if(pair) {
			ret.addWordPair(pair);
			ret.autosetCommonFlags();
		}
		return ret;
	};
})();
var PhNumeral = function(parent, dividePoint) {
	this.val = 1;
	this.construct(parent, dividePoint);
};

(function() {
	PhProtoWord.registrPhTyp(PhNumeral, PhProtoWord, 0, 0, 'PhNumeral');
	PhNumeral.prototype.wordsCollections = [];

	PhNumeral.prototype.construct = function(parent, dividePoint) {
		PhProtoWord.prototype.construct.call(this, parent, dividePoint);
	};

	PhNumeral.prototype.gen = function(settings) {
		// подбирает подходящие слова и обороты.
		this.setBySettings(settings);
		if(!this.noChangeWords) {
			if(!this.mathCountWords(settings)) return;
			if(this.parent && this.parent.noSubwords) {
				this.val = 1;
			} else {
				if(this.parent && this.parent.parent && this.parent.parent.constructorFunc === PhVerb) { // иначе бывает "2 сестры едят 3 яблока" или "11 сестер едят 2 яблока" странные ситуации.
					this.val = 'many';
					if(randomInt()) {
						this.adverbs = new PhAdverb(this, this.dividePoint);
						if(!this.adverbs.gen(settings)) {
							this.adverbs = this.adverbs.destruct();
						}
					}
				} else if(this.parent.constructorFunc === PhNoun && this.parent.word && !WordWithFlags.prototype.checkboxesValTest('возможно маленькое', this.parent.commonFlags)) { // если не маленькое, то ограничимся дюжиной.
					this.val = 1 + randomInt(11);
				} else {
					var rand = randomInt(3);
					if(!rand) {
						this.val = this.genFineNumber();
					} else if(rand === 1) {
						this.val = 'many';
					} else {
						this.val = 1;
					}
				}
			}
		}
		this.skipMany = 0;
		if(this.val === 'many' && randomInt(1)) {
			this.skipMany = 1; // значит не обязательно упоминать что именно "много", достаточно окончания у существительного.
		}
		return true;
	};

	PhNumeral.fineNumbers = [14,28,33,42,56,61,87,99,100,125,294,312,404,510,683,777,816,911,1280,2160,4490,8750,9031,10090,35001,51000,66400,72800,430000,700600,1000010,1000011,6000012];

	PhNumeral.prototype.genFineNumber = function() {
		// генерирует не слишком сложный номер. большинство повторяются, это должно помогать запоминанию.
		var why = randomInt(PhNumeral.fineNumbers.length * 5 + 89);
		if(why < 90) {
			why = '' + (why + 10);
			why = [why[0], why[1], '0', '0', '0'];
			why = shuffleArray(why);
			why = parseInt(why.join(''));
		} else {
			why = PhNumeral.fineNumbers[why % PhNumeral.fineNumbers.length];
		}
		return why;
	};

	PhNumeral.prototype.writeEn = function(many, ordinal) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		if(this.val === 'many' || many && (!this.parent || this.parent.adjectives !== this)) {
			if(this.adverbs) {
				return this.adverbs.writeEn();
			} else {
				if(this.skipMany) {
					return '';
				} else {
					return 'many';
				}
			}
		}
		if(this.val === 1 && (!this.parent || !this.parent.flagsTest('время'))) return '';
		var ret;
		if(this.parent && this.parent.adjectives === this) {
			ordinal = 1;
		}
		if(this.val < 20) {
			if(ordinal && baseWords.ordinalSpecPairs[this.val]) {
				return baseWords.ordinalSpecPairs[this.val][0].getFormed();
			} else {
				ret = baseWords.numeral20Pairs[this.val][0].getFormed();
			}
			if(ordinal) ret += 'th';
			return ret;
		} else {
			var val = this.val;
			var okOrdinal;
			var simple = 0;
			var teens = 0;
			var hundreds = 0;
			var thousands = 0;
			var millions = 0;
			simple = val % 10;
			val -= simple;
			teens = val % 100;
			val -= teens;
			hundreds = val % 1000;
			val -= hundreds;
			thousands = val % 1000000;
			val -= thousands;
			millions = val / 1000000;
			thousands /= 1000;
			hundreds /= 100;
			teens /= 10;
			if(teens === 1) {
				teens -= 1;
				simple += 10;
			}
			ret = '';
			var simpleStr = '';
			if(simple) {
				if(ordinal && baseWords.ordinalSpecPairs[simple]) {
					simpleStr = baseWords.ordinalSpecPairs[simple][0].getFormed();
					okOrdinal = 1;
				} else {
					simpleStr = baseWords.numeral20Pairs[simple][0].getFormed();
				}
			}
			ret = simpleStr;
			var teensStr = '';
			if(teens > 1) {
				teensStr = baseWords.numeralTeensPairs[teens - 2][0].getFormed();
				if(ret) teensStr += '-';
			}
			ret = teensStr + ret;
			if(ret && hundreds || thousands || millions) {
				ret = joins('and', ret);
			}
			var hundredsStr = '';
			if(hundreds) {
				hundredsStr = joins((hundreds > 1) ? baseWords.numeral20Pairs[hundreds][0].getFormed() : 0, baseWords.numeralDegreesPairs[1][0].getFormed());
			}
			ret = joins(hundredsStr, ret);
			var thousandsStr = '';
			if(thousands) {
				thousandsStr = joins((thousands > 1) ? baseWords.numeral20Pairs[thousands][0].getFormed() : 0, baseWords.numeralDegreesPairs[2][0].getFormed());
			}
			ret = joins(thousandsStr, ret);
			var millionsStr = '';
			if(millions) {
				millionsStr = joins((millions > 1) ? baseWords.numeral20Pairs[millions][0].getFormed() : 0, baseWords.numeralDegreesPairs[3][0].getFormed());
			}
			ret = joins(millions, ret);
			if(ordinal && !okOrdinal) ret += 'th';
			return ret;
		}
		return this.val;
	};

	var manyPadejs = ['многие', 'многих', 'многим', 'многие', 'многими', 'многих'];
	PhNumeral.prototype.writeRu = function(padej, many) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		if(this.val === 1 && (!this.parent || !this.parent.flagsTest('время'))) return '';
		if(this.val === 'many') {
			if(this.adverbs) {
				return this.adverbs.writeRu();
			} else {
				if(this.skipMany) {
					return '';
				} else if(padej === 3) {
					return manyPadejs[1];
				}
			}
		}
		if(this.val === 'many' || (many && !this.val && this.val !== 0)) return manyPadejs[padej || 0];
		if(this.parent && this.parent.adjectives === this) {
			var ends = ['й', 'го', 'му', 'го', 'м', 'м'];
			return this.val + '-' + ends[many || 0]; // во второй параметр, котоный "many" передается падеж.
		}
		return fineNumber(this.val);
	};

	var fineNumber = function(val) {
		// Добавляет пробелы, разделяющие порядки кратные 3.
		val = val + '';
		var i, ret = '';
		for(i = val.length; i > 0; i -= 3) {
			ret = val.substring((i - 3 > 0) ? i - 3 : 0, i) + ret;
			if(i > 3) ret = '\u200A' + ret; // тонкий пробел.
		}
		return ret;
	};

	PhProtoWord.prototype.mathRank = function(settings) {
		return settings.sPhNumeral;
	};

})();
var PhNoun = function(parent, dividePoint) {
	// Вообще, по логике, обычное PhNoun должно быть внутри PhVerb или наоборот, но тогда сложно будет манипулировать ими из PhPhrase. По этому PhNoun может как и PhVerb обладать значительной независимостью.
	this.construct(parent, dividePoint);
};

(function() {

	PhProtoWord.registrPhTyp(PhNoun, PhProtoWord, Noun, user.nouns, 'PhNoun');
	PhNoun.prototype.emphasisWordsCollection = user.emphasis.nouns;
	PhNoun.prototype.wordsCollections = [PhNoun.prototype.emphasisWordsCollection, PhNoun.prototype.wordsCollection];

	PhNoun.prototype.gen = function(settings, wordsCollection, flags) {
		// Подбирает случайные слова и обороты
		this.setBySettings(settings);
		if(!this.mathCountWords(settings) || this.parent && this.parent.noSubwords) return;
		if(!wordsCollection) wordsCollection = this.wordsCollection;
		var ofPronBool;
		if(this.parent && this.parent.of === this) {
			ofPronBool = !randomBool(settings.sOfPron);
		}
		if(randomBool(settings.sPron) && !ofPronBool) {
			wordsCollection = pronsAsNounsCollection;
		}
		var ret, i, fgs;
		if(this.parent && this.parent.of === this) {
			fgs = [];
			var piecesFlags = [];
			pushAll(piecesFlags, this.parent.flagsTest('часть абстрактная', 0, 0, []));
			pushAll(piecesFlags, this.parent.flagsTest('часть материальная', 0, 0, []));
			pushAll(fgs, Noun.prototype.toTildaFlags(piecesFlags));
			ret = PhProtoWord.prototype.gen.call(this, settings, wordsCollection, fgs); // в первую очередь ищем по явному указанию в флагах, частью чего это является.
			if(!ret) {
				var hummansObjs = ['родня', 'механизм', 'строение', 'здание', 'помещение', 'емкость', 'еда', 'твердое вещество', 'жидкость', 'предмет', 'мебель', 'событие', 'действие', 'эмоция', 'чувство', 'организация', 'разговорная'];
				for(i = 0; i < hummansObjs.length; i++) {
					if(this.flagsTest(hummansObjs[i])) {
						ret = PhProtoWord.prototype.gen.call(this, settings, wordsCollection, Noun.prototype.genFlags('человек')); // человеку может принадлежать много чего.
						break;
					}
				}
				if(!ret) {
					this.word = pronsAsNounsPairs[6];
				}
			}
		} else if(this.dividePoint && this.dividePoint.nouns === this && this.dividePoint.verbs) { // если в фразе уже есть verbs, то подбирать noun нужно в соответствии с его флагами.
			fgs = (flags || []).concat(this.dividePoint.verbs.commonFlags['who' || 0]).map(function(f) {return [f[0], f[1], f[2]];});
			if(this.dividePoint.verbs.verbFlagsTest('перемещаться')) {
				fgs = fgs.concat(Noun.prototype.genFlags('материальное', ['возможно большое'], 1));
				fgs = fgs.concat(Noun.prototype.genFlags('место', [], 1));
				fgs = fgs.concat(Noun.prototype.genFlags('путь', [], 1));
				fgs = fgs.concat(Noun.prototype.genFlags('селение', [], 1));
				fgs = fgs.concat(Noun.prototype.genFlags('помещение', [], 1));
			}
			ret = PhProtoWord.prototype.gen.call(this, settings, wordsCollection, fgs); // || 0 чтобы jshint не гнал.
		} else if(this.parent && this.parent.stage && this.parent.stage.than === this) {
			if(this.parent.constructorFunc === PhAdjective) {
				if(this.parent.parent.constructorFunc === PhNoun) {
					fgs = this.parent.parent.commonFlags;
					for(i = 0; i < fgs.length; i++) {
						ret = PhProtoWord.prototype.gen.call(this, settings, wordsCollection, [fgs[i]]);
						if(ret) break;
					}
				}
			} else if(this.parent instanceof PhProtoVerb) {
				fgs = this.parent.commonFlags['who' || 0];
				for(i = 0; i < fgs.length; i++) {
					ret = PhProtoWord.prototype.gen.call(this, settings, wordsCollection, [fgs[i]]);
					if(ret) break;
				}
			}
		} else {
			ret = PhProtoWord.prototype.gen.call(this, settings, wordsCollection, flags);
		}
		if(!ret && !ofPronBool && settings.sPron) { // значит не удалось найти подходящее существительное.
			// вместо существительного подставляем местоимение(они всегда подходят), вот и все дела.
			if(!fgs && flags) fgs = flags.map(function(f) {return [f[0], f[1], f[2]];});
			var pronFlags;
			if(!this.noChangeWords) {
				if(Noun.prototype.flagsValTest('одушевленное', 0, fgs)) {
					pronFlags = pushAll(Noun.prototype.genFlags('человек'), Noun.prototype.genFlags('группа')); // иначе будет "Я позвал это".
				} else {
					pronFlags = pushAll(Noun.prototype.genFlags('материальное'), Noun.prototype.genFlags('одушевленное', 0, 1)); // иначе будет "Я говорю собаку".
				}
			}
			ret = PhProtoWord.prototype.gen.apply(this, [settings, pronsAsNounsCollection, pronFlags]); // TODO: не любое местоимение подходит для любого случая, например "он беременный". Желательно отсеивать такие каверзы.
		}
		return ret;
	};

	PhNoun.prototype.genSecondGensQueue = function(settings) {
		var ret = PhProtoWord.prototype.genSecondGensQueue.call(this, settings);
		ret.push([this.genWhy, settings.sWhy]);
		ret.push([this.genParticiples, settings.sPhParticiple]);
		ret.push([this.genAttributiveClause, settings.sPhAttributiveClause]);
		ret.push([this.genAdjectives, settings.sPhAdjective]);
		ret.push([this.genOf, settings.sOf]);
		return ret;
	};

	PhNoun.prototype.genWhy = function(settings) {
		if(this.why) this.why = this.why.destruct();
		if(this.word && !this.word[0].isPron) { // возможно упущение, что не проверяются words.
			var isWhyParent = false;
			var th = this.parent;
			if(th) {
				while(th.parent) {
					if(th.why) isWhyParent = true;
					th = th.parent;
					if(th === this) break; // на всякий случай, если структура замкнется.
				}
				if(!isWhyParent) { // потому что 99 he это бред, he, she, i, you, it во множественном числе являются другими местоимениями, а we, they используются только во множественном.
					this.why = new PhNumeral(this, this.dividePoint);
					if(this.many) {
						this.why.val = 'many';
						this.why.noChangeWords = 1; // чтобы val не перегенерировалось.
					}
					if(!this.why.gen(settings)) {
						this.why = this.why.destruct();
					}
					if(this.many && this.why) {
						this.why.noChangeWords = 0; // чтобы потом val могло перегенерироваться.
					}
				}
			}
		}
	};

	PhNoun.prototype.genParticiples = function(settings) {
		if(this.participles) this.participles = this.participles.destruct();
		if(this.word && !this.word[0].isPron) {
			if(this.parent === this.dividePoint && this.parent.constructorFunc !== this.constructorFunc) { // прикрепляем причастие только к основному существительному, иначе чаще возникают сложные противоречия с глаголом.
				this.participles = new PhParticiple(this, this.dividePoint);
				if(!this.participles.gen(settings)) { // возможны противоречия типа "идущий человек стоит"
					this.participles = this.participles.destruct();
				}
			}
		}
	};

	PhNoun.prototype.genAttributiveClause = function(settings) {
		var ret;
		if(this.attributiveClause) this.attributiveClause = this.attributiveClause.destruct();
		if(this.word && !this.word[0].isPron) {
			if(this.parent === this.dividePoint && this.parent.constructorFunc !== this.constructorFunc) { // прикрепляем придаточные предложения только к основному существительному, иначе чаще возникают сложные противоречия с глаголом.
				this.attributiveClause = new PhAttributiveClause(this, this.dividePoint);
				ret = this.attributiveClause.gen(settings);
				if(!ret) { // возможны противоречия типа "идущий человек стоит"
					this.attributiveClause = this.attributiveClause.destruct();
				}
			}
		}
		return ret;
	};

	PhNoun.prototype.genAdjectives = function(settings) {
		if(this.adjectives) this.adjectives = this.adjectives.destruct();
		if(this.word && !this.word[0].isPron) {
			//if(this.parent === this.dividePoint && this.parent.constructorFunc !== this.constructorFunc) { // прикрепляем причастие только к основному существительному, иначе чаще возникают сложные противоречия с глаголом. TODO: тупая копипаста, вероятно лишнее.
				this.adjectives = new PhAdjective(this, this.dividePoint);
				if(!this.adjectives.gen(settings)) { // возможны противоречия типа "идущий человек стоит"
					this.adjectives = this.adjectives.destruct();
				}
			//}
		}
	};

	PhNoun.prototype.genOf = function(settings) {
		if(this.of) this.of = this.of.destruct();
		if((!this.parent || !this.parent.of) && (!this.word || !this.word[0].isPron)) {
			this.of = new PhNoun(this, this.dividePoint);
			if(!this.of.gen(settings)) {
				this.of = this.of.destruct();
			}
		}
	};

	PhNoun.prototype.writeEn = function(many) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var en = this.en; // Хранятся переменные для русской фразы.
		writeCommon(this, en, many);
		if(this.why) {
			en.whyStr = this.why.writeEn(many);
			if(this.why.val !== 1) en.many = true;
		}
		if(this.whyIn) {
			en.whyInStr = this.whyIn.writeEn(many);
		}
		if(this.word) {
			en.gender = this.word[0].gender;
		}
		if(en.many) en.gender = 3;
		PhProtoWord.prototype.writeEn.apply(this, arguments);
		if(this.point) {
			en.pointStr = this.point.writeEn(many);
		}
		if(this.adjectives) {
			en.adjectievsStr = this.adjectives.writeEn(en.gender);
		}
		if(this.participles) {
			en.participlesStr = this.participles.writeEn();
		}
		if(this.attributiveClause) {
			en.attributiveClauseStr = this.attributiveClause.writeEn();
		}
		// установим en.pron
		if(this.word[0].isPron) {
			en.pron = this.word[0].pronInd;
		} else {
			en.pron = genderToProns[en.gender];
		}

		en.ofAnimateStr = ''; // of для одушевленных используется без предлога of, в притяжательном склонении и в другом месте.
		if(this.of) {
			en.ofAnimateStr = this.of.writeEn();
			if(!this.of.word || en.attributiveClauseStr || this.of.word[0].pronInd === 6 || !this.of.word[0].flagsValTest('одушевленное') && !this.of.word[0].flagsValTest('одушевленных~одушевленное')) {
				en.ofStr = 'of ' + en.ofAnimateStr;
				en.ofAnimateStr = '';
			}
		}

		var adjectieve_participle_word = joins([en.adjectievsStr, ',', en.participlesStr], en.wordsStr);

		en.articleStr = '';
		if(!en.ofAnimateStr) {
			if(!en.many && this.word && !this.word[0].isPron) {
				if(this.why && this.why.val > 1 || en.whyStr || this.adjectives && this.adjectives.constructorFunc === PhNumeral && this.adjectives.val) {
				} else if(this.adjectives && this.adjectives.word && ['little', 'few', 'some'].indexOf(this.adjectives.word[0].getFormed()) !== -1) {
				} else if(this.word && this.word[0].gender === 3) {
				//} else if(this.adjectives && this.adjectives.constructorFunc === PhNumeral) { // Когда при имени имеются количественные числительные в роли описательных определений, например: страница шестьдесят семь - page sixty-seven.
				} else if(/*this.flagsTest('абстрактное') || */this.flagsTest('твердое вещество') || this.flagsTest('жидкость') || this.flagsTest('газ')/* || this.flagsTest('излучение')*/) {
				} else if(this.flagsTest('средство передвижения')) {
				} else {
					en.articleStr = 'a';
					if(en.articleStr === 'a' && enLang.vowel.indexOf(adjectieve_participle_word[0]) !== -1) {
						en.articleStr += 'n';
					}
				}
			}
		}
		if(this.adjectives && (this.adjectives.stage && this.adjectives.stage.comparative && this.adjectives.stage.comparative === 2 || this.adjectives.constructorFunc === PhNumeral || this.adjectives.checkboxesTest('индивидуализирующее')) || this.word && this.word[0].getFormed().substr(0, 4) === 'the ') {
			if(!this.flagsTest('календарное понятие')) {
				en.articleStr = 'the';
			}
		}
		if(!en.wordsStr) en.articleStr = '';
		var ret = joins(en.ofAnimateStr, en.whyStr, en.pointStr, en.articleStr, adjectieve_participle_word, en.attributiveClauseStr, en.whyInStr, en.ofStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhProtoWord.prototype.getFormedWordEn = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		var ret;
		if(this.parent && this.parent.of === this) {
			ret = this.word[0].getFormedOf();
		} else if(this.parent && this.parent.whom === this) {
			ret = this.word[0].getFormedWhom();
		} else {
			ret = this.word[0].getFormed(this.en.many ? 1 : 0);
		}
		if(ret.substr(0, 4) === 'the ') ret = ret.substr(4);
		return ret;
	};

	var writeCommon = function(th, wr, many) {
		if(many !== undefined) wr.many = many;
		wr.gender = 0;
		wr.whyStr = '';
		wr.pointStr = '';
		wr.adjectievsStr = '';
		wr.participlesStr = '';
		wr.ofStr = '';
	};

	PhNoun.prototype.writeRu = function(padej, many) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var ru = this.ru; // Хранятся переменные для русской фразы.
		writeCommon(this, ru, many);
		ru.padej = padej || 0;
		ru.gender = 0;
		var i;
		if(this.word) {
			ru.wordGender = this.word[1].gender;
		}
		if((this.why && this.why.val !== 1) || (ru.many && !this.why) || (!this.word && this.words && this.words.length > 1)) {
			ru.gender = 3;
			ru.many = true;
		} else {
			if(ru.wordGender) {
				ru.gender = ru.wordGender;
			}
		}
		if(this.why) {
			var why = this.why.val;
			if(why === 1) {
				ru.many = false;
			} else if(why === 'many') {
				if(ru.padej === 3 && !this.word[1].flagsValTest('одушевленное')) {
					ru.padej = 0; // люблю многие яблоки.
				}
			} else {
				ru.many = true;
				if(why > 1) {
					ru.many = true;
					if(why % 100 > 4 && why % 100 < 21) {
						if(ru.padej === 0 || padej === 3) { // 5 палок, 14 палок, 25 палок, 999 палок.
							ru.padej = 1;
						}
					} else if(why % 10 === 1) { // 1 палка, 21 палка.
						ru.many = false;
						ru.gender = ru.wordGender;
						//ru.padej = 0;
					} else if(why % 10 > 1 && why % 10 < 5) {
						if((ru.padej === 0 || ru.padej === 3) && ru.wordGender !== 1) { // 44 палки || 44 енотам.
							ru.padej = 1;
							ru.many = false;
						}
					} else if(ru.padej === 0 || ru.padej === 3) { // 7 палок,111 палок, 19 палок; 30 палок, 35 палок.
						ru.padej = 1;
					}
				}
			}
			ru.whyStr = this.why.writeRu(ru.padej, ru.many);
		}
		if(this.whyIn) {
			ru.whyInStr = this.whyIn.writeRu(many);
		}
		if(this.word && this.word[1] && ru.padej === 3 && ru.wordGender === 0) {
			if(!this.word[1].flagsValTest('одушевленное')) { // случай когда "винить большого зятя"(одуш) и "винить большой гриб"(неодуш) пишутся по разному.
				ru.padej = 0;
			}
		}
		if(this.why && this.why.adverbs) {
			ru.padej = 1;
		}
		if(this.parent && this.parent.of === this) {
			ru.padej = 1;
		}
		PhProtoWord.prototype.writeRu.apply(this, arguments);
		if(this.point) {
			ru.pointStr = this.point.writeRu(ru.gender ,padej);
		}
		if(this.adjectives) {
			ru.adjectievsStr = this.adjectives.writeRu(ru.gender, ru.padej);
		}
		if(this.participles) {
			ru.participlesStr = this.participles.writeRu(0, 0, ru.gender, ru.padej);
		}
		if(this.attributiveClause) {
			ru.attributiveClauseStr = this.attributiveClause.writeRu();
			if(ru.attributiveClauseStr) {
				ru.attributiveClauseStr = ', ' + ru.attributiveClauseStr + ' ,';
			}
		}
		// установим en.pron
		ru.pron = genderToProns[ru.gender];
		if(this.word && this.word[1].isPron) {
			ru.pron = this.word[1].pronInd;
			if(ru.many) { // В случае множественного - ru.pron практически заменяется.
				if(ru.pron === 0) {
					ru.pron = 1;
				} else if(ru.pron > 3) {
					ru.pron = 3;
				}
			}
		}
		ru.ofPronStr = '';
		if(this.of) {
			if(this.of.word && this.of.word[1].isPron && this.of.word[1].pronInd < 6) {
				ru.ofPronStr = this.of.writeRu(ru.padej, many);
			} else {
				ru.ofStr = this.of.writeRu(ru.padej, many);
			}
		}
		var ret = joins(ru.whyStr, ru.pointStr, ru.ofPronStr, [ru.adjectievsStr, ',', ru.participlesStr], ru.whyInStr, ru.wordsStr, ru.attributiveClauseStr, ru.ofStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhNoun.prototype.getFormedWordRu = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		if((this.word === baseNouns.timeUnitsPairs[5] || this.word === baseNouns.timeUnitsPairs[1]) && this.ru.padej === 5 && this.parent && this.parent.constructorFunc === PhTime) {// в этом году, часу. иначе в этом годе.
			return this.word[1].getFormed(2, this.ru.many || 0);
		}
		/*if(this.parent && this.parent.of === this) {
			return this.word[1].getFormedOf(1, this.ru.gender);
		} else */if(this.parent && this.parent.whom === this) {
			return this.word[1].getFormedWhom(this.ru.padej, this.ru.many);
		}
		return this.word[1].getFormed(this.ru.padej || 0, this.ru.many || 0);
	};

	PhNoun.prototype.mathCountWords = function(settings) {
		var ret = PhProtoWord.prototype.mathCountWords.call(this, settings);
		return ret;
	};

	PhNoun.prototype.mathRank = function(settings) {
		var ret = settings.sPhNoun;
		if(this.parent) {
			if(this.parent.of === this) {
				ret = this.reduceRank(ret, settings.sOf);
			} else if(this.parent.stage && this.parent.stage.than === this) {
				ret = this.reduceRank(ret, settings.sThan);
			}
		}
		return ret;
	};

})();
var PhProtoStage = function(parent, dividePoint) {
};

(function() {
	PhProtoWord.registrPhTyp(PhProtoStage, PhProtoWord, 0, 0, 'PhProtoStage');

	PhProtoStage.prototype.gen = function(settings, wordsCollection) {
		var ret = PhProtoWord.prototype.gen.apply(this, arguments);
		if(ret) {
			this.genStage(settings);
		}
		return ret;
	};

	PhProtoStage.prototype.genStage = function(settings) {
		if(this.stage && this.stage.than) this.stage.than = this.stage.than.destruct();
		this.stage = {};
		if(this.words || !randomBool(settings.sStage)) {
			return;
		}
		var rand = randomInt(veryVals.length + 2);
		var th = this.parent;
		while(th) {
			if(!rand && th.stage && th.stage.no || th.stage && th.stage.very || th.formTyp === 2) {
				return; // чтобы небыло "я не не очень умный", "я немного сильно смешно прыгаю", "я не почти любил".
			}
			if(th === this.dividePoint) break;
			th = th.parent;
		}
		if(rand) {
			if(rand < veryVals.length) {
				this.stage.very = rand - 3;
			}
		} else {
			this.stage.no = 1;
		}
		if(settings.noVery) this.stage.very = 0;
		this.stage.comparative = 0;
		if(!settings.noComparative && this.checkboxesTest('more/least')) {
			this.stage.comparative = randomInt(comparativeVals.length - 1) - 2;
			if(this.parent && this.parent.constructorFunc === PhNoun && (this.stage.comparative === -1 || this.stage.comparative === 1)) {
				this.stage.than = new PhNoun(this, this.dividePoint);
				if(!this.stage.than.gen(settings)) {
					this.stage.than = this.stage.than.destruct();
				}
			}
		}
		if(this.stage.comparative === -2 || this.stage.comparative === 2 || this.stage.comparative === 1 && this.stage.very === 1) this.stage.very = 0; // чтобы не было "сильно сильнее", "немного наименее", ...
		if(!this.stage.very && !this.stage.no && !this.stage.comparative) {
			this.stage = undefined;
		}
	};

	var veryVals = [['almost', 'почти'], ['little', 'немного'], ['', ''], ['strong', 'сильно'], ['too', 'слишком']];

	var comparativeVals = [['least', 'наименее'], ['less', 'слабее'], ['', ''], ['more', 'сильнее'], ['most', 'наиболее']];

	PhProtoStage.prototype.writeEn = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		// Пока просто записывает в переменные объекта, в финальных классах нужно конкатенировать эти обрывки и вернуть строку.
		var en = this.en; // Хранятся переменные для русской фразы
		writeCommon(this, en);
		if(this.stage && this.stage.no) {
			en.noStr = 'not';
		}
		if(en.very) {
			en.veryStr = veryVals[en.very + 2][0];
		}
		PhProtoWord.prototype.writeEn.apply(this, arguments);
		if(en.comparative) en.comparativeStr = comparativeVals[en.very + 2][0];
		if((en.comparative === 1 || en.comparative === -1) && this.stage.than) {
			en.thanStr = 'than ' + this.stage.than.writeEn(0);
			console.log('-----------------------------------------------------------------------------------');
			console.log('-----------------------------------------------------------------------------------');
			console.log('-----------------------------------------------------------------------------------');
			console.log('-----------------------------------------------------------------------------------');
		}
		/*en.the = '';
		if(this.stage && this.stage.comparative && this.stage.comparative === 2) {
			en.the = 'the';
		}*/
		en.comparativeStr = comparativeVals[en.comparative + 2][0];
	};

	var writeCommon = function(th, wr) {
		wr.noStr = '';
		wr.very = 0;
		wr.veryStr = '';
		if(th.stage && th.stage.very) wr.very = th.stage.very;
		if(th.stage && th.stage.no) wr.very = 0;
		wr.thanStr = '';
		wr.comparative = 0;
		wr.comparativeStr = '';
		if(th.stage && th.stage.comparative) wr.comparative = th.stage.comparative;
		if(th.stage && th.stage.no) wr.comparative = 0;
		if((wr.comparative === 2 || wr.comparative === -2) && wr.very !== -2) {
			wr.very = 0;
		}
	};

	PhProtoStage.prototype.writeRu = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		// Пока просто записывает в переменные объекта, в финальных классах нужно конкатенировать эти обрывки и вернуть строку.
		var ru = this.ru; // Хранятся переменные для русской фразы
		writeCommon(this, ru);
		if(this.stage && this.stage.no) {
			ru.noStr = 'не';
		}
		if(ru.very) {
			ru.veryStr = veryVals[ru.very + 2][1];
		}
		PhProtoWord.prototype.writeRu.apply(this, arguments);
		if(ru.comparative) ru.comparativeStr = comparativeVals[ru.very + 2][1];
		if((ru.comparative === 1 || ru.comparative === -1) && this.stage.than) {
			ru.thanStr = 'чем ' + this.stage.than.writeRu(0);
		}
		ru.comparativeStr = comparativeVals[ru.comparative + 2][1];
	};

})();
var PhProtoVerb = function() {
};

(function() {

	PhProtoWord.registrPhTyp(PhProtoVerb, PhProtoStage, Verb, user.verbs, 'PhProtoVerb');
	PhProtoVerb.prototype.emphasisWordsCollection = user.emphasis.verbs;
	PhProtoVerb.prototype.wordsCollections = [PhProtoVerb.prototype.emphasisWordsCollection, PhProtoVerb.prototype.wordsCollection];
	PhProtoVerb.prototype.construct = function(parent, dividePoint) {
		PhProtoStage.prototype.construct.call(this, parent, dividePoint);
		this.commonFlags = {}; // сюда будем записывать общие для всех слов флаги, а у глаголов флаги делятся на несколько предназначений, поэтому будем записывать в подобъекты.
	};

	PhProtoVerb.prototype.gen = function(settings, wordsCollection, flags, outInside, firstBites) {
		// подбирает подходящие для map слова и обороты.
		this.setBySettings(settings);
		if(!this.mathCountWords(settings) || this.parent && this.parent.noSubwords) return;
		var ok, pair, ret, i = 0;

		var currentNoun = this.getCurrentNoun();
		for(i = 0; i < 3; i++) {
			if(currentNoun && !this.noChangeWords) {
				ret = PhProtoStage.prototype.gen.call(this, settings, wordsCollection, currentNoun.commonFlags, true, Verb.bitesByFlagsGroupName['who' || 0]);
				if(ret) {
					if(this.verbFlagsTest('перемещаться')) {
						var moveFiltrs = ['место', 'путь', 'селение', 'помещение'];
						for(var n = 0; n < moveFiltrs.length; n++) {
							if(currentNoun.flagsTest(moveFiltrs[n]) && !this.flagsTest(moveFiltrs[n])) {
								return; // просто отбрасываем, если получается, например, "место падало";
							}
						}
					}
				}
			} else {
				ret = PhProtoStage.prototype.gen.apply(this, arguments);
			}
			if(ret) break;
		}
		if(ret) {
			if(this.adverbs || this.verbFlagsTest('активное действие') || this.commonFlags['...делать' || 0].length) {
				this.stage = 0;
			}
		}
		if(this.wordsForRemoveFromRootAllWords) {
			for(i = 0; i < this.wordsForRemoveFromRootAllWords.length; i++) {
				removeFromArrayByValue(this.root.allPhWordsInside, this.wordsForRemoveFromRootAllWords[i], 1);
			}
		}
		if(this.checkboxesTest('обязательно')) {
			ret = 0;
		}
		return ret;
	};

	PhProtoVerb.prototype.genRndPair = function(settings, wordsCollection, flags, outInside, firstBites, testFunc) {
		//flags = flags.map(function(a) {return [(firstBites || '') + a[0], a[1], a[2]]})
		var pair;
		var wordsCollections = this.wordsCollections;
		if(wordsCollection) wordsCollections = [wordsCollection];
		var root = this.root;
		var th = this;
		var filtr = function(word, currentFlag) {
			// проверяет, имеется ли у слова подходящий флаг в подкатегории who.
			if((!root || root.allWords.indexOf(word) === -1) && (!th.wordsFilter || th.wordsFilter(word))) {
				if(word.wordLang && th.dividePoint && th.dividePoint.timeTyp === 1 && word.verbCheckboxesValTest('совершенное')) {
					return; // соврешенные глаголы нельзя использовать в настоящем времени.
				}
				var aFlags = word.getApportionedFlags();
				if(flags) {
					if(aFlags) {
						for(var i = 0; i < aFlags.length; i++) {
							if(aFlags[i][3] === firstBites && !aFlags[i][2]) { // aFlags[i][2] означает, что этот флаг является фильтрующим.
								for(var n = 0; n < flags.length; n++) {
									if(aFlags[i][0] === flags[n][0].substr(0, aFlags[i][0].length)) {
										// Не знаю как проверить вхождение нужных checkboxов.
										return 1;
									}
								}
							}
						}
					}
				} else {
					return 1;
				}
			}
		};
		var i;
		for(i = 1; i < wordsCollections.length; i++) { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Не 1 а 0
			//if(flags) {
				pair = wordsCollections[i].getRndWordsPair(filtr, filtr, wordsCollections[i].treeFlagsBts, flags, outInside);
			/*} else {
				var allWords;
				if(root) allWords = root.allWords;
				pair = wordsCollections[i].getRndWordsPair(allWords, allWords, wordsCollections[i].treeFlagsBts, flags, outInside);
			}*/
			if(pair) return pair;
		}
	};

	PhProtoVerb.prototype.genSecondGensQueue = function(settings, wordsCollection) {
		var ret = PhProtoStage.prototype.genSecondGensQueue.call(this, settings);
		ret.push([this.genWhatDo, settings.sWhatDo]);
		ret.push([this.genWhom, settings.sWhom]);
		ret.push([this.genUsingWhat, settings.sUsingWhat]);
		ret.push([this.genAdverbs, settings.sPhAdverb]);
		//ret.push([this.genPlace, settings.sPhPlace]);
		ret.push([this.genWhen, settings.sPhTime]);
		return ret;
	};

	PhProtoVerb.prototype.genWhom = function(settings) {
		var ret;
		if(this.whom) this.whom = this.whom.destruct();
		if(!this.whatDo && this.commonFlags.whom && this.commonFlags.whom.length) {
			var fgs = this.commonFlags.whom;
			if(this.verbFlagsTest('перемещать')) {
				fgs = fgs.concat(Noun.prototype.genFlags('материальное', ['возможно большое'], 1));
			}
			this.whom = new PhNoun(this, this.dividePoint);
			this.whom.mandateForOneWord = 1;
			ret = this.whom.gen(settings, 0, fgs);
			if(!ret) {
				this.whom = this.whom.destruct();
			}
		}
		return ret;
	};

	PhProtoVerb.prototype.genUsingWhat = function(settings) {
		var ret;
		if(this.usingWhat) this.usingWhat = this.usingWhat.destruct();
		if(!this.whatDo && this.commonFlags.usingWhat && this.commonFlags.usingWhat.length) {
			var fgs = this.commonFlags.usingWhat;
			this.usingWhat = new PhNoun(this, this.dividePoint);
			this.usingWhat.mandateForOneWord = 1;
			ret = this.usingWhat.gen(settings, 0, fgs);
			if(!ret) {
				this.usingWhat = this.usingWhat.destruct();
			}
		}
		return ret;
	};

	PhProtoVerb.prototype.genWhatDo = function(settings, wordsCollection) {
		if(this.whatDo) this.whatDo = this.whatDo.destruct();
		var ret;
		if(this.commonFlags['...делать' || 0].length) {
			this.whatDo = new PhVerb(this, this.dividePoint);
			var currentNoun = this.getCurrentNoun();
			var nounsGenThere;
			if(!currentNoun) { // если нет целевого существительного - попросим сгенерировать его внеочереди.
				if(this.dividePoint && this.dividePoint.verbs && (this.dividePoint.verbs.word || this.dividePoint.verbs.words)) { // если phrase.verbs.word еще не установленно, то может возникнуть перебрасывание запросов на генерацию. По этому запрещаем.
					nounsGenThere = 1;
					var nounSettings = cloneStruct(settings);
					nounSettings.noSubwords = 1;
					if(this.dividePoint.genNouns(nounSettings)) { // TODO: ну вот сгенерировалось слово "комната", для него нет ни одного подходящег глагола?
						currentNoun = this.dividePoint.nouns;
					} else {
						ret = false;
					}
				} else {
					ret = false;
				}
			}
			if(currentNoun) {
				if(this.checkboxesTest('обязательно')) {
					this.whatDo.mandateForOneWord = 1;
				}
				var th = this;
				this.whatDo.wordsFilter = function(word) {
					if(th.flagsTest('активное') && !word.verbFlagsValTest('активное действие') || th.flagsTest('пассивное') && !word.verbFlagsValTest('пассивное действие')) {
					} else if(!th.verbCheckboxesTest('совершенное') && word.verbCheckboxesValTest('совершенное') || th.verbCheckboxesTest('продолжительное') && !word.verbCheckboxesValTest('продолжительное') || th.verbCheckboxesTest('занимает') && !word.verbCheckboxesValTest('занимает')) {
					} else {
						return 1;
					}
				};
				if(currentNoun.commonFlags.length) {
					ret = this.whatDo.gen(settings, wordsCollection, currentNoun.commonFlags, true, Verb.bitesByFlagsGroupName['who' || 0]);
				} else {
					ret = this.whatDo.gen(settings, wordsCollection);
				}
				if(!ret) {
					this.whatDo = this.whatDo.destruct();
					if(this.checkboxesTest('обязательно')) {
						if(nounsGenThere) { // если со случайным существительным ни чего не получается, значит такое существительное и не нужно.
							if(!isArray(this.wordsForRemoveFromRootAllWords)) this.wordsForRemoveFromRootAllWords = [];
							this.wordsForRemoveFromRootAllWords = this.wordsForRemoveFromRootAllWords.concat(currentNoun.getAllWords()); // запомним их, чтобы в конце удалить.
							this.dividePoint.nouns = this.dividePoint.nouns.destruct();
							this.root.allPhWordsInside.concat(currentNoun.getAllWords()); // добавим уже испробованные слова, чтобы снова на них не наткнуться.
							this.dividePoint.nouns = 0;
							nounsGenThere = 0;
						}
					}
				}
			}
		}
		if(ret && this.whom && randomInt()) {
			this.whom = this.whom.destruct(); // иначе "начинает делать это их"
		}
		if(ret && this.usingWhat && randomInt()) {
			this.usingWhat = this.usingWhat.destruct(); // иначе "начинает делать это их"
		}
		return ret;
	};

	PhProtoVerb.prototype.genAdverbs = function(settings) {
		if(this.adverbs) this.adverbs = this.adverbs.destruct();
		this.adverbs = new PhAdverb(this, this.dividePoint);
		if(!this.adverbs.gen(settings)) {
			this.adverbs = this.adverbs.destruct();
		}
	};

	PhProtoVerb.prototype.genWhen = function(settings) {
		if(this.when) this.when = this.when.destruct();
		this.when = new PhTime(this, this.dividePoint);
		if(!this.when.gen(settings)) {
			this.when = this.when.destruct();
		}
	};

	PhProtoVerb.prototype.autosetCommonFlags = function() {
		// устанавливает в переменную this.commonFlags те флаги, что сейчас актуальны. В случае verb флаги делятся на flagsGroups
		this.commonFlags = {};
		var i = 0;
		var specialFlagsLen = 0;
		for(i in Verb.specialFlags) {
			specialFlagsLen++;
		}
		var filtr;
		var filtrLen = (Verb.flagsGroups.length).toString(2).length;
		for(i = 0; i < Verb.flagsGroups.length; i++) {
			this.commonFlags[Verb.flagsGroups[i]] = this.wordsCollection.getCommonFlagsForWords(this.getAllWords(), Verb.bitesByFlagsGroupName[Verb.flagsGroups[i]]);
		}
		this.autosetCommonVerbFlags();
	};

	PhProtoVerb.prototype.autosetCommonVerbFlags = function() {
		// Устанавливает в переменную this.commonVerbFlags те флаги, что сейчас актуальны.
		this.commonVerbFlags = this.wordsCollection.getCommonVerbFlagsForWords(this.getAllWords());
	};

	PhProtoVerb.prototype.writeEn = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		// Пока просто записывает в переменные объекта, в финальных классах нужно конкатенировать эти обрывки и вернуть строку.
		var en = this.en; // Хранятся переменные для русской фразы
		PhProtoStage.prototype.writeEn.apply(this, arguments);
		writeCommon(this, en);

		if(this.adverbs) {
			en.adverbStr = this.adverbs.writeEn();
		}

		var sStart;
		var sEnd;

		if(this.whom) {
			en.whomStr = this.whom.writeEn();
		}

		if(this.usingWhat) {
			en.usingWhatStr = this.usingWhat.writeEn();
		}

		if(this.whatDo) {
			en.whatDoStr = this.whatDo.writeEn();
			if(['can', 'could', 'must', 'may', 'might', 'will', 'shall', 'would', 'should', 'need', 'dare'].indexOf(en.wordsStr) !== -1) { // Инфинитив употребляется без "to" после этих вспомогательных и модальных глаголов
				en.whatDoStr = en.whatDoStr.replace('to ', '');
			}
		}

		if(this.when) {
			en.whenStr = this.when.writeEn();
		}

		if(this.where) {
			en.whereStr = this.where.writeEn();
		}

		if(this.from) {
			en.fromStr = this.from.writeEn();
		}

		if(this.to) {
			en.toStr = this.to.writeEn();
		}

		if(this.for) {
			sStart = '';
			sEnd = '';
			padej = 1;
			if(this.for.preposition) {
				sStart = this.for.preposition[0];
			}
			if(this.for.nouns) {
				sEnd = this.for.nouns.writeEn(padej);
			} else {
				sStart = '';
			}
			en.forStr = sStart + ' ' + sEnd;
		}

		if(this.about) {
			if(this.about.type === 'about') {
				if(this.about.nouns) {
					en.aboutStr = 'about ' + this.about.nouns.writeEn();
				}
			} else if(this.about.type === 'about when') {
				if(this.about.phrase) {
					en.aboutStr = 'about when ' + this.about.phrase.writeEn(); // ------------------- TODO: ...
				}
			}
		}
	};

	var writeCommon = function(th, wr) {
		wr.adverbStr = '';
		wr.whomStr = '';
		wr.usingWhatStr = '';
		wr.whereStr = '';
		wr.fromStr = '';
		wr.toStr = '';
		wr.forStr = '';
		wr.aboutStr = '';
		wr.whatDoStr = '';
		wr.whenStr = '';
	};

	PhProtoVerb.prototype.writeRu = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		// Пока просто записывает в переменные объекта, в финальных классах нужно конкатенировать эти обрывки и вернуть строку.
		var ru = this.ru; // Хранятся переменные для русской фразы
		writeCommon(this, ru);
		PhProtoStage.prototype.writeRu.apply(this, arguments);

		if(this.adverbs) {
			ru.adverbStr = this.adverbs.writeRu();
		}

		var sStart;
		var sEnd;
		var padej;

		if(this.whom) {
			if(this.word && this.word[1].getFormed() == 'владеть') {
				ru.whomStr = this.whom.writeRu(4); // иначе "он владеет это сейчас".
			} else {
				ru.whomStr = this.whom.writeRu(3);
			}
		}

		if(this.usingWhat) {
			ru.usingWhatStr = this.usingWhat.writeRu(4);
		}

		if(this.whatDo) {
			ru.whatDoStr = this.whatDo.writeRu(0, 2, 1);
		}

		if(this.where) {
			ru.whereStr = this.where.writeRu();
		}

		if(this.when) {
			ru.whenStr = this.when.writeRu();
		}

		if(this.for) {
			sStart = '';
			sEnd = '';
			padej = 1;
			if(this.for.preposition) {
				sStart = this.for.preposition[1];
			}
			if(this.for.nouns) {
				sEnd = this.for.nouns.writeRu(padej);
			} else {
				sStart = '';
			}
			ru.forStr = sStart + ' ' + sEnd;
		}

		if(this.about) {
			if(this.about.type === 'about') {
				if(this.about.nouns) {
					ru.aboutStr = 'о ' + this.about.nouns.writeRu(5, 0);
				}
			} else if(this.about.type === 'about when') {
				if(this.about.phrase) {
					ru.aboutStr = 'о том как ' + this.about.phrase.writeRu(5, 0); // ------------------- TODO: ...
				}
			}
		}
	};

	PhProtoVerb.prototype.verbFlagsTest = function(str, strict, flags) {
		var ret;
		if(!flags) flags = this.commonVerbFlags;
		if(flags) ret = this.baseWordTyp.prototype.verbFlagsValTest(str, strict, this.commonVerbFlags);
		return ret;
	};
	PhProtoVerb.prototype.verbCheckboxesTest = function(str, flags) {
		if(!flags) flags = this.commonVerbFlags;
		if(flags) return this.baseWordTyp.prototype.verbCheckboxesValTest(str, flags);
	};

	PhProtoVerb.prototype.getCurrentNoun = function() {
		// Находит существительное, которое делает это действие.;
		if(!this.parent) return;
		if(this.parent.constructorFunc === PhNoun) return this.parent;
		if(this.parent.getCurrentNoun) return this.parent.getCurrentNoun();
		if(this.parent.nouns) return this.parent.nouns;
	};

})();

var PhAdjective = function(parent, dividePoint) {
	this.construct(parent, dividePoint);
};

(function() {
	PhProtoWord.registrPhTyp(PhAdjective, PhProtoStage, Adjective, user.adjectives, 'PhAdjective');
	PhAdjective.prototype.emphasisWordsCollection = user.emphasis.adjectives;
	PhAdjective.prototype.wordsCollections = [PhAdjective.prototype.emphasisWordsCollection, PhAdjective.prototype.wordsCollection];

	PhAdjective.prototype.construct = function(parent, dividePoint) {
		PhProtoStage.prototype.construct.call(this, parent, dividePoint);
	};

	PhAdjective.prototype.gen = function(settings, wordsCollection) {
		// Подбирает случайные слова и обороты
		this.setBySettings(settings);
		if(!this.mathCountWords(settings) || this.parent && this.parent.noSubwords) return;
		var ret;
		if(this.parent && this.parent.constructorFunc === PhNoun) {
			this.genStage(settings);
			ret = PhProtoStage.prototype.gen.call(this, settings, wordsCollection, this.parent.commonFlags);
		} else if(this.parent && this.parent === this.dividePoint && this.parent.toBe.adjectives === this) {
			if(!this.parent.nouns) this.parent.genNouns();
			var parNoun = this.parent.nouns;
			if(parNoun) {
				var flags = parNoun.commonFlags;
				console.log('adj', flags);
				ret = PhProtoStage.prototype.gen.call(this, settings, wordsCollection, flags);
			} else {
				ret = PhProtoStage.prototype.gen.apply(this, arguments);
			}
		} else {
			ret = PhProtoStage.prototype.gen.apply(this, arguments);
		}
		return ret;
	};

	PhAdjective.prototype.mathRank = function(settings) {
		var ret = settings.sPhAdjective;
		return ret;
	};

	PhAdjective.prototype.writeEn = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		var en = this.en; // Хранятся переменные для английской фразы
		writeCommon(this, en);
		PhProtoStage.prototype.writeEn.apply(this, arguments);
		en.comparativeStr = ''; // comparative формируется внутри слова (ведь может быть не "он более быстрый" а "он быстрее").
		var ret = joins(en.noStr, en.veryStr, en.comparativeStr, en.wordsStr, en.thanStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhAdjective.prototype.getFormedWordEn = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		return this.word[0].getFormed(this.en.comparative);
	};

	var writeCommon = function(th, wr) {
	};

	PhAdjective.prototype.writeRu = function(gender, padej) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		var ru = this.ru; // Хранятся переменные для русской фразы
		writeCommon(this, ru);
		if(this.inListing) {
			if(this.parent.ru.gender !== undefined) ru.gender = this.parent.ru.gender;
			if(this.parent.ru.padej !== undefined) ru.padej = this.parent.ru.padej;
		}
		if(gender !== undefined) ru.gender = gender;
		if(padej !== undefined) ru.padej = padej;
		PhProtoStage.prototype.writeRu.apply(this, arguments);
		ru.comparativeStr = ''; // comparative формируется внутри слова (ведь может быть не "он более быстрый" а "он быстрее").
		var ret = joins(ru.noStr, ru.veryStr, ru.comparativeStr, ru.wordsStr, ru.thanStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhAdjective.prototype.getFormedWordRu = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		/*var ru = this.ru;
		if(!this.ru.comparative && this.dividePoint && this.dividePoint.nouns.why) {
			var why = this.dividePoint.nouns.why.val;
			if(why > 1) {
				if(why % 100 > 4 && why % 100 < 21) {
				} else if(why % 10 === 1) {
					return this.word[1].getFormed(0, this.ru.gender, 0); // 31 черная палка
				} else if(why % 10 > 1 && why % 10 < 5) {
					return this.word[1].getFormed(0, this.ru.gender, 0); // 44 черные палки
				}
				return this.word[1].getFormed(0, this.ru.gender, 1); // 111 черных палок 19 палок // 30 палок, 35 палок
			}
		}*/
		return this.word[1].getFormed(this.ru.comparative, this.ru.gender || 0, this.ru.padej || 0);
	};

})();
var PhAdverb = function(parent, dividePoint) {
	this.construct(parent, dividePoint);
};

(function() {
	PhProtoWord.registrPhTyp(PhAdverb, PhProtoStage, Adverb, user.adverbs, 'PhAdverb');
	PhAdverb.prototype.emphasisWordsCollection = user.emphasis.adverbs;
	PhAdverb.prototype.wordsCollections = [PhAdverb.prototype.emphasisWordsCollection, PhAdverb.prototype.wordsCollection];

	PhAdverb.prototype.construct = function(parent, dividePoint) {
		PhProtoStage.prototype.construct.call(this, parent, dividePoint);
	};

	PhAdverb.prototype.gen = function(settings, wordsCollection) {
		this.setBySettings(settings);
		if(this.adverbs) this.adverbs = this.adverbs.destruct();
		if(!this.mathCountWords(settings) || this.parent && this.parent.noSubwords) return;
		var ret;
		// не использованы : где?, куда?, откуда?, на сколько времени?, колличество, делает 2 дела..., они делают дело..., там все было...
		if(this.dividePoint) {
			/*if(!this.noChangeWords && this.dividePoint.adverbHas) {
				return; // на самом деле зачастую можно вставлять в фразу несколько наречий, но пока упростил себе задачу.
			}*/
			this.dividePoint.adverbHas = 1;
		}
		if(!this.noChangeWords && this.parent === this.dividePoint) {
			ret = this.genByUniversalFlags(settings, wordsCollection, ['каково делать?']); // тогда фраза(особенно английская) строится особым образом.
			if(this.parent.stage && this.parent.stage.no) return; // иначе будет смысл "легко не делающий".
		} else if(!this.noChangeWords && this.parent && this.parent.constructorFunc === PhTime) {
			var parentVerb = this.getParentPhProtoVerb();
			var flags = [];
			if(parentVerb) {
				pushAll(flags, Adverb.prototype.genFlags('когда?', 0, 1));
				settings = cloneStruct(settings);
				settings.sStage = 0;
				if(this.dividePoint) {
					if(this.dividePoint.timeTyp === 0) {
						pushAll(flags, Adverb.prototype.genFlags('только будующее', 0, 1));
						pushAll(flags, Adverb.prototype.genFlags('преим. будующее', 0, 1));
						pushAll(flags, Adverb.prototype.genFlags('настоящее', 0, 1));
					} else if(this.dividePoint.timeTyp === 1) {
						pushAll(flags, Adverb.prototype.genFlags('только будующее', 0, 1));
						pushAll(flags, Adverb.prototype.genFlags('только прошедшее', 0, 1));
						pushAll(flags, Adverb.prototype.genFlags('преим. будующее', 0, 1));
						pushAll(flags, Adverb.prototype.genFlags('преим. прошедшее', 0, 1));
					} else if(this.dividePoint.timeTyp === 2) {
						pushAll(flags, Adverb.prototype.genFlags('только прошедшее', 0, 1));
						pushAll(flags, Adverb.prototype.genFlags('преим. прошедшее', 0, 1));
						pushAll(flags, Adverb.prototype.genFlags('настоящее', 0, 1));
					}
					if(!parentVerb.verbFlagsTest('кинетическое') && (!parentVerb.verbFlagsTest('активное действие') || parentVerb.verbFlagsTest('вербальное')) || parentVerb.verbCheckboxesTest('совершенное')) {
						pushAll(flags, Adverb.prototype.genFlags('продолжительность', 0, 1));
					}
				}
				if((parentVerb.word && parentVerb.word[0].getFormed() === 'become' || parentVerb.verbFlagsTest('удержание') || parentVerb.verbFlagsTest('создание'))) { // иначе "он чихает надолго".
					flags.push(Adverb.prototype.genFlags('на сколько времени?')[0]);
				}
			}
			ret = PhProtoStage.prototype.gen.call(this, settings, wordsCollection, flags); // !!! специально пропускаем PhAdverb.prototype !!!
		} else if(!this.noChangeWords && this.parent && this.parent.constructorFunc === PhNumeral) {
			ret = this.genByUniversalFlags(settings, wordsCollection, ['колличество']); // тогда фраза(особенно английская) строится особым образом.
			if(this.parent.stage && this.parent.stage.no) return; // иначе будет смысл "легко не делающий".
		} else if(!this.noChangeWords && this.parent && this.parent instanceof PhProtoVerb) {
			if(this.parent.stage && this.parent.stage.no) return; // иначе будет на подобии "не стоит смирно", "не локает обычно".
			/*if(this.parent.where && this.parent.where.adverbs === this) {
				ret = this.genByUniversalFlags(settings, wordsCollection, ['где?']);
			} else if(this.parent.from && this.parent.from.adverbs === this) {
				ret = this.genByUniversalFlags(settings, wordsCollection, ['откуда?']);
			} else if(this.parent.to && this.parent.to.adverbs === this) {
				ret = this.genByUniversalFlags(settings, wordsCollection, ['куда?']);
			} else {*/
				if(this.parent.commonVerbFlags) {
					ret = PhProtoStage.prototype.gen.call(this, settings, wordsCollection, this.parent.commonVerbFlags.map(Adverb.verbFlagToFlag));
				}
				if(!ret) {
					var verbUniversalFlags = [/*'когда?', 'на сколько времени?', */'насколько делает?', 'вероятность', 'как выглядеть?', 'почему?', 'в каком смысле?', 'каким образом делает?'];
					if(this.parent.word[0].getFormed() !== 'do') removeFromArrayByValue(verbUniversalFlags, 'в каком смысле?'); // иначе "он теоретически скачет".
					//if(this.parent.word[0].getFormed() !== 'become' && !this.parent.verbFlagsTest('удержание') && this.parent.verbFlagsTest('создание')) removeFromArrayByValue(verbUniversalFlags, 'на сколько времени?'); // иначе "он чихает надолго".
					if(this.parent.stage || this.stage) {
						removeFromArrayByValue(verbUniversalFlags, 'насколько делает?'); // иначе "не немного быстро" и т.д.
						removeFromArrayByValue(verbUniversalFlags, 'вероятность?'); // иначе "не наверное быстро" и т.д.
					}
					//if(this.parent.constructorFunc !== PhVerb) removeFromArrayByValue(verbUniversalFlags, 'когда?'); // "вчера летящих" склоняеть во времени научились только PhVerb.
					if(!this.parent.verbFlagsTest('кинетическое') && this.parent.verbFlagsTest('пассивное действие') || this.parent.verbFlagsTest('умственное')) removeFromArrayByValue(verbUniversalFlags, 'как выглядеть?'); // иначе "весело думать", "весело грустить".
					if(this.dividePoint.formTyp !== 1 || this.parent.stage && (this.parent.stage.no || this.parent.stage.very || this.parent.stage.comparative)) removeFromArrayByValue(verbUniversalFlags, 'вероятность');// иначе "Разве они вероятно любили?".
					if(this.parent.verbFlagsTest('пассивное действие')) removeFromArrayByValue(verbUniversalFlags, 'почему?'); // иначе "случайно растут", "добровольно существуют".
					shuffleArray(verbUniversalFlags);
					var newSettings = cloneStruct(settings);
					ret = this.genByUniversalFlags(newSettings, wordsCollection, verbUniversalFlags);
					if(!ret) ret = PhProtoStage.prototype.gen.call(this, newSettings, wordsCollection, [['',[]]]); // а теперь ищем всеядные наречия.
				}
			//}
		} else if(!this.noChangeWords && this.parent && this.parent.constructorFunc === PhAdverb) {
			if(this.parent.flagsTest('...быстро, ...сухо')) {
				var adverbUniversalFlags = ['как выглядеть?', 'каково делать?', 'каким образом делает?'];
				shuffleArray(adverbUniversalFlags);
				ret = this.genByUniversalFlags(settings, wordsCollection, adverbUniversalFlags);
			}
		} else {
			ret = PhProtoStage.prototype.gen.call(this, settings, wordsCollection);
		}
		if(this.flagsTest('насколько делает?')) {
			this.stage = 0;
		}
		if(!ret && this.dividePoint) {
			this.dividePoint.adverbHas = 0;
		}
		if((this.stage || this.parent.stage) && this.flagsTest('каким образом делает?')) {
			this.stage = 0;
		}
		return ret;
	};

	PhAdverb.prototype.genByUniversalFlags = function(settings, wordsCollection, universalFlags) {
		// Просто выделил функцию, чтобы удобней было строить ветвление внутри PhAdverb.gen();
		var flags; // флаги, которыми должно обладать искомое наречие.
		var outInside = 0; // 1 означает, что ищется не в более частных категориях, а в более общих.
		var startFlag; // основной флаг для universalFlags[i] .
		var wordsForRemoveFromAllWords = []; // под конец функции эти слова нужно очистить из root.allWords.
		var protoVerb;
		var ret;
		for(var i = 0; i < universalFlags.length; i++) {
			startFlag = Adverb.prototype.genFlags(universalFlags[i])[0];
			flags = [startFlag];
			if(universalFlags[i] === 'как выглядеть?' && this.dividePoint) {
				if(!this.dividePoint.nouns) this.dividePoint.genNouns();
				if(this.dividePoint.nouns) {
					outInside = 1;
					if(this.dividePoint.nouns.flagsTest('материальное')) {
						flags = Adverb.prototype.genFlags('материальное');
						if(this.dividePoint.nouns.flagsTest('одушевленное')) {
							flags = Adverb.prototype.genFlags('одушевленное');
							if(this.dividePoint.nouns.flagsTest('человек')) {
								flags = Adverb.prototype.genFlags('человек');
							}
						}
					} else if(this.dividePoint.nouns.flagsTest('абстрактное')) {
						flags = Adverb.prototype.genFlags('абстрактное');
					} else if(this.dividePoint.nouns.flagsTest('группа')) {
						flags = Adverb.prototype.genFlags('группа');
					}
				}
			} else if(universalFlags[i] === 'каким образом делает?' && this.dividePoint) {
				protoVerb = this.getParentPhProtoVerb();
				if(protoVerb && protoVerb.commonVerbFlags.length) {
					flags = [];
					for(var n = 0; n < protoVerb.commonVerbFlags.length; n++) {
						flags.push([startFlag[0] + protoVerb.commonVerbFlags[n][0], []]);
					}
				}
			/*} else if(universalFlags[i] === 'когда?' && this.dividePoint) {
				if(this.dividePoint.timeTyp === 0) {
					pushAll(flags, Adverb.prototype.genFlags('только будующее', 0, 1));
				} else if(this.dividePoint.timeTyp === 1) {
					pushAll(flags, Adverb.prototype.genFlags('только будующее', 0, 1));
					pushAll(flags, Adverb.prototype.genFlags('только прошедшее', 0, 1));
				} else if(this.dividePoint.timeTyp === 2) {
					pushAll(flags, Adverb.prototype.genFlags('только прошедшее', 0, 1));
				}
				if(this.parent && this.parent.verbFlagsTest) {
					if(!this.parent.verbFlagsTest('кинетическое') && (!this.parent.verbFlagsTest('активное действие') || this.parent.verbFlagsTest('вербальное'))) {
						pushAll(flags, Adverb.prototype.genFlags('продолжительность', 0, 1));
					}
				}*/
			} else if(universalFlags[i] === 'насколько делает?') {
				// flags.push([Adverb.prototype.flagsRanges['...более'][2],[], 1]); // а я решил вообще не использовать сравнительные прилагательные, а использую "более зеленый", "более быстро".
			} else if(universalFlags[i] === 'в каком смысле?') {
				if(!this.dividePoint.nouns) this.dividePoint.genNouns();
				if(this.dividePoint.nouns && !this.dividePoint.nouns.flagsTest('человек')) {
					pushAll(flags, Adverb.prototype.genFlags('только человек', 0, 1));
				}
			/*} else if(universalFlags[i] === 'где?') {
			*/
			} else if(universalFlags[i] === 'колличество') {
				pushAll(flags, Adverb.prototype.genFlags('всколькером', 0, 1));
				pushAll(flags, Adverb.prototype.genFlags('в какой раз', 0, 1));
				pushAll(flags, Adverb.prototype.genFlags('в сколько раз', 0, 1)); // вообще не используем наречия с этим флагом, аналогичные должны генерироваться в PhNumeral .
				if(!(this.parent instanceof PhProtoVerb)) {
					pushAll(flags, Adverb.prototype.genFlags('колличество раз', 0, 1));
				}
			}
			if(outInside) flags.push(['',[], 1]); // если используем outInside, то исключаем поиск по всеядным флагам. Подфлаги не будут отсееваться благодаря outInside.
			ret = PhProtoStage.prototype.gen.call(this, settings, wordsCollection, flags, outInside);
			if(ret) {
				if(this.checkboxesTest('более-менее')) this.genStage(settings);
				/*if(universalFlags[i] === 'где?') {

				} else if(universalFlags[i] === 'куда?') {

				} else if(universalFlags[i] === 'откуда?') {

				} else */if(universalFlags[i] === 'вероятность') {
					if(this.stage) {
						if(this.stage.than) this.stage.then = this.stage.then.destruct();
						this.stage = 0; // иначе "не вероятно идет", "не точно идет".
					}

				} else if(universalFlags[i] === 'насколько делает?') {
					if(this.flagsTest('...быстро, ...сухо')) {
						this.adverbs = new PhAdverb(this, this.dividePoint);
						ret = this.adverbs.gen(settings);
						if(!ret) {
							this.adverbs = this.adverbs.destruct();
						}
						wordsForRemoveFromAllWords.push(this.word);
						if(!ret) {
							this.word = 0; // removeWordFromRootArrs вызовем в конце функции.
							this.commonFlags = 0;
						}
						if(this.stage) {
							if(this.stage.than) this.stage.then = this.stage.then.destruct();
							this.stage = 0; // иначе "не вероятно идет", "не точно идет".
						}
					}
				} else if(universalFlags[i] === 'в каком смысле?') {

				} else if(universalFlags[i] === 'колличество') {

				}
				if(ret) break;
			} else {
				this.stage = 0;
			}
		}
		this.removeWordFromRootArrs(wordsForRemoveFromAllWords);
		return ret;
	};

	PhAdverb.prototype.mathRank = function(settings) {
		var ret = settings.sPhAdverb;
		return ret;
	};

	PhAdverb.prototype.writeEn = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var en = this.en; // Хранятся переменные для английской фразы.
		writeCommon(this, en);
		PhProtoStage.prototype.writeEn.call(this);
		if(this.adverbs) en.adverbsStr = this.adverbs.writeEn();
		var ret = joins(en.noStr, en.veryStr, en.the, en.comparativeStr, en.wordsStr, en.adverbsStr, en.thanStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	var writeCommon = function(th, wr) {
		wr.adverbsStr = '';
	};

	PhAdverb.prototype.writeRu = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var ru = this.ru; // Хранятся переменные для русской фразы.
		writeCommon(this, ru);
		PhProtoStage.prototype.writeRu.call(this);
		if(ru.comparativeStr === 'сильнее') ru.comparativeStr = 'более'; // иначе "сильнее рано шел".
		if(ru.comparativeStr === 'слабее') ru.comparativeStr = 'менее';
		if(this.adverbs) ru.adverbsStr = this.adverbs.writeRu();
		var ret = joins(ru.noStr, ru.veryStr, ru.comparativeStr, ru.wordsStr, ru.adverbsStr, ru.thanStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhAdverb.prototype.getParentPhProtoVerb = function() {
		// Ищет ближайший родительский PhProtoVerb.
		var th = this.parent;
		while(th) {
			if(th === this) return;
			if(th instanceof PhProtoVerb) return th;
			th = th.parent;
		}
		return th;
	};

})();
var PhParticiple = function(parent, dividePoint) {
	this.construct(parent, dividePoint);
};

(function() {
	PhProtoWord.registrPhTyp(PhParticiple, PhProtoVerb, 0, 0, 'PhParticiple');

	PhParticiple.prototype.gen = function(settings, wordsCollection) {
		this.setBySettings(settings);
		if(!this.mathCountWords(settings) || this.parent && this.parent.noSubwords) return;
		var ret;
		if(this.parent && this.parent.constructorFunc === PhNoun && this.parent.commonFlags.length) {
			ret = PhProtoVerb.prototype.gen.call(this, settings, wordsCollection, this.parent.commonFlags, 1);
			if(ret) {
				var oldNoChangeWords = this.noChangeWords;
				this.noChangeWords = true;
				ret = PhProtoVerb.prototype.gen.call(this, settings, wordsCollection);
				this.noChangeWords = oldNoChangeWords;
			}
		} else if(this.parent && this.parent === this.dividePoint && this.parent.toBe.participles === this) {
			if(!this.parent.nouns) {
				this.parent.genNouns();
			}
			ret = PhProtoVerb.prototype.gen.call(this, settings, wordsCollection, this.parent.nouns.commonFlags, 1);
		} else {
			//throw new Error('вот оно, лови его');
			ret = PhProtoVerb.prototype.gen.apply(this, arguments);
		}
		return ret;
	};

	/*PhParticiple.prototype.genRndPair = function(settings, wordsCollection, flags, outInside, firstBites) {
		var pair;
		var wordsCollections = this.wordsCollections;
		if(wordsCollection) wordsCollections = [wordsCollection];
		var root = this.root;
		var filtr = function(word, currentFlag) {
			// проверяет, имеется ли у слова подходящий флаг в подкатегории who.
			if(!root || !root.allWords || root.allWords.indexOf(word) === -1) {
				var aFlags = word.getApportionedFlags();
				if(aFlags) {
					for(var i = 0; i < aFlags.length; i++) {
						if(aFlags[i][3] === Verb.bitesByFlagsGroupName['who'] && !aFlags[i][2]) { // '001' означает подкатегорию who; aFlags[i][2] означает, что этот флаг является фильтрующим.
							for(var n = 0; n < flags.length; n++) {
								if(aFlags[i][0] === flags[n][0].substr(0, aFlags[i][0].length)) {
									// Не знаю как проверить вхождение нужных checkboxов.
									return 1;
								}
							}
						}
					}
				}
			}
			return 0;
		};
		var i;
		for(i = 0; i < wordsCollections.length; i++) {
			if(flags) {
				pair = wordsCollections[i].getRndWordsPair(filtr, filtr, 0, flags, outInside);
			} else {
				var allWords;
				if(root) allWords = root.allWords;
				pair = wordsCollections[i].getRndWordsPair(allWords, allWords, 0, flags, outInside);
			}
			if(pair) return pair;
		}
	};*/

	PhParticiple.prototype.mathRank = function(settings) {
		var ret = settings.sPhParticiple;
		return ret;
	};

	/*PhParticiple.prototype.genByFlags = function(settings, wordsCollection, flags, outInside) {
		// Ищем и устанавливаем слово(а), соответствующее флагам.
		this.setBySettings(settings);
		if(this.parent && this.parent.noSubwords) return;
		var wordsCollections = this.wordsCollections;
		if(wordsCollection && wordsCollections[0] !== wordsCollection) wordsCollections = [wordsCollection].concat(wordsCollections);
		var root = this.getRoot();
		var maxCountWords = 1;
		if(!settings.blockListings && !this.inListing) maxCountWords = this.mathCountWords(settings);
		var wordPairs;
		var filtrFunc = function(word) {
			// проверяет, имеется ли у слова подходящий флаг в подкатегории who.
			if(!root || !root.allWords || root.allWords.indexOf(word) === -1) {
				var aFlags = word.getApportionedFlags();
				if(aFlags) {
					for(var i = 0; i < aFlags.length; i++) {
						if(aFlags[i][3] === '001' && !aFlags[i][2]) { // '001' означает подкатегорию who; aFlags[i][2] означает, что этот флаг является фильтрующим.
							for(var n = 0; n < flags.length; n++) {
								if(aFlags[i][0] === flags[n][0].substr(0, aFlags[i][0].length)) {
									// Не знаю как проверить вхождение нужных checkboxов.
									return 1;
								}
							}
						}
					}
				}
			}
			return 0;
		};
		var i;
		for(i = 0; i < wordsCollections.length; i++) {
			wordPairs = wordsCollections[i].getRndWordsPairs(filtrFunc, filtrFunc, 0, flags, maxCountWords, outInside);
			if(wordPairs && wordPairs.length) break;
		}
		if(!wordPairs || !wordPairs.length) return false; // значит не удалось подобрать пару слов.
		for(i = 0; i < wordPairs.length; i++) {
			this.addWordPair(wordPairs[i], settings, wordsCollection, flags);
		}
		this.autosetCommonFlags();
		return true;
	};*/

	PhParticiple.prototype.writeEn = function(typ, passive) {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		// typ - означает какой вид причастия требуется, 0 - Present Participle Simple, 1 - Present Participle Perfect, 2 - Past Participle
		//                                                   открывающий, отдыхающая        открыв, отдохнув                открытый, отдохнувшая
		// passive: 0 - Действительный залог, 1 - Страдательный залог
		var en = this.en;
		writeCommon(this, en, typ, passive);
		en.typ = typ;
		en.passive = passive;
		PhProtoVerb.prototype.writeEn.apply(this, arguments);
		var ret = joins(en.whomStr, en.noStr, en.adverbStr, en.veryStr, en.comparativeStr, en.wordsStr, en.whatDoStr, en.thanStr, en.whereStr, en.fromStr, en.toStr, en.forStr, en.aboutStr, en.whenStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhParticiple.prototype.getFormedWordEn = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		return this.word[0].getFormedParticiple(this.en.typ || 0, this.en.passive || 0);
	};

	var writeCommon = function(th, wr, typ, passive) {
		if(typ !== undefined) wr.typ = typ;
		if(passive !== undefined) wr.passive = passive;
	};

	PhParticiple.prototype.writeRu = function(typ, passive, gender, padej) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		// typ - означает какой вид причастия требуется, 0 - Present Participle Simple, 1 - Present Participle Perfect, 2 - Past Participle
		//                                                   открывающий, отдыхающая        открыв, отдохнув                открытый, отдохнувшая
		// passive: 0 - Действительный залог, 1 - Страдательный залог
		var ru = this.ru; // хранятся переменные для русской фразы
		writeCommon(this, ru, typ, passive);
		if(gender !== undefined) ru.gender = gender;
		if(padej !== undefined) ru.padej = padej;
		PhProtoVerb.prototype.writeRu.apply(this, arguments);
		var ret = joins(ru.whereStr, ru.noStr, ru.adverbStr, ru.veryStr, ru.comparativeStr, ru.wordsStr, ru.whatDoStr, ru.thanStr, ru.whereStr, ru.whomStr, ru.fromStr, ru.toStr, ru.forStr, ru.aboutStr, ru.whenStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhParticiple.prototype.getFormedWordRu = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		return this.word[1].getFormedParticiple(this.ru.typ || 0, this.ru.passive || 0, this.ru.gender || 0, this.ru.padej || 0);
	};

})();
var PhTime = function(parent, dividePoint) {
	this.construct(parent, dividePoint);
};

(function() {

	PhProtoWord.registrPhTyp(PhTime, PhProtoWord, 0, 0, 'PhTime');

	PhTime.prototype.construct = function(parent, dividePoint) {
		PhProtoWord.prototype.construct.call(this, parent, dividePoint);
	};

	PhTime.prototype.gen = function(settings, wordsCollection) {
		if(!this.mathCountWords(settings) || this.parent && this.parent.noSubwords) return;
		if(this.parent && this.parent.stage && this.parent.stage.no) return;
		if(this.dividePoint && this.dividePoint.form === 2) return;
		var ret;
		var arr = [
			[this.genAdverb, settings.sTimeAdverb],
			[this.genActDirect, settings.sTimeActDirect],
			[this.genGap, settings.sTimeGap],
			[this.genPeriod, settings.sTimePeriod],
			[this.genClock, settings.sTimeClock],
			[this.genDate, settings.sTimeDate],
			[this.genYear, settings.sTimeYear],
		];
		this.shuffleSecondGensQueue(arr);
		if(!arr) return;
		shuffleArray(arr);
		for(var i = 0; i < arr.length; i++) {
			if(arr[i] && arr[i][1]) {
				ret = arr[i][0].call(this, settings, wordsCollection);
				if(ret) break;
			}
		}
		return ret;
	};

	PhTime.prototype.genAdverb = function(settings, wordsCollection) {
		if(this.adverb) this.adverb = this.adverb.destruct();
		if(this.parent.stage && this.parent.stage.no) return;
		if(this.parent.parent && this.parent.parent.whatDo === this.parent) return; // иначе не понятно к какому действию относится время. И вообще так не говорят.
		this.adverb = new PhAdverb(this, this.dividePoint);
		if(!this.adverb.gen(settings, wordsCollection)) {
			this.adverb = this.adverb.destruct();
			return;
		}
		return this.adverb;
	};

	PhTime.prototype.genActDirect = function(settings) {
		//  Генерирует время до/после события.
		if(this.parent && this.parent.parent && this.parent.parent.timeTyp === 1) {
			return;
		}
		if(this.actDirect) {
			if(this.actDirect.act) {
				this.actDirect.act = this.actDirect.act.destruct();
			}
			if(this.actDirect.direct) {
				this.actDirect.direct = this.actDirect.direct.destruct();
			}
			actDirect = 0;
		}
		var act = new PhNoun(this, this.dividePoint);
		var flags = [];
		['событие', 'действие', 'момент времени'].forEach(function(nam) {
			pushAll(flags, Noun.prototype.genFlags(nam));
		});
		var direct;
		settings = cloneStruct(settings);
		settings.sStage = 0;
		settings.sPron = 0;
		settings.noSubwords = 1; // можно конечно подслова оставить, но слишком тяжело понять.
		var ret = act.gen(settings, 0, flags);
		if(ret) {
			direct = new PhAdverb(this, this.dividePoint);
			if(randomInt()) {
				direct.addWordPair(baseAdverbs.afterPair, settings);
			} else {
				direct.addWordPair(baseAdverbs.beforePair, settings);
			}
			this.actDirect = {};
			this.actDirect.act = act;
			this.actDirect.direct = direct;
		} else {
			act = act.destruct();
		}
		return ret;
	};

	PhTime.prototype.genGap = function(settings) {
		// Генерирует отдаленность времени относительно сейчас.
		if(this.interval) this.interval = this.interval.destruct();
		if(this.dividePoint && this.dividePoint.timeTyp === 1) return;
		var gap = {};
		var typ = randomInt(5);
		gap.interval = this.createPh(getRandomElementFromArray(baseNouns.timeUnitsPairs));
		gap.interval.why = new PhNumeral(gap.interval, gap.interval.dividePoint);
		gap.interval.why.gen(settings);
		if(this.dividePoint && this.dividePoint.timeTyp) {
			gap.direct = this.createPh(baseAdverbs.inTimePair);
		} else {
			gap.direct = this.createPh(baseAdverbs.agoTimePair);
		}
		this.gap = gap;
		return gap;
	};

	PhTime.prototype.genPeriod = function(settings) {
	};

	PhTime.prototype.genClock = function(settings) {
		if(this.parent && this.parent.constructorFunc === PhVerb) return; // иначе "он любил в 14:21".
		if(this.clock) {
			if(this.clock.hours) this.clock.hours = this.clock.hours.destruct();
			if(this.clock.minutes) this.clock.minutes = this.clock.hours.minutes();
		}
		if(this.preposition) this.preposition = this.preposition.destruct();
		if(this.dividePoint && this.dividePoint.timeTyp === 1) return;
		this.clock = {};
		this.preposition = this.createPh(baseAdverbs.atTimePair);
		this.clock.hours = this.createPh(baseNouns.timeUnitsPairs[1]);
		this.clock.hours.why = new PhNumeral(this.clock.hours, this.clock.hours.dividePoint);
		this.clock.hours.why.val = randomInt(23);
		this.clock.minutes = this.createPh(baseNouns.timeUnitsPairs[0]);
		this.clock.minutes.why = new PhNumeral(this.clock.minutes, this.clock.minutes.dividePoint);
		this.clock.minutes.why.val = randomInt(59);
		return this.clock;
	};

	PhTime.prototype.genDate = function(settings, wordsCollection) {
		if(this.date) this.date = this.date.destruct();
		if(this.preposition) this.preposition = this.preposition.destruct();
		if(this.dividePoint && this.dividePoint.timeTyp === 1) return;
		var flags = Noun.prototype.genFlags('день');
		pushAll(flags, Noun.prototype.genFlags('месяц'));
		var noun = new PhNoun(this, this.dividePoint);
		settings = cloneStruct(settings);
		settings.sStage = 0;
		settings.sPron = 0;
		settings.noSubwords = 1;
		if(!noun.gen(settings, wordsCollection, flags)) {
			noun = noun.destruct();
			return;
		}
		var onlyMonth;
		if(noun.flagsTest('месяц')) onlyMonth = 1;
		var rand = randomInt(40);
		if(rand < 28) {
			if(onlyMonth) {
				noun.whyIn = new PhNumeral(noun, noun.dividePoint);
				noun.whyIn.val = rand % 28 + 1;
				onlyMonth = 0;
			} else {
				if(this.dividePoint.timeTyp) {
					noun.adjectives = noun.createPh(baseAdjectives.timeNextPair);
				} else {
					noun.adjectives = noun.createPh(baseAdjectives.timeLastPair);
				}
			}
		}
		if(onlyMonth) {
			this.preposition = this.createPh(baseAdverbs.inDatePair);
		} else {
			this.preposition = this.createPh(baseAdverbs.onDatePair);
		}
		this.date = noun;
		return this.date;
	};
	PhTime.prototype.genYear = function(settings) {
		if(this.year) this.year = this.year.destruct();
		if(this.preposition) this.preposition = this.preposition.destruct();
		if(this.dividePoint && this.dividePoint.timeTyp === 1) return;
		var direct = -1;
		if(this.dividePoint && this.dividePoint.timeTyp) direct = 1;
		this.year = this.createPh(baseNouns.timeUnitsPairs[5]);
		var rand = randomInt(10);
		this.preposition = this.createPh(baseAdverbs.inDatePair);
		if(rand > 1) {
			this.year.adjectives = new PhNumeral(this.year, this.year.dividePoint);
			this.year.adjectives.val = new Date().getFullYear() + rand * direct;
		} else {
			this.year.adjectives = new PhAdjective(this.year, this.year.dividePoint);
			if(rand) {
				if(direct > 0) {
					this.year.adjectives.addWordPair(baseAdjectives.timeNextPair);
				} else {
					this.year.adjectives.addWordPair(baseAdjectives.timeLastPair);
				}
			} else {
				this.year.adjectives.addWordPair(baseAdjectives.thisPair);
			}
		}
		return this.year;
	};

	PhTime.prototype.mathRank = function(settings) {
		var ret = settings.sPhTime;
		return ret;
	};

	PhTime.prototype.writeEn = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var ret;
		var en = this.en; // Хранятся переменные для английской фразы.
		writeCommon(this, en);
		if(this.preposition) {
			en.prepositionStr = this.preposition.writeEn();
		}
		if(this.actDirect) {
			en.actStr = this.actDirect.act.writeEn();
			en.directStr = this.actDirect.direct.writeEn();
			ret = joins(en.directStr, en.actStr);
		} else if(this.gap) {
			en.intervalStr = this.gap.interval.writeEn();
			en.directStr = this.gap.direct.writeEn();
			if(en.directStr == 'in') {
				ret = joins(en.directStr, en.intervalStr);
			} else {
				ret = joins(en.intervalStr, en.directStr);
			}
		} else if(this.clock) {
			var minutesStr = '';
			var pastStr = '';
			var toStr = '';
			if(this.clock.minutes) {
				var minutesVal = this.clock.minutes.why.val;
				if(minutesVal > 30) {
					this.clock.minutes.why.val = 60 - minutesVal;
					toStr = this.createPh(baseAdverbs.toTimePair).writeEn();
				} else {
					pastStr = this.createPh(baseAdverbs.pastTimePair).writeEn();
				}
				minutesStr = this.clock.minutes.why.writeEn();
				this.clock.minutes.why.val = minutesVal;
			}
			var pmStr = '';
			var hoursStr = '';
			if(this.clock.hours) {
				var hoursVal = this.clock.hours.why.val;
				if(hoursVal > 12) {
					pmStr = 'p. m.';
					this.clock.hours.why.val -= 12;
				}
				if(toStr) {
					this.clock.hours.why.val += 1;
				}
				hoursStr = this.clock.hours.why.writeEn();
				this.clock.hours.why.val = hoursVal;
			}
			ret = joins(en.prepositionStr, minutesStr, pastStr, toStr, hoursStr, pmStr);
		} else if(this.date) {
			ret = joins(en.prepositionStr, this.date.writeEn());
		} else if(this.year) {
			en.yearStr = this.year.writeEn();
			if(this.year.adjectives && ['next', 'this', 'last'].indexOf(this.year.adjectives.en.wordsStr) !== -1) {
				en.prepositionStr = '';
			}
			ret = joins(en.prepositionStr, en.yearStr);

		} else if(this.adverb) {
			ret = this.adverb.writeEn();
		}
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	var writeCommon = function(th, wr) {
	};

	PhTime.prototype.writeRu = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var ret;
		var ru = this.ru; // Хранятся переменные для русской фразы.
		writeCommon(this, ru);
		if(this.preposition) {
			ru.prepositionStr = this.preposition.writeRu();
		}
		if(this.actDirect) {
			ru.directStr = this.actDirect.direct.writeRu();
			if(ru.directStr === 'перед') {
				ru.actStr = this.actDirect.act.writeRu(4);
			} else {
				ru.actStr = this.actDirect.act.writeRu(1);
			}
			ret = joins(ru.directStr, ru.actStr);
		} else if(this.gap) {
			ru.intervalStr = this.gap.interval.writeRu(3);
			ru.directStr = this.gap.direct.writeRu();
			if(ru.directStr == 'через') {
				ret = joins(ru.directStr, ru.intervalStr);
			} else {
				ret = joins(ru.intervalStr, ru.directStr);
			}
		} else if(this.clock) {
			var hoursStr = '';
			if(this.clock.hours) {
				hoursStr = this.clock.hours.why.writeRu();
			}
			var minutesStr = '';
			if(this.clock.minutes) {
				minutesStr = this.clock.minutes.why.writeRu();
				if(minutesStr.length === 1) minutesStr = '0' + minutesStr;
			}
			ret = joins(ru.prepositionStr, hoursStr, ':', minutesStr);
		} else if(this.date) {
			if(this.date.flagsTest('месяц')) {
				if(this.date.whyIn) {
					ru.dateStr = this.date.writeRu(1);
					ru.prepositionStr = '';
				} else {
					ru.dateStr = this.date.writeRu(5);
				}
			} else {
				ru.dateStr = this.date.writeRu(0);
				if(ru.dateStr === 'вторник') ru.prepositionStr = 'во';
			}
			ret = joins(ru.prepositionStr, ru.dateStr);
		} else if(this.year) {
			ru.yearStr = this.year.writeRu(5);
			ret = joins(ru.prepositionStr, ru.yearStr);
		} else if(this.adverb) {
			ret = this.adverb.writeRu();
			if(!this.parent) ret = trim(ret);
		}
		return ret;
	};

	PhTime.prototype.mathRank = function(settings) {
		var ret = settings.sPhTime;
		if(this.root) {
			if(this.root.counts && this.root.counts[this.phWordTypeName] > 1) { // counts обычно инкрементируется до вызова gen() .
				ret = 0;
			}
		}
		return ret;
	};

})();
var PhPlace = function(parent, dividePoint) {
	this.construct(parent, dividePoint);
};

(function() {
	PhProtoWord.registrPhTyp(PhPlace, PhAdverb, 0, 'PhPlace');

	PhPlace.prototype.construct = function(parent, dividePoint) {
		PhAdverb.prototype.construct.call(this, parent, dividePoint);
	};

	PhPlace.prototype.gen = function(settings, wordsCollection) {

	};

	PhPlace.prototype.mathRank = function(settings) {
		var ret = settings.sPhPlace;
		return ret;
	};

	PhPlace.prototype.writeEn = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var en = this.en; // Хранятся переменные для английской фразы.
		writeCommon(this, en);
		PhAdverb.prototype.writeEn.call(this);
		var ret = joins(en.noStr, en.veryStr, en.the, en.comparativeStr, en.wordsStr, en.adverbsStr, en.thanStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	var writeCommon = function(th, wr) {
	};

	PhPlace.prototype.writeRu = function() {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам.
		var ru = this.ru; // Хранятся переменные для русской фразы.
		writeCommon(this, ru);
		PhAdverb.prototype.writeRu.call(this);
		var ret = joins(ru.noStr, ru.veryStr, ru.comparativeStr, ru.wordsStr, ru.adverbsStr, ru.thanStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

})();
var PhVerb = function(parent, dividePoint) {
	this.construct(parent, dividePoint);
};

(function() {
	PhProtoWord.registrPhTyp(PhVerb, PhProtoVerb, 0, 0, 'PhVerb');

	PhVerb.prototype.construct = function(parent, dividePoint) {
		this.gerund = 0;
		PhProtoVerb.prototype.construct.call(this, parent, dividePoint);
	};

	PhVerb.prototype.afterAllGen = function(settings) {
		if(this.parent && this.parent.whatDo === this && (this.parent.word && this.parent.word[0].getFormed() in onlyGerundVerbsMap || this.verbCheckboxesTest('продолжительное'))) {
			if(this.dividePoint) {
				if(!this.dividePoint.timeTyp || this.dividePoint.allPhWordsInside.length > 3) {
					this.gerund = 1;
				}
			}
		}
	};

	PhVerb.prototype.mathRank = function(settings) {
		var ret = settings.sPhVerb;
		return ret;
	};

	PhVerb.prototype.writeEn = function(pron, timeTyp, formTyp) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		// pron: индекс местоимения см. prons.js. timeTyp: 0-прошл 1-наст, 2-буд. form: 0-вопр, 1-утв, 2-отриц.

		var en = this.en; // Хранятся переменные для русской фразы
		writeCommon(this, en, pron, timeTyp, formTyp);
		if(en.pron === undefined && this.dividePoint && this.dividePoint.nouns) { // более глубокой вложенности нет, либо она уже не относится к данному обороту.
			en.gender = this.dividePoint.nouns.en.gender;
			if(this.dividePoint.nouns.en.pron !== undefined) {
				en.pron = this.dividePoint.nouns.en.pron;
			} else {
				en.pron = genderToProns[en.gender];
			}
		} else {
			en.gender = pronsToGender[en.pron];
		}
		PhProtoVerb.prototype.writeEn.apply(this, arguments);
		var noStr = en.noStr;
		if(en.noStr && this.parent && this.parent.constructorFunc === PhPhrase) noStr = ''; // пускай PhPhrase само вставляет noStr.
		var when1 = '';
		var when2 = en.whenStr;
		if(this.when && this.when.adverb && this.when.adverb.flagsTest('периодичность')) {
			when1 = when2; // Наречия частотности (always, often, seldom, sometimes, never, usually, generally) обычно ставятся перед глаголом.
			when2 = '';
		}
		var ret = joins(noStr, en.adverbStr, when1, en.veryStr, en.comparativeStr, en.wordsStr, en.whatDoStr, en.usingWhatStr, en.whereStr, en.fromStr, en.toStr, en.forStr, en.thanStr, en.aboutStr, en.whomStr, when2);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhVerb.prototype.getFormedWordEn = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		if(this.parent) {
			if(this.parent && (this.parent.toBe && this.parent.toBe.continuousVerbs === this || this.parent.presentContinuous && this.parent.verbs === this)) {
				return this.word[0].getFormedIng();
			}
			if(this.gerund) {
				return this.word[0].getFormedIng();
			}
			if(this.parent.whatDo === this) { // значит инфинитив.
				if(this.verbCheckboxesTest('продолжительное')) {
					return 'to be ' + this.word[0].getFormedIng();
				} else { // Perfect и Perfect Continuous нами не употребляются (и, вероятно, не научимся).
					return 'to ' + this.word[0].getFormed();
				}
			}
			if(this.parent.perfect) {
				return this.word[0].getFormed(1); // в perfect всегда III фоама (-ed).
			}
		}
		return this.word[0].getFormed(this.en.pron, this.en.timeTyp, this.en.formTyp);
	};

	var writeCommon = function(th, wr, pron, timeTyp, formTyp) {
		if(pron !== undefined) wr.pron = pron;
		if(timeTyp !== undefined) wr.timeTyp = timeTyp;
		if(formTyp !== undefined) wr.formTyp = formTyp;
		if(wr.timeTyp === undefined && th.dividePoint) {
			wr.timeTyp = th.dividePoint.timeTyp;
		}
		if(wr.formTyp === undefined && th.dividePoint) {
			wr.formTyp = th.dividePoint.formTyp;
		}
	};

	PhVerb.prototype.writeRu = function(pron, timeTyp, formTyp) {
		// Возвращает часть предложения, относящуюся к этому слову или этим словам
		// pron: индекс местоимения см. prons.js. timeTyp: 0-прошл 1-наст, 2-буд. formTyp: 0-вопр, 1-утв, 2-отриц.
		var ru = this.ru; // хранятся переменные для русской фразы.
		writeCommon(this, ru, pron, timeTyp, formTyp);
		if(this.gerund) {
			return this.word[1].getFormedNoun();
		}
		if(ru.pron === undefined && this.dividePoint && this.dividePoint.nouns) { // более глубокой вложенности нет, либо она уже не относится к данному обороту.
			ru.gender = this.dividePoint.nouns.ru.gender;
			if(this.dividePoint.nouns.ru.pron !== undefined) {
				ru.pron = this.dividePoint.nouns.ru.pron;
			} else {
				ru.pron = genderToProns[ru.gender];
			}
		} else {
			ru.gender = pronsToGender[ru.pron];
		}
		PhProtoVerb.prototype.writeRu.apply(this, arguments);
		var noStr = ru.noStr;
		if(ru.noStr && this.parent && this.parent.constructorFunc === PhPhrase) noStr = ''; // пускай PhPhrase само вставляет noStr.
		var ret = joins(noStr, ru.adverbStr, ru.veryStr, ru.comparativeStr, ru.wordsStr, ru.whatDoStr, ru.usingWhatStr, ru.whereStr, ru.fromStr, ru.toStr, ru.forStr, ru.thanStr, ru.aboutStr, ru.whomStr, ru.whenStr);
		if(!this.parent) ret = trim(ret);
		return ret;
	};

	PhVerb.prototype.getFormedWordRu = function() {
		// Возвращает строковое предсавление основного слова в нужном падеже(и т.п.).
		return this.word[1].getFormed(this.ru.pron, this.ru.timeTyp, this.ru.formTyp);
	};

	PhVerb.genToBe = function(parent, dividePoint) {
		// Генерирует чистый PhVerb с установленным словом to be.
		var ret = new PhVerb(parent, dividePoint);
		ret.addWordPair(baseVerbs.toBePair);
		return ret;
	};

})();

var PhPhrase = function(parent, dividePoint) {
	// Из слов может содержать только взаимосвязанные PhVerb и PhNoun, остальные части речи цепляются к ним.
	// Еще может содержать особые формы(because, but, ...) с прикрепленными PhPhrase.
	this.timeTyp = 0;
	this.formTyp = 0;
	if(!parent) {
		this.counts = {}; // подсчитывает, сколько Ph-слов каждого типа присутствует в фразе. Работает только в случае корневой фразы.
		this.allWords = []; // Чтобы все знали, и не использовали лишний раз в фразе это слово.
		this.allPhWordsInside = [];
	}
	this.construct(parent, dividePoint);
};

(function(){

	PhProtoWord.registrPhTyp(PhPhrase, PhProtoWord, 0, 0, 'PhPhrase');
	PhPhrase.prototype.stdSettings = {
		sListing: 0, // как часто использовать перечисления.
		sToBe: 0.4, // как часто использовать конструкцию toBe.
		sReductions: 0, // как часто использовать сокращения.
		sSubphrases: 0, // как часто использовать подфразы.
		sComplexInfinitive: 1, // как часто использовать сложные формы инфинитива.
		noSubwords: false, // запретить подслова.
		sPhNoun: 1,
		sPron: 0.5, // подменять в PhNoun существительные на местоимения.
		sOf: 1, // как часто использовать принадлежность существительному/местоимению.
		sOfPron: 0, // как часто можно использовать в of местоимения.
		sPhNumeral: 0,
		sStage: 0.7, // как часто выбирать не дефолтные значения в stage.
		noVery: false, // не использовать степени в stage.
		noComparative: false, // не использовать сравнения в stage.
		sThan: 0.9, // как часто использовать сравнение с чем то.
		sPhPreposition: 0.8,
		sPhAdjective: 0.8,
		sPhParticiple: 0,
		sPhAttributiveClause: 1,
		sPhVerb: 1,
		sPhAdverb: 0.8,
		sWhom: 1,
		sUsingWhat: 1,
		sWhatDo: 1,
		sPhPlace: 0.6, // как часто использовать указания на место/места.
		sTimeTypContinuous: 1,
		sTimeTypPerfect: 1,
		sTimeTypPrefectContinuous: 1,
		sTimeTypFutureInThePast: 1,
		sPhTime: 0.8, // как часто использовать указания на время.
		sTimeAdverb: 1, // как часто использовать в PhTime наречия времени.
		sTimeGap: 1, // как часто использовать указания отдаленность времени.
		sTimeActDirect: 1, // как часто использовать до/после процесса/действия.
		sTimeClock: 1, // как часто использовать в PhTime часы-минуты.
		sTimeDate: 1, // как часто использовать указания на дни-месяцы.
		sTimeYear: 1, // как часто использовать указания на годы.
	};
	PhPhrase.prototype.nullSettings = {};
	for(var k in PhPhrase.prototype.stdSettings) {
		var v = PhPhrase.prototype.nullSettings[k];
		if(v === true || v === false) {
			v = true;
		} else {
			v = 0;
		}
		PhPhrase.prototype.nullSettings[k] = v;
	}

	PhPhrase.prototype.construct = function(parent, dividePoint) {
		PhProtoWord.prototype.construct.call(this, parent, dividePoint);
		this.secondGensQueue = [];
	};

	PhPhrase.prototype.normalizeSettings = function(settings) {
		// Устраняет известные противоречия в настройках.
		if(!settings) settings = {};
		for(var k in this.stdSettings) {
			if(settings[k || 0] === undefined) settings[k || 0] = this.stdSettings[k || 0];
		}
		return settings;
	};

	PhPhrase.prototype.gen = function(settings) {
		// генерирует случайную структуру для фразы
		this.genErrors = 0;
		if(this.parent) {
			this.comments = this.parent.dividePoint.comments;
		} else {
			this.comments = [];
		}
		this.settings = this.normalizeSettings(settings);
		this.setBySettings(settings);
		if(this.parent && this.parent.noGenSubwords) return;
		this.genTimeTyp(settings);
		this.genFormTyp(settings);

		this.genToBe(settings);
		if(!this.toBe && this.nouns && this.nouns.parent === this) {
			this.nouns = this.nouns.destruct();
		}

		//if(randomInt()) {
			if(!this.verbs) {
				this.genVerbs(settings);
			}
			if(this.toBe === 1) this.genToBe(settings);
			if(!this.nouns) {
				this.genNouns(settings);
			}
		/*} else {
			if(!this.nouns) this.genNouns(settings);
			if(!this.verbs) this.genVerbs(settings);
			if(this.toBe === 1) this.genToBe(settings);
		}*/

		// TODO: добавить возможность выбирать сначала существительное, за тем глагол.

		if(!this.genErrors && this.nouns && (this.toBe || this.verbs)) {
			for(var i = 0; i < this.allPhWordsInside.length; i++) {
				if(this.allPhWordsInside[i] && this.allPhWordsInside[i].afterGen) this.allPhWordsInside[i].afterGen(settings);
			}
			this.runSecondQueue(settings);
			return 1;
		}
	};

	PhPhrase.prototype.genTimeTyp = function(settings) {
		this.timeTyp = randomInt(2);
	};

	PhPhrase.prototype.setTimeSpec = function(settings) {
		// В английском есть дополнительный параметр времени: Indefinite || Continuous || Perfect || Perfect Continuous .
		// А еще есть время Future in the Past .
		this.perfect = 0;
		this.prefectContinuous = 0;
		this.futureInThePast = 0;
		if(this.verbs) {
			if(this.parent && !this.parent.dividePoint.timeTyp) {
				this.sequenceOfTenses(settings);
			} else {
				this.autosetFutureInPast(settings);
				this.autosetContinuous(settings);
				if(!this.toBe) {
					this.autosetPrefect(settings);
					this.autosetPrefectContinuous(settings);
				}
			}
		}
	};
	PhPhrase.prototype.sequenceOfTenses = function(settings) {
		// Согласование времен.
		if(this.timeTyp === 2) {
			this.setFutureInPast();
		} if(this.timeTyp === 1) {
			this.autosetContinuous(settings);
		} else {
			this.autosetPrefect(settings);
			this.autosetPrefectContinuous(settings);
		}
	};
	PhPhrase.prototype.autosetContinuous = function(settings) {
		if(this.verbs && !this.verbs.verbCheckboxesTest('совершенное') && this.verbs.verbCheckboxesTest('продолжительное') && this.verbs.verbCheckboxesTest('занимает')) {
			if(this.timeTyp === 1 || this.verbs.when) { // http://www.alleng.ru/mybook/3gram/6verb_15.htm#table224 - Past Continuous употребляется для выражения:1. Действия, протекавшего в указанный момент в прошлом, который может быть обозначен:а) обстоятельством времени;б) другим прошедшим действием в Past Indefinite;в) другим одновременным действием в Past Continuous;г) контекстом или ситуацией.2. Длительного действия, протекавшего в определен. период времени в прошлом.
				if(this.verbs.word && !(this.verbs.word[0].getFormed() in baseVerbs.notContinuousVerbsMap) && !this.verbs.verbFlagsTest('обладать')) {
					if(true) { // Кажется, если у глагола есть некие наречия, то continuous маловероятен.
						this.setContinuous(settings);
					}
				}
			}
		}
	};
	PhPhrase.prototype.setContinuous = function(settings) {
		if(this.toBe && this.toBe.continuousVerbs) return;
		if(!settings.sToBe) this.genErrors = 1;
		this.toBe = {};
		this.toBe.continuousVerbs = this.verbs;
		this.verbs = 0;
	};
	PhPhrase.prototype.autosetPrefect = function(settings) {
		if(this.verbs && this.verbs.verbCheckboxesTest('совершенное')) {
			if(this.timeTyp === 2) {
				this.comments.push('Можно было использовать Future Prefect, но оно используется очень редко.');
				// употребляется редко, пусть пока идет лесом.
			} else {
				if(!this.verbs.when) {
					this.setPrefect(settings);
				}
			}
		}
	};
	PhPhrase.prototype.setPrefect = function(settings) {
		if(!this.sPresent) this.genErrors = 1;
		this.perfect = 1;
	};
	PhPhrase.prototype.autosetPrefectContinuous = function() {
		if(!this.perfect) {
			if(this.verbs && this.verbs.verbCheckboxesTest('продолжительное')) {
				if(this.timeTyp === 2) {
					this.comments.push('Можно было использовать Prefect Continuous, но оно используется очень редко.');
					// употребляется редко, пусть пока идет лесом.
				} else if(this.timeTyp) {
					if(this.verbs.when && this.verbs.when.duration) {
						this.setPrefectContinuous(settings);
					} else if(this.verbs.verbCheckboxesTest('совершенное')) {
						this.setPrefectContinuous(settings);
					}
				} else {
					if(this.verbs.when && this.verbs.verbCheckboxesTest('совершенное') && this.verbs.when.duration) {
						this.setPrefectContinuous(settings);
					}
				}
			}
		}
	};
	PhPhrase.prototype.setPrefectContinuous = function(settings) {
		if(!this.sPresentContinuous) this.genErrors = 1;
		this.prefectContinuous = 1;
	};
	PhPhrase.prototype.autosetFutureInPast = function(settings) {
		this.futureInThePast = 0;
		if(this.timeTyp && this.parent && !this.parent.dividePoint.timeTyp) {
			this.setFutureInPast(settings);
		}
	};
	PhPhrase.prototype.setFutureInPast = function(settings) {
		this.futureInThePast = 1;
	};

	PhPhrase.prototype.genFormTyp = function(settings) {
		if(this.parent) {
			if(!this.parent.dividePoint.timeTyp) {}
		} else {
			this.formTyp = randomInt(2);
		}
	};

	PhPhrase.prototype.genVerbs = function(settings) {
		if(this.toBe) {
			//this.verbs = PhVerb.genToBe(this);
			return;
		}
		if(this.verbs) this.verbs = this.verbs.destruct();
		this.verbs = new PhVerb(this);
		if(this.formTyp === 2) {
			if(!this.verbs.stage) this.verbs.stage = {};
			this.verbs.stage.no = true;
		}
		if(!this.verbs.gen(settings || this.settings)) {
			this.verbs = undefined;
		}
		if(!this.verbs && !this.toBe) {
			console.log('нет глагола, будет to be');
			this.toBe = 1;
		}
		return true;
	};
	PhPhrase.prototype.genNouns = function(settings) {
		if(this.nouns) this.nouns = this.nouns.destruct();
		this.nouns = new PhNoun(this);
		if(!this.nouns.gen(settings || this.settings)) {
			console.log('нет существительного');
			this.nouns.mandateForOneWord = 1;
			if(!this.nouns.gen(settings || this.settings)) {
				this.nouns = this.nouns.destruct();
				return false;
			}
		}
		return true;
	};
	PhPhrase.prototype.genToBe = function(settings) {
		if(this.toBe && this.toBe !== 1) {
			if(this.toBe.adjectives) this.toBe.adjectives = this.toBe.adjectives.destruct();
			if(this.toBe.participles) this.toBe.participles = this.toBe.participles.destruct();
			if(this.toBe.when) this.toBe.when = this.toBe.when.destruct();
			if(this.toBe.continuousVerbs) this.toBe.continuousVerbs = this.toBe.continuousVerbs.destruct();
			this.toBe = 0;
		}
		if(this.toBe === 1 || randomBool(settings.sToBe)) {
			this.toBe = {};
			var rand = randomInt();
			for(i = 0; i < 2; i++) {
				if(rand - i) {
					this.toBe.adjectives = new PhAdjective(this, this);
					if(this.toBe.adjectives.gen(settings)) {
						break;
					} else {
						this.toBe.adjectives = this.toBe.adjectives.destruct();
					}
				} else {
					this.toBe.participles = new PhParticiple(this, this);
					if(this.toBe.participles.gen(settings)) {
						break;
					} else {
						this.toBe.participles = this.toBe.participles.destruct();
					}
				}
			}
			if(this.toBe.adjectives || this.toBe.participles) {
				this.toBe.when = new PhTime(this, this);
				if(!this.toBe.when.gen(settings)) {
					this.toBe.when = this.toBe.when.destruct();
				}
				if(!this.toBe.participles && !this.toBe.adjectives) {
					if(this.toBe.when) this.toBe.when = this.toBe.when.destruct();
					this.toBe = 0;
				}
			} else {
				this.toBe = 0;
			}
		}
	};

	PhPhrase.prototype.addToSecondQueue = function(obj) {
		this.secondGensQueue.push(obj);
	};
	PhPhrase.prototype.runSecondQueue = function(settings) {
		for(var i = 0; i < this.secondGensQueue.length; i++) {
			if(this.allPhWordsInside.indexOf(this.secondGensQueue[i]) !== -1) {
				this.secondGensQueue[i].runSecondGens(settings);
			}
		}
		this.setTimeSpec(settings);
	};

	PhPhrase.createRandomPhrase = function(settings) {
		var phr;
		for(var i = 0; i < 4; i++) { // даем 4 попытки.
			phr = new PhPhrase();
			if(phr.gen(settings)) {
				if(phr.genErrors) {
					continue;
				}
				var enStr = phr.writeEn();
				var ruStr = phr.writeRu();
				if(!enStr || !ruStr || phr.writeError) {
					continue;
				}
				return [enStr, ruStr, phr];
			}
		}
		return ['not generated', 'не сгенерировано', phr];
	};

	PhPhrase.prototype.writeEn = function(onlyWords) {
		// Пытается записать сгенерированную/установленную структуру английским текстом.
		var en = this.en; // Хранятся переменные для русской фразы.
		en.timeTyp = this.timeTyp;
		if(this.futureInThePast && en.timeTyp < 2) {
			en.timeTyp = 2;
		}
		this.writeCommon(this, en);

		en.pron = 6;
		en.nounsStr = '';
		if(this.dividePoint && this.dividePoint !== this && this.nouns === this.dividePoint.nouns) {
			// если this.nouns позаимствован у предка - оставляем строку пустой, ведь по смысту ясно о каком объекте идет речь. TODO: по крайней мере иногда нужно оставлять noun.
			en.pron = this.nouns.en.pron;
		} else if(this.nouns) {
			en.nounsStr = this.nouns.writeEn();
			en.pron = this.nouns.en.pron;
		}

		en.verbsStr = '';
		var doOrBe = ''; // это строка, которой будет заменены "does", "do", "did".
		en.toBeStr = '';
		en.whenStr = '';
		if(this.toBe) {
			doOrBe = PhVerb.genToBe(this).writeEn(en.pron, en.timeTyp, this.formTyp);
			if(this.toBe.adjectives) {
				en.toBeStr = this.toBe.adjectives.writeEn();
			} else if(this.toBe.participles) {
				en.toBeStr = this.toBe.participles.writeEn();
			} else if(this.toBe.continuousVerbs) {
				en.toBeStr = this.toBe.continuousVerbs.writeEn();
			} else {

			}
			if(this.toBe.when) {
				en.whenStr = this.toBe.when.writeEn();
			}
			if(en.timeTyp === 2 || this.formTyp === 1) {
				en.verbsStr = doOrBe;
			}
		}
		if(this.verbs) en.verbsStr = this.verbs.writeEn(en.pron, en.timeTyp, this.formTyp);
		en.startStr = '';
		en.afterNounStr = '';
		en.endPhrStr = '.';
		if(!this.formTyp) {
			if(en.timeTyp === 2) {
				en.startStr = this.futureInThePast ? 'would' : 'will';
				if(this.perfect) {
					en.afterNounStr = 'have';
				}
			} else if(en.timeTyp) {
				if(this.nouns.en.pron > 3) {
					en.startStr = doOrBe || 'does';
					if(this.perfect) en.startStr = 'has';
				} else {
					en.startStr = doOrBe || 'do';
					if(this.perfect) en.startStr = 'have';
				}
			} else {
				en.startStr = doOrBe || 'did';
				if(this.perfect) en.startStr = 'had';
			}
			en.endPhrStr = '?';
		} else if(this.formTyp === 1) {
			if(en.timeTyp === 2) {
				en.afterNounStr = this.futureInThePast ? 'would' : 'will';
				if(this.perfect) en.afterNounStr += ' have';
			} else if(en.timeTyp) {
				if(this.perfect) {
					if(this.nouns.en.pron > 3) {
						if(this.perfect) en.afterNounStr = 'has';
					} else {
						if(this.perfect) en.afterNounStr = 'have';
					}
				}
			} else {
				if(this.perfect) en.afterNounStr = 'had';
			}
		} else {
			if(en.timeTyp === 2) {
				en.afterNounStr = (this.futureInThePast ? 'would' : 'will') + ' not';
				if(this.perfect) en.afterNounStr += ' have';
			} else if(en.timeTyp) {
				if(this.nouns.en.pron > 3) {
					en.afterNounStr = doOrBe || 'does not';
					if(this.perfect) en.afterNounStr = 'has not';
				} else {
					en.afterNounStr = doOrBe || 'do not';
					if(this.perfect) en.afterNounStr = 'have not';
				}
			} else {
				en.afterNounStr = doOrBe || 'did not';
				if(this.perfect) en.afterNounStr = doOrBe || 'had not';
			}
		}
		if(this.prefectContinuous) en.afterNounStr = joins(en.afterNounStr, 'been');

		if(this.dividePoint) en.endPhrStr = '';
		var ret = joins(en.startStr, en.nounsStr, en.afterNounStr, en.verbsStr, en.toBeStr, en.whenStr, en.endPhrStr);
		if(!this.parent && !onlyWords) {
			ret = trim(ret);
			ret = ret[0].toUpperCase() + ret.substr(1);
		}
		return ret;
	};

	PhPhrase.prototype.writeCommon = function() {
	};

	PhPhrase.prototype.writeRu = function(onlyWords) {
		// Пытается записать сгенерированную/установленную структуру русским текстом
		var ru = this.ru; // Хранятся переменные для русской фразы.
		this.writeCommon(this, ru);

		ru.pron = 6;
		ru.nounsStr = '';
		if(this.dividePoint && this.dividePoint !== this && this.nouns === this.dividePoint.nouns) {
			// если this.nouns позаимствован у предка - оставляем строку пустой, ведь по смыслу ясно о каком объекте идет речь.
		} else if(this.nouns) {
			if(this.toBe === 1 && this.formTyp === 2) {
				ru.nounsStr = this.nouns.writeRu(1); // звук не было - звука не было.
			} else {
				ru.nounsStr = this.nouns.writeRu();
			}
		}
		if(this.nouns) {
			ru.pron = this.nouns.ru.pron;
			if(ru.pron === undefined) {
				if(this.nouns.why && this.nouns.why.val !== 1) {
					ru.pron = 2;
				} else {
					if(this.nouns.word) {
						ru.pron = genderToProns[this.nouns.word[1].gender];
					}
				}
			}
		}
		var timeTyp = this.timeTyp;
		var formTyp = this.formTyp;
		ru.verbsStr = '';
		if(this.verbs) ru.verbsStr = this.verbs.writeRu(ru.pron, timeTyp, formTyp);
		ru.endPhrStr = '.';
		if(!this.formTyp) {
			ru.endPhrStr = '?';
		}
		ru.reallyStr = '';
		if(!this.formTyp && !this.toBe) {
			ru.reallyStr = 'разве';
		}
		ru.willStr = '';
		var complete;
		if(this.verbs && this.verbs.word) {
			complete = this.verbs.word[1].verbCheckboxesValTest('совершенное');
		}
		if(timeTyp === 2) {
			if(!this.toBe) {
				if(!complete) ru.verbsStr = joins(PhVerb.genToBe(this).writeRu(ru.pron, timeTyp, formTyp), ru.verbsStr);
			}
		}
		ru.noStr = '';
		ru.toBeStr = '';
		if(formTyp === 2) ru.noStr = 'не';
		if(this.toBe === 1 && formTyp === 2 && timeTyp === 1) {
			ru.noStr = '';
			ru.verbsStr = 'нет';
		} else {
			if(this.toBe) {
				if(!complete) ru.toBeStr = PhVerb.genToBe(this).writeRu(ru.pron, timeTyp, formTyp);
				if(this.toBe.adjectives) {
					if(timeTyp === 1) ru.toBeStr = '';
					ru.toBeStr = joins(ru.toBeStr, this.toBe.adjectives.writeRu(pronsToGender[ru.pron], 0));
				} else if(this.toBe.participles) {
					if(timeTyp === 1) ru.toBeStr = '';
					ru.toBeStr = joins(ru.toBeStr, this.toBe.participles.writeRu(0, 0, pronsToGender[ru.pron], timeTyp !== 1 ? 4 : 0));
				} else if(this.toBe.continuousVerbs) {
					var contComplete;
					if(this.toBe.continuousVerbs.word) {
						contComplete = this.toBe.continuousVerbs.word[1].verbCheckboxesValTest('совершенное');
					}
					if(timeTyp !== 2 || contComplete) ru.toBeStr = '';
					ru.toBeStr = joins(ru.toBeStr, this.toBe.continuousVerbs.writeRu(ru.pron, timeTyp));
				} else {
					if(formTyp === 2 && !complete) {
						if(!timeTyp) {
							ru.toBeStr = 'было'; // исключение "Его/их/... не было".
						} else if(timeTyp === 2 && ru.pron < 4) {
							ru.toBeStr = 'будет';
						}
					}
				}
			}
		}
		ru.whenStr = '';
		if(this.toBe) {
			if(this.toBe.when) {
				ru.whenStr = this.toBe.when.writeRu();
			}
		}

		if(this.dividePoint) ru.endPhrStr = '';
		var ret = joins(ru.reallyStr, ru.nounsStr, ru.noStr, ru.willStr, ru.verbsStr, ru.toBeStr, ru.whenStr, ru.endPhrStr);
		if(!this.parent && !onlyWords) {
			ret = trim(ret);
			ret = ret[0].toUpperCase() + ret.substr(1);
		}
		return ret;
	};

	PhPhrase.prototype.mathRank = function(settings) {
		if(this.parent) {
			return settings.sSubphrases;
		}
		return 1;
	};

	PhPhrase.prototype.emptyAllWriteVars = function() {
		// Обнуляет все переменные this.ru и this.en внутри этой фразы. Они используются так, что повторно на них уже нельзя производить writeRu(), writeEn().
		this.ru = {};
		this.en = {};
		for(var i = 0; i < this.allPhWordsInside.length; i++) {
			if(this.allPhWordsInside[i]) {
				this.allPhWordsInside[i].ru = {};
				this.allPhWordsInside[i].en = {};
				if(this.allPhWordsInside[i].emptyAllWriteVars && this.allPhWordsInside[i] !== this) {
					this.allPhWordsInside[i].emptyAllWriteVars();
				}
			}
		}
	};

})();
var PhAttributiveClause = function(parent, dividePoint) {
	this.construct(parent, dividePoint);
};

(function() {
	PhProtoWord.registrPhTyp(PhAttributiveClause, PhPhrase, 0, 0, 'PhAttributiveClause');

	PhAttributiveClause.prototype.gen = function(settings, wordsCollection) {
		if(this.parent) {
			if(this.parent.flagsTest('одушевленное')) {
				this.pronAdjectiveAdverbPair = baseNouns.whoAttributivePair;
			} else {
				this.pronAdjectiveAdverbPair = baseNouns.whichAttributivePair;
			}
		} else {
			this.pronAdjectiveAdverbPair = getRandomElementFromArray([baseNouns.whoAttributivePair, baseNouns.whichAttributivePair]);
		}
		if(this.parent.constructorFunc !== PhNoun) {
			warning();
		}
		var ret = PhPhrase.prototype.gen.call(this, settings, wordsCollection);
		return ret;
	};

	PhAttributiveClause.prototype.sequenceOfTenses = function(settings) {
		// Согласование времен.
		if(this.timeTyp === 2) {
			this.setFutureInPast();
		} if(this.timeTyp === 1) {
			this.setFutureInPast();
		}
		/////////////////////////////////////////// this.genErrors = 1; // возможно лучше отказаться от такого варианта (по правилам может быть и Past Indefinite или Past Continuous и Past Perfect или Past Perfect Continuous, определить можно только понимая смысл фразы).
		this.autosetContinuous(settings);
		this.autosetPrefect(settings);
		this.autosetPrefectContinuous(settings);
	};

	PhAttributiveClause.prototype.genFormTyp = function() {
		this.formTyp = 1;
	};

	PhAttributiveClause.prototype.genNouns = function(settings) {
		this.nouns = this.parent;
	};

	PhAttributiveClause.prototype.mathRank = function(settings) {
		var ret = settings.sPhAttributiveClause;
		return ret;
	};

	PhAttributiveClause.prototype.writeEn = function() {
		var en = this.en;
		var ret = PhPhrase.prototype.writeEn.call(this);
		en.pronOrAdverbStr = this.pronAdjectiveAdverbPair[0].getFormed();
		return joins(en.pronOrAdverbStr, ret);
	};

	var writeCommon = function(th, wr, gender) {
	};

	PhAttributiveClause.prototype.writeRu = function(gender) {
		var ru = this.ru;
		var ret = PhPhrase.prototype.writeRu.call(this);
		ru.pronOrAdverbStr = this.pronAdjectiveAdverbPair[1].getFormed(0, pronsToGender[ru.pron], 0);
		return joins(ru.pronOrAdverbStr, ret);
	};

	PhAttributiveClause.prototype.getCurrentNoun = function() {
		return this.parent;
	};

})();
addToOnloadBody(function() {
	// Загружаем общие слова из файлов.
	win['setCW' || 0] = function(v, obj) {
		// ls[productName + '_' + v + '_' + p] = d; // а нафиг хранить это в localStorage, ведь файловый кеш работает не хуже.
		var i, n, typ, entries, entry, wds;
		var part = [];
		words.commonArray = [0];
		for(i = 1; i < obj.length; i++) {
			entry = obj[i];
			typ = wordsTypesByNum[entry[0]];
			wds = [];
			enWord = new typ.constructorEn(entry[1]);
			wds.push(enWord);
			words.commonArray.push(enWord);
			for(n = 0; n < entry[2].length; n++) {
				if(entry[1][n]) {
					ruWord = new typ.constructorRu(entry[2][n]);
					wds.push(ruWord);
				}
			}
			typ.collection.addWords(wds);
		}
		words.trigger('load');
	};
	var resp_script = domCreater('script');
	getJSON('commonWords/' + productVersion + '.js');
});

var iterateTrainingsPhSettings = cloneStruct(PhPhrase.prototype.nullSettings); // постепенно будем усложнять базовые настройки для последующих тренеровок.

addToOnloadBody(function() {
	var settings = {};
	settings.speed = 10;
	settings.order = 1;
	settings.PhSettings = iterateTrainingsPhSettings;
	iterateTrainingsPhSettings.sPhNoun = 1;
	iterateTrainingsPhSettings.sPron = 1;
	iterateTrainingsPhSettings.sPhVerb = 1;
	iterateTrainingsPhSettings = cloneStruct(iterateTrainingsPhSettings);

	var processor = hungTrainingPage('training-first', PhPhrase.createRandomPhrase, settings);
});
addToOnloadBody(function() {
	var settings = {};
	settings.speed = 10;
	settings.order = 1;
	settings.PhSettings = iterateTrainingsPhSettings;
	iterateTrainingsPhSettings.sToBe = 1;
	iterateTrainingsPhSettings.sPron = 0.7;
	iterateTrainingsPhSettings.sPhAdjective = 1;
	iterateTrainingsPhSettings = cloneStruct(iterateTrainingsPhSettings);

	var processor = hungTrainingPage('training-tobe', PhPhrase.createRandomPhrase, settings);
});
addToOnloadBody(function() {
	var settings = {};
	settings.speed = 999;
	settings.order = 1;
	settings.PhSettings = {};

	var processor = hungTrainingPage('training-turnable', PhPhrase.createRandomPhrase, settings);
});
addToOnloadBody(function() {
	var settings = {};
	settings.speed = 10;
	settings.order = 1;
	settings.PhSettings = iterateTrainingsPhSettings;
	iterateTrainingsPhSettings.sToBe = 0.5;
	iterateTrainingsPhSettings.sPron = 0.8;
	iterateTrainingsPhSettings.sPhAdverb = 1;
	iterateTrainingsPhSettings.sOf = 10;
	iterateTrainingsPhSettings.sOfPron = 0.7;
	iterateTrainingsPhSettings.noComparative = false;
	iterateTrainingsPhSettings.sThan = 1;
	iterateTrainingsPhSettings.sWhom = 10;
	iterateTrainingsPhSettings = cloneStruct(iterateTrainingsPhSettings);

	var processor = hungTrainingPage('training-prons-and-adjectives', PhPhrase.createRandomPhrase, settings);
});
addToOnloadBody(function() {
	var settings = {};
	settings.speed = 10;
	settings.order = 1;
	settings.PhSettings = iterateTrainingsPhSettings;
	iterateTrainingsPhSettings.sOf = 0.7;
	iterateTrainingsPhSettings.sWhom = 0.7;
	iterateTrainingsPhSettings.sSubphrases = 10;
	iterateTrainingsPhSettings.sPhNumeral = 10;
	iterateTrainingsPhSettings = cloneStruct(iterateTrainingsPhSettings);

	var processor = hungTrainingPage('training-attributive-clause-and-numeral', PhPhrase.createRandomPhrase, settings);
});
addToOnloadBody(function() {
	var settings = {};
	settings.speed = 10;
	settings.order = 1;
	settings.PhSettings = iterateTrainingsPhSettings;
	iterateTrainingsPhSettings.sPron = 0.4;
	iterateTrainingsPhSettings.sSubphrases = 0.7;
	iterateTrainingsPhSettings.sPhNumeral = 1;
	iterateTrainingsPhSettings.sPhTime = 10;
	iterateTrainingsPhSettings.sTimeAdverb = 0.7;
	iterateTrainingsPhSettings.sTimeActDirect = 0.7;
	iterateTrainingsPhSettings.sTimeGap = 0.7;
	iterateTrainingsPhSettings.sTimePeriod = 0.7;
	iterateTrainingsPhSettings.sTimeClock = 0.7;
	iterateTrainingsPhSettings.sTimeDate = 0.7;
	iterateTrainingsPhSettings.sTimeYear = 0.7;
	iterateTrainingsPhSettings.sTimeTypContinuous = 1;

	iterateTrainingsPhSettings = cloneStruct(iterateTrainingsPhSettings);

	var processor = hungTrainingPage('training-time-and-continuous', PhPhrase.createRandomPhrase, settings);
});
var domGetById = function(id) {
	return document.getElementById(id);
};
var domGetByClass = function(clas, elem) {
	return [].slice.call((elem || document).getElementsByClassName(clas));
};
var domGetBySelector = function(quer, elem) {
	return [].slice.call((elem || document).querySelectorAll(quer));
};
var domCreateDomElement = function(tag) {
	return document.createElement(tag);
};
var domCreater = function(tag, clas, parent, childs, other) {
	if(!tag) tag = 'div';
	var elem = domCreateDomElement(tag);
	if(clas) elem.className = clas;
	if(parent) domAppendChild(parent, elem);
	if(childs) {
		if(isString(childs)) {
			domSetInnerText(elem, childs);
		} else if(isArray(childs)) {
			for(var i = 0; i < childs.length; i++) {
				if(childs[i]) domAppendChild(elem, childs[i]);
			}
		} else {
			domAppendChild(elem, childs);
		}
	}
	if(other) {
		//
	}
	return elem;
};
var domGetChilds = function(elem) {
	if(!elem || !elem.childNodes) return [];
	if(elem) {
		return [].slice.call(elem.childNodes);
	}
};
var domAppendChild = function(parent, child) {
	if(parent && child) {
		parent.appendChild(child);
		return child;
	}
};
var domPrependChild = function(parent, child) {
	if(parent && child && parent.childNodes.length) {
		parent.insertBefore(child, parent.firstChild);
	} else {
		domAppendChild(parent, child);
	}
};
var domInsertBefore = function(child, next) {
	if(child && next) {
		next.parentNode.insertBefore(child, next);
		return child;
	}
};
var domRemove = function(elem) {
	if(isArray(elem)) {
		elem.forEach(domRemove);
	} else if(elem.parentNode) {
		elem.parentNode.removeChild(elem);
	}
	return elem;
};
var domAddCssClass = function(elem, clas) {
	if(elem && clas) {
		var i;
		if(isArray(clas)) {
			for(i = 0; i < clas.length; i++) {
				domAddCssClass(elem, clas[i]);
			}
		} else if(isArray(elem)) {
			for(i = 0; i < elem.length; i++) {
				domAddCssClass(elem[i], clas);
			}
		} else {
			if(elem.classList) {
				elem.classList.add(clas);
			} else {
				elem.className += ' ' + clas;
			}
		}
		return elem;
	}
};

var domRemoveCssClass = function(elem, clas) {
	if(elem && clas) {
		var i;
		if(isArray(clas)) {
			for(i = 0; i < clas.length; i++) {
				domRemoveCssClass(elem, clas[i]);
			}
		} else if(isArray(elem)) {
			for(i = 0; i < elem.length; i++) {
				domRemoveCssClass(elem[i], clas);
			}
		} else {
			if(elem.classList) {
				elem.classList.remove(clas);
			} else {
				elem.className = elem.className.replace(new RegExp('\\b' + clas + '\\b'), '');
			}
		}
		return elem;
	}
};
var domToggleCssClass = function(elem, clas) {
	if(elem, clas) {
		var i;
		if(isArray(clas)) {
			for(i = 0; i < clas.length; i++) {
				domToggleCssClass(elem, clas[i]);
			}
		} else if(isArray(elem)) {
			for(i = 0; i < elem.length; i++) {
				domToggleCssClass(elem[i], clas);
			}
		} else {
			elem.classList.toggle(clas);
		}
		return elem;
	}
};
var domShow = function(elem) {
	if(elem) {
		if(isArray(elem)) {
			for(i = 0; i < elem.length; i++) {
				domShow(elem[i]);
			}
		} else {
			if(elem.className.match(/(?:^|\s)trns(?:$|\s)/)) {
				windowSetTimeout(function() {
					elem.className = elem.className.replace(/(?:^|\s)(o|w|h|s)0(?:$|\s)/g, function(str, p) {return ' ' + p + '1 ';});
				}, 100);
			}
		}
	}
	return domRemoveCssClass(elem, 'hide');
};
var domHide = function(elem, cb) {
	if(elem) {
		if(isArray(elem)) {
			for(i = 0; i < elem.length; i++) {
				domHide(elem[i]);
			}
		} else {
			if(elem.className.match(/(?:^|\s)trns(?:$|\s)/)) {
				elem.className = elem.className.replace(/(?:^|\s)(o|w|h|s)1(?:$|\s)/g, function(str, p) {return ' ' + p + '0 ';});
				windowSetTimeout(function() {
					domAddCssClass(elem, 'hide');
					if(cb) cb();
				}, 600);
				return elem;
			} else {
				domAddCssClass(elem, 'hide');
				if(cb) cb();
				return elem;
			}
		}
	}
};
var domOffset = function(elem) {
	var x = 0;
	var y = 0;
	do {
		x += elem.offsetLeft;
		y += elem.offsetTop;
		elem = elem.offsetParent;
	} while(elem);
	return {left:x, top:y};
};
var domSetAttribute = function(elem, nam, val) {
	elem.setAttribute(nam, val);
	return elem;
};
var domGetData = function(elem, nam) {
	return elem.getAttribute('data-' + nam);
};
var domSetData = function(elem, nam, val) {
	domSetAttribute(elem, 'data-' + nam, val);
	return elem;
};

var domSetInnerHTML = function(elem, text) {
	if(elem) {
		elem.innerHTML = text;
		return elem;
	}
};
var domSetInnerText = function(elem, text) {
	if(isArray(elem)) {
		for(i = 0; i < elem.length; i++) {
			domSetInnerText(elem[i], text);
		}
	} else if(elem) {
		if(typeof(elem.innerText) != 'undefined') {
			elem.innerText = text;
		} else {
			elem.textContent = text;
		}
		return elem;
	}
};
var domGetInnerText = function(elem) {
	if(elem) {
		if(typeof(elem.innerText) != 'undefined') {
			return elem.innerText;
		} else {
			return elem.textContent;
		}
	}
};
var domBindEvent = function(elem, eventName, f) {
	if(!eventName) eventName = 'click';
	if(elem) {
		var i;
		if(isArray(elem)) {
			for(i = 0; i < elem.length; i++) {
				domBindEvent(elem[i], eventName, f);
			}
		} else {
			if(isArray(f)) {
				for(i = 0;  i < f.length; i++) {
					domBindEvent(elem, eventName, f[i]);
				}
			} else if(isArray(eventName)) {
				for(i = 0;  i < eventName.length; i++) {
					domBindEvent(elem, eventName[i], f);
				}
			} else if(elem.addEventListener) {
				elem.addEventListener(eventName, f, false);
			} else if(elem.attachEvent) {
				elem.attachEvent('on' + eventName, f);
			}
		}
		return elem;
	}
};
var domUnbindEvent = function(elem, eventName, f) {
	if(elem) {
		if(elem.addEventListener) {
			elem.removeEventListener(eventName, f, false);
		} else if(elem.attachEvent) {
			elem.detachEvent('on' + eventName, f);
		}
		return elem;
	}
};
var domCancelEvent = function(e) {
	if(e) {
		if(e.preventDefault) {
			e.preventDefault();
		} else {
			e.returnValue = false;
		}
		e.domCancelEvent = true; // это для своей реализации аналогичного функционала.
	}
};
var domCancelEventPropagation = function(e) {
	if(e) {
		if(e.stopPropagation) {
			e.stopPropagation();
		} else {
			e.cancelBubble = true;
		}
	}
};

var windowBindToKeys; // Навешивает обработчики на нажатия определенных клавиш.
var windowUnbindToKeys;
var windowKeyIsPressed;
var windowKeyNoPressed;
(function() {
	var keys = {};
	windowBindToKeys = function(code, act, func) {
		var ob = keys[code];
		if(!ob) ob = keys[code] = {};
		if(act == 'press') {
			if(!ob.onPress) ob.onPress = [];
			ob.onPress.push(func);
		} else if(act == 'down') {
			if(!ob.onDown) ob.onDown = [];
			ob.onDown.push(func);
		} else if(act == 'up') {
			if(!ob.onUp) ob.onUp = [];
			ob.onUp.push(func);
		}
	};
	windowUnbindToKeys = function(code, act, func) {
		var ob = keys[code];
		if(ob) {
			if(ob[act]) {
				if(act == 'press') ob = ob.onPress;
				if(act == 'down') ob = ob.onDown;
				if(act == 'up') ob = ob.onUp;
				for(var i = 0; i < ob.length; i++) {
					if(ob[i] == func) {
						ob[i] = undefined;
					}
				}
			}
		}
	};
	windowKeyIsPressed = function(code) {
		if(isObject(keys[code])) return keys[code].pressed;
	};
	windowKeyNoPressed = function(code) {
		if(isObject(keys[code])) keys[code].pressed = 0;
	};
	domBindEvent(window, 'keydown', function(e) {
		var ob = keys[e.keyCode];
		if(!ob) {
			keys[e.keyCode] = ob = {};
		}
		ob.pressed = 1;
		if(ob.onDown) {
			for(var i = 0; i < ob.onDown.length; i++) {
				if(ob.onDown[i] && typeof ob.onDown[i] == 'function') {
					ob.onDown[i](e);
					if(e.domCancelEvent) break;
				}
			}
		}
	});
	domBindEvent(window, 'keyup', function(e) {
		var ob = keys[e.keyCode];
		if(ob) {
			var i;
			if(ob.onUp) {
				for(i = 0; i < ob.onUp.length; i++) {
					if(ob.onUp[i] && typeof ob.onUp[i] == 'function') {
						ob.onUp[i](e);
						if(e.domCancelEvent) break;
					}
				}
			}
			if(ob.pressed && ob.onPress) {
				for(i = 0; i < ob.onPress.length; i++) {
					if(ob.onPress[i] && typeof ob.onPress[i] == 'function') {
						ob.onPress[i](e);
						if(e.domCancelEvent) break;
					}
				}
			}
			ob.pressed = 0;
		}
	});
})();
// Тут вносим некоторые изменения во многие эллементы html.

addToOnloadBody(function() {
	if(!('-webkit-transform' in document.body.style) && !('-ms-transform' in document.body.style) && !('transform' in document.body.style)) {
		domAddCssClass(document.body, 'unsupTransform');
	}
	domBindEvent(domGetBySelector('#fastButtons button'), 'mousedown', function(e) {
		domCancelEventPropagation(e);
	});
});
var createAutocompletedInput = function(cbVariants, cbSet, cbChange) {
	// создает text-input с автодополнениями.
	// cbVariants должна возвращать вариатны автодополнения для введенных букв. В паре с текстом автодополнения может идти некое не текстовое значение (объект).
	// cbSet коллбек срабатывает после выбора автодополнения, он передает объект из пары автодополнения.
	// cbChange вызывается после каждого изменения вводимого текста, передается новый текст.
	var input, ul;
	var elem = domCreater('div', 'autocompletedInput', 0, [input = domCreater('input'), domCreater('div', 0, 0, ul = domCreater('ul'))]);
	input.type = 'text';
	domHide(ul);
	var val = input.value;
	var lis = [];
	var lisTexts = [];
	var lisSets = [];
	var selectItem = -1;
	var asyncVariants = function(str, variants) {
		if(str == val) {
			var filtredVariants = {};
			for(var text in variants) {
				if(lisTexts.indexOf(text) == -1) {
					filtredVariants[text] = variants[text];
				}
			}
			addVariants(filtredVariants, 1);
		}
	};
	domBindEvent(input, ['change', 'keyup'], function(e) {
		if(input.value != val) {
			val = input.value;
		} else {
			return;
		}
		lis.forEach(domRemove);
		lis = [];
		lisTexts = [];
		lisSets = [];
		selectItem = -1;
		if(cbChange) cbChange(val);
		addVariants(cbVariants(val, 0, asyncVariants));
	});
	var addVariants = function(variants, isAsync) {
		if(variants) {
			var li;
			var bindMouseEvents = function() {
				var itemNum = lis.length;
				domBindEvent(li, 'mouseover', function(e) {
					selectItem = itemNum;
					autoSelectItem();
				});
				domBindEvent(li, 'mousedown', function(e) {
					selectItem = itemNum;
					setBySelectItem();
					domCancelEvent(e);
				});
			};
			for(var text in variants) {
				li = domCreater('li', 0, ul, text);
				bindMouseEvents();
				lis.push(li);
				lisTexts.push(text);
				lisSets.push(variants[text]);
			}
		}
		if(lis.length) {
			domShow(ul);
		} else {
			domHide(ul);
		}
	};
	domBindEvent(input, 'blur', function() {
		var variants = cbVariants(val, 1);
		if(variants) {
			cbSet(variants[val]);
		}
		selectItem = -1;
		domHide(ul);
	});
	var setBySelectItem = function() {
		if(selectItem > -1) {
			input.value = val = lisTexts[selectItem];
			if(cbSet) cbSet(lisSets[selectItem]);
		}
		domHide(ul);
	};
	var autoSelectItem = function() {
		domRemoveCssClass(lis, 'select');
		domAddCssClass(lis[selectItem], 'select');
	};
	domBindEvent(input, 'keyup', function(e) {
		if(lis.length) {
			if(e.keyCode == 13) {
				domCancelEvent(e);
				setBySelectItem();
			} else if(e.keyCode == 38) {
				if(--selectItem < 0) {
					selectItem = lis.length - 1;
				}
				autoSelectItem();
			} else if(e.keyCode == 40) {
				if(++selectItem >= lis.length) {
					selectItem = 0;
				}
				autoSelectItem();
			} else {
				return;
			}

		}
	});
	return elem;
};

var createWordsInputsPanel = function() { // Создает панель для добавления наборов слов.
	var panel = {};
	panel.wrapper = domCreater(0, 'winps');
	panel.typeChooseWrapper = domCreater(0, 'bmks', panel.wrapper);
	panel.typeChoosers = {};
	panel.activeType = 0;

	panel.createTypesChooser = function() {
		var wordTypeName;
		var funcBindClick = function() {
			var name = wordTypeName;
			if(!panel.typeChoosers[name]) {
				panel.typeChoosers[name] = domCreater('button', 'bmk', panel.typeChooseWrapper, name);
				domBindEvent(panel.typeChoosers[name], 0, function() {panel.setActiveType(name);});
			}
		};
		for(var wtn in wordsTypes) {
			wordTypeName = wtn;
			funcBindClick();
		}
	};
	panel.setActiveType = function(name) {
		//if(panel.activeType && name == Word.wordTypeName) return;
		if(panel.activeType == name) return;
		panel.activeType = name;
		var chosers = [];
		for(var k in panel.typeChoosers) chosers.push(panel.typeChoosers[k]);
		domRemoveCssClass(chosers, 'active');
		domAddCssClass(panel.typeChoosers[name], 'active');
		for(var lang = 0; lang < 2; lang++) {
			var oldWordInputs = panel.wordInputs[lang].concat();
			panel.wordInputs[lang] = [];
			for(var i = 0; i < oldWordInputs.length; i++) {
				var oldOtherTypesWords = [];
				if(oldWordInputs[i].word) oldOtherTypesWords.push(oldWordInputs[i].word);
				if(oldWordInputs[i].oherTypesWords) oldOtherTypesWords.concat(oldWordInputs[i].otherTypesWords);
				var n;
				for(n = 0; n < oldWordInputs[i].cbsRemove.length; n++) {
					oldWordInputs[i].cbsRemove[n]();
				}
				var newWordInput;
				for(n = oldOtherTypesWords.length; n--;) {
					if(oldOtherTypesWords[n].wordTypeName == name) {
						newWordInput = panel.addWordInput(lang, oldOtherTypesWords[n]);
						break;
					}
				}
				if(!newWordInput) newWordInput = panel.addWordInput(lang, oldWordInputs[i].getInputsValues());
				newWordInput.otherTypesWords = oldOtherTypesWords;
				newWordInput.settedFromCollection = oldWordInputs[i].settedFromCollection;
				if(newWordInput.spoilerHide && oldWordInputs[i].spoilerBool) newWordInput.spoilerHide();
			}
		}
	};
	panel.side = [];
	panel.addButtons = [];
	panel.wordInputs = [[],[]];
	panel.addWordInput = function(lang, word, setFocus) {
		var wordInput = wordsTypes[panel.activeType].inputConstructors[lang]();
		//var wordInput = panel.types[panel.activeType][lang]();
		panel.wordInputs[lang].push(wordInput);
		wordInput.cbsRemove.push(function() {
			removeFromArrayByValue(panel.wordInputs[lang], wordInput);
		});
		wordInput.cbSetWord = function(word) {
			// Выполняется, когда выбрано слово (обычно из автодополнений).
			wordInput.settedFromCollection = true;
			var typNam = word.wordTypeName;
			var i;
			var newWordInput;
			if(word.wordLang != lang) {
				newWordInput = panel.addWordInput(1 - lang, word, true);
				if(newWordInput.spoilerHide) newWordInput.spoilerHide();
				newWordInput.settedFromCollection = wordInput.settedFromCollection;
				for(i = 0; i < wordInput.cbsRemove.length; i++) {
					wordInput.cbsRemove[i]();
				}
			}
			if(typNam != panel.activeType && typNam in wordsTypes) {
				panel.setActiveType(typNam);
			}
			var nodes = (wordsTypes[panel.activeType].collection || words).searchNodes(word); // Сначала ищет в общих словах.
			if(!nodes.length) nodes = nodes.concat((wordsTypes[panel.activeType].userCollection || user.words).searchNodes(word)); // Если в общих словах не найдено - ищет в словах юзера.
			var newLinkedWords = [];
			for(i = 0; i < nodes.length; i++) {
				wCicle:
				for(var n = 0; n < nodes[i].os.length; n++) {
					var w = nodes[i].os[n];
					if(w && w != word && newLinkedWords.indexOf(w) == -1) {
						var l = w.wordLang;
						for(var p = 0; p < panel.wordInputs[l].length; p++) {
							if(panel.wordInputs[l][p].word == w) break wCicle;
						}
						newLinkedWords.push(w);
					}
				}
			}
			for(i = 0; i < newLinkedWords.length; i++) {
				newWordInput = panel.addWordInput(newLinkedWords[i].wordLang, newLinkedWords[i]);
				if(newWordInput.spoilerHide) newWordInput.spoilerHide();
			}
		};
		wordInput.createElements(setFocus);
		if(word) {
			wordInput.fillByWord(word);
		} else {
			wordInput.fillAsInps(panel.wordInputs[1 - lang].concat());
		}
		domPrependChild(panel.side[lang], wordInput.wrapper);
		return wordInput;
	};
	var funcBindClick = function(lang) {
		domBindEvent(panel.addButtons[lang], 0, function() {
			panel.addWordInput(lang, 0, true);
		});
	};
	for(var i = 0; i < 2; i++) {
		var sideWrapper = domCreater(0, 'wordsSide ' + 'n' + i, panel.wrapper);
		var h6 = domCreater('h6', 0, sideWrapper, i ? 'Russian:' : 'English:');
		panel.addButtons[i] = domCreater('button', 'mid btn add', sideWrapper, 'add');
		panel.side[i] = domCreater(0, 0, sideWrapper);
		funcBindClick(i);
	}
	panel.clear = function() { // удаляет все слова с панели.
		for(var lang = 0; lang < 2; lang++) {
			var oldWordInputs = panel.wordInputs[lang].concat();
			for(var i = 0; i < oldWordInputs.length; i++) {
				for(var n = 0; n < oldWordInputs[i].cbsRemove.length; n++) {
					oldWordInputs[i].cbsRemove[n]();
				}
			}
		}
	};
	panel.finish = function() {
		var delOtherConnections = []; // Указывает на разорваные связи.
		var analog;
		var funcDelOldAnalog = function() {
			wordsTypes[analog.wordTypeName].userCollection.remoweWord(analog);
		};
		var skipAnalog = function() {
			word.skipAnalog = 1;
		};
		var funcChangeTypeNewWord = function() {
			panel.setActiveType(analog.wordTypeName);
		};
		var autoaddFlags = function() {
			var commonFlags = wordsTypes[panel.activeType].userCollection.getCommonFlagsForWords(wordsByLang[1 - word.wordLang]);
			json = word.toJSON();
			var newWord = new wordsTypesByNum[word.wordType].constructors[word.wordLang](json);
			newWord.flags = word.flags.concat(word.disapportionFlags(commonFlags));
			wordInput.fillByWord(newWord);
		};
		var wrds = [], wordsInputsValues = [], wordsByLang = [[],[]];
		var i, n, lang, word, wordInput, wordInputValues, step = -1, buttons; // Осторожно! Эти переменные используются разными частями функции nextIter.
		var nextIter = function(toNextStep) {
			// т.к. используем диалоговые окна - будем после пропуска диалогового окна переходить к следующей стадии проверки.
			if(toNextStep) {
				i = 0;
				n = 0;
				lang = 0;
				word = 0;
				wordInput = 0;
				wordInputValues = 0;
				step++;
			}
			if(!step) {
				if(panel.activeType == Word.wordTypeName) {
					modalWindow.createAndShowWindow('Выберите, пожалуйста, тип слова.');
					return;
				}
				if((panel.wordInputs[0].length + panel.wordInputs[1].length > 0) && (!panel.wordInputs[0].length || !panel.wordInputs[1].length)) {
					modalWindow.createAndShowWindow('Необходимо добавить хоть один перевод для слов(а).');
					return;
				}
				nextIter(1);
			} else if(step === 1) {
				while(lang < 2) {
					while(i < panel.wordInputs[lang].length) {
						wordInput = panel.wordInputs[lang][i];
						i++;
						var values = wordInput.getInputsValues();
						if(wordInput.word) {
							word = wordInput.word;
							word.setOnlyValues(values); // убираем из слова все лишнее.
						} else {
							word = new wordInput.wordType();
							word.setByInputValues(values);
							if(word.wordLang === undefined) word.wordLang = lang;
						}
						if(wordInput.settedFromCollection) {
							delOtherConnections.push(word);
						}
						if(!lang) {
							analog = baseWords.searchByStr(word.getFormed());
							if(analog) {
								modalWindow.createAndShowWindow('Слово \"' + analog.getFormed() + '\" уже есть в списке особых слов.');
								return;
							}
						}
						analog = words.searchByStr(word.getFormed());
						var analogFromCommon;
						if(analog) {
							analogFromCommon = true;
						} else {
							analog = user.words.searchByStr(word.getFormed());
						}
						if(analog) {
							if(analog.wordTypeName != panel.activeType && !word.skipAnalog) {
								buttons = [];
								if(!analogFromCommon) {
									buttons.push(['удалить старое слово (' + analog.wordTypeName + ')', funcDelOldAnalog]);
								} else {
									buttons.push(['все равно добавить как ' + word.wordTypeName, skipAnalog]);
								}
								buttons.push(['поменять тип вводимого слова на ' + analog.wordTypeName, funcChangeTypeNewWord]);
								modalWindow.createAndShowWindow('Слово \"' + analog.getFormed() + '\" уже имеет ' + (analogFromCommon ? 'в общем' : 'в вашем') + ' словаре тип ' + analog.wordTypeName + ', а не ' + word.wordTypeName + '.', buttons);
								return;
							}
							if(analog && wordInput.word === analog) {
								wordInput.word.setOnlyValues(values); // Значит пользователь, скорее всего, хочет изменить именно уже имеющееся в коллекции слово.
							}
						}
						wrds.push(word);
						wordsInputsValues.push(values);
						wordsByLang[lang].push(word);
					}
					i = 0;
					lang++;
				}
				nextIter(1);
			} else if(step === 2) {
				while(i < panel.wordInputs[0].length) {
					wordInput = panel.wordInputs[0][i];
					word = wordsByLang[0][i];
					i++;
					analog = baseWords.searchByStr(word.getFormed());
					if(analog) {
						modalWindow.createAndShowWindow('Слово \"' + analog.getFormed() + '\" является особым.');
						return;
					}
				}
				nextIter(1);
			} else if(step === 3) {
				while(lang < 2) {
					while(i < panel.wordInputs[lang].length) {
						wordInput = panel.wordInputs[lang][i];
						word = wordsByLang[lang][i];
						i++;
						wordInputValues = wordsInputsValues[wrds.indexOf(word)];
						if(!wordInputValues[1] || !wordInputValues[1].length) {
							buttons = [];
							buttons.push(['пускай не используются', nextIter]);
							buttons.push(['установить флаги как у перевода', autoaddFlags]);
							modalWindow.createAndShowWindow('Слово \"' + word.getFormed() + '\" не имеет ни одного флага. Без флагов оно не будет использоваться в предложениях.', buttons);
							return;
						}
					}
					i = 0;
					lang++;
				}
				nextIter(1);
			} else if(step === 4) {
				while(i < wordsByLang[0].length) {
					var enWord = wordsByLang[0][i];
					while(n < wordsByLang[1].length) {
						var ruWord = wordsByLang[1][n];
						n++;
						var commonFlags = wordsTypes[panel.activeType].userCollection.getCommonFlagsForWords([enWord, ruWord]);
						if(!commonFlags || !commonFlags.length) {
							buttons = [];
							buttons.push(['пускай не используются', nextIter]);
							buttons.push(['изменить']);
							modalWindow.createAndShowWindow('Слова \"' + enWord.getFormed() + '\" и \"' + ruWord.getFormed() + '\" не имеют общих флагов. Без общих флагов слова не будут использоваться в предложениях.', buttons);
							return;
						}
					}
					n = 0;
					i++;
				}
				nextIter(1);
			} else if(step === 5) {
				wordsTypes[panel.activeType].userCollection.addWords(wrds, true); // Там аналоги сами должны замениться.
				wrds.forEach(function(word) { if(word.changeWordFlags) word.changeWordFlags(); });
				for(i = 0; i < delOtherConnections.length; i++) {
					wordsTypes[panel.activeType].userCollection.removeOtherWordsLinks(delOtherConnections[i], wrds);
				}
				panel.clear();
			}
		};

		nextIter(1);
	};
	var finishWrapper = domCreater(0, 'okWrap', panel.wrapper);
	panel.finishButton = domCreater('button', 'btn', finishWrapper, 'Ok');
	domBindEvent(panel.finishButton, 0, panel.finish);
	panel.setToEdit = function(word) {
		// Показывает панель с указанным словом и всеми функциями редактирования.
		panel.clear();
		panel.createTypesChooser();
		domAppendChild(domGetById('addWordPanel'), panel.wrapper);
		if(word) {
			panel.setActiveType(word.wordTypeName);
			panel.addWordInput(word.wordLang, word, 1).cbSetWord(word);
			panel.wordInputs[1 - word.wordLang].forEach(function(inp) {
				inp.spoilerShow();
			});
		} else {
			panel.setActiveType(Word.wordTypeName);
			panel.addWordInput(0, 0, 1);
		}
	};
	return panel;
};

var wordsInputsPanel = createWordsInputsPanel();
addToOnloadBody(function() {
	domBindEvent([domGetById('addWordButton'), domGetById('addWordButton2')], 0, function(e) {
		domHide(domGetById('addWordButton2'));
		toFastPanel(1);
		wordsInputsPanel.setToEdit();
		/*wordsInputsPanel.clear();
		wordsInputsPanel.createTypesChooser();
		wordsInputsPanel.setActiveType(Word.wordTypeName);
		wordsInputsPanel.addWordInput(0, 0, 1);
		domAppendChild(domGetById('addWordPanel'), wordsInputsPanel.wrapper);*/
		//domHide(domGetById('addWordButton'));
	});
});
// Реализовано без прототипов, но с наследованием.

var createWordInput = function(collection, wordType, userCollection) {
	if(!collection) collection = words;
	if(!wordType) wordType = Word;
	if(!userCollection) userCollection = user.words;
	var inp = {}; // Выполняет роль this для ООП.
	inp.wordType = wordType || Word;
	inp.otherInputsPre = [];
	inp.cbsRemove = [function() {
		if(inp.wrapper) domRemove(inp.wrapper);
	}];
	inp.word = undefined;
	inp.cbSetWord = undefined;
	inp.autocomplete = function(str, strong, asyncCb) {
		var ret = {};
		var collects = [collection, userCollection, baseWords];
		var autocomplets = [];
		var searched;
		var i;
		for(i = 0; i < collects.length; i++) {
			if(collects[i]) {
				if(strong) {
					autocomplets = autocomplets.concat(collects[i].searchByStr(str));
				} else {
					autocomplets = autocomplets.concat(collects[i].autocomplete(str));
				}
			}
		}
		if(autocomplets.length < 5) {
			for(i = 0; i < collects.length; i++) {
				if(collects[i]) {
					if(strong) {
						autocomplets = autocomplets.concat(collects[i].searchByStr('the ' + str));
					} else {
						autocomplets = autocomplets.concat(collects[i].autocomplete('the ' + str));
					}
				}
			}
		}
		if(autocomplets.length < 3 && asyncCb && str.length > 2) {
			getJSON('http://' + productName + productSubfold + '/backend/ac.php?str=' + encodeURI(str));
			autocompleteCallbacks[str] = function(args) {
				for(var i = 1; i < args.length; i++) {
					if(!ret[args[i]]) {
						ret[args[i]] = args[i];
					}
				}
				asyncCb(args[0], ret);
			};
		}
		for(i = 0; i < autocomplets.length; i++) {
			if(autocomplets[i]) {
				ret[autocomplets[i].getFormed()] = autocomplets[i];
				if(i > 8) break;
			}
		}
		return ret;
	};
	inp.fillByWord = function(word) {
		if(!word) return;
		var forms = [];
		var simpleWord = new inp.wordType(); // тестовый экземпляр для проверки, какие input-ы являются нестандартными.
		var arr = word;
		var newWord = word;
		if(!isArray(word)) {
			arr = word.toJSON();
		} else {
			newWord = new inp.wordType();
			newWord.setByInputValues(word);
		}
		if(arr) {
			var otherInputsVals = [arr[0]];
			for(var i = 1; i < inp.otherInputsPre.length; i++) {
				simpleWord.setByInputValues([arr[0], arr[1], otherInputsVals, arr[3]]);
				var simpleWordFormed = simpleWord.getFormed(i);
				inp.inputs[i].placeholder = simpleWord.getFormed(i) || '';
				var newWordFormed = newWord.getFormed(i);
				if(inp.inputs[i].placeholder !== newWordFormed) {
					inp.inputs[i].value = newWordFormed || '';
				}
				if(inp.inputs[i].value) {
					otherInputsVals[i] = inp.inputs[i].value;
				}
			}
		}
		word = newWord;
		inp.word = word;
		var wordFormed = word.getFormed();
		if(wordFormed) inp.inputs[0].value = word.getFormed();
		return word;
	};
	inp.fillAsInps = function(inps) {
		// Устанавливает, например флаги, так же как и у inps.
		// inps - инпуты другого языка.
	};
	inp.getInputsValues = function(full) {
		var ret = [];
		for(var i = 0; i < inp.inputs.length; i++) {
			ret[i] = inp.inputs[i].value;
			if(full && !ret[i]) ret[i] = inp.word.getFormed(i);
		}
		return [ret[0], 0, ret]; // Да, формат для передачи по значению для всех типов примерно такой: ['воля', 0, ['воля']]
	};
	inp.createOtherElements = function() {
		inp.otherInputsWrapper = domCreater(0, 0, inp.wrapper);
		var label;
		var input;
		var funcBindKeyUp = function() {
			domBindEvent(input, 'keyup', function(e) {
				inp.fillByWord(inp.getInputsValues());
			});
		};
		for(var i = 1; i < inp.otherInputsPre.length; i++) {
			label = domCreater('label', 0, inp.otherInputsWrapper, inp.otherInputsPre[i]);
			input = domCreater('input', 0, label);
			input.type = 'text';
			inp.inputs[i] = input;
			funcBindKeyUp();
		}
	};
	inp.spoilerBool = true;
	inp.createSpoilerButton = function() {
		if(!inp.spoilerButton) {
			inp.spoilerButton = domCreater('button', 'spoil min btn', inp.wrapper);
			inp.spoilerButton.title = 'показать параметры этого слова';
			inp.spoilerShow = function() {
				domRemoveCssClass(inp.spoilerButton, 'down');
				domShow(inp.otherInputsWrapper);
				inp.spoilerBool = true;
			};
			inp.spoilerHide = function() {
				domAddCssClass(inp.spoilerButton, 'down');
				domHide(inp.otherInputsWrapper);
				inp.spoilerBool = false;
			};
			domBindEvent(inp.spoilerButton, 0, function() {
				if(inp.spoilerBool) {
					inp.spoilerHide();
				} else {
					inp.spoilerShow();
				}
			});
		}
		if(inp.wordType == Word) {
			domHide(inp.spoilerButton);
		}
	};
	inp.inputs = [];
	inp.createElements = function(setFocus) { // лучше вызывать этот метод извне.
		inp.wrapper = domCreater(0, 'wwi');
		inp.closeButton = domCreater('button', 'rm btn', inp.wrapper, 'X');
		domBindEvent(inp.closeButton, 0, inp.cbsRemove);
		var label = domCreater('label', 0, inp.wrapper);
		var autocompletedInput = createAutocompletedInput(inp.autocomplete, function(word) {
			if(word) {
				if(isString(word)) {
					loadWord(word, function(word) {
						loadCommonTranslations(word, function(words) {
							inp.fillByWord(word);
							if(inp.cbSetWord) inp.cbSetWord(word);
						});
					});
				} else {
					inp.fillByWord(word);
					if(inp.cbSetWord) {
						inp.cbSetWord(word);
					}
				}
			}
		}, function(str) {
			inp.fillByWord(inp.getInputsValues());
		});
		inp.inputs.push(autocompletedInput.firstChild);
		domAppendChild(label, autocompletedInput);
		if(setFocus) {
			inp.inputs[0].autofocus = 1;
			window.setTimeout(function(){inp.inputs[0].focus();},0);
		}
		inp.createOtherElements();
		inp.createSpoilerButton();
		return inp;
	};
	return inp;
};

// Реализовано без прототипов, но с наследованием.
var createWordInputWithFlags;
(function() {

	createWordInputWithFlags = function(collection, wordType, userCollection, flagsMap) {
		var inp = createWordInput(collection, wordType, userCollection);
		inp.flagsTypes = {};
		inp.addFlagsType = function(strName, ind, flagsMap, getWordFlagsArray) {
			// Оказалось что для verbInput нужна дополнительная категория флагов, чтобы не копипастить код - создал эту функцию.
			// ind - индекс эллемента в json, в который записываются значения этого флага.
			var obj = {};
			inp.flagsTypes[strName] = obj;
			var oldCreateElements = inp.createElements;
			inp.createElements = function(setFocus) {
				oldCreateElements(setFocus);
				obj.flagsWrapper = domCreater(0, 0, inp.otherInputsWrapper);
				inp.addFlagButton = domCreater('button', 'btn min', inp.otherInputsWrapper, 'add ' + strName);
				domBindEvent(inp.addFlagButton, 0, function() {
					obj.createFlagElements();
				});
				inp.createSpoilerButton();
				return inp;
			};
			var flags = [];
			obj.flags = flags;
			obj.createFlagElements = function() {
				var flagWrapper = domCreater();
				var flag = createWordInputWithFlags.createAndSetFlagElements(flagWrapper, flagsMap, function(e) {
					//inp.fillByWord(inp.getInputsValues());
				});
				domAddCssClass(flag[0], 'flags');
				domAppendChild(obj.flagsWrapper, flagWrapper); // вставляем попозже, чтобы в случае чего не тормозить перерисовками.
				var removeButton = domCreater('button', 'btn rm', flag[0], 'X');
				flag[2] = removeButton;
				domBindEvent(removeButton, 0, function() {
					domRemove(flag[0]);
					removeFromArrayByValue(flags, flag);
					removeFromArrayByValue(inp.inputs, flag[1]);
				});
				flags.push(flag);
				inp.inputs.push(flag[1]);
				return flag;
			};
			if(!getWordFlagsArray) {
				getWordFlagsArray = function(word) {
					return word.flags;
				};
			}
			var oldFillByWord = inp.fillByWord;
			inp.fillByWord = function(word) {
				var oldFlagsVals;
				if(inp.word) {
					oldFlagsVals = cloneStruct(inp.word.flags);
				}
				word = oldFillByWord(word);
				var i, changed = 1;
				if(oldFlagsVals && inp.word.flags && oldFlagsVals.length === inp.word.flags.length) {
					changed = 0;
					for(i = 0; i < oldFlagsVals[i]; i++) {
						if(oldFlagsVals[i] !== inp.word.flags[i]) {
							changed = 1;
						}
					}
				}
				if(changed) {
					for(i = 0; i < flags.length; i++) { // удаляем все старые элементы флагов.
						if(flags[i]) {
							domRemove(flags[i][0]);
							removeFromArrayByValue(inp.inputs, flags[i][1]);
						}
					}
					flags = [];
					var wordFlags = getWordFlagsArray(word);
					if(wordFlags) {
						if(wordFlags.length === 0 && !devMode) {
							wordFlags = [0];
						}
						for(i = 0; i < wordFlags.length; i++) {
							var flag = obj.createFlagElements();
							createWordInputWithFlags.createAndSetFlagElements(flag[0], flagsMap, 0, wordFlags[i]);              //////////////////////////////////////
						}
					}
				}
				return word;
			};
			var oldGetInputsValues = inp.getInputsValues;
			inp.getInputsValues = function() {
				var ret = oldGetInputsValues();
				var flagsInts = [];
				var i, n, intSum;
				for(i = 0; i < flags.length; i++) {
					var sum = createWordInputWithFlags.getFlagsSumm(flags[i][0]);
					if(sum) {
						if(isArray(sum)) {
							for(n = 0; n < sum.length; n++) {
								intSum = parseInt('1' + sum[n], 2); // добавляем 1, чтобы при конвертации в int первые нули не отрезались.
								flagsInts.push(intSum);
							}
						} else {
							intSum = parseInt('1' + sum, 2); // добавляем 1, чтобы при конвертации в int первые нули не отрезались.
							flagsInts.push(intSum);
						}
					}
				}
				ret[ind] = flagsInts;
				return ret; // Да, формат для передачи по значению для всех типов примерно такой: ['воля', 0, ['воля']]
			};
		};
		inp.addFlagsType('flag', 1, flagsMap);
		var oldFillAsInps = inp.fillAsInps;
		inp.fillAsInps = function(inps) {
			// Устанавливает, например флаги, так же как и у inps.
			oldFillAsInps(inps);
			if(inps.length && inps[0] && inps[0]) {
				// установим только по первому слову, для простоты.
				var json = inps[0].getInputsValues();
				if(json) {
					json[0] = '';
					json[2] = [];
					inp.fillByWord(json);
				}
			}
		};
		return inp;
	};

	createWordInputWithFlags.createAndSetFlagElements = function(container, subMap, cb, str) {
		// Создает новую форму для установки флага, или устанавливает в уже созданную.
		// contanrer - dom объект, куда вставлять пункты.
		// subMap - обьект, описывающий какие инпуты должны находиться на этом и вложенных уровнях.
		// cb - коллбек изменения значения флага.
		// str - биты, по которым нужно выбрать инпуты.
		var bindRadio = function(radio) {
			domBindEvent(radio, 'click', function(e) {
				domCancelEvent(e);
				domCancelEventPropagation(e);
				var val = (domGetData(radio, 'check') === '1') ? 0 : 1;
				var isCtrl = windowKeyIsPressed(17); // зажатый ctrl - хак для избранных.
				windowSetTimeout(function() {
					radio.checked = val ? true : false;
					var elemContainer, input, childsContainer;
					for(var i = 0; i < radiosElems.length; i++) {
						elemContainer = radiosElems[i];
						input = elemContainer.firstChild.firstChild;
						childsContainer = elemContainer.childNodes[1];
							if(input === radio && (input.checked || isCtrl && val)) {
								if(childsContainer) domShow(childsContainer);
								domSetData(input, 'check', 1);
								createWordInputWithFlags.createAndSetFlagElements(childsContainer, subMap[radioNames[i]], cb);
							} else if(!isCtrl || input === radio && !val) {
								input.checked = '';
								if(childsContainer) domHide(childsContainer);
								domSetData(input, 'check', 0);
							}
					}
					if(cb) cb(e);
				}, 10);
			});
		};
		var checkboxCallback = function() {
			window.setTimeout(cb, 20);
		};
		var inputs = [];
		var radioNames = [];
		var containerChilds = domGetChilds(container);
		var checkboxesElems = [];
		var radiosElems = [];
		var elemContainer, label, input, childsContainer, textName, indexTilda;
		for(var name in subMap) {
			if(name === 'title') {

			} else if(name === 'checkboxes') {
				for(var i = 0; i < subMap[name].length; i++) {
					if(containerChilds.length) {
						elemContainer = containerChilds[checkboxesElems.length + radiosElems.length];
						input = elemContainer.firstChild.firstChild;
						if(str && str[checkboxesElems.length] === '1') {
							input.checked = '1';
						} else {
							input.checked = '';
						}
					} else {
						elemContainer = domCreater(0, 0, container);
						label = domCreater('label', 0, elemContainer);
						input = domCreater('input', 0, label);
						input.type = 'checkbox';
						if(str && str[checkboxesElems.length] === '1') {
							input.checked = '1';
						} else {
							input.checked = '';
						}
						domBindEvent(input, 'change', checkboxCallback);
						textName = subMap[name][i];
						if(isObject(subMap[name][i])) {
							textName = subMap[name][i].name;
							elemContainer.title = subMap[name][i].title;
						}
						indexTilda = textName.indexOf('~');
						if(indexTilda > 0) textName = textName.substr(8, indexTilda);
						domCreater('span', 0, label, textName);
					}
					checkboxesElems.push(elemContainer);
					inputs.push(input);
				}
			} else {
				if(containerChilds.length) {
					elemContainer = containerChilds[checkboxesElems.length + radiosElems.length];
					input = elemContainer.firstChild.firstChild;
					input.checked = '';
					domSetData(input, 'check', 0);
					domHide(elemContainer.childNodes[1]);
				} else {
					elemContainer = domCreater(0, 0, container);
					label = domCreater('label', 0, elemContainer);
					input = domCreater('input', 0, label);
					input.type = 'radio';
					bindRadio(input);
					textName = name;
					indexTilda = textName.indexOf('~'); // в названиях встречаестя ~ отделяющая читабельное названия от дополнений.
					if(indexTilda > 0) {
						textName = textName.substr(0, indexTilda);
					}
					domCreater('span', 0, label, textName);
					if(subMap[name].title) {
						label.title = subMap[name].title;
					}
					if(isObject(subMap[name])) {
						childsContainer = domCreater(0, 'sub', elemContainer);
						domHide(childsContainer);
					} else if(isString(subMap[name])) {
						elemContainer.title = subMap[name];
					}
				}
				radiosElems.push(elemContainer);
				radioNames.push(name);
				inputs.push(input);
			}
		}
		containerChilds = domGetChilds(container);
		if(str) {
			var len = Math.ceil(Math.log(radiosElems.length + 1) / Math.log(2)); // колличетсво битов необходимое для записи выбранного(или пустого) radio
			var bitsRadio = str.substr(checkboxesElems.length, len);
			while(bitsRadio.length < len) {
				bitsRadio += '0';
			}
			var selectedRadio = parseInt(bitsRadio, 2) - 1;
			elemContainer = radiosElems[selectedRadio];
			if(elemContainer) {
				input = elemContainer.firstChild.firstChild;
				input.checked = 'checked';
				domSetData(input, 'check', 1);
				childsContainer = elemContainer.childNodes[1];
				domShow(childsContainer);
				createWordInputWithFlags.createAndSetFlagElements(childsContainer, subMap[radioNames[selectedRadio]], cb, str.substr(checkboxesElems.length + len));
			}
		}
		return [container, inputs];
	};

	createWordInputWithFlags.getFlagsSumm = function(container) {
		// Возвращает строку битов, соответствующую выделенным input-ам.
		var ret;
		var retArr = [];
		var elemContainer;
		var input;
		var selectedRadio;
		var hasOtherSelectedRadio;
		var prevSelectedRadio = -1;
		var radios;
		var addToRetArrByChilds = function(str) {
			var sum = ret + str;
			if(retArr.indexOf(sum) === -1) {
				retArr.push(sum);
			}
		};
		var len, bitsRadio, childSums, i;
		do {
			selectedRadio = -1; // -1 - значит ни чего не выбрано
			hasOtherSelectedRadio = 0;
			radios = [];
			ret = '';
			for(i = 0; i < container.childNodes.length; i++) {
				elemContainer = container.childNodes[i];
				input = elemContainer.firstChild.firstChild;
				if(input) {
					if(input.type === 'checkbox') {
						if(input.checked) {
							ret += '1';
						} else {
							ret += '0';
						}
					} else if(input.type === 'radio') {
						if(input.checked) {
							if(selectedRadio !== -1) {
								hasOtherSelectedRadio = 1;
							} else if(radios.length > prevSelectedRadio) {
								selectedRadio = radios.length;
								prevSelectedRadio = selectedRadio;
							}
						}
						radios.push(elemContainer);
					}
				}
			}
			if(selectedRadio !== -1 || ret !== '') {
				len = Math.ceil(Math.log(radios.length + 1) / Math.log(2)); // колличетсво битов необходимое для записи выбранного(или пустого) radio
				bitsRadio = (selectedRadio + 1).toString(2);
				while(bitsRadio.length < len) {
					bitsRadio = '0' + bitsRadio;
				}
				ret += bitsRadio;
				if(selectedRadio === -1) {
					retArr.push(ret);
				} else {
					if(radios[selectedRadio].childNodes[1] && radios[selectedRadio].childNodes[1].childNodes.length) {
						childSums = createWordInputWithFlags.getFlagsSumm(radios[selectedRadio].childNodes[1]);
						if(childSums.length) {
							childSums.forEach(addToRetArrByChilds);
						} else {
							if(ret.indexOf('1') === -1) ret = '';
							retArr.push(ret);
						}
					} else {
						if(ret.indexOf('1') === -1) ret = '';
						retArr.push(ret);
					}
				}
			}
		} while(hasOtherSelectedRadio);
		removeFromArrayByValue(retArr, '');
		return retArr;
	};

})();

// Реализовано без прототипов, но с наследованием.

var createNounInput = function(collection, wordType, userCollection, flags) {
	if(!collection) collection = nouns;
	if(!userCollection) userCollection = user.nouns;
	var inp = createWordInputWithFlags(collection, wordType, userCollection, flags);
	// Относительно WordInputWithFlags добавляются специальные флаги для глаголов.
	inp.gerundAutocomplete = function(str) {
		var ret = {};
		var autocomplets = verbs.autocomplete(str); // сначала ищем в общей коллекции глаголов.
		if(autocomplets.length < 3 && user.verbs) {
			autocomplets = autocomplets.concat(user.verbs.autocomplete(str)); // если в общей коллекции ни чего не найдено - ищем в коллекции глаголов юзера.
		}
		for(var i = 0; i < autocomplets.length; i++) {
			if(autocomplets[i].wordLang === wordType.prototype.wordLang) {
				ret[autocomplets[i].getFormed()] = autocomplets[i];
			}
		}
		return ret;
	};
	var oldCreateOtherElements = inp.createOtherElements;
	inp.gerundVal = '';
	inp.createOtherElements = function() {
		oldCreateOtherElements();
		var genderFlagWrapper = domCreater();
		inp.genderFlags = createWordInputWithFlags.createAndSetFlagElements(genderFlagWrapper, wordType.genderFlags, function(e) {
			inp.fillByWord(inp.getInputsValues());
		});
		domPrependChild(inp.otherInputsWrapper, genderFlagWrapper);

		var label = domCreater('label', 0, inp.otherInputsWrapper, 'глагол(для герундия)');
		inp.gerundAutocompletedInput = createAutocompletedInput(inp.gerundAutocomplete, function(word) {
			inp.gerundVal = word.getFormed();
		}, function(str) {
			inp.gerundVal = str;
		});
		domAppendChild(label, inp.gerundAutocompletedInput);
	};
	var oldGetInputsValues = inp.getInputsValues;
	inp.getInputsValues = function() {
		var ret = oldGetInputsValues();
		var genderFlag = createWordInputWithFlags.getFlagsSumm(inp.genderFlags[0]);
		ret[3] = parseInt(genderFlag, 2) || 0;
		ret[4] = inp.gerundVal;
		return ret; // Да, формат для передачи по значению для всех типов примерно такой: ['воля', 0, ['воля']]
	};
	var oldFillByWord = inp.fillByWord;
	inp.fillByWord = function(word) {
		if(!word) return;
		oldFillByWord(word);
		if(word.gender !== undefined) {
			createWordInputWithFlags.createAndSetFlagElements(inp.genderFlags[0], wordType.genderFlags, 0, word.genderToBites());                     /////////////////////////////////
		}
		return word;
	};
	return inp;
};

var createNounEnInput = function(collection, wordType, userCollection) {
	if(!wordType) wordType = NounEn;
	var inp = createNounInput(collection, wordType, userCollection, NounEn.flags);
	inp.otherInputsPre = NounEn.preHelpsForPadeji;
	return inp;
};

var createNounRuInput = function(collection, wordType, userCollection) {
	if(!wordType) wordType = NounRu;
	var inp = createNounInput(collection, wordType, userCollection, NounRu.flags);
	inp.otherInputsPre = NounRu.preHelpsForPadeji;
	return inp;
};

//wordsInputsPanel.addType(Noun.prototype.wordTypeName, nouns, createNounEnInput, createNounRuInput, user.nouns);

// Реализовано без прототипов, но с наследованием.

var createVerbInput = function(collection, wordType, userCollection, flags) {
	if(!collection) collection = verbs;
	if(!userCollection) userCollection = user.verbs;
	var inp = createWordInputWithFlags(collection, wordType, userCollection, flags);
	// Относительно WordInputWithFlags добавляются специальные флаги для глаголов.
	inp.addFlagsType('verb flag', 3, Verb.verbFlags, function(word) {return word.verbFlags;});
	return inp;
};

var createVerbEnInput = function(collection, wordType, userCollection) {
	if(!wordType) wordType = VerbEn;
	var inp = createVerbInput(collection, wordType, userCollection, VerbEn.flags);
	inp.otherInputsPre = VerbEn.preHelpsForPadeji;
	return inp;
};

var createVerbRuInput = function(collection, wordType, userCollection) {
	if(!wordType) wordType = VerbRu;
	var inp = createVerbInput(collection, wordType, userCollection, VerbRu.flags);
	inp.otherInputsPre = VerbRu.preHelpsForPadeji;
	return inp;
};
// Реализовано без прототипов, но с наследованием.

var createAdjectiveEnInput = function(collection, wordType, userCollection) {
	if(!collection) collection = adjectives;
	if(!wordType) wordType = AdjectiveEn;
	if(!userCollection) userCollection = user.adjectives;
	var inp = createWordInputWithFlags(collection, wordType, userCollection, AdjectiveEn.flags);
	inp.otherInputsPre = AdjectiveEn.preHelpsForComparative;
	return inp;
};

var createAdjectiveRuInput = function(collection, wordType, userCollection) {
	if(!collection) collection = adjectives;
	if(!wordType) wordType = AdjectiveRu;
	if(!userCollection) userCollection = user.adjectives;
	var inp = createWordInputWithFlags(collection, wordType, userCollection, AdjectiveRu.flags);
	inp.otherInputsPre = AdjectiveRu.preHelpsForComparative;
	return inp;
};
// Реализовано без прототипов, но с наследованием.
var createAdverbInput = function(collection, wordType, userCollection) {
	if(!collection) collection = adverbs;
	if(!userCollection) userCollection = user.adverbs;
	var inp = createWordInputWithFlags(collection, wordType, userCollection, AdverbEn.flags);
	var oldCreateOtherElements = inp.createOtherElements;
	inp.createOtherElements = function() {
		oldCreateOtherElements();
		inp.suitableVerbsInput = domCreater('input', 0, domCreater('label', 0, inp.otherInputsWrapper, 'suitable verbs'));
		inp.suitableVerbsInput.type = 'text';
	};
	var oldGetInputsValues = inp.getInputsValues;
	inp.getInputsValues = function() {
		var ret = oldGetInputsValues();
		ret[3] = (inp.suitableVerbsInput.value || '').split(',').map(trim);
		return ret; // Да, формат для передачи по значению для всех типов примерно такой: ['воля', 0, ['воля']]
	};
	var oldFillByWord = inp.fillByWord;
	inp.fillByWord = function(word) {
		if(!word) return;
		oldFillByWord(word);
		inp.suitableVerbsInput.value = (word.suitableVerbs || []).join(', ');
		return word;
	};
	return inp;
};

var createAdverbEnInput = function(collection, wordType, userCollection) {
	if(!wordType) wordType = AdverbEn;
	var inp = createAdverbInput(collection, wordType, userCollection, AdverbEn.flags);
	inp.otherInputsPre = AdverbEn.preHelpsForComparative;
	return inp;
};

var createAdverbRuInput = function(collection, wordType, userCollection) {
	if(!wordType) wordType = AdverbRu;
	var inp = createAdverbInput(collection, wordType, userCollection, AdverbRu.flags);
	inp.otherInputsPre = AdverbRu.preHelpsForComparative;
	return inp;
};
// Добовляет key-биндеры, таймауты, интерваллы, которые можно приостановить в удобный момент.
var addDeactivatableActiveMixin;

(function() {

	var actStrs = ['down', 'up', 'press', 'pressed'];
	var getActId = function(str) {
		if(str === 'down') return 0;
		if(str === 'up') return 1;
		if(str === 'press') return 2;
		if(str === 'pressed') return 3;
	};

	var activeObs = [];
	addDeactivatableActiveMixin = function(ob) {
		addEventsMixin(ob);
		ob.key = [[],[],[],[]];
		ob.intervals = [];
		ob.timeouts = undefined;
		ob.deactiveTime = 0;
		// далее привяжем функции для простого обращения. Не изящно, за то просто.
		ob.bindToKeys = function(code, act, func) {
			return bindToKeys(ob.key, code, act, func);
		};
		ob.unbindToKeys = function(code, act, func) {
			return unbindToKeys(ob.key, code, act, func);
		};
		ob.setInterval = function(func, interval) {
			return obSetInterval(ob, func, interval);
		};
		ob.clearInterval = function(ind) {
			return obClearInterval(ob, ind);
		};
		ob.setTimeout = function(func, timeout) {
			return obSetTimeout(ob, func, timeout);
		};
		ob.clearTimeout = function(ind) {
			return obClearTimeout(ob, ind);
		};
		var isActive;
		ob.setActive = function(bool) {
			if(bool != isActive) {
				isActive = bool;
				if(bool) {
					activeObs.push(ob);
				} else {
					removeFromArrayByValue(activeObs, ob);
				}
				ob.trigger('active', bool);
			}
		};
		ob.getActive = function() {
			return isActive;
		};
		return ob;
	};

	// Чтобы по нажатию клавиш не вызывались десятки обработчиков - вешаем один обработчик на window, а он передает событие callback-ам активной страницы.
	var bindToKeys = function(ob, code, act, func) {
		// code - код клавиши
		// func - одна функция-обработчик.
		var actId = getActId(act);
		if((actId === undefined) || !isNumber(code) || !isFunction(func)) {
			warning('illegal argument value', arguments);
			return;
		}
		if(!ob[actId][code]) ob[actId][code] = [];
		ob[actId][code].push(func);
	};
	var unbindToKeys = function(ob, code, act, func) {
		// code - код клавиши
		// func - одна функция-обработчик.
		var actId = getActId(act);
		if(['press', 'down', 'up'].inexOf(act) === -1 || !isNumber(code)) {
			warning('illegal argument value', arguments);
			return;
		}
		if(!func) {
			ob[actId][code] = undefined;
		} else if(isArray(ob[actId][code])) {
			removeFromArrayByValue(ob[actId][code], func);
		}
	};
	var callFuncsForKey = function(act, ob, e) {
		var funcs = ob.key[actStrs.indexOf(act)][e.keyCode];
		if(funcs) {
			for(var i = 0; i < funcs.length; i++) {
				funcs[i](e);
				if(e.domCancelEvent) break;
			}
		}
	};
	domBindEvent(window, 'keydown', function(e) {
		var ob;
		for(var i = 0; i < activeObs.length; i++) {
			ob = activeObs[i];
			if(ob) {
				callFuncsForKey('down', ob, e);
				ob.key[3][e.keyCode] = 1;
			}
		}
	});
	domBindEvent(window, 'keyup', function(e) {
		var ob;
		for(var i = 0; i < activeObs.length; i++) {
			ob = activeObs[i];
			if(ob) {
				callFuncsForKey('up', ob, e);
				if(ob.key[3][e.keyCode]) {
					callFuncsForKey('press', ob, e);
				}
				ob.key[3][e.keyCode] = 0;
			}
		}
	});

	var intervalCount = 0;
	windowSetInterval(function() {
		intervalCount++;
		var ob;
		for(var o = 0; o < activeObs.length; o++) {
			ob = activeObs[o];
			var intervals = ob.intervals;
			if(intervals) {
				for(var i = 0; i < intervals.length; i += 3) {
					if(intervals[i] && (intervals[i + 1] + intervalCount)%intervals[i + 2] === 0) {
						intervals[i]();
					}
				}
			}

		}
	}, 20);
	var obSetInterval = function(ob, func, interval) {
		// если intervalCount делится на Math.ceil(divisor/20) без остатка - func вызывается.
		// такое округление может оптимизировать прорисовку многих одновременных анимаций.
		if(ob.intervals === undefined) ob.intervals = [];
		var intervals = ob.intervals;
		if(!isFunction(func) || !isNumber(interval)) {
			warning('illegal argument type');
		}
		var ret = intervals.length;
		intervals.push(func);
		intervals.push(intervalCount);
		intervals.push(Math.ceil(interval / 20));
		return ret;
	};
	var obClearInterval = function(ob, ind) {
		var intervals = ob.intervals;
		if(ind % 3 === 0) intervals[ind] = undefined;
	};
	var obSetTimeout = function(ob, func, timeout) {
		var timeouts = ob.timeouts;
		if(!timeouts) {
			timeouts = ob.timeouts = [];
			ob.addEventListener('active', function(actObj, bool) {
				if(bool) {
					if(ob.timeouts && ob.deactiveTime) {
						var dateTime = new Date().getTime();
						var timeDifferent = dateTime - ob.deactiveTime;
						var timeouts = ob.timeouts;
						var funcSetTimeout = function(ind) {
							windowSetTimeout(function() {
								realizeTimeoutIfActive(ob, ind);
							}, Math.max(ob.timeouts[ind + 2] + (ob.timeouts[ind + 1] || dateTime) - dateTime, 0));
						};
						for(var i = 0; i < timeouts.length; i += 3) {
							if(timeouts[i] !== undefined) {
								if(timeouts[i + 1]) {
									timeouts[i + 1] += timeDifferent;
								} else {
									timeouts[i + 1] = dateTime;
								}
								funcSetTimeout(i);
							}
						}
					}
				} else {
					ob.deactiveTime = new Date().getTime();
				}
			});
		}
		var ret = timeouts.length;
		timeouts.push(func);
		if(ob.getActive()) {
			timeouts.push(new Date().getTime());
			windowSetTimeout(function() {
				realizeTimeoutIfActive(ob, ret);
			}, timeout);
		} else {
			timeouts.push(0);
			ob.deactiveTime = new Date().getTime();
		}
		timeouts.push(timeout);
		return ret;
	};
	var realizeTimeoutIfActive = function(ob, ind) {
		if(ob.getActive()) {
			if(ob.timeouts === undefined || ob.timeouts[ind] === undefined || (new Date().getTime() < ob.timeouts[ind + 1] + ob.timeouts[ind + 2])) return;
			if(isFunction(ob.timeouts[ind])) ob.timeouts[ind]();
			ob.timeouts[ind] = undefined;
		}
	};
	var obClearTimeout = function(ob, ind) {
		if(ob.timeouts !== undefined) ob.timeouts[ind] = undefined;
	};

})();

var myObj = {};

addDeactivatableActiveMixin(myObj);

var myFunc = function() {
	console.log('aaaaaaaaaa');
};

myObj.setInterval(myFunc, 1000);

var baseDct, userDct, dcts = [];
addToOnloadBody(function() {
	var createDct = function(container, collection, edited) {
		var dct = {};
		dcts.push(dct);
		var focusedVerbElem;
		var columns;
		var containerWidth = 300;
		var settingsElem;
		var settingsDomNod;
		domBindEvent(document.body, 0, function() {
			if(settingsDomNod) domRemove(settingsDomNod);
			if(settingsElem) domRemoveCssClass(settingsElem, 'settingsOn');
		});
		var showSettings = function(e, elem, x) {
			if(settingsDomNod) domRemove(settingsDomNod);
			if(settingsElem) domRemoveCssClass(settingsElem, 'settingsOn');
			settingsElem = elem;
			domAddCssClass(elem, 'settingsOn');
			settingsDomNod = domCreater(0, 'settings', elem);
			var star = domCreater('button', 'min btn', settingsDomNod);
			star.innerHTML = '★';
			var word = collection.searchByStr(domGetInnerText(domGetChilds(elem)[1]));
			domBindEvent(star, 0, function() {
				if(word) {
					if(word.isEmphasis()) {
						word.setEmphasis();
						domRemoveCssClass(elem, 'emphasis');
					} else {
						word.setEmphasis(1);
						domAddCssClass(elem, 'emphasis');
					}
				}
			});
			star.title = 'Это слово будет употребляться в тренеровках чаще.';
			var edit = domCreater('button', 'min btn', settingsDomNod, 'edit');
			edit.title = 'изменить слово';
			domBindEvent(edit, 0, function() {
				wordsInputsPanel.setToEdit(word);
			});
			var delet = domCreater('button', 'min btn', settingsDomNod, 'delete');
			delet.title = 'удалить';
			domBindEvent(delet, 0, function() {
				if(word) {
					wordsTypes[word.wordTypeName].userCollection.removeWord(word);
				}
			});
		};
		var clickFunc = function(e) {
			domCancelEvent(e);
			domCancelEventPropagation(e);
			var x = e.pageX - this.offsetLeft;
			if(x > 26) {
				showSettings(e, this, x);
			} else {
				domToggleCssClass(this, 'checked');
			}
			this.focus();
		};
		var focusFunc = function(e) {
			focusedVerbElem = this;
		};
		var blurFunc = function(e) {
			if(focusedVerbElem == this) focusedVerbElem = 0;
		};
		var createWordElement = function(en, ru) {
			var wrap;
			if(edited) {
				wrap = domCreater('a', 'item');
				wrap.href = javascriptUrl;
				wrap.innerHTML = '★' + en + ' → ' + ru + '';
				domBindEvent(wrap, 'click', clickFunc);
				domBindEvent(wrap, 'focus', focusFunc);
				domBindEvent(wrap, 'blur', blurFunc);
			} else {
				wrap = domCreater(0, 'item');
				wrap.innerHTML = '' + en + ' → ' + ru + '';
			}
			return wrap;
		};
		windowBindToKeys(13, 'down', function(e) {
			if(fastPanelMode && focusedVerbElem) {
				domCancelEvent(e);
				domCancelEventPropagation(e);
				windowKeyNoPressed(13);
				showSettings(e, focusedVerbElem, 100);
			}
		});
		windowBindToKeys(32, 'down', function(e) {
			if(fastPanelMode && focusedVerbElem) {
				domCancelEvent(e);
				domCancelEventPropagation(e);
				windowKeyNoPressed(32);
				domToggleCssClass(focusedVerbElem, 'checked');
			}
		});
		var firstFill = 1;
		var fillList = function() {
			firstFill = 0;
			// Вставляет все слова без разбора и предшествующей сортировки.
			columns = [];
			var sumWidth = 0;
			countColumns = 0;
			do {
				columns.push(domCreater(0, 'column', container));
				sumWidth += 250;
				countColumns++;
			} while(sumWidth + 250 < containerWidth);
			var inColumn = collection.btsByLang[0].vals.length / columns.length;
			var columnInd = 0;
			var counter = 0;
			var nod = collection.btsByLang[0].first();
			if(nod) {
				var emphasis;
				var funcDevideWordsByLang = function() {
					return devideWordsByLangs(nod.os).map(function(v) {
						return v.map(function(v) {
							if(v.isEmphasis()) {
								emphasis = 1;
							}
							return v.getFormed();
						});
					});
				};
				do {
					emphasis = 0;
					counter++;
					if(counter > inColumn) {
						counter = 0;
						columnInd++;
					}
					var wordsByLangs = funcDevideWordsByLang();
					var elem = createWordElement(wordsByLangs[0].join(), wordsByLangs[1].join(', '), emphasis);
					if(emphasis) {
						domAddCssClass(elem, 'emphasis');
					}
					domAppendChild(columns[columnInd], elem);
					nod = collection.btsByLang[0].next(nod);
				} while(nod);
			}
		};
		collection.addEventListener('change', function() { dct.redraw(); });
		var insertWordElemToList = function(elem) {
			var en = domGetChilds(elem)[0].innerText.toLowerCase();
			var elems = domGetChilds(container);
			var minInd = -1;
			var maxInd = elems.length;
			if(!maxInd) {
				return domAppendChild(container, elem);
			}
			for(i = 0; i < elems.length/2 + 2; i++) {
				if(maxInd - minInd < 2) {
					if(maxInd < elems.length) {
						domInsertBefore(elem, elems[maxInd]);
					} else {
						domAppendChild(container, elem);
					}
					return 1;
				} else {
					var midInd = minInd + Math.round((maxInd - minInd)/2);
					var text = domGetChilds(elems[midInd])[0].innerText.toLowerCase();
					if(text == en) {
						return;
					} if(text > en) {
						maxInd = midInd;
					} else {
						minInd = midInd;
					}
				}
			}
			domAppendChild(container, elem);
		};
		dct.addWord = function(obj) {
			if(!obj || !obj[0].length || !obj[1] || !obj[1].length) return;
			var en = obj[0].getFormed();
			var ru = obj[1].getFormed();
			if(!en || !ru) return;
			var elem = createWordElement(en, ru);
			if(insertWordElemToList(elem)) {
				return elem;
			}
		};
		var checkActionsWrapper;
		if(edited) {
			checkActionsWrapper = domCreater(0, 'checkedActions');
			//domCreater(0, 'spacer', checkActionsWrapper);
			var checkAll = domCreater('button', 'min btn', checkActionsWrapper, 'check all');
			checkAll.title = 'отметить все';
			domBindEvent(checkAll, 0, function() {
				for(var i = 0; i < columns.length; i++) {
					var childs = domGetChilds(columns[i]);
					for(var n = 0; n < childs.length; n++) {
						domAddCssClass(childs[n], 'checked');
					}
				}
			});
			var uncheckAll = domCreater('button', 'min btn', checkActionsWrapper, 'uncheck all');
			uncheckAll.title = 'убрать все отметки';
			domBindEvent(uncheckAll, 0, function() {
				for(var i = 0; i < columns.length; i++) {
					var childs = domGetChilds(columns[i]);
					for(var n = 0; n < childs.length; n++) {
						domRemoveCssClass(childs[n], 'checked');
					}
				}
			});
			var del = domCreater('button', 'min btn', checkActionsWrapper, 'delete checked');
			del.title = 'удалить отмеченные';
			domBindEvent(del, 0, function() {
				for(var i = 0; i < columns.length; i++) {
					var childs = domGetChilds(columns[i]);
					for(var n = 0; n < childs.length; n++) {
						if(childs[n].className.indexOf('checked') !== -1) {
							var word = collection.searchByStr(domGetInnerText(domGetChilds(childs[n])[1]));
							if(word) {
								wordsTypes[word.wordTypeName].userCollection.removeWord(word);
							} else {
								warning();
							}
						}
					}
				}
			});
			var autoadding = domCreater('button', 'min btn', checkActionsWrapper, 'auto adding');
			var autoaddingOnPage = domGetById('autoaddCW');
			var autoaddingOnCange = function(act, bool) {
				var titl = 'в';
				if(bool) {
					titl += 'ы';
					domAddCssClass(autoadding, 'active');
				} else {
					domRemoveCssClass(autoadding, 'active');
				}
				titl += 'ключить автодобавление слов из частотного словаря';
				autoadding.title = titl;
				domSetInnerText(autoaddingOnPage, titl);
			};
			user.addEventListener('cAutoaddCW', autoaddingOnCange);
			autoaddingOnCange(0, user.settings.autoaddCW);
			domBindEvent([autoadding, autoaddingOnPage], 0, function(e) {
				domCancelEvent(e);
				user.setAutoaddCW(!user.settings.autoaddCW);
			});
			var addNextWord = domCreater('button', 'min btn', checkActionsWrapper, 'add next word');
			addNextWord.title = 'добавить следующее слово из частотного словоря';
			domBindEvent(addNextWord, 0, function() {
				user.autoaddCW();
			});
		}
		var hide = true;
		dct.show = function() {
			hide = false;
			domShow(container);
			if(mustRedraw) {
				dct.redraw();
			}
		};
		dct.hide = function() {
			hide = true;
			domHide(container);
		};
		var mustRedraw = false;
		dct.redraw = function() {
			if(!hide) {
				containerWidth = container.offsetWidth;
				container.innerHTML = '';
				var oldContainer = container;
				container = document.createDocumentFragment();
				domAppendChild(container, checkActionsWrapper);
				fillList();
				domAppendChild(oldContainer, container);
				container = oldContainer;
				mustRedraw = false;
			} else {
				mustRedraw = true;
			}
		};

		var timeout;
		var countColumns = 0;
		dct.resize = function(e) {
			if(timeout) windowClearTimeout(timeout);
			if(e) {
				timeout = windowSetTimeout(dct.resize, 500);
			} else {
				timeout = 0;
				containerWidth = container.offsetWidth;
				if((countColumns + 1) * 250 < containerWidth || countColumns * 250 > containerWidth) {
					dct.redraw();
				}
			}
		};
		domBindEvent(window, 'resize', dct.resize);

		dct.show();
		//fillList();

		return dct;
	};

	baseDct = createDct(domGetById('baseDct'), baseWords);
	baseDct.redraw(); // т.к. baseWords полностью подгрузились ранее.
	userDct = createDct(domGetById('userDct'), user.words, 1);
	if(user.words.loaded) baseDct.redraw();

	domBindEvent(domGetById('addManyWords'), 0, function() {
		for(var i = 0; i < 1000; i++) {
			user.autoaddCW();
		}
	});

});
/*
В документе много страниц, но разумно сделать активной только находящуюся ближе к центру экрана.
И именно ее элементы получат обработку сочетаний клавиш и т.д.
А для написания потенциально активных страниц - используем описаные ниже соглашения и обертки функций.
*/
var pages = {}; // Контейнер для всех страничек.
addEventsMixin(pages);
var toFastPanel;
var fastPanelMode = 0;
var mouseXPoz = 0;
var mouseYPoz = 0;
var scrollX = 0;
var scrollY = 0;
var setMousePoz;
(function() {
	setMousePoz = function(e) {
		if(e.changedTouches) {
			var touchobj = e.changedTouches[0];
			mouseXPoz = touchobj.pageX;
			mouseYPoz = touchobj.pageY;
		} else if(e.pageX || e.pageY) {
			mouseXPoz = e.pageX;
			mouseYPoz = e.pageY;
		} else {
			mouseXPoz = e.clientX + scrollX + document.documentElement.scrollLeft;
			mouseYPoz = e.clientY + scrollY + document.documentElement.scrollTop;
		}
	};
	domBindEvent(window, 'mousemove', setMousePoz);

	// Делаем левый/правый края кнопками для перехода к панели быстрых действий.
	addToOnloadBody(function() {
		domBindEvent(document.body, ['mousedown', 'touchstart'], function(e) {
			setMousePoz(e);
			if(fastPanelMode && mouseXPoz > document.body.offsetWidth * 0.9 && (mouseYPoz - scrollY) > 130 || mouseXPoz > document.body.offsetWidth * 0.9 + 40) {
				toFastPanel(0);
				domCancelEvent(e);
			} else if(!fastPanelMode && mouseXPoz < 45 && mouseYPoz - scrollY > 130) {
				toFastPanel(1);
				domCancelEvent(e);
			}
		});
	});
	toFastPanel = function(bool) {
		if(fastPanelMode != bool) {
			if(bool) {
				domRemoveCssClass(document.body, 'fastPanelNoActive');
				domAddCssClass(document.body, 'fastPanelIsActive');
				setActivePageInd(-1);
			} else {
				domRemoveCssClass(document.body, 'fastPanelIsActive');
				domAddCssClass(document.body, 'fastPanelNoActive');
			}
			pages.trigger('toFastPanel', bool);
			fastPanelMode = bool;
			onScrollFunc();
		}
	};
	// Делаем так, что переключаясь по TAB можно попасть в fastPanel и обратно.
	addToOnloadBody(function() {
		domGetById('focusDetectPagesLogo').focus();
		domBindEvent(domGetById('focusDetectPagesStart'), 'focus', function(e) {
			if(!fastPanelMode) {
				toFastPanel(1);
			}
		});
		domBindEvent(domGetById('focusDetectPagesLogo'), 'focus', function(e) {
			if(fastPanelMode) {
				toFastPanel(0);
			}
		});
		domBindEvent(domGetById('focusDetectFastPanel'), 'focus', function(e) {
			if(!fastPanelMode) {
				toFastPanel(1);
			}
		});
		domBindEvent(domGetById('focusDetectPagesEnd'), 'focus', function(e) {
			if(fastPanelMode) {
				toFastPanel(0);
			}
		});
	});
	// Назначаем на горизонтальный свайп переход к панели быстрых действий.
	addToOnloadBody(function() {
		var startY, startX, startScrollY;
		var touchsurface = document.body;
		domBindEvent(touchsurface, 'touchstart', function(e) {
			var touchobj = e.changedTouches[0];
			startY = touchobj.pageY;
			startX = touchobj.pageX;
			startScrollY = scrollY;
		});
		domBindEvent(touchsurface, 'touchmove', function(e) {
			if(dragStarted) return;
			var touchobj = e.changedTouches[0];
			var changeY = touchobj.pageY - startY + (scrollY - startScrollY);
			var changeX = touchobj.pageX - startX;
			if(Math.abs(changeX) > Math.abs(changeY) * 2 + 50) {
				if(changeX > 0) {
					toFastPanel(1);
				} else if(changeX < 0) {
					toFastPanel(0);
				}
				startX = touchobj.pageX + changeX / 2;
			}
		});
	});

	var onScrollFunc;
	var lastScrollCenter = {};
	var nowResize = 0;
	addToOnloadBody(function() {
		var timeout;
		onScrollFunc = function(e) {
			if(fastPanelMode) {
				pages.setCenterPage(0, 1); // Надеюсь удастся красиво запретить прокрутку основной страницы.
			} else {
				// При скролле ищем страницу, центр которой блеже всего к центру экрана.
				scrollX = document.body.scrollLeft || document.documentElement.scrollLeft || pageXOffset;
				scrollY = document.body.scrollTop || document.documentElement.scrollTop || pageYOffset;
				if(!timeout) {
					timeout = windowSetTimeout(function() {
						timeout = 0;
						var clientHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
						var nearPageInd = -1;
						var minDistActive = 999999;
						var elem, dist, i;
						for(i = 0; i < interactivePages.length; i++) {
							// Предпологаем, что #pages без отступов сверху
							elem = interactivePages[i].element;
							dist = Math.abs(elem.offsetTop + elem.offsetHeight / 2 - scrollY - clientHeight / 2);
							// Если elem не виден на экране - отметаем.
							if(dist < minDistActive && (scrollY < elem.offsetTop + elem.offsetHeight) && (scrollY + clientHeight > elem.offsetTop)) {
								nearPageInd = i;
								minDistActive = dist;
							}
						}
						setActivePageInd(nearPageInd);
						var minDistCenter = 999999;
						nearPageInd = -1;
						for(i = 0; i < pages.allPagesElems.length; i++) {
							elem = pages.allPagesElems[i];
							dist = Math.abs(elem.offsetTop + elem.offsetHeight / 2 - scrollY - clientHeight / 2);
							// Если elem не виден на экране - отметаем.
							if(dist < minDistCenter) {
								nearPageInd = i;
								minDistCenter = dist;
							}
						}
						if(nearPageInd >= 0) {
							if(!nowResize) {
								elem = pages.allPagesElems[nearPageInd];
								var offset = (elem.offsetTop + elem.offsetHeight / 2 - scrollY - clientHeight / 2) / clientHeight; // относительное смещение центра страницы.
								lastScrollCenter.elem = elem;
								lastScrollCenter.offset = offset;
								pages.trigger('scrollCenterPage', elem.id, offset, elem);
							}
						}
					}, 100);
				}
			}
		};
		domBindEvent(document.body, 'scroll', onScrollFunc);
		domBindEvent(window, 'scroll', onScrollFunc);

		pages.allPagesElems = [];
		domGetChilds(domGetById('pages')).forEach(function(el) {
			if(el && el.id && el.className && el.className.split(' ').indexOf('page') > -1) {
				pages.allPagesElems.push(el);
			}
		});

		var resizeTimeout;
		domBindEvent(window, 'resize', function() { // после ресайза перематываем к старой центральной странице.
			nowResize = 1;
			if(!resizeTimeout) {
				resizeTimeout = windowSetTimeout(function() {
					nowResize = 0;
					resizeTimeout = 0;
					pages.setCenterPage();
				}, 200);
			}
		});
	});

	pages.setCenterPage = function(elem, strong, offset) {
		if(isString(elem)) {
			elem = domGetById(elem);
		}
		if(!elem) {
			elem = lastScrollCenter.elem;
		}
		if(elem) {
			if(offset === undefined) offset = lastScrollCenter.offset;
			if(!offset) offset = 0;
			var clientHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
			var y = elem.offsetTop + elem.offsetHeight / 2 - clientHeight / 2 - offset * clientHeight;
			if(strong || Math.abs(scrollY - y) > clientHeight / 3) {
				window.scrollTo(0, y);
			}
		}
	};

	var interactivePages = [];
	pages.registrInteractivePage = function(element) { // Для регистрации страниц, имеющих интерактивные элементы.
		// element - dom элемент
		// cb - функция, выполняемая при получении/отнятии фокуса.
		// отдает специальный объект для простого доступа к общим функциям с привязкой к контексту данной страницы.
		if(!isDomNode(element)) {
			warning();
		}
		var ret = {};
		addEventsMixin(ret);
		ret.element = element;
		interactivePages.push(ret);
		return ret;
	};
	var activePageInd;
	var setActivePageInd = function(ind) {
		if(ind == activePageInd) return;
		var page = interactivePages[activePageInd];
		var funcs;
		var i;
		if(page) {
			page.active = 0;
			page.trigger('active', 0);
		}
		activePageInd = ind;
		if(activePageInd >= 0) {
			page = interactivePages[activePageInd];
			if(page) {
				page.active = 1;
				page.trigger('active', 1);
			}
		}
		pages.trigger('changeActivePage', page, activePageInd);
	};

})();
var notific = function(ins) {
	var elem = domCreater(0, 'notific trns o0 w0', domGetById('notifics'));
	if(isObject(ins)) {
		elem.appendChild(ins);
	} else {
		domSetInnerText(elem, ins);
	}
	domShow(elem);
	windowSetTimeout(function() {
		domHide(elem);
	}, 2000 + domGetInnerText(elem).length * 100);
};
var modalWindow = {};
(function() {
	var container, wrapper, closeButton, inside, cbsClose, close;
	var isOnLoad = false;
	addToOnloadBody(function() {
		container = domGetById('modalContainer');
		wrapper = domCreater(0, 'window', container);
		domBindEvent(wrapper, 0, domCancelEventPropagation);
		closeButton = domCreater('a', 'rm btn', wrapper, 'X');
		modalWindow.inside = inside = domCreater(0, 'inside', wrapper);
		modalWindow.close = close = function() {
			domHide(container, function() {
				if(cbsClose) {
					if(isArray(cbsClose)) {
						for(var i = 0; i < cbsClose.length; i++) {
							cbsClose[i]();
						}
					} else {
						cbsClose();
					}
				}
				cbsClose = 0;
				inside.innerHTML = '';
			});
		};
		domBindEvent(closeButton, 0, close);
		domBindEvent(container, 0, close);
		if(isOnLoad) isOnLoad();
		isOnLoad = true;
	});
	var createButton = function(title, cb) {
		var elem = domCreater('button', 'mid btn', 0, title);
		domBindEvent(elem, 0, cb);
		return elem;
	};
	modalWindow.createAndShowWindow = function(innerElements, buttons, cbsCl) {
		// buttons - массив, элементами которого являются массивы с данными о кнопках в виде ['title', function]
		var f = function() {
			if(innerElements) {
				if(isString(innerElements)) {
					domSetInnerText(inside, innerElements);
				} else if(isArray(innerElements)) {
					innerElements.forEach(function(elem){domAppendChild(inside, elem);});
				} else {
					domAppendChild(inside, innerElements);
				}
			}
			var buttonsContainer = domCreater(0, 0, inside);
			if(buttons) {
				buttons.forEach(function(elem){domAppendChild(buttonsContainer, createButton(elem[0], [elem[1], close]));});
			}
			if(cbsCl) {
				cbsClose = cbsCl;
			}
			domShow(container);
		};
		if(!isOnLoad) {
			isOnLoad = f;
		} else {
			f();
		}
	};

})();
// createDraggable создает объект, который управляет перетаскиванием элемента.
// На обработаный createDraggable элемент бесполезно вешать onclick, нужно вешать на возвращаемый объект.
var createDraggable;
var dragStarted;
addToOnloadBody(function() {
	var dragNow;
	var touchElem;
	var touchObj;
	var touchsurface = document.body;
	var startDragOffsetX;
	var startDragOffsetY;
	var startDragX;
	var startDragY;
	var startPosition;
	var startLeft;
	var startTop;
	var positionTyp;
	var startDrag = function(e, elem, obj) {
		domCancelEventPropagation(e);
		domCancelEvent(e);
		if(dragNow) stopDrag(e);
		touchObj = obj;
		touchElem = elem;
		dragNow = 0;
		setMousePoz(e);
		startDragX = mouseXPoz;
		startDragY = mouseYPoz;
		var offset = domOffset(touchElem);
		startDragOffsetX = offset.left - mouseXPoz;
		startDragOffsetY = offset.top - mouseYPoz;
		touchObj.trigger('startdrag', e);
		dragStarted = 1;
	};
	var move = function(e) {
		if(dragNow) {
			domCancelEventPropagation(e);
			domCancelEvent(e);
		}
		if(!dragStarted) return;
		setMousePoz(e);
		if(dragNow || mouseXPoz > startDragX + 10 || mouseXPoz < startDragX - 10 || mouseYPoz > startDragY + 10 || mouseYPoz < startDragY - 10) {
			if(!dragNow) {
				startPosition = touchElem.style.position;
				startLeft = touchElem.style.left;
				startTop = touchElem.style.top;
				touchElem.style.position = positionTyp;
				dragNow = 1;
			}
			if(positionTyp === 'fixed') {
				touchElem.style.left = (mouseXPoz - scrollX + startDragOffsetX) + 'px';
				touchElem.style.top = (mouseYPoz - scrollY + startDragOffsetY) + 'px';
			} else if(positionTyp === 'absolute') {
				touchElem.style.left = (mouseXPoz + startDragOffsetX) + 'px';
				touchElem.style.top = (mouseYPoz + startDragOffsetY) + 'px';
			}
			touchObj.trigger('move', e);
		}
	};
	var stopDrag = function(e) {
		if(dragStarted && !dragNow) touchObj.trigger('click', e);
		if(dragNow) {
			domCancelEventPropagation(e);
			domCancelEvent(e);
			setMousePoz(e);
			touchObj.trigger('stopdrag', e);
			touchElem.style.position = startPosition;
			touchElem.style.left = startLeft;
			touchElem.style.top = startTop;
		}
		dragNow = 0;
		dragStarted = 0;
	};
	domBindEvent(touchsurface, 'touchmove', move);
	domBindEvent(touchsurface, 'touchend', stopDrag);
	domBindEvent(touchsurface, 'mousemove', move);
	domBindEvent(touchsurface, 'mouseup', stopDrag);

	createDraggable = function(elem, posTyp) {
		if(positionTyp === 'absolute') {
			positionTyp = positionTyp;
		} else {
			positionTyp = 'fixed'; // посмотрел на нексусе - с fixed лучше.
		}
		var ret = {};
		addEventsMixin(ret);
		ret.elem = elem;
		var start = function(e) {
			startDrag(e, elem, ret);
		};
		domBindEvent(elem, 'touchstart', start);
		domBindEvent(elem, 'mousedown', start);
		return ret;
	};

});
var trainingTypes = {};
trainingTypes[0] = {
	create: function(page, func, stdVals) {
		var ret = {};
		addDeactivatableActiveMixin(ret);
		ret.elem = domGetByClass('training-simple', page.element)[0]; // в html изначально уже есть все элементы для тренировок этого типа.
		// навешивает на элименты страницы с тренеровкой перевода фраз функционал.
		var sentence0 = domGetByClass('sentence0', ret.elem)[0];
		var sentence1 = domGetByClass('sentence1', ret.elem)[0];
		var settingsBtn = domGetByClass('settings', ret.elem)[0];
		var speed = stdVals.speed || 5;
		var speedLabel = domCreater('label', 0, settingsBtn, 'speed:');
		var speedInput = domCreater('input', 'speed', speedLabel);
		speedInput.value = speed;
		speedInput.type = 'text';
		speedInput.maxlength = 2;
		domBindEvent(speedInput, 'change', function() {
			speed = Math.min(parseInt(this.value) || 5, 100);
		});
		var order = stdVals.order || 0;
		var orderWrapper = domCreater('div', 0, settingsBtn, 'order:');
		var orders = ['en', 'ru', 'mix'];
		var sid = nextSID();
		orders.forEach(function(orderStr, i) {
			var orderVal = i;
			var radioLabel = domCreater('label', 0, orderWrapper);
			var orderInput = domCreater('input', 0, radioLabel);
			domCreater('span', 0, radioLabel, orders[orderVal]);
			orderInput.type = 'radio';
			orderInput.name = sid;
			domBindEvent(orderInput, 'change', function() {
				if(this.checked) {
					order = orderVal;
				}
			});
			if(order == orderVal) orderInput.checked = 'checked';
		});
		var history = [];
		var indInHistory = -1;
		var timeouts = [];
		var lastAnswerInHistory = -1; // указывает, на индекс фраз в истории, для которой в последний раз показывался ответ.
		var showAnswer;
		var nextPhrase = function(e) {
			if(lastAnswerInHistory > -2 && lastAnswerInHistory !== indInHistory) {
				showAnswer();
				return;
			}
			lastAnswerInHistory = indInHistory;
			for(var i = 0; i < timeouts.length; i++) {
				ret.clearTimeout(timeouts[i]);
			}
			indInHistory++;
			var strs;
			var ord;
			if(indInHistory >= history.length) {
				strs = func(stdVals.PhSettings);
				ord = order;
				history[indInHistory] = [strs, ord];
			} else {
				strs = history[indInHistory][0];
				ord = history[indInHistory][1];
			}
			if(!strs) strs = [];
			if(!strs[0]) strs[0] = 'Phrase is not generated.';
			if(!strs[1]) strs[1] = 'Фраза не сгенерирована.';
			if(ord == 2) ord = randomInt(2);
			domSetInnerText(sentence0, strs[ord]);
			domSetInnerText(sentence1, '');
			timeouts[0] = ret.setTimeout(function() {
				domSetInnerText(sentence1, '.');
			}, speed * 150);
			timeouts[1] = ret.setTimeout(function() {
				domSetInnerText(sentence1, '..');
			}, speed * 300);
			timeouts[2] = ret.setTimeout(function() {
				domSetInnerText(sentence1, '...');
			}, speed * 450);
			showAnswer = function() {
				domSetInnerText(sentence1, strs[1 - ord]);
				sentence1.title = strs[2].comments;
				for(var i = 0; i < timeouts.length; i++) {
					ret.clearTimeout(timeouts[i]);
				}
				timeouts[4] = ret.setTimeout(function() {
					lastAnswerInHistory = indInHistory;
				}, 100);
				timeouts[6] = ret.setTimeout(function() {
					// Если пользователь мог взглянуть на ответ - добавим словам succesfull.
					var phrase = strs[2];
					if(history[history.length - 1][0] === strs[0]) {
						if(phrase) {
							phrase.allWords.forEach(function(w) {
								w.addSuccesfull(1);
							});
						}
					}
				}, Math.min(Math.max(500, speed * 50), 2000));
				timeouts[5] = ret.setTimeout(nextPhrase, speed * 400);
			};
			timeouts[3] = ret.setTimeout(showAnswer, speed * 600);
			domCancelEvent(e);
		};
		domBindEvent(domGetByClass('next', ret.elem), 0, nextPhrase);
		ret.bindToKeys(39, 'press', nextPhrase);
		var previousPage = function() {
			if(indInHistory > 0) {
				indInHistory -= 2;
				lastAnswerInHistory = -2;
				nextPhrase();
			}
		};
		var previousButton = domGetByClass('previous', ret.elem)[0];
		domBindEvent(previousButton, 0, previousPage);
		ret.bindToKeys(37, 'down', previousPage);
		ret.start = nextPhrase;
		ret.setInterval(user.autoaddCW, 200000);
		return ret;
	}
};
trainingTypes[1] = {
	create: function(page, func, stdVals) {
		var ret = {};
		addDeactivatableActiveMixin(ret);
		ret.elem = domCreater(0, 'trPhCn');
		var table = domCreater('table', 'heap noselect', ret.elem);
		var tr = domCreater('tr', 0, table);
		var wordsHeapL = domCreater('td', 0, tr);
		var wordsHeapR = domCreater('td', 'r', tr);
		var ruSentence = domCreater(0, 'ru', ret.elem);
		var enSentence = domCreater(0, 'en noselect', ret.elem);
		var nextButton = domCreater('button', 'btn big', ret.elem, '→');
		var settingsButton = domCreater('a', 'btn big settings', ret.elem, domCreater(0, 'ico'));
		var phr, pieces, shufflePieces, endPiece, endPieceElem, piecesElems, pieceContainers;
		var nextPhrase = function() {
			wordsHeapL.innerHTML = '';
			wordsHeapR.innerHTML = '';
			ruSentence.innerHTML = '';
			enSentence.innerHTML = '';
			var containerL = document.createDocumentFragment();
			var containerR = document.createDocumentFragment();
			var strs = func(stdVals.PhSettings);
			domSetInnerText(ruSentence, strs[1]);
			phr = strs[2];
			pieces = removeFromArrayByValue(phr.writeEn(1).split(' '), '');
			endPiece = pieces.splice(pieces.length - 1, 1)[0];
			endPieceElem = domCreater('span', 'end', enSentence, endPiece);
			shufflePieces = shuffleArray(pieces.concat());
			piecesElems = [];
			pieceContainers = [];
			shufflePieces.forEach(function(piece, i) {
				var pieceContainer = domCreater(0, 'pieceContainer');
				var pieceElem = domCreater('button', 'piece', pieceContainer);
				domSetInnerText(pieceElem, piece);
				var draggable = createDraggable(pieceElem);
				draggable.addEventListener('stopdrag', function(act, e) {
					if(mouseXPoz > domOffset(table).left) {
						domAppendChild(pieceContainer, pieceElem);
						return;
					}
					var childs = domGetChilds(enSentence);
					var child;
					var firstNext;
					for(i = 0; i < childs.length; i++) {
						child = childs[i];
						if(childs !== pieceElem) {
							if(mouseYPoz < child.offsetTop - child.offsetHeight / 3) {
								firstNext = child;
								break;
							} else if(mouseYPoz < child.offsetTop + child.offsetHeight * 2) {
								if(mouseXPoz < child.offsetLeft + child.offsetWidth / 2) {
									firstNext = child;
									break;
								}
							}
						}
					}
					if(firstNext) {
						domInsertBefore(pieceElem, firstNext);
					} else {
						domInsertBefore(pieceElem, endPieceElem);
					}
					testSentence();
				});
				var click = function(act, e) {
					if(pieceElem.parentNode !== enSentence) {
						domInsertBefore(pieceElem, endPieceElem);
					} else {
						domAppendChild(pieceContainer, pieceElem);
					}
					testSentence();
				};
				//domBindEvent(pieceElem, 0, click);
				draggable.addEventListener('click', click);
				piecesElems.push(pieceElem);
				pieceContainers.push(pieceContainer);
				domAppendChild((piece.length > 3) ? containerL : containerR, pieceContainer);
			});
			domAppendChild(wordsHeapL, containerL);
			domAppendChild(wordsHeapR, containerR);
		};
		domBindEvent(nextButton, 0, nextPhrase);
		var testSentence = function() {
			var childs = domGetChilds(enSentence);
			var i, n;
			var err;
			var countPieces = 0;
			fors:
			for(i = 0; i < childs.length; i++) {
				for(n = 0; n < piecesElems.length; n++) {
					if(piecesElems[n] === childs[i]) {
						countPieces++;
						if(pieces[i] === shufflePieces[n]) {
						} else {
							err = i;
							break fors;
						}
					}
				}
			}
			if(!err && countPieces === pieces.length) nextPhrase();
		};
		ret.start = nextPhrase;
		ret.setInterval(user.autoaddCW, 160000);
		return ret;
	}
};
var hungTrainingPage = function(pageName, func, stdVals, specValsForTrainingTypes) {
	// func - функция, создающяя фразы.
	if(!specValsForTrainingTypes) specValsForTrainingTypes = {}; // специальные настройки для разных видов тренировки.
	var elem = domGetById(pageName);
	var page = pages.registrInteractivePage(elem);
	var chooser = domGetByClass('chooser', elem)[0];
	var choses = domGetByClass('choose', chooser);
	var trainingContainer = domGetByClass('training-container', elem)[0];
	var trainingElems = {}; // объекты разных трпнировок по типам.
	var activeTrainingTyp = 0;
	var setActiveTrainingElem = function(trainingTyp, chose) {
		if(activeTrainingTyp === trainingTyp) return;
		trainingElems[activeTrainingTyp].setActive(0);
		domRemove(trainingElems[activeTrainingTyp].elem);
		if(trainingTyp in trainingElems) {
			domAppendChild(trainingContainer, trainingElems[trainingTyp].elem);
			if(page.active) {
				var t = trainingElems[trainingTyp];
				t.setActive(1);
				if(!t.started) {
					t.start();
					t.started = 1;
				}
			}
			activeTrainingTyp = trainingTyp;
			domRemoveCssClass(choses, 'active');
			domAddCssClass(chose, 'active');
		}
	};
	choses.forEach(function(chose, i) {
		var trainingTyp = domGetData(chose, 'trainingTyp');
		if(!trainingTyp) trainingTyp = i;
		if(trainingTypes[trainingTyp]) {
			trainingElems[trainingTyp] = trainingTypes[trainingTyp].create(page, func, stdVals, specValsForTrainingTypes[trainingTyp]);
			if(!i) setActiveTrainingElem(trainingTyp, chose);
		} else {
			warning();
		}
		domBindEvent(chose, 0, function() {
			setActiveTrainingElem(trainingTyp, chose);
		});
	});
	page.addEventListener('active', function(act, bool) {
		var t = trainingElems[activeTrainingTyp];
		if(t) {
			trainingElems[activeTrainingTyp].setActive(bool);
			if(bool && !t.started) {
				t.start();
				t.started = 1;
			}
		}
	});
};
addToOnloadBody(function() {
	var dict = {};
	var activeInf;
	var timeout;
	var active = function(e) {
		if(activeInf !== this) {
			var infHint = domGetByClass('infHint', this)[0];
			if(infHint) {
				deactive();
				activeInf = this;
				//setMousePoz(e);
				var x = this.offsetLeft;
				infHint.style.left = x + 'px';
				if(x > document.body.offsetWidth - 220) {
					infHint.style.left = document.body.offsetWidth - 220 + 'px';
				}
				domAddCssClass(this, 'active');
				if(timeout) windowClearTimeout(timeout);
				timeout = windowSetTimeout(deactive, domGetInnerText(infHint).length * 40 + 2000);
			}
		}
	};
	var deactive = function() {
		domRemoveCssClass(activeInf, 'active');
		activeInf = 0;
	};
	domBindEvent(win, 0, deactive);
	domGetByClass('inf').forEach(function(el) {
		var text = domGetInnerText(el).toLowerCase();
		var titl = el.title;
		var onDict;
		if(!titl) {
			titl = dict[text.substr(0, 4)];
			onDict = 1;
		}
		if(titl) {
			if(!onDict) dict[text.substr(0, 4)] = titl;
			var infHint = domCreater(0, 'infHint');
			domSetInnerText(infHint, titl);
			domAppendChild(el, infHint);
			domBindEvent(el, ['mouseover', 'click'], active);
			el.title = '';
		}
	});
});
})();