var VnObject = require('./object');
var simpleEquals = require('./value').simpleEquals;

var Klass = new Class();
module.exports = Klass;

var Status = {
	 CLEAN    : 1
	,LOADING  : 2
	,READY    : 3
	,ERROR    : 4
};

var SortWay = {
	 ASC   : 1
	,DESC  : 2 
};

Klass.extend({
	 Status
	,SortWay
});

Klass.implement({
	Extends: VnObject
	,Tag: 'vn-model'
	,Properties: {
		/**
		 * The model internal data.
		 */
		data: {
			type: Array
			,get() {
				return this._data;
			}
			,set(x) {
				this._setData(x);
			}
		},
		/**
		 * The number of rows in the model.
		 */
		numRows: {
			type: Number
			,get() {
				if (this._data)
					return this._data.length;

				return 0;
			}
		},
		/**
		 * The current status of the model.
		 */
		status: {
			type: Number
			,get() {
				return this._status;
			}
		},
		/**
		 * Checks if the model data is ready.
		 */
		ready: {
			type: Boolean
			,get() {
				return this._status === Status.READY;
			}
		}
	}

	,_data: null
	,_status: Status.CLEAN

	,_requestedSortName: null
	,_sortColumn: null
	,_sortWay: null

	,_requestedIndexes: {}
	,_indexes: []

	//+++++++++++++++++++++++++++++ Data hadling
	
	,_setData(data) {
		this._data = data;
		this._refreshRowIndexes(0);

		if (this._requestedSortName != null)
			this._realSort(this._requestedSortName, this._sortWay);
		for (column in this._requestedIndexes)
			this._buildIndex(column);

		this._setStatus(Status.READY);
	}

	/**
	 * Checks if the row exists.
	 *
	 * @param {Integer} rowIndex The row index
	 * @return {Boolean} %true if row exists, %false otherwise
	 */
	,checkRowExists(rowIndex) {
		return this._data != null
			&& rowIndex >= 0
			&& rowIndex < this._data.length;
	}

	/**
	 * Gets a value from the model.
	 *
	 * @param {Number} rowIndex The row index
	 * @param {Number} columnName The column name
	 * @return {*} The value
	 */
	,get(rowIndex, columnName) {
		if (!this.checkRowExists(rowIndex))
			return undefined;
		
		return this._data[rowIndex][columnName];
	}

	/**
	 * Updates a value on the model.
	 *
	 * @param {Number} rowIndex The row index
	 * @param {Number} columnName The column name
	 * @param {*} value The new value
	 */
	,set(rowIndex, columnName, value) {
		if (!this.checkRowExists(rowIndex))
			return;

		this.emit('row-updated-before', rowIndex);
		this._data[rowIndex][columnName] = value;
		this.emit('row-updated', rowIndex, [columnName]);
	}

	/**
	 * Gets a row as an object using the column index.
	 *
	 * @param {Number} rowIndex The row index
	 * @return {Object} The row as an object
	 */
	,getObject(rowIndex) {
		return this.checkRowExists(rowIndex) ?
			this._data[rowIndex] : null;
	}
	
	,_setStatus(status) {
		this._status = status;
		this.emit('status-changed', status);
		this.emit('status-changed-after', status);
	}
	
	/**
	* Inserts a new row on the model.
	*
	* @return The index of the inserted row
	*/
   ,insertRow(newRow) {
		var rowIndex = this._data.push(newRow) - 1;
		newRow.index = rowIndex;
		this.emit('row-inserted', rowIndex);
		return rowIndex;
   }

	/**
	 * Deletes a row from the model. 
	 *
	 * @param {Number} rowIndex The row index
	 */
	,deleteRow(rowIndex) {
		if (!this.checkRowExists(rowIndex))
			return;

		this.emit('row-deleted-before', rowIndex);
		this._data.splice(rowIndex, 1);
		this.emit('row-deleted', rowIndex);
		this._refreshRowIndexes(rowIndex);
	}

	//+++++++++++++++++++++++++++++ Sorting

	/**
	 * Orders the model by the specified column name.
	 *
	 * @param {String} columnName The column name
	 * @param {SortWay} way The sort way
	 */
	,sort(columnName, way) {
		this._requestedSortName = columnName;
		this._sort(columnName, way);
	}
	 
	,_sort(columnName, way) {
		var status = this._status;
		this._setStatus(Status.LOADING);
		this._realSort(columnName, way);
		this._setStatus(status);
	}
	
	,_realSort(columnName, way) {
		if (!this._data)
			return;

		if (columnName !== this._sortColumn) {
			if (way === SortWay.DESC)
				var sortFunction = this.sortFunctionDesc;
			else
				var sortFunction = this.sortFunctionAsc;
	
			this._data.sort(sortFunction.bind(this, columnName));
		} else if (way !== this._sortWay)
			this._data.reverse();

		this._sortColumn = columnName;
		this._sortWay = way;

		this._refreshRowIndexes(0);
	}

	,_refreshRowIndexes(start) {
		var data = this._data;

		for (var i = start; i < data.length; i++)
			data[i].index = i;
	}
	
	/*
	 * Function used to sort the model ascending.
	 */
	,sortFunctionAsc(column, a, b) {
		if (a[column] < b[column])
			return -1;
		else if (a[column] > b[column])
			return 1;
			
		return 0;
	}
	
	/*
	 * Function used to sort the model descending.
	 */
	,sortFunctionDesc(column, a, b) {
		if (a[column] > b[column])
			return -1;
		else if (a[column] < b[column])
			return 1;
			
		return 0;
	}
	
	//+++++++++++++++++++++++++++++ Searching

	/**
	 * Builds an internal hash index for the specified column, this speeds
	 * significantly searches on that column, specially when model has a lot of
	 * rows.
	 *
	 * FIXME: Not fully implemented.
	 *
	 * @param {String} column The column name
	 */
	,indexColumn(column) {
		this._requestedIndexes[column] = true;

		if (this._status === Status.READY)
			this._buildIndex(column);
	}

	,getHashValue(value) {
		if (value instanceof Date)
			return value.getTime();
		else
			return value;
	}

	,_buildIndex(columnName) {
		if (this.columnMap[columnName] === undefined)
			return;

		var data = this._data;
		var values = {};
		var nulls = [];

		for (var i = 0; i < data.length; i++) {
			var value = data[i][columnName];

			if (value == null) {
				nulls.push(data[i]);
				continue;
			}

			index[this.getHashValue(value)] = data[i];
		}

		this._indexes[columnName] = {
			values: values,
			index: index
		};
	}

	/**
	 * Searchs a value on the model and returns the row index of the first
	 * ocurrence.
	 *
	 * @param {Number} columnName The column name
	 * @param {Object} value The value to search
	 * @return {Number} The column index
	 */
	,search(columnName, value) {
		var data = this._data;

		if (data == null)
			return -1;

		// Searchs the value using an internal index.

		var index = this._indexes[columnName];
		
		if (index) {
			if (value == null) {
				if (index.nulls[0] !== undefined)
					return index.nulls[0].index;
			} else {
				var row = index.values[this.getHashValue(value)];

				if (rowIndex !== undefined)
					return row.index;
			}

			return -1;
		}

		// Searchs the value using a loop.

		for (var i = 0; i < data.length; i++)
		if (simpleEquals(data[i][columnName], value))
			return i;

		return -1;
	}
	
	//+++++++++++++++++++++++++++++ Virtual

	,refresh() {
		this._setStatus(Status.READY);
	}
});