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

#include "emc/ini/inifile.hh"

#ifndef LINUXCNC_SOURCE_DIR
#error "LINUXCNC_SOURCE_DIR must point at the LinuxCNC source tree"
#endif

#ifndef LINUXCNC_SOURCE_CONFIG_DIR
#error "LINUXCNC_SOURCE_CONFIG_DIR must point at the LinuxCNC configs/sim tree"
#endif

namespace {

std::string source_path(const std::string &rel)
{
    return std::string(LINUXCNC_SOURCE_DIR) + "/" + rel;
}

std::string source_config_path(const std::string &rel)
{
    return std::string(LINUXCNC_SOURCE_CONFIG_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_ini_value(
    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_task_owner()
{
    const std::string text = read_text(source_path("src/emc/task/taskclass.cc"));
    const bool db_init =
        contains(text, "findString(\"DB_PROGRAM\", \"EMCIO\")") &&
        contains(text, "tooldata_set_db(db_mode)") &&
        contains(text, "tooldata_db_init(db_program, random_toolchanger)");
    const bool tbl_ignored = contains(text, "DB_PROGRAM active: IGNORING tool table file");
    const bool load_unload_notify =
        contains(text, "tooldata_db_notify(SPINDLE_UNLOAD") &&
        contains(text, "tooldata_db_notify(SPINDLE_LOAD");

    print_bool("tool_db_task_db_mode_init", db_init);
    print_bool("tool_db_task_ignores_tool_table_with_db_program", tbl_ignored);
    print_bool("tool_db_task_load_unload_notify", load_unload_notify);
    return db_init && tbl_ignored && load_unload_notify;
}

bool check_tooldata_db_owner()
{
    const std::string text = read_text(source_path("src/emc/tooldata/tooldata_db.cc"));
    const bool child_process =
        contains(text, "pipe2(pipes[PARENT_READ_PIPE]") &&
        contains(text, "pipe2(pipes[PARENT_WRITE_PIPE]") &&
        contains(text, "fork()") &&
        contains(text, "execv(myargv[0], myargv)");
    const bool executable_check =
        contains(text, "access(child_argv[0],X_OK)") &&
        contains(text, "not executable");
    const bool version_handshake =
        contains(text, "#define DB_VERSION \"v2.1\"") &&
        contains(text, "read_reply(reply,sizeof(reply))") &&
        contains(text, "strncmp(reply,DB_VERSION");
    const bool getall =
        contains(text, "send_request((char*)\"g\\n\")") &&
        contains(text, "tooldata_reset()") &&
        contains(text, "strstr(reply,\"FINI\")") &&
        contains(text, "tooldata_read_entry(reply)");
    const bool notify =
        contains(text, "snprintf(msg,sizeof(msg),\"l %s\\n\",buffer)") &&
        contains(text, "snprintf(msg,sizeof(msg),\"u %s\\n\",buffer)") &&
        contains(text, "snprintf(msg,sizeof(msg),\"p %s\\n\",buffer)") &&
        contains(text, "send_and_verify(msg)");

    print_bool("tool_db_child_process_boundary", child_process);
    print_bool("tool_db_program_executable_check", executable_check);
    print_bool("tool_db_v2_1_handshake", version_handshake);
    print_bool("tool_db_getall_g_until_fini", getall);
    print_bool("tool_db_notify_l_u_p_protocol", notify);
    return child_process && executable_check && version_handshake && getall && notify;
}

bool check_db_program_owner()
{
    const std::string text = read_text(source_config_path("axis/db_demo/db.py"));
    const bool imports =
        contains(text, "from tooldb import tooldb_callbacks") &&
        contains(text, "from tooldb import tooldb_tools") &&
        contains(text, "from tooldb import tooldb_loop");
    const bool callbacks =
        contains(text, "def user_get_tool(tno):") &&
        contains(text, "def user_put_tool(tno,params):") &&
        contains(text, "def user_load_spindle_nonran_tc(tno,params):") &&
        contains(text, "def user_unload_spindle_nonran_tc(tno,params):") &&
        contains(text, "tooldb_callbacks(user_get_tool") &&
        contains(text, "tooldb_tools(toollist)") &&
        contains(text, "tooldb_loop()");
    const bool state_targets =
        contains(text, "db_nonran_savefile = \"/tmp/db_nonran_file\"") &&
        contains(text, "toolno_min = 10") &&
        contains(text, "toolno_max = 19") &&
        contains(text, "pocket_offset      = 100") &&
        contains(text, "return tno+pocket_offset") &&
        contains(text, "D['P'] = \"0\"") &&
        contains(text, "nonran_restore_pocket(spindle_tool)") &&
        contains(text, "linuxcnc.command().load_tool_table") &&
        contains(text, "save_tools_to_file(db_savefile");
    const bool reload_rules =
        contains(text, "apply_db_rules()") &&
        contains(text, "G10L0");

    print_bool("tool_db_program_tooldb_module", imports);
    print_bool("tool_db_program_nonran_callbacks", callbacks);
    print_bool("tool_db_program_nonran_state_targets", state_targets);
    print_bool("tool_db_program_reload_rules", reload_rules);
    return imports && callbacks && state_targets && reload_rules;
}

}  // namespace

int main()
{
    const std::string ini_path = source_config_path("axis/db_demo/db_nonran.ini");
    const std::string ini_text = read_text(ini_path);
    linuxcnc::IniFile ini(ini_path);
    const bool opened = static_cast<bool>(ini);
    print_bool("db_nonran_ini_open", opened);
    if (!opened) {
        return 1;
    }

    bool ok = true;
    ok &= check_ini_value(ini, "db_nonran", "EMCIO", "RANDOM_TOOLCHANGER", "0");
    ok &= check_ini_value(ini, "db_nonran", "EMCIO", "DB_PROGRAM", "./db_nonran.py");
    const bool tool_table_ignored = contains(ini_text, "TOOL_TABLE= is not used with DB_PROGRAM");
    print_bool("db_nonran_tool_table_ignored_with_db_program", tool_table_ignored);
    ok &= tool_table_ignored;

    ok &= check_task_owner();
    ok &= check_tooldata_db_owner();
    ok &= check_db_program_owner();

    print_bool("tool_db_native_source_protocol_proof", ok);
    print_bool("tool_db_execution_enabled", false);
    print_bool("tool_db_promotion_allowed", false);
    return ok ? 0 : 1;
}
