#include <iostream>
#include <stdexcept>
#include <exception>
#include <getopt.h>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <sstream>
#include <opts.h>
#include <spinscale/cps/callableTracer.h>


OptionParser::Exception::Exception(std::string message_)
: message(std::move(message_))
{
}

const char* OptionParser::Exception::what() const noexcept
{
    return message.c_str();
}

OptionsParserError::OptionsParserError(
    const std::string& errorMessage, const OptionParser& parser
)
: OptionParser::Exception(errorMessage + "\n" + parser.getUsage())
{
}

JustPrintUsageNoError::JustPrintUsageNoError(const OptionParser& parser)
: OptionParser::Exception(parser.getUsage())
{
}

struct option OptionParser::longOptions[] = {
    {"dapspec", required_argument, 0, 's'},
    {"spec", required_argument, 0, 's'},
    {"dapfile", required_argument, 0, 'd'},
    {"api-lib", required_argument, 0, 'a'},
    {"apilib", required_argument, 0, 'a'},
    {"api", required_argument, 0, 'a'},
    {"lib", required_argument, 0, 'a'},
    {"api-lib-path", required_argument, 0, 'p'},
    {"apipath", required_argument, 0, 'p'},
    {"libpath", required_argument, 0, 'p'},
    {"comparator-lib", required_argument, 0, 'c'},
    {"comp-lib", required_argument, 0, 'c'},
    {"complib", required_argument, 0, 'c'},
    {"verbose", no_argument, 0, 'v'},
    {"trace-callables", no_argument, 0, 't'},
    {"call-trace", no_argument, 0, 't'},
    {"calltrace", no_argument, 0, 't'},
    {"help", no_argument, 0, 'h'},
    {0, 0, 0, 0}
};

void OptionParser::parseArguments(int argc, char *argv[], char **envp)
{
    (void)envp;
    int opt;
    int optionIndex = 0;

    argv0 = argv[0];

    optind = 1; // Reset optind to 1 before parsing
    opterr = 0;
    while ((opt = getopt_long(
        argc, argv, "s:d:a:p:c:vht?", longOptions, &optionIndex)) != -1)
    {
        switch (opt)
        {
        case 's':
            if (!dapSpecs.empty()) {
                dapSpecs += "||";
            }
            dapSpecs += std::string(optarg);
            break;
        case 'd':
            dapSpecFiles.push_back(optarg);
            break;
        case 'a':
            senseApiLibs.push_back(optarg);
            break;
        case 'p':
        {
            struct stat info;

            if (stat(optarg, &info) != 0 || !(info.st_mode & S_IFDIR))
            {
                throw OptionsParserError(
                    std::string(__func__) + " - The specified path is not a "
                    "directory: " + optarg, *this);
            }

            senseApiLibPath.push_back(optarg);
            break;
        }
        case 'c':
            comparatorLibs.push_back(optarg);
            break;
        case 'v':
            verbose = true;
            break;
        case 't':
            traceCallables = true;
            break;
        case 'h':
            throw JustPrintUsageNoError(*this);
        case '?':
            throw OptionsParserError(
                std::string(__func__) + " - Invalid argument encountered: "
                + std::string(argv[optind - 1]), *this);
        }
    }

    sscl::cps::CallableTracer::optTraceCallables = traceCallables;
}

std::string OptionParser::getUsage() const
{
    return "Usage: " + argv0 + " [-s|--dapspec|--spec <device_spec>] "
           "[-d|--dapfile <DAP_spec_file>] "
           "[-a|--api-lib|--apilib|--api|--lib <filename>] "
           "[-c|--comparator-lib|--comp-lib|--complib <filename>] "
           "[-p|--api-lib-path|--apipath|--libpath <directory>] "
           "[-v|--verbose] "
           "[-t|--trace-callables|--call-trace|--calltrace] "
           "[-h|--help]\n\n"
           "Example DAP spec:\n"
           "  -s '+edev|my-cam|video-qualeiface|v4l2-video(fps-hz=30)|v4l2()|/dev/video0'";
}

std::string OptionParser::stringifyOptions(void) const
{
    std::ostringstream oss;

    if (verbose) {
        oss << "Verbose mode is on" << std::endl;
    }

    if (traceCallables) {
        oss << "Callable tracing is enabled" << std::endl;
    }

    oss << "Cmdline DAP Specs: " << dapSpecs << std::endl;

    oss << "DAP Spec Files: ";
    for (const auto& file : dapSpecFiles) {
        oss << file << " ";
    }
    oss << std::endl;

    oss << "Stim Buff API Library Paths: ";
    for (const auto& path : senseApiLibPath) {
        oss << path << " ";
    }
    oss << std::endl;
    oss << "Stim Buff API Libraries: ";
    for (const auto& lib : senseApiLibs) {
        oss << lib << " ";
    }
    oss << std::endl;

    oss << "Comparator Libraries: ";
    for (const auto& lib : comparatorLibs) {
        oss << lib << " ";
    }
    oss << std::endl;

    return oss.str();
}
