/**
 * ***************************************************************
 * JADE - Java Agent DEvelopment Framework is a framework to develop
 * multi-agent systems in compliance with the FIPA specifications.
 * Copyright (C) 2000 CSELT S.p.A.
 * 
 * GNU Lesser General Public License
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation,
 * version 2.1 of the License.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 * **************************************************************
 */
package jade.content.abs;

import jade.core.CaseInsensitiveString;
import jade.util.leap.ArrayList;
import jade.util.leap.HashMap;
import jade.util.leap.Iterator;

/**
 * Base class for all non-primitive abstract descriptor classes.
 * This class is not intended to be used by programmers.
 * @author Federico Bergenti - Universita` di Parma
 * @author Giovanni Caire - TILAB
 */
public class AbsObjectImpl implements AbsObject {
	private HashMap elements = new HashMap();
	/** This list keeps the keys in the same order as they were added **/
	private ArrayList orderedKeys = new ArrayList();
	private String    typeName = null;
	/** true if this object is changed and its hash must be recomputed **/
	private boolean changed = true;
	private int hashCode = 0;

	/**
	 * Construct an Abstract descriptor to hold an object of
	 * the proper type.
	 * @param typeName The name of the type of the object held by this
	 * abstract descriptor.
	 */
	protected AbsObjectImpl(String typeName) {
		this.typeName = typeName;
	}

	/**
	 * @return The name of the type of the object held by this
	 * abstract descriptor.
	 * @see AbsObject#getTypeName()
	 */
	public String getTypeName() {
		return typeName;
	} 

	void setTypeName(String typeName) {
		this.typeName = typeName;
	}
	
	/**
	 * Sets an attribute of the object held by this
	 * abstract descriptor.
	 * @param name The name of the attribute to be set.
	 * @param value The new value of the attribute. If <code>value</code>
	 * is null the current mapping with <code>name</code> (if any) is
	 * removed.
	 */
	protected void set(String name, AbsObject value) {
		CaseInsensitiveString ciName = new CaseInsensitiveString(name);
		if (value == null) {
			orderedKeys.remove(name);
			elements.remove(ciName);
		}
		else {
			if (!orderedKeys.contains(name)) {
				orderedKeys.add(name);
			}
			elements.put(ciName, value);
		}
		changed = true;
	} 

	/**
	 * Gets the value of an attribute of the object held by this
	 * abstract descriptor.
	 * @param name The name of the attribute.
	 * @return value The value of the attribute.
	 * @see AbsObject#getAbsObject(String)
	 */
	public AbsObject getAbsObject(String name) {
		return (AbsObject) elements.get(new CaseInsensitiveString(name));
	} 

	/**
	 * @return the name of all attributes.
	 * @see AbsObject#getNames()
	 */
	public String[] getNames() {
		String[] names = new String[orderedKeys.size()];
		int j=0;
		for (Iterator i=orderedKeys.iterator(); i.hasNext(); )
			names[j++] = (String)i.next();
		return names;
	} 

	/**
	 * Tests if the object is grounded, i.e., if no one of its attributes 
	 * is associated with a variable
	 * @return <code>true</code> if the object is grounded.
	 * @see AbsObject#isGrounded()
	 */
	public boolean isGrounded() {
		for (Iterator i = elements.values().iterator(); i.hasNext(); ) {
			AbsObject abs = (AbsObject) i.next();
			if (!abs.isGrounded()) {
				return false;
			}
		}
		return true;
	} 

	/**
	 * Gets the number of attributes.
	 * @return the number of attributes.
	 * @see AbsObject#getCount()
	 */
	public int getCount() {
		return elements.size();
	} 

	/**
	 * This method is here just for debugging. Notice that it is highly innefficient.
	 * The method StringCodec.encode() should be used instead.
	 * @see StringCodec.encode(AbsContentElement content)
	 */
	public String toString() {
		StringBuffer sb = new StringBuffer("(");
		sb.append(getTypeName());

		String[] names = getNames();

		for (int i = 0; i < names.length; i++) {
			sb.append(" :");
			sb.append(names[i]);
			sb.append(" ");
			sb.append(getAbsObject(names[i]));
		}
		sb.append(")");

		return sb.toString();
	}

	//ADDED BY SANDER FAAS:
	/**
	 * Returns true if the attribute is equal to
	 * this abstract descriptor, based on the contents
	 * of both descriptors.
	 */
	public boolean equals(Object obj) {
		if (obj instanceof AbsObjectImpl) {
			AbsObjectImpl abs = (AbsObjectImpl) obj;
			if (abs.getClass().equals(getClass()) && abs.getTypeName().equals(typeName)) {
				return f(abs) == f(this);
			}
		}
		return false;
	}

	/**
	 * Returns an integer hashcode calculated from the 
	 * contents of this abstract descriptor
	 */
	public int hashCode() {
		// if this object is changed, then recompute its hash, otherwise use the previous value
		if (changed) {
			hashCode = f(this);
			changed = false;
		}
		return hashCode;
	}

	/**
	 * Calculates the hashcode according to a formula based on the 
	 * slot names and values taken in an lexicographical order
	 */
	private final int f(AbsObjectImpl o, int x) {
		String slotNames[] = o.getNames();
		sort(slotNames);
		int[] v = new int[2*slotNames.length + 1];
		int j=0;
		for(int i = 0; i < slotNames.length; i++)
		{
			v[j++] = slotNames[i].hashCode();
			v[j++] = o.getAbsObject(slotNames[i]).hashCode();
		}
		v[j++] = o.getTypeName().hashCode();

		int sum = 0;
		int counter = 0;
		for (int i=0; i<v.length; i++ ) {
			sum += v[i] * x^counter;
			counter++;
		}
		return sum;
	}

	private final int f(AbsObjectImpl o) {
		return f(o, 2);
	} 

	private final void sort(String[] strs) {
		for (int i = 1; i < strs.length; ++i) {
			for (int j = i; j > 0 && (strs[j-1].compareTo(strs[j]) > 0); --j) {
				swap(strs, j, j-1);
			}
		}
	}

	private final void swap(String[] strs, int x, int y) {
		String t = strs[x];
		strs[x] = strs[y];
		strs[y] = t;
	}
	
    public int getAbsType() {
    	return UNKNOWN;
    }
}

