/* GemRB - Infinity Engine Emulator
 * Copyright (C) 2003-2005 The GemRB Project
 *
 * 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 "Scriptable/Container.h"

#include "strrefs.h"

#include "DisplayMessage.h"
#include "Game.h"
#include "GameData.h"
#include "Item.h"
#include "Sprite2D.h"
#include "TileMap.h"
#include "GameScript/GSUtils.h"
#include "GUI/GameControl.h"
#include "System/StringBuffer.h"

namespace GemRB {

#define YESNO(x) ( (x)?"Yes":"No")

Container::Container(void)
	: Highlightable( ST_CONTAINER )
{
	Type = 0;
	LockDifficulty = 0;
	Flags = 0;
	TrapDetectionDiff = 0;
	TrapRemovalDiff = 0;
	Trapped = 0;
	TrapDetected = 0;
	inventory.SetInventoryType(INVENTORY_HEAP);
	OpenFail = 0;
}

void Container::FreeGroundIcons()
{
	for (int i = 0; i < MAX_GROUND_ICON_DRAWN; i++) {
		groundicons[i] = nullptr;
	}
}

Container::~Container()
{
	FreeGroundIcons();
}

Region Container::DrawingRegion() const
{
	Region r(Pos.x, Pos.y, 0, 0);
	
	for (int i = 0; i < MAX_GROUND_ICON_DRAWN; ++i) {
		const Holder<Sprite2D> icon = groundicons[i];
		if (icon) {
			Region frame = icon->Frame;
			frame.x = frame.x * -1 + Pos.x;
			frame.y = frame.y * -1 + Pos.y;
			r.ExpandToRegion(frame);
		}
	}
	
	return r;
}

void Container::Draw(bool highlight, const Region& vp, Color tint, BlitFlags flags) const
{
	Video* video = core->GetVideoDriver();

	for (int i = 0;i<MAX_GROUND_ICON_DRAWN;i++) {
		const Holder<Sprite2D> icon = groundicons[i];
		if (icon) {
			if (highlight) {
				video->BlitGameSprite(icon, Pos - vp.Origin(), flags, tint);
			} else {
				const Color trans;
				PaletteHolder p = icon->GetPalette();
				Color tmpc = p->col[1];
				p->CopyColorRange(&trans, &trans + 1, 1);
				video->BlitGameSprite(icon, Pos - vp.Origin(), flags, tint);
				p->CopyColorRange(&tmpc, &tmpc + 1, 1);
			}
		}
	}
}

void Container::SetContainerLocked(bool lock)
{
	if (lock) {
		Flags|=CONT_LOCKED;
	} else {
		Flags&=~CONT_LOCKED;
	}
}

//This function doesn't exist in the original IE, destroys a container
//turning it to a ground pile
void Container::DestroyContainer()
{
	//it is already a groundpile?
	if (Type == IE_CONTAINER_PILE)
		return;
	Type = IE_CONTAINER_PILE;
	RefreshGroundIcons();
	//probably we should stop the script or trigger it, whatever
}

//Takes an item from the container's inventory and returns its pointer
CREItem *Container::RemoveItem(unsigned int idx, unsigned int count)
{
	CREItem *ret = inventory.RemoveItem(idx, count);
	//if we just took one of the first few items, groundpile changed
	if ((Type == IE_CONTAINER_PILE) && (idx<MAX_GROUND_ICON_DRAWN)) {
		RefreshGroundIcons();
	}
	return ret;
}

//Adds an item to the container's inventory
//containers always have enough capacity (so far), thus we always return 2
int Container::AddItem(CREItem *item)
{
	inventory.AddItem(item);
	//we just added a 3. or less item, groundpile changed
	if ((Type == IE_CONTAINER_PILE) && (inventory.GetSlotCount()<=MAX_GROUND_ICON_DRAWN)) {
		RefreshGroundIcons();
	}
	return 2;
}

void Container::RefreshGroundIcons()
{
	int i = inventory.GetSlotCount();
	if (i>MAX_GROUND_ICON_DRAWN)
		i = MAX_GROUND_ICON_DRAWN;
	FreeGroundIcons();
	while (i--) {
		CREItem *slot = inventory.GetSlotItem(i); //borrowed reference
		Item *itm = gamedata->GetItem( slot->ItemResRef ); //cached reference
		if (!itm) continue;
		//well, this is required in PST, needs more work if some other
		//game is broken by not using -1,0
		groundicons[i] = gamedata->GetBAMSprite( itm->GroundIcon, 0, 0 );
		gamedata->FreeItem( itm, slot->ItemResRef ); //decref
	}
}

int Container::IsOpen() const
{
	if (Flags&CONT_LOCKED) {
		return false;
	}
	return true;
}

void Container::TryPickLock(const Actor *actor)
{
	if (LockDifficulty == 100) {
		if (OpenFail != (ieDword)-1) {
			displaymsg->DisplayStringName(OpenFail, DMC_BG2XPGREEN, actor, IE_STR_SOUND|IE_STR_SPEECH);
		} else {
			displaymsg->DisplayConstantStringName(STR_CONT_NOPICK, DMC_BG2XPGREEN, actor);
		}
		return;
	}
	int stat = actor->GetStat(IE_LOCKPICKING);
	if (core->HasFeature(GF_3ED_RULES)) {
		int skill = actor->GetSkill(IE_LOCKPICKING);
		if (skill == 0) { // a trained skill, make sure we fail
			stat = 0;
		} else {
			stat *= 7; // convert to percent (magic 7 is from RE)
			int dexmod = actor->GetAbilityBonus(IE_DEX);
			stat += dexmod; // the original didn't use it, so let's not multiply it
			displaymsg->DisplayRollStringName(39301, DMC_LIGHTGREY, actor, stat-dexmod, LockDifficulty, dexmod);
		}
	}
	if (stat < LockDifficulty) {
		displaymsg->DisplayConstantStringName(STR_LOCKPICK_FAILED, DMC_BG2XPGREEN, actor);
		AddTrigger(TriggerEntry(trigger_picklockfailed, actor->GetGlobalID()));
		core->PlaySound(DS_PICKFAIL, SFX_CHAN_HITS); //AMB_D21
		return;
	}
	SetContainerLocked(false);
	core->GetGameControl()->ResetTargetMode();
	displaymsg->DisplayConstantStringName(STR_LOCKPICK_DONE, DMC_LIGHTGREY, actor);
	AddTrigger(TriggerEntry(trigger_unlocked, actor->GetGlobalID()));
	core->PlaySound(DS_PICKLOCK, SFX_CHAN_HITS); //AMB_D21D
	ImmediateEvent();
	int xp = actor->CalculateExperience(XP_LOCKPICK, actor->GetXPLevel(1));
	const Game *game = core->GetGame();
	game->ShareXP(xp, SX_DIVIDE);
}

void Container::TryBashLock(Actor *actor)
{
	//Get the strength bonus agains lock difficulty
	int bonus;
	unsigned int roll;

	if (core->HasFeature(GF_3ED_RULES)) {
		bonus = actor->GetAbilityBonus(IE_STR);
		roll = actor->LuckyRoll(1, 100, bonus, 0);
	} else {
		int str = actor->GetStat(IE_STR);
		int strEx = actor->GetStat(IE_STREXTRA);
		bonus = core->GetStrengthBonus(2, str, strEx); //BEND_BARS_LIFT_GATES
		roll = actor->LuckyRoll(1, 10, bonus, 0);
	}

	if (core->HasFeature(GF_3ED_RULES)) {
		// ~Bash door check. Roll %d + %d Str mod > %d door DC.~
		// there is no separate string for non-doors
		displaymsg->DisplayRollStringName(20460, DMC_LIGHTGREY, actor, roll, bonus, LockDifficulty);
	}

	actor->FaceTarget(this);
	if(roll < LockDifficulty || LockDifficulty == 100) {
		displaymsg->DisplayConstantStringName(STR_CONTBASH_FAIL, DMC_BG2XPGREEN, actor);
		return;
	}

	displaymsg->DisplayConstantStringName(STR_CONTBASH_DONE, DMC_LIGHTGREY, actor);
	SetContainerLocked(false);
	core->GetGameControl()->ResetTargetMode();
	//Is this really useful ?
	AddTrigger(TriggerEntry(trigger_unlocked, actor->GetGlobalID()));
	ImmediateEvent();
}

void Container::dump() const
{
	StringBuffer buffer;
	buffer.appendFormatted( "Debugdump of Container %s\n", GetScriptName() );
	buffer.appendFormatted( "Container Global ID: %d\n", GetGlobalID());
	buffer.appendFormatted( "Position: %d.%d\n", Pos.x, Pos.y);
	buffer.appendFormatted( "Type: %d, Locked: %s, LockDifficulty: %d\n", Type, YESNO(Flags&CONT_LOCKED), LockDifficulty );
	buffer.appendFormatted( "Flags: %d, Trapped: %s, Detected: %d\n", Flags, YESNO(Trapped), TrapDetected );
	buffer.appendFormatted( "Trap detection: %d%%, Trap removal: %d%%\n", TrapDetectionDiff,
		TrapRemovalDiff );
	const char *name = "NONE";
	if (Scripts[0]) {
		name = Scripts[0]->GetName();
	}
	buffer.appendFormatted( "Script: %s, Key: %s\n", name, KeyResRef );
	inventory.dump(buffer);
	Log(DEBUG, "Container", buffer);
}

bool Container::TryUnlock(Actor *actor) {
	if (!(Flags&CONT_LOCKED)) return true;

	return Highlightable::TryUnlock(actor, false);
}


}
