package prolog.implementation;

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

import prolog.*;

/**
 * <p>The AndStatement represents the and operation over two tables.</p>

 */
public class AndStatement
	extends NodeStatement
{
	/**
	 * constructor 1
	 */
	public AndStatement( int name )
	{
		super( name );
	}

	/**
	 * constructor 2
	 */
	public AndStatement( IFact f )
	{
		super( f.getName() );
		this.relation = (Relation) f.getRelation();
	}

	/**
	 * queries the child nodes and calls the recursive
	 * findMatching method to merge all results.
	 * @param f - the target format
	 * @param l - the fact database
	 * @param constraints - the statements to resolve when not found in fdb
	 * @return a result factlist with all matching results in the format
	 * described in the f parameter fact
	 * @throws ParameterMatchingError
	 */
	public IFactList query( IFact f, IFactDb l, IStatementMap constraints  )
		throws
			ParameterMatchingError
	{
		if( f.getRelation().getValueCount() != getRelation().getValueCount() )
			throw new RuntimeException(
				"INTERNAL MACHINE ERROR: AndStatement.query failed! " +
				"The given fact does not match the used format!" );

		// here the format of this and statement is described
		Map thisRelationFormat = getRelation().buildMap();

		// <- here all sub queries of the childs are stored in
		List resultCollection = new ArrayList(0);

		// now all childs are queries, the results are stored in resultCollection
		Iterator ci = childs.iterator();
		while( ci.hasNext() )
		{
			IStatement s = (IStatement) ci.next();

			// build fact matching the child statement format
			IFact reorderedFact =
				generateFact(
					f,
					thisRelationFormat,
					s );

			// query the child
			IFactList results =
				s.query(
					reorderedFact,
					l,
					constraints );

			// add query result to collector
			resultCollection.add( results );
		}

		// here the matchings are saved in while doing enormous search,
		// if som results produce a conflict in this cachmap, the result
		// is discarded
		CacheMap cacheMap = new CacheMap();

		// in this factlist, the final results of this and node are stored
		IFactList back =
			SingletonFactory.getBuilder().createFactList(
				f.getName(),
				f.getRelation().getValueCount() );
		back.setRelation( f.getRelation() ); // set format

		try
		{
			// do the recursive enormous search now
			findMatching( f, resultCollection, 0, back, cacheMap );
		}
		catch( Exception ae )
		{
			ae.printStackTrace();
		}

		return back;
	}

	/**
	 * finds all matchings in the list with results collected by query and
	 * puts it in targetformat into the finalresult factlist
	 * @param f - the target format
	 * @param all - the list with matchings returned by the childs
	 * @param index - the index of the current factlist, begins with 0
	 * @param finalResults - the collector for really matching final results
	 * @param cachMap - the cachemap used to find matching values
	 * @return
	 * @throws Exception
	 */
	protected void findMatching(
		IFact f,
		List all,
		int index,
		IFactList finalResults,
		CacheMap cachMap )
	throws Exception
	{
		// table returned by child[index]
		IFactList childTable = (IFactList) all.get( index );

		// rows of table returned by child[index]
		Iterator childTableRows = childTable.getRelations();

		// iterate relations/rows of indexed factlist/table
		while( childTableRows.hasNext() )
		{
			CacheMap tmpCache = (CacheMap) cachMap.clone();

			// format+values of the factlist returned by child[index]
			IRelation rFormat = childTable.getRelation();
			Iterator keys = rFormat.getValues();

			// indexed row+atoms of the factlist returned by child[index]
			IRelation indexedRow = (IRelation) childTableRows.next();
			Iterator indexedAtoms = indexedRow.getValues();

			// check format of current row, exit if failed
			if( indexedRow.getValueCount() != rFormat.getValueCount() )
				throw new RuntimeException(
					"INTERNAL MACHINE ERROR: AndStatement.findMatching failed! " +
					"Indexed row does not match the indexed table format" );

			// iterate values of relation and push in cachemap
			boolean succeeded = true;
			while( indexedAtoms.hasNext() )
				if( ! addMatching( tmpCache, keys.next(), indexedAtoms.next() ) )
				{ // find multiple matchings? ->return
					succeeded = false;
					break;
				}

			// if no errors occured during building cachemap, specify...
			if( succeeded )
			{
				// if not last indexed result table go deeper in recursion
				if( index < all.size() - 1 )
					findMatching(
						f,
						all,
						index+1,
						finalResults,
						tmpCache );
				// if last indexed result table add final result
				else
				{
					// create final relation
					Relation newFinalResult = (Relation) SingletonFactory.getBuilder().createRelation();

					// get targetformat
					IRelation targetFormat = f.getRelation();
					Iterator tfi = targetFormat.getValues();

					while( tfi.hasNext() )
					{
						// get format atom
						Object key = tfi.next();

						// at this point, the cached map is really validated ;-),
						// the given cachmap does only contain results of a
						// higher recursion
						Object value = tmpCache.get( key );

						// check value is not null
						if( value == null )
							throw new RuntimeException(
								"INTERNAL MACHINE ERROR: " +
								"AndStatement.findMatching failed while inserting final result! " +
								"variable \"" + key.toString() + "\" not found! " +
								cachMap.toString()  );

						newFinalResult.add( value );
					}

					// now add the final result row found
					finalResults.addRelation( newFinalResult );
				}
			}
		}
	}

	/**
	 * checks wether the key is allready stored in the cachmap. if not,
	 * the value is stored and true is returned. if yes, it was stored,
	 * the stored value is compared with the given mapping. then the
	 * result of the comparation is returned
	 * @param m - the cachemap where to add the matching
	 * @param key - the key under which the value should be stored
	 * @param mapping - the value to be stored
	 * @return true if the given key and mapping does not produce any conflicts,
	 * else false
	 */
	protected boolean addMatching( CacheMap m, Object key, Object mapping )
	{
		if( m.containsKey( key ) ) // is key in cache
		{
			return m.get( key ).equals( mapping ); // return compare result
		}
		else
		{
			m.put( key, mapping ); // store matching
			return true;
		}
	}

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

	/**
	 * returns a string representation of this class with decoded symbols
	 * @param table - the symboltable to decode the symbols
	 * @return a string representation of this class
	 */
	public String toString( ISymbolTable table )
	{
		String back = "";
		Iterator i=childs.iterator();
		while( i.hasNext() )
		{
			back += ((IStatement)i.next()).toString( table );
			if( i.hasNext() )
				back += ",";
		}
		return back;
	}

	/**
	 * this class is the implementation of a cache map
	 * @author MRoc
	 * @version 1.0
	 */
	protected class CacheMap
		extends
			HashMap
		implements
			Cloneable
	{
		/**
		 * constructor
		 */
		public CacheMap()
		{
		}

		/**
		 * puts a value into this cachmap
		 * @param key
		 * @param value
		 */
		public Object put( Object key, Object value )
		{
			if( key == null )
				throw new RuntimeException(
					"INTERNAL MACHINE ERROR: CacheMap.put failed! " +
					"Could not store with key=null!" );
			if( value == null )
				throw new RuntimeException(
					"INTERNAL MACHINE ERROR: CacheMap.put failed! " +
					"Could not store value=null!" );

			return super.put( key, value );
		}

		/**
		 * @return a string representation of this class for debug purpose
		 */
		public String toString()
		{
			StringBuffer out = new StringBuffer();
			out.append( "matchingcache: ");
			Iterator keys = this.keySet().iterator();
			while( keys.hasNext() )
			{
				Object key = keys.next();
				out.append( key );
				out.append( "/" );
				out.append( get( key ) );
				out.append( " " );
			}
			return out.toString();
		}
	}
}
