/**
 * Part of JMF Library core
 * This file contains data structure class named Ordered list
 * It can be used as an array, hash and bidirectional non circular list 
 * 
 * SVN:$Id: data.orderedlist.js 6163 2008-08-14 14:36:31Z mjezierski $
 * @version 0.6b
 * changelog:
 * 0.6
 * Added getLike method
 * 0.5
 * added getOrderedIdArray method 
 * 0.4
 * added reverse iteration
 * 0.3
 * added getOrderedArray method
 */

JMF.registerLib('JMF.Data.OrderedList','$Id: data.orderedlist.js 6163 2008-08-14 14:36:31Z mjezierski $');

JMF.Data = {};

/**
 * Ordered list with element Id reference
 * @constructor
 * @member JMF.Data.OrderedList
 */
JMF.Data.OrderedList = function() {
	//first element reference
	this.first 	= null;
	//last ele.ment reference
	this.last 	= null;
	//current element reference
	this.curr 	= null;
	//iteration element reference
	this.iterElem = null;
	//element count
	this.count 	= 0;
	//reference shortcut for getById methods
	this.shortRef = {};
	//last direction of iteration
	this.lastIterDir = 0;
	//shortcut position reference
	this.posRef = [];
	//indicates wheter position reference needs to be recalculated
	this.needReindexPos = false;
};

/**
 * Returns all elements that have id like searchId
 * @member JMF.Data.OrderedList
 * @return {Array} Array of found elements
 */
JMF.Data.OrderedList.prototype.getLike = function(searchId) {
   if(this.needReindexPos) {
      this.reindexPos();
   }
   var retArr = [];
   for(var i=0;i<this.posRef.length;i++) {
      if(this.posRef[i].id.indexOf(searchId) !== -1) {
      	retArr.push(this.posRef[i]);
      }
   }
   return retArr;
};

/**
 * Returns ordered array with elements' ids
 * @member JMF.Data.OrderedList
 * @return {Array} Array with elements' ids
 */
JMF.Data.OrderedList.prototype.getOrderedIdArray = function() {
	if(this.needReindexPos) {
      this.reindexPos();
   }
   var retArr = [];
   for(var i=0;i<this.posRef.length;i++) {
      retArr.push(this.posRef[i].id);
   }
   return retArr;
};

/**
 * @member JMF.Data.OrderedList
 * @return ordered array with elements references
 */
JMF.Data.OrderedList.prototype.getOrderedArray = function() {
	if(this.needReindexPos) {
		this.reindexPos();
	}
	return this.posRef;
};

/**
 * Sorts list by reference list
 * NOTE: sorted list must be subset of reference list, otherwise elements that are not present in reference list will be ignored
 * @member JMF.Data.OrderedList
 * @return sorted list
 */
JMF.Data.OrderedList.prototype.sortByReferenceList = function(refList) {
	var tempList = new JMF.Data.OrderedList();
	refList.iResetPos();
	var cSel;
	while((cSel = refList.iterate())) {
		if(this.getById(cSel.id)) {
			tempList.add(cSel.id,null,cSel.data);
		}
	}

	return tempList;
};

/**
 *	Make constructor accessible inside class 
 */
JMF.Data.OrderedList.prototype.OrderedList = JMF.Data.OrderedList;

/**
 * Gets list element by id
 * @member JMF.Data.OrderedList
 * @param {Object} id
 * @return {ListElem} or null
 */
JMF.Data.OrderedList.prototype.getById = function(id) {
	return this.shortRef[id];
};

/**
 * Internal function, reindexes position reference array on demand
 * @member JMF.Data.OrderedList
 */
JMF.Data.OrderedList.prototype.reindexPos = function() {
	this.posRef = [];
	if(!this.count) {
		return;
	}

	var elem = this.first;
	do {
		this.posRef.push(elem);
	} while((elem = elem.next));
	this.needReindexPos = false;
};

/**
 * Gets list element by position
 * @member JMF.Data.OrderedList
 * @param {Object} pos element position
 * @return {ListElement} or null
 */
JMF.Data.OrderedList.prototype.getByPos = function(pos) {
	if(isNaN(parseInt(pos,10))) {
		return null;
	}
	
	if(!this.count || pos>this.count || pos < 0) {
		return null;	
	}
	
	if(this.needReindexPos) {
		this.reindexPos();
	}
	
	return this.posRef[pos];
};

/**
 * Updates element id
 * @member JMF.Data.OrderedList
 * @param {String} id current element id
 * @param {String} newId new element id
 * @return {Boolean} false on error, otherwise true
 */
JMF.Data.OrderedList.prototype.updateId = function(id,newId) {
	if(this.shortRef[newId]) {
		return false;
	}
	
	this.shortRef[newId] = this.shortRef[id];
	if(this.shortRef[newId]) {
		this.shortRef[newId].id = newId;
	}
	this.shortRef[id] = null;
	return true;
};

/**
 * Adds element to the end of list 
 * Element should contain id property
 * @member JMF.Data.OrderedList
 * @param {Object} data element to add
 * @return {Boolean} false on error
 */
JMF.Data.OrderedList.prototype.push = function(data) {
	return this.add(data.id,null,data);
};

/**
 * Adds element to list
 * @member JMF.Data.OrderedList
 * @param {Object} id element id
 * @param {Object} nextElementId id of next element, if null, new element will be added to end of list
 * @param {Object} data element data
 * @return {Boolean} false on error
 */
JMF.Data.OrderedList.prototype.add = function(id,nextElementId,data) {
	var nElem;
	this.needReindexPos = true;
	this.count++;
	//list is empty
	if(!this.first) {
		this.first = new JMF.Data.OrderedList.ListElem(id,data);
		this.last = this.first;
		this.curr = this.first;
		this.shortRef[id] = this.first;
		
		return true;	
	}
	
	//put element at the end of list
	if(!nextElementId) {
		nElem = this.last.insertAfter(id,data);
		this.last = nElem;
		this.shortRef[id] = nElem;
		return true;
	}
	
	//put element at specific point
	var currElem = this.shortRef[nextElementId];
	//oops error
	if(!currElem) {
		this.count--;
		return false;
	}
	nElem = currElem.insertBefore(id,data);
	if(!nElem.prev) {
		this.first = nElem;
	}
	this.shortRef[id] = nElem;
	return true; 
};

/**
 * Moves element to specific position
 * @member JMF.Data.OrderedList
 * @param {String} id element id
 * @param {String} siblingElementId next element id, if null, element will be moved to the end of list
 * @param {Boolean} insertBefore if true element is moved before sibling element 
 * @return {Boolean} false on error
 */
JMF.Data.OrderedList.prototype.move = function(id,siblingElId,insertBefore) {
   var i = this;
   var cnt = 0;
   var assertString = [];

	//if list is empty return false
	if(this.count < 2 || !id) {
		return false;
	}
	
	//if element to move does not exists return false
	var mElem = this.shortRef[id];
	if(!mElem) {
		return false;
	} 

   var sElem = siblingElId !== null?this.shortRef[siblingElId]:null;
   //if sibling id does not correspond to element return false 
   if(siblingElId !== null && !sElem) {
   	return false;
   }

   //if sibling id is null and element has to be moved to end of list or to the beginnig and already is there return
   if(null === siblingElId && ((this.last === mElem && !insertBefore)||(this.first === mElem && insertBefore))) {
   	return true;
   }
   
   if(id === siblingElId) {
   	return true;
   }
   
	//mark that position reference needs reindex 
	this.needReindexPos = true;

   if(mElem.prev) {
      mElem.prev.next = mElem.next;
      if(!mElem.next) {
      	this.last = mElem.prev;
      }
   }
   
   if(mElem.next) {
      mElem.next.prev = mElem.prev;
      if(!mElem.prev) {
      	this.first = mElem.next; 
      }
   }
   
   if(sElem) {
   	if(insertBefore) {
   		mElem.next = sElem;
   		mElem.prev = sElem.prev;
   		if(mElem.prev) {
   			mElem.prev.next = mElem;
   		} else {
   			this.first = mElem;
   		}
   		sElem.prev = mElem;
   	} else {
   		mElem.prev = sElem;
   		mElem.next = sElem.next;
   		if(mElem.next) {
   			mElem.next.prev = mElem;
   		} else {
   			this.last = mElem;
   		}
   		sElem.next = mElem;
   	}
   	return true;
   } else {
   	if(insertBefore) {
   		this.first.prev = mElem;
   		mElem.prev = null;
   		mElem.next = this.first;
   		this.first = mElem;
   	} else {
   		this.last.next = mElem;
   		mElem.prev = this.last;
   		mElem.next = null;
   		this.last = mElem;
   	}
   	return true;
   }
   return false;
};

/**
 * Sets cursor position for next/prev method
 * @member JMF.Data.OrderedList
 * @param {String} id Object id, if left null cursor is set to beginning of the list
 * @return {Boolean} false on error 
 */
JMF.Data.OrderedList.prototype.setPos = function(id) {
	if(!id) {
		this.currElem = this.first;
		return true;
	} 
	
	if(!this.shortRef[id]){
		return false;
	}
	
	this.currElem = this.shortRef[id];
};

/**
 * Gets next element from list and moves current element cursor
 * @member JMF.Data.OrderedList
 * @return {ListElem} or null if there is no more elements
 */
JMF.Data.OrderedList.prototype.next = function() {
	if(!this.count) {
		return false;
	}
		
	if(!this.currElem.next) {
		return false;
	}	
	this.currElem = this.currElem.next;	
	return this.currElem;	
};

/**
 * Gets previous element from list and moves current element cursor
 * @member JMF.Data.OrderedList
 * @return {ListElem} or null if there is no previous element
 */
JMF.Data.OrderedList.prototype.prev = function() {
	if(!this.count) {
		return false;
	} 
	
	if(!this.currElem.prev) {
		return false;
	}
	this.currElem = this.currElem.prev;
	
	return this.currElem;
};

/**
 * Removes given element from list
 * @member JMF.Data.OrderedList
 * @param {String} id element id
 * @return false on error, removed elem on success
 */
JMF.Data.OrderedList.prototype.remove = function(id) {
	this.needReindexPos = true;
	var elem = this.shortRef[id];
	
	if(!elem) {
		return false;
	}
	
	if(elem == this.first) {
		this.first = elem.next;
	}
	
	if(elem == this.last) {
		this.last = elem.prev;
	}
	
	elem.remove();
	delete this.shortRef[id];
	this.count--;
	return elem;
};

/**
 * Returns current iteration element and moves iteration cursor to next element
 * @member JMF.Data.OrderedList
 * @return {ListElem} or null on end of the list
 */
JMF.Data.OrderedList.prototype.iterate = function() {
	if(!this.count || !this.iterElem) {
		return false;
	}
	var elem = this.iterElem;
	this.iterElem = this.iterElem.next;

	return elem; 
};

/**
 * Returns current iteration element and moves iteration cursor to previous element
 * @member JMF.Data.OrderedList
 * @return {ListElem} or null on end of the list
 */
JMF.Data.OrderedList.prototype.rIterate = function() {
   if(!this.count || !this.iterElem) {
      return false;
   }
   var elem = this.iterElem;
   this.iterElem = this.iterElem.prev;

   return elem; 
};

/**
 * Resets iterator position
 * @member JMF.Data.OrderedList
 * @param {Boolean} reverse If true, iterator will be reset to reverse iteration
 */
JMF.Data.OrderedList.prototype.iResetPos = function(reverse) {
	if(reverse) {
	  this.iterElem = this.last;
	} else {
	  this.iterElem = this.first;
	}
};

/**
 * Removea all elements from list
 * @member JMF.Data.OrderedList
 */
JMF.Data.OrderedList.prototype.clear = function() {
	this.needReindexPos = true;

	var elem = this.first;
	var tmpElem = null;

	while(elem) {
		tmpElem = elem;
		elem = elem.next;
		tmpElem.remove();
	}
	this.OrderedList();
};

/**
 * List element class
 * @constructor
 * @member JMF.Data.OrderedList.ListElem
 * @param {String} id Unique id 
 * @param {String} data Element Data 
 */
JMF.Data.OrderedList.ListElem = function(id,data) {
	this.prev = null;
	this.next = null;
	this.data = data;
	this.id = id;
};

/**
 * Inserts element before 
 * @member JMF.Data.OrderedList.ListElem
 * @param {String} id Unique id, optional if data is an instance of ListElem 
 * @param {Object} data Element data or ListElem object
 */
JMF.Data.OrderedList.ListElem.prototype.insertBefore = function(id,data) {
	var newElem = null;
	if(data instanceof JMF.Data.OrderedList.ListElem) {
		newElem = data;	
	}	else {	
		newElem = new JMF.Data.OrderedList.ListElem(id,data);
	}
	
	if(this.prev) {
		this.prev.next = newElem;
	}
	newElem.next = this;
	newElem.prev = this.prev;
	this.prev = newElem;
	
	return newElem;
};

/**
 * Inserts element after 
 * @member JMF.Data.OrderedList.ListElem
 * @param {String} id Unique id, optional if data is an instance of ListElem 
 * @param {Object} data Element data or ListElem object
 */
JMF.Data.OrderedList.ListElem.prototype.insertAfter = function(id,data) {
	var newElem = null;
	if(data instanceof JMF.Data.OrderedList.ListElem) {
		newElem = data;	
	}	else {	
		newElem = new JMF.Data.OrderedList.ListElem(id,data);
	}

	if(this.next) {
		this.next.prev = newElem;
	}
	newElem.next = this.next;
	newElem.prev = this;
	this.next = newElem;
	
	return newElem;
};

/**
 * Removes current element from list chain
 * @member JMF.Data.OrderedList.ListElem 
 */
JMF.Data.OrderedList.ListElem.prototype.remove = function() {
	if(this.next) {
		this.next.prev = this.prev;
	}
	
	if(this.prev) {
		this.prev.next = this.next;
	}
	
	this.id = null;
	this.data = null;
	this.next = null;
	this.prev = null;
};