//===--- iwyu_globals.cc - global variables for include-what-you-use ------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "iwyu_globals.h"

#include <algorithm>                    // for sort, make_pair
#include <cstdio>                       // for printf
#include <cstdlib>                      // for atoi, exit, getenv
#include <map>                          // for map
#include <set>                          // for set
#include <string>                       // for string, operator<, etc
#include <utility>                      // for make_pair, pair

#include "iwyu_cache.h"
#include "iwyu_include_picker.h"
#include "iwyu_getopt.h"
#include "iwyu_lexer_utils.h"
#include "iwyu_location_util.h"
#include "iwyu_path_util.h"
#include "iwyu_port.h"  // for CHECK_, etc
#include "iwyu_stl_util.h"
#include "iwyu_string_util.h"
#include "iwyu_verrs.h"
#include "iwyu_version.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/Version.h"
#include "clang/Lex/HeaderSearch.h"

using clang::DirectoryEntry;
using std::make_pair;
using std::map;
using std::string;
using std::vector;

namespace include_what_you_use {

static CommandlineFlags* commandline_flags = nullptr;
static clang::SourceManager* source_manager = nullptr;
static IncludePicker* include_picker = nullptr;
static const clang::LangOptions default_lang_options;
static const clang::PrintingPolicy default_print_policy(default_lang_options);
static SourceManagerCharacterDataGetter* data_getter = nullptr;
static FullUseCache* function_calls_full_use_cache = nullptr;
static FullUseCache* class_members_full_use_cache = nullptr;
static int ParseIwyuCommandlineFlags(int argc, char** argv);
static int ParseInterceptedCommandlineFlags(int argc, char** argv);

static void PrintHelp(const char* extra_msg) {
  printf("USAGE: include-what-you-use [-Xiwyu --iwyu_opt]... <clang opts>"
         " <source file>\n"
         "Here are the <iwyu_opts> you can specify (e.g. -Xiwyu --verbose=3):\n"
         "   --check_also=<glob>: tells iwyu to print iwyu-violation info\n"
         "        for all files matching the given glob pattern (in addition\n"
         "        to the default of reporting for the input .cc file and its\n"
         "        associated .h files).  This flag may be specified multiple\n"
         "        times to specify multiple glob patterns.\n"
         "   --keep=<glob>: tells iwyu to always keep these includes.\n"
         "        This flag may be specified multiple times to specify\n"
         "        multiple glob patterns.\n"
         "   --mapping_file=<filename>: gives iwyu a mapping file.\n"
         "   --no_default_mappings: do not add iwyu's default mappings.\n"
         "   --pch_in_code: mark the first include in a translation unit as a\n"
         "        precompiled header.  Use --pch_in_code to prevent IWYU from\n"
         "        removing necessary PCH includes.  Though Clang forces PCHs\n"
         "        to be listed as prefix headers, the PCH-in-code pattern can\n"
         "        be used with GCC and is standard practice on MSVC\n"
         "        (e.g. stdafx.h).\n"
         "   --prefix_header_includes=<value>: tells iwyu what to do with\n"
         "        in-source includes and forward declarations involving\n"
         "        prefix headers.  Prefix header is a file included via\n"
         "        command-line option -include.  If prefix header makes\n"
         "        include or forward declaration obsolete, presence of such\n"
         "        include can be controlled with the following values\n"
         "          add:    new lines are added\n"
         "          keep:   new lines aren't added, existing are kept intact\n"
         "          remove: new lines aren't added, existing are removed\n"
         "        Default value is 'add'.\n"
         "   --transitive_includes_only: do not suggest that a file add\n"
         "        foo.h unless foo.h is already visible in the file's\n"
         "        transitive includes.\n"
         "   --max_line_length: maximum line length for includes.\n"
         "        Note that this only affects comments and alignment thereof,\n"
         "        the maximum line length can still be exceeded with long\n"
         "        file names (default: 80).\n"
         "   --no_comments: do not add 'why' comments.\n"
         "   --update_comments: always add 'why' comments, even if no\n"
         "        #include lines need to be added or removed.\n"
         "   --no_fwd_decls: do not use forward declarations.\n"
         "   --verbose=<level>: the higher the level, the more output.\n"
         "   --quoted_includes_first: when sorting includes, place quoted\n"
         "        ones first.\n"
         "   --cxx17ns: suggests the more concise syntax introduced in C++17\n"
         "   --error[=N]: exit with N (default: 1) for iwyu violations\n"
         "   --error_always[=N]: always exit with N (default: 1) (for use\n"
         "        with 'make -k')\n"
         "\n"
         "In addition to IWYU-specific options you can specify the following\n"
         "options without -Xiwyu prefix:\n"
         "   --help: prints this help and exits.\n"
         "   --version: prints version and exits.\n");
  if (extra_msg)
    printf("\n%s\n\n", extra_msg);
}

static void PrintVersion() {
  llvm::outs() << "include-what-you-use " << IWYU_VERSION_STRING;
  // IWYU_GIT_REV should be provided by build system.
  string iwyu_rev = IWYU_GIT_REV;
  if (!iwyu_rev.empty()) {
    llvm::outs() << " (git:" << iwyu_rev << ")";
  }
  llvm::outs() << " based on " << clang::getClangFullVersion()
               << "\n";
}

static bool ParseIntegerOptarg(const char* optarg, int* res) {
  char* endptr = nullptr;
  long val = strtol(optarg, &endptr, 10);
  if (!endptr || endptr == optarg)
    return false;

  if (*endptr != '\0')
    return false;

  if (val > INT_MAX || val < INT_MIN)
    return false;

  *res = (int)val;
  return true;
}

OptionsParser::OptionsParser(int argc, char** argv) {
  // Separate out iwyu-specific, intercepted, and clang flags.  iwyu-specific
  // flags are "-Xiwyu <iwyu_flag>", intercepted flags are usual clang flags
  // like --version, --help, which we intercept to  provide custom handling.
  char** iwyu_argv = new char*[argc + 1];
  iwyu_argv[0] = argv[0];
  int iwyu_argc = 1;
  char** intercepted_argv = new char*[argc + 1];
  intercepted_argv[0] = argv[0];
  int intercepted_argc = 1;
  clang_argv_ = new const char*[argc + 1];
  clang_argv_[0] = argv[0];
  clang_argc_ = 1;
  for (int i = 1; i < argc; ++i) {
    if (i < argc - 1 && strcmp(argv[i], "-Xiwyu") == 0)
      iwyu_argv[iwyu_argc++] = argv[++i];   // the word after -Xiwyu
    else if (strcmp(argv[i], "--help") == 0)
      intercepted_argv[intercepted_argc++] = argv[i];     // intercept --help
    else if (strcmp(argv[i], "--version") == 0)
      intercepted_argv[intercepted_argc++] = argv[i];     // intercept --version
    else
      clang_argv_[clang_argc_++] = argv[i];
  }
  // argv should be nullptr-terminated
  iwyu_argv[iwyu_argc] = nullptr;
  intercepted_argv[intercepted_argc] = nullptr;
  clang_argv_[clang_argc_] = nullptr;

  ParseInterceptedCommandlineFlags(intercepted_argc, intercepted_argv);
  ParseIwyuCommandlineFlags(iwyu_argc, iwyu_argv);

  delete [] iwyu_argv;
  delete [] intercepted_argv;
}

OptionsParser::~OptionsParser() {
  delete [] clang_argv_;
}

CommandlineFlags::CommandlineFlags()
    : transitive_includes_only(false),
      verbose(getenv("IWYU_VERBOSE") ? atoi(getenv("IWYU_VERBOSE")) : 1),
      no_default_mappings(false),
      max_line_length(80),
      prefix_header_include_policy(CommandlineFlags::kAdd),
      pch_in_code(false),
      no_comments(false),
      update_comments(false),
      no_fwd_decls(false),
      quoted_includes_first(false),
      cxx17ns(false),
      exit_code_error(EXIT_SUCCESS),
      exit_code_always(EXIT_SUCCESS) {
  // Always keep Qt .moc includes; its moc compiler does its own IWYU analysis.
  keep.emplace("*.moc");
}

int CommandlineFlags::ParseArgv(int argc, char** argv) {
  static const struct option longopts[] = {
    {"check_also", required_argument, nullptr, 'c'},  // can be specified >once
    {"keep", required_argument, nullptr, 'k'},  // can be specified >once
    {"transitive_includes_only", no_argument, nullptr, 't'},
    {"verbose", required_argument, nullptr, 'v'},
    {"mapping_file", required_argument, nullptr, 'm'},
    {"no_default_mappings", no_argument, nullptr, 'n'},
    {"prefix_header_includes", required_argument, nullptr, 'x'},
    {"pch_in_code", no_argument, nullptr, 'h'},
    {"max_line_length", required_argument, nullptr, 'l'},
    {"no_comments", no_argument, nullptr, 'o'},
    {"update_comments", no_argument, nullptr, 'u'},
    {"no_fwd_decls", no_argument, nullptr, 'f'},
    {"quoted_includes_first", no_argument, nullptr, 'q' },
    {"cxx17ns", no_argument, nullptr, 'C'},
    {"error", optional_argument, nullptr, 'e'},
    {"error_always", optional_argument, nullptr, 'a'},
    {nullptr, 0, nullptr, 0}
  };
  static const char shortopts[] = "v:c:m:n";
  while (true) {
    switch (getopt_long(argc, argv, shortopts, longopts, nullptr)) {
      case 'c': AddGlobToReportIWYUViolationsFor(optarg); break;
      case 'k': AddGlobToKeepIncludes(optarg); break;
      case 't': transitive_includes_only = true; break;
      case 'v': verbose = atoi(optarg); break;
      case 'm': mapping_files.push_back(optarg); break;
      case 'n': no_default_mappings = true; break;
      case 'o': no_comments = true; break;
      case 'u': update_comments = true; break;
      case 'f': no_fwd_decls = true; break;
      case 'x':
        if (strcmp(optarg, "add") == 0) {
          prefix_header_include_policy = CommandlineFlags::kAdd;
        } else if (strcmp(optarg, "keep") == 0) {
          prefix_header_include_policy = CommandlineFlags::kKeep;
        } else if (strcmp(optarg, "remove") == 0) {
          prefix_header_include_policy = CommandlineFlags::kRemove;
        } else {
          PrintHelp("FATAL ERROR: unknown --prefix_header_includes value.");
          exit(EXIT_FAILURE);
        }
        break;
      case 'h': pch_in_code = true; break;
      case 'l':
        max_line_length = atoi(optarg);
        CHECK_((max_line_length >= 0) && "Max line length must be positive");
        break;
      case 'q': quoted_includes_first = true; break;
      case 'C': cxx17ns = true; break;
      case 'e':
        if (!optarg) {
          exit_code_error = EXIT_FAILURE;
        } else if (!ParseIntegerOptarg(optarg, &exit_code_error)) {
          PrintHelp("FATAL ERROR: --error argument must be valid integer.");
          exit(EXIT_FAILURE);
        }
        break;
      case 'a':
        if (!optarg) {
          exit_code_always = EXIT_FAILURE;
        } else if (!ParseIntegerOptarg(optarg, &exit_code_always)) {
          PrintHelp(
              "FATAL ERROR: --error_always argument must be valid "
              "integer.");
          exit(EXIT_FAILURE);
        }
        break;
      case -1: return optind;   // means 'no more input'
      default:
        PrintHelp("FATAL ERROR: unknown flag.");
        exit(EXIT_FAILURE);
        break;
    }
  }

  CHECK_UNREACHABLE_("All switches should be handled above");
}

// Though option -v prints version too, it isn't intercepted because it also
// provides other functionality like printing clang invocation, header search
// paths.
// TODO(vsapsai): provide IWYU version in Driver::PrintVersion when version
// callbacks are supported (see FIXME in Driver::PrintVersion).
static int ParseInterceptedCommandlineFlags(int argc, char** argv) {
  static const struct option longopts[] = {
    {"help", no_argument, nullptr, 'h'},
    {"version", no_argument, nullptr, 'v'},
    {nullptr, 0, nullptr, 0}
  };
  static const char shortopts[] = "";
  while (true) {
    switch (getopt_long(argc, argv, shortopts, longopts, nullptr)) {
      case 'h': PrintHelp(""); exit(EXIT_SUCCESS); break;
      case 'v': PrintVersion(); exit(EXIT_SUCCESS); break;
      case -1: return optind;   // means 'no more input'
      default:
        PrintHelp("FATAL ERROR: unknown flag.");
        exit(EXIT_FAILURE);
        break;
    }
  }
  return optind;  // unreachable
}

// Handles all iwyu-specific flags, like --verbose.  Returns the index into
// argv past all the iwyu commandline flags.
static int ParseIwyuCommandlineFlags(int argc, char** argv) {
  CHECK_(commandline_flags == nullptr && "Only parse commandline flags once");
  commandline_flags = new CommandlineFlags;
  const int retval = commandline_flags->ParseArgv(argc, argv);
  SetVerboseLevel(commandline_flags->verbose);

  VERRS(4) << "Setting verbose-level to " << commandline_flags->verbose << "\n";

  return retval;
}

// Make sure we put longer search-paths first, so iwyu will map
// /usr/include/c++/4.4/foo to <foo> rather than <c++/4.4/foo>.
static bool SortByDescendingLength(const HeaderSearchPath& left,
                                   const HeaderSearchPath& right) {
  return left.path.length() > right.path.length();
}

// Sorts them by descending length, does other kinds of cleanup.
static vector<HeaderSearchPath> NormalizeHeaderSearchPaths(
    const map<string, HeaderSearchPath::Type>& include_dirs_map) {
  vector<HeaderSearchPath> include_dirs;
  for (const auto& entry : include_dirs_map) {
    include_dirs.push_back(HeaderSearchPath(entry.first, entry.second));
  }

  sort(include_dirs.begin(), include_dirs.end(), &SortByDescendingLength);
  return include_dirs;
}

// Asks clang what the search-paths are for include files, normalizes
// them, and returns them in a vector.
static vector<HeaderSearchPath> ComputeHeaderSearchPaths(
    clang::HeaderSearch* header_search) {
  map<string, HeaderSearchPath::Type> search_path_map;
  for (auto it = header_search->system_dir_begin();
       it != header_search->system_dir_end(); ++it) {
    if (const DirectoryEntry* entry = it->getDir()) {
      const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName().str()));
      search_path_map[path] = HeaderSearchPath::kSystemPath;
    }
  }
  for (auto it = header_search->search_dir_begin();
       it != header_search->search_dir_end(); ++it) {
    if (const DirectoryEntry* entry = it->getDir()) {
      // search_dir_begin()/end() includes both system and user paths.
      // If it's a system path, it's already in the map, so everything
      // new is a user path.  The insert only 'takes' for new entries.
      const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName().str()));
      search_path_map.insert(make_pair(path, HeaderSearchPath::kUserPath));
    }
  }
  return NormalizeHeaderSearchPaths(search_path_map);
}

void InitGlobals(clang::SourceManager* sm, clang::HeaderSearch* header_search) {
  CHECK_(sm && "InitGlobals() needs a non-nullptr SourceManager");
  source_manager = sm;
  data_getter = new SourceManagerCharacterDataGetter(*source_manager);
  vector<HeaderSearchPath> search_paths =
      ComputeHeaderSearchPaths(header_search);
  SetHeaderSearchPaths(search_paths);
  include_picker = new IncludePicker(GlobalFlags().no_default_mappings);
  function_calls_full_use_cache = new FullUseCache;
  class_members_full_use_cache = new FullUseCache;

  for (const HeaderSearchPath& entry : search_paths) {
    const char* path_type_name =
        (entry.path_type == HeaderSearchPath::kSystemPath ? "system" : "user");
    VERRS(6) << "Search path: " << entry.path << " (" << path_type_name
             << ")\n";
  }

  // Add mappings.
  for (const string& mapping_file : GlobalFlags().mapping_files) {
    include_picker->AddMappingsFromFile(mapping_file);
  }
}

const CommandlineFlags& GlobalFlags() {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  return *commandline_flags;
}

CommandlineFlags* MutableGlobalFlagsForTesting() {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  return commandline_flags;
}

clang::SourceManager* GlobalSourceManager() {
  CHECK_(source_manager && "Must call InitGlobals() before calling this");
  return source_manager;
}

const IncludePicker& GlobalIncludePicker() {
  CHECK_(include_picker && "Must call InitGlobals() before calling this");
  return *include_picker;
}

IncludePicker* MutableGlobalIncludePicker() {
  CHECK_(include_picker && "Must call InitGlobals() before calling this");
  return include_picker;
}

const clang::PrintingPolicy& DefaultPrintPolicy() {
  return default_print_policy;
}

const SourceManagerCharacterDataGetter& DefaultDataGetter() {
  CHECK_(data_getter && "Must call InitGlobals() before calling this");
  return *data_getter;
}

FullUseCache* FunctionCallsFullUseCache() {
  return function_calls_full_use_cache;
}

FullUseCache* ClassMembersFullUseCache() {
  return class_members_full_use_cache;
}

void AddGlobToReportIWYUViolationsFor(const string& glob) {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  commandline_flags->check_also.insert(NormalizeFilePath(glob));
}

bool ShouldReportIWYUViolationsFor(const clang::FileEntry* file) {
  const string filepath = GetFilePath(file);
  for (const string& glob : GlobalFlags().check_also)
    if (GlobMatchesPath(glob.c_str(), filepath.c_str()))
      return true;
  return false;
}

void AddGlobToKeepIncludes(const string& glob) {
  CHECK_(commandline_flags && "Call ParseIwyuCommandlineFlags() before this");
  commandline_flags->keep.insert(NormalizeFilePath(glob));
}

bool ShouldKeepIncludeFor(const clang::FileEntry* file) {
  if (GlobalFlags().keep.empty())
    return false;
  const string filepath = GetFilePath(file);
  for (const string& glob : GlobalFlags().keep)
    if (GlobMatchesPath(glob.c_str(), filepath.c_str()))
      return true;
  return false;
}

void InitGlobalsAndFlagsForTesting() {
  CHECK_(commandline_flags == nullptr && "Only parse commandline flags once");
  CHECK_(include_picker == nullptr && "Only call InitGlobals[ForTesting] once");
  commandline_flags = new CommandlineFlags;
  source_manager = nullptr;
  data_getter = nullptr;
  include_picker = new IncludePicker(GlobalFlags().no_default_mappings);
  function_calls_full_use_cache = new FullUseCache;
  class_members_full_use_cache = new FullUseCache;

  // Use a reasonable default for the -I flags.
  map<string, HeaderSearchPath::Type> search_path_map;
  search_path_map["/usr/include/"] = HeaderSearchPath::kSystemPath;
  search_path_map["/usr/include/c++/4.3/"] = HeaderSearchPath::kSystemPath;
  search_path_map["/usr/include/c++/4.2/"] = HeaderSearchPath::kSystemPath;
  search_path_map["./"] = HeaderSearchPath::kUserPath;
  search_path_map["/usr/src/linux-headers-2.6.24-gg23/include/"] =
      HeaderSearchPath::kSystemPath;

  SetHeaderSearchPaths(NormalizeHeaderSearchPaths(search_path_map));
}

}  // namespace include_what_you_use
