#include "doomtype.h"
#include "doomstat.h"
#include "v_font.h"
#include "v_video.h"
#include "sbar.h"
#include "r_defs.h"
#include "w_wad.h"
#include "m_random.h"
#include "d_player.h"
#include "st_stuff.h"
#include "r_local.h"
#include "m_swap.h"
#include "templates.h"
#include "a_keys.h"
#include "a_strifeweaps.h"
#include "a_strifeglobal.h"
#include "gi.h"

// Number of tics to move the popscreen up and down.
#define POP_TIME (TICRATE/8)

// Popscreen height when fully extended
#define POP_HEIGHT 104

// Number of tics to scroll keys left
#define KEY_TIME (TICRATE/3)

class FHealthBar : public FTexture
{
public:
	FHealthBar ();

	const BYTE *GetColumn (unsigned int column, const Span **spans_out);
	const BYTE *GetPixels ();
	bool CheckModified ();
	void Unload ();

	void SetVial (int level);

protected:
	BYTE Pixels[200*2];
	BYTE Colors[8];
	static const Span DummySpan[2];

	int VialLevel;
	bool NeedRefresh;

	void MakeTexture ();
	void FillBar (int min, int max, BYTE light, BYTE dark);
};

const FTexture::Span FHealthBar::DummySpan[2] = { { 0, 2 }, { 0, 0 } };

FHealthBar::FHealthBar ()
: VialLevel(0), NeedRefresh(false)
{
	int i;

	static const BYTE rgbs[8*3] =
	{
		180, 228, 128,	// light green
		128, 180, 80,	// dark green

		196, 204, 252,	// light blue
		148, 152, 200,	// dark blue

		224, 188, 0,	// light gold
		208, 128, 0,	// dark gold

		216, 44,  44,	// light red
		172, 28,  28	// dark red
	};

	Width = 200;
	Height = 2;
	WidthBits = 8;
	HeightBits = 1;
	WidthMask = 255;

	for (i = 0; i < 8; ++i)
	{
		Colors[i] = ColorMatcher.Pick (rgbs[i*3], rgbs[i*3+1], rgbs[i*3+2]);
	}
}

bool FHealthBar::CheckModified ()
{
	return NeedRefresh;
}

void FHealthBar::Unload ()
{
}

const BYTE *FHealthBar::GetColumn (unsigned int column, const Span **spans_out)
{
	if (NeedRefresh)
	{
		MakeTexture ();
	}
	if (column > 199)
	{
		column = 199;
	}
	if (spans_out != NULL)
	{
		*spans_out = &DummySpan[column >= (unsigned int)VialLevel*2];
	}
	return Pixels + column*2;
}

const BYTE *FHealthBar::GetPixels ()
{
	if (NeedRefresh)
	{
		MakeTexture ();
	}
	return Pixels;
}

void FHealthBar::SetVial (int level)
{
	if (level < 0)
	{
		level = 0;
	}
	else if (level > 200 && level != 999)
	{
		level = 200;
	}
	if (VialLevel != level)
	{
		VialLevel = level;
		NeedRefresh = true;
	}
}

void FHealthBar::MakeTexture ()
{
	if (VialLevel == 999)
	{
		FillBar (0, 100, Colors[4], Colors[5]);
	}
	else
	{
		if (VialLevel <= 100)
		{
			if (VialLevel <= 10)
			{
				FillBar (0, VialLevel, Colors[6], Colors[7]);
			}
			else if (VialLevel <= 20)
			{
				FillBar (0, VialLevel, Colors[4], Colors[5]);
			}
			else
			{
				FillBar (0, VialLevel, Colors[0], Colors[1]);
			}
			FillBar (VialLevel, 100, 0, 0);
		}
		else
		{
			int stop = 100 - (VialLevel - 100);
			FillBar (0, stop, Colors[0], Colors[1]);
			FillBar (stop, 100, Colors[2], Colors[3]);
		}
	}
}

void FHealthBar::FillBar (int min, int max, BYTE light, BYTE dark)
{
#ifdef WORDS_BIGENDIAN
	SDWORD fill = (light << 24) | (dark << 16) | (light << 8) | dark;
#else
	SDWORD fill = light | (dark << 8) | (light << 16) | (dark << 24);
#endif
	if (max > min)
	{
		clearbuf (&Pixels[min*4], max - min, fill);
	}
}

class FStrifeStatusBar : public FBaseStatusBar
{
public:
	FStrifeStatusBar () : FBaseStatusBar (32)
	{
		static const char *sharedLumpNames[] =
		{
			NULL,		NULL,		"INVFONY0",	"INVFONY1",	"INVFONY2",
			"INVFONY3",	"INVFONY4",	"INVFONY5",	"INVFONY6",	"INVFONY7",
			"INVFONY8",	"INVFONY9",	NULL,		NULL,		NULL,
			NULL,		NULL,		NULL,		NULL,		NULL,
			NULL,		NULL,		NULL,		"INVFONG0",	"INVFONG1",
			"INVFONG2",	"INVFONG3",	"INVFONG4",	"INVFONG5",	"INVFONG6",
			"INVFONG7",	"INVFONG8",	"INVFONG9"
		};

		FBaseStatusBar::Images.Init (sharedLumpNames, NUM_BASESB_IMAGES);
		DoCommonInit ();
	}

	~FStrifeStatusBar ()
	{
	}

	void NewGame ()
	{
		Images.Uninit ();
		DoCommonInit ();
		if (CPlayer != NULL)
		{
			AttachToPlayer (CPlayer);
		}
	}

	void Draw (EHudState state)
	{
		FBaseStatusBar::Draw (state);

		if (state == HUD_Fullscreen)
		{
			SB_state = screen->GetPageCount ();
			DrawFullScreenStuff ();
		}
		else if (state == HUD_StatusBar)
		{
			if (SB_state != 0)
			{
				SB_state--;
			}
			DrawMainBar ();
		}
	}

	void ShowPop (int popnum)
	{
		if (popnum == CurrentPop)
		{
			if (popnum == POP_Keys)
			{
				AInventory *item;
				int i;

				KeyPopPos += 10;
				KeyPopScroll = 280;

				for (item = CPlayer->mo->Inventory, i = 0;
					item != NULL;
					item = item->Inventory)
				{
					if (item->IsKindOf (RUNTIME_CLASS(AKey)))
					{
						if (i == KeyPopPos)
						{
							return;
						}
						i++;
					}
				}
			}
			PendingPop = POP_None;
			// Do not scroll keys horizontally when dropping the popscreen
			KeyPopScroll = 0;
			KeyPopPos -= 10;
		}
		else
		{
			KeyPopPos = 0;
			PendingPop = popnum;
		}
	}

private:
	void DoCommonInit ()
	{
		static const char *strifeLumpNames[] =
		{
			"INVCURS", "CURSOR01", "INVBACK", "INVTOP", "INVPOP", "INVPOP2",
			"INVPBAK", "INVPBAK2",
			"INVFONG0", "INVFONG1", "INVFONG2", "INVFONG3", "INVFONG4",
			"INVFONG5", "INVFONG6", "INVFONG7", "INVFONG8", "INVFONG9",
			"INVFONG%",
			"INVFONY0", "INVFONY1", "INVFONY2", "INVFONY3", "INVFONY4",
			"INVFONY5", "INVFONY6", "INVFONY7", "INVFONY8", "INVFONY9",
			"INVFONY%",
			"I_COMM", "I_MDKT", "I_ARM1", "I_ARM2"
		};

		Images.Init (strifeLumpNames, NUM_STRIFESB_IMAGES);

		CursorImage = Images[imgINVCURS] != NULL ? imgINVCURS : imgCURSOR01;

		SB_state = screen->GetPageCount ();

		CurrentPop = POP_None;
		PendingPop = POP_NoChange;
		PopHeight = 0;
		KeyPopPos = 0;
		KeyPopScroll = 0;
		ItemFlash = 0;
	}

	void Tick ()
	{
		FBaseStatusBar::Tick ();

		if (ItemFlash > 0)
		{
			ItemFlash -= FRACUNIT/14;
			if (ItemFlash < 0)
			{
				ItemFlash = 0;
			}
		}

		PopHeightChange = 0;
		if (PendingPop != POP_NoChange)
		{
			if (PopHeight < 0)
			{
				PopHeightChange = POP_HEIGHT / POP_TIME;
				PopHeight += POP_HEIGHT / POP_TIME;
			}
			else
			{
				CurrentPop = PendingPop;
				PendingPop = POP_NoChange;
			}
		}
		else
		{
			if (CurrentPop == POP_None)
			{
				PopHeight = 0;
			}
			else if (PopHeight > -POP_HEIGHT)
			{
				PopHeight -= POP_HEIGHT / POP_TIME;
				if (PopHeight < -POP_HEIGHT)
				{
					PopHeight = -POP_HEIGHT;
				}
				else
				{
					PopHeightChange = -POP_HEIGHT / POP_TIME;
				}
			}
			if (KeyPopScroll > 0)
			{
				KeyPopScroll -= 280 / KEY_TIME;
				if (KeyPopScroll < 0)
				{
					KeyPopScroll = 0;
				}
			}
		}
	}

	void FlashItem (const TypeInfo *itemtype)
	{
		ItemFlash = FRACUNIT*3/4;
	}

	void DrawMainBar ()
	{
		AInventory *item;
		int i;

		// Pop screen (log, keys, and status)
		if (CurrentPop != POP_None && PopHeight < 0)
		{
			DrawPopScreen (Scaled ? (ST_Y - 8) * screen->GetHeight() / 200 : ST_Y - 8);
		}

		DrawImage (Images[imgINVBACK], 0, 0);
		DrawImage (Images[imgINVTOP], 0, -8);

		// Health
		DrINumber (CPlayer->health, 79, -6, imgFONG0);
		if (CPlayer->cheats & CF_GODMODE)
		{
			HealthBar.SetVial (999);
		}
		else
		{
			HealthBar.SetVial (CPlayer->health);
		}
		DrawImage (&HealthBar, 49, 4);
		DrawImage (&HealthBar, 49, 7);

		// Armor
		item = CPlayer->mo->FindInventory<ABasicArmor>();
		if (item != NULL && item->Amount > 0)
		{
			DrawImage (TexMan(item->Icon), 2, 9);
			DrINumber (item->Amount, 27, 23, imgFONY0);
		}

		// Ammo
		AAmmo *ammo1, *ammo2;
		int ammocount1, ammocount2;

		GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2);
		if (ammo1 != NULL)
		{
			DrINumber (ammo1->Amount, 311, -6, imgFONG0);
			DrawImage (TexMan(ammo1->Icon), 290, 13);
			if (ammo2 != NULL)
			{
/*				int y = MIN (-5 - BigHeight, -5 - TexMan(ammo1->Icon)->GetHeight());
				screen->DrawTexture (TexMan(ammo2->Icon), -14, y,
					DTA_HUDRules, HUD_Normal,
					DTA_CenterBottomOffset, true,
					TAG_DONE);
				DrBNumberOuterFont (ammo2->Amount, -67, y - BigHeight);
*/			}
		}

		// Sigil
		item = CPlayer->mo->FindInventory<ASigil>();
		if (item != NULL)
		{
			DrawImage (TexMan(item->Icon), 253, 7);
		}

		// Inventory
		CPlayer->InvFirst = ValidateInvFirst (6);
		for (item = CPlayer->InvFirst, i = 0; item != NULL && i < 6; item = item->NextInv(), ++i)
		{
			if (item == CPlayer->InvSel)
			{
				screen->DrawTexture (Images[CursorImage],
					42 + 35*i + ST_X, 12 + ST_Y,
					DTA_320x200, Scaled,
					DTA_Alpha, FRACUNIT - ItemFlash,
					TAG_DONE);
			}
			if (item->Icon != 0)
			{
				DrawImage (TexMan(item->Icon), 48 + 35*i, 14);
			}
			DrINumber (item->Amount, 74 + 35*i, 23, imgFONY0);
		}
	}

	void DrawFullScreenStuff ()
	{
		// Draw health
		DrINumberOuter (CPlayer->health, 4, -10, false, 7);
		screen->DrawTexture (Images[imgMEDI], 14, -17,
			DTA_HUDRules, HUD_Normal,
			DTA_CenterBottomOffset, true,
			TAG_DONE);

		// Draw armor
		ABasicArmor *armor = CPlayer->mo->FindInventory<ABasicArmor>();
		if (armor != NULL && armor->Amount != 0)
		{
			DrINumberOuter (armor->Amount, 35, -10, false, 7);
			screen->DrawTexture (TexMan(armor->Icon), 45, -17,
				DTA_HUDRules, HUD_Normal,
				DTA_CenterBottomOffset, true,
				TAG_DONE);
		}

		// Draw ammo
		AAmmo *ammo1, *ammo2;
		int ammocount1, ammocount2;

		GetCurrentAmmo (ammo1, ammo2, ammocount1, ammocount2);
		if (ammo1 != NULL)
		{
			// Draw primary ammo in the bottom-right corner
			DrINumberOuter (ammo1->Amount, -23, -10, false, 7);
			screen->DrawTexture (TexMan(ammo1->Icon), -14, -17,
				DTA_HUDRules, HUD_Normal,
				DTA_CenterBottomOffset, true,
				TAG_DONE);
			if (ammo2 != NULL)
			{
				// Draw secondary ammo just above the primary ammo
				DrINumberOuter (ammo2->Amount, -23, -48, false, 7);
				screen->DrawTexture (TexMan(ammo2->Icon), -14, -55,
					DTA_HUDRules, HUD_Normal,
					DTA_CenterBottomOffset, true,
					TAG_DONE);
			}
		}

		if (deathmatch)
		{ // Draw frags (in DM)
			DrBNumberOuterFont (CPlayer->fragcount, -44, 1);
		}

		// Draw inventory
		if (CPlayer->inventorytics == 0)
		{
			if (CPlayer->InvSel != 0)
			{
				if (ItemFlash > 0)
				{
					FTexture *cursor = Images[CursorImage];
					screen->DrawTexture (cursor, -28, -15,
						DTA_HUDRules, HUD_Normal,
						DTA_LeftOffset, cursor->GetWidth(),
						DTA_TopOffset, cursor->GetHeight(),
						DTA_Alpha, ItemFlash,
						TAG_DONE);
				}
				DrINumberOuter (CPlayer->InvSel->Amount, -51, -10, false, 7);
				screen->DrawTexture (TexMan(CPlayer->InvSel->Icon), -42, -17,
					DTA_HUDRules, HUD_Normal,
					DTA_CenterBottomOffset, true,
					TAG_DONE);
			}
		}
		else
		{
			CPlayer->InvFirst = ValidateInvFirst (6);
			int i = 0;
			AInventory *item;
			if (CPlayer->InvFirst != NULL)
			{
				for (item = CPlayer->InvFirst; item != NULL && i < 6; item = item->NextInv(), ++i)
				{
					if (item == CPlayer->InvSel)
					{
						screen->DrawTexture (Images[CursorImage], -100+i*35, -21,
							DTA_HUDRules, HUD_HorizCenter,
							DTA_Alpha, TRANSLUC75,
							TAG_DONE);
					}
					if (item->Icon != 0)
					{
						screen->DrawTexture (TexMan(item->Icon), -94 + i*35, -19,
							DTA_HUDRules, HUD_HorizCenter,
							TAG_DONE);
					}
					DrINumberOuter (item->Amount, -89 + i*35, -10, true, 7);
				}
			}
		}

		// Draw pop screen (log, keys, and status)
		if (CurrentPop != POP_None && PopHeight < 0)
		{
			DrawPopScreen (screen->GetHeight());
		}
	}

	void DrawPopScreen (int bottom)
	{
		char buff[64];
		const char *label;
		int i;
		AInventory *item;
		int xscale, yscale, left, top;
		int bars = (CurrentPop == POP_Status) ? imgINVPOP : imgINVPOP2;
		int back = (CurrentPop == POP_Status) ? imgINVPBAK : imgINVPBAK2;
		// Extrapolate the height of the popscreen for smoother movement
		int height = clamp<int> (PopHeight + FixedMul (r_TicFrac, PopHeightChange), -POP_HEIGHT, 0);

		xscale = CleanXfac;
		yscale = CleanYfac;
		left = screen->GetWidth()/2 - 160*CleanXfac;
		top = bottom + height * yscale;

		screen->DrawTexture (Images[back], left, top, DTA_CleanNoMove, true, DTA_Alpha, FRACUNIT*3/4, TAG_DONE);
		screen->DrawTexture (Images[bars], left, top, DTA_CleanNoMove, true, TAG_DONE);
		screen->SetFont (SmallFont2);

		switch (CurrentPop)
		{
		case POP_Log:
			// Draw the latest log message.
			sprintf (buff, "%02d:%02d:%02d",
				(level.time/TICRATE)/3600,
				((level.time/TICRATE)%3600)/60,
				(level.time/TICRATE)%60);

			screen->DrawText (CR_UNTRANSLATED, left+210*xscale, top+8*yscale, buff,
				DTA_CleanNoMove, true, TAG_DONE);

			if (CPlayer->LogText != NULL)
			{
				brokenlines_t *lines = V_BreakLines (272, CPlayer->LogText);
				for (i = 0; lines[i].width >= 0; ++i)
				{
					screen->DrawText (CR_UNTRANSLATED, left+24*xscale, top+(18+i*12)*yscale,
						lines[i].string, DTA_CleanNoMove, true, TAG_DONE);
				}
				V_FreeBrokenLines (lines);
			}
			break;

		case POP_Keys:
			// List the keys the player has.
			int pos, endpos, leftcol;
			int clipleft, clipright;
			
			pos = KeyPopPos;
			endpos = pos + 10;
			leftcol = 20;
			clipleft = left + 17*xscale;
			clipright = left + (320-17)*xscale;
			if (KeyPopScroll > 0)
			{
				// Extrapolate the scroll position for smoother scrolling
				int scroll = MAX<int> (0,KeyPopScroll - FixedMul (r_TicFrac, 280/KEY_TIME));
				pos -= 10;
				leftcol = leftcol - 280 + scroll;
			}
			for (i = 0, item = CPlayer->mo->Inventory;
				i < endpos && item != NULL;
				item = item->Inventory)
			{
				if (!item->IsKindOf (RUNTIME_CLASS(AKey)))
					continue;
				
				if (i < pos)
				{
					i++;
					continue;
				}

				label = item->GetClass()->Meta.GetMetaString (AMETA_StrifeName);
				if (label == NULL)
				{
					label = item->GetClass()->Name + 1;
				}

				int colnum = ((i-pos) / 5) & (KeyPopScroll > 0 ? 3 : 1);
				int rownum = (i % 5) * 18;

				screen->DrawTexture (TexMan(item->Icon),
					left + (colnum * 140 + leftcol)*xscale,
					top + (6 + rownum)*yscale,
					DTA_CleanNoMove, true,
					DTA_ClipLeft, clipleft,
					DTA_ClipRight, clipright,
					TAG_DONE);
				screen->DrawText (CR_UNTRANSLATED,
					left + (colnum * 140 + leftcol + 17)*xscale,
					top + (11 + rownum)*yscale,
					label,
					DTA_CleanNoMove, true,
					DTA_ClipLeft, clipleft,
					DTA_ClipRight, clipright,
					TAG_DONE);
				i++;
			}
			break;

		case POP_Status:
			// Show miscellaneous status items.
			
			// Print stats
			DrINumber2 (CPlayer->accuracy, left+268*xscale, top+28*yscale, 7*xscale, imgFONY0);
			DrINumber2 (CPlayer->stamina, left+268*xscale, top+52*yscale, 7*xscale, imgFONY0);

			// How many keys does the player have?
			for (i = 0, item = CPlayer->mo->Inventory;
				item != NULL;
				item = item->Inventory)
			{
				if (item->IsKindOf (RUNTIME_CLASS(AKey)))
				{
					i++;
				}
			}
			DrINumber2 (i, left+268*xscale, top+76*yscale, 7*xscale, imgFONY0);

			// Does the player have a communicator?
			item = CPlayer->mo->FindInventory (RUNTIME_CLASS(ACommunicator));
			if (item != NULL)
			{
				screen->DrawTexture (TexMan(item->Icon),
					left + 280*xscale,
					top + 74*yscale,
					DTA_CleanNoMove, true, TAG_DONE);
			}

			// How much ammo does the player have?
			static const struct
			{
				const TypeInfo *AmmoType;
				int Y;
			} AmmoList[7] =
			{
				{ RUNTIME_CLASS(AClipOfBullets),			19 },
				{ RUNTIME_CLASS(APoisonBolts),				35 },
				{ RUNTIME_CLASS(AElectricBolts),			43 },
				{ RUNTIME_CLASS(AHEGrenadeRounds),			59 },
				{ RUNTIME_CLASS(APhosphorusGrenadeRounds),	67 },
				{ RUNTIME_CLASS(AMiniMissiles),				75 },
				{ RUNTIME_CLASS(AEnergyPod),				83 }
			};
			for (i = 0; i < 7; ++i)
			{
				item = CPlayer->mo->FindInventory (AmmoList[i].AmmoType);

				if (item == NULL)
				{
					DrINumber2 (0, left+206*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
					DrINumber2 (((AInventory *)GetDefaultByType (AmmoList[i].AmmoType))->MaxAmount,
						left+239*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
				}
				else
				{
					DrINumber2 (item->Amount, left+206*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
					DrINumber2 (item->MaxAmount, left+239*xscale, top+AmmoList[i].Y*yscale, 7*xscale, imgFONY0);
				}
			}

			// What weapons does the player have?
			static const struct
			{
				const char *TypeName;
				int X, Y;
			} WeaponList[6] =
			{
				{ "StrifeCrossbow",			23, 19 },
				{ "AssaultGun",				21, 41 },
				{ "FlameThrower",			57, 50 },
				{ "MiniMissileLauncher",	20, 64 },
				{ "StrifeGrenadeLauncher",	55, 20 },
				{ "Mauler",					52, 75 },
			};
			for (i = 0; i < 6; ++i)
			{
				item = CPlayer->mo->FindInventory (TypeInfo::FindType (WeaponList[i].TypeName));

				if (item != NULL)
				{
					screen->DrawTexture (TexMan(item->Icon),
						left + WeaponList[i].X*xscale,
						top + WeaponList[i].Y*yscale,
						DTA_CleanNoMove, true,
						DTA_LeftOffset, 0,
						DTA_TopOffset, 0,
						TAG_DONE);
				}
			}
			break;
		}

		screen->SetFont (SmallFont);
	}

	void DrINumber (signed int val, int x, int y, int imgBase) const
	{
		x -= 7;

		if (val == 0)
		{
			DrawImage (Images[imgBase], x, y);
		}
		else
		{
			while (val != 0)
			{
				DrawImage (Images[imgBase+val%10], x, y);
				val /= 10;
				x -= 7;
			}
		}
	}

	void DrINumber2 (signed int val, int x, int y, int width, int imgBase) const
	{
		x -= width;

		if (val == 0)
		{
			screen->DrawTexture (Images[imgBase], x, y, DTA_CleanNoMove, true, TAG_DONE);
		}
		else
		{
			while (val != 0)
			{
				screen->DrawTexture (Images[imgBase+val%10], x, y, DTA_CleanNoMove, true, TAG_DONE);
				val /= 10;
				x -= width;
			}
		}
	}

	enum
	{
		imgINVCURS,
		imgCURSOR01,
		imgINVBACK,
		imgINVTOP,
		imgINVPOP,
		imgINVPOP2,
		imgINVPBAK,
		imgINVPBAK2,
		imgFONG0,
		imgFONG1,
		imgFONG2,
		imgFONG3,
		imgFONG4,
		imgFONG5,
		imgFONG6,
		imgFONG7,
		imgFONG8,
		imgFONG9,
		imgFONG_PERCENT,
		imgFONY0,
		imgFONY1,
		imgFONY2,
		imgFONY3,
		imgFONY4,
		imgFONY5,
		imgFONY6,
		imgFONY7,
		imgFONY8,
		imgFONY9,
		imgFONY_PERCENT,
		imgCOMM,
		imgMEDI,
		imgARM1,
		imgARM2,

		NUM_STRIFESB_IMAGES
	};

	FImageCollection Images;
	FHealthBar HealthBar;

	int CursorImage;
	int CurrentPop, PendingPop, PopHeight, PopHeightChange;
	int KeyPopPos, KeyPopScroll;
	fixed_t ItemFlash;
};

FBaseStatusBar *CreateStrifeStatusBar ()
{
	return new FStrifeStatusBar;
}
