/*  EQEMu:  Everquest Server Emulator
	Copyright (C) 2001-2003  EQEMu Development Team (http://eqemulator.net)

  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; version 2 of the License.
  
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY except by those people which sell it, which
	are required to give you total support for your newly bought product;
	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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "../common/debug.h"
#include <iostream>
#include <stdlib.h>

#include "object.h"
#include "entity.h"
#include "client.h"
#include "doors.h"
#include "PlayerCorpse.h"
#include "groups.h"
#include "../common/database.h"
#include "../common/packet_functions.h"
#include "../common/packet_dump.h"
#include "StringIDs.h"
using namespace std;

const int MIN_LEVEL_ALCHEMY = 25;
const char DEFAULT_OBJECT_NAME[] = "IT63_ACTORDEF";
const char DEFAULT_OBJECT_NAME_SUFFIX[] = "_ACTORDEF";

extern Database database;
extern Zone* zone;
extern EntityList entity_list;

// Loading object from database
Object::Object(uint32 id, uint32 type, uint32 icon, const Object_Struct& object, const ItemInst* inst)
{
	// Invoke base class
	Entity::Entity();
	
	// Initialize members
	m_id = id;
	m_type = type;
	m_icon = icon;
	m_inuse = false;
	m_inst = NULL;
	m_ground_spawn=false;
	// Copy object data
	memcpy(&m_data, &object, sizeof(Object_Struct));
	if (inst) {
		m_inst = inst->Clone();
	}
	
	// Set drop_id to zero - it will be set when added to zone with SetID()
	m_data.drop_id = 0;
}
Object::Object(const ItemInst* inst, char* name,float max_x,float min_x,float max_y,float min_y,float z,float heading,int32 respawntimer){
	
	m_max_x=max_x;
	m_max_y=max_y;
	m_min_x=min_x;
	m_min_y=min_y;
	Entity::Entity();
	m_id	= 0;
	m_inst	= (inst) ? inst->Clone() : NULL;
	m_type	= OT_DROPPEDITEM;
	m_icon	= 0;
	m_inuse	= false;
	m_ground_spawn = true;
	// Set as much struct data as we can
	memset(&m_data, 0, sizeof(Object_Struct));
	m_data.heading = heading;
	m_data.y = ((rand()%(int)max_y)-(rand()%(int)min_y));
	m_data.x = ((rand()%(int)max_x)-(rand()%(int)min_x));
	//printf("Spawning object %s at %f,%f,%f\n",name,m_data.x,m_data.y,m_data.z);
	m_data.z = z;
	m_data.zone_id = zone->GetZoneID();
	respawn=new Timer(respawntimer);
	respawn->Disable();
	// Hardcoded portion for unknown members
	m_data.unknown020[1] = 0x00001194;
	m_data.unknown060[0] = 0x0000000D;
	m_data.unknown060[1] = 0x0000001E;
	m_data.unknown060[2] = 0x000032ED;
	m_data.unknown060[4] = 0xFFFFFFFF;
	m_data.unknown084[0] = 0xFFFFFFFF;
	strcpy(m_data.object_name, name);
}
// Loading object from client dropping item on ground
Object::Object(Client* client, const ItemInst* inst)
{
	// Invoke base class
	Entity::Entity();
	
	// Initialize members
	m_id	= 0;
	m_inst	= (inst) ? inst->Clone() : NULL;
	m_type	= OT_DROPPEDITEM;
	m_icon	= 0;
	m_inuse	= false;
	m_ground_spawn = false;
	// Set as much struct data as we can
	memset(&m_data, 0, sizeof(Object_Struct));
	m_data.heading = client->GetHeading();
	m_data.y = client->GetX();
	m_data.x = client->GetY();
	m_data.z = client->GetZ();
	m_data.zone_id = zone->GetZoneID();
	
	// Hardcoded portion for unknown members
	m_data.unknown020[1] = 0x00001194;
	m_data.unknown060[0] = 0x0000000D;
	m_data.unknown060[1] = 0x0000001E;
	m_data.unknown060[2] = 0x000032ED;
	m_data.unknown060[4] = 0xFFFFFFFF;
	m_data.unknown084[0] = 0xFFFFFFFF;
	
	// Set object name
	if (inst) {
		const Item_Struct* item = inst->GetItem();
		if (item && item->IDFile) {
			if (strlen(item->IDFile) == 0) {
				strcpy(m_data.object_name, DEFAULT_OBJECT_NAME);
			}
			else {
				// Object name is idfile + _ACTORDEF
				uint32 len_idfile = strlen(inst->GetItem()->IDFile);
				uint32 len_copy = sizeof(m_data.object_name) - len_idfile - 1;
				if (len_copy > sizeof(DEFAULT_OBJECT_NAME_SUFFIX)) {
					len_copy = sizeof(DEFAULT_OBJECT_NAME_SUFFIX);
				}
				
				memcpy(&m_data.object_name[0], inst->GetItem()->IDFile, len_idfile);
				memcpy(&m_data.object_name[len_idfile], DEFAULT_OBJECT_NAME_SUFFIX, len_copy);
			}
		}
		else {
			strcpy(m_data.object_name, DEFAULT_OBJECT_NAME);
		}
	}
}

Object::~Object()
{
	safe_delete(m_inst);
}

void Object::SetID(int16 set_id)
{
	// Invoke base class
	Entity::SetID(set_id);
	
	// Store new id as drop_id
	m_data.drop_id = (uint32)this->GetID();
}

// Reset state of object back to zero
void Object::ResetState()
{
	safe_delete(m_inst);
	
	m_id	= 0;
	m_type	= 0;
	m_icon	= 0;
	memset(&m_data, 0, sizeof(Object_Struct));
}

bool Object::Save()
{
	if (m_id) {
		// Update existing
		database.UpdateObject(m_id, m_type, m_icon, m_data, m_inst);
	}
	else {
		// Doesn't yet exist, add now
		m_id = database.AddObject(m_type, m_icon, m_data, m_inst);
	}
	
	return true;
}

// Remove object from database
void Object::Delete(bool reset_state)
{
	if (m_id != 0) {
		database.DeleteObject(m_id);
	}
	
	if (reset_state) {
		ResetState();
	}
}

// Add item to object (only logical for world tradeskill containers
void Object::PutItem(uint8 index, const ItemInst* inst)
{
	if (index<0 || index>9) {
		LogFile->write(EQEMuLog::Error, "Object::PutItem: Invalid index specified (%i)", index);
		return;
	}
	
	if (m_inst && m_inst->IsType(ItemTypeContainer)) {
		ItemContainerInst* bag = (ItemContainerInst*)m_inst;
		if (inst) {
			bag->PutItem(index, *inst);
		}
		else {
			bag->DeleteItem(index);
		}
		database.SaveWorldContainer(zone->GetZoneID(),m_id,bag);
		// This is _highly_ inefficient, but for now it will work: Save entire object to database
		//Save();
	}
}

// Remove item from container
void Object::DeleteItem(uint8 index)
{
	if (m_inst && m_inst->IsType(ItemTypeContainer)) {
		ItemContainerInst* bag = (ItemContainerInst*)m_inst;
		bag->DeleteItem(index);
		
		// This is _highly_ inefficient, but for now it will work: Save entire object to database
		Save();
	}
}

// Pop item out of container
ItemInst* Object::PopItem(uint8 index)
{
	ItemInst* inst = NULL;
	
	if (m_inst && m_inst->IsType(ItemTypeContainer)) {
		ItemContainerInst* bag = (ItemContainerInst*)m_inst;
		inst = bag->PopItem(index);
		
		// This is _highly_ inefficient, but for now it will work: Save entire object to database
		Save();
	}
	
	return inst;
}

void Object::CreateSpawnPacket(APPLAYER* app)
{
	app->opcode = OP_CreateObject;
	app->pBuffer = new uchar[sizeof(Object_Struct)];
	app->size = sizeof(Object_Struct);
	memcpy(app->pBuffer, &m_data, sizeof(Object_Struct));
}

void Object::CreateDeSpawnPacket(APPLAYER* app)
{
	app->opcode = OP_ClickObject;
	app->pBuffer = new uchar[sizeof(ClickObject_Struct)];
	app->size = sizeof(ClickObject_Struct);
	ClickObject_Struct* co = (ClickObject_Struct*) app->pBuffer;
	co->drop_id = m_data.drop_id;
	co->player_id = 0;
}
bool Object::Process(){
	if(m_ground_spawn && respawn->Check()){
		respawn->Disable();
		m_data.y = ((rand()%(int)m_max_y)-(rand()%(int)m_min_y));
		m_data.x = ((rand()%(int)m_max_x)-(rand()%(int)m_min_x));
		//printf("Spawning object %s at %f,%f,%f\n",m_data.object_name,m_data.x,m_data.y,m_data.z);
		APPLAYER app;
		CreateSpawnPacket(&app);
		entity_list.QueueCloseClients(0,&app,true);
	}
	return true;
}
bool Object::HandleClick(Client* sender, const ClickObject_Struct* click_object)
{
	if(m_ground_spawn){//This is a Cool Groundspawn
			respawn->Start();
	}
	if (m_type == OT_DROPPEDITEM) {
		if (m_inst && sender) {
			// Transfer item to client
			sender->PutItemInInventory(SLOT_CURSOR, *m_inst, false);
			sender->SendItemPacket(SLOT_CURSOR, m_inst, ItemPacketTrade);
			if(!m_ground_spawn)
				safe_delete(m_inst);
			
			// No longer using a tradeskill object
			sender->SetTradeskillObject(NULL);
		}
		
		// Send click to all clients (removes entity on client)
		APPLAYER* outapp = new APPLAYER(OP_ClickObject, sizeof(ClickObject_Struct));
		memcpy(outapp->pBuffer, click_object, sizeof(ClickObject_Struct));
		entity_list.QueueClients(NULL, outapp, false);
		safe_delete(outapp);
		
		// Remove object
		database.DeleteObject(m_id);
		if(!m_ground_spawn)
		entity_list.RemoveEntity(this->GetID());
	}
	else {
		// Tradeskill item
		APPLAYER* outapp = new APPLAYER(OP_ClickObjectAck, sizeof(ClickObjectAck_Struct));
		ClickObjectAck_Struct* coa = (ClickObjectAck_Struct*)outapp->pBuffer;
		
		if (m_inuse) {
			// Finished using object
			sender->SetTradeskillObject(NULL);
			coa->open		= 0x00;
			// Hardcoded values
			coa->type		= 0x05727d10;
			coa->unknown16	= 0x05962698;
		}
		else {
			// Starting to use this object
			sender->SetTradeskillObject(this);
			coa->open		= 0x01;
			m_inuse			= true;
			coa->type		= m_type;
			coa->unknown16	= 0x0a;		
		}
		
		coa->drop_id	= click_object->drop_id;
		coa->player_id	= click_object->player_id;
		coa->icon		= m_icon;
		
		sender->QueuePacket(outapp);
		safe_delete(outapp);
		// Send items inside of container

			if (m_inst && m_inst->IsType(ItemTypeContainer)) {
				APPLAYER* outapp=new APPLAYER(OP_ClientReady,0);
				sender->QueuePacket(outapp);
				safe_delete(outapp);
				ItemContainerInst* container = (ItemContainerInst*)m_inst;
				for (uint8 i=0; i<10; i++) {
					const ItemInst* inst = container->GetItem(i);
					if (inst) {
						//sender->GetInv().PutItem(i+4000,inst);
						sender->SendItemPacket(i, inst, ItemPacketWorldContainer);
					}
				}
			}
	}
	
	return true;
}

// Perform tradeskill combine
void Object::HandleCombine(Client* user, const NewCombine_Struct* in_combine)
{
	if (!user || !in_combine) {
		LogFile->write(EQEMuLog::Error, "Client or NewCombine_Struct not set in Object::HandleCombine");
		return;
	}
	
	Inventory& user_inv = user->GetInv();
	PlayerProfile_Struct& user_pp = user->GetPP();
	ItemContainerInst* container = NULL;
	ItemInst* inst = NULL;
	uint8 tradeskill = 0xE8;
	bool worldcontainer=false;
	
	if (in_combine->container_slot == SLOT_TRADESKILL) {
		inst = m_inst;
		worldcontainer=true;
	}
	else {
		inst = user_inv.GetItem(in_combine->container_slot);
		if (inst) {
			const Item_Struct* item = inst->GetItem();
			if (item && inst->IsType(ItemTypeContainer)) {
				tradeskill = item->Container.PackType;
			}
		}
	}
	
	if (!inst || !inst->IsType(ItemTypeContainer)) {
		user->Message(13, "Error: Server does not recognize specified tradeskill container");
		return;
	}
	
	container = (ItemContainerInst*)inst;
	
	// Convert container type to tradeskill type
	switch (tradeskill)
	{
	case 16:
		tradeskill = TAILORING;
		break;
	case 0xE8: //Generic World Container
		switch (m_type) {
			case 0: {
				tradeskill = 0;
				break;
			}
			case OT_MEDICINEBAG: {
				if ((user_pp.class_ == SHAMAN) & (user_pp.level >= MIN_LEVEL_ALCHEMY))
					tradeskill = ALCHEMY;
				else if (user_pp.class_ != SHAMAN)
					user->Message(13, "This tradeskill can only be performed by a shaman.");
				else if (user_pp.level < MIN_LEVEL_ALCHEMY)
					user->Message(13, "You cannot perform alchemy until you reach level %i.", MIN_LEVEL_ALCHEMY);
				break;
			}
			case OT_SEWINGKIT: {
				tradeskill = TAILORING;
				break;
			}
			case OT_FORGE:
			case OT_TEIRDALFORGE:
			case OT_OGGOKFORGE:
			case OT_STORMGUARDF: {
				tradeskill = BLACKSMITHING;
				break;
			}
			case OT_FLETCHINGKIT: {
				tradeskill = FLETCHING;
				break;
			}
			case OT_BREWBARREL: {
				tradeskill = BREWING;
				break;
			}
			case OT_JEWELERSKIT: {
				tradeskill = JEWELRY_MAKING;
				break;
			}
			case OT_POTTERYWHEEL: {
				tradeskill = POTTERY;
				break;
			}
			case OT_KILN: {
				tradeskill = BAKING;
				break;
			}
			case OT_KEYMAKER: { //unknown for now...
				tradeskill = 0;
				break;
			}
			case OT_WIZARDLEX:
			case OT_MAGELEX:
			case OT_NECROLEX:
			case OT_ENCHLEX: {
				tradeskill = RESEARCH;
				break;
			}
		}
		break;
	case 18:
		tradeskill = FLETCHING;
		break;
	case 20:
		tradeskill = JEWELRY_MAKING;
		break;
	case 30: //Pottery Still needs completion
		tradeskill = POTTERY;
		break;
	case 14: // Baking 
	case 15:
		tradeskill = BAKING;
		break;
	case 9: //Alchemy Still needs completion
		if ((user_pp.class_ == SHAMAN) & (user_pp.level >= MIN_LEVEL_ALCHEMY))
			tradeskill = ALCHEMY;
		else if (user_pp.class_ != SHAMAN)
			user->Message(13, "This tradeskill can only be performed by a shaman.");
		else if (user_pp.level < MIN_LEVEL_ALCHEMY)
			user->Message(13, "You cannot perform alchemy until you reach level %i.", MIN_LEVEL_ALCHEMY);
		break;
	case 10: //Tinkering Still needs completion
		if (user_pp.race == GNOME)
			tradeskill = TINKERING;
		else
			user->Message(13, "Only gnomes can tinker.");
		break; 
	case 24: //Research Still needs completion
	case 25:
	case 26:
	case 27:
		tradeskill = RESEARCH;
		break;
	case 12:
		if (user_pp.class_ == ROGUE)
			tradeskill = MAKE_POISON;
		else
			user->Message(13, "Only rogues can mix poisons.");
		break;
	case 0x0D: //Quest Containers-Most use 1E but item 17111 uses this one, odd Still needs completion
		tradeskill = 75;// Making our own type here
		break;
	case 46: //Fishing Still needs completion
		tradeskill = FISHING;
		break;
	default:
		user->Message(13, "This tradeskill has not been implemented yet, if you get this message send a "
			"petition and let them know what tradeskill you were trying to use. and give them the following code: 0x%02X", tradeskill);
	}
	
	if (tradeskill == 0) {
		return;
	}
	
	sint16 skill_needed = 0;
	uint16 trivial = 0;
	uint32 product1_id = 0;
	uint32 product2_id = 0;
	uint32 product_count = 0;
	uint32 failproduct_id = 0;
	sint16 user_skill = (sint16)user->GetSkill(tradeskill);
	float chance = 0;
	
	// statbonus 20%/10% with 200 + 0.05% / 0.025% per point above 200
	float wisebonus =  (user_pp.WIS > 200) ? 20 + ((user_pp.WIS - 200) * 0.05) : user_pp.WIS * 0.1;
	float intbonus =  (user_pp.INT > 200) ? 10 + ((user_pp.INT - 200) * 0.025) : user_pp.INT * 0.05;
	
	if (database.GetTradeRecipe(container, tradeskill, &product1_id, &product2_id, &failproduct_id, &skill_needed, &trivial, &product_count)) {
		if (product_count < 1)
			product_count = 1;  //Make sure we return at least one item.
		
		if (((sint16)user_skill - (sint16)skill_needed) > 0) {
			chance = 80+wisebonus-10; // 80% basechance + max 20% stats
			user->SimpleMessage_StringID(4,TRADESKILL_TRIVIAL,0);
		}
		else {
			if ((skill_needed - user_skill) < 20) {
				// 40 base chance success + max 40% skill + 20% max stats
				chance = 40 + wisebonus + 40 - ((skill_needed - user_skill)*2);
			}
			else {
				// 0 base chance success + max 30% skill + 10% max stats
				chance = 0 + (wisebonus/2) + 30 - (((skill_needed - user_skill) * (skill_needed - user_skill))*0.01875);
			}
			
			// skillincrease?
			if ((55-(user_skill*0.236))+intbonus > (float)rand()/RAND_MAX*100) {
				user->SetSkill(tradeskill, user_skill + 1);
				//user->Message(4, "You have become better at (skillid=%i)", tradeskill);
			}
		}
		
		if ((tradeskill==75) || user->GetGM() || (chance > ((float)rand()/RAND_MAX*100))){
			user->SimpleMessage_StringID(4,TRADESKILL_SUCCEED,0);
			if (product_count == 0)
				product_count = 1;
			user->SummonItem(product1_id, product_count);
		}
		else {
			user->SimpleMessage_StringID(4,TRADESKILL_FAILED,0);
			if (failproduct_id != 0) { 
				ItemInst* fail_inst = ItemInst::Create(failproduct_id);
				user->PutItemInInventory(SLOT_CURSOR, *fail_inst, true);                        
				safe_delete(fail_inst);
			}
		}
	}
	else {
		user->SimpleMessage_StringID(4,TRADESKILL_NOCOMBINE,0);
		APPLAYER* outapp = new APPLAYER(OP_TradeSkillCombine, 0);
		user->QueuePacket(outapp);
		safe_delete(outapp);
		return;
	}
	
	// Send acknowledgement packets to client
	APPLAYER* outapp = new APPLAYER(OP_TradeSkillCombine, 0);
	user->QueuePacket(outapp);
	safe_delete(outapp);
	if(worldcontainer){
			container->Clear();
			outapp = new APPLAYER(OP_ClearObject,0);
			user->QueuePacket(outapp);
			safe_delete(outapp);
			database.DeleteWorldContainer(m_id,zone->GetZoneID());
	}
	else{
			for (uint8 i=0; i<10; i++){
				const ItemInst* inst = container->GetItem(i);
				if (inst)
					user->DeleteItemInInventory(Inventory::CalcSlotId(in_combine->container_slot,i),0,true);
			}
			container->Clear();
	}
}
