// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2003  The 'ac++' developers (see aspectc.org)
//                                                                
// 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 of 
// the License, 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., 59 Temple Place, Suite 330, Boston, 
// MA  02111-1307  USA                                            

#include <set>
using namespace std;

#include "PointCutEvaluator.h"
#include "PointCutExpr.h"
#include "PointCutContext.h"
#include "Binding.h"
#include "Naming.h"

#include "Puma/ACClassDatabase.h"
#include "Puma/ACPointcutInfo.h"
#include "Puma/ACAspectInfo.h"
#include "Puma/ErrorSink.h"
#include "Puma/ErrorSeverity.h"
#include "Puma/CObjectInfo.h"
#include "Puma/CClassInfo.h"
#include "Puma/CFunctionInfo.h"
#include "Puma/CArgumentInfo.h"
#include "Puma/CTypeInfo.h"
#include "Puma/Filter.h"
#include "Puma/CTree.h"

void PointCutEvaluator::work (PointCut &pc, Binding &binding, CTree *pc_node,
			      JoinPointLoc::join_point_type expected) {

  // create an abstract syntax tree of the expression
  PointCutExpr *expr = create (pc_node, expected);

  // if there was an error, return immediately
  if (!expr)
    return;
  
  // if there was no error, check each join point if it belong to the pointcut

  // filter the joinpoints: only expected joinpoints are considered
  JoinPointLocList::Selection all;
  _context.world ().select (expected, all);
    
  bool have_binding = false;
  for (JoinPointLocList::Selection::iterator iter = all.begin ();
       iter != all.end (); ++iter) {
    JoinPointLoc &jpl = **iter;
    Binding this_binding;
    Condition condition;
    _context.pseudo_true (false);
    if (expr->evaluate (jpl, _context, this_binding, condition)) {
      pc.append (*new JoinPoint (&jpl, condition));
      if (jpl.type () == JoinPointLoc::MethodCall &&
          ((JPL_MethodCall&)jpl).is_pseudo ())
        continue;
      // check if the context variable binding is the same for all non-pseudo
      // join points
      if (!have_binding) {
        binding = this_binding;
        have_binding = true;
      }
      else if (binding != this_binding) {
        _err << sev_error 
             << pc_node->token ()->location ()
             << "incompatible argument bindings in pointcut expression" 
             << endMessage;
        return;
      }
    }
  }
  
  // copy the cflow trigger pointcut from the expressions to the pointcut
  for (set<PointCutExpr*>::const_iterator iter = _context.cflows ().begin ();
       iter != _context.cflows ().end (); ++iter) {
    pc.cflow_triggers(((PCE_CFlow*)*iter)->arg_pointcut ());
  }
  _context.cflow_reset ();
  
  // finally set the pointcut type before destroy the expression(!)
  pc.type (expr->type() == PCE_CODE ? PointCut::PCT_CODE : PointCut::PCT_CLASS);
  
  // destroy the syntax tree
  destroy (expr);

//  cout << "PC:" << endl << pc;
}


PointCutExpr *PointCutEvaluator::create (CTree *pc_node,
  JoinPointLoc::join_point_type expected) {
    
  // transform the parser syntax tree into a real pointcat expression
  PointCutExpr *expr = create_tree (pc_node);
  if (_context.func ())
    expr = new PCE_Named (_context.func (), expr);
  
  // do a semantic analysis / error checking
  assert (expected & (JoinPointLoc::Name | JoinPointLoc::Code));
  expr->semantics (_err, _context);

  // check if the whole expression has the expected type
  bool names_allowed = (expected & JoinPointLoc::Name);
  bool code_allowed  = (expected & JoinPointLoc::Code);
  if ((expr->type() == PCE_NAME && !names_allowed) ||
      (expr->type() == PCE_CODE && !code_allowed)) {
    _err << sev_error << pc_node->token ()->location ()
         << "unexpected pointcut type '"
         << (expr->type() == PCE_CODE ? "code" : "name") << "'" << endMessage;
  }

  if (_err.severity () >= sev_error) {
    // destroy the syntax tree
    destroy (expr);
    expr = 0;
  }
  
  return expr;
}


void PointCutEvaluator::destroy (PointCutExpr * expr) {
  if (!expr) return;
  for (int i = 0; i < expr->args (); i++)
    destroy (expr->arg (i));
  delete expr;
}

PointCutExpr *PointCutEvaluator::create_tree (CTree *node) {
  PointCutExpr *result = (PointCutExpr*)0;;

  // skip braces and builtin cast nodes
  while (true) {
    if (node->NodeName () == CT_ImplicitCast::NodeId ())
      node = ((CT_ImplicitCast*)node)->Expr ();
    else if (node->NodeName () == CT_BracedExpr::NodeId ())
      node = ((CT_BracedExpr*)node)->Expr ();
    else
      break;
  }

  // now handle builtin pointcut functions and named pointcuts
  if (node->NodeName () == CT_CallExpr::NodeId ()) {

    CT_CallExpr *call_node = (CT_CallExpr*)node;
    CT_ExprList *args = call_node->Arguments ();
    CFunctionInfo *func = call_node->Object ()->FunctionInfo ();

    if (!func) {
      _err << sev_error << node->token ()->location ()
	   << "invalid pointcut" << endMessage;
      return (PointCutExpr*)0;
    }
    
    // check for builtin pointcut functions
    if (func->Scope ()->ScopeInfo()->GlobalScope()) {
      bool check_arg_count = true;
      const char *name = func->Name ();
      if (strcmp (name, "classes") == 0)
        result = new PCE_Classes (create_tree (args->Entry (0)));
      else if (strcmp (name, "base") == 0)
        result = new PCE_Base (create_tree (args->Entry (0)));
      else if (strcmp (name, "derived") == 0)
        result = new PCE_Derived (create_tree (args->Entry (0)));
      else if (strcmp (name, "within") == 0)
        result = new PCE_Within (create_tree (args->Entry (0)));
      else if (strcmp (name, "execution") == 0)
        result = new PCE_Execution (create_tree (args->Entry (0)));
      else if (strcmp (name, "call") == 0)
        result = new PCE_Call (create_tree (args->Entry (0)));
      else if (strcmp (name, "construction") == 0)
        result = new PCE_Construction (create_tree (args->Entry (0)));
      else if (strcmp (name, "destruction") == 0)
        result = new PCE_Destruction (create_tree (args->Entry (0)));
      else if (strcmp (name, "that") == 0)
        result = new PCE_That (create_tree (args->Entry (0)));
      else if (strcmp (name, "target") == 0)
        result = new PCE_Target (create_tree (args->Entry (0)));
      else if (strcmp (name, "cflow") == 0)
        result = new PCE_CFlow (create_tree (args->Entry (0)));
      else if (strcmp (name, "args") == 0) {
        PCE_Args *args_node = new PCE_Args;
        for (int i = 0; i < args->Entries (); i++)
          args_node->add_arg (create_tree (args->Entry (i)));
        result = args_node;
        check_arg_count = false;
      }
      else if (strcmp (name, "result") == 0)
        result = new PCE_Result (create_tree (args->Entry (0)));
      else {
        result = (PointCutExpr*)0; // no builtin pointcut function!
        check_arg_count = false;
      }

      if (check_arg_count && args->Entries () > 1) {
	_err << sev_error << node->token ()->location ()
	     << "pointcut function '" << name << "' has only one argument" 
	     << endMessage;
	return (PointCutExpr*)0;
      }
    }

    // handle user-defined (named) pointcuts
    if (!result) {

      // look up the real pointcut function (might be virtual!)
      if (!(func = _context.lookup_pointcut (func, _err, node)))
	return (PointCutExpr*)0;

      // create a node
      CFunctionInfo *save = _context.func (func); // needed?
      result = new PCE_Named (func, create_tree (func->Init ()->Entry (0)));
      _context.func (save);
    }
  }
  else if (node->NodeName () == CT_BinaryExpr::NodeId ()) {
    // check for binary operators
    CT_BinaryExpr *expr = (CT_BinaryExpr*)node;
    const char *oper = expr->Son (1)->token ()->text ();
    
    if (strcmp (oper, "||") == 0)
      result = new PCE_Or (create_tree (expr->Son (0)), create_tree (expr->Son(2)));
    else if (strcmp (oper, "&&") == 0)
      result = new PCE_And (create_tree (expr->Son (0)), create_tree (expr->Son(2)));
    else {
      _err << sev_error << node->token ()->location ()
	   << "invalid binary expression in pointcut" << endMessage;
      return (PointCutExpr*)0;
    }
  }
  else if (node->NodeName () == CT_UnaryExpr::NodeId ()) {
    // check for unary operators
    CT_UnaryExpr *expr = (CT_UnaryExpr*)node;
    const char *oper = expr->Son (0)->token ()->text ();
    
    if (strcmp (oper, "!") == 0)
      result = new PCE_Not (create_tree (expr->Son(1)));
    else {
      _err << sev_error << node->token ()->location ()
	   << "invalid unary expression in pointcut" << endMessage;
      return (PointCutExpr*)0;
    }
  }
  else if (node->NodeName () == CT_String::NodeId ()) {
    // this match be a match string
    result = new PCE_Match (((CT_String*)node)->Value ()->StrLiteral ()->
			    String ());
  }
  else if (node->NodeName () == CT_SimpleName::NodeId ()) {
    // this must be the name of a context variable
    result = new PCE_ContextVar (((CT_SimpleName*)node)->Object ()->Name ());
  }
  else {
    // hm, unknown node type -> must be an invalid expression
    _err << sev_error << node->token ()->location ()
	 << "invalid pointcut expression" << endMessage;
    return (PointCutExpr*)0;
  }

  result->node (node);

  return result;
}

