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

#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_python_runtime_owner()
{
    const std::string interp_python = read_text(source_path("src/emc/rs274ngc/interp_python.cc"));
    const std::string python_plugin = read_text(source_path("src/emc/pythonplugin/python_plugin.cc"));

    const bool pycall_dispatch =
        contains(interp_python, "PythonPlugin *python_plugin") &&
        contains(interp_python, "Interp::pycall") &&
        contains(interp_python, "python_plugin->call(module,funcname");
    const bool remap_phases =
        contains(interp_python, "PY_PROLOG") &&
        contains(interp_python, "PY_BODY") &&
        contains(interp_python, "PY_EPILOG");
    const bool generator_finish =
        contains(interp_python, "PyGen_Check(retval.ptr())") &&
        contains(interp_python, "PY_FINISH_PROLOG") &&
        contains(interp_python, "PY_FINISH_EPILOG");
    const bool callable_lookup =
        contains(interp_python, "Interp::is_pycallable") &&
        contains(interp_python, "python_plugin->is_callable(module,funcname)");
    const bool execute_runtime =
        contains(interp_python, "Interp::py_execute") &&
        contains(interp_python, "python_plugin->run_string(cmd, retval, as_file)");

    const bool initializes_python =
        contains(python_plugin, "PyConfig_InitPythonConfig(&config)") &&
        contains(python_plugin, "Py_InitializeFromConfig(&config)");
    const bool toplevel_exec =
        contains(python_plugin, "findString(\"TOPLEVEL\", section)") &&
        contains(python_plugin, "bp::exec_file(abs_path, main_namespace, main_namespace)");
    const bool ini_python_path =
        contains(python_plugin, "findString(n, \"PATH_PREPEND\", \"PYTHON\")") &&
        contains(python_plugin, "findString(n, \"PATH_APPEND\", \"PYTHON\")") &&
        contains(python_plugin, "PyRun_SimpleString(pycmd)");
    const bool callable_invoke =
        contains(python_plugin, "PythonPlugin::call") &&
        contains(python_plugin, "PyObject_Call(function.ptr(), tupleargs.ptr(), kwargs.ptr())");
    const bool reload_on_change =
        contains(python_plugin, "PythonPlugin::reload") &&
        contains(python_plugin, "reload_on_change");

    print_bool("python_runtime_pycall_dispatch", pycall_dispatch);
    print_bool("python_runtime_remap_phases", remap_phases);
    print_bool("python_runtime_generator_finish", generator_finish);
    print_bool("python_runtime_callable_lookup", callable_lookup);
    print_bool("python_runtime_execute_runtime", execute_runtime);
    print_bool("python_plugin_initializes_python", initializes_python);
    print_bool("python_plugin_toplevel_exec_file", toplevel_exec);
    print_bool("python_plugin_ini_python_path", ini_python_path);
    print_bool("python_plugin_callable_invoke", callable_invoke);
    print_bool("python_plugin_reload_on_change", reload_on_change);

    return pycall_dispatch && remap_phases && generator_finish && callable_lookup &&
        execute_runtime && initializes_python && toplevel_exec && ini_python_path &&
        callable_invoke && reload_on_change;
}

bool check_axis_laser_family()
{
    const std::string ini = read_text(source_config_path("axis/laser/laser.ini"));
    const std::string remap = read_text(source_config_path("axis/laser/python/remap.py"));
    const bool ok =
        contains(ini, "REMAP= M10 modalgroup=10 python=rasterStop") &&
        contains(ini, "REMAP= M11 modalgroup=10 python=rasterBegin") &&
        contains(ini, "REMAP= M12 modalgroup=10 python=rasterData") &&
        contains(ini, "REMAP= M13 modalgroup=10 python=rasterStart") &&
        contains(ini, "TOPLEVEL=python/toplevel.py") &&
        contains(ini, "PATH_APPEND=python") &&
        contains(remap, "def rasterBegin(self, **words):") &&
        contains(remap, "def rasterData(self, **words):") &&
        contains(remap, "def rasterStart(self, **words):") &&
        contains(remap, "def rasterStop(self, **words):");
    print_bool("python_family_axis_laser_inventory", ok);
    return ok;
}

bool check_twp_nutating_family()
{
    const std::string ini = read_text(source_config_path(
        "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/xyzacb-trsrn_twp/xyzacb-trsrn.ini"));
    const std::string remap = read_text(source_config_path(
        "axis/vismach/5axis/table-rotary_spindle-rotary-nutating/python/remap.py"));
    const bool ok =
        contains(ini, "REMAP = G68.2") &&
        contains(ini, "python=g682") &&
        contains(ini, "REMAP = G68.3") &&
        contains(ini, "python=g683") &&
        contains(ini, "REMAP = G68.4") &&
        contains(ini, "python=g684") &&
        contains(ini, "python=g53x_core") &&
        contains(ini, "python=g69_core") &&
        contains(ini, "PATH_APPEND = ../python") &&
        contains(ini, "TOPLEVEL = ../python/toplevel.py") &&
        contains(remap, "def g53x_core(self):") &&
        contains(remap, "def g69_core(self):") &&
        contains(remap, "def g682(self, **words):") &&
        contains(remap, "def g683(self, **words):") &&
        contains(remap, "def g684(self, **words):");
    print_bool("python_family_twp_nutating_inventory", ok);
    return ok;
}

bool check_cycle_and_stdglue_families()
{
    const std::string cycle_ini = read_text(source_config_path("axis/remap/cycle/cycle.ini"));
    const std::string cycle_remap = read_text(source_config_path("axis/remap/cycle/python/remap.py"));
    const std::string stdglue = read_text(source_config_path(
        "axis/nc_files/remap_lib/python-stdglue/stdglue.py"));
    const std::string gmoccapy_stdglue = read_text(source_config_path("gmoccapy/python/stdglue.py"));

    const bool cycle_ok =
        contains(cycle_ini, "python=g842") &&
        contains(cycle_ini, "prolog=cycle_prolog") &&
        contains(cycle_ini, "epilog=cycle_epilog") &&
        contains(cycle_ini, "PATH_PREPEND=./python") &&
        contains(cycle_ini, "PATH_APPEND=../../nc_files/remap_lib/python-stdglue") &&
        contains(cycle_remap, "def g842(self,**words):") &&
        contains(stdglue, "def cycle_prolog(self,**words):") &&
        contains(stdglue, "def cycle_epilog(self,**words):");

    const bool gmoccapy_ok =
        contains(gmoccapy_stdglue, "def settool_prolog(self,**words):") &&
        contains(gmoccapy_stdglue, "def settool_epilog(self,**words):") &&
        contains(gmoccapy_stdglue, "def change_prolog(self, **words):") &&
        contains(gmoccapy_stdglue, "def change_epilog(self, **words):");

    print_bool("python_family_axis_remap_cycle_inventory", cycle_ok);
    print_bool("python_family_gmoccapy_stdglue_inventory", gmoccapy_ok);
    return cycle_ok && gmoccapy_ok;
}

}  // namespace

int main()
{
    bool ok = true;
    ok &= check_python_runtime_owner();
    ok &= check_axis_laser_family();
    ok &= check_twp_nutating_family();
    ok &= check_cycle_and_stdglue_families();

    print_bool("python_remap_native_source_inventory_proof", ok);
    print_bool("python_remap_execution_enabled", false);
    print_bool("python_remap_promotion_allowed", false);
    return ok ? 0 : 1;
}
