#include <kconfig.hpp>

#include <convert.h>
#include <cargs.h>
#include <printf.h>

#include "kernel.h"

// TODO: Implement proper fprintf
EXTERNC void fprintf(FILE *stream, const char *Format, ...)
{
    va_list args;
    va_start(args, Format);
    vprintf(Format, args);
    va_end(args);
    UNUSED(stream);
}

// TODO: Implement proper fputs
EXTERNC void fputs(const char *s, FILE *stream)
{
    printf("%s", s);
    UNUSED(stream);
}

static struct cag_option ConfigOptions[] = {
    {.identifier = 'a',
     .access_letters = "aA",
     .access_name = "alloc",
     .value_name = "TYPE",
     .description = "Memory allocator to use"},

    {.identifier = 'c',
     .access_letters = "cC",
     .access_name = "cores",
     .value_name = "VALUE",
     .description = "Number of cores to use (0 = all; 1 is the first code, not 0)"},

    {.identifier = 'p',
     .access_letters = "pP",
     .access_name = "ioapicirq",
     .value_name = "VALUE",
     .description = "Which core will be used for I/O APIC interrupts"},

    {.identifier = 't',
     .access_letters = "tT",
     .access_name = "tasking",
     .value_name = "MODE",
     .description = "Tasking mode (multi, single)"},

    {.identifier = 'd',
     .access_letters = "dD",
     .access_name = "drvdir",
     .value_name = "PATH",
     .description = "Directory to load drivers from"},

    {.identifier = 'i',
     .access_letters = "iI",
     .access_name = "init",
     .value_name = "PATH",
     .description = "Path to init program"},

    {.identifier = 'l',
     .access_letters = NULL,
     .access_name = "udl",
     .value_name = "BOOL",
     .description = "Unlock the deadlock after 10 retries"},

    {.identifier = 'o',
     .access_letters = NULL,
     .access_name = "ioc",
     .value_name = "BOOL",
     .description = "Enable Interrupts On Crash. If enabled, the navigation keys will be enabled on crash"},

     {.identifier = 's',
     .access_letters = NULL,
     .access_name = "simd",
     .value_name = "BOOL",
     .description = "Enable SIMD instructions"},

    {.identifier = 'h',
     .access_letters = "h",
     .access_name = "help",
     .value_name = NULL,
     .description = "Show help on screen and halt"}};

KernelConfig ParseConfig(char *Config)
{
    int argc = 0;
    char **argv = nullptr;

    struct KernelConfig config = {Memory::MemoryAllocatorType::XallocV1,
                                  0,
                                  {'/', 's', 'y', 's', 't', 'e', 'm', '/', 'd', 'r', 'i', 'v', 'e', 'r', 's', '\0'},
                                  {'/', 's', 'y', 's', 't', 'e', 'm', '/', 'i', 'n', 'i', 't', '\0'},
                                  true,
                                  0,
                                  0,
                                  false,
                                  false};

    if (Config == NULL)
    {
        KPrint("Warning! Empty kernel parameters! Using default values.");
        return config;
    }
    else
    {
        KPrint("Kernel parameters: %s", Config);
        goto Parse;
    }

Parse:
{
    // Based on https://stackoverflow.com/a/2568792
    char *DestinationBuffer;
    char *TempScan;

    char **TempBuffer = (char **)kmalloc(sizeof(char *) * 10);
    char **ArgvTemp = TempBuffer;

    while (isspace((unsigned char)*Config))
        Config++;

    Config = TempScan = strdup(Config);

    int Itr = 10;
    while (1)
    {
        while (isspace((unsigned char)*TempScan))
            TempScan++;
        if (*TempScan == '\0')
            break;

        if (++argc >= Itr)
        {
            Itr += Itr / 2;
            TempBuffer = (char **)krealloc(TempBuffer, Itr * sizeof(char *));
            ArgvTemp = TempBuffer + (argc - 1);
        }

        *(ArgvTemp++) = DestinationBuffer = TempScan;

        while (1)
        {
            char TempChar0 = *(TempScan++);
            switch (TempChar0)
            {
            case '\0':
                goto Completed;
            case ' ':
            case '\t':
            case '\n':
            case '\f':
            case '\r':
            case '\v':
            case '\b':
                goto EmptyBufferAndComplete;
            case '\\':
            {
                if ((*(DestinationBuffer++) = *(TempScan++)) == '\0')
                    goto Completed;
                break;
            }
            case '\'':
            {
                while (1)
                {
                    char TempChar1 = (*TempScan)++;
                    switch (TempChar1)
                    {
                    case '\0':
                    {
                        KPrint("\eFF2200Unterminated string constant in kernel parameters. (0)");
                        CPU::Stop();
                        break;
                    }
                    case '\'':
                        *DestinationBuffer = '\0';
                        goto OutsideLoop;
                    case '\\':
                        TempChar1 = (*TempScan)++;
                        switch (TempChar1)
                        {
                        case '\0':
                        {
                            KPrint("\eFF2200Unterminated string constant in kernel parameters. (1)");
                            CPU::Stop();
                            break;
                        }
                        default:
                            *(DestinationBuffer++) = '\\';
                            break;

                        case '\\':
                        case '\'':
                            break;
                        }
                        [[fallthrough]];
                    default:
                        *(DestinationBuffer++) = TempChar1;
                        break;
                    }
                }
            OutsideLoop:
                break;
            }
            case '"':
            {
                while (1)
                {
                    char TempChar2 = *(TempScan++);
                    switch (TempChar2)
                    {
                    case '\0':
                    {
                        KPrint("\eFF2200Unterminated string constant in kernel parameters. (2)");
                        CPU::Stop();
                        break;
                    }
                    case '"':
                        (*DestinationBuffer) = '\0';
                        goto OutsideLoop2;
                    case '\\':
                    {
                        char TempChar3 = (*TempScan)++;
                        switch (TempChar3)
                        {
                        case 'a':
                            TempChar3 = '\a';
                            break;
                        case 'b':
                            TempChar3 = '\b';
                            break;
                        case 't':
                            TempChar3 = '\t';
                            break;
                        case 'n':
                            TempChar3 = '\n';
                            break;
                        case 'v':
                            TempChar3 = '\v';
                            break;
                        case 'f':
                            TempChar3 = '\f';
                            break;
                        case 'r':
                            TempChar3 = '\r';
                            break;
                        case '\0':
                        {
                            KPrint("\eFF2200Unterminated string constant in kernel parameters. (3)");
                            CPU::Stop();
                        }
                        }
                        TempChar2 = TempChar3;
                        [[fallthrough]];
                    }
                    default:
                        *(DestinationBuffer++) = TempChar2;
                        break;
                    }
                OutsideLoop2:
                    break;
                }
                break;
            }
            default:
                *(DestinationBuffer++) = TempChar0;
            }
        }

    EmptyBufferAndComplete:
        *DestinationBuffer = '\0';
    }

    KPrint("Failed to parse kernel parameters string: %s", Config);
    CPU::Stop();

Completed:
    argv = TempBuffer;
    if (argc == 0)
        kfree((void *)Config);

    goto ParseSuccess;
}

ParseSuccess:
{
#ifdef DEBUG
    for (int i = 0; i < argc; i++)
        KPrint("\e22AAFFargv[%d] = %s", i, argv[i]);
    KPrint("\e22AAFFargc = %d", argc);
#endif
    char identifier;
    const char *value;
    cag_option_context context;

    cag_option_prepare(&context, ConfigOptions, CAG_ARRAY_SIZE(ConfigOptions), argc, argv);
    while (cag_option_fetch(&context))
    {
        identifier = cag_option_get(&context);
        switch (identifier)
        {
        case 'a':
        {
            value = cag_option_get_value(&context);
            if (strcmp(value, "xallocv1") == 0)
            {
                KPrint("\eAAFFAAUsing XallocV1 as memory allocator");
                config.AllocatorType = Memory::MemoryAllocatorType::XallocV1;
            }
            else if (strcmp(value, "liballoc11") == 0)
            {
                KPrint("\eAAFFAAUsing Liballoc11 as memory allocator");
                config.AllocatorType = Memory::MemoryAllocatorType::liballoc11;
            }
            else if (strcmp(value, "pages") == 0)
            {
                KPrint("\eAAFFAAUsing Pages as memory allocator");
                config.AllocatorType = Memory::MemoryAllocatorType::Pages;
            }
            else
            {
                KPrint("\eAAFFAAUnknown memory allocator: %s", value);
                config.AllocatorType = Memory::MemoryAllocatorType::None;
            }
            break;
        }
        case 'c':
        {
            value = cag_option_get_value(&context);
            KPrint("\eAAFFAAUsing %s cores", atoi(value) ? value : "all");
            config.Cores = atoi(value);
            break;
        }
        case 'p':
        {
            value = cag_option_get_value(&context);
            KPrint("\eAAFFAARedirecting I/O APIC interrupts to %s%s", atoi(value) ? "core " : "", atoi(value) ? value : "BSP");
            config.IOAPICInterruptCore = atoi(value);
            break;
        }
        case 't':
        {
            value = cag_option_get_value(&context);
            if (strcmp(value, "multi") == 0)
            {
                KPrint("\eAAFFAAUsing Multi-Tasking Scheduler");
                config.SchedulerType = 1;
            }
            else if (strcmp(value, "single") == 0)
            {
                KPrint("\eAAFFAAUsing Mono-Tasking Scheduler");
                config.SchedulerType = 0;
            }
            else
            {
                KPrint("\eAAFFAAUnknown scheduler: %s", value);
                config.SchedulerType = 0;
            }
            break;
        }
        case 'd':
        {
            value = cag_option_get_value(&context);
            strncpy(config.DriverDirectory, value, strlen(value));
            KPrint("\eAAFFAAUsing %s as driver directory", value);
            break;
        }
        case 'i':
        {
            value = cag_option_get_value(&context);
            strncpy(config.InitPath, value, strlen(value));
            KPrint("\eAAFFAAUsing %s as init program", value);
            break;
        }
        case 'o':
        {
            value = cag_option_get_value(&context);
            strcmp(value, "true") ? config.InterruptsOnCrash = false : config.InterruptsOnCrash = true;
            KPrint("\eAAFFAAInterrupts on crash: %s", value);
            break;
        }
        case 'l':
        {
            value = cag_option_get_value(&context);
            strcmp(value, "true") ? config.UnlockDeadLock = false : config.UnlockDeadLock = true;
            KPrint("\eAAFFAAUnlocking the deadlock after 10 retries");
            break;
        }
        case 's':
        {
            value = cag_option_get_value(&context);
            strcmp(value, "true") ? config.SIMD = false : config.SIMD = true;
            KPrint("\eAAFFAASingle Instruction, Multiple Data (SIMD): %s", value);
            break;
        }
        case 'h':
        {
            KPrint("\n---------------------------------------------------------------------------\nUsage: kernel.fsys [OPTION]...\nKernel configuration.");
            cag_option_print(ConfigOptions, CAG_ARRAY_SIZE(ConfigOptions), nullptr);
            KPrint("\eFF2200System Halted.");
            CPU::Stop();
        }
        }
    }
    return config;
}
    return config;
}