// 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 <stdlib.h>
#include <sstream>
#include <set>

// Clang includes
#include "clang/Basic/TargetInfo.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Basic/Version.h"

// AspectC++ includes
#include "ACConfig.h"
#include "version.h"
#include "Naming.h"
#include "ACBase/SysCall.h"

using namespace std;
using namespace ACBase;

static OptsParser::Option options[] = {
  { ACConfig::ACOPT_VERSION, "V", "version",
    "\t" "Version information", OptsParser::AT_NONE },
  { ACConfig::ACOPT_HELP, "h", "help",
    "\t" "\t" "Get this help text", OptsParser::AT_NONE },
  { ACConfig::ACOPT_VERBOSE, "v", "verbose",
    "\t" "Level of verbosity (0-9)", OptsParser::AT_MANDATORY },
  { ACConfig::ACOPT_COMPILE, "c", "compile",
    "\t" "Name of the input file", OptsParser::AT_MANDATORY },
  { ACConfig::ACOPT_OUTPUT, "o", "output",
    "\t" "Name of the output file", OptsParser::AT_MANDATORY },
  { ACConfig::ACOPT_INCLUDE_FILES, "i", "include_files",
    "\t" "Generate manipulated header files", OptsParser::AT_NONE },
  { ACConfig::ACOPT_ASPECT_HEADER, "a", "aspect_header",
    "Name of aspect header file or '0'", OptsParser::AT_MANDATORY }, 
  { ACConfig::ACOPT_REPOSITORY, "r", "repository",
    "Name of the project repository", OptsParser::AT_MANDATORY }, 
  { ACConfig::ACOPT_EXPRESSION, "x", "expr",
    "\tPointcut expression to match in repository", OptsParser::AT_MANDATORY },
  { ACConfig::ACOPT_PROBLEM_LOCAL_CLASS, NULL, "problem_local_class",
    "Back-end does not support local classes correctly", OptsParser::AT_NONE }, 
  { ACConfig::ACOPT_NO_PROBLEM_LOCAL_CLASS, NULL, "no_problem_local_class",
    "Back-end supports local classes correctly", OptsParser::AT_NONE }, 
  { ACConfig::ACOPT_PROBLEM_SPEC_SCOPE, NULL,
    "problem_spec_scope",
    "Back-end does not support template specialization in non-namespace",
    OptsParser::AT_NONE }, 
  { ACConfig::ACOPT_NO_PROBLEM_SPEC_SCOPE, NULL,
    "no_problem_spec_scope",
    "Back-end supports template specialization in non-namespace scope",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_PROBLEM_FORCE_INLINE, NULL, "problem_force_inline",
    "Back-end does not support forced inlining correctly", OptsParser::AT_NONE }, 
  { ACConfig::ACOPT_NO_PROBLEM_FORCE_INLINE, NULL, "no_problem_force_inline",
    "Back-end supports forced inlining correctly", OptsParser::AT_NONE }, 
  { ACConfig::ACOPT_NOLINE, NULL,
    "no_line",
    "\t" "\t" "Don't generate #line directives",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_GEN_SIZE_TYPE, NULL, "gen_size_type",
    "Argument type for generated new operator", OptsParser::AT_MANDATORY }, 
  { ACConfig::ACOPT_PROJ_FILE, NULL, "proj_file",
    "\t" "Path to the project file", OptsParser::AT_MANDATORY},
  { ACConfig::ACOPT_WARN_DEPRECATED, NULL, "warn_deprecated",
    "\t" "Warn if deprecated syntax is used", OptsParser::AT_NONE },
  { ACConfig::ACOPT_NO_WARN_DEPRECATED, NULL, "no_warn_deprecated",
    "Don't warn if deprecated syntax is used", OptsParser::AT_NONE },
  { ACConfig::ACOPT_WARN_MACRO, NULL, "warn_macro",
    "\t" "Warn if a macro transformation had to be ignored",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_NO_WARN_MACRO, NULL, "no_warn_macro",
    "\t" "Don't warn if a macro transformation had to be ignored",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_WARN_LIMITATIONS, NULL, "warn_limitations",
    "\t" "Warn if a feature does not work as familiar due to limitations",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_NO_WARN_LIMITATIONS, NULL, "no_warn_limitations",
    "" "Don't warn if a feature does not work as familiar due to limitations",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_KEYWORDS, "k", "keywords",
    "\t" "Allow AspectC++ keywords in normal project files",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_DYNAMIC, 0, "dynamic",
    "\t\t" "Prepare generated code for dynamic weaving",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_INTROSPECTION, 0, "introspection",
    "\t" "Support introspection mechanism",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_FLOW_ANALYSIS, 0, "flow_analysis",
    "\t" "Support control-flow analysis and data-flow analysis",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_BUILTIN_OPERATORS, 0, "builtin_operators",
    "Support joinpoints on builtin operator calls",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_DATA_JOINPOINTS, 0, "data_joinpoints",
    "\t" "Support data-based joinpoints like get(), set(), ...",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_INTRODUCTION_DEPTH, 0, "introduction_depth",
    "Set the maximum depth for nested introductions", OptsParser::AT_MANDATORY },
  { ACConfig::ACOPT_WARN_COMPATIBILITY, NULL, "warn_compatibility",
    "Enable migration warnings for code based on major version <arg>",
    OptsParser::AT_MANDATORY },
  { ACConfig::ACOPT_ATTRIBUTES, NULL, "attributes",
    "\t" "Support user-defined attributes",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_NO_ATTRIBUTES, NULL, "no_attributes",
    "\t" "Disable support user-defined attributes",
    OptsParser::AT_NONE },
  { ACConfig::ACOPT_GEN_DEPS, NULL, "gen_deps",
    "\t" "Generate aspect dependency file based on pragmas in aspect headers",
    OptsParser::AT_MANDATORY },
  { 0, NULL, 0, 0, OptsParser::AT_NONE }
};

string ACConfig::size_type () const {
  string result = _size_type;
  // if the size-type string wasn't provided with the command line option
  // --gen_size_type, ask the front end for its target size type.
  if (result == "") {
#ifdef FRONTEND_CLANG
    clang::CompilerInstance *ci = _project.get_compiler_instance ();
    clang::TargetInfo &target = ci->getTarget ();
    result = clang::TargetInfo::getTypeName (target.getSizeType ());
#endif
  }
  return result;
}

bool ACConfig::analyze () {

  // setup defaults
  _v                   = 0; // default verbosity without "-v"
  _ifiles              = false;
  _file_in.clear();
  _file_out.clear();
  _repository.clear();
  _iterate_tunits      = false;
  _nosave              = false;
  _noline              = false;
  _iterate_aspects     = true;

  // default setting: unknown compilers have a problem with everything
  _problem_local_class         = true;
  _problem_spec_scope          = true;
  _problem_force_inline        = true;
  if (_project.config ().Option ("--gnu") ||
      _project.config ().Option ("--gnu-2.95")) {
    _problem_local_class         = false;
    _problem_force_inline        = false;
  } else if (_project.config ().Option ("--vc")) {
    _problem_spec_scope          = false;
  }
  _warn_deprecated     = true;
  _warn_macro          = false;
  _warn_limitations    = true;
  _dynamic             = false;
  _introspection       = false;
  _attributes          = true;
  _flow_analysis       = false;
  _builtin_operators   = false;
  _data_joinpoints     = false;
  _keywords            = false;
  _introduction_depth  = 10;
  _warn_compatibility = -1;

  OptsParser op (_argc, _argv, options);

  bool have_proj_file = false;
  while (op.getOption () != OptsParser::FINISH) {
    switch (op.getResult ()) {
    case ACOPT_VERBOSE:
      if (op.getArgument ().empty ())
        // -v without number
        _v = 3;
      else
        _v = atoi (op.getArgument ().c_str ());
      break;
    case ACOPT_VERSION:
      cout << ac_program () << " " << ac_version ()
           << " (" << __DATE__;
#ifdef FRONTEND_CLANG
      cout << ", clang " CLANG_VERSION_STRING;
#endif // FRONTEND_CLANG
      cout << ")" << endl;
      return true;
    case ACOPT_HELP:
      usage (_argv[0]);
      return true;
    case ACOPT_COMPILE:
      _file_in = op.getArgument ();
#if defined (WIN32)
      make_unix_path(_file_in);
#endif
      break;
    case ACOPT_OUTPUT:
      _file_out = op.getArgument ();
#if defined (WIN32)
      make_unix_path(_file_out);
#endif
      break;
    case ACOPT_INCLUDE_FILES:
      _ifiles = true;
      break;
    case ACOPT_NOSAVE:
      _nosave = true;
      break;
    case ACOPT_NOLINE:
      _noline = true;
      break;
    case ACOPT_ASPECT_HEADER:
      _iterate_aspects = false;
      if (!(op.getArgument () == "0")) {
        string ah = op.getArgument ();
#if defined (WIN32)
        make_unix_path(ah);
#endif
        _aspect_headers.push_back(ah);
      }
      break;
    case ACOPT_REPOSITORY:
      _repository = op.getArgument ();
#if defined (WIN32)
      make_unix_path(_repository);
#endif
      break;
    case ACOPT_GEN_DEPS:
      _gen_deps_file = op.getArgument ();
#if defined (WIN32)
      make_unix_path(_gen_deps_file);
#endif
      break;
    case ACOPT_EXPRESSION:
      _expr = op.getUnstrippedArgument ();
      break;
    case ACOPT_PROBLEM_LOCAL_CLASS:
      _problem_local_class = true;
      break;
    case ACOPT_NO_PROBLEM_LOCAL_CLASS:
      _problem_local_class = false;
      break;
    case ACOPT_PROBLEM_SPEC_SCOPE:
      _problem_spec_scope = true;
      break;
    case ACOPT_NO_PROBLEM_SPEC_SCOPE:
      _problem_spec_scope = false;
      break;
    case ACOPT_PROBLEM_FORCE_INLINE:
      _problem_force_inline = true;
      break;
    case ACOPT_NO_PROBLEM_FORCE_INLINE:
      _problem_force_inline = false;
      break;
    case ACOPT_GEN_SIZE_TYPE:
      _size_type = op.getArgument ().c_str ();
      break;
    case ACOPT_PROJ_FILE:
      have_proj_file = true;
      _proj_file = op.getArgument ().c_str ();
      break;
    case ACOPT_WARN_DEPRECATED:
      _warn_deprecated = true;
      break;
    case ACOPT_NO_WARN_DEPRECATED:
      _warn_deprecated = false;
      break;
    case ACOPT_WARN_MACRO:
      _warn_macro = true;
      break;
    case ACOPT_NO_WARN_MACRO:
      _warn_macro = false;
      break;
    case ACOPT_WARN_LIMITATIONS:
      _warn_limitations = true;
      break;
    case ACOPT_NO_WARN_LIMITATIONS:
      _warn_limitations = false;
      break;
    case ACOPT_WARN_COMPATIBILITY:
      _warn_compatibility = atoi (op.getArgument ().c_str ());
      break;
    case ACOPT_KEYWORDS:
      _keywords = true;
      break;
    case ACOPT_DYNAMIC:
      _dynamic = true;
      break;
    case ACOPT_INTROSPECTION:
      _introspection = true;
      break;
    case ACOPT_FLOW_ANALYSIS:
      _flow_analysis = true;
      break;
    case ACOPT_BUILTIN_OPERATORS:
      _builtin_operators = true;
      break;
    case ACOPT_DATA_JOINPOINTS:
      _data_joinpoints = true;
      break;
    case ACOPT_INTRODUCTION_DEPTH:
      _introduction_depth = atoi (op.getArgument ().c_str ());
      break;
    case ACOPT_ATTRIBUTES:
      _attributes = true;
      break;
    case ACOPT_NO_ATTRIBUTES:
      _attributes = false;
      break;

    default:
      usage (_argv[0]);
      err () << sev_error;
      return false;
    }
  }

  bool have_std_prj   = _project.has_source_path();
  bool have_prj       = have_proj_file || have_std_prj;
  bool have_dest      = _project.has_dest_path();
  
  // --expr is a separate function. It requires a project repository file, but cannot
  // be combined with code generation
  if (!expr().empty ()) {
    // expression matching requires a repository
    if (repository().empty()) {
      usage (_argv[0]);
      err () << sev_error;
      return false;
    }
    return true;
  }


  // --gen_deps is a separate function. It requires -p or a project file, but cannot be
  // combined with code generation (for now)
  if (!_gen_deps_file.empty()) {
    if (!have_prj) {
      err () << sev_error << "option '--gen_deps' requires project path (-p) to be configured"
        << endMessage;
      return false;
    }
    if (have_dest || !_file_in.empty() || !_file_out.empty()) {
      err () << sev_error << "option '--gen_deps' cannot be combined with code generation"
        << endMessage;
      return false;
    }
    return true;
  }

  // now check if the other options make sense
  if (!have_prj || (have_std_prj && have_proj_file) ||
      (_ifiles && (!_file_in.empty() || !_project.has_dest_path())) ||
      (!_file_out.empty() && have_dest) ||
      (_file_in.empty() && !_ifiles && !have_dest)) {
    usage (_argv[0]);
    err () << sev_error;
    return false;
  }

  // check option dependency
  if( _data_joinpoints ) {
    if( ! _builtin_operators ) {
      err() << sev_warning << "--data_joinpoints internally requires --builtin_operators, please check and enable it to silence this warning!" << endMessage;
      _builtin_operators = true;
    }
  }

  // check for the compatibility mode (iterate through the project tree)
  if (!_ifiles && _file_in.empty() && _file_out.empty() && have_dest)
    _iterate_tunits = true;

  // set reasonable default values
  if (_file_out.empty() && !_file_in.empty())
    _file_out = "ac.out";

  if (_file_in.empty()) {
    if (!_file_out.empty())
      _file_in = _file_out;
    else {
      stringstream genfile;
      if (_project.has_source_path())
        genfile << _project.paths().front().src_path;
      genfile << "ac_gen." << extension ();
      _file_in = genfile.str();
    }
  }

  // determine a string that uniquely identifies the project
  if (have_std_prj) { // a normal project with one or more -p options
    set<string> paths;
    for (auto &p : _project.paths()) {
      std::string canon;
      SysCall::make_canonical_path (p.src_path.c_str(), canon);
      ostringstream name;
      Naming::mangle_file (name, canon);
      paths.insert (name.str ());
    }
    _project_id = *paths.begin ();
  }
  else { // used a project file

    // load the project file and set up the project
    if (!_project.loadProject (_proj_file)) {
      err () << sev_error << "failed to load project file '"
           << _proj_file.c_str () << "'" << endMessage;
      return false;
    }
    std::string canon;
    SysCall::make_canonical_path (_proj_file.c_str (), canon);
    ostringstream name;
    Naming::mangle_file (name, canon);
    _project_id = name.str ();
  }
  
  if (_iterate_aspects) {
    // collect the names of aspect header files
    PathIterator ah_iter (".*\\.ah$");
    while (_project.iterate (ah_iter))
      _project.aspect_headers().push_back(ah_iter.file ());
  }
  else {
    // Get the names from the -a options
    for (int i = 0; i < aspect_headers (); i++)
      _project.aspect_headers().push_back(aspect_header (i));
  }

  return true;
}

void ACConfig::usage (const char *prog) const {
  cerr << "usage: " << prog << " options" << endl
       << "   options are ..." << endl;
  cerr << "     -p|--path <arg>\t\t" << "project directory tree" << endl
       << "     -d|--dest <arg>\t\t" << "target directory tree" << endl
       << "     -e|--extension <arg>\t" << "translation unit filename ext."
       << endl;
  for (int o = 0; options[o].number > 0; o++) {
    cerr << "     ";
    if (options[o].shortName)  cerr << "-" << options[o].shortName;
    if (options[o].shortName && options[o].longName) cerr << "|";
    if (options[o].longName) cerr << "--" << options[o].longName;
    if (options[o].argType != OptsParser::AT_NONE) cerr << " <arg>";
    if (options[o].desc) cerr << "\t" << options[o].desc;
    cerr << endl;
  }
  cerr << "   further options for correct parsing are ..." << endl
       << "     -I <arg>\t\t\t" << "include file search path" << endl
       << "     -D <name>[=<value>]\t" << "macro definitions" << endl;
}


// get the extension string for cc files
 
const char *ACConfig::extension () {
  for (unsigned i = _project.config ().Options (); i > 0; i--) {
    const ConfOption *o = _project.config ().Option (i-1);
    if (o->name() == "-e" || o->name() == "--extension") {
      if (! o->arguments ())
        continue;
      return o->argument (0).c_str();
    }
  }
  return "cc";
}

// replace all '\' in a string (filename path) with '/'
#if defined (WIN32)
void ACConfig::make_unix_path(string &path) {
  for (unsigned i = 0; i < path.length(); ++i)
    if (path[i] == '\\')
      path[i] = '/';
}
#endif
