// 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>
#include <fcntl.h>
#ifdef _MSC_VER
#include <io.h>
#else
#include <unistd.h> // for access()!
#endif // _MSC_VER

// AspectC++ includes
#include "ACBase/Logger.h"
using namespace ACBase;

#include "Weaver.h"
#include "ACConfig.h"
#include "ACFileID.h"
#include "Transformer.h"
#include "IncludeExpander.h"
#include "ClangIncludeExpander.h"
#include "Naming.h"
#include "NamespaceAC.h"
#include "ACModel/XmlModelReader.h"
#include "PointCutExprParser.h"
#include "PointCutSearcher.h"
#include "PointCutContext.h"
#include "version.h"

Weaver::Weaver (ACProject& project, ACConfig &conf) :
  _project (project), _conf (conf),
  _line_mgr (err (), _conf)
 {
 }

// TODO: Why is this line directive handling here and not in LineDirectiveMgr?

// helper function for checking whether the current line is a #line directive
static bool is_line_dir (const char *start, const char *eof) {
  const char *i = start;
  // skip whitespace at the beginning of the line
  while (isspace (*i) && i < eof)
    i++;
  if (!(i < eof && *i == '#'))
    return false;
  // skip whitespace again
  while (isspace (*i) && i < eof)
    i++;
  if (isdigit(*i) || (strncmp(i+1, "line ", 5) == 0))
    return true;
  return false;
}

// Token line number handling works different in the clang configuration. If #line directives
// are not generated (as in the Puma configuration), the line numbers in the project model and
// the joinpoint-API are wrong, because Phase 1 transformations will affect the loctions.
// To avoid this effect, the --no_line option has to be implemented at the end of all
// transformations steps (here) by removing all #line directives from the code instead of
// not generating the in the first place.
string update_line_directives_clang (llvm::StringRef buf, const char *filename, LineDirectiveMgr &line_mgr) {
  std::ostringstream os;
  unsigned lineno = 1;
  bool start_of_line = true;
  for (const char *i = buf.begin(), *e = buf.end(); i < e; ++i) {
    // check for \s*#\s*(line|[0-9])
    if (start_of_line && is_line_dir (i, e)) {
      bool skip_line = false;
      if (line_mgr.noline ()) {
        skip_line = true;
      }
      else {
        const char *p = strchr(i, '#'); // note: 'i' remains were it was (beginning of line
        if (strncmp(p+1, "line ", 5) == 0) {
          p = i + 6;
          // Skip over the line number.
          while (isspace(*p) || isdigit(*p))
            ++p;

          // Replace <ac... and <intro... line directives with pointers to the final
          // file; "#line <built-in>" shall be removed from output
          if (strncmp (p, "\"<built-in>\"", 12) == 0)
            skip_line = true;
          else if (strncmp (p, "\"<ac", 3) == 0 || strncmp (p, "\"<intro", 7) == 0) {
            os << "#line " << (lineno + 1) << " \"" << filename << "\"";
            i = strchr(p, '\n');
          }
        }
      }
      if (skip_line) {
        // skip to the end of line
        while ((i < e) && (*i != '\n') && (*i != '\r'))
          i++;
        if (i == e || (i+1 == e))
          break;
        if ((*i == '\n' && *(i+1) == '\r') || (*i == '\r' && *(i+1) == '\n'))
          i++; // double line ending (windows/mac)
        continue; // go to the next line without incrementing the line number counter
      }
    }

    // Count lines.
    start_of_line = (*i == '\n' || *i == '\r');
    if (start_of_line) {
      ++lineno;
      // handle double line ending (windows/mac)
      if (((i+1) < e) && ((*i == '\n' && *(i+1) == '\r') || (*i == '\r' && *(i+1) == '\n'))) {
        os << *i;
        i++;
      }
    }
    os << *i;
  }

  return os.str();
}

void Weaver::weave ()
 {
   // configure the log output manager
   logger.verbosity(_conf.verbose ());

   // now some action ...
   logger << "Running " << ac_program () << " " << ac_version () << endlog;

   if (!_conf.expr().empty()) {
     match_expr_in_repo ();
     return;
   }

   bool header_changed = false;
   if (_conf.iterate ()) {
     logger << "Simple Dependency Check" << endlog;
     ACBase::PathIterator dep_iter (".*\\.a?h$");
     logger++;
     while (_project.iterate (dep_iter)) {
	 
	     if (_project.is_newer (dep_iter.file ())) {
	       header_changed = true;
	       logger << "new or modified: " << dep_iter.file() << endlog;
	     }
     }
     logger--;
   }

   // set of manipulated (and saved) units
   set<ACFileID> h_units;
   set<ACFileID> cc_units;
   string aspect_includes;
   string aspect_fwd_decls;
        
   if (!_conf.iterate () && !_conf.ifiles () && !_conf.file_in ().empty()) {
     _project.create_compiler_instance (_conf);

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

     // Transform a single translation unit
     ACFileID fid = translate (_conf.file_in ().c_str(), transformer);

     // remember this unit
     if (fid.is_valid())
       cc_units.insert (fid);

     // remember the aspect includes and forward declarations
//     aspect_includes  = transformer.aspect_includes ();
     _aspect_include_clusters = transformer.aspect_include_clusters();
     aspect_fwd_decls = transformer.aspect_fwd_decls ();
           
     // 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 () << "$";
     
     bool first = true;
     ACBase::PathIterator iter (extpat.str ().data ());
     while (_project.iterate (iter)) {
       if (!(_project.is_newer (iter.file ()) || header_changed))
      	 continue;
       
       _project.create_compiler_instance (_conf);

       // it seems that creating the transformer for every file is essential
       // to avoid very strange parse errors!
       Transformer transformer (err (), _project, _conf, _line_mgr);
       ACFileID fid = translate (iter.file (), transformer);

       // remember that we need this file
       if (fid.is_valid())
         cc_units.insert (fid);

       // remember the aspect units for inclusion and their forward declarations
       if (first) {
         // aspect_includes  = transformer.aspect_includes ();
         _aspect_include_clusters = transformer.aspect_include_clusters();
         aspect_fwd_decls = transformer.aspect_fwd_decls ();
         first = false;
       }

       clang::SourceManager &SM = _project.get_compiler_instance ()->getSourceManager();
       for (clang::SourceManager::fileinfo_iterator fi = SM.fileinfo_begin(),
                                                    fe = SM.fileinfo_end();
            fi != fe; ++fi) {
         auto file_entry = fi->first;
         if (cc_units.find (ACFileID (file_entry)) == cc_units.end ()) {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
           SM.bypassFileContentsOverride (file_entry);
#elif CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
           SM.bypassFileContentsOverride (file_entry->getLastRef());
#else
           SM.bypassFileContentsOverride (*file_entry);
#endif
           _project.close (file_entry);
         }
       }
       
       // break in case of errors
       if (err ().severity () >= sev_error)
         return;
     }
   }
   
   if (_conf.ifiles () || header_changed) {
     logger << "Handling include files" << endlog;
     logger++;
     
     _project.create_compiler_instance (_conf);

     stringstream str;
     str << "// This file is generated by AspectC++ \n\n";
     ACBase::PathIterator h_iter (".*\\.h$");
     str << "#ifndef __ac_have_predefined_includes__\n"
            "/*** begin of includes ***/" << endl;
     while (_project.iterate (h_iter))  {
       auto incname = _project.include_path(_conf.file_in (), h_iter.file ());
       str << "#include \"" << incname << "\"" << endl;
     }
     str << "/*** end of includes ***/\n#endif" << endl;
     
     ACFileID vfid = _project.addVirtualFile(_conf.file_in (), str.str ());

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

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

     // remember the aspect units for inclusion and forward declarations
     // aspect_includes  = transformer.aspect_includes ();
     _aspect_include_clusters = transformer.aspect_include_clusters();
     aspect_fwd_decls = transformer.aspect_fwd_decls ();

     // add header files to the list of manipulated units
     h_units.clear ();
     for (set<ACFileID>::iterator tf_iter = transformer.touched_files ().begin ();
          tf_iter != transformer.touched_files ().end (); ++tf_iter) {
       if (ACFileID(*tf_iter) != vfid &&
           _project.is_below ((*tf_iter).name ()) &&
           cc_units.find (ACFileID (*tf_iter)) == cc_units.end ())
         h_units.insert (*tf_iter);
     }

     // discard the generated translation unit
     _project.removeVirtualFile(vfid);

     logger--;
   }
   
   if (!_conf.gen_deps_file().empty()) {
     logger << "Generating aspect dependency file '" << _conf.gen_deps_file() << "'" << endlog;
     _project.create_compiler_instance (_conf);
     ACFileID vfid = _project.addVirtualFile("ac-dummy.cpp", "/* to be ignored */\n");
     Transformer transformer (err (), _project, _conf, _line_mgr);
     translate ("ac-dummy.cpp", transformer, true);
     _project.removeVirtualFile(vfid);
     logger << "Saving dependency file" << endlog;
     ofstream dep_file(_conf.gen_deps_file());
     if (dep_file.fail()) {
        err() << sev_error << "Couldn't open aspect dependency file '" << _conf.gen_deps_file().c_str()
          << "'" << endMessage;
     }
     else {
       for (auto rule : transformer.deps()) {
         dep_file << "\"" << std::get<0>(rule) << "\" => \"" << std::get<1>(rule) << "\"" << endl;
       }
       dep_file.close();
     }
     logger << "Done" << endlog;
     return;
   }

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

   // this make sure all closed file will no longer be seen
   _project.create_compiler_instance (_conf);

   logger << "Inserting unit pro- and epilogues" << endlog;
   insert_aspect_includes (cc_units, h_units, aspect_includes, aspect_fwd_decls);
   
   logger << "Updating #line directives of generated code fragments" << endlog;
   update_line_directives (cc_units, h_units);
   
     // this make sure all closed file will no longer be seen
   _project.create_compiler_instance (_conf);

   if (_conf.nosave ()) {
     logger << "Don't save" << endlog;
   }
   else {
     logger << "Saving" << endlog;
     logger++;

     if (_project.has_dest_path()) {
       logger << "Project tree" << endlog;

       // create_compiler_instance() always creates a file_entry for the main file (ac_gen.cc)
       clang::SourceManager &SM = _project.get_compiler_instance()->getSourceManager();
       _project.close(ACFileID::main_file(SM));
      // 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 ().empty()) {

       // expand project includes
       logger << "Expanding project includes" << endlog;
       ClangIncludeExpander cie (err (), _project, _line_mgr);
       std::ostringstream buf;
       cie.expand(buf);

       // update generated #line <NUM> "<ac...> directives for debuggers
       logger << "Fixing #line directives" << endlog;
       string code = update_line_directives_clang (buf.str(), _conf.file_out ().c_str(), _line_mgr);
       
       // now save
       logger << "Path \"" << _conf.file_out () << "\"" << endlog;
       ofstream out (_conf.file_out (), ios::out|ios::binary);
       if (out.is_open ()) {
         out << code;
       } else
         err () << sev_error << "can't open file \"" 
                << _conf.file_out ().c_str() << "\"" << endMessage;
     }
     
     logger--;
   }
   logger << "Done" << endlog;
 }


// update #line directives in all project files
void Weaver::update_line_directives (set<ACFileID> &cc_units,
  set<ACFileID> &h_units) {
  for (set<ACFileID>::iterator iter = cc_units.begin ();
       iter != cc_units.end (); ++iter) {
    ostringstream out;
    if (_project.get_dest_path ((*iter).name (), out)) {
      update_line_directives (*iter, out.str ().c_str ());
    }
  }
  for (set<ACFileID>::iterator iter = h_units.begin ();
       iter != h_units.end (); ++iter) {
    ostringstream out;
    if (_project.get_dest_path ((*iter).name (), out)) {
      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 Weaver::insert_aspect_includes (set<ACFileID> &cc_units,
  set<ACFileID> &h_units, const string &aspect_includes, const string &aspect_fwd_decls) {
  logger++;
  for (set<ACFileID>::iterator iter = cc_units.begin ();
       iter != cc_units.end (); ++iter) {
    logger << "Manipulating translation unit file " << (*iter).name () << endlog;
//    insert_aspect_includes (vm, *iter, false, aspect_includes, aspect_fwd_decls);
    insert_aspect_includes (*iter, false, gen_aspect_includes(*iter), aspect_fwd_decls);
  }
  for (set<ACFileID>::iterator iter = h_units.begin ();
       iter != h_units.end (); ++iter) {
    logger << "Manipulating header file " << (*iter).name () << endlog;
//    insert_aspect_includes (vm, *iter, true, aspect_includes, aspect_fwd_decls);
    insert_aspect_includes (*iter, true, gen_aspect_includes(*iter), aspect_fwd_decls);
  }
  logger--;
}
       
void Weaver::insert_aspect_includes (ACFileID fid,
  bool header, const string &aspect_includes, const string &aspect_fwd_decls) {

  clang::SourceManager &SM = _project.get_compiler_instance ()->getSourceManager ();
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
  auto mb = SM.getMemoryBufferForFileOrNone(fid.file_entry());
#else
  const llvm::MemoryBuffer *mb = SM.getMemoryBufferForFile (fid.file_entry());
#endif
  if (mb->getBuffer () == "") {
    // file is empty
    logger++; logger << "File is empty" << endlog; logger--;
    return;
  }
  ostringstream prologue;
  ostringstream epilogue;
  
  // 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, fid);
  prologue << endl;
  prologue << "#ifndef __ac_have_predefined_includes__" << endl;
  prologue << "#define __ac_have_predefined_includes__" << endl;
  prologue << "#endif // __ac_have_predefined_includes__" << endl;
  // insert AC only in header files, in cc files is has already been done
//  if (header) {
    prologue << NamespaceAC::def( _conf );
    prologue << aspect_fwd_decls;
//  }
  prologue << "#endif // " << first_macro.c_str () << endl;

  epilogue << endl << "#ifdef __ac_FIRST_FILE_";
  Naming::mangle_file (epilogue, fid);
  epilogue << endl;
  epilogue << aspect_includes;
  epilogue << "#undef " << first_macro.c_str () << endl;
  epilogue << "#undef __ac_FIRST_FILE_";
  Naming::mangle_file (epilogue, fid);
  epilogue << endl;
  epilogue << "#endif // __ac_FIRST_FILE_";
  Naming::mangle_file (epilogue, fid);
  epilogue << endl;

  string new_stuff = prologue.str ();
  new_stuff += mb->getBuffer();
  new_stuff += epilogue.str ();
  SM.overrideFileContents(fid.file_entry(),
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
      llvm::MemoryBuffer::getMemBufferCopy(new_stuff, fid.file_entry().getName()));
#else
      llvm::MemoryBuffer::getMemBufferCopy(new_stuff, fid.file_entry()->getName()));
#endif
}

// transform all #line <NUM> "<ac.." directives into
// #line <REAL-NUM> "<TARGET-FILENAME>". This is necessary for
// debuggers to find generated code.
void Weaver::update_line_directives (ACFileID fid, const char *filename) {
  clang::SourceManager &sm = _project.get_compiler_instance ()->getSourceManager ();

#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
  auto mb = sm.getMemoryBufferForFileOrNone(fid.file_entry());
#else
  const llvm::MemoryBuffer *mb = sm.getMemoryBufferForFile(fid.file_entry ());
#endif
  string buf = update_line_directives_clang (mb->getBuffer(), filename, _line_mgr);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  sm.overrideFileContents(fid.file_entry(), llvm::MemoryBuffer::getMemBufferCopy(buf, fid.file_entry().getName()));
#else
  sm.overrideFileContents(fid.file_entry(), llvm::MemoryBuffer::getMemBufferCopy(buf, fid.file_entry()->getName()));
#endif
}


ACFileID Weaver::translate (const char *file, Transformer &transformer, bool gen_deps)
 {
   logger << "Handling Translation Unit `";
   const char *fname = strrchr (file, (int)'/');
   logger << (fname ? ++fname : file) << "'." << endlog;

   logger++;
   ACFileID fid = transformer.work (file, gen_deps); // run only phase 1 if gen_deps is used
   logger--;
   
   return fid; // return the created unit
 }

class Searcher : public PointCutSearcher {
public:
  ACM_Pointcut *lookup_pct_func (bool root_qualified, std::vector<std::string> &qual_name) {
    return 0;
  }

  ACM_Attribute *lookup_pct_attr(bool root_qualified, std::vector<string> &qual_name) {
    return 0;
  }
};

void Weaver::match_expr_in_repo() {
  const string &repo_file = _conf.repository();

  if (getenv("ACOLDREPO") != NULL) {
    err() << sev_error << "matching not supported with old repository format"
        << endMessage;
    return;
  }

  logger << "Reading project repository '" << repo_file << "'" << endlog;
  ACModelFile model_file(repo_file);
  if (!model_file.open()) {
    err() << sev_error << "project repository '" << repo_file.c_str()
      << "' cannot be opened" << endMessage;
    return;
  }
  ProjectModel project_model;
  XmlModelReader reader;
  if (!reader.read (project_model, model_file)) {
    err() << sev_error << "project repository '" << repo_file.c_str()
      << "' is invalid" << endMessage;
    return;
  }
  model_file.close();
  if (project_model.get_version () != ac_version ())
    err() << sev_warning << "project file version '" << project_model.get_version().c_str ()
         << "' differs from ac++ version" << endMessage;

  string pct = _conf.expr();
  if (pct == "") {
    err() << sev_error << "Empty pointcut expression." << endMessage;
    return;
  }

  logger << "Fixing non-persistent element relations" << endlog;
  ProjectModel::Selection all_classes;
  project_model.select ((JoinPointType)(JPT_Class|JPT_Aspect), all_classes);
  // set derived relation for all base relations
  for (ProjectModel::Selection::iterator iter = all_classes.begin ();
     iter != all_classes.end (); ++iter) {
    ACM_Class &cls = (ACM_Class&)**iter;
    typedef ACM_Container<ACM_Class, false> BC;
    BC &bases = cls.get_bases();
    for (BC::iterator bi = bases.begin (); bi != bases.end (); ++bi)
      (*bi)->get_derived().insert(&cls);
  }

  ProjectModel::Selection all_calls;
  project_model.select (JPT_Call, all_calls);
  // set backward links from all functions to calls of it
  for (ProjectModel::Selection::iterator iter = all_calls.begin ();
     iter != all_calls.end (); ++iter) {
    ACM_Call &call = (ACM_Call&)**iter;
    call.get_target()->get_calls().insert(&call);
  }

  logger << "Matching pointcut expression " << pct << endlog;
  // remove blanks (not handled by parser correctly); blanks in match expression must remain
  string tmp;
  bool in_quotes = false;
  for (string::iterator i = pct.begin (); i != pct.end(); ++i) {
    if (!in_quotes && *i == ' ')
      continue;
    tmp += *i;
    if (*i == '\"')
      in_quotes = !in_quotes;
  }
  pct = tmp;

  PointCutExprParser *pce_parser = PointCutExprParser::instance( _conf );
  Searcher searcher;
  PointCutContext context (project_model);
  PointCutExpr *pce = 0;
  try {
    pce = pce_parser->parse(pct, searcher);
    pce->semantics(err (), context);

  }
  catch (const std::exception &e) {
    err () << sev_error
      << "Invalid pointcut expression: " << e.what() << "." << endMessage;
    return;
  }

  // match all joinpoints
  // iterate through all introduction advice in the plan
  ProjectModel::Selection all;
  project_model.select ((JoinPointType)(JPT_Class|JPT_Aspect|JPT_Function|JPT_Variable|JPT_Code), all);
  // update the plan for intros
  for (ProjectModel::Selection::iterator iter = all.begin ();
     iter != all.end (); ++iter) {
    ACM_Any &jpl = (ACM_Any&)**iter;
    if (is_pseudo(jpl))
      continue;
    Binding binding;     // binding and condition not used for intros
    Condition condition;
    if (pce->match (jpl, context, binding, condition)) {
      cout << filename (jpl) << ":" << line(jpl) << ":\t" << jpl.type_str() << " \"";
      if (jpl.type_val() & JPT_Code)
       cout << signature((ACM_Code&)jpl);
      else
       cout << signature((ACM_Name&)jpl);
      cout << "\"";
      if (condition)
        cout << " ; condition: " << condition;
      cout << endl;
    }
  }
}

string Weaver::gen_aspect_includes (ACFileID target_unit) {
  // generate the code
  stringstream cluster;
  for (list<AspectIncludeCluster>::const_iterator c = _aspect_include_clusters.begin();
      c != _aspect_include_clusters.end(); ++c) {
    cluster << "#ifdef __ac_need_";
    Naming::mangle_file (cluster, c->_aspect);
    cluster << endl;

    const set<ACFileID> &cluster_units = c->_cluster;
    for (set<ACFileID>::const_iterator i = cluster_units.begin ();
      i != cluster_units.end (); ++i) {
      ACFileID aspect_unit = *i;
      auto incname = _project.include_path(target_unit.name (), aspect_unit.name ());
      cluster << "#ifndef __ac_have_";
      Naming::mangle_file (cluster, aspect_unit);
      cluster << endl;
      cluster << "#define __ac_have_";
      Naming::mangle_file (cluster, aspect_unit);
      cluster << endl;
      cluster << "#include \"" << incname << "\"" << endl;
      cluster << "#endif" << endl;
    }

    cluster << "#endif" << endl;
  }
  return cluster.str ();
}

