/*
@version $Id: data.js 270 2009-12-28 13:24:34Z roosit $
@copyright Copyright (C) 2008 Abricos. All rights reserved.
@license http://www.gnu.org/copyleft/gpl.html GNU/GPL, see LICENSE.php
*/

/**
 * @module Sys
 */

Brick.namespace('util.data.byid');

var Component = new Brick.Component();
Component.requires = {
	mod:[{name: 'sys', files: ['data.js']}]
};
Component.entryPoint = function(){
	
	/**
	 * Менеджер таблиц DataSet. 
	 * 
	 * @class DataSet
	 * @submodule Data
	 * @namespace Brick.util.data.byid
	 * @constructor
	 * @param name {String} Имя модуля платформы BrickCMS с которым происходит обмен данными
	 * @param prefix {String} (optional) Префикс
	 */
	var DataSet = function(name, prefix){
		
		/**
		 * Имя DataSet
		 * 
		 * @property name
		 * @type String
		 */
		this.name = name;
		
		/**
		 * Префикс
		 * 
		 * @property prefix
		 * @type String
		 */
		this.prefix = prefix;
		
		/**
		 * Коллекция таблиц
		 * 
		 * @property tables
		 * @type {String: Brick.util.data.byid.Table}
		 */
		this.tables = {};
		
	    /**
	     * Событие, вызывает, когда данные приходят с сервера в запросе и
	     * заполняют таблицы 
	     *
	     * @event onComplete
	     */
		this.onComplete = new YAHOO.util.CustomEvent("onComplete");
		
		this.session =  Math.round((new Date()).getTime()/1000);
	};
	DataSet.prototype = {
		
		/**
		 * Получить объект данных подготовелных для отправки в запросе серверу
		 * 
		 * @method _getPostData
		 * @param tables {Brick.util.data.byid.Table[]} (optional) Массив таблиц, если не указан, 
		 * будут использованы таблицы из коллекции этого DataSet 
		 * @return {Object}
		 * @private
		 */
		_getPostData: function(tables, resetFlags){
			tables = tables || this.tables;
			resetFlags = resetFlags || false;
			var ts = [], tmp;
			for (var nn in tables){
				var table = this.tables[nn];
				tmp = table.getPostData();
				
				if (!YAHOO.lang.isNull(tmp)){
					ts[ts.length] = tmp;
					if (resetFlags){
						table.resetFlags();
					}
				}
			}
			if (ts.length == 0){
				return null;
			}
			return ts;
		},
		
		/**
		 * Добавляет таблицу в коллекцию
		 * 
		 * @method add
		 * @param table {Brick.util.data.byid.Table} Добавляемая таблица
		 * @return {Boolean} True если таблица добавлена, иначе False
		 */
		add: function(table){
			if (this.tables[table.name]){
				return false;
			}
			this.tables[table.name] = table;
			return true;
		},
		
		remove: function(name){
			delete this.tables[name];
		},
		
		/**
		 * Добавляет массив таблиц в коллекцию
		 * 
		 * @method addRange
		 * @param tables {Brick.util.data.byid.Table[]} Массив добавляемых таблиц
		 */
		addRange: function(tables){
			for (var nn in tables){
				this.add(tables[nn]);
			}
		},
		
		/**
		 * Получить таблицу из коллекции
		 * 
		 * @method get
		 * @param name {String} Имя таблицы
		 * @param createIfNotFound {Boolean} True - создаст таблицу, если ее нет в коллекции и вернее ее
		 * @return {Brick.util.data.byid.Table} Таблица
		 */
		get: function(name, createIfNotFound){
			if (!this.tables[name] && createIfNotFound){
				this.add(new Table(name));
			}
			return this.tables[name] || null;
		},
		
		/**
		 * Обновить данные в таблицах, которые сервер вернул в запросе, 
		 * а так же выполнить событие onComplete
		 * 
		 * @method update
		 * @param obj {Object} Данные ответа сервера
		 * @return {Boolean} 
		 */
		update: function(obj){
			
			var checker = function(rq){
				this.check = function(tables){
					for (var i=0;i<tables.length;i++){
						if (rq[tables[i]]){ return true; }
					}
					return false;
				};
				
				this.checkWithParam = function(tname, param){
					if (!rq[tname]){
						return false;
					}
					var fromKey = Rows.getParamHash(param);
					var arr = rq[tname];
					for (var i=0;i<arr.length;i++){
						if (fromKey == Rows.getParamHash(arr[i])){ return true; }
					}
					return false;
				};
			};
			var updtbls = {};
			var i, di, isempty, table;
			for (i=0; i<obj['_ds'].length; i++){
				di = obj['_ds'][i];
				
				updtbls[di['nm']] = function(){
					var lst = [];
					for (var i=0;i<di['rs'].length;i++){
						lst[lst.length] = di['rs'][i]['p'];
					}
					return lst;
				}();
				this.get(di['nm'], true).update(di);
			}
			this.onComplete.fire(new checker(updtbls));
		},
		
		
		/**
		 * Запросить сервер обновить данные в таблицах
		 * 
		 * @method request
		 * @param hidden {Boolean} Если True, то запрос осуществить в фоновом режиме
		 */
		request: function(hidden){
			hidden = hidden || false;
			var ts = this._getPostData(this.tables, true);
			if (YAHOO.lang.isNull(ts)){ return; }
			Brick.util.Connection.sendCommand(this.name, 'js_data', {
				hidden: hidden,
				json: {
					'_ds': {
					'pfx': this.prefix,
					'ss': this.session,
					'ts': ts
				}
			}});
		},
		
		/**
		 * Являются ли таблицы заполнеными
		 * 
		 * @method isFill
		 * @param tables {Brick.util.data.byid.Table[]} (optional) Массив таблиц
		 * @return {Boolean} Если False, то таблицы нуждаются в обновлении данных с сервера
		 */
		isFill: function(tables){
			var ts = this._getPostData(tables);
			return YAHOO.lang.isNull(ts); 
		}
	};
	
	/**
	 * Таблица 
	 * 
	 * @namespace Brick.util.data.byid
	 * @class Table
	 * @constructor
	 * @param name {String} Имя таблицы, используется для идентификации таблицы в коллекции
	 */
	var Table = function(name){

		/**
		 * Имя таблицы
		 * 
		 * @property name
		 * @type String
		 */
		this.name = name;
		
		/**
		 * Коллекция колонок
		 * 
		 * @property columns
		 * @type Brick.util.data.byid.Columns
		 */
		this.columns = new Columns();
		
		var _rowsparam = new RowsParam();
		var _lastUpdate = 0;
		var _recycleclear = false;
		
		/**
		 * Получить последний обновляемый rows
		 * 
		 * @method getLastUpdateRows
		 * @return {Brick.util.data.byid.RowsParam}
		 */
		this.getLastUpdateRows = function(){
			return _rowsparam.getLastUpdateRows();
		};
		
		/**
		 * Очистить таблицу от данных.
		 * 
		 * @method clear
		 */
		this.clear = function(){
			_rowsparam.clear();
		};
		
		this.countRowsParam = function(){
			return _rowsparam.count();
		};
		
		/**
		 * 
		 */
		this.findRows = function(exp){
			return _rowsparam.findRows(exp);
		};
		
		/**
		 * Создать и вернуть новую запись в таблице.
		 * 
		 * @method newRow
		 * @return {Row} Новая запись
		 */
		this.newRow = function(){
			var cols = this.columns.getArray();
			var data = {};
			for (var i=0;i<cols.length;i++){
				data[cols[i].name] = "";
			}
			return new Row(data);
		};
		
		this.recycleClear = function(){
			_recycleclear = true;
		};
		
		this.getPostData = function(){
			var cmd = [];
			if (this._lastUpdate == 0 || this.columns.count() == 0){
				cmd[cmd.length] = 'i';
			}
			if (_recycleclear){
				cmd[cmd.length] = 'rc';
				_rowsparam.clear();
			}
			
			var rows = _rowsparam.getPostData();
			if (YAHOO.lang.isNull(rows)){ return null; }
			var post = { 'nm': this.name, 'rs': rows };
			
			post['cmd'] = cmd;
			return post;
		}; 

		/**
		 * Указать, актуальны ли данные в этой таблицы, если нет, то есть
		 * необходимость запросить сервер на ее обновление.
		 * 
		 * @method isFill
		 * @return {Boolean} Если True, таблицу необходимо обновить.
		 */
		this.isFill = function(){
			var pd = this.getPostData();
			return YAHOO.lang.isNull(pd); 
		};

		/**
		 * Получить коолекцию записей по определенным параметрам.
		 * @method getRows
		 * @param {Object} param Параметры коллекции, так же является
		 * идентификатором ее.
		 * @param {Object} overparam Дополнительные параметры коллекции
		 * @return {Rows}
		 */
		this.getRows = function(param, overparam){
			return _rowsparam.getRows(param, overparam);
		};
		
		this.getAllRows = function(){
			return _rowsparam.getAllRows();
		};

		this.removeNonParam = function(param){
			_rowsparam.removeNonParam(param);
		};
		
		this.removeByParam = function(param){
			_rowsparam.removeByParam(param);
		};
		
		this.update = function(o){
			o['cs'] = o['cs'] || [];
			this.columns.update(o['cs']);
			_rowsparam.update(o['rs']);
			_lastUpdate =  Math.round((new Date()).getTime()/1000);
			_recycleclear = false;
		};
		
		/**
		 * Применить изменения в таблицы, тем самым указав DataSet 
		 * что ее необходимо обновить запросом на сервер. 
		 * 
		 * @method applyChanges
		 */
		this.applyChanges = function(){
			_rowsparam.applyChanges();
		};
		
		this.resetFlags = function(){
			_rowsparam.resetFlags();
		};
	};
	
	var Column = function(name){ 
		this.init(name); 
	};
	Column.prototype = {
		init: function(name){
			this.name = name;
		}
	};
	
	var Columns = function(){ this.init(); };
	Columns.prototype = {
		init: function(){
			var _cols = {};
			var _count = 0;
			
			this.clear = function(){
				_cols = {};
				_count = 0;
			};
			
			this.add = function(col){
				if (!_cols[col.name]){ _count++; }
				_cols[col.name] = col;
			};

			this.update = function(o){
				if (o.length == 0){ return; }
				var i;
				for (i=0;i<o.length;i++){
					this.add(new Column(o[i]));
				}
			};
			
			this.count = function(){ return _count; };
			
			this.getArray = function(){
				var ret = [];
				for (var nn in _cols){
					ret[ret.length] = _cols[nn];
				}
				return ret;
			};
		}
	};
	
	var _globalRowId = 1;
	
	/**
	 * Запись (строка) в коллекции Rows
	 * 
	 * @namespace Brick.util.data.byid
	 * @class Row
	 * @constructor
	 * @param data {String: Object} (optional) Данные записи 
	 */
	var Row = function(data){
		data = data || {};
		var _isnew = false;
		var _applychanges = false;
		var _isupdate = false;
		var _isremove = false;
		var _isrestore = false;
		
		if (!data['id']){
			data['id'] = 'nn'+(_globalRowId++);
			_isnew = true;
		}
		
		/**
		 * Идентификатор записи
		 * 
		 * @property id
		 * @type String
		 */
		this.id = data['id'];
		
		/**
		 * Данные записи
		 * @property cell
		 * @type Object
		 */
		this.cell = data;
		
		/**
		 * Указывает, является ли запись новой
		 * @method isNew
		 * @return {Boolean} Возвращает True, если запись новая, иначе False
		 */
		this.isNew = function(){ return _isnew; };
		
		/**
		 * Указывает, были ли изменены данные в записи
		 * @method isUpdate
		 * @return {Boolean} Возвращает True, если данные записи
		 * были изменены, иначе False
		 */
		this.isUpdate = function(){ return _isupdate; };
		
		/**
		 * Указывает, были ли какие либо изменения в записи 
		 * (удалена, обновлена или новая). Метод необходим для
		 * определения необходимости отправить эту запись серверу.
		 * 
		 * @method isApplyChanges
		 * @return {Boolean} Возвращает True, если запись
		 * была изменена, иначе False
		 */
		this.isApplyChanges = function(){ return _applychanges; };
		
		/**
		 * Указывает, помечена ли запись на удаление
		 * @method isRemove
		 * @return {Boolean} 
		 */
		this.isRemove = function(){ return _isremove; };
		
		/**
		 * Указывает, помечена ли запись как восстановленая
		 * @method isRestore
		 * @return {Boolean} 
		 */
		this.isRestore = function(){ return _isrestore; };
		
		/**
		 * Применить изменения в записи, тем самым подтвердив то,
		 * что запись необходимо актуализировать на сервере.
		 * @method applyChanges
		 */
		this.applyChanges = function(){
			if (this.isNew() || this.isUpdate() || this.isRemove() || this.isRestore()){
				_applychanges = true; 
			}
		};
		
		/**
		 * Отметить флаг состояния записи: удалена
		 * @method remove
		 */
		this.remove = function(){
			_isremove = true;
		};
		
		/**
		 * Отметить флаг состояния записи: восстановлена
		 * @method restore
		 */
		this.restore = function(){
			_isrestore = true;
		};
		
		/**
		 * Сравнить совпадение данных выражения с данными записи. 
		 * @method checkExpression
		 * @param {Object} exp Данные выражения
		 */
		this.checkExpression = function(exp){
			for (var nn in exp){
				if (this.cell[nn] != exp[nn]){ return false; }
			}
			return true;
		};
		
		this.getPostData = function(){
			if (!_applychanges){ return null; }
			var flag = "";
			if (this.isRestore()){ flag = 'r';
			}else if (this.isRemove()){ flag = 'd';
			}else if (this.isNew()){ flag = 'a';
			}else if (this.isUpdate()){ flag = 'u'; }
			return { f: flag, d: this.cell };
		};

		/**
		 * Сброс всех флагов указывающих на изменения данных в строке.
		 * 
		 * @method resetFlags
		 */
		this.resetFlags = function(){
			_isnew = false;
			_applychanges = false;
			_isupdate = false;
			_isremove = false;
			_isrestore = false;
		};
		
		/**
		 * Удалить данные полей, при этом не удалять поле id и те, что 
		 * указаны в параметре noneRemove. <br>
		 * Зачастую бывает необходимо отправить на сервер изменения в
		 * полях записи, но при этом не отправлять в запросе все поля. 
		 * Как раз для этих случаев необходимо вызывать этот метод.  
		 * 
		 * @method clearFields
		 * @param {String} noneRemove Поля, которые необходимо оставить. 
		 * Указываются через запятую.
		 */
		this.clearFields = function(noneRemove){
			noneRemove = noneRemove || "";
			var arr = noneRemove.split(",");
			var newCell = {};
			for (var nn in this.cell){
				if (nn == 'id'){
					newCell[nn] = this.cell[nn];
				}else{
					var flagRemove = true;
					for (var i=0;i<arr.length;i++){
						if (nn == YAHOO.lang.trim(arr[i])){
							flagRemove = false;
							break;
						}
					}
					if (!flagRemove){
						newCell[nn] = this.cell[nn];
					}
				}
			}
			this.cell = newCell;
		};
		
		/**
		 * Обновить данные в записи.
		 * @method update
		 * @param {Object} data
		 */
		this.update = function(data){
			var newval, oldval;
			for (var nn in data){
				newval = data[nn];
				oldval = this.cell[nn];
				if (newval != oldval){
					_isupdate = true;
					this.cell[nn] = data[nn];
				}
			}
		};
		
		/**
		 * Клонировать запись.
		 * @method clone
		 * @return {Row}
		 */
		this.clone = function(){
			var data = {};
			for (var nn in this.cell){
				data[nn] = this.cell[nn];
			}
			var row = new Row(data);
			return row;
		};
		
		this.sync = function(row){
			this.update(row.cell);
			_isnew = row.isNew();
			_applychanges = row.isApplyChanges();
			_isupdate = row.isUpdate();
			_isremove = row.isRemove();
			_isrestore = row.isRestore();
		};
	};
	
	var globalForeachId = 1;
	
	var keysort = function(a, b){
	    var anew = a.toLowerCase();
	    var bnew = b.toLowerCase();
	    if (anew < bnew) return -1;
	    if (anew > bnew) return 1;
	    return 0;
	};
	
	/**
	 * Коллекция записей
	 * @class Rows
	 * @constructor
	 * @param {Object} param Параметры коллекции, так же является ее идентификатором.
	 * @param {Object} overparam Дополнительные параметры коллекции.
	 */
	var Rows = function(param, overparam){
		this.init(param, overparam);
	};
	
	var toString = function(p){
		var ret = '';
		
		if (YAHOO.lang.isArray(p) || YAHOO.lang.isObject(p)){
			for (var n in p){
				ret += toString(p[n]); 
			}
		} else if (YAHOO.lang.isFunction(p)){
		} else {
			ret += p + '';
		}
		return ret;
	};
	
	/**
	 * Получить хеш-идентификатор из объекта параметров коллекции.
	 * 
	 * @method getParamHash
	 * @static
	 * @param {Object} param Параметры коллекции записей.
	 * @return {String} Хеш-идентификатор
	 */
	Rows.getParamHash = function(param){
		param = param || {};
		var arr = [];
		for (var nn in param){
			arr[arr.length] = nn + toString(param[nn]);
		}
		arr.sort(keysort);
		return 'p'+arr.join('');
	};
	
	Rows.prototype = {
		init: function(param, overparam){
			param = param || {};
			overparam = overparam || {};
			
			/**
			 * Параметры коллекции, так же является ее идентификатором.
			 * @property param
			 * @type Object
			 */
			this.param = param;

			/**
			 * Дополнительные параметры коллекции.
			 * @property overparam
			 * @type Object
			 */
			this.overparam = overparam;
			
			/**
			 * Идентификатор коллекции, сформирован из param методом Rows.getParamHash().
			 * 
			 * @property key
			 * @type String
			 */
			this.key = Rows.getParamHash(param);

			var _rows = {};
			var _count = 0;
			var _lastUpdate = 0;
			
			this.lastUpdateTime = function(){ return _lastUpdate; };
			
			/**
			 * Очистить записи в коллекции, тем самым указав DataSet необходимость
			 * обновить их запросом на сервер.
			 * @method clear
			 */
			this.clear = function(){
				_rows = {};
				_count = 0;
				_lastUpdate = 0;
			};
			
			/**
			 * Кол-во записей.
			 * @method count
			 * @return Integer
			 */
			this.count = function(){ return _count; };
			
			/**
			 * Добавить запись в коллекцию.
			 * @method add
			 * @param {Row} row запись.
			 */
			this.add = function(row){
				if (!_rows[row.id]){ _count++; }
				_rows[row.id] = row;
			};
			
			this.remove = function(row){ delete _rows[row.id]; };
			
			/**
			 * Получить запись из коллекции по идентификатор row.id.
			 * @method getById
			 * @param {String} id Идентификатор записи.
			 * @return {Row | null}
			 */
			this.getById = function(id){
				if (_rows[id])
					return _rows[id];
				return null;
			};
			
			this.get = function(field, value){
				var row;
				for (var id in _rows){
					row = _rows[id];
					if (row[field] == value){
						return row;
					}
				}
				return null;
			};
			/**
			 * Получить запись из коллекции по индексу.
			 * @method getByIndex
			 * @param {Integer} index Индекс записи.
			 * @return {Row | null}
			 */
			this.getByIndex = function(index){
				var i = 0;
				for (var id in _rows){
					if (i == index){
						return _rows[id];
					}
					i++;
				}
				return null;
			};
			
			/**
			 * Найти запись в коллекции используя выражение exp
			 * 
			 * @method find
			 * @param exp {String: String|Integer} Выражение
			 * @return
			 */
			this.find = function(exp){
				var rows = this.filter(exp);
				if (rows.count() == 0){
					return null;
				}
				return rows.getByIndex(0);
			};
			
			/**
			 * Вернуть коллекцию записей в таблице отфильтрованных по выражению exp.
			 * 
			 * Например: filter({'field1': 0, 'field2': 'black'})
			 * 
			 * @method filter
			 * @param exp {String: String|Integer} Выражение
			 * @return {Rows}
			 */
			this.filter = function(exp){ // example: {fld1: 0, fld2: ''}
				var row, rows = new Rows(), isret;
				for (var id in _rows){
					row = this.getById(id);
					if (row.checkExpression(exp)){ 
						rows.add(row); 
					}
				}
				return rows;
			};
			
			/**
			 * Организовать проход по записям в коллекции.
			 * 
			 * @method foreach
			 * @param {Function} func Функция обработчик прохода. Необходимо
			 * определять с параметром, в него будет передаваться строка в процессе
			 * прохода по коллекции. 
			 * @param {Object} owner
			 */
			this.foreach = function(func, owner){
				var fname = "__rows_foreach"+(globalForeachId++);
				owner = owner || {};
				owner[fname] = func;
				var i = 0;
				for (var id in _rows){
					owner[fname](_rows[id], i);
					i++;
				}
				delete owner[fname];
			};

			/**
			 * Получить массив записей с их данными.
			 * @method getValues
			 * @return {Array}
			 */
			this.getValues = function(from, count){
				
				from = from || 0;
				count = count || 9999999;
				
				var d = [];
				var i=0;
				for (var id in _rows){
					if (i >= from && d.length < count){
						d[d.length] = _rows[id].cell;
					}
					i++;
				}
				return d;
			};
			
			/**
			 * Получить массив записей
			 * @method getArray
			 * @return {[Row]}
			 */
			this.getArray = function(){
				var data = [];
				for (var id in _rows){
					data[data.length] = _rows[id];
				}
				return data;
			};
			
			this.applyChanges = function(){
				for (var id in _rows){
					_rows[id].applyChanges();
				}
			};

			this.getPostData = function(){
				var arr = [], pd;
				for (var id in _rows){
					pd = _rows[id].getPostData();
					if (!YAHOO.lang.isNull(pd)){ arr[arr.length] = pd; }
				}
				if (arr.length > 0 || _lastUpdate == 0){
					return { 
						p: param,
						op: overparam,
						r: arr
					};
				}
				return null;
			};
			
			this.update = function(data){
				this.clear();
				var i, row;
				for (i=0;i<data.length;i++){
					row = new Row(data[i]);
					this.add(row);
				}
				_lastUpdate =  Math.round(((new Date()).getTime()/1000));
			};
			
			this.resetFlags = function(){
				for (var id in _rows){
					_rows[id].resetFlags();
				}
			};
			
			/**
			 * Клонировать коллекцию.
			 * @method clone
			 * @return {Rows}
			 */
			this.clone = function(){
				var rows = new Rows(this.param);
				var data = [];
				for (var id in _rows){
					data[data.length] = _rows[id].clone().cell;
				}
				rows.update(data);
				return rows;
			};
			
			this.sync = function(rows){
				var source = rows.getArray();
				var srow, drow;
				for (var i=0;i<source.length;i++){
					srow = source[i];
					drow = this.getById(srow.id);
					if (YAHOO.lang.isNull(drow)){
						this.add(srow);
					}else{
						drow.sync(srow);
					}
				}
			};
		}
	};
	
	/**
	 * Коллекция коллекций записей в таблице. Идентификатором коллекции 
	 * является набор параметров
	 * @class RowsParam
	 */
	var RowsParam = function(){
		this.init();
	};
	
	RowsParam.prototype = {
		init: function(){
			var _rows = {};
			var _count = 0;
			
			this.count = function(){ return _count; };
			
			/**
			 * Удаление всех rows не соотвествующих param
			 * @method removeNonParam
			 */
			this.removeNonParam = function(param){
				var _newrows = {};
				var key = Rows.getParamHash(param);
				_count = 0;
				if (_rows[key]){
					_newrows[key] = _rows[key];
					_count = 1;
				}
				_rows = _newrows;
			};
			
			/**
			 * Удаление всех rows соответствующих param
			 * 
			 * @method removeByParam
			 */
			this.removeByParam = function(param){
				var key = Rows.getParamHash(param);
				
				if (_rows[key]){
					delete _rows[key];
					_count--;
				}
			};
			
			/**
			 * Получить коллекцию записей
			 * 
			 * @method getRows
			 * @param param 
			 * @param overparam
			 * @return
			 */
			this.getRows = function(param, overparam){
				var key = Rows.getParamHash(param);

				if (!_rows[key]){
					_rows[key] = new Rows(param, overparam);
					_count++;
				}
				return _rows[key];
			};
			
			this.getAllRows = function(){
				return _rows;
			};
			
			this.getLastUpdateRows = function(){
				var rows = null;
				var lastUpdateTime = 0;
				for(var nn in _rows){
					var lu = _rows[nn].lastUpdateTime(); 
					if (lu >= lastUpdateTime){
						lu = lastUpdateTime;
						rows = _rows[nn];
					}
				}
				return rows;
			};
			
			/**
			 * Найти запись среди коллекций записей используя выражение exp
			 *  
			 * @method findRow
			 * @param exp Выражение
			 * @return {Row}
			 */
			this.findRow = function(exp){
				for(var nn in _rows){
					var row = _rows[nn].find(exp);
					if (row){ return row; }
				}
				return null;
			};
			
			// Метод findFows для обеспечения совместимости предыдущей версии.
			// Вместо него необходимо использовать метод findRow
			this.findRows = function(exp){
				return this.findRow(exp); 
			};
			
			this.update = function(o){
				var di, i, rows;
				for (i=0;i<o.length;i++){
					di = o[i];
					rows = this.getRows(di['p']);
					rows.update(di['d']);
				}
			};
			
			this.clear = function(){
				for(var nn in _rows){
					_rows[nn].clear();
				}
			};
			
			this.applyChanges = function(){
				for(var nn in _rows){
					_rows[nn].applyChanges();
				}				
			};
			
			this.getPostData = function(){
				if (this.count() == 0){ this.getRows(); }
				var ret = [];
				for(var nn in _rows){
					var obj = _rows[nn].getPostData();
					if (!YAHOO.lang.isNull(obj)){
						ret[ret.length] = obj;
					}
				}
				if (ret.length > 0){ return ret; }
				return null;
			};
			
			this.resetFlags = function(){
				for(var nn in _rows){
					_rows[nn].resetFlags();
				}				
			};

		}
	};

	
	Brick.util.data.byid.DataSet = DataSet;
	Brick.util.data.byid.Table = Table;
	Brick.util.data.byid.Row = Row;	

};

// Реализация первых версий
(function(){
	Brick.namespace('util.Data');
	
	var Table = function(name){
		this.init(name);
	};
	Table.prototype = {
		init: function(name){
			this.data = [];
			this.name = name;
			this.lastUpdate = null;
			this.onUpdate = new YAHOO.util.CustomEvent("onUpdate"); 
		},
		isFill: function(){
			return !YAHOO.lang.isNull(this.lastUpdate);
		},
		setReloadFlag: function(){
			this.lastUpdate = null;
		},
		update: function(data, session){
			session = session || 1;
			this.lastUpdate = new Date();
			this.data = data;
			this.onUpdate.fire({data: this.data, session: session});
		},
		find: function(name, value){
			var i, di;
			for (i=0;i<this.data.length;i++){
				di = this.data[i];
				if (di[name] == value){
					return di;
				}
			}
			return null;
		},
		count: function(){
			return this.data.length;
		},
		filter: function(name, value){
			var ret = [], i, di;
			for (i=0;i<this.data.length;i++){
				di = this.data[i];
				if (di[name] == value){
					ret[ret.length] = di;
				}
			}
			return ret;
		}
	};
	Brick.util.Data.Table = Table;

	var loader = function(parent, moduleName, mmPrefix){
		this.init(parent, moduleName, mmPrefix);
	};
	loader.prototype = {
		init: function(parent, moduleName, mmPrefix){
			this.moduleName = moduleName;
			this.parent = parent;
			this.moduleManagerPrefix = mmPrefix;
		},
		add: function(tableName){
			var table = this.parent.get(tableName, true);
			table.lastUpdate = null;
		},
		getJSON: function(){
			var obj = [], send = false, table;
			for (var n in this.parent.ds){
				table = this.parent.ds[n];
				if (!table.isFill()){
					obj[obj.length] = table.name;
					send = true;
				}
			}
			if (!send){ return null; }

			var json = {
				'__data': {
					'session':  Math.round((new Date()).getTime()/1000), 
					'dictlist': obj
				}
			};
			if (this.moduleManagerPrefix){
				json['__data']['mmprefix'] = this.moduleManagerPrefix; 
			}
			return json;
		},
		request: function(){
			var json = this.getJSON();
			if (YAHOO.lang.isNull(json)){
				return;
			}
			Brick.util.Connection.sendCommand(this.moduleName, 'js_data', { json: json });
		}
	};
	
	var DataSet = function(moduleName, mmPrefix){
		this.init(moduleName, mmPrefix);
	};
	DataSet.prototype = {
		init: function(moduleName, mmPrefix){
			this.ds = {};
			if (moduleName){
				this.loader = new loader(this, moduleName, mmPrefix);
			}
			this.onComplete = new YAHOO.util.CustomEvent("onComplete"); 
		}, 
		update: function(name, data, session){
			session = session || 1;
			var table = this.get(name, true);
			table.update(data, session);
		},
		setReloadFlag: function(tables){
			tables = tables || [];
			var i, table;
			for (i=0;i<tables.length;i++){
				table = this.get(tables[i], true);
				table.setReloadFlag();
			}
		},
		get: function(name, createIfNotExists){
			if (!createIfNotExists){
				return this.ds[name];
			}
			var table;
			if (!this.ds[name]){
				table = new Table(name);
				this.ds[name] = table;
			}else{
				table = this.ds[name];
			}
			return table;
		},
		isFill: function(){
			for (var tn in this.ds){
				if (!this.ds[tn].isFill()){
					return false;
				}
			}
			return true;
		},
		complete: function(){
			this.onComplete.fire();
		}
	};
	
	Brick.util.Data.DataSet = DataSet;

	var TreeNode = function(id, pid, data){
		this.init(id, pid, data);
	};
	TreeNode.prototype = {
		init: function(id, pid, data){
			this.id = id;
			this.pid = pid;
			this.data = data;
			this.parent = null;
			this.child = {};
		},
		setChild: function(node){
			this.child[node['id']] = node;
			node['parent'] = this;
		}
	};
	
	var Tree = function(cfg){
		this.init(cfg);
	};
	Tree.prototype = {
		init: function(cfg){
			this.cfg = YAHOO.lang.merge({id: 'id', pid: 'pid', root: {}}, cfg || {});
			this.root = new TreeNode(0, -1, this.cfg['root']);
		},
		update: function(d){
			this.root = new TreeNode(0, -1, this.cfg['root']);
			this.build(this.root, d);
		}, 
		build: function(node, d){
			var c = this.cfg, cnode, di;
			for (var i=0;i<d.length;i++){
				di = d[i];
				if (node['id'] == di[c['pid']]){
					cnode = new TreeNode(di[c['id']], di[c['pid']], di);
					node.setChild(cnode);
				}
			}
		}
	};
	
	Brick.util.Data.Tree = Tree;

})();
