package prolog.implementation;

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

/**
 * FactList is the base implementation for a collection of relations.
 * It can be compared with a database table containing rows of relations.
 * The name of the factlist would be the tablename. That's why
 * factlist extends IFact. So it inherits a name. The relation of the
 * extended IFact are also used for factlists returned by queries.
 * In this relation, parameters of the statements are stored.</p>
 * <p>prolog:</p>
 * <p>myFact(joe,dan).</p>
 * <p>myFact(joe,dan2).</p>
 * <p>In this example, the two facts "myFact" are stored in
 * a factlist.</p>
 * <p>interpreter internal:</p>
 * <p>myFact(joe,dan).</p>
 * <p>myFact(joe,dan2).</p>
 * <p>myStatement(X,Y):-myFact(X,Y).</p>
 * <p>In this example, the results returned by myFact(X,Y) are stored in a factlist.</p>
 */
public class FactList
	implements
		IFactList
{
	/**
	 * stores the parameters of this fact list
	 */
	protected Relation relation = null;

	/**
	 * stores the name of this fact list
	 */
	protected int name = -1;

	/**
	 * stores the number of columns stored in this factlist
	 */
	protected int colCount = 0;

	/**
	 * stores the relations
	 */
	protected List facts = new ArrayList(0);

	/**
	 * bean constructor
	 * @param name is the name of this factlist
	 */
	public FactList( int name, int colCount )
	{
		this.name = name;
		this.colCount = colCount;
	}

	/**
	 * copy constructor
	 * @param list to copy
	 */
	public FactList( FactList list )
	{
		this.relation = (Relation) SingletonFactory.getBuilder().createRelation();
		this.name = list.name;
		this.colCount = list.colCount;
		this.relation = (Relation) list.getRelation();
		Iterator i=list.getRelations();
		while( i.hasNext() )
			addRelation( (IRelation) i.next() );
	}

	/**
	 * adds a attribute to the relation
	 * @param value is the attribute to be added
	 */
	public void addAttribute( Object value )
	{
		if( value == null )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: FactList.addAttribute failed! "+
				"Can not add null attribute as format!" );

		relation.add( value );
	}

	/**
	 * @return the name of this factlist
	 */
	public int getName()
	{
		return this.name;
	}

	/**
	 * @return the number of columns stored in this factlist
	 */
	public int getColCount()
	{
		return this.colCount;
	}

	/**
	 * @return the number of relations in this factlist
	 */
	public int getRowCount()
	{
		return this.facts.size();
	}

	/**
	 * sets the relation format of this factlist
	 * @param relation
	 */
	public void setRelation( IRelation relation )
	{
		if( relation == null )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: FactList.setRelation failed! " +
				"Can not set null format!" );
		this.relation = (Relation) relation;
	}

	/**
	 * @return the relation of this factlist. in this
	 * relation, the variablen names of this factlist are contained
	 */
	public IRelation getRelation()
	{
		return relation;
	}

	/**
	 * adds a relation to this factlist
	 */
	public void addRelation( IRelation relation )
	{
		if( relation == null )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: FactList.addRelation failed! " +
				"Can not add an empty relation!" );

		// following code is disabled because factDb contains factlists without a format
		/*if( getRelation() == null )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: FactList.addRelation failed! " +
				"Factlist format still not specified!" );*/

		if( SingletonFactory.getBuilder().getSymbolTable() == null )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: FactList.addRelation failed! " +
				"No symboltable found (null)!" );

		if( colCount != relation.getValueCount() )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: FactList.addRelation failed! " +
				"colCount=\"" + colCount + "\" " +
				"relation.length=\"" + relation.getValueCount() + "\" " +
				"format=\"" + getRelation().toString( SingletonFactory.getBuilder().getSymbolTable() ) + "\"" );

		facts.add( relation );
	}

	/**
	 * @return a iterator over all relations contained in this class
	 */
	public Iterator getRelations()
	{
		return facts.iterator();
	}

	/**
	 * @return a string representation of this class
	 */
	public String toString()
	{
		return toString( null );
	}

	/**
	 * @param table is the ISymbolTable required to decode the symbols
	 * @return a string representation of this class with decoded symbols
	 */
	public String toString( ISymbolTable table )
	{
		StringBuffer out = new StringBuffer();

		out.append( "factlist " );
		if( table != null )
			out.append( table.decode( name ) );
		else
			out.append( name );

		if( relation != null ) // when factlist in FactDb relation is null
		{
			out.append( " format: " );
			out.append( relation.toString( table ) );
		}
		else
		{
			out.append( " columncount: " );
			out.append( this.colCount );
		}

		out.append( " values:\n" );
		Iterator i = this.getRelations();
		while( i.hasNext() )
		{
			out.append( "\t" );
			if( table != null )
				out.append( ((IRelation)i.next()).toString( table ) );
			else
				out.append( i.next() );
			out.append( "\n" );
		}

		return out.toString();
	}

	/**
	 * generates a xml representation of this class
	 * @param out the writer to append the xml
	 * @param table is the ISymbolTable to decode the symbols
	 * @throws IOException
	 */
	public void genXml( Writer out, ISymbolTable table )
		throws IOException
	{
		out.write( "<factlist>" );

		Iterator i = this.getRelations();
		while( i.hasNext() )
			((IFact)i.next()).genXml( out, table );

		out.write( "</factlist>" );

	}

	/**
	 * @return a copy of this class
	 */
	public IFactList getCopy()
	{
		return new FactList( this );
	}

	/**
	 * returns a new factlist with facts matching this given fact
	 * @param fact is the fact to search for
	 * @return
	 */
	public IFactList getMatchings( IFact fact )
		throws ParameterMatchingError
	{
		if( fact.getRelation().getValueCount() != colCount )
		{
			throw new ParameterMatchingError(
				"Parameter matching error: "+
				"Factlist " + SingletonFactory.getBuilder().getSymbolTable().decode( this.getName() ) +
				" has " + this.colCount + " columns, the given fact " +
				fact.toString( SingletonFactory.getBuilder().getSymbolTable() ) + " has " +
				fact.getRelation().getValueCount() ,
				fact );
		}

		IFactList back = getCopy();
		back.setRelation( fact.getRelation() );
		Iterator params = fact.getRelation().getValues();
		int index = 0;
		while( params.hasNext() )
		{
			Object value1 = params.next();
			if( ! (value1 instanceof Variable) )
			{ // if not variable
				// remove all non matching facts from factlist
				Iterator facts = back.getRelations();
				while( facts.hasNext() )
				{
					IRelation f = (IRelation) facts.next();
					IAtom value2 = f.get( index );
					if( ! value2.equals( value1 ) )
						facts.remove();
				}
			}
			index++;
		}

		return back;
	}
}
