// 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                                            

// C++ includes
#include <iostream>
#include <fstream>
#include <set>
using namespace std;
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _MSC_VER
#include <io.h>
#else
#include <unistd.h> // for access()!
#endif // _MSC_VER

// AspectC++ includes
#include "ACWeaver.h"
#include "ACConfig.h"
#include "Transformer.h"
#include "ACUnit.h"
#include "IncludeExpander.h"
#include "Repository.h"
#include "Naming.h"
#include "NamespaceAC.h"

// PUMA includes
#include "Puma/CProject.h"
#include "Puma/ErrorStream.h"
#include "Puma/FileUnit.h"
#include "Puma/CScanner.h"
#include "Puma/VerboseMgr.h"
#include "Puma/PreParser.h"
#include "Puma/CPrintVisitor.h"
#include "Puma/SysCall.h"

// Some macro-like functions that define the application name and version
#include "version.h"


ACWeaver::ACWeaver (CProject& project, ACConfig &conf) :
  _project (project), _conf (conf),
  _line_mgr (project.err (), _conf)
 {
   // fill the unit with the declaration of the predefined pointcuts
   _pointcut_defs << "#ifndef __ac_have_predefined__" << endl;
   _pointcut_defs << "#define __ac_have_predefined__" << endl;
   _pointcut_defs << "/*** begin of predefined pointcuts ***/" << endl;
   _pointcut_defs << "pointcut execution(...) = 1;" << endl;
   _pointcut_defs << "pointcut call(...) = 1;" << endl;
   _pointcut_defs << "pointcut construction(...) = 1;" << endl;
   _pointcut_defs << "pointcut destruction(...) = 1;" << endl;
   _pointcut_defs << "pointcut that(...) = 1;" << endl;
   _pointcut_defs << "pointcut target(...) = 1;" << endl;
   _pointcut_defs << "pointcut args(...) = 1;" << endl;
   _pointcut_defs << "pointcut result(...) = 1;" << endl;
   _pointcut_defs << "pointcut cflow(...) = 1;" << endl;
   _pointcut_defs << "pointcut classes(...) = 1;" << endl;
   _pointcut_defs << "pointcut base(...) = 1;" << endl;
   _pointcut_defs << "pointcut derived(...) = 1;" << endl;
   _pointcut_defs << "pointcut within(...) = 1;" << endl;
   _pointcut_defs << "/*** end of predefined pointcuts ***/" << endl;

   _pointcut_defs << "class JoinPoint {" << endl;
   _pointcut_defs << " public:" << endl;
   _pointcut_defs << "  typedef __unknown_t That;" << endl;
   _pointcut_defs << "  typedef __unknown_t Target;" << endl;
   _pointcut_defs << "  typedef __unknown_t Result;" << endl;
   _pointcut_defs << "  enum { ARGS, JPID, JPTYPE };" << endl;
   _pointcut_defs << "  template <int> struct Arg {" << endl;
   _pointcut_defs << "    typedef __unknown_t Type;" << endl;
   _pointcut_defs << "    typedef __unknown_t ReferredType;" << endl;
   _pointcut_defs << "  };" << endl;
   _pointcut_defs << "  typedef char* Type;" << endl;   
   _pointcut_defs << "  static const char *signature ();" << endl;
   _pointcut_defs << "  static int args ();" << endl;
   _pointcut_defs << "  void *arg (int);" << endl;
   _pointcut_defs << "  template <int> __unknown_t *arg ();" << endl;
   _pointcut_defs << "  static const Type argtype(int);" << endl;
   _pointcut_defs << "  static const Type type();" << endl;
   _pointcut_defs << "  static unsigned id();" << endl;
   _pointcut_defs << "  static const Type resulttype();" << endl;
   _pointcut_defs << "  Result *result();" << endl;
   _pointcut_defs << "  That *that();" << endl;
   _pointcut_defs << "  Target *target();" << endl;
   _pointcut_defs << "  static int jptype();" << endl;
   _pointcut_defs << "  AC::Action& action();" << endl;
   _pointcut_defs << "  void proceed();" << endl;
   _pointcut_defs << "} *thisJoinPoint, *tjp;" << endl;
   _pointcut_defs << "#endif" << endl;

   _pointcut_defs << ends;
 }


void ACWeaver::weave ()
 {
   VerboseMgr vm (cout);

   // analyze the command line arguments
   if (!_conf.analyze ()) {
     err () << sev_error;
     return;
   }

   // configure the log output manager
   vm.verbose (_conf.verbose ());

   // now some action ...
   vm << "Running " << ac_program () << " " << ac_version () << endvm;
   Repository repo;
   if (_conf.repository ()) {
#ifdef _MSC_VER
     if (!_access (_conf.repository (), 04)) {
#else
     if (!access (_conf.repository (), R_OK)) {
#endif // _MSC_VER
       vm << "Opening project repository '" << _conf.repository ()
	  << "'" << endvm;
       repo.open (_conf.repository (), err ());
     }
     else {
       vm << "Creating project repository '" << _conf.repository ()
	  << "'" << endvm;
       repo.create (_conf.repository ());
     }
   }
   // break in case of errors
   if (err ().severity () >= sev_error)
     return;

   bool header_changed = false;
   if (_conf.iterate ()) {
     vm << "Simple Dependency Check" << endvm;
     PathIterator dep_iter (".*\\.a?h$");
     vm++;
     while (_project.iterate (dep_iter))
       {
	 // workaround for PURE problem with fame include files:
	 if (strstr (dep_iter.file (), "fame"))
	   continue;
	 
	 if (_project.isNewer (dep_iter.file ()))
	   {
	     header_changed = true;
	     vm << "new or modified: " << dep_iter.file() << endvm;
	   }
       }
     vm--;
   }

   // set of manipulated (and saved) units
   set<Unit*> h_units;
   set<Unit*> cc_units;
     
   if (!_conf.iterate () && !_conf.ifiles () && _conf.file_in ()) {

     Transformer transformer (vm, err (), _project, repo, _conf, _line_mgr);

     // Transform a single translation unit
     Unit *unit = translate (vm, _conf.file_in (), transformer);

     // remember this unit
     cc_units.insert (unit);
     
     // break in case of errors
     if (err ().severity () >= sev_error)
       return;
   }
   else if (_conf.iterate ()) {

     // Transform all translation units in the project directory tree
     stringstream extpat;
     extpat << ".*\\." << _conf.extension () << "$" << ends;
     
     PathIterator iter (extpat.str ().data ());
     while (_project.iterate (iter)) {
       if (!(_project.isNewer (iter.file ()) || header_changed)) {
      	 continue;
       }
       
       // it seems that creating the transformer for every file is essential
       // to avoid very strange parse errors!
       Transformer transformer (vm, err (), _project, repo, _conf, _line_mgr);
       Unit *unit = translate (vm, iter.file (), transformer);
       
       // remember that we need this file
       cc_units.insert (unit);

       // discard changes in header files
       UnitManager::UMap &umap = _project.unitManager ().getTable ();
       for (UnitManager::UMapIter iter = umap.begin ();
            iter != umap.end (); ++iter) {
         Unit *curr = (*iter).second;
         if (cc_units.find (curr) == cc_units.end ())
           _project.close (curr->name (), true, false);
       }
       _project.unitManager ().removeNonames ();
       
       // break in case of errors
       if (err ().severity () >= sev_error)
         return;
     }
   }
   
   if (_conf.ifiles () || header_changed) {
     vm << "Handling include files" << endvm;
     vm++;
     
     Unit *unit = _project.addFile (_conf.file_in ());
     stringstream str;
     str << "// This file is generated by AspectC++ \n\n";
     PathIterator h_iter (".*\\.h$");
     str << "/*** begin of includes ***/" << endl;
     while (_project.iterate (h_iter))  {
      
       // workaround for PURE problem with fame include files:
   	   if (strstr (h_iter.file (), "fame"))
	       continue;
	 
       Filename incname = _project.getInclString (h_iter.file ());
	     str << "#include \"" << incname << "\"" << endl;
     }
     str << "/*** end of includes ***/" << endl;
     str << ends;
     
     CScanner scanner (err ());
     scanner.fill_unit (str.str ().data (), *unit); 
     
     Transformer transformer (vm, err (), _project, repo, _conf, _line_mgr);

     translate (vm, _conf.file_in (), transformer);

     // discard the generated translation unit
     _project.close (_conf.file_in (), true, true);

     // add header files to the list of manipulated units
     UnitManager::UMap &umap = _project.unitManager ().getTable ();
     for (UnitManager::UMapIter iter = umap.begin ();
          iter != umap.end (); ++iter) {
       Unit *unit = (*iter).second;
       if (unit->isFile () && _project.isBelow (unit) &&
        cc_units.find (unit) == cc_units.end ())
         h_units.insert (unit);
     }

     vm--;
   }
   
   // break in case of errors
   if (err ().severity () >= sev_error)
      return;

   vm << "Inserting unit pro- and epilogues" << endvm;
   insert_aspect_includes (vm, cc_units, h_units);
   
   vm << "Updating #line directives of generated code fragments" << endvm;
   update_line_directives (cc_units, h_units);
   
   if (_conf.nosave ()) {
     vm << "Don't save" << endvm;
   }
   else {
     vm << "Saving" << endvm;
     vm++;

     if (_project.numPaths () > 0 && _project.dest (0L)) {
       vm << "Project tree" << endvm;

       // discard the generated cc file if only headers should be produced
       if ((_conf.ifiles ()))
         _project.close (_conf.file_in (), true);
       
       _project.save ();
     }
     
     if (_conf.file_out ()) {

       // expand project includes
       vm << "Expanding project includes" << endvm;
       IncludeExpander ie (err (), _project, _line_mgr);
       ie.expand (_conf.file_in ());

       // update generated #line <NUM> "<ac...> directives for debuggers
       vm << "Fixing #line directives" << endvm;
       update_line_directives (&(ie.unit ()), _conf.file_out ());
       
       // now save
       vm << "Path \"" << _conf.file_out () << "\"" << endvm;
       ofstream out (_conf.file_out (), ios::out|ios::binary);
       if (out.is_open ())
         out << ie.unit ();
       else
         err () << sev_error << "can't open file \"" 
                << _conf.file_out () << "\"" << endMessage;
     }
     
     if (repo.initialized ()) {
       vm << "Saving project repository" << endvm;
       repo.save (err ());
     }

     vm--;
   }
   vm << "Done" << endvm;
 }


// update #line directives in all project files
void ACWeaver::update_line_directives (set<Unit*> &cc_units,
  set<Unit*> &h_units) {
  for (set<Unit*>::iterator iter = cc_units.begin ();
       iter != cc_units.end (); ++iter) {
    ostringstream out;
    if (_project.getDestinationPath ((*iter)->name (), out)) {
      out << ends;
      update_line_directives (*iter, out.str ().c_str ());
    }
  }
  for (set<Unit*>::iterator iter = h_units.begin ();
       iter != h_units.end (); ++iter) {
    ostringstream out;
    if (_project.getDestinationPath ((*iter)->name (), out)) {
      out << ends;
      update_line_directives (*iter, out.str ().c_str ());
    }
  }
}

// insert a pro- and epilogue into all saved units to make sure that in
// any case the relevant aspect headers will be defined
void ACWeaver::insert_aspect_includes (VerboseMgr &vm, set<Unit*> &cc_units,
  set<Unit*> &h_units) {
  vm++;
  for (set<Unit*>::iterator iter = cc_units.begin ();
       iter != cc_units.end (); ++iter) {
    vm << "Manipulating translation unit file " << (*iter)->name () << endvm;
    insert_aspect_includes (vm, *iter, false);
  }
  for (set<Unit*>::iterator iter = h_units.begin ();
       iter != h_units.end (); ++iter) {
    vm << "Manipulating header file " << (*iter)->name () << endvm;
    insert_aspect_includes (vm, *iter, true);
  }
  vm--;
}
       
void ACWeaver::insert_aspect_includes (VerboseMgr &vm, Unit* u, bool header) {

  assert (u->isFile ());
  FileUnit *unit = (FileUnit*)u;
  
  ListElement *file_first = (ListElement*)unit->first ();
  ListElement *file_last  = (ListElement*)unit->last ();
  if (!file_first) {
    // file is empty
    vm++; vm << "File is empty" << endvm; vm--;
    return;
  }
  assert (file_last);
 
  // create the file prologue
  ACUnit prologue (err ());
  
  // determine the name of the ac_FIRST... macro
  string first_macro = "__ac_FIRST_";
  first_macro += _conf.project_id ();
  
  // generate the preprocessor directives
  prologue << "#ifndef " << first_macro.c_str () << endl;
  prologue << "#define " << first_macro.c_str () << endl;
  prologue << "#define __ac_FIRST_FILE_";
  Naming::mangle_file (prologue, unit);
  prologue << endl;
  // insert AC only in header files, in cc files is has already been done
  if (header)
    prologue << NamespaceAC::def (_conf.size_type ());
  prologue << "#endif // " << first_macro.c_str () << endl;
  prologue << endu;
  unit->move_before (file_first, prologue);
  // insert a #line directive at this point
  _line_mgr.insert (unit, (Token*)file_first);

  ACUnit epilogue (err ());
  epilogue.name ("<ac-epilogue>");
  epilogue << endl << "#ifdef __ac_FIRST_FILE_";
  Naming::mangle_file (epilogue, unit);
  epilogue << endl;
  aspect_includes (epilogue, true); // generate the aspect includes
  epilogue << "#undef " << first_macro.c_str () << endl;
  epilogue << "#undef __ac_FIRST_FILE_";
  Naming::mangle_file (epilogue, unit);
  epilogue << endl;
  epilogue << "#endif // __ac_FIRST_FILE_";
  Naming::mangle_file (epilogue, unit);
  epilogue << endl;
  epilogue << endu;
  // insert a #line directive at this point
  _line_mgr.insert (&epilogue, (Token*)epilogue.first ());

  unit->move (file_last, epilogue);
}


// transform all #line <NUM> "<ac.." directives into
// #line <REAL-NUM> "<TARGET-FILENAME>". This is necessary for
// debuggers to find generated code.
void ACWeaver::update_line_directives (Unit *unit,
                                       const char *filename) {
  int line = 1;
  bool in_dir = false;
  int have_line = 0;
  Token *start = 0;
  Token *end = 0;
  for (Token *token = (Token*)unit->first (); token;
       token = (Token*)unit->next (token)) {
    if (token->is_directive ()) {
      if (!in_dir) {
        in_dir = true;
        start = token;
      }
      if (strncmp ("\"<ac", token->text (), 4) == 0 ||
          strcmp ("\"intro\"", token->text ()) == 0)
        have_line = line;
    }
    else if (in_dir) {
      in_dir = false;
      if (have_line != 0) {
        end = (Token*)unit->prev (unit->prev (token));
        CUnit new_dir (err ());
        new_dir << "#line " << (have_line + 1) << " \"" << filename
                << "\"" << endu;
        assert (start && end);
        unit->move_before (start, new_dir);
        unit->kill (start, end);
        have_line = 0;
      }
    }
    line += token->line_breaks ();
  }
}


// Create includes for the aspect header files that should be regarded
// in this translation unit
void ACWeaver::aspect_includes (ostream &includes, bool conditional) {

  includes << "/*** begin of aspect includes ***/" << endl;

  if (_conf.iterate_aspects ()) {
    // collect the names of aspect header files,
    // generate a unit with include statements for these files,
    PathIterator ah_iter (".*\\.ah$");
    while (_project.iterate (ah_iter))
      aspect_include (includes, ah_iter.file (), conditional);
  }
  else {
    // Get the names from the configuration object (-a options)
    for (int i = 0; i < _conf.aspect_headers (); i++)
      aspect_include (includes, _conf.aspect_header (i), conditional);
  }
  includes << "/*** end of aspect includes ***/" << endl;
}

// Create an include directive for one aspect header file that should be
// considered in this translation unit
void ACWeaver::aspect_include (ostream &includes, const char *name,
                               bool conditional) {

  Filename incname = _project.getInclString (name);
  if (conditional) {
    Unit *unit = _project.unitManager ().getUnit (name, true);
    assert (unit && unit->isFile ());
    includes << "#if !defined(__ac_have_";
    Naming::mangle_file (includes, (FileUnit*)unit);
    includes << ") && defined(__ac_need_";
    Naming::mangle_file (includes, (FileUnit*)unit);
    includes << ")" << endl;
    includes << "#define __ac_have_";
    Naming::mangle_file (includes, (FileUnit*)unit);
    includes << endl;
  }
  includes << "#include \"" << incname << "\"" << endl;
  if (conditional)
    includes << "#endif" << endl;
}

Unit *ACWeaver::translate (VerboseMgr &vm, const char *file, 
			  Transformer &transformer)
 {
   vm << "Handling Translation Unit `";
   char *fname = strrchr (file, (int)'/');
   vm << (fname ? ++fname : file) << "'." << endvm;
   vm++;
   vm << "Path \"" << file << "\"" << endvm;

   Unit *unit = _project.scanFile (file);
   if (!unit)
    {
      err () << sev_error << "can't scan file \"" << file << "\""
	     << endMessage;
      return 0;
    }
   ListElement *file_first = (ListElement*)unit->first ();
   ListElement *file_last  = (ListElement*)unit->last ();
   if (!file_first) {
      // file is empty
      vm << "file is empty" << endvm;
      vm--;
      return unit;
   }
   assert (file_last);
   
   ACUnit global_includes (err ());
   aspect_includes (global_includes, false);
   global_includes << endu;
   ListElement *inc_first = (ListElement*)global_includes.first ();
   ListElement *inc_last  = (ListElement*)global_includes.last ();

   unit->move (file_last, global_includes);

   ACUnit global_pointcuts (err ());
   global_pointcuts << _pointcut_defs.str () << endu;
   ListElement *pct_first = (ListElement*)global_pointcuts.first ();
   ListElement *pct_last  = (ListElement*)global_pointcuts.last ();

   unit->move_before (file_first, global_pointcuts);
   
   // insert a #line directive at this point
   _line_mgr.insert (unit, (Token*)file_first);

   transformer.work (unit, (Token*)file_first, (Token*)file_last);

   unit->kill (pct_first, pct_last);
   unit->kill (inc_first, inc_last);

   vm--;
   
   return unit; // return the created unit
 }

