#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>

#include "emc/ini/inifile.hh"

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

namespace {

struct MachineCase {
    const char *label;
    const char *ini_rel;
    const char *machine;
    const char *kinematics;
    const char *coordinates;
    const char *joints;
    const char *parameter_file;
    const char *tool_table;
    bool expects_m430;
};

std::string vendor_path(const std::string &rel)
{
    return std::string(LINUXCNC_VENDOR_DIR) + "/" + rel;
}

std::string read_text(const std::string &path)
{
    std::ifstream input(path);
    return std::string(
        std::istreambuf_iterator<char>(input),
        std::istreambuf_iterator<char>());
}

bool contains(const std::string &text, const std::string &needle)
{
    return text.find(needle) != std::string::npos;
}

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

bool check_string(
    const linuxcnc::IniFile &ini,
    const std::string &label,
    const std::string &section,
    const std::string &tag,
    const std::string &expected)
{
    const auto actual = ini.findString(tag, section);
    std::cout << label << "_" << section << "." << tag << "="
              << (actual ? *actual : "<missing>") << "\n";
    const bool ok = actual && *actual == expected;
    print_bool(label + "_" + section + "." + tag + "_ok", ok);
    return ok;
}

bool check_remap_entry(
    const std::vector<std::string> &remaps,
    const std::string &label,
    const std::string &mcode,
    const std::string &ngc)
{
    const std::string mcode_needle = "REMAP = " + mcode;
    const std::string ngc_needle = "ngc=" + ngc;
    bool ok = false;
    for (const auto &remap : remaps) {
        if (contains(remap, mcode) && contains(remap, ngc_needle)) {
            ok = true;
        }
    }
    print_bool(label + "_remap_" + mcode + "_" + ngc, ok);
    (void)mcode_needle;
    return ok;
}

bool check_remap_file(
    const std::string &label,
    const std::string &dir_rel,
    const std::string &mcode,
    const std::string &expected_kinstype)
{
    const std::string path = vendor_path(dir_rel + "/remap_subs/" + mcode.substr(1) + "remap.ngc");
    const std::string text = read_text(path);
    const bool ok =
        contains(text, "o<" + mcode.substr(1) + "remap>sub") &&
        contains(text, "#<kinstype> = " + expected_kinstype) &&
        contains(text, "#<SWITCHKINS_PIN> = 3") &&
        contains(text, "#<_hal[motion.switchkins-type]>") &&
        contains(text, "M68") &&
        contains(text, "M66") &&
        contains(text, "o<" + mcode.substr(1) + "remap>endsub");
    print_bool(label + "_" + mcode + "_remap_file_from_linuxcnc", ok);
    return ok;
}

bool check_text_mcodes(
    const std::string &label,
    const std::string &rel,
    bool expects_m428,
    bool expects_m429)
{
    const std::string text = read_text(vendor_path(rel));
    const bool ok =
        (!expects_m428 || contains(text, "M428")) &&
        (!expects_m429 || contains(text, "M429"));
    print_bool(label + "_switch_mcodes_from_linuxcnc", ok);
    return ok;
}

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

    ok &= check_string(ini, machine.label, "EMC", "MACHINE", machine.machine);
    ok &= check_string(ini, machine.label, "KINS", "KINEMATICS", machine.kinematics);
    ok &= check_string(ini, machine.label, "TRAJ", "COORDINATES", machine.coordinates);
    ok &= check_string(ini, machine.label, "KINS", "JOINTS", machine.joints);
    ok &= check_string(ini, machine.label, "RS274NGC", "SUBROUTINE_PATH", "./remap_subs");
    ok &= check_string(ini, machine.label, "RS274NGC", "PARAMETER_FILE", machine.parameter_file);
    ok &= check_string(ini, machine.label, "EMCIO", "TOOL_TABLE", machine.tool_table);

    const auto remaps = ini.findStringAll("REMAP", "RS274NGC");
    std::cout << machine.label << "_remap_count=" << remaps.size() << "\n";
    ok &= check_remap_entry(remaps, machine.label, "M428", "428remap");
    ok &= check_remap_entry(remaps, machine.label, "M429", "429remap");
    if (machine.expects_m430) {
        ok &= check_remap_entry(remaps, machine.label, "M430", "430remap");
    }

    const std::string ini_text = read_text(ini_path);
    const bool has_switchkins_halcmd =
        contains(ini_text, "motion.analog-out-03 => motion.switchkins-type");
    ok &= has_switchkins_halcmd;
    print_bool(std::string(machine.label) + "_halcmd_switchkins_from_linuxcnc", has_switchkins_halcmd);

    const std::string dir_rel = std::string(machine.ini_rel).substr(
        0,
        std::string(machine.ini_rel).find_last_of('/'));
    return ok && check_remap_file(machine.label, dir_rel, "M428",
                    std::string(machine.label) == "bridgemill" ? "0" : "1") &&
        check_remap_file(machine.label, dir_rel, "M429",
                    std::string(machine.label) == "bridgemill" ? "1" : "0") &&
        (!machine.expects_m430 || check_remap_file(machine.label, dir_rel, "M430", "2"));
}

}  // namespace

int main()
{
    const MachineCase cases[] = {
        {
            "bridgemill",
            "configs/sim/axis/vismach/5axis/bridgemill/5axis.ini",
            "Sim-5Axis Bridge Mill (xyzbcw)",
            "5axiskins coordinates=xyzbcwy",
            "XYZBCWY",
            "7",
            "5axis.var",
            "5axis.tbl",
            true,
        },
        {
            "xyzab_tdr",
            "configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini",
            "sim-xyzab-tdr-kins (switchkins)",
            "xyzab_tdr_kins",
            "XYZAB",
            "5",
            "xyzab-tdr.var",
            "xyzab-tdr.tbl",
            false,
        },
        {
            "xyzac_trt",
            "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini",
            "sim-xyzac-trt-kins (switchkins)",
            "xyzac-trt-kins sparm=identityfirst",
            "XYZAC",
            "5",
            "xyzac.var",
            "xyzac-trt.tbl",
            true,
        },
        {
            "xyzbc_trt",
            "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini",
            "sim-xyzbc-trt-kins (switchkins)",
            "xyzbc-trt-kins sparm=identityfirst",
            "XYZBC",
            "5",
            "xyzbc.var",
            "xyzbc-trt.tbl",
            true,
        },
    };

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

    ok &= check_text_mcodes(
        "xyzab_tdr",
        "configs/sim/axis/vismach/5axis/table-dual-rotary/README",
        true,
        true);
    ok &= check_text_mcodes(
        "xyzac_trt",
        "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
        true,
        true);
    ok &= check_text_mcodes(
        "xyzbc_trt",
        "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc",
        true,
        true);
    ok &= check_text_mcodes(
        "xyzac_trt_impeller",
        "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/"
        "impeller-7bl-xyzac.ngc",
        true,
        true);

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