#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "TileTypes.h"

#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/Items.h"
#include "modules/World.h"
#include "modules/Designations.h"
#include "modules/Persistence.h"
#include "modules/Units.h"
#include "modules/Screen.h"
#include "modules/Gui.h"

// #include "uicommon.h"

#include "df/world.h"
#include "df/building.h"
#include "df/world_raws.h"
#include "df/building_def.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/building_stockpilest.h"
#include "df/plotinfost.h"
#include "df/item_quality.h"

#include <map>
#include <unordered_map>

using df::building_stockpilest;
using std::map;
using std::multimap;
using std::pair;
using std::string;
using std::unordered_map;
using std::vector;

using namespace DFHack;
using namespace df::enums;

DFHACK_PLUGIN("automelt");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(cursor);
REQUIRE_GLOBAL(plotinfo);

namespace DFHack
{
    DBG_DECLARE(automelt, status, DebugCategory::LINFO);
    DBG_DECLARE(automelt, cycle, DebugCategory::LINFO);
    DBG_DECLARE(automelt, perf, DebugCategory::LINFO);
}

static const string CONFIG_KEY = string(plugin_name) + "/config";
static const string STOCKPILE_CONFIG_KEY_PREFIX = string(plugin_name) + "/stockpile/";
static PersistentDataItem config;

// static vector<PersistentDataItem> watched_stockpiles;
// static unordered_map<int, size_t> watched_stockpiles_indices;

static unordered_map<int32_t, PersistentDataItem> watched_stockpiles;

enum StockpileConfigValues
{
    STOCKPILE_CONFIG_ID = 0,
    STOCKPILE_CONFIG_MONITORED = 1,

};

static int get_config_val(PersistentDataItem &c, int index)
{
    if (!c.isValid())
        return -1;
    return c.ival(index);
}

static bool get_config_bool(PersistentDataItem &c, int index)
{
    return get_config_val(c, index) == 1;
}

static void set_config_val(PersistentDataItem &c, int index, int value)
{
    if (c.isValid())
        c.ival(index) = value;
}

static void set_config_bool(PersistentDataItem &c, int index, bool value)
{
    set_config_val(c, index, value ? 1 : 0);
}

static PersistentDataItem &ensure_stockpile_config(color_ostream &out, int id)
{
    DEBUG(cycle,out).print("ensuring stockpile config id=%d\n", id);
    if (watched_stockpiles.count(id)){
        DEBUG(cycle,out).print("stockpile exists in watched_indices\n");
        return watched_stockpiles[id];
    }

    string keyname = STOCKPILE_CONFIG_KEY_PREFIX + int_to_string(id);
    DEBUG(status,out).print("creating new persistent key for stockpile %d\n", id);
    watched_stockpiles.emplace(id, World::GetPersistentData(keyname, NULL));
    return watched_stockpiles[id];
}

static void remove_stockpile_config(color_ostream &out, int id)
{
    if (!watched_stockpiles.count(id))
        return;
    DEBUG(status, out).print("removing persistent key for stockpile %d\n", id);
    World::DeletePersistentData(watched_stockpiles[id]);
    watched_stockpiles.erase(id);
}

static void validate_stockpile_configs(color_ostream &out)
{
    for (auto &c : watched_stockpiles) {
        int id = get_config_val(c.second, STOCKPILE_CONFIG_ID);
        if (!df::building::find(id)){
            remove_stockpile_config(out, id);
        }
    }
}

static const int32_t CYCLE_TICKS = 1200;
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle

static command_result do_command(color_ostream &out, vector<string> &parameters);
static int32_t do_cycle(color_ostream &out);

DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands)
{
    DEBUG(status, out).print("initializing %s\n", plugin_name);

    // provide a configuration interface for the plugin
    commands.push_back(PluginCommand(
        plugin_name,
        "Auto melt items in designated stockpiles.",
        do_command));

    return CR_OK;
}

DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
    if (enable != is_enabled)
    {
        is_enabled = enable;
        DEBUG(status, out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled");
    }
    else
    {
        DEBUG(status, out).print("%s from the API, but already %s; no action\n", is_enabled ? "enabled" : "disabled", is_enabled ? "enabled" : "disabled");
    }
    return CR_OK;
}

DFhackCExport command_result plugin_shutdown(color_ostream &out)
{
    DEBUG(status, out).print("shutting down %s\n", plugin_name);

    return CR_OK;
}

DFhackCExport command_result plugin_load_data(color_ostream &out)
{
    cycle_timestamp = 0;
    config = World::GetPersistentData(CONFIG_KEY);

    if (!config.isValid())
    {
        DEBUG(status, out).print("no config found in this save; initializing\n");
        config = World::AddPersistentData(CONFIG_KEY);
    }

    DEBUG(status, out).print("loading persisted enabled state: %s\n", is_enabled ? "true" : "false");
    vector<PersistentDataItem> loaded_persist_data;
    World::GetPersistentData(&loaded_persist_data, STOCKPILE_CONFIG_KEY_PREFIX, true);
    watched_stockpiles.clear();
    const size_t num_watched_stockpiles = loaded_persist_data.size();
    for (size_t idx = 0; idx < num_watched_stockpiles; ++idx)
    {
        auto &c = loaded_persist_data[idx];
        watched_stockpiles.emplace(get_config_val(c, STOCKPILE_CONFIG_ID), c);
    }
    validate_stockpile_configs(out);

    return CR_OK;
}

DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
    if (event == DFHack::SC_WORLD_UNLOADED)
    {
        if (is_enabled)
        {
            DEBUG(status, out).print("world unloaded; disabling %s\n", plugin_name);
            is_enabled = false;
        }
    }

    return CR_OK;
}

DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
    if (!Core::getInstance().isWorldLoaded())
        return CR_OK;
    if (is_enabled && world->frame_counter - cycle_timestamp >= CYCLE_TICKS)
    {
        int32_t marked = do_cycle(out);
        if (0 < marked)
            out.print("automelt: marked %d item(s) for melting\n", marked);
    }
    return CR_OK;
}


static bool call_automelt_lua(color_ostream *out, const char *fn_name,
        int nargs = 0, int nres = 0,
        Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
        Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
    DEBUG(status).print("calling automelt lua function: '%s'\n", fn_name);

    CoreSuspender guard;

    auto L = Lua::Core::State;
    Lua::StackUnwinder top(L);

    if (!out)
        out = &Core::getInstance().getConsole();

    return Lua::CallLuaModuleFunction(*out, L, "plugins.automelt", fn_name,
            nargs, nres,
            std::forward<Lua::LuaLambda&&>(args_lambda),
            std::forward<Lua::LuaLambda&&>(res_lambda));
}

static command_result do_command(color_ostream &out, vector<string> &parameters) {
    CoreSuspender suspend;

    if (!Core::getInstance().isWorldLoaded()) {
        out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
        return CR_FAILURE;
    }

    bool show_help = false;
    if (!call_automelt_lua(&out, "parse_commandline", parameters.size(), 1,
            [&](lua_State *L) {
                for (const string &param : parameters)
                    Lua::Push(L, param);
            },
            [&](lua_State *L) {
                show_help = !lua_toboolean(L, -1);
            })) {
        return CR_FAILURE;
    }

    return show_help ? CR_WRONG_USAGE : CR_OK;
}

static inline bool is_metal_item(df::item *item)
{
    MaterialInfo mat(item);
    return (mat.getCraftClass() == craft_material_class::Metal);
}

static bool isStockpile(df::building * building) {
    return building->getType() == df::building_type::Stockpile;
}

struct BadFlagsCanMelt {
    uint32_t whole;

    BadFlagsCanMelt() {
        df::item_flags flags;
        #define F(x) flags.bits.x = true;
        F(dump); F(forbid); F(garbage_collect);
        F(hostile); F(on_fire); F(rotten); F(trader);
        F(in_building); F(construction); F(artifact);
        F(spider_web); F(owned); F(in_job);
        #undef F
        whole = flags.whole;
    }
};

struct BadFlagsMarkItem {
    uint32_t whole;

    BadFlagsMarkItem() {
        df::item_flags flags;
        #define F(x) flags.bits.x = true;
        F(dump); F(forbid); F(garbage_collect);
        F(hostile); F(on_fire); F(rotten); F(trader);
        F(in_building); F(construction); F(artifact);
        F(spider_web); F(owned);
        #undef F
        whole = flags.whole;
    }
};

// Copied from Kelly Martin's code
static inline bool can_melt(df::item *item)
{

    static const BadFlagsCanMelt bad_flags;

    if (item->flags.whole & bad_flags.whole)
        return false;

    df::item_type t = item->getType();

    if (t == df::enums::item_type::BOX || t == df::enums::item_type::BAR)
        return false;

    if (!is_metal_item(item))
        return false;

    for (auto &g : item->general_refs)
    {
        switch (g->getType())
        {
        case general_ref_type::CONTAINS_ITEM:
        case general_ref_type::UNIT_HOLDER:
        case general_ref_type::CONTAINS_UNIT:
            return false;
        case general_ref_type::CONTAINED_IN_ITEM:
        {
            df::item *c = g->getItem();
            for (auto &gg : c->general_refs)
            {
                if (gg->getType() == general_ref_type::UNIT_HOLDER)
                    return false;
            }
        }
        break;
        default:
            break;
        }
    }

    if (item->getQuality() >= item_quality::Masterful)
        return false;

    return true;
}

static inline bool is_set_to_melt(df::item *item)
{
    return item->flags.bits.melt;
}

static int mark_item(color_ostream &out, df::item *item, BadFlagsMarkItem bad_flags, int32_t stockpile_id,
                     int32_t &premarked_item_count, int32_t &item_count, map<int32_t, bool> &tracked_item_map, bool should_melt)
{
    DEBUG(perf,out).print("%s running mark_item\nshould_melt=%d\n", plugin_name,should_melt);

    if (DBG_NAME(perf).isEnabled(DebugCategory::LDEBUG)) {
        string name = "";
        item->getItemDescription(&name, 0);
        DEBUG(perf,out).print("item %s %d\n", name.c_str(), item->id);
    }

    if (item->flags.whole & bad_flags.whole){
        DEBUG(perf,out).print("rejected flag check\n");
        return 0;
    }

    if (item->isAssignedToThisStockpile(stockpile_id))
    {
        DEBUG(perf,out).print("assignedToStockpile\n");
        size_t marked_count = 0;
        std::vector<df::item *> contents;
        Items::getContainedItems(item, &contents);
        for (auto child = contents.begin(); child != contents.end(); child++)
        {
            DEBUG(perf,out).print("inside child loop\n");
            marked_count += mark_item(out, *child, bad_flags, stockpile_id, premarked_item_count, item_count, tracked_item_map, should_melt);
        }
        return marked_count;
    }

    DEBUG(perf,out).print("past recurse loop\n");

    if (is_set_to_melt(item)) {
        DEBUG(perf,out).print("already set to melt\n");
        tracked_item_map.emplace(item->id, true);
        premarked_item_count++;
        DEBUG(perf,out).print("premarked_item_count=%d\n", premarked_item_count);
        item_count++;
        return 0;
    }

    if (!can_melt(item)) {
        DEBUG(perf,out).print("cannot melt\n");
        return 0;
    }

    DEBUG(perf,out).print("increment item count\n");
    item_count++;

    //Only melt if told to, else count
    if (should_melt) {
        DEBUG(perf,out).print("should_melt hit\n");
        insert_into_vector(world->items.other[items_other_id::ANY_MELT_DESIGNATED], &df::item::id, item);
        item->flags.bits.melt = 1;
        tracked_item_map.emplace(item->id, true);
        return 1;
    } else {
        return 0;
    }

}


static int32_t mark_all_in_stockpile(color_ostream &out, PersistentDataItem & stockpile, int32_t &premarked_item_count, int32_t &item_count,  map<int32_t, bool> &tracked_item_map, bool should_melt)
{
    DEBUG(perf,out).print("%s running mark_all_in_stockpile\nshould_melt=%d\n", plugin_name, should_melt);

    static const BadFlagsMarkItem bad_flags;

    int32_t marked_count = 0;

    if(!stockpile.isValid()) {
        return 0;
    }

    int spid = get_config_val(stockpile, STOCKPILE_CONFIG_ID);
    auto found = df::building::find(spid);
    if (!isStockpile(found)){

        return 0;
    }

    df::building_stockpilest * pile_cast = virtual_cast<df::building_stockpilest>(found);

    if (!pile_cast)
        return 0;

    Buildings::StockpileIterator stored;
    DEBUG(perf,out).print("starting item iter. loop\n");
    for (stored.begin(pile_cast); !stored.done(); ++stored) {
        DEBUG(perf,out).print("calling mark_item\n");
        marked_count += mark_item(out, *stored, bad_flags, spid, premarked_item_count, item_count, tracked_item_map, should_melt);
        DEBUG(perf,out).print("end mark_item call\npremarked_item_count=%d\n", premarked_item_count);
    }
    DEBUG(perf,out).print("end item iter. loop\n");
    DEBUG(perf,out).print("exit mark_all_in_stockpile\nmarked_count %d\npremarked_count %d\n", marked_count, premarked_item_count);
    return marked_count;
}


static int32_t scan_stockpiles(color_ostream &out, bool should_melt, map<int32_t, int32_t> &item_count_piles, map<int32_t, int32_t> &premarked_item_count_piles,
                                                                map<int32_t, int32_t> &marked_item_count_piles, map<int32_t, bool> &tracked_item_map) {
    DEBUG(perf,out).print("running scan_stockpiles\n");
    int32_t newly_marked = 0;

    item_count_piles.clear();
    premarked_item_count_piles.clear();
    marked_item_count_piles.clear();

    //Parse all the watched piles
    for (auto &c : watched_stockpiles) {
        int id = get_config_val(c.second, STOCKPILE_CONFIG_ID);
        //Check monitor status
        bool monitored = get_config_bool(c.second, STOCKPILE_CONFIG_MONITORED);

        if (!monitored) continue;

        DEBUG(perf,out).print("%s past monitor check\nmonitored=%d\n", plugin_name, monitored);

        int32_t item_count = 0;

        int32_t premarked_count = 0;

        int32_t marked = mark_all_in_stockpile(out, c.second, premarked_count, item_count, tracked_item_map, should_melt);

        DEBUG(perf,out).print("post mark_all_in_stockpile premarked_count=%d\n", premarked_count);

        item_count_piles.emplace(id, item_count);

        premarked_item_count_piles.emplace(id, premarked_count);

        marked_item_count_piles.emplace(id, marked);

        newly_marked += marked;

    }
    DEBUG(perf,out).print("exit scan_stockpiles\n");
    return newly_marked;
}

static int32_t scan_all_melt_designated(color_ostream &out, map<int32_t, bool> &tracked_item_map) {

    DEBUG(perf,out).print("running scan_all_melt_designated\n");
    int32_t marked_item_count = 0;
    //loop over all items marked as melt-designated
    for (auto item : world->items.other[items_other_id::ANY_MELT_DESIGNATED]) {
        //item has already been marked/counted as inside a stockpile. Don't double-count.
        if (tracked_item_map.count(item->id)) {
            continue;
        }
        marked_item_count++;
    }
    DEBUG(perf,out).print("exiting scan_all_melt_designated\n");
    return marked_item_count;
}

static int32_t scan_count_all(color_ostream &out, bool should_melt, int32_t &marked_item_count_total, int32_t &marked_total_count_all_piles, int32_t &marked_item_count_global,
                              int32_t &total_items_all_piles, map<int32_t, int32_t> &item_count_piles, map<int32_t, int32_t> &premarked_item_count_piles, map<int32_t, int32_t> &marked_item_count_piles) {

    //Wraps both scan_stockpiles and scan_all_melt_designated
    //Returns count of items in piles freshly marked

    int32_t newly_marked_items_piles = 0;

    map<int32_t, bool> tracked_item_map_piles;

    tracked_item_map_piles.clear();

    newly_marked_items_piles = scan_stockpiles(out, should_melt, item_count_piles, premarked_item_count_piles, marked_item_count_piles, tracked_item_map_piles);
    marked_item_count_global = scan_all_melt_designated(out, tracked_item_map_piles);

    for (auto &i : watched_stockpiles) {
        int id = get_config_val(i.second, STOCKPILE_CONFIG_ID);
        total_items_all_piles+= item_count_piles[id];
        marked_total_count_all_piles += premarked_item_count_piles[id];
    }

    marked_item_count_total = marked_item_count_global + marked_total_count_all_piles;


    return newly_marked_items_piles;

}

static int32_t do_cycle(color_ostream &out) {
    DEBUG(cycle,out).print("running %s cycle\n", plugin_name);
    cycle_timestamp = world->frame_counter;

    validate_stockpile_configs(out);

    int32_t marked_item_count_total = 0;
    int32_t marked_item_count_global = 0;
    int32_t newly_marked_items_piles = 0;
    int32_t total_items_all_piles = 0;
    int32_t marked_total_count_all_piles = 0;
    map<int32_t, int32_t> item_count_piles, premarked_item_count_piles, marked_item_count_piles;

    newly_marked_items_piles = scan_count_all(out, true, marked_item_count_total, marked_total_count_all_piles, marked_item_count_global,
                                              total_items_all_piles, item_count_piles, premarked_item_count_piles, marked_item_count_piles);

    DEBUG(perf,out).print("exit %s do_cycle\n", plugin_name);
    return newly_marked_items_piles;
}

static int getSelectedStockpile(color_ostream &out) {
    df::building *selected_bldg = NULL;
    selected_bldg = Gui::getSelectedBuilding(out, true);
    if (selected_bldg->getType() != df::building_type::Stockpile) {
        DEBUG(status,out).print("Selected building is not stockpile\n");
        return -1;
    }

    return selected_bldg->id;
}

static PersistentDataItem *getSelectedStockpileConfig(color_ostream &out) {
    int32_t bldg_id = getSelectedStockpile(out);
    if (bldg_id == -1) {
        DEBUG(status,out).print("Selected bldg invalid\n");
        return NULL;
    }

    validate_stockpile_configs(out);
    PersistentDataItem *c = NULL;
    if (watched_stockpiles.count(bldg_id)) {
        c = &(watched_stockpiles[bldg_id]);
        return c;
    } else {
        DEBUG(status,out).print("No existing config\n");
        return NULL;
    }

}

static void push_stockpile_config(lua_State *L, int id, bool monitored) {
    map<string, int32_t> stockpile_config;
    stockpile_config.emplace("id", id);
    stockpile_config.emplace("monitored", monitored);
    Lua::Push(L, stockpile_config);
}

static void push_stockpile_config(lua_State *L, PersistentDataItem &c) {
    push_stockpile_config(L, get_config_val(c, STOCKPILE_CONFIG_ID),
            get_config_bool(c, STOCKPILE_CONFIG_MONITORED));
}

static void automelt_designate(color_ostream &out) {
    DEBUG(status, out).print("entering automelt designate\n");
    out.print("designated %d item(s) for melting\n", do_cycle(out));
}

static void automelt_printStatus(color_ostream &out) {
    DEBUG(status,out).print("entering automelt_printStatus\n");
    validate_stockpile_configs(out);
    out.print("automelt is %s\n\n", is_enabled ? "enabled" : "disabled");

    int32_t marked_item_count_total = 0;
    int32_t marked_item_count_global = 0;
    int32_t total_items_all_piles = 0;
    int32_t marked_total_count_all_piles = 0;
    map<int32_t, int32_t> item_count_piles, premarked_item_count_piles, marked_item_count_piles;

    scan_count_all(out, false, marked_item_count_total, marked_total_count_all_piles, marked_item_count_global,
                                              total_items_all_piles, item_count_piles, premarked_item_count_piles, marked_item_count_piles);

    out.print("summary:\n");
    out.print("                         total items in monitored piles: %d\n", total_items_all_piles);
    out.print("                        marked items in monitored piles: %d\n", marked_total_count_all_piles);
    out.print("marked items global (excludes those in monitored piles): %d\n", marked_item_count_global);
    out.print("         marked items global (monitored piles + others): %d\n", marked_item_count_total);

    int name_width = 11;
    for (auto &stockpile : world->buildings.other.STOCKPILE) {
        if (!isStockpile(stockpile)) continue;
        if (stockpile->name.empty()) {
            string stock_name = "Stockpile #" + int_to_string(stockpile->stockpile_number);
            name_width = std::max(name_width, (int)(stock_name.size()));
        } else {
            name_width = std::max(name_width, (int)stockpile->name.size());
        }
    }
    name_width = -name_width;

    const char *fmt = "%*s  %9s  %5s  %6s\n";
    out.print(fmt, name_width, "name", "monitored", "items", "marked");
    out.print(fmt, name_width, "----", "---------", "-----", "------");

    for (auto &stockpile : world->buildings.other.STOCKPILE) {
        if (!isStockpile(stockpile)) continue;
        bool monitored = false;
        int32_t item_count = 0;
        int32_t marked_item_count = 0;
        if (watched_stockpiles.count(stockpile->id)) {
            auto &c = watched_stockpiles[stockpile->id];
            monitored = get_config_bool(c, STOCKPILE_CONFIG_MONITORED);
            int id = get_config_val(c, STOCKPILE_CONFIG_ID);
            item_count = item_count_piles[id];
            marked_item_count = premarked_item_count_piles[id];
        }

        if (stockpile->name.empty()) {
            string stock_name = "Stockpile #" + int_to_string(stockpile->stockpile_number);
            out.print(fmt, name_width, stock_name.c_str(), monitored ? "[x]": "[ ]",
                        int_to_string(item_count).c_str(), int_to_string(marked_item_count).c_str());
        } else {
            out.print(fmt, name_width, stockpile->name.c_str(), monitored ? "[x]": "[ ]",
                        int_to_string(item_count).c_str(), int_to_string(marked_item_count).c_str());
        }


    }
    DEBUG(status,out).print("exiting automelt_printStatus\n");

}

static void automelt_setStockpileConfig(color_ostream &out, int id, bool monitored) {
    DEBUG(status,out).print("entering automelt_setStockpileConfig for id=%d and monitored=%d\n", id, monitored);
    validate_stockpile_configs(out);
    auto bldg = df::building::find(id);
    bool isInvalidStockpile = !bldg || !isStockpile(bldg);
    bool hasNoData = !monitored;
    if (isInvalidStockpile || hasNoData) {
        DEBUG(cycle,out).print("calling remove_stockpile_config with id=%d monitored=%d\n", id, monitored);
        remove_stockpile_config(out, id);
        return;
    }

    PersistentDataItem &c = ensure_stockpile_config(out, id);
    set_config_val(c, STOCKPILE_CONFIG_ID, id);
    set_config_bool(c, STOCKPILE_CONFIG_MONITORED, monitored);

    //If we're marking something as monitored, go ahead and designate contents.
    if (monitored) {
        automelt_designate(out);
    }
}

static int automelt_getStockpileConfig(lua_State *L) {
    color_ostream *out = Lua::GetOutput(L);
    if (!out)
        out = &Core::getInstance().getConsole();
    DEBUG(status, *out).print("entering automelt_getStockpileConfig\n");
    validate_stockpile_configs(*out);

    int id;
    if (lua_isnumber(L, -1)) {
        bool found = false;
        id = lua_tointeger(L, -1);

        for (auto &stockpile : world->buildings.other.STOCKPILE) {
            if (!isStockpile(stockpile)) continue;
            if (id == stockpile->stockpile_number){
                id = stockpile->id;
                found = true;
                break;
            }
        }

        if (!found)
            return 0;

    } else {
        const char * name = lua_tostring(L, -1);
        if (!name)
            return 0;
        string nameStr = name;
        bool found = false;
        for (auto &stockpile : world->buildings.other.STOCKPILE) {
            if (!isStockpile(stockpile)) continue;
            if (nameStr == stockpile->name) {
                id = stockpile->id;
                found = true;
                break;
            } else {
                string stock_name = "Stockpile #" + int_to_string(stockpile->stockpile_number);
                if (stock_name == nameStr) {
                    id = stockpile->id;
                    found = true;
                    break;
                }
            }

        }
        if (!found)
            return 0;
    }

    if (watched_stockpiles.count(id)) {
        push_stockpile_config(L, watched_stockpiles[id]);
    } else {
        push_stockpile_config(L, id, false);
    }
    return 1;
}

static int automelt_getSelectedStockpileConfig(lua_State *L){
    color_ostream *out = Lua::GetOutput(L);
    if (!out)
        out = &Core::getInstance().getConsole();
    DEBUG(status, *out).print("entering automelt_getSelectedStockpileConfig\n");
    validate_stockpile_configs(*out);

    int32_t stock_id = getSelectedStockpile(*out);
    PersistentDataItem *c = getSelectedStockpileConfig(*out);

    //If we have a valid config entry, use that. Else assume all false.
    if (c) {
        push_stockpile_config(L, *c);
    } else {
        push_stockpile_config(L, stock_id, false);
    }

    return 1;
}

//TODO
static int automelt_getItemCountsAndStockpileConfigs(lua_State *L) {
    color_ostream *out = Lua::GetOutput(L);
    if (!out)
        out = &Core::getInstance().getConsole();
    DEBUG(status,*out).print("entering automelt_getItemCountsAndStockpileConfigs\n");
    validate_stockpile_configs(*out);

    int32_t marked_item_count_total = 0;
    int32_t marked_item_count_global = 0;
    int32_t total_items_all_piles = 0;
    int32_t marked_total_count_all_piles = 0;
    map<int32_t, int32_t> item_count_piles, premarked_item_count_piles, marked_item_count_piles;

    scan_count_all(*out, false, marked_item_count_total, marked_total_count_all_piles, marked_item_count_global,
                                              total_items_all_piles, item_count_piles, premarked_item_count_piles, marked_item_count_piles);

    map<string, int32_t> summary;
    summary.emplace("total_items", total_items_all_piles);
    summary.emplace("premarked_items", marked_total_count_all_piles);
    summary.emplace("marked_item_count_global", marked_item_count_global);
    summary.emplace("marked_item_count_total", marked_item_count_total);

    Lua::Push(L, summary);
    Lua::Push(L, item_count_piles);
    Lua::Push(L, marked_item_count_piles);
    Lua::Push(L, premarked_item_count_piles);
    int32_t bldg_count = 0;

    for (auto pile : world->buildings.other.STOCKPILE) {
        if (!isStockpile(pile))
            continue;
        bldg_count++;

        int id = pile->id;
        if (watched_stockpiles.count(id)) {
            DEBUG(cycle,*out).print("indexed_id=%d\n", get_config_val(watched_stockpiles[id], STOCKPILE_CONFIG_ID));
            push_stockpile_config(L, watched_stockpiles[id]);
        } else {
            push_stockpile_config(L, id, false);
        }
    }
    DEBUG(perf, *out).print("exit automelt_getItemCountsAndStockpileConfigs\n");

    return 4+bldg_count;
}

DFHACK_PLUGIN_LUA_FUNCTIONS{
    DFHACK_LUA_FUNCTION(automelt_printStatus),
    DFHACK_LUA_FUNCTION(automelt_designate),
    DFHACK_LUA_FUNCTION(automelt_setStockpileConfig),
    DFHACK_LUA_END};

DFHACK_PLUGIN_LUA_COMMANDS{
    DFHACK_LUA_COMMAND(automelt_getStockpileConfig),
    DFHACK_LUA_COMMAND(automelt_getItemCountsAndStockpileConfigs),
    DFHACK_LUA_COMMAND(automelt_getSelectedStockpileConfig),
    DFHACK_LUA_END};