#include "cmdline.hpp"

#include <climits>
#include <cstring>
#include <stdexcept>
#include <thread>

using namespace synth;

unsigned const kDefaultMaxIdSz = 128;

static char const* getOptVal(char const* const* opt)
{
    if (!opt[1])
        throw std::runtime_error("Missing value for " + std::string(opt[0]));
    return opt[1];
}

static void getOptVal(char const* const* opt, char const*& out)
{
    if (out)
        throw std::runtime_error("Duplicate option " + std::string(opt[0]));
    out = getOptVal(opt);
}

static unsigned getUintOptVal(char const* const* opt)
{
    char const* nStr = getOptVal(opt);
    int n;
    try {
        n = std::stoi(nStr);
    } catch (std::exception const& e) {
        throw std::runtime_error(
            std::string("Integer expected for ") + opt[0] + ": " + e.what());
    }
    if (n < 0) {
        throw std::runtime_error(
            std::string("Value for ") + opt[0] + " must not be negative.");
    }
    return static_cast<unsigned>(n);
}

CmdLineArgs CmdLineArgs::parse(int argc, char const* const* argv)
{
    if (argc < 3)
        throw std::runtime_error("Too few arguments.");
    CmdLineArgs r = {};
    r.maxIdSz = kDefaultMaxIdSz;
    bool maxIdSzFound = false;
    bool foundCmd = false;
    int i;
    for (i = 1; i < argc; ++i) {
        if (argv[i][0] != '-') {
            r.inOutDirs.push_back({argv[i], nullptr});
        } else if (!std::strcmp(argv[i], "-e")) {
            r.clangArgs.push_back(getOptVal(argv + i++));
        } else if (!std::strcmp(argv[i], "-t")) {
             getOptVal(argv + i++, r.templateFile);
        } else if (!std::strcmp(argv[i], "-j")) {
            if (r.nThreads != 0)
                throw std::runtime_error("Duplicate option -j.");
            r.nThreads = getUintOptVal(argv + i++);
        } else if (!std::strcmp(argv[i], "--max-id-sz")) {
            if (maxIdSzFound)
                throw std::runtime_error("Duplicate option --max-id-sz.");
            r.maxIdSz = getUintOptVal(argv + i++);
        } else if (!std::strcmp(argv[i], "-o")) {
            if (r.inOutDirs.empty()) {
                throw std::runtime_error(
                    "-o without preceeding input directory");
            }
            getOptVal(argv + i++, r.inOutDirs.back().second);
        } else if (!std::strcmp(argv[i], "--doxytags")) {
            std::pair<char const*, char const*> tagOpts;
            getOptVal(argv + i++, tagOpts.first);
            getOptVal(argv + i++, tagOpts.second);
            r.doxyTagFiles.push_back(std::move(tagOpts));
        } else if (!std::strcmp(argv[i], "--cmd")) {
            // These come before any extra-args, thus use insert(begin(), ...).
            r.clangArgs.insert(r.clangArgs.begin(), argv + i + 1, argv + argc);
            r.nClangArgs = argc - i - 1;
            foundCmd = true;
            i = argc;
            break;
        } else if (!std::strcmp(argv[i], "--db")) {
            r.compilationDbDir = getOptVal(argv + i++);
            foundCmd = true;
            ++i;
            break;
        } else {
            throw std::runtime_error(std::string("Bad argument ") + argv[i]);
        }
    }

    if (!foundCmd)
        throw std::runtime_error("Missing command.");
    if (i != argc)
        throw std::runtime_error("Superfluous commandline arguments.");

    if (r.nThreads == 0)
        r.nThreads = std::thread::hardware_concurrency();
    for (auto& dir : r.inOutDirs) {
        if (!dir.second)
            dir.second = ".";
    }

    return r;
}