#include "check-structures-sanity.h"

#include "LuaTools.h"
#include "LuaWrapper.h"

DFHACK_PLUGIN("check-structures-sanity");

static command_result command(color_ostream &, std::vector<std::string> &);

DFhackCExport command_result plugin_init(color_ostream &, std::vector<PluginCommand> & commands)
{
    commands.push_back(PluginCommand(
        "check-structures-sanity",
        "performs a sanity check on df-structures",
        command,
        false,
        "check-structures-sanity [-enums] [-sizes] [-lowmem] [-maxerrors n] [-failfast] [starting_point]\n"
        "\n"
        "-enums: report unexpected or unnamed enum or bitfield values.\n"
        "-sizes: report struct and class sizes that don't match structures. (requires sizecheck)\n"
        "-unnamed: report unnamed enum/bitfield values, not just undefined ones.\n"
        "-maxerrors n: set the maximum number of errors before bailing out.\n"
        "-failfast: crash if any error is encountered. useful only for debugging.\n"
        "-maybepointer: report integers that might actually be pointers.\n"
        "starting_point: a lua expression or a word like 'screen', 'item', or 'building'. (defaults to df.global)\n"
        "\n"
        "by default, check-structures-sanity reports invalid pointers, vectors, strings, and vtables."
    ));

    known_types_by_size.clear();
    build_size_table();

    return CR_OK;
}

bool check_malloc_perturb()
{
    struct T_test {
        uint32_t data[1024];
    };
    auto test = new T_test;
    bool ret = (test->data[0] == 0xd2d2d2d2);
    delete test;
    return ret;
}

static command_result command(color_ostream & out, std::vector<std::string> & parameters)
{
    if (!check_malloc_perturb())
    {
        out.printerr("check-structures-sanity: MALLOC_PERTURB_ not set, cannot continue\n");
        return CR_FAILURE;
    }

    CoreSuspender suspend;

    Checker checker(out);

    // check parameters with values first
#define VAL_PARAM(name, expr_using_value) \
    auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \
    if (name ## _idx != parameters.end()) \
    { \
        if (name ## _idx + 1 == parameters.end()) \
        { \
            return CR_WRONG_USAGE; \
        } \
        try \
        { \
            auto value = std::move(*(name ## _idx + 1)); \
            parameters.erase((name ## _idx + 1)); \
            parameters.erase(name ## _idx); \
            checker.name = (expr_using_value); \
        } \
        catch (std::exception & ex) \
        { \
            out.printerr("check-structures-sanity: argument to -%s: %s\n", #name, ex.what()); \
            return CR_WRONG_USAGE; \
        } \
    }
    VAL_PARAM(maxerrors, std::stoul(value));
#undef VAL_PARAM

#define BOOL_PARAM(name) \
    auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \
    if (name ## _idx != parameters.end()) \
    { \
        checker.name = true; \
        parameters.erase(name ## _idx); \
    }
    BOOL_PARAM(enums);
    BOOL_PARAM(sizes);
    BOOL_PARAM(unnamed);
    BOOL_PARAM(failfast);
    BOOL_PARAM(noprogress);
    BOOL_PARAM(maybepointer);
#undef BOOL_PARAM

    if (parameters.size() > 1)
    {
        return CR_WRONG_USAGE;
    }

    if (parameters.empty())
    {
        checker.queue_globals();
    }
    else
    {
        using namespace DFHack::Lua;
        using namespace DFHack::Lua::Core;
        using namespace DFHack::LuaWrapper;

        StackUnwinder unwinder(State);
        PushModulePublic(out, "utils", "df_expr_to_ref");
        Push(parameters.at(0));
        if (!SafeCall(out, 1, 1))
        {
            return CR_FAILURE;
        }
        if (!lua_touserdata(State, -1))
        {
            return CR_WRONG_USAGE;
        }

        QueueItem item(parameters.at(0), get_object_ref(State, -1));
        lua_getfield(State, -1, "_type");
        lua_getfield(State, -1, "_identity");
        auto identity = reinterpret_cast<type_identity *>(lua_touserdata(State, -1));
        if (!identity)
        {
            out.printerr("could not determine type identity\n");
            return CR_FAILURE;
        }

        checker.queue_item(item, CheckedStructure(identity));
    }

    while (checker.process_queue())
    {
        if (!checker.noprogress)
        {
            out << "checked " << checker.checked_count << " fields\r" << std::flush;
        }
    }

    out << "checked " << checker.checked_count << " fields" << std::endl;

    return checker.error_count ? CR_FAILURE : CR_OK;
}