#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"

#include "DataDefs.h"
#include "df/world.h"
#include "df/ui.h"
#include "df/building_stockpilest.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/unit.h"
#include "df/building.h"
#include "df/items_other_id.h"
#include "df/item_stockpile_ref.h"
#include "modules/MapCache.h"
#include "modules/Items.h"


using std::vector;
using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;

using df::global::world;
using df::global::ui;
using df::global::selection_rect;

using df::building_stockpilest;

static command_result copystock(color_ostream &out, vector <string> & parameters);
static command_result stockcheck(color_ostream &out, vector <string> & parameters);
static bool copystock_guard(df::viewscreen *top);

DFHACK_PLUGIN("stockcheck");

DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{
    if (world && ui) {
		commands.push_back(
			PluginCommand("stockcheck", "Check for unprotected rottable items.",
				stockcheck, false, 
				"Scan world for items that are susceptible to rot.  Currently just lists the items.\n"
			)
		);
    }
    return CR_OK;
}

DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
    return CR_OK;
}

struct StockpileInfo {
	building_stockpilest* sp;
	int size;
	int free;
	int x1, x2, y1, y2, z;

public:
	StockpileInfo(building_stockpilest *sp_) : sp(sp_) 
	{
		MapExtras::MapCache mc;

		z = sp_->z;
		x1 = sp_->room.x; 
		x2 = sp_->room.x + sp_->room.width;
		y1 = sp_->room.y;
		y2 = sp_->room.y + sp_->room.height;
		int e = 0;
		size = 0;
		free = 0;
		for (int y = y1; y < y2; y++) 
			for (int x = x1; x < x2; x++) 
				if (sp_->room.extents[e++] == 1) 
				{
					size++;
					DFCoord cursor (x,y,z);
					uint32_t blockX = x / 16;
					uint32_t tileX = x % 16;
					uint32_t blockY = y / 16;
					uint32_t tileY = y % 16;
					MapExtras::Block * b = mc.BlockAt(cursor/16);
					if(b && b->is_valid())
					{
						auto &block = *b->getRaw();
						df::tile_occupancy &occ = block.occupancy[tileX][tileY];
						if (!occ.bits.item)
							free++;
					}
				}
	}

	bool isFull() { return free == 0; }

	bool canHold(df::item *i) 
	{
		return false;
	}
	
	bool inStockpile(df::item *i)
	{
		df::item *container = Items::getContainer(i);
		if (container) 
			return inStockpile(container);

		if (i->pos.z != z) return false;
		if (i->pos.x < x1 || i->pos.x >= x2 ||
			i->pos.y < y1 || i->pos.y >= y2) return false;
		int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
		return sp->room.extents[e] == 1;
	}

	int getId() { return sp->id; }
};

static command_result stockcheck(color_ostream &out, vector <string> & parameters)
{
	CoreSuspender suspend;
	
	std::vector<StockpileInfo*> stockpiles;

	for (int i = 0; i < world->buildings.all.size(); ++i)
	{
		df::building *build = world->buildings.all[i];
		auto type = build->getType();
		if (df::enums::building_type::Stockpile == type)
		{
			building_stockpilest *sp = virtual_cast<building_stockpilest>(build);
			StockpileInfo *spi = new StockpileInfo(sp);
			stockpiles.push_back(spi);
		}

	}

	std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];

	// Precompute a bitmask with the bad flags
    df::item_flags bad_flags;
    bad_flags.whole = 0;

#define F(x) bad_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(artifact1);
	F(spider_web); F(owned); F(in_job);
#undef F

    for (size_t i = 0; i < items.size(); i++)
    {
        df::item *item = items[i];
		if (item->flags.whole & bad_flags.whole)
            continue;

		// we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG

		df::item_type typ = item->getType();
		if (typ != df::enums::item_type::MEAT &&
			typ != df::enums::item_type::FISH &&
			typ != df::enums::item_type::FISH_RAW &&
			typ != df::enums::item_type::PLANT &&
			typ != df::enums::item_type::CHEESE &&
			typ != df::enums::item_type::FOOD &&
			typ != df::enums::item_type::EGG)
			continue;

		df::item *container = 0;
		df::unit *holder = 0;
		df::building *building = 0;

        for (size_t i = 0; i < item->itemrefs.size(); i++)
        {
            df::general_ref *ref = item->itemrefs[i];

            switch (ref->getType())
            {
            case general_ref_type::CONTAINED_IN_ITEM:
				container = ref->getItem();
                break;

            case general_ref_type::UNIT_HOLDER:
				holder = ref->getUnit();
                break;

            case general_ref_type::BUILDING_HOLDER:
				building = ref->getBuilding();
                break;

            default:
                break;
			}
		}

		df::item *nextcontainer = container;
		df::item *lastcontainer = 0;

		while(nextcontainer) {
			df::item *thiscontainer = nextcontainer;
			nextcontainer = 0;
	        for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++)
		    {
			    df::general_ref *ref = thiscontainer->itemrefs[i];

	            switch (ref->getType())
				{
	            case general_ref_type::CONTAINED_IN_ITEM:
					lastcontainer = nextcontainer = ref->getItem();
			        break;

	            case general_ref_type::UNIT_HOLDER:
					holder = ref->getUnit();
			        break;

	            case general_ref_type::BUILDING_HOLDER:
					building = ref->getBuilding();
			        break;

                default:
                    break;
				}
			}
		}

		if (holder) 
			continue; // carried items do not rot as far as i know
		
		if (building) {
			df::building_type btype = building->getType();
			if (btype == df::enums::building_type::TradeDepot ||
				btype == df::enums::building_type::Wagon)
				continue; // items in trade depot or the embark wagon do not rot
			
			if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox)
				continue; // eggs in nest box do not rot			
		}

		int canHoldCount = 0;
		StockpileInfo *current = 0;

		for (int idx = 0; idx < stockpiles.size(); idx++)
		{
			StockpileInfo *spi = stockpiles[idx];
			if (spi->canHold(item)) canHoldCount++;
			if (spi->inStockpile(item)) current=spi;
		}

		if (current) 
			continue;

		std::string description;
		item->getItemDescription(&description, 0);
		out << " * " << description;

        if (container) {
			std::string containerDescription;
			container->getItemDescription(&containerDescription, 0);
			out << ", in container " << containerDescription;
			if (lastcontainer) {
				std::string lastcontainerDescription;
				lastcontainer->getItemDescription(&lastcontainerDescription, 0);
				out << ", in container " << lastcontainerDescription;
			}
		}

		if (holder) {
			out << ", carried";
		}

		if (building) {
			out << ", in building " << building->id << " (type=" << building->getType() << ")";
		}

		out << ", flags=" << std::hex << item->flags.whole << std::dec;
		out << endl;

	}

	return CR_OK;
}