/* This file is part of the KDE project
   Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include "parser_p.h"
#include "sqlparser.h"

#include <kdebug.h>
#include <tdelocale.h>

#include <tqregexp.h>

#include <assert.h>

using namespace KexiDB;

Parser *parser = 0;
Field *field = 0;
//bool requiresTable;
TQPtrList<Field> fieldList;
int current = 0;
TQString ctoken = "";

//-------------------------------------

ParserPrivate::ParserPrivate()
 : reservedKeywords(997, 997, false)
 , initialized(false)
{
	clear();
	table = 0;
	select = 0;
	db = 0;
}

ParserPrivate::~ParserPrivate()
{
	delete select;
	delete table;
}

void ParserPrivate::clear()
{
	operation = Parser::OP_None;
	error = ParserError();
}

//-------------------------------------

ParseInfo::ParseInfo(KexiDB::QuerySchema *query)
 : repeatedTablesAndAliases(997, false)
 , querySchema(query)
{
	repeatedTablesAndAliases.setAutoDelete(true);
}

ParseInfo::~ParseInfo()
{
}

//-------------------------------------

extern int yyparse();
extern void tokenize(const char *data);

void yyerror(const char *str)
{
	KexiDBDbg << "error: " << str << endl;
	KexiDBDbg << "at character " << current << " near tooken " << ctoken << endl;
	parser->setOperation(Parser::OP_Error);

	const bool otherError = (tqstrnicmp(str, "other error", 11)==0);
	
	if (parser->error().type().isEmpty() 
		&& (str==0 || strlen(str)==0 
		|| tqstrnicmp(str, "syntax error", 12)==0 || tqstrnicmp(str, "parse error", 11)==0)
		|| otherError)
	{
		KexiDBDbg << parser->statement() << endl;
		TQString ptrline = "";
		for(int i=0; i < current; i++)
			ptrline += " ";

		ptrline += "^";

		KexiDBDbg << ptrline << endl;

		//lexer may add error messages
		TQString lexerErr = parser->error().error();

		TQString errtypestr(str);
		if (lexerErr.isEmpty()) {
#if 0
			if (errtypestr.startsWith("parse error, unexpected ")) {
				//something like "parse error, unexpected IDENTIFIER, expecting ',' or ')'"
				TQString e = errtypestr.mid(24);
				KexiDBDbg << e <<endl;
				TQString token = "IDENTIFIER";
				if (e.startsWith(token)) {
					TQRegExp re("'.'");
					int pos=0;
					pos = re.search(e, pos);
					TQStringList captured=re.capturedTexts();
					if (captured.count()>=2) {
//						KexiDBDbg << "**" << captured.at(1) << endl;
//						KexiDBDbg << "**" << captured.at(2) << endl;
					}
				}
					
					
					
//			 IDENTIFIER, expecting '")) {
				e = errtypestr.mid(47);
				KexiDBDbg << e <<endl;
//				,' or ')'
//		lexerErr i18n("identifier was expected");
				
			} else 
#endif
			if (errtypestr.startsWith("parse error, expecting `IDENTIFIER'"))
				lexerErr = i18n("identifier was expected");
		}
		
		if (!otherError) {
			if (!lexerErr.isEmpty())
				lexerErr.prepend(": ");

			if (parser->isReservedKeyword(ctoken.latin1()))
				parser->setError( ParserError(i18n("Syntax Error"), 
					i18n("\"%1\" is a reserved keyword").arg(ctoken)+lexerErr, ctoken, current) );
			else
				parser->setError( ParserError(i18n("Syntax Error"), 
					i18n("Syntax Error near \"%1\"").arg(ctoken)+lexerErr, ctoken, current) );
		}
	}
}

void setError(const TQString& errName, const TQString& errDesc)
{
	parser->setError( ParserError(errName, errDesc, ctoken, current) );
	yyerror(errName.latin1());
}

void setError(const TQString& errDesc)
{
	setError("other error", errDesc);
}

/* this is better than assert() */
#define IMPL_ERROR(errmsg) setError("Implementation error", errmsg)

bool parseData(Parser *p, const char *data)
{
/* todo: make this REENTRANT */
	parser = p;
	parser->clear();
	field = 0;
	fieldList.clear();
//	requiresTable = false;

	if (!data) {
		ParserError err(i18n("Error"), i18n("No query specified"), ctoken, current);
		parser->setError(err);
		yyerror("");
		parser = 0;
		return false;
	}

	tokenize(data);
	if (!parser->error().type().isEmpty()) {
		parser = 0;
		return false;
	}
	yyparse();

	bool ok = true;
	if(parser->operation() == Parser::OP_Select)
	{
		KexiDBDbg << "parseData(): ok" << endl;
//			KexiDBDbg << "parseData(): " << tableDict.count() << " loaded tables" << endl;
/*			TableSchema *ts;
			for(TQDictIterator<TableSchema> it(tableDict); TableSchema *s = tableList.first(); s; s = tableList.next())
			{
				KexiDBDbg << "  " << s->name() << endl;
			}*/
/*removed
			Field::ListIterator it = parser->select()->fieldsIterator();
			for(Field *item; (item = it.current()); ++it)
			{
				if(tableList.findRef(item->table()) == -1)
				{
					ParserError err(i18n("Field List Error"), i18n("Unknown table '%1' in field list").arg(item->table()->name()), ctoken, current);
					parser->setError(err);

					yyerror("fieldlisterror");
					ok = false;
				}
			}*/
			//take the dummy table out of the query
//			parser->select()->removeTable(dummy);
	}
	else {
		ok = false;
	}

//		tableDict.clear();
	parser = 0;
	return ok;
}

	
/* Adds \a column to \a querySchema. \a column can be in a form of
 table.field, tableAlias.field or field
*/
bool addColumn( ParseInfo& parseInfo, BaseExpr* columnExpr )
{
	if (!columnExpr->validate(parseInfo)) {
		setError(parseInfo.errMsg, parseInfo.errDescr);
		return false;
	}

	VariableExpr *v_e = columnExpr->toVariable();
	if (columnExpr->exprClass() == KexiDBExpr_Variable && v_e) {
		//it's a variable:
		if (v_e->name=="*") {//all tables asterisk
			if (parseInfo.querySchema->tables()->isEmpty()) {
				setError(i18n("\"*\" could not be used if no tables are specified"));
				return false;
			}
			parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) );
		}
		else if (v_e->tableForQueryAsterisk) {//one-table asterisk
			parseInfo.querySchema->addAsterisk( 
				new QueryAsterisk(parseInfo.querySchema, v_e->tableForQueryAsterisk) );
		}
		else if (v_e->field) {//"table.field" or "field" (bound to a table or not)
			parseInfo.querySchema->addField(v_e->field, v_e->tablePositionForField);
		}
		else {
			IMPL_ERROR("addColumn(): unknown case!");
			return false;
		}
		return true;
	}

	//it's complex expression
	parseInfo.querySchema->addExpression(columnExpr);

#if 0
	KexiDBDbg << "found variable name: " << varName << endl;
	int dotPos = varName.find('.');
	TQString tableName, fieldName;
//TODO: shall we also support db name?
	if (dotPos>0) {
		tableName = varName.left(dotPos);
		fieldName = varName.mid(dotPos+1);
	}
	if (tableName.isEmpty()) {//fieldname only
		fieldName = varName;
		if (fieldName=="*") {
			parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema) );
		}
		else {
			//find first table that has this field
			Field *firstField = 0;
			for (TableSchema::ListIterator it(*parseInfo.querySchema->tables()); it.current(); ++it) {
				Field *f = it.current()->field(fieldName);
				if (f) {
					if (!firstField) {
						firstField = f;
					} else if (f->table()!=firstField->table()) {
						//ambiguous field name
						setError(i18n("Ambiguous field name"), 
							i18n("Both table \"%1\" and \"%2\" have defined \"%3\" field. "
								"Use \"<tableName>.%4\" notation to specify table name.")
								.arg(firstField->table()->name()).arg(f->table()->name())
								.arg(fieldName).arg(fieldName));
						return false;
					}
				}
			}
			if (!firstField) {
					setError(i18n("Field not found"), 
						i18n("Table containing \"%1\" field not found").arg(fieldName));
					return false;
			}
			//ok
			parseInfo.querySchema->addField(firstField);
		}
	}
	else {//table.fieldname or tableAlias.fieldname
		tableName = tableName.lower();
		TableSchema *ts = parseInfo.querySchema->table( tableName );
		if (ts) {//table.fieldname
			//check if "table" is covered by an alias
			const TQValueList<int> tPositions = parseInfo.querySchema->tablePositions(tableName);
			TQValueList<int>::ConstIterator it = tPositions.constBegin();
			TQCString tableAlias;
			bool covered = true;
			for (; it!=tPositions.constEnd() && covered; ++it) {
				tableAlias = parseInfo.querySchema->tableAlias(*it);
				if (tableAlias.isEmpty() || tableAlias.lower()==tableName.latin1())
					covered = false; //uncovered
				KexiDBDbg << " --" << "covered by " << tableAlias << " alias" << endl;
			}
			if (covered) {
				setError(i18n("Could not access the table directly using its name"), 
					i18n("Table \"%1\" is covered by aliases. Instead of \"%2\", "
					"you can write \"%3\"").arg(tableName)
					.arg(tableName+"."+fieldName).arg(tableAlias+"."+fieldName.latin1()));
				return false;
			}
		}
		
		int tablePosition = -1;
		if (!ts) {//try to find tableAlias
			tablePosition = parseInfo.querySchema->tablePositionForAlias( tableName.latin1() );
			if (tablePosition>=0) {
				ts = parseInfo.querySchema->tables()->at(tablePosition);
				if (ts) {
//					KexiDBDbg << " --it's a tableAlias.name" << endl;
				}
			}
		}


		if (ts) {
			TQValueList<int> *positionsList = repeatedTablesAndAliases[ tableName ];
			if (!positionsList) {
				IMPL_ERROR(tableName + "." + fieldName + ", !positionsList ");
				return false;
			}

			if (fieldName=="*") {
				if (positionsList->count()>1) {
					setError(i18n("Ambiguous \"%1.*\" expression").arg(tableName),
						i18n("More than one \"%1\" table or alias defined").arg(tableName));
					return false;
				}
				parseInfo.querySchema->addAsterisk( new QueryAsterisk(parseInfo.querySchema, ts) );
			}
			else {
//				KexiDBDbg << " --it's a table.name" << endl;
				Field *realField = ts->field(fieldName);
				if (realField) {
					// check if table or alias is used twice and both have the same column
					// (so the column is ambiguous)
					int numberOfTheSameFields = 0;
					for (TQValueList<int>::iterator it = positionsList->begin();
						it!=positionsList->end();++it)
					{
						TableSchema *otherTS = parseInfo.querySchema->tables()->at(*it);
						if (otherTS->field(fieldName))
							numberOfTheSameFields++;
						if (numberOfTheSameFields>1) {
							setError(i18n("Ambiguous \"%1.%2\" expression").arg(tableName).arg(fieldName),
								i18n("More than one \"%1\" table or alias defined containing \"%2\" field")
								.arg(tableName).arg(fieldName));
							return false;
						}
					}

					parseInfo.querySchema->addField(realField, tablePosition);
				}
				else {
					setError(i18n("Field not found"), i18n("Table \"%1\" has no \"%2\" field")
						.arg(tableName).arg(fieldName));
					return false;
				}
			}
		}
		else {
			tableNotFoundError(tableName);
			return false;
		}
	}
#endif
	return true;
}

//! clean up no longer needed temporary objects
#define CLEANUP \
	delete colViews; \
	delete tablesList; \
	delete options

QuerySchema* buildSelectQuery( 
	QuerySchema* querySchema, NArgExpr* colViews, NArgExpr* tablesList, 
	SelectOptionsInternal* options )
{
	ParseInfo parseInfo(querySchema);
	
	//-------tables list
//	assert( tablesList ); //&& tablesList->exprClass() == KexiDBExpr_TableList );

	uint columnNum = 0;
/*TODO: use this later if there are columns that use database fields, 
        e.g. "SELECT 1 from table1 t, table2 t") is ok however. */
	//used to collect information about first repeated table name or alias:
//	TQDict<char> tableNamesAndTableAliases(997, false);
//	TQString repeatedTableNameOrTableAlias;
	if (tablesList) {
		for (int i=0; i<tablesList->args(); i++, columnNum++) {
			BaseExpr *e = tablesList->arg(i);
			VariableExpr* t_e = 0;
			TQCString aliasString;
			if (e->exprClass() == KexiDBExpr_SpecialBinary) {
				BinaryExpr* t_with_alias = e->toBinary();
				assert(t_with_alias);
				assert(t_with_alias->left()->exprClass() == KexiDBExpr_Variable);
				assert(t_with_alias->right()->exprClass() == KexiDBExpr_Variable
					&& (t_with_alias->token()==AS || t_with_alias->token()==0));
				t_e = t_with_alias->left()->toVariable();
				aliasString = t_with_alias->right()->toVariable()->name.latin1();
			}
			else {
				t_e = e->toVariable();
			}
			assert(t_e);
			TQCString tname = t_e->name.latin1();
			TableSchema *s = parser->db()->tableSchema(TQString(tname));
			if(!s) {
				setError(//i18n("Field List Error"), 
					i18n("Table \"%1\" does not exist").arg(TQString(tname)));
	//			yyerror("fieldlisterror");
				CLEANUP;
				return 0;
			}
			TQCString tableOrAliasName;
			if (!aliasString.isEmpty()) {
				tableOrAliasName = aliasString;
//				KexiDBDbg << "- add alias for table: " << aliasString << endl;
			} else {
				tableOrAliasName = tname;
			}
			// 1. collect information about first repeated table name or alias
			//    (potential ambiguity)
			TQValueList<int> *list = parseInfo.repeatedTablesAndAliases[tableOrAliasName];
			if (list) {
				//another table/alias with the same name
				list->append( i );
//				KexiDBDbg << "- another table/alias with name: " << tableOrAliasName << endl;
			}
			else {
				list = new TQValueList<int>();
				list->append( i );
				parseInfo.repeatedTablesAndAliases.insert( tableOrAliasName, list );
//				KexiDBDbg << "- first table/alias with name: " << tableOrAliasName << endl;
			}
	/*		if (repeatedTableNameOrTableAlias.isEmpty()) {
				if (tableNamesAndTableAliases[tname])
					repeatedTableNameOrTableAlias=tname;
				else
					tableNamesAndTableAliases.insert(tname, (const char*)1);
			}
			if (!aliasString.isEmpty()) {
				KexiDBDbg << "- add alias for table: " << aliasString << endl;
	//			querySchema->setTableAlias(columnNum, aliasString);
				//2. collect information about first repeated table name or alias
				//   (potential ambiguity)
				if (repeatedTableNameOrTableAlias.isEmpty()) {
					if (tableNamesAndTableAliases[aliasString])
						repeatedTableNameOrTableAlias=aliasString;
					else
						tableNamesAndTableAliases.insert(aliasString, (const char*)1);
				}
			}*/
//			KexiDBDbg << "addTable: " << tname << endl;
			querySchema->addTable( s, aliasString );
		}
	}

	/* set parent table if there's only one */
//	if (parser->select()->tables()->count()==1)
	if (querySchema->tables()->count()==1)
		querySchema->setMasterTable(querySchema->tables()->first());

	//-------add fields
	if (colViews) {
		BaseExpr *e;
		columnNum = 0;
		const uint colCount = colViews->list.count();
//		for (BaseExpr::ListIterator it(colViews->list);(e = it.current()); columnNum++)
		colViews->list.first();
		for (; columnNum<colCount; columnNum++) {
			e = colViews->list.current();
			bool moveNext = true; //used to avoid ++it when an item is taken from the list
			BaseExpr *columnExpr = e;
			VariableExpr* aliasVariable = 0;
			if (e->exprClass() == KexiDBExpr_SpecialBinary && e->toBinary()
				&& (e->token()==AS || e->token()==0))
			{
				//KexiDBExpr_SpecialBinary: with alias
				columnExpr = e->toBinary()->left();
	//			isFieldWithAlias = true;
				aliasVariable = e->toBinary()->right()->toVariable();
				if (!aliasVariable) {
					setError(i18n("Invalid alias definition for column \"%1\"")
						.arg(columnExpr->toString())); //ok?
					CLEANUP;
					return 0;
				}
			}
	
			const int c = columnExpr->exprClass();
			const bool isExpressionField = 
					c == KexiDBExpr_Const
				|| c == KexiDBExpr_Unary
				|| c == KexiDBExpr_Arithm
				|| c == KexiDBExpr_Logical
				|| c == KexiDBExpr_Relational
				|| c == KexiDBExpr_Const
				|| c == KexiDBExpr_Function
				|| c == KexiDBExpr_Aggregation;
	
			if (c == KexiDBExpr_Variable) {
				//just a variable, do nothing, addColumn() will handle this
			}
			else if (isExpressionField) {
				//expression object will be reused, take, will be owned, do not destroy
//		KexiDBDbg << colViews->list.count() << " " << it.current()->debugString() << endl;
				colViews->list.take(); //take() doesn't work
				moveNext = false;
			}
			else if (aliasVariable) {
				//take first (left) argument of the special binary expr, will be owned, do not destroy
				e->toBinary()->m_larg = 0;
			}
			else {
				setError(i18n("Invalid \"%1\" column definition").arg(e->toString())); //ok?
				CLEANUP;
				return 0;
			}
	
			if (!addColumn( parseInfo, columnExpr )) {
				CLEANUP;
				return 0;
			}
			
			if (aliasVariable) {
//				KexiDBDbg << "ALIAS \"" << aliasVariable->name << "\" set for column " 
//					<< columnNum << endl;
				querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1());
			}
	/*		if (e->exprClass() == KexiDBExpr_SpecialBinary && dynamic_cast<BinaryExpr*>(e)
				&& (e->type()==AS || e->type()==0))
			{
				//also add alias
				VariableExpr* aliasVariable =
					dynamic_cast<VariableExpr*>(dynamic_cast<BinaryExpr*>(e)->right());
				if (!aliasVariable) {
					setError(i18n("Invalid column alias definition")); //ok?
					return 0;
				}
				kdDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column " 
					<< columnNum << endl;
				querySchema->setColumnAlias(columnNum, aliasVariable->name.latin1());
			}*/
	
			if (moveNext) {
				colViews->list.next();
//				++it;
			}
		}
	}
	//----- SELECT options
	if (options) {
		//----- WHERE expr.
		if (options->whereExpr) {
			if (!options->whereExpr->validate(parseInfo)) {
				setError(parseInfo.errMsg, parseInfo.errDescr);
				CLEANUP;
				return 0;
			}
			querySchema->setWhereExpression(options->whereExpr);
		}
		//----- ORDER BY
		if (options->orderByColumns) {
			OrderByColumnList &orderByColumnList = querySchema->orderByColumnList();
			OrderByColumnInternal::ListConstIterator it = options->orderByColumns->constEnd();
			uint count = options->orderByColumns->count();
			--it;
			for (;count>0; --it, --count) 
				/*opposite direction due to parser specifics*/
			{
				//first, try to find a column name or alias (outside of asterisks)
				QueryColumnInfo *columnInfo = querySchema->columnInfo( (*it).aliasOrName, false/*outside of asterisks*/ );
				if (columnInfo) {
					orderByColumnList.appendColumn( *columnInfo, (*it).ascending );
				}
				else {
					//failed, try to find a field name within all the tables
					if ((*it).columnNumber != -1) {
						if (!orderByColumnList.appendColumn( *querySchema,
							(*it).ascending, (*it).columnNumber-1 ))
						{
							setError(i18n("Could not define sorting - no column at position %1")
								.arg((*it).columnNumber));
							CLEANUP;
							return 0;
						}
					}
					else {
						Field * f = querySchema->findTableField((*it).aliasOrName);
						if (!f) {
							setError(i18n("Could not define sorting - "
								"column name or alias \"%1\" does not exist").arg((*it).aliasOrName));
							CLEANUP;
							return 0;
						}
						orderByColumnList.appendField( *f, (*it).ascending );
					}
				}
			}
		}
	}

//	KexiDBDbg << "Select ColViews=" << (colViews ? colViews->debugString() : TQString())
//		<< " Tables=" << (tablesList ? tablesList->debugString() : TQString()) << endl;
	
	CLEANUP;
	return querySchema;
}

#undef CLEANUP