#include "a_keys.h"
#include "tarray.h"
#include "gi.h"
#include "gstrings.h"
#include "d_player.h"
#include "c_console.h"
#include "s_sound.h"
#include "sc_man.h"
#include "v_palette.h"
#include "w_wad.h"



struct OneKey
{
	const TypeInfo * key;
	int count;

	bool check(AActor * owner)
	{
		return !!owner->FindInventory(key);
	}
};

struct Keygroup
{
	TArray<OneKey> anykeylist;

	bool check(AActor * owner)
	{
		for(int i=0;i<anykeylist.Size();i++)
		{
			if (anykeylist[i].check(owner)) return true;
		}
		return false;
	}
};

struct Lock
{
	TArray<Keygroup *> keylist;
	char * message;
	char * remotemsg;
	int locksound;
	int	rgb;

	Lock()
	{
		message=remotemsg=NULL;
		rgb=0;
	}

	~Lock()
	{
		for(int i=0;i<keylist.Size();i++) delete keylist[i];
		keylist.Clear();
		if (message) delete [] message;
		if (remotemsg) delete [] remotemsg;
	}

	bool check(AActor * owner)
	{
		// An empty key list means that any key will do
		if (!keylist.Size())
		{
			for (AInventory * item = owner->Inventory; item != NULL; item = item->Inventory)
			{
				if (item->IsKindOf (RUNTIME_CLASS(AKey)))
				{
					return true;
				}
			}
		}
		else for(int i=0;i<keylist.Size();i++)
		{
			if (!keylist[i]->check(owner)) return false;
		}
		return true;
	}
};


static Lock * locks[256];		// all valid locks
static bool keysdone=false;		// have the locks been initialized?
static int currentnumber;		// number to be assigned to next key
static bool ignorekey;			// set to true when the current lock is not being used


static const char * keywords_lock[]={
	"ANY",
	"MESSAGE",
	"REMOTEMESSAGE",
	"MAPCOLOR",
	"LOCKEDSOUND",
	NULL
};

//===========================================================================
//
//
//===========================================================================

static void AddOneKey(Keygroup * keygroup, const TypeInfo * mi)
{
	if (mi)
	{
		// Any inventory item can be used to unlock a door
		if (mi->IsDescendantOf(RUNTIME_CLASS(AInventory)))
		{
			OneKey k = {mi,1};
			keygroup->anykeylist.Push (k);

			//... but only keys get key numbers!
			if (mi->IsDescendantOf(RUNTIME_CLASS(AKey)))
			{
				if (!ignorekey &&
					static_cast<AKey*>(GetDefaultByType(mi))->KeyNumber == 0)
				{
					static_cast<AKey*>(GetDefaultByType(mi))->KeyNumber=++currentnumber;
				}
			}
		}
		else
		{
			SC_ScriptError("'%s' is not an inventory item", sc_String);
		}
	}
	else
	{
		SC_ScriptError("Unknown item '%s'", sc_String);
	}
}


//===========================================================================
//
//
//===========================================================================

static Keygroup * ParseKeygroup()
{
	Keygroup * keygroup;
	const TypeInfo * mi;

	SC_MustGetStringName("{");
	keygroup=new Keygroup;
	while (!SC_CheckString("}"))
	{
		SC_MustGetString();
		mi=TypeInfo::FindType(sc_String);
		AddOneKey(keygroup, mi);
	}
	if (keygroup->anykeylist.Size()==0)
	{
		delete keygroup;
		return NULL;
	}
	keygroup->anykeylist.ShrinkToFit();
	return keygroup;
}

//===========================================================================
//
//
//===========================================================================

static void PrintMessage (const char *str)
{
	if (str != NULL)
	{
		string temp;

		if (strchr (str, '$'))
		{
			// The message or part of it is from the LANGUAGE lump
			string name;

			size_t part1 = strcspn (str, "$");
			temp = string(str, part1);

			size_t part2 = strcspn (str + part1 + 1, "$");
			name = string(str + part1 + 1, part2);

			temp += GStrings(name.GetChars());
			if (str[part1 + 1 + part2] == '$')
			{
				temp += str + part1 + part2 + 2;
			}
			str = temp.GetChars();
		}
		C_MidPrint (str);
	}
}

//===========================================================================
//
//
//===========================================================================

static void ParseLock()
{
	int i,r,g,b;
	int keynum;
	Lock sink;
	Lock * lock=&sink;
	Keygroup * keygroup;
	const TypeInfo * mi;

	SC_MustGetNumber();
	keynum=sc_Number;

	SC_MustGetString();
	if (SC_Compare("DOOM"))
	{
		if (gameinfo.gametype != GAME_Doom) keynum=-1;
	}
	else if (SC_Compare("HERETIC"))
	{
		if (gameinfo.gametype != GAME_Heretic) keynum=-1;
	}
	else if (SC_Compare("HEXEN"))
	{
		if (gameinfo.gametype != GAME_Hexen) keynum=-1;
	}
	else if (SC_Compare("STRIFE"))
	{
		if (gameinfo.gametype != GAME_Strife) keynum=-1;
	}
	else SC_UnGet();

	ignorekey=true;
	if (keynum>0 && keynum<255) 
	{
		lock=new Lock;
		if (locks[keynum]) delete locks[keynum];
		locks[keynum]=lock;
		locks[keynum]->locksound = S_FindSound("misc/keytry");
		ignorekey=false;
	}
	else if (keynum!=-1)
	{
		SC_ScriptError("Lock index %d out of range", keynum);
	}

	SC_MustGetStringName("{");
	while (!SC_CheckString("}"))
	{
		SC_MustGetString();
		switch(i=SC_MatchString(keywords_lock))
		{
		case 0:	// Any
			keygroup=ParseKeygroup();
			if (keygroup) lock->keylist.Push(keygroup);
			break;

		case 1:	// message
			SC_MustGetString();
			lock->message=copystring(sc_String);
			break;

		case 2: // remotemsg
			SC_MustGetString();
			lock->remotemsg=copystring(sc_String);
			break;

		case 3:	// mapcolor
			SC_MustGetNumber();
			r=sc_Number;
			SC_MustGetNumber();
			g=sc_Number;
			SC_MustGetNumber();
			b=sc_Number;
			lock->rgb=MAKERGB(r,g,b);
			break;

		case 4:	// locksound
			SC_MustGetString();
			lock->locksound = S_FindSound(sc_String);
			break;

		default:
			mi=TypeInfo::FindType(sc_String);
			if (mi) 
			{
				keygroup=new Keygroup;
				AddOneKey(keygroup, mi);
				if (keygroup) 
				{
					keygroup->anykeylist.ShrinkToFit();
					lock->keylist.Push(keygroup);
				}
			}
			break;
		}
	}
	// copy the messages if the other one does not exist
	if (!lock->remotemsg && lock->message) lock->remotemsg=strdup(lock->message);
	if (!lock->message && lock->remotemsg) lock->message=strdup(lock->remotemsg);
	lock->keylist.ShrinkToFit();
}

//===========================================================================
//
// Clears all key numbers so the parser can assign its own ones
// This ensures that only valid keys are considered by the key cheats
//
//===========================================================================

static void ClearLocks()
{
	int i;
	for(i=0;i<TypeInfo::m_NumTypes;i++)
	{
		if (TypeInfo::m_Types[i]->IsDescendantOf(RUNTIME_CLASS(AKey)))
		{
			static_cast<AKey*>(GetDefaultByType(TypeInfo::m_Types[i]))->KeyNumber=0;
		}
	}
	for(i=0;i<256;i++)
	{
		if (locks[i]!=NULL) 
		{
			delete locks[i];
			locks[i]=NULL;
		}
	}
	currentnumber=0;
	keysdone=false;
}

//===========================================================================
//
//
//===========================================================================

void P_InitKeyMessages()
{
	int lastlump, lump;

	lastlump = 0;

	ClearLocks();
	while ((lump = Wads.FindLump ("LOCKDEFS", &lastlump)) != -1)
	{
		SC_OpenLumpNum (lump, "LOCKDEFS");
		while (SC_GetString ())
		{
			if (SC_Compare("LOCK")) 
			{
				ParseLock();
			}
			else if (SC_Compare("CLEARLOCKS"))
			{
				// clear all existing lock defintions and key numbers
				ClearLocks();
			}
			else 
				SC_ScriptError("Unknown command %s in LockDef", sc_String);
		}
		SC_Close();
	}
	keysdone=true;
}


//===========================================================================
//
// P_CheckKeys
//
// Returns true if the actor has the required key. If not, a message is
// shown if the actor is also the consoleplayer's camarea, and false is
// returned.
//
//===========================================================================

bool P_CheckKeys (AActor *owner, int keynum, bool remote)
{
	const char *failtext = NULL;
	int failsound = 0;

	failtext = NULL;
	failsound = NULL;

	if (keynum<=0 || keynum>255) return true;
	// Just a safety precaution. The messages should have been initialized upon game start.
	if (!keysdone) P_InitKeyMessages();

	if (!locks[keynum]) 
	{
		if (keynum==103 && gameinfo.gametype == GAME_Strife)
			failtext = "THIS AREA IS ONLY AVAILABLE IN THE RETAIL VERSION OF STRIFE";
		else
			failtext = "That doesn't seem to work";

		failsound = S_FindSound("misc/keytry");
	}
	else
	{
		if (locks[keynum]->check(owner)) return true;
		failtext = remote? locks[keynum]->remotemsg : locks[keynum]->message;
		failsound = locks[keynum]->locksound;
	}

	// If we get here, that means the actor isn't holding an appropriate key.

	if (owner == players[consoleplayer].camera)
	{
		PrintMessage(failtext);
		S_SoundID (owner, CHAN_VOICE, failsound, 1, ATTN_NORM);
	}

	return false;
}

//==========================================================================
//
// AKey implementation
//
//==========================================================================

IMPLEMENT_STATELESS_ACTOR (AKey, Any, -1, 0)
 PROP_Inventory_FlagsSet (IF_INTERHUBSTRIP)
 PROP_Inventory_PickupSound ("misc/k_pkup")
END_DEFAULTS

bool AKey::HandlePickup (AInventory *item)
{
	// In single player, you can pick up an infinite number of keys
	// even though you can only hold one of each.
	if (multiplayer)
	{
		return Super::HandlePickup (item);
	}
	if (GetClass() == item->GetClass())
	{
		item->ItemFlags |= IF_PICKUPGOOD;
		return true;
	}
	if (Inventory != NULL)
	{
		return Inventory->HandlePickup (item);
	}
	return false;
}

bool AKey::ShouldStay ()
{
	return !!multiplayer;
}

//==========================================================================
//
// These 2 methods are practically obsolete... ;)
//
//==========================================================================

const char *AKey::NeedKeyMessage (bool remote, int keynum)
{
	return "You don't have the key";
}

const char *AKey::NeedKeySound ()
{
	return "misc/keytry";
}


//==========================================================================
//
// These functions can be used to get color information for
// automap display of keys and locked doors
//
//==========================================================================

int P_GetMapColorForLock (int lock)
{
	if (lock > 0 && lock < 256)
	{
		if (locks[lock]) return locks[lock]->rgb;
	}
	return 0;
}

//==========================================================================
//
//
//
//==========================================================================

int P_GetMapColorForKey (AInventory * key)
{
	int i;

	for (i = 0; i < 256; i++)
	{
		if (locks[i] && locks[i]->check(key)) return locks[i]->rgb;
	}
	return 0;
}
