// This file is part of the AspectC++ compiler 'ac++'.
// Copyright (C) 1999-2004  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 "CCompiler.h"
#include "StdSystem.h"
#include "regex.h"
#include "ACDeps.h"
#include "GccDepFile.h"

//Puma includes
#include "Puma/SysCall.h"
#include "Puma/VerboseMgr.h"
#include "Puma/PathManager.h"
using namespace Puma;

//stdc++ includes
#include <iostream>
#include <fstream>

static bool transform_to_canonical_path(Puma::ErrorStream &err, set<string> &files) {
  set<string> out;
  for (const auto &file: files) {
    error_code ec;
    filesystem::path file_path = filesystem::canonical(file, ec);
    if (ec) {
      err << sev_fatal << "Dependency file '" << file.c_str()
          << "': " << ec.message().c_str() << endMessage;
      return false;
    }
    out.insert(file_path.string());
  }
  files = move(out);
  return true;
}

bool CCompiler::make_dep()
{
  AGxxConfig::FileCont::const_iterator file = _config.proj_paths().begin();

  // if there were no aspect headers given as arguments of ag++
  // search all aspect headers in all project paths
  if (_config.ah_files().empty())
  {

    PathManager path(this->_err);

    while (file != _config.proj_paths().end())
    {
      path.addPath(file->c_str());
      file++;
    }

    PathIterator ah_iter(".*\\.ah$");
    while (path.iterate(ah_iter))
    {
      _config.ah_files().push_back(ah_iter.file());
    }
  }

  // open and read all aspect dependency files into a single object
  ACDeps ac_deps;
  for (auto depfile : _config.dep_files()) {
    if (!ac_deps.merge_file(depfile, _err))
      return false;
  }

  // register all aspect headers -> detects duplicates,
  // creates a regular expression per aspect header for checking file dependencies
  for (auto ah_file : _config.ah_files())
    if (ah_file != "0")
      if (!ac_deps.register_aspect_header(ah_file, _err))
        return false;

  //assemble call to gcc
  string exec_str = "\"" + _config.cc_bin() + "\"";
  string target_file_mf, target_file; // ..mf has priority
  int translation_units = 0;
  bool phony_target_mode = false;
  OptionVec::const_iterator opt = _config.optvec().begin();
  while (opt != _config.optvec().end())
  {
    // find all g++ options for creating dependency information and files
    switch ((opt->flag()
        & (OptionItem::OPT_FILE | OptionItem::OPT_DEP)))
    {
      case OptionItem::OPT_DEP:
        // if phony targets will be generated for header deps, store this information for later
        if (opt->name() == "-MP")
          phony_target_mode = true;
          // filter-out (and save) the target filename if there is any (-o or -MF option)
        if (opt->name() == "-o")
          target_file = opt->arg().substr(2, opt->arg().length() - 4); // strip prefix " \"" and suffix "\" "
        else if (opt->name() == "-MF")
          target_file_mf = opt->arg().substr(2, opt->arg().length() - 4); // strip prefix " \"" and suffix "\" " 
        else
          exec_str.append(" " + opt->name() + opt->arg());
        break;
      case OptionItem::OPT_FILE:
        exec_str.append(" \"" + opt->name() + "\"");
        translation_units++;
        break;
    }

    opt++;
  }

  // The target file from the -MF option has priority
  if (!target_file_mf.empty())
    target_file = target_file_mf;

  // Add all aspect headers as translation units at the end of the command line to
  // get their header file dependencies, too.
  list<string> aspect_headers;
  ac_deps.get_aspect_headers(aspect_headers);
  for (const auto &ah : aspect_headers)
    exec_str += " -xc++ \"" + ah + "\"";

  // call gcc and save stdout and stderr
  StdSystem gcc(_err, _config, exec_str);
  if (!gcc.execute())
    return false;

  // if the execution was successful, try to parse the output
  GccDepFile gcc_deps(translation_units, aspect_headers.size(), phony_target_mode);
  if (!gcc_deps.parse(_err, gcc.stdout_str()))
    return false;
  ostream *out = &cout;
  ofstream fout;
  if (!target_file.empty()) {
    fout.open(target_file);
    out = &fout;
    // if you run g++ -M -MF <out> tu1.cc tu2.cc only the deps of tu2 are save in out
    gcc_deps.discard_overwritten_rules();
    translation_units = 1;
  }

  // determine the of dependency files (to be used below)
  set<string> dep_files (_config.dep_files().begin(), _config.dep_files().end());

  // merge the aspect dependencies into the dependencies of the translation units
  for (int tu = 0; tu < translation_units; tu++) {

    // all translation units depend on all dependency files
    // => change a dependency file and you will get a full rebuild
    gcc_deps.merge_dependencies(tu, dep_files);
  
    // all translation units depend on aspect headers for which no rule was given
    list<string> aspect_headers;
    ac_deps.get_aspect_headers(aspect_headers, ACDeps::WITHOUT_RULE);
    for (auto &ah : aspect_headers)
      gcc_deps.merge_aspect_dependencies(ah, tu);  // TODO: handle phony target changes

    // now handle the rules that are defined in the aspect dependency files
    ac_deps.get_aspect_headers(aspect_headers, ACDeps::WITH_RULE);
    set<string> deps;
    gcc_deps.retrieve_dependencies(tu, deps);
    do {
      if (!transform_to_canonical_path(_err, deps))
        return false;
      set<string> new_deps; // empty
      auto ah_iter = aspect_headers.begin();
      while (ah_iter != aspect_headers.end()) {
        auto ah_curr = ah_iter++;
        if (ac_deps.affects_any(*ah_curr, deps)) {
          gcc_deps.retrieve_aspect_dependencies(*ah_curr, new_deps);
          aspect_headers.remove(*ah_curr);
        }
      }
      gcc_deps.merge_dependencies(tu, new_deps);
      deps = move(new_deps);
    } while (!aspect_headers.empty() && !deps.empty());
  }

  gcc_deps.print(*out);
  return true;
}

bool CCompiler::compile()
{

  // construct execution string
  string exec_str = "\"" + _config.cc_bin() + "\"";

  OptionVec::iterator opt = _config.optvec().begin();

  // loop through all options in option vector
  while (opt != _config.optvec().end())
  {
    // g++ options
    if ((opt->flag() & (OptionItem::OPT_GCC | OptionItem::OPT_FILE)) 
        == OptionItem::OPT_GCC)
    {
      exec_str.append(" " + opt->name() + opt->arg());
    }
    // woven source files
    else if ((opt->flag()
              & (OptionItem::OPT_GCC | OptionItem::OPT_ACC | OptionItem::OPT_FILE))
            == (OptionItem::OPT_GCC | OptionItem::OPT_ACC | OptionItem::OPT_FILE))
    {
      // explictly mark .acc files  as c++ files
      exec_str.append(" -xc++ \"" + opt->name() + "\" -xnone");
    }
    // other source files
    else if ((opt->flag() & (OptionItem::OPT_GCC | OptionItem::OPT_FILE))
            == (OptionItem::OPT_GCC | OptionItem::OPT_FILE))
    {
      exec_str.append(" \"" + opt->name() + "\" ");
    }
    opt++;
  }

  // execute compiler
  System gcc(_err, _config, exec_str);
  return gcc.execute();
}

