#include "QueryParser.h"
#include "Query.h"
#include "htString.h"

//
// parse a query string
//
//
Query *
QueryParser::Parse(const String &query_string)
{
	error = "";
	Token().Set(query_string);

	Query *result = ParseExpression();
	if(result && !Token().IsEnd())
	{
		Expected("end of query");
		delete result;
		result = 0;
	}
	return result;
}

// parse one word
// return a fuzzy word query
//
Query *
QueryParser::ParseWord()
{
	Query *result = new FuzzyWordQuery(Token().Value());
	Token().Next();
	return result;
}

//
// parse one word
// return an exact query
//
Query *
QueryParser::ParseExactWord()
{
	Query *result = new ExactWordQuery(Token().Value());
	Token().Next();
	return result;
}

//	"a"	--> a
//	"a b"	--> a next b
//	"a b c"	--> (a next b) next c
//	...
// 
Query *
QueryParser::ParsePhrase()
{
	Query *result = 0;
	if(!Token().IsEnd() && !Token().IsQuote())
	{
		result = ParseExactWord();
	}
	while(result && !Token().IsEnd() && !Token().IsQuote())
	{
		Query *next = ParseExactWord();
		if(next)
		{
			result = new NextQuery(result, next);
		}
		else
		{
			delete result;
			result = 0;
		}
	}
	if(!result)
	{
		Expected("at least one word after \"");
	}
	return result;
}

void
QueryParser::Expected(const String &what)
{
	error << "Expected " << what;
	if(Token().IsEnd())
	{
		error << " at the end";
	}
	else
	{
		error << " instead of " << Token().Value();
	}
}

//
// expr == term { term }
//
Query *
SimpleQueryParser::ParseExpression()
{
	Query *result = 0;
	Query *term = ParseTerm();
	if(term)
	{
		if(token.IsEnd())
		{
			result = term;
		}
		else
		{
			result = MakeQuery();
			while(term && !token.IsEnd())
			{
				token.Next();
				result->Add(term);
				term = ParseTerm();
			}
		}
	}
	if(!term)
	{
		delete result;
		result = 0;
	}
	return result;
}


//
// term == word | '"' phrase '"'
//
Query *
SimpleQueryParser::ParseTerm()
{
	Query *result = 0;

	if(token.IsWord())
	{
		// don't advance token here!
		result = ParseWord();
	}
	else if(token.IsQuote())
	{
		token.Next();
		result = ParsePhrase();
		if(result)
		{
			if(token.IsQuote())
			{
				token.Next();
			}
			else
			{
				Expected("closing \"");
				delete result;
				result = 0;
			}
		}
	}
	else
	{
		Expected("a word or a quoted phrase");
	}
	return result;
}


//
// expr == andlist { 'or' andlist }
//
Query *
BooleanQueryParser::ParseExpression()
{
	Query *result = 0;
	Query *term = ParseAnd();
	if(term)
	{
		if(token.IsOr())
		{
			result = new OrQuery;
			while(term && token.IsOr())
			{
				result->Add(term);
				token.Next();
				term = ParseAnd();
			}
		}
		else
		{
			result = term;
		}
	}
	if(!term && result)
	{
		delete result;
	}
	return result;
}

Query *
GParser::ParseExpression()
{
	List factors;
	Query *result = 0;
	String op;

	Query *factor = ParseFactor();
	if(factor)
	{
		result = factor;
	}
	while(factor && (token.IsOr() || token.IsAnd() || token.IsNot() || token.IsNear()))
	{
		if(op != token.Value())
		{
			Query *previous = result;
			result = MakeOperatorQuery(op);
			result->Add(previous);
			op = token.Value();
		}
		token.Next();
		factor = ParseFactor();
		if(factor)
		{
			result->Add(factor);
		}
	}
	if(!factor && result)
	{
		delete result;
		result = 0;
	}
	return result;
}

OperatorQuery *
GParser::MakeOperatorQuery(const String &op) const
{
	OperatorQuery *result = 0;
	if(op == String("or"))
	{
		result = new OrQuery;
	}
	else if(op == String("and"))
	{
		result = new AndQuery;
	}
	else if(op == String("not"))
	{
		result = new NotQuery;
	}
	return result;
}

//
// andlist = notlist { 'and' notlist }
//
Query *
BooleanQueryParser::ParseAnd()
{
	Query *result = 0;
	Query *not = ParseNot();
	if(not)
	{
		if(token.IsAnd())
		{
			result = new AndQuery();
			while(not && token.IsAnd())
			{
				result->Add(not);
				token.Next();
				not = ParseNot();
			}
		}
		else
		{
			result = not;
		}
	}
	if(!not && result)
	{
		delete result;
		result = 0;
	}
	return result;
}

//
// notlist = nearlist { 'not' nearlist }
//
Query *
BooleanQueryParser::ParseNot()
{
	Query *result = 0;
	Query *near = ParseNear();
	if(near)
	{
		if(token.IsNot())
		{
			result = new NotQuery();
			while(near && token.IsNot())
			{
				result->Add(near);
				token.Next();
				near = ParseNear();
			}
		}
		else
		{
			result = near;
		}
	}
	if(!near && result)
	{
		delete result;
		result = 0;
	}
	return result;
}

//
// near == factor { 'near' factor }
// 'near' query is binary
//
Query *
BooleanQueryParser::ParseNear()
{
	Query *result = ParseFactor();
	while(result && token.IsNear())
	{
		token.Next();
		int distance = 10; // config["default_near_distance"];
		if(token.IsSlash())
		{
			distance = 0;
			token.Next();
			if(token.IsWord())
			{
				distance = token.Value().as_integer();
				token.Next();
			}
		}
		if(distance > 0)
		{
			Query *right = ParseFactor();
			if(right)
			{
				result = new NearQuery(result, right, distance);
			}
			else
			{
				delete result;
				result = 0;
			}
		}
		else
		{
			Expected("a distance > 0 for 'Near'");
			delete result;
			result = 0;
		}
	}
	return result;
}

//
// factor == word | '"' phrase '"' | '(' expression ')'
//
Query *
BooleanQueryParser::ParseFactor()
{
	Query *result = 0;

	if(token.IsWord())
	{
		result = ParseWord();
	}
	else if(token.IsQuote())
	{
		token.Next();
		result = ParsePhrase();
		if(result)
		{
			if(token.IsQuote())
			{
				token.Next();
			}
			else
			{
				Expected("closing \"");
				delete result;
				result = 0;
			}
		}
	}
	else if(token.IsLeftParen())
	{
		token.Next();
		result = ParseExpression();
		if(result)
		{
			if(token.IsRightParen())
			{
				token.Next();
			}
			else
			{
				Expected(")");
				delete result;
				result = 0;
			}
		}
	}
	else
	{
		Expected("(, \", or a word");
	}
	return result;
}

