// 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 "JoinPointPlan.h"
#include "ACUnit.h"
#include "IntroductionInfo.h"
#include "IntroductionUnit.h"
#include "AspectInfo.h"
#include "WeaverBase.h"
#include "LineDirectiveMgr.h"

#include "Puma/CProtection.h"
#include "Puma/ErrorSink.h"
#include "Puma/ManipCommander.h"
#include "Puma/CFunctionInfo.h"
#include "Puma/CArgumentInfo.h"

// issue an advice in the plan for this code join point
void JPP_Code::issue (int index) {
  AdviceInfo *ai = _unsorted[index];
  const Condition &cond = *_conds[index];
  switch (ai->type()) {
  case ADVICE_BEFORE:
    _jpa.do_before (ai, cond);
    break;
  case ADVICE_AROUND:
    _jpa.do_around (ai, cond);
    break;
  case ADVICE_AFTER:
    _jpa.do_after (ai, cond);
    break;
  }
}

// consider an advice in the plan for this code join point
bool JPP_Code::consider (AdviceInfo *ai, const Condition &cond) {
  _unsorted.append (ai);
  _conds.append (&cond);
  return true;
}

bool JPP_Code::check(ErrorSink &err) {
  if (!plan ()) {
    err << sev_error << "order directive not resolveable";
    return false;
  }
  for (int i = 0; i < items (); i++) {
    ACAspectInfo* ai = &item (i);
    for (int j = 0; j < _unsorted.length(); j++) {
      AdviceInfo* a = _unsorted[j];
      if (a && (a->aspect()->acnode() == ai)) {
        issue(j);
        _unsorted[j] = 0;
      }
    }
  }
  for (int j = 0; j < _unsorted.length(); j++)
    if (_unsorted[j] != 0) {
      issue(j);
    }
  return true;
}

// issue an introduction in the plan for this class join point
void JPP_Class::issue (IntroductionInfo *ii) {
  switch (ii->type ()) {
  case IntroductionInfo::INTRO_BASECLASS:
    _base.append (ii);
    break;
  case IntroductionInfo::INTRO_OTHER:
    _other.append (ii);
    break;
  case IntroductionInfo::INTRO_SLICE_REF: // slices might contribute new
  case IntroductionInfo::INTRO_SLICE_DECL: {// members and base classes
    CObjectInfo *obj = ii->object (0);
    assert (obj && obj->Tree ());
    assert (obj->Tree ()->NodeName () == CT_ClassSliceDecl::NodeId ());
    CT_ClassSliceDecl *csd = (CT_ClassSliceDecl*)obj->Tree ();
    if (csd->base_clause ())
      _base.append (ii);
    if (csd->members () && csd->members ()->Entries ())
      _other.append (ii);
    }
    break;
  case IntroductionInfo::INTRO_INHERITED:
    break;
  case IntroductionInfo::INTRO_ERROR:
    break;
  }
}

// consider an introduction in the plan for this class join point
bool JPP_Class::consider (IntroductionInfo *ii) {
  switch (ii->type ()) {
  case IntroductionInfo::INTRO_BASECLASS:
  case IntroductionInfo::INTRO_INHERITED:
  case IntroductionInfo::INTRO_OTHER:
  case IntroductionInfo::INTRO_SLICE_REF:
  case IntroductionInfo::INTRO_SLICE_DECL:
    _unsorted.append (ii);
    return true;
  default:
    return false;
  }
}

bool JPP_Class::check(ErrorSink &err) {
  if (!plan ()) {
    err << sev_error << "order directive not resolveable";
    return false;
  }
  for (int i = 0; i < items (); i++) {
    ACAspectInfo* ai = &item (i);
    for (int j = 0; j < _unsorted.length(); j++) {
      if (_unsorted[j] && (_unsorted[j]->aspect()->acnode() == ai)) {
        issue(_unsorted[j]);
        _unsorted[j] = 0;
      }
    }
  }
  for (int j = 0; j < _unsorted.length(); j++)
    if (_unsorted[j] != 0)
      issue(_unsorted[j]);

  return true;
}

string JPP_Class::gen_baseclass_list () const {

  // do nothing if there are no base classes to add
  if (_base.length () == 0)
    return "";

  ostringstream result;
  // fill the unit with a baseclass list
  for (int i = 0; i < _base.length (); i++) {
    IntroductionInfo *ii = _base.lookup (i);
    
    // generate the code for this entry
    if (i > 0) result << ", ";

    if (ii->type () == IntroductionInfo::INTRO_SLICE_DECL ||
        ii->type () == IntroductionInfo::INTRO_SLICE_REF) {
      // slice-based base class intro
      CObjectInfo *obj = ii->object (0);
      assert (obj && obj->Tree ());
      assert (obj->Tree ()->NodeName () == CT_ClassSliceDecl::NodeId ());
      CT_ClassSliceDecl *csd = (CT_ClassSliceDecl*)obj->Tree ();
      CT_Intro *base = csd->base_clause ();
      for (int e = 1; e < base->Entries (); e++) // skip entry(0) -- the ':'
        result << base->Entry (e)->token ()->text () << " ";
    }
    else {
      // old-style base class intro with 'baseclass(virtual Bla)'
      // get all the needed information
      CProtection::Type prot = ii->object (0)->Protection ();
      bool virt = ii->object (0)->isVirtual ();
      CTypeInfo *type = ii->object (0)->FunctionInfo ()->
        Argument (0u)->TypeInfo ();
      
      if (virt) result << "virtual ";
      switch (prot) {
        case CProtection::PROT_PRIVATE:   result << "private "; break;
        case CProtection::PROT_PROTECTED: result << "protected "; break;
        case CProtection::PROT_PUBLIC:    result << "public "; break;
        default:
          assert (false);
      }
      result << *type;
    }
  }
  result << ends;
  return result.str ();
}

Unit *JPP_Class::gen_intro (ErrorStream &err, CStructure *target,
  LineDirectiveMgr &lmgr) const {

  // create the new unit
  IntroductionUnit *unit = 
    new IntroductionUnit ((Unit*)target->Tree ()->token ()->belonging_to ());

  // create a unit with the target class name
  ACUnit target_name (err);
  target_name << target->Name () << endu;
  // ... some unit for formatting
  ACUnit ws (err);
  ws << " " << endu;
    
  // at least on token is needed in the unit for 'move' (see below)
  unit->append (*((Token*)ws.first ())->duplicate ());

  // handle all intros
  for (int i = 0; i < otherIntros (); i++) {
    IntroductionInfo *ii = otherIntro (i);
    
    // ignore non-inline introductions here (for now)
    if (ii->prot () == CProtection::PROT_NONE)
      continue;

    // TODO: member intros from slices are ignored here!
    if (ii->type () == IntroductionInfo::INTRO_SLICE_DECL ||
        ii->type () == IntroductionInfo::INTRO_SLICE_REF) {
      CObjectInfo *obj = ii->object (0);
      assert (obj && obj->Tree ());
      assert (obj->Tree ()->NodeName () == CT_ClassSliceDecl::NodeId ());
      CT_ClassSliceDecl *csd = (CT_ClassSliceDecl*)obj->Tree ();
      // add "private:\n"
      ACUnit slice_start (err);
      if (ii->prot () == CProtection::PROT_PRIVATE)
        slice_start << "  private:" << endl;
      else
        slice_start << "  public:" << endl;
      // add "  typedef <target-name> <slice-name>;\n"
      if (!obj->isAnonymous ()) {
        slice_start << "  typedef " << target->Name () << " "
                    << obj->Name () << ";" << endl;
      }
      slice_start << endu;
      unit->move ((Token*)unit->last (), slice_start);
      // generate the intro itself from the stored pattern
      Token *curr = (Token*)ii->pattern ().first ();
      Location loc = curr->location ();
      ACUnit dir (err);
      lmgr.directive (dir, (Unit*)&ii->pattern (), curr);
      dir << endu;
      if (!dir.empty ())
        unit->move ((Token*)unit->last (), dir);
      for (int m = 0; m < csd->members ()->Entries (); m++) {
        Unit tmp_pattern;
        tmp_pattern.name (ii->pattern ().name ());
        CT_Intro *intro = (CT_Intro*)csd->members ()->Entry (m);
        for (int i = 0; i < intro->Entries (); i++) {
          tmp_pattern.append (*curr->duplicate ());
          curr = (Token*)ii->pattern ().next (curr);
        }
        instantiate_intro (err, unit, intro, tmp_pattern, target_name, loc);
      }
    }
    else {
      // add privat/public/protected
      ACUnit prot (err);
      prot << WeaverBase::protection_string (ii->prot ()) << "  " << endu;
      unit->move ((Token*)unit->last (), prot);
      
      // obtain the pattern for this intro
      const Unit &pattern = ii->pattern ();
  
      CT_Intro *intro = (CT_Intro*)ii->tree ()->Decl ();
      Token *curr = (Token*)ii->pattern ().first ();
      ACUnit dir (err);
      lmgr.directive (dir, (Unit*)&pattern, curr);
      dir << endu;
      if (!dir.empty ())
        unit->move ((Token*)unit->last (), dir);
      Location loc = ((Token*)pattern.first ())->location ();
      instantiate_intro (err, unit, intro, pattern, target_name, loc);
    }
  }
  return unit;
}

void JPP_Class::instantiate_intro (ErrorStream &err, Unit *unit,
  CT_Intro *intro, const Unit &pattern, Unit &target_name,
  Location &loc) const {
    
  // ... some units for formatting
  ACUnit nl (err);
  nl << endl << endu;
  ACUnit ws (err);
  ws << " " << endu;
  
  int i = 0, e = 0;
  // create a formatted unit and replace the advice name with the target name
  for (Token *curr = (Token*)pattern.first (); curr;
       curr = (Token*)pattern.next (curr), e++) {
    Token *tok = curr;
    if (curr->location ().line () != loc.line ()) {
      for (int l = loc.line (); l < curr->location ().line (); l++)
        unit->append (*((Token*)nl.first ())->duplicate ());
      loc = curr->location ();
    }
    else
      unit->append (*((Token*)ws.first ())->duplicate ());
    if (i < intro->NameIndices () && e == intro->NameIndex (i)) {
      // target class name is used instead of intro token
      tok = (Token*)target_name.first ();
      i++;
    }
    unit->append (*tok->duplicate ());
  }
}
