/**
 * Copyright 2004 Rob Rohan (robrohan@gmail.com) All rights reserved
 * This library my not be used without permission.
 *
 * General collection objects 
 * modeled after java.util
 *
 * Gives:
 *		List()
 *		Set()
 *		Map()
 */
var COLLECTIONS_VERSION = "0.1";

/**
 * Base object for the collections. Mostly List and Set
 */
function Collection(){
	this.avalues = new Array();
	this.asize = -1;
	
	/**
	 * gets the size of this map object
	 */
	this.size = function(){
		return this.asize + 1;
	}
	
	/**
     * Removes all of the elements from this list (optional operation).     
	 * void 
	 */
    this.clear = function(){
    	this.avalues = new Array();
    };
    
    /**
	 * Returns an array containing all of the elements in this list in proper sequence.
	 *  Object[]
	 */    
	this.toArray = function(){
		return this.avalues;
	};
	
	/**
     * Returns true if this list contains the specified element.
     *  boolean
     */
	this.contains = function(o){
		if(this.indexOf(o) >= 0){
			return true;
		}
		return false;
	};
	
	/**
	 * Returns the index in this list of the first occurrence of the specified 
	 * element, or -1 if this list does not contain this element.
	 * int
	 */
	this.indexOf = function(o){
		var l_x = 0;
		for(;l_x<this.size();l_x++){
			if(this.avalues[l_x] == o){
				return l_x;
			}
		}
		return -1;
	};
	
	/**
     * Returns true if this list contains no elements.
     * boolean
     */
	this.isEmpty = function(){
    	if(this.asize == -1)
    		return true;
    		
		return false;
    };
    
    /**
     * Returns the index in this list of the last occurrence of the specified element, 
     * or -1 if this list does not contain this element.
     * int
     */      
	this.lastIndexOf = function(o){
		var indx = -1;
		var l_x = 0;
		for(;l_x<this.size();l_x++){
			if(this.avalues[l_x] == o){
				indx = l_x;
			}
		}
		return indx;
	};
	
	/**
	 * Returns the element at the specified position in this list.
	 *  Object
	 */
 	this.get = function(index){
 		if(index > this.asize || index < 0)
			throw new Error("Collection::get : index is > size or < 0 can't get element");
		return this.avalues[index];
 	};
 	
 	/**
     * Removes the first occurrence in this list of the specified element (optional 
     * operation). 
     * boolean
     */
	this.remove = function(o){
		var idx = this.indexOf(o);
		this.removeByIndex(idx);
	};
	
	/**
	 * Removes the element at the specified position in this list (optional operation).
	 * Object
	 */
	this.removeByIndex = function(index){
		if(index > this.asize)
			throw new Error("Collection::remove : index is > size can't remove element");
		
		/* tarry = new Array();
		j=this.avalues.length;
		for(x=0;x<j;x++)
		{
			if(x != index)
			{
				tarry[tarry.length] = this.avalues[x];
			}
		}
		
		alert(tarry.length);
		
		for(i in tarry)
		{
			this.avalues[i] = tarry[i];
		}
		//this.avalues = tarry;
		this.asize = tarray.length; */
		
		//this is IE 5.5 and higher... write my own?
		try {
			this.avalues.splice(index, 1);
		} catch (ex) {
			//IE 5.0 is not smart enough to know what to do with this
			// just swallow it
		}
	};
	
	/**
	 * Replaces the element at the specified position in this list with the specified element 
	 * (optional operation).
	 *  Object
	 */
	this.set = function(index, element){
		if(index > this.asize)
			throw new Error("Collection::set : index is > size can't set element");
		this.avalues[index] = element;
	};
	
	/**
	 * Returns an array containing all of the elements in this list in proper sequence.
	 *  Object[]
	 */    
	this.toArray = function(){
		return this.avalues;
	};
}

/**
 * just a list of elements. More or
 * less an array with search functions
 *
 * An ordered collection (also known as a sequence). The user of this object
 * has precise control over where in the list each element is inserted. The user 
 * can access elements by their integer index (position in the list), and search for 
 * elements in the list.
 */
List.prototype = new Collection();
function List(){
	this.avalues = new Array();
	this.asize = -1;
	
	/** 
	 * Inserts the specified element at the specified position in 
	 * this list (optional operation).
	 */
	/* add = function(index,element)
	{
		if(index > this.asize)
			throw new Error(
				"List::add : Index is > size can't add element"
			);
		this.avalues[index] = element;
	}; */
	
	/**
	 * Appends the specified element to the end of this list (optional operation).
	 * boolean
	 */
	this.add = function(o){
		this.asize++;
		this.avalues[this.asize] = o;
	};
    
    /* Appends all of the elements in the specified collection to the end of this 
     * list, in the order that they are returned by the specified collection's iterator 
     * (optional operation).      
 	boolean addAll(Collection c)
 	*/
    
    /**
	 * Inserts all of the elements in the specified collection into this list at the 
	 * specified position (optional operation).
	 boolean addAll(int index, Collection c)
	 */
	      
 	/**
 	 * Compares the specified object with this list for equality.
	 * boolean
	 * equals(Object o)
	 */
	
	/**
	 * Returns a view of the portion of this list between the specified fromIndex, inclusive, and 
	 * toIndex, exclusive.
	 * List
	subList = function(fromIndex, toIndex)
	{
	
	}; */
	
	/**
	 * Turns this List (array backed) into a string list, or "thin list" using
	 * sep as the separator
	 */
	this.toThinList = function(sep){
		return this.avalues.join(sep)
	}
	
	/**
	 * creates a list from a "thin list" list using separator sep
	 */
	this.fromThinList = function(list,sep){
		this.avalues = list.split(sep);
		this.asize = this.avalues.length;
	}
	
	/**
	 *
	 */
	this.toString = function(){
		var z=0;
		var str = "[";
		var con = "";
		
		if(this.isEmpty()){
			con = "empty";
		}else{
    		con = this.toThinList(",");
    		/* for(;z<this.size();z++)
    		{
    			con += this.avalues[z] + ",";
    		}
    		con = con.substring(con,con.length-1);
    		*/
    	}
		str += con + "]"
		
		return str;
	};
	
	/**
	 * This is kind of weak and not very expandable, but its only used when
	 * this object is supposed to go to a web service and it requires the
	 * gateway
	 */
	this.toWSArray = function(varname,type){
		//xsi:type=\"" + type + "\" 
		var wsstr = "<" + varname;
		wsstr    += " xsi:type=\"soapenc:Array\" soapenc:arrayType=\"ns2:anyType["+this.size()+"]\">";
		
		for(v=0;v<this.size();v++)
		{
			wsstr += "<item xsi:type=\"ns2:string\">" + this.avalues[v].toString() + "</item>";
		}
		//soapxml	+= "<" + pname + " xsi:type=\"" + ptype + "\">" + pvalue + "</" + pname + "> ";
		//"<item xsi:type="ns2:string">item1</item>"
		wsstr    += "</" + varname + "> ";
		return wsstr;
	}
}

/**
 * Set keeps a unique list of elements
 */
Set.prototype = new Collection();
function Set(){
	this.avalues = new Array();
	this.asize = -1;
	
	/**
	 * Adds the specified element to this set if it is not already present 
	 * (optional operation).
	 * boolean 	add(Object o)
	 */
	this.add = function(o){
		if(!this.contains(o)){
			this.asize++;
			this.avalues[this.asize] = o;
		}	
	}

	/**
	 * Adds all of the elements in the specified collection to this set if 
	 * they're not already present (optional operation).
	 * boolean addAll(Collection c)
	 */
	//this.addAll = function(c){;}
	
	/**
	 * Returns true if this set contains all of the elements of the specified 
	 * collection.
	 * boolean containsAll(Collection c)
	 */
	//this.containsAll = function(c){;}

	/**
	 * Compares the specified object with this set for equality.
	 * boolean equals(Object o)
	 */
	//this.equals = function(o){;}
	
	/**
	 * Returns the hash code value for this set.
	 * int hashCode()
	 */
	//this.hashCode = function(){;}
	
	/**
	 * Returns an iterator over the elements in this set.
	 * Iterator iterator()
	 */
	//this.iterator = function(){;}

	/**
	 * Removes from this set all of its elements that are contained in the 
	 * specified collection (optional operation).
	 * boolean removeAll(Collection c)
	 */
	//this.removeAll = function(c){;}
	
	/**
	 * Retains only the elements in this set that are contained in the specified 
	 * collection (optional operation).
	 * boolean retainAll(Collection c)
	 */
	//this.retainAll = function(c){;}

	/**
	 * Returns an array containing all of the elements in this set; the runtime 
	 * type of the returned array is that of the specified array.
	 * Object[] toArray(Object[] a)
	 */
	//this.toArray = function(a){;}
}

/**
 * Simple Map object with a lame seaching
 * algorithm.
 */
function Map(){
	this.aname = new Array();
	this.avalue = new Array();
	this.asize = -1;
	
	/**
	 * adds a name value pair
	 */
	this.put = function(nkey,nvalue){
    	//if this value is already in here
    	//remove it
    	if(this.contains(nkey)){
    		//remove the old value
    		this.remove(nkey);
    	}
		//increase to add to the end
       	this.asize++;	
       	
    	this.aname[this.asize]  = nkey;
   		this.avalue[this.asize] = nvalue;
	}	
	
	/**
	 * gets all the keys from this map
	 * as an array
	 */
	this.getKeysAsArray = function(){
		return this.aname;
	}
	
	/**
	 * Gets a value from a key
	 */
	this.get = function(keyname){
		var m_x = 0;
		for(; m_x<this.size(); m_x++){
			if(this.aname[m_x] == keyname){
				return this.avalue[m_x];
			}
		}
		return null
	}	
	
	/**
     * Removes the first occurrence in this map of the specified key
     */
	this.remove = function(key){
		var idx = this.indexOf(key);
		this.removeByIndex(idx);
	};
	
	/**
	 * see if keyname is already in this map
	 * lame algorithm
	 */
	this.contains = function(keyname){
		var s = this.size();
		var m_i = 0;
		for(;m_i<s;m_i++){
			if(this.aname[m_i] == keyname){
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Returns the index the key or -1 if this map does not contain 
	 * the key
	 * int
	 */
	this.indexOf = function(key){
		var l_x = 0;
		for(;l_x<this.size();l_x++){
			if(this.aname[l_x] == key){
				return l_x;
			}
		}
		
		return -1;
	};
	
	/**
	 * Removes the element at the specified position in this list (optional operation).
	 * Object
	 */
	this.removeByIndex = function(index){
		if(index > this.asize)
			throw new Error(
				"Map remove index is > size can't remove key/value"
			);	
		//this is IE 5.5 and higher... write my own?
		try { 
			this.aname.splice(index, 1);
			this.avalue.splice(index, 1);
			this.asize--;
		} catch (ex) {
			//IE 5.0 will just have to suffer without a removeByIndex
			//function
		}
	};
	
	/**
	 * gets the size of this map object
	 */
	this.size = function(){
		return this.asize + 1;
	}
	
	/**
	 * Put this whole map (including sub maps)
	 * into a xml fragment to send to the web service
	 */
	this.toWSStruct = function(varname,id,depth){
		var frag = "<" + varname + " href=\"#id0\" soapenc:root=\"0\" xsi:type=\"asoap:Map\">";
		frag += this.__recurse_map("multiRef",id,depth);
		frag += "</" + varname + ">";
		return frag;
	}
	
	/**
	 * This is kind of weak and not very expandable
	 * but it works :)
	 * this goes over each element and creates a proper soap map fragment
	 * for this map
	 */
	this.__recurse_map = function(varname,id,depth){
		var nesteditems = new Array();
		depth += 1;
		var wsstr = "<" + varname + " ";
		if(id + depth == 0){
			wsstr += "id=\"id" + id + "\" soapenc:root=\"0\" xsi:type=\"asoap:Map\">";
		}else{
			wsstr += "id=\"id" + id + "" + depth + "\" soapenc:root=\"0\" xsi:type=\"asoap:Map\">";
		}
		
		for(var z=0;z<this.aname.length;z++){
			wsstr += "<item>";
			wsstr += "<key xsi:type=\"ns2:string\">" + this.aname[z].toString() + "</key>";
			if(this.avalue[z] instanceof Map){
				nlen = nesteditems.length;
				nesteditems[nlen] = this.avalue[z];
				wsstr += "<value href=\"#id" + nlen + "" + (depth+1) + "\"/>";
			}else{
				wsstr += "<value xsi:type=\"ns2:string\">" + this.avalue[z].toString() + "</value>";
			}
			wsstr += "</item>";
		}
		wsstr += "</" + varname + "> ";
		
		for(var z=0;z<nesteditems.length;z++){
			wsstr += nesteditems[z].__recurse_map(varname,z,depth);
		}
		return wsstr; 
	}
	
	/**
	 * the ol'toString
	 */
	this.toString = function(){
		var str = "[";
		
		if(this.size() > 0){
			var m_x=0;
        	for(;m_x<this.size();m_x++){
        		str += this.aname[m_x] + "=" + this.avalue[m_x] + ",";
        	}
        	//remove that last comma
        	str = str.substring(0,str.length -1);
        }else{
        	str += "empty"
        }
    	str += "]";
    	
    	return str;
	}
}
