package prolog.syntax;

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

import prolog.*;

public class Parser
{
	protected Scanner scanner = null;
	protected Program programm = null;
	private ILogger logger = null;
	private Stack stack = new Stack();

	public Parser(
		Scanner scanner,
		Program programm,
		ILogger logger )
	{
		this.scanner = scanner;
		this.programm = programm;
		if( logger == null )
			this.logger = new StdOutLogger();
		else
			this.logger = logger;
	}

	public void reset()
	{
		stack.clear();
	}

	public void parse()
		throws SyntaxError
	{
		logger.msg( "parsing..." );
		char c = (char)scanner.input();
		while( scanner.getCurrentChar() != -1 )
		{
			if( scanner.test( '?' ) )
			{
				scanner.input();
				syntaxTest('-');
				scanner.input();
				if( ! scanner.isCurrentChar() )
					throw new SyntaxError(
						"character as identifier expected, but \"" +
						((char)scanner.getCurrentChar()) + "\" was found!" );
				scanner.scanWord();
				IFact f = parseFact( scanner.getCurrentWord(), 0, true );
				programm.getQuerys().addQuery( new Query( f ) );
				syntaxTest('.');
				logger.msg( "query found... " + f.toString( programm.getSymbolTable() ) );
			}
			else if( scanner.isCurrentChar() )
			{
				// identifier
				scanner.scanWord();
				Fact f = parseFact( scanner.getCurrentWord(), 0, false );
				if( scanner.test(':') )
				{ // constraint
					scanner.input();
					if( scanner.test('-') )
					{ // ":-"
						scanner.input();
						this.expression( f, 1 );
						IStatement s = (IStatement) stack.pop();
						if( s != null )
						{
							ConstraintStatement ss =
								(ConstraintStatement)SingletonFactory.getBuilder().createConstraintStatement( f.getName(), f.getRelation() );
							ss.setExpression( s );
							programm.getStatements().addStatement( ss );
							logger.msg( "expression found... " + ss.toString( programm.getSymbolTable() ) );
						}
						else
							throw new SyntaxError( "fatal error" );
						stack.clear();
					}
					else
						syntaxTest('-');
				}
				else if( scanner.test('.') )
				{ // eol
					logger.msg( "fact found... " + f.toString( programm.getSymbolTable() ) );
					programm.getFacts().add( f );
				}
				else
					throw new SyntaxError(
						"\":\" or \".\" expected, but \"" +
						((char)scanner.getCurrentChar()) +
						"\" was found!" );
			}
			else
				scanner.skipWhiteSpace();
		}
	}

	private void expression( Fact f, int depth )
		throws SyntaxError
	{
		logger.msg( "expression", depth );
		NodeStatement statement = null;
		term( f, depth+1 );
		do
		{
			if( scanner.test( ';' ) )
			{
				if( statement == null )
				{
//					statement = new OrStatement( f );
//					statement.addStatement( (IStatement) stack.pop() );
					throw new RuntimeException( "not supported" );
				}
				scanner.input();
				term( f, depth+1 );
				statement.addStatement( (IStatement) stack.pop() );
			}
		}
		while( scanner.test( ';' ) );
		if( statement != null )
		{
			stack.push( statement );
		}
	}

	private void term( Fact f, int depth )
		throws SyntaxError
	{
		logger.msg( "term", depth );
		NodeStatement statement = null;
		factor( f, depth+1 );
		do
		{
			if( scanner.test(',' ) )
			{
				if( statement == null )
				{
					statement = (AndStatement) SingletonFactory.getBuilder().createAndStatement( f );
					statement.addStatement( (IStatement) stack.pop() );
				}
				scanner.input();
				factor( f, depth +1 );
				statement.addStatement( (IStatement) stack.pop() );
			}
		}
		while( scanner.test(',') );

		if( statement != null )
		{
			stack.push( statement );
		}
	}

	private void factor( Fact f, int depth )
		throws SyntaxError
	{
		logger.msg( "factor", depth );
		if( scanner.test( '(' ) )
		{
			scanner.input();
			expression( f, depth + 1 );
			syntaxTest( ')' );
		}
		else if( scanner.isCurrentChar() )
		{
			//emit( ID, scanner.getCurrentChar() );
			scanner.scanWord();
			Fact newFact = parseFact( scanner.getCurrentWord(), depth+1, false );
			MatchingStatement s = (MatchingStatement)
				SingletonFactory.getBuilder().createMatchingStatement( newFact.getName(), newFact.getRelation() );
			stack.push( s );
		}
		else
			throw new SyntaxError( "unknown sign found" );
	}

	private Fact parseFact( int iName, int depth, boolean question )
		throws SyntaxError
	{
		List params = new ArrayList(1);

		// do parse
		if( scanner.getCurrentChar() == '(' )
		{
			do
			{
				scanner.input();
				if( question &&
					'A' <= ((char)scanner.getCurrentChar()) &&
					((char)scanner.getCurrentChar()) <= 'Z' )
				{
					scanner.scanWord();
					Variable v = new Variable( scanner.getCurrentWord() );
					params.add( v );
				}
				else
				{
					scanner.scanWord();
					Value v = new Value( scanner.getCurrentWord() );
					params.add( v );
				}
			}
			while( ((char)scanner.getCurrentChar()) == ',' );
			if( ((char)scanner.getCurrentChar()) != ')' )
				throw new SyntaxError(
					"\")\" expected, but \"" +
					((char)scanner.getCurrentChar()) + "\" was found!" );
			scanner.input();
		}
		else
			throw new SyntaxError(
				"\"(\" expected, but \"" +
				((char)scanner.getCurrentChar()) +
				"\" was found!" );

		Fact back = (Fact) SingletonFactory.getBuilder().createFact( iName );
		Iterator i = params.iterator();
		while( i.hasNext() )
			back.addAttribute( i.next() );
		return back;
	}

	public Program getProgramm()
	{
		return programm;
	}

	public String printStack()
	{
		StringBuffer out = new StringBuffer();
		out.append( "<stack>\n" );
		Iterator i = stack.iterator();
		while( i.hasNext() )
		{
			out.append( "\t<item>" );
			out.append( ((Fact)i.next()).toString() );
			out.append( "</item>\n" );
		}
		out.append( "</stack>" );
		return out.toString();
	}

	private void syntaxTest( char c )
		throws SyntaxError
	{
		if( scanner.getCurrentChar() == -1 ||
			c != (char)scanner.getCurrentChar() )
			throw new SyntaxError(
				"\"" + c + "\" expected but \"" +
				((char)scanner.getCurrentChar()) +
				"\" found in line " +
				scanner.getCurrentLine() +
				" col " +
				scanner.getCurrentSign() );
	}
}
