#include "command/argument_parser.h"

#include <array>

#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/Driver/Driver.h"
#include "clang/Driver/Options.h"
#include "clang/Driver/Types.h"

namespace clice {

namespace {

namespace opt = llvm::opt;
namespace driver = clang::driver;

/// Access private members of OptTable via the Thief pattern.
bool enable_dash_dash_parsing(const opt::OptTable& table);
bool enable_grouped_short_options(const opt::OptTable& table);

template <auto MP1, auto MP2>
struct Thief {
    friend bool enable_dash_dash_parsing(const opt::OptTable& table) {
        return table.*MP1;
    }

    friend bool enable_grouped_short_options(const opt::OptTable& table) {
        return table.*MP2;
    }
};

template struct Thief<&opt::OptTable::DashDashParsing, &opt::OptTable::GroupedShortOptions>;

auto& option_table = driver::getDriverOptTable();

}  // namespace

std::unique_ptr<llvm::opt::Arg> ArgumentParser::parse_one(unsigned& index) {
    assert(!enable_dash_dash_parsing(option_table));
    assert(!enable_grouped_short_options(option_table));
    return option_table.ParseOneArg(*this, index, opt::Visibility(visibility_mask));
}

using ID = clang::driver::options::ID;

bool is_discarded_option(unsigned id) {
    switch(id) {
        /// Input file, unknown args, and output — we manage these ourselves.
        case ID::OPT_INPUT:
        case ID::OPT_UNKNOWN:
        case ID::OPT__DASH_DASH:
        case ID::OPT_c:
        case ID::OPT_o:
        case ID::OPT_dxc_Fc:
        case ID::OPT_dxc_Fo:
        case ID::OPT__SLASH_Fo:
        case ID::OPT__SLASH_Fd:

        /// PCH building.
        case ID::OPT_emit_pch:
        case ID::OPT_include_pch:
        case ID::OPT__SLASH_Yu:
        case ID::OPT__SLASH_Fp:

        /// Dependency scan.
        case ID::OPT_E:
        case ID::OPT_M:
        case ID::OPT_MM:
        case ID::OPT_MD:
        case ID::OPT_MMD:
        case ID::OPT_MF:
        case ID::OPT_MT:
        case ID::OPT_MQ:
        case ID::OPT_MG:
        case ID::OPT_MP:
        case ID::OPT_show_inst:
        case ID::OPT_show_encoding:
        case ID::OPT_show_includes:
        case ID::OPT__SLASH_showFilenames:
        case ID::OPT__SLASH_showFilenames_:
        case ID::OPT__SLASH_showIncludes:
        case ID::OPT__SLASH_showIncludes_user:

        /// C++ modules — we handle these ourselves.
        case ID::OPT_fmodule_file:
        case ID::OPT_fmodule_output:
        case ID::OPT_fprebuilt_module_path: return true;

        default: return false;
    }
}

bool is_user_content_option(unsigned id) {
    switch(id) {
        case ID::OPT_I:
        case ID::OPT_isystem:
        case ID::OPT_iquote:
        case ID::OPT_idirafter:
        case ID::OPT_D:
        case ID::OPT_U:
        case ID::OPT_include: return true;
        default: return false;
    }
}

bool is_include_path_option(unsigned id) {
    switch(id) {
        case ID::OPT_I:
        case ID::OPT_isystem:
        case ID::OPT_iquote:
        case ID::OPT_idirafter: return true;
        default: return false;
    }
}

bool is_xclang_option(unsigned id) {
    return id == ID::OPT_Xclang;
}

bool is_toolchain_option(unsigned id) {
    switch(id) {
        case ID::OPT_target:
        case ID::OPT_target_legacy_spelling:
        case ID::OPT_isysroot:
        case ID::OPT__sysroot_EQ:
        case ID::OPT__sysroot:
        case ID::OPT_stdlib_EQ:
        case ID::OPT_gcc_toolchain:
        case ID::OPT_gcc_install_dir_EQ:
        case ID::OPT_nostdinc:
        case ID::OPT_nostdincxx:
        case ID::OPT_std_EQ:
        case ID::OPT_x: return true;
        default: return false;
    }
}

std::optional<std::uint32_t> get_option_id(llvm::StringRef argument) {
    llvm::SmallString<64> buffer = argument;

    if(argument.ends_with("=")) {
        buffer += "placeholder";
    }

    unsigned index = 1;
    std::array arguments = {"clang++", buffer.c_str(), "placeholder"};
    llvm::opt::InputArgList arg_list(arguments.data(), arguments.data() + arguments.size());

    if(auto arg = option_table.ParseOneArg(arg_list, index)) {
        return arg->getOption().getID();
    } else {
        return {};
    }
}

llvm::StringRef resource_dir() {
    static std::string dir = [] {
        // Use address of this lambda to locate our binary via dladdr/proc.
        static int anchor;
        auto exe = llvm::sys::fs::getMainExecutable("", &anchor);
        if(exe.empty()) {
            return std::string{};
        }
        return clang::driver::Driver::GetResourcesPath(exe);
    }();
    return dir;
}

bool is_codegen_option(unsigned id, const llvm::opt::Option& opt) {
    /// Debug info options form a group (-g, -gdwarf-*, -gsplit-dwarf, etc.).
    if(opt.matches(ID::OPT_DebugInfo_Group)) {
        return true;
    }

    switch(id) {
        /// Position-independent code — pure codegen, no macro or semantic effect.
        case ID::OPT_fPIC:
        case ID::OPT_fno_PIC:
        case ID::OPT_fpic:
        case ID::OPT_fno_pic:
        case ID::OPT_fPIE:
        case ID::OPT_fno_PIE:
        case ID::OPT_fpie:
        case ID::OPT_fno_pie:

        /// Frame pointer and unwind tables — pure codegen.
        case ID::OPT_fomit_frame_pointer:
        case ID::OPT_fno_omit_frame_pointer:
        case ID::OPT_funwind_tables:
        case ID::OPT_fno_unwind_tables:
        case ID::OPT_fasynchronous_unwind_tables:
        case ID::OPT_fno_asynchronous_unwind_tables:

        /// Stack protection — pure codegen.
        case ID::OPT_fstack_protector:
        case ID::OPT_fstack_protector_strong:
        case ID::OPT_fstack_protector_all:
        case ID::OPT_fno_stack_protector:

        /// Section splitting, LTO, semantic interposition — pure codegen/linker.
        case ID::OPT_fdata_sections:
        case ID::OPT_fno_data_sections:
        case ID::OPT_ffunction_sections:
        case ID::OPT_fno_function_sections:
        case ID::OPT_flto:
        case ID::OPT_flto_EQ:
        case ID::OPT_fno_lto:
        case ID::OPT_fsemantic_interposition:
        case ID::OPT_fno_semantic_interposition:
        case ID::OPT_fvisibility_inlines_hidden:

        /// Diagnostics output formatting — doesn't affect analysis.
        case ID::OPT_fcolor_diagnostics:
        case ID::OPT_fno_color_diagnostics:

        /// Floating-point codegen — doesn't define macros (unlike -ffast-math).
        case ID::OPT_ftrapping_math:
        case ID::OPT_fno_trapping_math: return true;

        default: return false;
    }
}

std::string print_argv(llvm::ArrayRef<const char*> args) {
    std::string buf;
    llvm::raw_string_ostream os(buf);
    bool sep = false;
    for(llvm::StringRef arg: args) {
        if(sep)
            os << ' ';
        sep = true;
        if(llvm::all_of(arg, llvm::isPrint) &&
           arg.find_first_of(" \t\n\"\\") == llvm::StringRef::npos) {
            os << arg;
            continue;
        }
        os << '"';
        os.write_escaped(arg, /*UseHexEscapes=*/true);
        os << '"';
    }
    return std::move(os.str());
}

unsigned default_visibility(llvm::StringRef driver) {
    namespace options = clang::driver::options;
    auto name = llvm::sys::path::filename(driver);
    name.consume_back(".exe");

    auto is_cl = [](llvm::StringRef s) {
        return s.equals_insensitive("cl") || s.equals_insensitive("clang-cl");
    };

    /// cl.exe and clang-cl.exe both need MSVC-style /options.
    /// Also handle versioned names like clang-cl-17, clang-cl-17.0.1.
    if(is_cl(name) || is_cl(name.rtrim("0123456789.-"))) {
        return ~0u;
    }
    /// Exclude CLOption to prevent /U, /D, /I from matching Unix paths.
    return ~static_cast<unsigned>(options::CLOption);
}

bool is_c_family_file(llvm::StringRef filename) {
    namespace types = clang::driver::types;
    auto ext = llvm::sys::path::extension(filename);
    if(ext.empty()) {
        return false;
    }
    /// Drop the leading dot: ".cpp" → "cpp".
    auto type = types::lookupTypeForExtension(ext.drop_front());
    return type != types::TY_INVALID && types::isAcceptedByClang(type);
}

}  // namespace clice
