#define private public
#include "emc/rs274ngc/rs274ngc_interp.hh"
#undef private

#include <cstring>
#include <cstdio>
#include <filesystem>
#include <iostream>
#include <string>
#include <vector>

#include "emc/ini/inifile.hh"
#include "emc/rs274ngc/interp_internal.hh"
#include "emc/rs274ngc/rs274ngc_return.hh"

#ifndef LINUXCNC_VENDOR_DIR
#error "LINUXCNC_VENDOR_DIR must point at wasm-port/vendor/linuxcnc"
#endif

namespace {

struct ExpectedRemap {
    int mcode;
    const char *name;
    const char *ngc;
};

struct MachineCase {
    const char *label;
    const char *ini_rel;
    std::vector<ExpectedRemap> expected;
};

std::filesystem::path vendor_path(const std::string &rel)
{
    return std::filesystem::path(LINUXCNC_VENDOR_DIR) / rel;
}

void print_bool(const std::string &name, bool value)
{
    std::cout << name << "=" << (value ? 1 : 0) << "\n";
}

bool check_remap(Interp &interp, const std::string &label, const ExpectedRemap &expected)
{
    const auto by_name = interp.remapping(expected.name);
    const auto by_mcode = interp._setup.m_remapped[expected.mcode];

    const bool name_ok = by_name != nullptr;
    const bool mcode_ok = by_mcode != nullptr && by_mcode == by_name;
    const bool ngc_ok = by_mcode != nullptr && by_mcode->remap_ngc != nullptr &&
                        std::strcmp(by_mcode->remap_ngc, expected.ngc) == 0;
    const bool modal_group_ok = by_mcode != nullptr && by_mcode->modal_group == 10;
    const bool no_python_ok = by_mcode != nullptr && by_mcode->remap_py == nullptr &&
                              by_mcode->prolog_func == nullptr &&
                              by_mcode->epilog_func == nullptr;
    const bool mcode_remappable = interp.is_m_code_remappable(expected.mcode);

    const std::string prefix = label + "_" + expected.name;
    print_bool(prefix + "_name_lookup", name_ok);
    print_bool(prefix + "_mcode_lookup", mcode_ok);
    print_bool(prefix + "_ngc", ngc_ok);
    print_bool(prefix + "_modalgroup10", modal_group_ok);
    print_bool(prefix + "_no_python", no_python_ok);
    print_bool(prefix + "_linuxcnc_mcode_remappable", mcode_remappable);

    return name_ok && mcode_ok && ngc_ok && modal_group_ok && no_python_ok &&
           mcode_remappable;
}

bool check_machine(const MachineCase &machine)
{
    const auto ini_path = vendor_path(machine.ini_rel);
    const auto machine_dir = ini_path.parent_path();
    const auto remap_dir = machine_dir / "remap_subs";

    linuxcnc::IniFile ini(ini_path.string());
    const bool opened = static_cast<bool>(ini);
    print_bool(std::string(machine.label) + "_ini_open", opened);
    if (!opened) {
        return false;
    }

    Interp interp;
    std::snprintf(interp._setup.program_prefix, sizeof(interp._setup.program_prefix), "%s",
                  machine_dir.c_str());
    interp._setup.subroutines[0] = strstore(remap_dir.c_str());

    bool ok = true;
    int remap_count = 0;
    int line_number = 0;
    while (auto remap = ini.findString(++remap_count, "REMAP", "RS274NGC")) {
        line_number = ini.lineOf(remap_count, "REMAP", "RS274NGC").second;
        const int rc = interp.parse_remap(remap->c_str(), line_number);
        std::cout << machine.label << "_parse_remap_" << remap_count << "=" << rc << "\n";
        ok &= rc == INTERP_OK;
    }
    remap_count -= 1;
    std::cout << machine.label << "_parsed_remap_count=" << remap_count << "\n";
    ok &= remap_count == static_cast<int>(machine.expected.size());

    std::cout << machine.label << "_m_remapped_count=" << interp._setup.m_remapped.size()
              << "\n";
    ok &= interp._setup.m_remapped.size() == machine.expected.size();

    for (const auto &expected : machine.expected) {
        ok &= check_remap(interp, machine.label, expected);
    }

    print_bool(std::string(machine.label) + "_linuxcnc_ngc_remaps_parsed", ok);
    return ok;
}

}  // namespace

int main()
{
    const MachineCase cases[] = {
        {
            "xyzac_trt",
            "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
            {
                {428, "M428", "428remap"},
                {429, "M429", "429remap"},
                {430, "M430", "430remap"},
            },
        },
        {
            "xyzbc_trt",
            "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini",
            {
                {428, "M428", "428remap"},
                {429, "M429", "429remap"},
                {430, "M430", "430remap"},
            },
        },
        {
            "xyzab_tdr",
            "configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini",
            {
                {428, "M428", "428remap"},
                {429, "M429", "429remap"},
            },
        },
        {
            "bridgemill",
            "configs/sim/axis/vismach/5axis/bridgemill/5axis.ini",
            {
                {428, "M428", "428remap"},
                {429, "M429", "429remap"},
                {430, "M430", "430remap"},
            },
        },
    };

    bool ok = true;
    for (const auto &machine : cases) {
        ok &= check_machine(machine);
    }

    print_bool("fiveaxis_linuxcnc_remap_parse_path", ok);
    return ok ? 0 : 1;
}
