package prolog.implementation;

import java.io.*;
import java.util.*;
import prolog.util.*;
import prolog.model.*;

/**
 * <p>The base class of a relation between some atoms, like a row
 * in a database. This interface is used to store and get facts but
 * also to store and get parameters e.g. in a statement</p>
 * <p>prolog: myFact(joe,dan).</p>
 * <p>in this example, the relation would contain the atoms "joe" and "dan"</p>
 */
public class Relation
	implements
		IRelation,
		Cloneable
{
	/**
	 * the list where all atoms of the relation are stored
	 */
	protected List values = new ArrayList(0);

	/**
	 * costructor
	 */
	public Relation()
	{
	}

	/**
	 * copy constructor
	 * @param r - relation to copy
	 */
	public Relation( IRelation r )
	{
		Iterator i = r.getValues();
		while( i.hasNext() )
			add( i.next() );
	}

	/**
	 * adds a atom to this relation
	 * @param value - the atom to be added
	 */
	public void add( Object value )
	{
		if( value == null )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: Relation.add failed! " +
				" The atom added can not be null!" );

		if( ! ( value instanceof IAtom ) )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: Relation.add failed! " +
				" Can not add objects that are not a IAtom!" );

		values.add( value );
	}

	/**
	 * returns the number of atoms contained in this relation
	 * @return number of atoms
	 */
	public int getValueCount()
	{
		return values.size();
	}

	/**
	 * builds a indexmap. a indexmap contains the atom
	 * as key and the index as integer. this function is only
	 * used for parameter matching.
	 * @return the created indexmap
	 */
	public Map buildMap()
	{
		Map back = new HashMap();
		Iterator i=this.getValues();
		int counter = 0;
		while( i.hasNext() )
			back.put( i.next(), new Integer( counter++ ) );
		return back;
	}

	/**
	 * returns the atom at the specified index
	 * @param index of the atom to be returned
	 * @return atom at the specified index
	 */
	public IAtom get( int index )
	{
		if( index < 0 || index >= values.size() )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: Relation.get( index ) failed! " +
				" Invalid index specified! " +
				"index=\"" + index + "\" " +
				"length=" + values.size() + "\"" );

		return (IAtom)values.get( index );
	}

	/**
	 * returns an iterator over all atoms contained in this relation
	 * @return iterator over the values in this relation
	 */
	public Iterator getValues()
	{
		return values.iterator();
	}

	public boolean equals( Object o )
	{
		boolean back = true;

		IRelation r = (IRelation) o;
		if( getValueCount() == r.getValueCount() )
		{
			Iterator i1 = r.getValues();
			Iterator i2 = getValues();
			while( i1.hasNext() )
				if( ! i1.next().equals( i2.next() ) )
				{
					back = false;
					break;
				}
		}
		else
			back = false;

		return back;
	}

	/**
	 * @return a string representation of this object
	 */
	public String toString()
	{
		return StringBuilder.build( getValues(), "," );
	}

	/**
	 * returns a string representation of this object with decoded symbols
	 * @param table - the symboltable used to decode
	 * @return the string representation
	 */
	public String toString( ISymbolTable table )
	{
		if( table != null )
			return StringBuilder.buildSymbolDecoded( getValues(), ",", table );
		else
			return toString();
	}

	/**
	 * returns a string representation of this object with decoded symbols
	 * in prolog syntax used for query results e.g. X=a, Y=b
	 * @param table - the symboltable used to decode
	 * @param parameterNames - the names of the parameters in this relation
	 * @return the string representation
	 */
	public String toString( int[] parameterNames, ISymbolTable table )
	{
		if( values.size() != parameterNames.length )
			throw new RuntimeException(
				"Runtime error: Relation.toString failed! " +
				"value.size()=\"" + values.size() + "\" " +
				"names.length=\"" + parameterNames.length + "\"" );

		StringBuffer back = new StringBuffer();

		int count = 0;
		Iterator i = getValues();
		while( i.hasNext() )
		{
			Object value = i.next();
			back.append( table.decode( parameterNames[ count++ ] ) );
			back.append( '=' );
			back.append( table.decode( ((IAtom)value) ) );
			if( i.hasNext() )
				back.append( ", " );
		}
		return back.toString();
	}

	/**
	 * generates xml describing this relation
	 * @param out the writer where o append the xml
	 * @param table the symboltable if the xml should be decoded
	 * @throws IOException if writing xml fails
	 */
	public void genXml( Writer out, ISymbolTable table )
	   throws IOException
	{
		out.write( "<relation>" );
		Iterator i = getValues();
		while( i.hasNext() )
		{
			out.write( "<param>" );
			if( table != null )
				out.write( table.decode( ((IAtom)i.next()) ) );
			else
				out.write( i.next().toString() );
			out.write( "</param>" );
		}
		out.write( "</relation>" );
	}
}
