package prolog.implementation;

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

import prolog.util.ParameterMatchingError;

/**
 * the fact database stores all data found in the prolog program.
 * it stores all facts in factlists that are sorted by their name,
 * so it gets a database representation with a table for every
 * fact type found.
 */
public class FactDb
	implements IFactDb
{
	/**
	 * list of listeners to be informed about changes
	 */
	protected List listeners = new ArrayList(0);

	/**
	 * the map storing all factlists under their name
	 */
	protected Map factCollections = new HashMap();

	/**
	 * constructor
	 */
	public FactDb()
	{
	}

	/**
	 * clears the whole fact database
	 */
	public void clear()
	{
		factCollections.clear();
		this.fireModelChanged();
	}

	/**
	 * adds a fact to this database. this fact
	 * is stored in the factlist having the
	 * same name with the fact itself.
	 */
	public void add( IFact f )
	{
		add( f.getRelation(), f.getName() );
	}

	/**
	 * adds a relation into the factlist with the specified name
	 * @param relation - the relation to be added
	 * @param name - the name of the relation
	 */
	public void add( IRelation relation, int name )
	{
		IFactList l = null;
		if( (l=getFactList( name )) == null )
		{
			l = (IFactList) SingletonFactory.getBuilder().createFactList( name, relation.getValueCount() );
			add( l );
		}
		l.addRelation( relation );
		this.fireRelationAdded( relation, name );
	}

	/**
	 * adds a factlist to the database
	 */
	public void add( IFactList l )
	{
		if( getFactList( l.getName() ) != null )
			throw new RuntimeException( "no double factlists!!!" );
		factCollections.put( new Value( l.getName() ), l );
		this.fireFactListAdded( l );
	}

	/**
	 * returns the factlist with the specified name
	 * @param key - the name of the factlist
	 * @return - the found factlist or null if no one was found
	 */
	public IFactList getFactList( int key )
	{
		return (IFactList) factCollections.get( new Value( key ) );
	}

	/**
	 * returns a iterator over all facts stored in this factlist
	 */
	public Iterator getFactLists()
	{
		return factCollections.values().iterator();
	}

	/**
	 * returns true if the given fact can be found in the database
	 * @return true if fact was found
	 */
	public boolean isFact( IFact f )
		throws ParameterMatchingError
	{
		boolean back = false;
		IFactList l = getFactList( f.getName() );
		if( l != null )
			back = l.getMatchings( f ).getRelations().hasNext();
		return back;
	}

	/**
	 * returns a string representation of this class
	 */
	public String toString()
	{
		StringBuffer out = new StringBuffer();
		Iterator i= this.getFactLists();
		while( i.hasNext() )
		{
			IFactList l = (IFactList) i.next();
			out.append( l.toString() );
			if( i.hasNext() )
				out.append( '\n');
		}
		return out.toString();
	}

	/**
	 * returns a string representation of this class
	 * with decoded symbols
	 */
	public String toString( ISymbolTable table )
	{
		StringBuffer out = new StringBuffer();
		out.append( "facts:\n" );
		Iterator i= this.getFactLists();
		while( i.hasNext() )
		{
			IFactList l = (IFactList) i.next();
			Iterator facts = l.getRelations();
			while( facts.hasNext() )
			{
				out.append( "\t" );
				out.append( table.decode( l.getName() ) );
				out.append( "(" );
				out.append( ((IRelation)facts.next()).toString( table ) );
				out.append( ")" );
				if( facts.hasNext() )
					out.append( '\n');
			}
		}
		return out.toString();
	}

	/**
	 * generates xml representing this object
	 * @param out
	 * @param table
	 * @throws IOException
	 */
	public void genXml( Writer out, ISymbolTable table )
		throws IOException
	{
		out.write( "<factdb>" );

		Iterator i1 = this.factCollections.keySet().iterator();
		while( i1.hasNext() )
		{
			Value key = (Value)i1.next();
			IFactList relationList = getFactList( key.getName() );
			out.write( "<table>" );
			out.write( "<name>" );
			if( table != null )
				out.write( table.decode( relationList.getName() ) );
			else
				out.write( Integer.toString( relationList.getName() ) );
			out.write( "</name>" );

			Iterator i2 = relationList.getRelations();
			while( i2.hasNext() )
			{
				out.write( "<data>" );
				((IRelation)i2.next()).genXml( out, table );
				out.write( "</data>" );
			}
			out.write( "</table>" );
		}

		out.write( "</factdb>" );
	}

	/**
	 * adds a fact db listener
	 */
	public void addListener( IFactDbListener l )
	{
		listeners.add( l );
	}

	/**
	 * removes a fact db listener
	 */
	public void removeListener( IFactDbListener l )
	{
		listeners.remove( l );
	}

	/**
	 * fires a model changed notification
	 */
	protected void fireModelChanged()
	{
		Iterator i = listeners.iterator();
		while( i.hasNext() )
			((IFactDbListener)i.next()).onFactDbChanged();
	}

	/**
	 * fires a fact list added notification
	 */
	protected void fireFactListAdded( IFactList l )
	{
		Iterator i = listeners.iterator();
		while( i.hasNext() )
			((IFactDbListener)i.next()).onFactListAdded( l );
	}

	/**
	 * fires a fact added notification
	 */
	protected void fireRelationAdded( IRelation l, int name )
	{
		Iterator i = listeners.iterator();
		while( i.hasNext() )
			((IFactDbListener)i.next()).onRelationAdded( l, name );
	}
}
