/*
Minetest-c55
Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "craftdef.h"

#include "irrlichttypes.h"
#include "log.h"
#include <sstream>
#include "utility.h"
#include "gamedef.h"
#include "inventory.h"

CraftPointerInput::~CraftPointerInput()
{
	for(u32 i=0; i<items.size(); i++)
		delete items[i];
}

CraftPointerInput createPointerInput(const CraftInput &ci, IGameDef *gamedef)
{
	std::vector<InventoryItem*> items;
	for(u32 i=0; i<ci.items.size(); i++){
		InventoryItem *item = NULL;
		if(ci.items[i] != ""){
			std::istringstream iss(ci.items[i], std::ios::binary);
			item = InventoryItem::deSerialize(iss, gamedef);
		}
		items.push_back(item);
	}
	return CraftPointerInput(ci.width, items);
}

CraftInput createInput(const CraftPointerInput &cpi)
{
	std::vector<std::string> items;
	for(u32 i=0; i<cpi.items.size(); i++){
		if(cpi.items[i] == NULL)
			items.push_back("");
		else{
			std::ostringstream oss(std::ios::binary);
			cpi.items[i]->serialize(oss);
			items.push_back(oss.str());
		}
	}
	return CraftInput(cpi.width, items);
}

std::string CraftInput::dump() const
{
	std::ostringstream os(std::ios::binary);
	os<<"(width="<<width<<"){";
	for(u32 i=0; i<items.size(); i++)
		os<<"\""<<items[i]<<"\",";
	os<<"}";
	return os.str();
}

std::string CraftDefinition::dump() const
{
	std::ostringstream os(std::ios::binary);
	os<<"{output=\""<<output<<"\", input={";
	for(u32 i=0; i<input.items.size(); i++)
		os<<"\""<<input.items[i]<<"\",";
	os<<"}, (input.width="<<input.width<<")}";
	return os.str();
}

void CraftDefinition::serialize(std::ostream &os) const
{
	writeU8(os, 0); // version
	os<<serializeString(output);
	writeU8(os, input.width);
	writeU16(os, input.items.size());
	for(u32 i=0; i<input.items.size(); i++)
		os<<serializeString(input.items[i]);
}

void CraftDefinition::deSerialize(std::istream &is)
{
	int version = readU8(is);
	if(version != 0) throw SerializationError(
			"unsupported CraftDefinition version");
	output = deSerializeString(is);
	input.width = readU8(is);
	u32 count = readU16(is);
	for(u32 i=0; i<count; i++)
		input.items.push_back(deSerializeString(is));
}

class CCraftDefManager: public IWritableCraftDefManager
{
public:
	virtual ~CCraftDefManager()
	{
		clear();
	}
	virtual InventoryItem* getCraftResult(const CraftPointerInput &input_cpi,
			IGameDef *gamedef) const
	{
		if(input_cpi.width > 3){
			errorstream<<"getCraftResult(): ERROR: "
					<<"input_cpi.width > 3; Failing to craft."<<std::endl;
			return NULL;
		}
		InventoryItem *input_items[9];
		for(u32 y=0; y<3; y++)
		for(u32 x=0; x<3; x++)
		{
			u32 i=y*3+x;
			if(x >= input_cpi.width || y >= input_cpi.height())
				input_items[i] = NULL;
			else
				input_items[i] = input_cpi.items[y*input_cpi.width+x];
		}
		for(core::list<CraftDefinition*>::ConstIterator
				i = m_craft_definitions.begin();
				i != m_craft_definitions.end(); i++)
		{
			CraftDefinition *def = *i;

			/*infostream<<"Checking "<<createInput(input_cpi).dump()<<std::endl
					<<" against "<<def->input.dump()
					<<" (output=\""<<def->output<<"\")"<<std::endl;*/

			try {
				CraftPointerInput spec_cpi = createPointerInput(def->input, gamedef);
				if(spec_cpi.width > 3){
					errorstream<<"getCraftResult: ERROR: "
							<<"spec_cpi.width > 3 in recipe "
							<<def->dump()<<std::endl;
					continue;
				}
				InventoryItem *spec_items[9];
				for(u32 y=0; y<3; y++)
				for(u32 x=0; x<3; x++)
				{
					u32 i=y*3+x;
					if(x >= spec_cpi.width || y >= spec_cpi.height())
						spec_items[i] = NULL;
					else
						spec_items[i] = spec_cpi.items[y*spec_cpi.width+x];
				}

				bool match = checkItemCombination(input_items, spec_items);

				if(match){
					std::istringstream iss(def->output, std::ios::binary);
					return InventoryItem::deSerialize(iss, gamedef);
				}
			}
			catch(SerializationError &e)
			{
				errorstream<<"getCraftResult: ERROR: "
						<<"Serialization error in recipe "
						<<def->dump()<<std::endl;
				// then go on with the next craft definition
			}
		}
		return NULL;
	}
	virtual void registerCraft(const CraftDefinition &def)
	{
		infostream<<"registerCraft: registering craft definition: "
				<<def.dump()<<std::endl;
		if(def.input.width > 3 || def.input.height() > 3){
			errorstream<<"registerCraft: input size is larger than 3x3,"
					<<" ignoring"<<std::endl;
			return;
		}
		m_craft_definitions.push_back(new CraftDefinition(def));
	}
	virtual void clear()
	{
		for(core::list<CraftDefinition*>::Iterator
				i = m_craft_definitions.begin();
				i != m_craft_definitions.end(); i++){
			delete *i;
		}
		m_craft_definitions.clear();
	}
	virtual void serialize(std::ostream &os)
	{
		writeU8(os, 0); // version
		u16 count = m_craft_definitions.size();
		writeU16(os, count);
		for(core::list<CraftDefinition*>::Iterator
				i = m_craft_definitions.begin();
				i != m_craft_definitions.end(); i++){
			CraftDefinition *def = *i;
			// Serialize wrapped in a string
			std::ostringstream tmp_os(std::ios::binary);
			def->serialize(tmp_os);
			os<<serializeString(tmp_os.str());
		}
	}
	virtual void deSerialize(std::istream &is)
	{
		// Clear everything
		clear();
		// Deserialize
		int version = readU8(is);
		if(version != 0) throw SerializationError(
				"unsupported CraftDefManager version");
		u16 count = readU16(is);
		for(u16 i=0; i<count; i++){
			// Deserialize a string and grab a CraftDefinition from it
			std::istringstream tmp_is(deSerializeString(is), std::ios::binary);
			CraftDefinition def;
			def.deSerialize(tmp_is);
			// Register
			registerCraft(def);
		}
	}
private:
	core::list<CraftDefinition*> m_craft_definitions;
};

IWritableCraftDefManager* createCraftDefManager()
{
	return new CCraftDefManager();
}