#include "ACProject.h"
#include "ACConfig.h"
#include "version.h"
#include "ACBase/SysCall.h"
#include "ACBase/StringUtils.h"
using namespace ACBase;

#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
#include "llvm/TargetParser/Host.h"
#else
#include "llvm/Support/Host.h"
#endif
#include "clang/Basic/Version.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Rewrite/Core/Rewriter.h"
#include "clang/Lex/PreprocessorOptions.h"
// for diagnostics
#include "ClangTransformInfo.h"
#include "IntroductionUnit.h"
using namespace clang;

#include <libxml/xpath.h>
#include <stdio.h>
#include <fstream>
#include <regex>
#include <filesystem>

ACProject::ACProject (ACErrorStream &err, int &argc, char **&argv)
    : ACBase::PathManager(err), _err (err), _ci (0), _puma_config (_puma_err), _tricore_hack(false) {

  if (!_puma_config.CustomSystemConfigFile (argc, argv))
    _puma_config.Read (); // read global config file, e.g. $PUMA_CONFIG

  _puma_config.Read (argc, argv); // read command line config arguments

  // determine the supported C++ versions
  _backend_standard = -1; // unknown standard
  for (unsigned i = 0; i < _puma_config.Options (); i++) {
    const ACBase::ConfOption *o = _puma_config.Option (i);
    if (o->name() == "-D") {
      if (o->argument(0) == "__cplusplus") {
        if (o->argument(1) == "199711L")
          _backend_standard = 1998;
        else if (o->argument(1) == "201103L") // is it C++11?
          _backend_standard = 2011;
        else if (o->argument(1) == "201300L" ||
            o->argument(1) == "201402L")  // is it C++14?
          _backend_standard = 2014;
        else if (o->argument(1) == "201406L" ||
            o->argument(1) == "201500L" ||
            o->argument(1) == "201703L") // is it C++17?
          _backend_standard = 2017;
        else if (o->argument(1) == "201707L" ||
            o->argument(1) == "201709L" ||
            o->argument(1) == "202002L") // is it C++20?
          _backend_standard = 2020;
      }
    }
  }

  // read command line arguments
  config ().Add ("-D", "__acweaving", ""); // only defined during weaving phase

  // configure path manager
  configure_project_file_manager (_puma_config);
}

// checks whether the given file is an aspect header
bool ACProject::is_aspect_header (const std::filesystem::path filename) const {
  if (aspect_headers_.size() != aspect_headers_canonical_.size()) {
    for (auto &ah_file : aspect_headers_)
      aspect_headers_canonical_.insert(std::filesystem::canonical(ah_file));
  }
  return aspect_headers_canonical_.find(filesystem::weakly_canonical(filename)) != aspect_headers_canonical_.end();
}

class PatchedTargetInfo : public clang::TargetInfo {
  
private:
  void replaceOrAppendDL(std::string &dl, std::string entry, std::string newEntry) {
      // find entry
      std::size_t pos = dl.find(entry);
      
      if (pos == std::string::npos) {
        // No entry present; append one.
        if (!dl.empty() && dl.back() != '-')
          dl.push_back('-');

        dl += newEntry;
        return;
      }

      // Replace the existing entry:: segment until the next '-' or end.
      std::size_t end = dl.find('-', pos);
      if (end == std::string::npos)
        dl.replace(pos, dl.size() - pos, newEntry);
      else
        dl.replace(pos, end - pos, newEntry);
    };

public:
  void patchTC() {
    // get DL string
    std::string DL = std::string(getDataLayoutString());

    // patch size type
    SizeType = clang::TransferrableTargetInfo::IntType::UnsignedLong;

    // patch long double
    LongDoubleWidth = 8 * 8;
    //LongDoubleAlign = 4 * 8;
    //LongDoubleFormat = &llvm::APFloat::IEEEdouble();

    // patch double
    DoubleAlign = 4 * 8;
    replaceOrAppendDL(DL, "f64:", "f64:32:32");

    // patch long long
    LongLongAlign = 4 * 8;
    replaceOrAppendDL(DL, "i64:", "i64:32:32");

    // reset DL 
    resetDataLayout(DL);
  }
};

ACProject::~ACProject() {
  delete _ci;
}

// Configure the project from the command line or a file.
void ACProject::configure_project_file_manager(const ACBase::Config &c) {
  const ACBase::ConfOption *d = 0, *p = 0;

  unsigned num = c.Options();
  for (unsigned i = 0; i < num; i++) {
    const ACBase::ConfOption *o = c.Option(i);
    bool new_p = false, new_d = false;

    if (o->name() == "-w") {
      if (o->arguments() != 1)
        continue;
      protect(o->argument(0));
    } else if (o->name() == "-p") {
      if (o->arguments() != 1)
        continue;
      new_p = true;
    } else if (o->name() == "-d") {
      if (o->arguments() != 1)
        continue;
      new_d = true;
    }

    if (new_p) {
      if (p) {
        add_path(p->argument(0), d ? d->argument(0) : std::string());
        if (d)
          d = 0;
      }
      p = o;
    }

    if (new_d) {
      if (d) {
        add_path(p ? p->argument(0) : std::string(), d->argument(0));
        if (p)
          p = 0;
      }
      d = o;
    }
  }

  if (p || d)
    add_path(p ? p->argument(0) : std::string(), d ? d->argument(0) : std::string());
}

void ACProject::addFiles (xmlDocPtr doc, xmlNodePtr node, const string& xpath) {
  xmlXPathContextPtr  m_Context = xmlXPathNewContext(doc);
  xmlXPathCompExprPtr m_Expr = xmlXPathCompile((const xmlChar*)xpath.c_str());;
  xmlXPathObjectPtr   m_Result = 0;
  if( m_Context != 0 && m_Expr != 0 ) {
    m_Result = xmlXPathCompiledEval(m_Expr,m_Context);
    if( m_Result != 0 && m_Result->nodesetval != 0 ) {
      for( int i=0; i<m_Result->nodesetval->nodeNr; ++i ) {
        xmlChar* path = xmlGetProp(m_Result->nodesetval->nodeTab[i],
                                   (xmlChar*)"path");
        xmlChar* relpath = xmlGetProp(m_Result->nodesetval->nodeTab[i],
                                      (xmlChar*)"relpath");
        if (path) {

          string destname;
          if (has_dest_path()) {
            // make the destination path
            destname = paths().front().dest_path;
            char last = destname[destname.length () - 1];
            if (last != '/' && last != '\\')
              destname += '/';
#ifdef WIN32
            std::string relpath_unix = (const char*)relpath;
            SysCall::make_unix_path(relpath_unix);
            destname += relpath_unix;
#else
            destname += (const char*)relpath;
#endif
          }
          else
            destname = "<unused-dest-name>";
          
          // add the file to the project
          add_file ((const char*)path, destname);
        }
        else {
          assert (false);
        }
        if (path != 0) xmlFree(path);
        if (relpath != 0) xmlFree(relpath);
      }
      xmlXPathFreeObject(m_Result);
    }
  }
  if( m_Context ) xmlXPathFreeContext(m_Context);
  if( m_Expr )    xmlXPathFreeCompExpr(m_Expr);
}

// Add a new *virtual* file to the project.
ACFileID ACProject::addVirtualFile (const string &filename, const string &contents) {
#ifdef FRONTEND_CLANG
  // Create the ac_gen file to keep clang-based phase1 from crashing.
  // FIXME: Hack
//  fclose (fopen (filename.c_str (), "w+"));
  clang::FileManager &fm = _ci->getFileManager ();
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
  ACFileID fid (fm.getVirtualFileRef (filename, 0, 0));
#else
  ACFileID fid (fm.getVirtualFile (filename, 0, 0));
#endif
  _ci->getSourceManager().overrideFileContents(fid.file_entry (),
      llvm::MemoryBuffer::getMemBufferCopy(contents));
  _virtual_files.insert(fid);
  return fid;
#endif
}


// Remove a virtual file
void ACProject::removeVirtualFile (ACFileID fid) {
  string filename = fid.name();
  close (fid);
}

bool ACProject::loadProject (string acprj) {
  //std::cout << "try to find project" << std::endl;
  if( acprj.empty() == false ) {
    //std::cout << "proj file " << acprj << std::endl;
    xmlDocPtr doc = xmlParseFile(acprj.c_str());
    if( doc != 0 ) {
      xmlNodePtr root = xmlDocGetRootElement(doc);
      if( root != 0 ) {
        addFiles(doc,root,"/project/source/file");
        addFiles(doc,root,"/project/aspect/file[@active='true']");
        return true;
      }
    }
  }
  return false;
}

#ifdef FRONTEND_CLANG
class Diag : public TextDiagnosticPrinter {
  string _msg;
  llvm::raw_string_ostream _str_stream;
  llvm::raw_ostream &_orig_stream;
public:
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_21_0_0
  Diag(raw_ostream &os, DiagnosticOptions &diags) :
#else
  Diag(raw_ostream &os, DiagnosticOptions *diags) :
#endif
    TextDiagnosticPrinter(_str_stream, diags), _str_stream(_msg),
    _orig_stream (os) {}
  void HandleDiagnostic (DiagnosticsEngine::Level DiagLevel,
      const Diagnostic &Info) {
    // call Clang's HandleDiagnostic function. Output will be written into _msg.
    TextDiagnosticPrinter::HandleDiagnostic(DiagLevel, Info);

    // cout << "Handle Diag: " << endl << _msg << endl << "Handle Diag End" << endl;
    _msg = std::regex_replace(_msg, std::regex("(^|\r?\n)(.*<built-in>:.*)(\r?\n)"), "");
    // search for <intro:address> in _msg and replace it with a proper filename
    for (unsigned pos = 0; pos < _msg.size(); pos++) {
      if (_msg.substr(pos, 7) == "<intro:") {
        // determine pointer value and line number
        pos += 7;
        void *ptr;
        sscanf(_msg.c_str() + pos, "%p", &ptr);
        while (_msg[pos] != ':')
          pos++;
        pos++; // skip ':'
        unsigned line;
        sscanf(_msg.c_str() + pos, "%u", &line);
        while (_msg[pos] != ':')
          pos++;

        // find the corresponding slice fragment ("body")
        IntroductionUnit *unit = (IntroductionUnit*)ptr;

        ACM_Introduction *intro = unit->intro();
        if(!intro) {
          // No linked AspectC++ model introduction available. This happens when
          // Clang emits errors or warning inside code that was *generated* and
          // injected by AspectC++ and which therefore has no associated file and
          // thus is not inside the model. An example for such code is the struct
          // "__TI" which is generated and injected into each class for
          // introspection ("--introspection").
          // Output "there is no file and line info":
          _orig_stream << "<intro>:1:";
          continue;
        }

        TI_ClassSlice *slice = 0;
        if(intro->has_named_slice())
          slice = TI_ClassSlice::of(*intro->get_named_slice());
        else
          slice = TI_ClassSlice::of(*intro->get_anon_slice());

        // generate the new file and line information
        if (unit->is_members_intro()) {
          const TI_ClassSlice::SliceBody &body = slice->get_tokens();
          _orig_stream << body.file.name() << ":" << body.line + (line - 1) << ":";
        }
        else if (unit->is_base_intro()) {
          const TI_ClassSlice::SliceBody &body = slice->get_base_tokens();
          _orig_stream << body.file.name() << ":" << body.line + (line - 1) << ":";
        }
        else if (unit->non_inline_member_no() >= 0) {
          int no = unit->non_inline_member_no();
          const TI_ClassSlice::SliceBody &body = slice->non_inline_members()[no];
          _orig_stream << body.file.name() << ":" << body.line + (line - 1) << ":";
        }
        else
          _orig_stream << "<intro>:1:";
        continue;
      }
      _orig_stream << _msg[pos];
    }
    _msg.clear();
  }
};

void ACProject::create_compiler_instance (ACConfig &conf) {
  std::list<std::string> StringBuf;
  SmallVector<const char *, 16> Args;
  Args.push_back("-fsyntax-only"); // Just a dummy action.

  // TODO: this hard-coded stuff should be replaced by a mechanism that
  //       fetches these settings from the command line
  Args.push_back("-disable-free");
  Args.push_back("-fcxx-exceptions");
  Args.push_back("-fexceptions");
  Args.push_back("-pic-level");
  Args.push_back("2");
#if CLANG_VERSION_NUMBER < VERSION_NUMBER_18_1_3
  Args.push_back("-ftemplate-depth");
  Args.push_back("65536");
#endif
  Args.push_back("-fblocks");

  // disable all diagnostics: this is up the backend compiler
  Args.push_back("-Wno-everything");

  // set arg for configuring the C++ standard
  switch (_backend_standard) {
    case 1998: Args.push_back("-std=c++98"); break;
    case 2011: Args.push_back("-std=c++11"); break;
    case 2014: Args.push_back("-std=c++14"); break;
    case 2017: Args.push_back("-std=c++17"); break;
    case 2020: Args.push_back("-std=c++20"); break;
  }

  // Translate ACBase's built-in config file into clang arguments.
  // ACBase already sorts the options and gives system options a lower
  // priority than user-provided options.
  ACBase::Config &c = _puma_config;

  bool need_neon_support = false;

  // Start with all system options that are not include paths
  for (unsigned i = 0; i < c.Options (); i++) {
    const ACBase::ConfOption *o = c.Option (i);
    std::string name = o->name();
    const char *name_ptr = o->name().c_str();

    // For things like --isystem.
    if (starts_with(name, "--i")) {
      name = name.substr(1); // Skip first '-'.
      name_ptr++;
    }

    if (name == "-D") {
      // Disable definition of macros that Clang will define anyway -- even with "UsePredefines = false".
      if (o->argument(0) == "__cplusplus" ||
          o->argument(0) == "__STDC__" ||
          o->argument(0) == "__STDC_HOSTED__" ||
          o->argument(0) == "__STDC_VERSION__" ||
          o->argument(0) == "__STDC_UTF_16__" ||
          o->argument(0) == "__STDC_UTF_32__" ||
          o->argument(0) == "__STDC_EMBED_EMPTY__" ||
          o->argument(0) == "__STDC_EMBED_FOUND__" ||
          o->argument(0) == "__STDC_EMBED_NOT_FOUND__" ||
          o->argument(0) == "__STDCPP_DEFAULT_NEW_ALIGNMENT__" ||
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
          // TODO: it would be better to check whether *any* macros from the config file are always defined by clang
          o->argument(0) == "__FLT_EVAL_METHOD__" ||
          o->argument(0) == "__STDCPP_THREADS__" ||
#endif
          o->argument(0) == "__has_include(STR)" ||
          o->argument(0) == "__has_include_next(STR)")
        continue;

      // hacks to work around clang bugs/incompatibilities ...
      if (o->argument(0) == "__GNUC__") {
        // "Missing support for GCC's attribute malloc (deallocator) and malloc (deallocator, ptr-index)" #53152
        // if __GNUC__ is >= 11 we remove the arguments of __malloc__ by a macro
        if (o->arguments() == 2 && atoi(o->argument(1).c_str()) >= 11) {
          Args.push_back("-D __malloc__(A,B)=__malloc__");
        }
        // g++-13 support _Float{32|64|128} natively, but clang does not
        if (o->arguments() == 2 && atoi(o->argument(1).c_str()) >= 13) {
          Args.push_back("-D __acdef_float");
        }
      }

      // clang doesn't support the new float suffixes (e.g. in "typedef __decltype(0.0bf16) __bfloat16_t;"), yet
      if (starts_with(o->argument(0), "__BFLT16_") ||
          starts_with(o->argument(0), "__FLT32_") ||
          starts_with(o->argument(0), "__FLT64_") ||
          starts_with(o->argument(0), "__FLT128_")) {
        continue;
      }

      // enable Arm NEON if needed
      if (starts_with(o->argument(0), "__ARM_NEON")) {
        if (o->arguments() == 2 && starts_with(o->argument(1), "1"))
          need_neon_support = true;
      }

      // Don't define the __puma macro when Clang parses the code
      if (o->argument(0) == "__puma")
        continue;
      // otherwise define the macro from the config file/command line in Clang
      Args.push_back(name_ptr);
      if (o->arguments() > 1) {
        StringBuf.push_back(o->argument(0) + std::string("=") + o->argument (1));
        Args.push_back(StringBuf.back().c_str());
      }
      else
        Args.push_back(o->argument(0).c_str());
    }
    // replace the --target option by -triple
    else if (name == "--target") {
      Args.push_back("-triple");
      // clang has no tricore target
      if(starts_with(o->argument(0), "tricore")) {
        // enable tricore hack
        _tricore_hack = true;

        // use elfiamcu as base because its similar to tricore and has allowsLargerPreferedTypeAlignment as false
        Args.push_back("i386-pc-elfiamcu");
      }
      else
        Args.push_back(o->argument(0).c_str());
    }
    // if gnu extensions shall be supported, enable the necessary clang parser options
    else if (name == "--gnu") {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_20_0_0
      Args.push_back("-Wno-error=invalid-gnu-asm-cast"); // see bug 766 (ignore strange casts of LValues)
#else
      Args.push_back("-fheinous-gnu-extensions"); // see bug 766 (ignore strange casts of LValues)
#endif
    }

    // Pass macro definitions, include paths, and framework paths to clang. Everything else is
    // specific and dropped.
    if (!(starts_with(name, "-I")  || starts_with(name, "-i") || starts_with(name, "-F")))
      continue;

    bool ignore_path = false;
    for (unsigned i2 = i+1; i2 < c.Options(); i2++) {
      const ACBase::ConfOption *o2 = c.Option (i2);
      if (o2->name() == "--isystem" && o->argument(0) == o2->argument(0)) {
        ignore_path = true;
        break;
      }
    }

    if (!ignore_path) {
      Args.push_back(name_ptr);
      // cout << "Arg: " << Name;
      for (unsigned j = 0; j < o->arguments (); j++) {
        Args.push_back(o->argument(j).c_str());
        // cout << " " << o->Argument (j);
      }
      // cout << endl;
    }
    // else
    //   cout << "system path ignored" << endl;
  }

  // if g++ has __ARM_NEON=1 defined, we must tell clang to support SIMD types
  if (need_neon_support) {
    Args.push_back("-target-feature");
    Args.push_back("+simd");
  }

  // Add the main TU.
  Args.push_back("-x");
  Args.push_back("c++");
  Args.push_back(conf.file_in().c_str());

  // Create diagnostic (= diagnostics engine) (which is used for emitting errors, warnings
  // and so on): We use the adapted diagnostic "Diag" (the class "Diag" is defined above)
  // which replaces file names and line numbers of code that was injected by AspectC++
  // before outputting it to the console / before showing it to the user.
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_21_0_0
  auto fs = llvm::vfs::getRealFileSystem();
  // Now create a -cc1 invocation out of the config.
  clang::DiagnosticOptions diag_opts;
  auto diags = CompilerInstance::createDiagnostics(*fs, diag_opts, new Diag(llvm::errs(), diag_opts));
  shared_ptr<CompilerInvocation> CI(new CompilerInvocation());
  CompilerInvocation::CreateFromArgs(*CI, Args, *diags);
  CompilerInstance *new_ci = new CompilerInstance(CI);
#elif CLANG_VERSION_NUMBER >= VERSION_NUMBER_20_0_0
  auto fs = llvm::vfs::getRealFileSystem();
  // Now create a -cc1 invocation out of the config.
  CompilerInstance *new_ci = new CompilerInstance;
  auto diags = CompilerInstance::createDiagnostics(*fs, &new_ci->getDiagnosticOpts(), new Diag(llvm::errs(), &new_ci->getDiagnosticOpts()));
  shared_ptr<CompilerInvocation> CI(new CompilerInvocation());
  CompilerInvocation::CreateFromArgs(*CI, Args, *diags);
  new_ci->setInvocation(std::move(CI));
#else
  // Now create a -cc1 invocation out of the config.
  CompilerInstance *new_ci = new CompilerInstance;
  new_ci->createDiagnostics(new Diag(llvm::errs(), &new_ci->getDiagnosticOpts()));
  shared_ptr<CompilerInvocation> CI(new CompilerInvocation());
  CompilerInvocation::CreateFromArgs(*CI, Args, new_ci->getDiagnostics());
  new_ci->setInvocation(std::move(CI));
#endif

  // Create the diagnostics engine again (see above)
  // Note: it is important to do this *after* calling 'setInvocation', because otherwise
  //       the diagnostics options are uninitialized / all false
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_21_0_0
  new_ci->createDiagnostics(*fs, new Diag(llvm::errs(), new_ci->getDiagnosticOpts()));
#elif CLANG_VERSION_NUMBER >= VERSION_NUMBER_20_0_0
  new_ci->createDiagnostics(*fs, new Diag(llvm::errs(), &new_ci->getDiagnosticOpts()));
#else
  new_ci->createDiagnostics(new Diag(llvm::errs(), &new_ci->getDiagnosticOpts()));
#endif
  // Create vital components.
  if (_ci) {
    new_ci->setFileManager (&_ci->getFileManager ()); // reference in _ci must be set to 0 later!
    // forced includes shall not be modified
    new_ci->getInvocation ().getPreprocessorOpts ().Includes =
        _ci->getInvocation ().getPreprocessorOpts ().Includes;
  }
  else
    new_ci->createFileManager();
  new_ci->createSourceManager(new_ci->getFileManager());
//  new_ci->setTarget(clang::TargetInfo::CreateTargetInfo(new_ci->getDiagnostics(),
//                                                        &new_ci->getTargetOpts()));
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_21_0_0
  clang::TargetOptions &targetOptions = new_ci->getTargetOpts();
#else
  const std::shared_ptr<clang::TargetOptions> targetOptions = std::make_shared<clang::TargetOptions>(new_ci->getTargetOpts());
#endif
//  targetOptions->Triple = llvm::sys::getDefaultTargetTriple();
  clang::TargetInfo *pTargetInfo = clang::TargetInfo::CreateTargetInfo(new_ci->getDiagnostics(), targetOptions);

  // apply tricore patch if necessary
  if(_tricore_hack) {
    PatchedTargetInfo *pti = (PatchedTargetInfo*)pTargetInfo;
    pti->patchTC();
  }

  new_ci->setTarget (pTargetInfo);

  new_ci->getPreprocessorOpts().UsePredefines = false;

  if (_ci) {
    // refreshing compiler instance
    clang::SourceManager &new_SM = new_ci->getSourceManager ();
    clang::SourceManager &SM = _ci->getSourceManager ();
    for (clang::SourceManager::fileinfo_iterator fi = SM.fileinfo_begin(),
                                                 fe = SM.fileinfo_end();
         fi != fe; ++fi) {
      auto file_entry = fi->first;
      if (_closed_files.find (ACFileID (file_entry)) == _closed_files.end ()) {
        if (SM.isFileOverridden(file_entry) &&
            !new_SM.isFileOverridden(file_entry)) {
          // save changes in 'file_entry->getName ()' in the new source manager

#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
          auto opt_mb = SM.getMemoryBufferForFileOrNone (fi->first);
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_18_1_3
          new_SM.overrideFileContents(fi->first,
            llvm::MemoryBuffer::getMemBufferCopy(opt_mb.value().getBuffer(),
            opt_mb.value().getBufferIdentifier()));
#else
          new_SM.overrideFileContents(fi->first,
            llvm::MemoryBuffer::getMemBufferCopy(opt_mb.getValue().getBuffer(),
            opt_mb.getValue().getBufferIdentifier()));
#endif
#else
          const llvm::MemoryBuffer *buf = fi->second->getRawBuffer();
          new_SM.overrideFileContents(fi->first,
            llvm::MemoryBuffer::getMemBufferCopy(buf->getBuffer(), buf->getBufferIdentifier()));
#endif
        }
      }
    }
    _closed_files.clear ();
    string main_file = ACFileID::main_file(SM).name();
    new_ci->InitializeSourceManager(clang::FrontendInputFile(main_file, clang::InputKind(clang::Language::CXX)));
    _ci->setFileManager (0);
  }

  // store the new compiler instance, potentially replacing an old one
  delete _ci;
  _ci = new_ci;
}
#endif

// Add a forced include to the front end
void ACProject::add_forced_include (const string &file) {
  std::string incname;
  if (!SysCall::make_canonical_path(file, incname))
    return; // error, shouldn't happen
#ifdef FRONTEND_CLANG
  std::vector<std::string> &incs = _ci->getInvocation ().getPreprocessorOpts ().Includes;
  incs.push_back (incname);
#endif
}

//Remove a forced include from the front end
void ACProject::remove_forced_include (const string &file) {
  std::string incname;
  if (!SysCall::make_canonical_path(file, incname))
    return; // error, shouldn't happen
#ifdef FRONTEND_CLANG
  vector<string> &incs = _ci->getInvocation ().getPreprocessorOpts ().Includes;
  for (vector<string>::iterator i = incs.begin (); i != incs.end (); ++i) {
    if (incname == *i) {
      incs.erase (i);
      break;
    }
  }
#endif
}

// Remove all forced includes
void ACProject::remove_forced_includes () {
#ifdef FRONTEND_CLANG
  _ci->getInvocation ().getPreprocessorOpts ().Includes.clear ();
#endif
}

// Get the list of forced includes from the front end as a vector of filenames
void ACProject::get_forced_includes (vector<string> &forced_includes) {
#ifdef FRONTEND_CLANG
  forced_includes = _ci->getInvocation ().getPreprocessorOpts ().Includes;
#endif
}

string ACProject::include_path(const string &from, const string &to) {

  // as a first strategy try to find an include path relative to a
  // search path (-I option)
  for (unsigned i = config ().Options (); i > 0; i--) {
    const ACBase::ConfOption *o = config ().Option (i-1);
    if (o->name() == "-I") {
      if (!o->arguments())
        continue;
      std::filesystem::path dir = o->argument(0);
      auto rel = std::filesystem::relative(to, dir);
      if (*rel.begin() != "..") {
        // cout << from << " " << to << ": " << rel << endl;
        return rel.string(); // explicit conversion needed for Windows where paths use wide characters
      }
    }
  }

  // alternatively generate a path from the directory in which 'from' is located to 'to'.
  auto dir = std::filesystem::weakly_canonical(from).remove_filename(); // from might be 'main.cc' (without dir part)
  auto rel = std::filesystem::weakly_canonical(to).lexically_relative(dir);
  // cout << from << " " << to << ": " << rel << endl;
  return rel.string(); // explicit conversion needed for Windows where paths use wide characters
}

#ifdef FRONTEND_CLANG

// Save all files that have been opened
void ACProject::save () const {
  clang::SourceManager &SM = _ci->getSourceManager();

  for (clang::SourceManager::fileinfo_iterator fi = SM.fileinfo_begin(),
                                               fe = SM.fileinfo_end();
       fi != fe; ++fi) {
    auto file_entry = fi->first;
    ACFileID file_id(file_entry);
    if (_closed_files.find(file_id) == _closed_files.end()) {
#if CLANG_VERSION_NUMBER >= VERSION_NUMBER_12_0_0
      auto mb = SM.getMemoryBufferForFileOrNone(file_entry);
#else
      const llvm::MemoryBuffer *mb = SM.getMemoryBufferForFile(file_entry);
#endif
      ACBase::PathManager::save(file_id.name(), SM.isFileOverridden (file_entry), mb->getBufferStart ());
    }
  }
}

void ACProject::close (ACFileID fid) {
  _closed_files.insert (fid);
}

#endif
