/*
_____       _    _    Corso   Italia,  178
(_|__   .  (_   |_|_  56125           Pisa
(_|_) |)|(()_)()| |   tel.  +39  050 46380
  |   |               picosoft@picosoft.it

 Copyright (C) Picosoft s.r.l. 1995-2002

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

 This program 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 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
# include "where.h"
static char rcsid[] = "$Id: where.cpp 5.3 2000/08/22 14:54:33 picoSoft Exp $";
static char rcsidh[] = where_h;
# include "sqlpars.h"
# include "pstack.h" 
# include "pbool.h"
# include "piteratr.h"

class PBoolSStack : public PObject
{
   PBool * array;
   int numItems;
public:
   void Push (const PBool & s) { array[numItems++] = s; }
   PBool Pop () { return array[--numItems]; }
   int Size () {  return numItems; }
   PBoolSStack(int n) { numItems = 0;
                      if (n>0) array = new PBool[n];
                      else array = 0; }
   ~PBoolSStack() {  if (array) delete array; }
};

PCLASSID(Where)

PTSTACK(Token);

Where::Where (SqlParser *sp, Expression::Method m, PBool aggr) :
Parent(sp),
findAttr (m),
parseAggregate(aggr)
{
}

void 
Where::Clear ()
{
   Condition *c;
    
   for (c = Stack.GetFirst(); c != 0; c = Stack.DeleteCurrent())
      delete c;                                                  
   for (c = ValidCond.GetFirst(); c != 0; c = ValidCond.DeleteCurrent())
      ;
}


Error
Where::Parse (Lex &l, const Token *startToken)
{  
   PStack(Condition) localStack; 
   PStack(Token) openPar;
   Error Return = SQL_SUCCESS;
   Token *t;
   int nPar = 0;
   PBool notEmpty = Stack.GetItemNum() > 0;

   while (Return == SQL_SUCCESS && (t = l.GetToken ()) &&
                                   t->token != ORDER &&
                                   t->token != GROUP &&
                                   t->token != FOR &&
                                   t->token != UNION &&
                                   t->token != END_SUBQUERY &&
                                   !(t->token == ')' && openPar.Size()==0)) {
      switch (t->token) {
      case '(':
         t = l.GetToken();
         if (t && t->token == SELECT) {
            int nSubQuery = 1;
            t = l.UngetToken();
            t->token = START_SUBQUERY;
            while (nSubQuery > 0 && (t = l.GetToken ())) {
               switch (t->token) {
               case '(':
                  nSubQuery++;
                  break;
               case ')':
                  nSubQuery--;
                  if (nSubQuery == 0)
                     t->token = END_SUBQUERY;
                  break;
               }
            }
         } else {
            t = l.UngetToken();
            openPar.Push (t);
         }
         break;
      case ')':
         if (nPar == openPar.Size()) {
            if (nPar == 0) {
               Return = SQL_ERROR;
               Parent->PrintError (E_SYNTAX_ERROR_000, "Unexpected <)> in WHERE clause");
            } else {
               t->token = CLOSE_BOOL_PAR;
               t = openPar.Pop();
               t->token = OPEN_BOOL_PAR;
               nPar--;
            }
         } else {
            t = openPar.Pop();
            if (t == 0) {
               Return = SQL_ERROR;
               Parent->PrintError (E_SYNTAX_ERROR_000, "Unexpected <)> in WHERE clause");
            }
         }
         break;
      case AND:
      case OR:
      case '=': // i seguenti token debbono gli stessi CfrtOp
      case '>':
      case '<':
      case _GE_:
      case _LE_:
      case _NE_:
      case _OJ_:
      case LIKE:
      case IS:
      case IN_:
      case BETWEEN:
         nPar = openPar.Size();
         break;
      }
   }
   if (Return == SQL_SUCCESS) {
      while ((t = l.UngetToken ()) != startToken)
         ;
      Return = FirstTerm (l, localStack);
      if (Return == SQL_SUCCESS) {
         reduce (0, localStack);
         if ((t = l.GetToken ()) != 0 && t->token != ORDER &&
                                         t->token != GROUP &&
                                         t->token != FOR &&
                                         t->token != UNION &&
                                         t->token != ')' &&
                                         t->token != ';' &&
                                         t->token != END_SUBQUERY) {
            Return = SQL_ERROR;
            Parent->PrintError (E_SYNTAX_ERROR_000, "Unexpected token <" + t->word + "> in WHERE clause");
         } else {
            if (t)
               l.UngetToken();
            if (notEmpty)
               Stack.AddItem (new Condition (AND, Parent));
         }
      }
      while (localStack.Size() > 0)
         delete localStack.Pop();
   }
   return Return;
}

void
Where::reduce (Token *lookAhead, PStack(Condition) &localStack)
{
   Condition *leftItem, *opItem, *rightItem;
 
   rightItem = localStack.Pop();
   while (rightItem != 0) {
      if (!rightItem->isAndOr()) {  // c'e' una condizione 
         opItem = localStack.Pop();
         if (opItem && opItem->isAndOr()) {  // c'e' un AND-OR
            if (lookAhead && 
                lookAhead->token == AND && opItem->getOperator() == OR) {
               localStack.Push (opItem);
               localStack.Push (rightItem);
               rightItem = 0;
            } else {
               leftItem = localStack.Pop();
               if (leftItem && !leftItem->isAndOr()) { // c'e' la seconda condizione
                  Stack.AddItem (leftItem);
                  Stack.AddItem (rightItem);
                  Stack.AddItem (opItem);
                  rightItem = localStack.Pop();
               } else {   // non c'e' la seconda condizione
                  if (leftItem != 0)
                     localStack.Push (leftItem);
                  if (Stack.GetItemNum() > 0) {
                     Stack.AddItem (rightItem);
                     Stack.AddItem (opItem);
                  } else {
                     Parent->PrintError (E_SYNTAX_ERROR_000, "Invalid production left");
                  }
                  rightItem = 0;
               }
            }
         } else {// c'e' solo una condizione
            if (opItem != 0)
               localStack.Push (opItem);
            if (lookAhead == 0 || lookAhead->token == CLOSE_BOOL_PAR)
               Stack.AddItem (rightItem);
            else
               localStack.Push (rightItem);
            rightItem = 0;
         }
      } else { // il primo termine non e' una condizione
         opItem = rightItem;
         rightItem = 0;
         if (Stack.GetItemNum() > 0) { // nella pila ci sono gia' elementi
            if (lookAhead && 
                lookAhead->token == AND && opItem->getOperator() == OR) {
               localStack.Push(opItem);
            } else {
               leftItem = localStack.Pop();
               if (leftItem && !leftItem->isAndOr()) { // c'e' la seconda condizione
                  Stack.AddItem (leftItem);
                  Stack.AddItem (opItem);
                  rightItem = localStack.Pop();
               } else {
                  Stack.AddItem (opItem);
                  rightItem = leftItem;
               }
            }
         } else {
            Parent->PrintError (E_SYNTAX_ERROR_000, "Invalid production right");
         }
      }
   }
}

Error
Where::FirstTerm (Lex &l, PStack(Condition) &localStack)
{
   Error Return;
   Expression *ex1;
   PBool isNot = PFalse;

   Token *t = l.GetToken ();
   if (t && t->token == NOT) {
      isNot = !isNot;
      t = l.GetToken();
   }   
   if (t) {
      switch (t->token) {
      case OPEN_BOOL_PAR:
         {
            PStack(Condition) llStack;

            Return = FirstTerm (l, llStack);
            if (Return == SQL_SUCCESS) {
               t = l.GetToken ();
               if (t == 0 || t->token != CLOSE_BOOL_PAR) {
                  Return = SQL_ERROR;
                  if (t)
                     Parent->PrintError (E_SYNTAX_ERROR_000, "Missing parenthesis before " + t->word);
                  else
                     Parent->PrintError (E_SYNTAX_ERROR_000, "Missing parenthesis before end");
               } else {
                  reduce(0, llStack);
                  if (isNot)
                     Complement ();
                  Return = BoolOp (l, localStack);
               }
            }
         }
         break;
      case EXISTS:
         t = l.GetToken ();
         if (t == 0 || t->token != START_SUBQUERY) {
            Return = SQL_ERROR;
            if (t)
               Parent->PrintError (E_SYNTAX_ERROR_000, "<(> expected, found <" + t->word + ">");
            else
               Parent->PrintError (E_SYNTAX_ERROR_000, "Empty statement after <EXISTS>");
         } else {
            t = l.GetToken ();
            if (t == 0 || t->token != SELECT) {
               Return = SQL_ERROR;
               if (t)
                  Parent->PrintError (E_SYNTAX_ERROR_000, "<SELECT> expected, found <" + t->word + ">");
               else
                  Parent->PrintError (E_SYNTAX_ERROR_000, "Empty statement after <EXISTS>");
            } else {
               PString cmd = t->word;
               while ((t = l.GetToken ()) && t->token != END_SUBQUERY)
                  if (t) {
                     cmd += ' ';
                     cmd += t->word;
                  }
               SqlParser *subQuery = new SqlParser (Parent->GetDB(), Parent);
               Return = subQuery->Parse (cmd.gets(), cmd.size());
               if (Return == SQL_SUCCESS) {
                  Condition *c;
                  if (isNot)
                     c = new Condition (NOT_EXISTS, Parent, subQuery);
                  else
                     c = new Condition (EXISTS, Parent, subQuery);
                  localStack.Push (c);
                  Return = BoolOp (l, localStack);
               } else
                  delete subQuery;
            }
         }
         break;
      default:
         l.UngetToken();
         ex1 = new Expression (*Parent, l, findAttr, parseAggregate);
         if (ex1->isOk()) {
            Return = CfrtOp (l, localStack, ex1, isNot);
         } else {
            delete ex1;
            Return = SQL_ERROR;
         }
         break;
      }
   } else {
      Return = SQL_ERROR;
      Parent->PrintError (E_SYNTAX_ERROR_000, "Unaspected end of statement");
   }
   return Return;
}

Error
Where::CfrtOp (Lex &l, PStack(Condition) &localStack,
               Expression *ex1, PBool isNot)
{
   Error Return;
   Token *t = l.GetToken ();
   if (t && t->token == NOT) {
      isNot = !isNot;
      t = l.GetToken();
   }
   if (t) {
      switch (t->token) { // tutti i token debbono stare nella ricerca delle parentesi
      case '=':
      case '>':
      case '<':
      case _GE_:
      case _LE_:
      case _NE_:
      case _OJ_:
      case LIKE:
         if (SecondTerm (l, localStack, ex1, t->token, isNot) == SQL_SUCCESS)
            Return = BoolOp (l, localStack);
         else
            Return = SQL_ERROR;
         break;
      case IS:
         t = l.GetToken ();
         if (t && t->token == NOT) {
            isNot = !isNot;
            t = l.GetToken();
         }
         if (t == 0) {
            Parent->PrintError (E_SYNTAX_ERROR_000, "Unexpected end of statement after <IS>");
            delete ex1;
            Return = SQL_ERROR;
         } else {
            if (t->token != NULL_) {
               Parent->PrintError (E_SYNTAX_ERROR_000, "NULL expected, found <" + t->word + ">");
               delete ex1;
               Return = SQL_ERROR;
            } else {
               Condition *c = new Condition (IS_NULL, ex1, Parent);
               if (isNot)
                  c->Complement();
               localStack.Push (c);
               Return = BoolOp (l, localStack);
            }
         }
         break;
      case IN_:
         {
            Condition *c;
            Return = SQL_ERROR;
            t = l.GetToken();
            if (t && t->token == '(') {
               PStack(Condition) llStack;
               while (t) {
                  if (SecondTerm (l, llStack, new Expression (*ex1), (toknum)'=', isNot) == SQL_SUCCESS) {
                     t = l.GetToken ();
                     if (t) {
                        if (t->token == (toknum)',') {
                           c = new Condition (OR, Parent);
                           if (isNot)
                              c->Complement();
                           llStack.Push (c);
                        } else if (t->token == (toknum)')') {
                           t = 0;
                           reduce (0, llStack);
                           Return = BoolOp (l, localStack);
                        } else {
                           Parent->PrintError (E_SYNTAX_ERROR_000, "Expected <)>, found <" + t->word + ">");
                           t = 0;
                        }
                     } else {
                        Parent->PrintError (E_SYNTAX_ERROR_000, "Expected <)>, found <end statement>" );
                        t = 0;
                     }

                  } else {
                     t = 0;
                  }
               }
            } else {
               if (t)
                  Parent->PrintError (E_SYNTAX_ERROR_000, "Expected <(>, found <" + t->word + ">");
               else
                  Parent->PrintError (E_SYNTAX_ERROR_000, "Expected <(>, found <end statement>" );
            }
         }
         delete ex1;
         break;
      case BETWEEN:
         {
            Condition *c;
            if (SecondTerm (l, localStack, new Expression (*ex1), _GE_, isNot) == SQL_SUCCESS) {
               t = l.GetToken ();
               if (t == 0 || t->token != AND) {
                  Return = SQL_ERROR;
                  if (t)
                     Parent->PrintError (E_SYNTAX_ERROR_000, "Expected <AND>, found <" + t->word + ">");
                  else
                     Parent->PrintError (E_SYNTAX_ERROR_000, "Expected <AND>, found <end statement>" );
                  delete ex1;
               } else {
                  c = new Condition (AND, Parent);
                  if (isNot)
                     c->Complement();
                  localStack.Push (c);
                  if (SecondTerm (l, localStack, ex1, _LE_, isNot) == SQL_SUCCESS) {
                     Return = BoolOp (l, localStack);
                  } else {
                     Return = SQL_ERROR;
                  }
               }
            } else {
               Return = SQL_ERROR;
            }
         }
         break;
      default:
         Return = SQL_ERROR;
         Parent->PrintError (E_SYNTAX_ERROR_000, "Expected cfrt operator found <" + t->word + ">");
         delete ex1;
         break;
      }
   } else {
      Return = SQL_ERROR;
      Parent->PrintError (E_SYNTAX_ERROR_000, "Unaspected end of statement");
      delete ex1;
   }
   return Return;
}

Error
Where::SecondTerm (Lex &l, PStack(Condition) &localStack,
                   Expression *ex1, toknum op, PBool isNot)
{
   Error Return;
   Expression *ex2;
   Condition *c;

   ex2 = new Expression (*Parent, l, findAttr, parseAggregate);
   if (ex2->isOk()) {
      c  = new Condition (op, ex1, Parent);
      c->SetRight (ex2);
      if (isNot)
         c->Complement();
      localStack.Push (c);
      Return = SQL_SUCCESS;
   } else {
      delete ex1;
      delete ex2;
      Return = SQL_ERROR;
   }
   return Return;
}

Error
Where::BoolOp (Lex &l, PStack(Condition) &localStack)
{
   Error Return = SQL_SUCCESS;
   Token *t = l.GetToken();

   reduce (t, localStack);

   if (t) {
      switch (t->token) {
      case AND:
      case OR:
         localStack.Push(new Condition (t->token, Parent));
         Return = FirstTerm (l, localStack);
         break;
      default:
         l.UngetToken();
         break;
      }
   }
   return Return;
}

Error
Where::verifyExpression()
{
   Condition *c;
   int numeric1;
   int numeric2;

   for (c = Stack.GetFirst(); c != 0; c = Stack.GetNext())
      if (!c->isAndOr())
         if (c->left && c->left->verify(numeric1, PFalse) != SQL_SUCCESS)
            return SQL_ERROR;
         else if (c->right && c->right->verify(numeric2, PFalse) != SQL_SUCCESS)
            return SQL_ERROR;
         else if (c->subQuery && c->subQuery->ChecksBeforeExec() != SQL_SUCCESS)
            return SQL_ERROR;
/*
         else if (c->left && c->right && numeric1 != numeric2) {
            Parent->PrintError (E_DATA_EXCEPT_018, "Type mismatch in WHERE" );
            return SQL_ERROR;
         }
*/
   return SQL_SUCCESS;
}

void
Where::Complement ()
{
   int arity;
   Condition *c;

   for (arity = 1, c = Stack.GetLast(); arity > 0 && c != 0;
                   c = Stack.GetPrevious()) {
      c->Complement ();
      if (c->getOperator() == OR || c->getOperator() == AND)
         arity ++;
      else
         arity --;
   }
}

Error
Where::Eval (StmtRel *sr, PBool &result)
{
   Error Return = SQL_SUCCESS;
   Condition *c;
   PBool b1, b2;

   if ((c = Stack.GetFirst()) == 0) {
      result = PTrue;
      return Return;
   }
   PBoolSStack rpnStack ((int)Stack.GetItemNum());
   for ( ; c != 0; c = Stack.GetNext())
      switch (c->getOperator()) {
      case AND:
         if (rpnStack.Size() < 2) {
            Parent->PrintError (E_UNKNOWN_ERROR, "Corrupted RPN stack (i)");
            return SQL_ERROR;
         }
         b1 = rpnStack.Pop();
         b2 = rpnStack.Pop();
         rpnStack.Push (b1 && b2 ? PTrue : PFalse);
         break;
      case OR:
         if (rpnStack.Size() < 2) {
            Parent->PrintError (E_UNKNOWN_ERROR, "Corrupted RPN stack (ii)");
            return SQL_ERROR;
         }
         b1 = rpnStack.Pop();
         b2 = rpnStack.Pop();
         rpnStack.Push (b1 || b2 ? PTrue : PFalse);
         break;
      case NOT:
         if (rpnStack.Size() < 1) {
            Parent->PrintError (E_UNKNOWN_ERROR, "Corrupted RPN stack (iii)");
            return SQL_ERROR;
         }
         b1 = rpnStack.Pop();
         rpnStack.Push (!b1 ? PTrue : PFalse);
         break;
      default:
         if (c->Eval(sr, b1) == SQL_SUCCESS)
            rpnStack.Push (b1);
         else
            return SQL_ERROR;
         break;
      }

   if (rpnStack.Size() == 0)
      result = PTrue;
   else if (rpnStack.Size() != 1) {
      Parent->PrintError (E_UNKNOWN_ERROR, "Corrupted RPN stack (iv)");
      Return = SQL_ERROR;
   } else
      result = rpnStack.Pop();
   return Return;
}


void
Where::toString (PString &result)
{
   PStack(PString) ss;
   Condition *c;
   PString *s1, *s2;

   for (c = Stack.GetFirst(); c != 0; c = Stack.GetNext())
      switch (c->getOperator()) {
      case AND:
         s1 = ss.Pop();
         s2 = ss.Pop();
         ss.Push (new PString (*s2 + " AND " + *s1));
         delete s1;
         delete s2;
         break;
      case OR:
         s1 = ss.Pop();
         s2 = ss.Pop();
         ss.Push (new PString ("(" + *s2 + " OR " + *s1 + ")"));
         delete s1;
         delete s2;
         break;
      case NOT:
         s1 = ss.Pop();
         *s1 = " NOT " + *s1;
         ss.Push(s1);
         break;
      default:
         s1 = new PString;
         c->toString(*s1);
         ss.Push(s1);
         break;
      }
   s1 = ss.Pop();
   if (s1) {
      result += *s1;
      delete s1;
   }
}
