
struct MugShot
{
	enum StateFlags
	{
		STANDARD = 0x0,

		XDEATHFACE = 0x1,
		ANIMATEDGODMODE = 0x2,
		DISABLEGRIN = 0x4,
		DISABLEOUCH = 0x8,
		DISABLEPAIN = 0x10,
		DISABLERAMPAGE = 0x20,
		CUSTOM = 0x40,
	}
}


class BaseStatusBar native ui
{
	enum EPop
	{
		POP_NoChange = -1,
		POP_None,
		POP_Log,
		POP_Keys,
		POP_Status
	}

	// Status face stuff
	enum EMug
	{
		ST_NUMPAINFACES		= 5,
		ST_NUMSTRAIGHTFACES	= 3,
		ST_NUMTURNFACES		= 2,
		ST_NUMSPECIALFACES	= 3,
		ST_NUMEXTRAFACES	= 2,
		ST_FACESTRIDE		= ST_NUMSTRAIGHTFACES+ST_NUMTURNFACES+ST_NUMSPECIALFACES,
		ST_NUMFACES			= ST_FACESTRIDE*ST_NUMPAINFACES+ST_NUMEXTRAFACES,

		ST_TURNOFFSET		= ST_NUMSTRAIGHTFACES,
		ST_OUCHOFFSET		= ST_TURNOFFSET + ST_NUMTURNFACES,
		ST_EVILGRINOFFSET	= ST_OUCHOFFSET + 1,
		ST_RAMPAGEOFFSET	= ST_EVILGRINOFFSET + 1,
		ST_GODFACE			= ST_NUMPAINFACES*ST_FACESTRIDE,
		ST_DEADFACE			= ST_GODFACE + 1
	}
	
	enum EHudState
	{
		HUD_StatusBar,
		HUD_Fullscreen,
		HUD_None,

		HUD_AltHud // Used for passing through popups to the alt hud
	}
	
	enum DI_Flags
	{
		DI_SKIPICON = 0x1,
		DI_SKIPALTICON = 0x2,
		DI_SKIPSPAWN = 0x4,
		DI_SKIPREADY = 0x8,
		DI_ALTICONFIRST = 0x10,
		DI_TRANSLATABLE = 0x20,
		DI_FORCESCALE = 0x40,
		DI_DIM = 0x80,
	};

	enum IconType
	{
		ITYPE_PLAYERICON = 1000,
		ITYPE_AMMO1,
		ITYPE_AMMO2,
		ITYPE_ARMOR,
		ITYPE_WEAPON,
		ITYPE_SIGIL,
		ITYPE_WEAPONSLOT,
		ITYPE_SELECTEDINVENTORY,
	}
	
	enum HexArmorType
	{
		HEXENARMOR_ARMOR,
		HEXENARMOR_SHIELD,
		HEXENARMOR_HELM,
		HEXENARMOR_AMULET,
	};
	
	enum EAlign
	{
		ALIGN_TOP = 0,
		ALIGN_VCENTER = 1,
		ALIGN_BOTTOM = 2,
		ALIGN_VOFFSET = 3,
		ALIGN_VMASK = 3,

		ALIGN_LEFT = 0,
		ALIGN_HCENTER = 4,
		ALIGN_RIGHT = 8,
		ALIGN_HOFFSET = 12,
		ALIGN_HMASK = 12,

		ALIGN_CENTER = ALIGN_VCENTER|ALIGN_HCENTER,
		ALIGN_CENTER_BOTTOM = ALIGN_BOTTOM|ALIGN_HCENTER,
		ALIGN_OFFSETS = ALIGN_HOFFSET|ALIGN_VOFFSET
	};
	
	enum ETextAlign
	{
		TEXT_LEFT = 0,
		TEXT_CENTER = 1,
		TEXT_RIGHT = 2
	};

	enum SBGameModes
	{
		GAMEMODE_SINGLEPLAYER	= 0x1,
		GAMEMODE_COOPERATIVE	= 0x2,
		GAMEMODE_DEATHMATCH		= 0x4,
		GAMEMODE_TEAMGAME		= 0x8
	};

	enum AmmoModes
	{
		AMMO_PRIMARY,
		AMMO_SECONDARY,
		AMMO_ANY,
		AMMO_BOTH
	};
	
	enum EHudDraw
	{
		HUD_Normal,
		HUD_HorizCenter
	}
	
	enum ENumFlags
	{
		FNF_FILLZEROS,
		FNF_WHENNOTZERO,
	}
	
	const XHAIRSHRINKSIZE =(1./18);
	const XHAIRPICKUPSIZE = (2+XHAIRSHRINKSIZE);
	const POWERUPICONSIZE = 32;
	

	native int ST_X, ST_Y;
	native int RelTop;
	native int HorizontalResolution, VerticalResolution;
	native bool Scaled;
	native bool Centering;
	native bool FixedOrigin;
	native bool CompleteBorder;
	native double CrosshairSize;
	native double Displacement;
	native PlayerInfo CPlayer;
	native bool ShowLog;

	// These are block properties for the drawers. A child class can set them to have a block of items use the same settings.
	native double Alpha;
	native Vector2 drawOffset;		// can be set by subclasses to offset drawing operations
	native double drawClip[4];		// defines a clipping rectangle (not used yet)
	native bool fullscreenOffsets;	// current screen is displayed with fullscreen behavior.
	native Vector2 cleanScale;		// factor for scaled fullscreen display.
	
	
	native void SetSize(int height, int vwidth, int vheight);
	virtual void Init() {}

	native virtual void SetScaled(bool scale, bool force = false);
	native virtual void Tick ();
	native virtual void Draw (int state, double TicFrac);
	native virtual void ScreenSizeChanged ();
	native virtual clearscope void ReceivedWeapon (Weapon weapn);

	virtual void FlashItem (class<Inventory> itemtype) {}
	virtual void AttachToPlayer (PlayerInfo player) { CPlayer = player; }
	virtual void FlashCrosshair () { CrosshairSize = XHAIRPICKUPSIZE; }
	virtual void NewGame () {}
	virtual void ShowPop (int popnum) { ShowLog = (popnum == POP_Log && !ShowLog); }
	virtual bool MustDrawLog(int state) { return true; }
	virtual void SetMugShotState (String state_name, bool wait_till_done=false, bool reset=false) {}
	
	native void RefreshBackground () const;
	native TextureID GetMugshot(PlayerInfo player, String default_face, int accuracy, int stateflags=MugShot.STANDARD);
	
	// These functions are kept native solely for performance reasons. They get called repeatedly and can drag down performance easily if they get too slow.
	native Inventory ValidateInvFirst (int numVisible) const;
	native static TextureID, bool GetInventoryIcon(Inventory item, int flags);
	native void DrawGraphic(TextureID texture, bool animate, Vector2 pos, double Alpha,  bool translatable, bool dim, int imgAlign, int screenalign, bool alphamap, Vector2 box);
	native void DrawString(Font font, String string, Vector2 pos , double Alpha, int translation, int align, int screenalign, int spacing=0, bool monospaced = false, int shadowX=0, int shadowY=0, int wrapwidth = -1, int linespacing = 4);
	native static String FormatNumber(int number, int minsize, int maxsize, int format, String prefix = "");
	
	
	//============================================================================
	//
	// DBaseStatusBar :: GetCurrentAmmo
	//
	// Returns the types and amounts of ammo used by the current weapon. If the
	// weapon only uses one type of ammo, it is always returned as ammo1.
	//
	//============================================================================

	Inventory, Inventory, int, int GetCurrentAmmo () const
	{
		Ammo ammo1, ammo2;
		if (CPlayer.ReadyWeapon != NULL)
		{
			ammo1 = CPlayer.ReadyWeapon.Ammo1;
			ammo2 = CPlayer.ReadyWeapon.Ammo2;
			if (ammo1 == NULL)
			{
				ammo1 = ammo2;
				ammo2 = NULL;
			}
		}
		else
		{
			ammo1 = ammo2 = NULL;
		}
		let ammocount1 = ammo1 != NULL ? ammo1.Amount : 0;
		let ammocount2 = ammo2 != NULL ? ammo2.Amount : 0;
		return ammo1, ammo2, ammocount1, ammocount2;
	}

	//============================================================================
	//
	// Get an icon
	//
	//============================================================================

	TextureID, Vector2 GetIcon(Inventory item, int flags, bool showdepleted = false)
	{
		TextureID icon;
		Vector2 scale = (1,1);
		icon.SetInvalid();
		if (item != null)
		{
			bool applyscale;
			[icon, applyscale] = GetInventoryIcon(item, flags);
			
			if (item.Amount == 0 && !showdepleted) return icon;
			
			if (applyscale)
				scale = item.Scale;
		}
		return icon;
	}
	
	//============================================================================
	//
	// Convenience functions to retrieve item tags
	//
	//============================================================================

	String GetAmmoTag(bool secondary = false)
	{
		let w = CPlayer.ReadyWeapon;
		if (w == null) return "";
		let ammo = secondary? w.ammo2 : w.ammo1;
		return ammo.GetTag();
	}	

	String GetWeaponTag()
	{
		let w = CPlayer.ReadyWeapon;
		if (w == null) return "";
		return w.GetTag();
	}	

	String GetSelectedInventoryTag()
	{
		let w = CPlayer.mo.InvSel;
		if (w == null) return "";
		return w.GetTag();
	}	

	// These cannot be done in ZScript.
	native String GetGlobalACSString(int index);
	native String GetGlobalACSArrayString(int arrayno, int index);
	native int GetGlobalACSValue(int index);
	native int GetGlobalACSArrayValue(int arrayno, int index);
	
	//============================================================================
	//
	// Convenience functions to retrieve some numbers
	//
	//============================================================================
	
	int GetArmorAmount()
	{
		let armor = CPlayer.mo.FindInventory("BasicArmor");
		return armor? armor.Amount : 0;
	}
	
	int GetMaxAmount(class<Inventory> item)
	{
		let it = CPlayer.mo.FindInventory(item);
		return it? it.MaxAmount : GetDefaultByType(item).MaxAmount;
	}

	int GetArmorSavePercent()
	{
		double add = 0;
		let harmor = HexenArmor(CPlayer.mo.FindInventory("HexenArmor"));
		if(harmor != NULL)
		{
			add = harmor.Slots[0] + harmor.Slots[1] + harmor.Slots[2] + harmor.Slots[3] + harmor.Slots[4];
		}
		//Hexen counts basic armor also so we should too.
		let armor = BasicArmor(CPlayer.mo.FindInventory("BasicArmor"));
		if(armor != NULL)
		{
			add += armor.SavePercent * 100;
		}
		return int(add);
	}
	
	// Note that this retrieves the value in tics, not seconds like the equivalent SBARINFO function.
	// The idea is to let the caller decide what to do with it instead of destroying accuracy here.
	int GetAirTime()
	{
		if(CPlayer.mo.waterlevel < 3)
			return level.airsupply;
		else
			return max(CPlayer.air_finished - level.time, 0);
	}
	
	int GetSelectedInventoryAmount()
	{
		if(CPlayer.mo.InvSel != NULL) return CPlayer.mo.InvSel.Amount;
		return 0;
	}
	
	int GetKeyCount()
	{
		int num = 0;
		for(Inventory item = CPlayer.mo.Inv;item != NULL;item = item.Inv)
		{
			if(item is "Key") num++;
		}
		return num;
	}
	
	//============================================================================
	//
	// various checker functions, based on SBARINFOs condition nodes.
	//
	//============================================================================

	//============================================================================
	//
	// checks current game mode against a flag mask
	//
	//============================================================================

	bool CheckGameMode(int ValidModes)
	{
		return  (!multiplayer && (ValidModes & GAMEMODE_SINGLEPLAYER)) ||
				(deathmatch && (ValidModes & GAMEMODE_DEATHMATCH)) ||
				(multiplayer && !deathmatch && (ValidModes & GAMEMODE_COOPERATIVE)) ||
				(teamplay && (ValidModes & GAMEMODE_TEAMGAME));
	}

	//============================================================================
	//
	// checks ammo use of current weapon
	//
	//============================================================================

	bool WeaponUsesAmmo(int ValidModes)
	{
		if (CPlayer == null) return false;
		let w = CPlayer.ReadyWeapon;
		if (w == null) return false;
		bool usesammo1 = w.AmmoType1 != NULL;
		bool usesammo2 = w.AmmoType2 != NULL;
		
		if (ValidModes == AMMO_PRIMARY) return usesammo1;
		if (ValidModes == AMMO_SECONDARY) return usesammo2;
		if (ValidModes == AMMO_ANY) return (usesammo1 || usesammo2);
		if (ValidModes == AMMO_BOTH) return (usesammo1 && usesammo2);
		return false;
	}

	//============================================================================
	//
	// checks if inventory bar is visible
	//
	//============================================================================

	bool isInventoryBarVisible()
	{
		if (CPlayer == null) return false;
		return (CPlayer.inventorytics <= 0 || level.NoInventoryBar);
	}
	
	//============================================================================
	//
	// checks if aspect ratio is in a given range
	//
	//============================================================================
	
	bool CheckAspectRatio(double min, double max)
	{
		if (CPlayer == null) return false;
		double aspect = screen.GetAspectRatio();
		return (aspect >= min && aspect < max);
	}

	//============================================================================
	//
	// checks if weapon is selected.
	//
	//============================================================================

	bool CheckWeaponSelected(class<Weapon> weap, bool checksister = true)
	{
		if (CPlayer == null) return false;
		let w = CPlayer.ReadyWeapon;
		if (w == null) return false;
		if (w.GetClass() == weap) return true;
		if (checksister && w.SisterWeapon != null && w.SisterWeapon.GetClass() == weap) return true;
		return false;
	}

	//============================================================================
	//
	// checks if player has the given display name
	//
	//============================================================================

	bool CheckDiplayName(String displayname)
	{
		if (CPlayer == null) return false;
		return displayname == PlayerPawn.GetPrintableDisplayName(CPlayer.mo.GetClass());
	}

	//============================================================================
	//
	// checks if player has the given weapon piece
	//
	//============================================================================

	bool CheckWeaponPiece(class<Weapon> weap, int piecenum)
	{
		if (CPlayer == null) return false;
		for(let inv = CPlayer.mo.Inv; inv != NULL; inv = inv.Inv)
		{
			let wh = WeaponHolder(inv);
			if (wh != null && wh.PieceWeapon == weap)
			{
				return (!!(wh.PieceMask & (1 << (PieceNum-1))));
			}
		}
		return false;
	}

	//============================================================================
	//
	// checks if player has the given weapon piece
	//
	//============================================================================

	bool WeaponUsesAmmoType(class<Ammo> ammotype)
	{
		if (CPlayer == null) return false;
		let w = CPlayer.ReadyWeapon;
		if (w == NULL) return false;
		return w.AmmoType1 == ammotype || w.AmmoType2 == ammotype;
	}

	//============================================================================
	//
	// checks if player has the required health
	//
	//============================================================================

	bool CheckHealth(int Amount, bool percentage = false)
	{
		if (CPlayer == null) return false;
		
		int phealth = percentage ? CPlayer.mo.health * 100 / CPlayer.mo.GetMaxHealth() : CPlayer.mo.health;
		return (phealth >= Amount);
	}
	
	//============================================================================
	//
	// checks if player is invulnerable
	//
	//============================================================================

	bool isInvulnerable()
	{
		if (CPlayer == null) return false;
		return ((CPlayer.mo.bInvulnerable) || (CPlayer.cheats & (CF_GODMODE | CF_GODMODE2)));
	}

	//============================================================================
	//
	// checks if player owns enough of the item
	//
	//============================================================================

	bool CheckInventory(class<Inventory> item, int amount = 1)
	{
		if (CPlayer == null) return false;

		let it = CPlayer.mo.FindInventory(item);
		return it != null && it.Amount >= amount;
	}

	
	//---------------------------------------------------------------------------
	//
	// DrawPowerups
	//
	//---------------------------------------------------------------------------

	virtual void DrawPowerups ()
	{
		// The AltHUD specific adjustments have been removed here, because the AltHUD uses its own variant of this function
		// that can obey AltHUD rules - which this cannot.
		Vector2 pos = (-20, POWERUPICONSIZE * 5 / 4);
		double maxpos = screen.GetWidth() / 2;
		for (let item = CPlayer.mo.Inv; item != NULL; item = item.Inv)
		{
			let icon = item.GetPowerupIcon();
			if (icon.IsValid())
			{
				// Each icon gets a 32x32 block.
				DrawTexture(icon, pos, true, 1.0, ALIGN_TOP|ALIGN_RIGHT, (POWERUPICONSIZE, POWERUPICONSIZE), ALIGN_CENTER_BOTTOM);
				pos.x -= POWERUPICONSIZE;
				if (pos.x < -maxpos)
				{
					pos.x = -20;
					pos.y += POWERUPICONSIZE * 3 / 2;
				}
			}
		}
	}
	
	//============================================================================
	//
	// draw stuff
	//
	//============================================================================
	
	int GetTranslation() const
	{
		if(gameinfo.gametype & GAME_Raven)
			return Translation.MakeID(TRANSLATION_PlayersExtra, CPlayer.mo.PlayerNumber());
		else
			return Translation.MakeID(TRANSLATION_Players, CPlayer.mo.PlayerNumber());
	}
	
	//============================================================================
	//
	// draw stuff
	//
	//============================================================================

	void DrawTexture(TextureID texture, Vector2 pos, bool animated = false, double alpha = 1.0, int screenalign = ALIGN_TOP|ALIGN_LEFT, Vector2 boxsize = (-1, -1), int itemAlign = ALIGN_TOP|ALIGN_LEFT, int flags = 0, Vector2 scale = (1., 1.) )
	{
		if (!texture.IsValid()) return;	// nothing to draw
		
		alpha *= self.alpha;
		if (alpha <= 0) return;					// invisible

		Vector2 texsize = TexMan.GetScaledSize(texture);
		texsize.X *= scale.X;
		texsize.Y *= scale.Y;
		if (boxsize.X > 0 || boxsize.Y > 0)
		{
			double scale1 = 1., scale2 = 1.;
			
			if (boxsize.X != -1 && (boxsize.X < texsize.X || (flags & DI_FORCESCALE)))
			{
				scale1 = boxsize.X / texsize.X;
			}
			if (boxsize.Y != -1 && (boxsize.Y < texsize.Y || (flags & DI_FORCESCALE)))
			{
				scale2 = boxsize.Y / texsize.Y;
			}
			
			if (flags & DI_FORCESCALE)
			{
				if (boxsize.X == -1 || (boxsize.Y != -1 && scale2 < scale1))
					scale1 = scale2;
			}
			else scale1 = min(scale1, scale2);
			
			boxsize = texsize * scale1;
		}
		else
		{
			boxsize = texsize;
		}
		DrawGraphic(texture, animated, pos, Alpha, !!(flags & DI_TRANSLATABLE), !!(flags & DI_DIM), itemAlign, screenAlign, false, boxsize);
	}
	
	//============================================================================
	//
	//
	//
	//============================================================================

	void DrawImage(String imagename, Vector2 pos, bool animated = false, double alpha = 1.0, int screenalign = ALIGN_TOP|ALIGN_LEFT, Vector2 boxsize = (-1, -1), int itemAlign = ALIGN_TOP|ALIGN_LEFT, int flags = 0, Vector2 scale = (1., 1.) )
	{
		let tex = TexMan.CheckForTexture(imagename, TexMan.TYPE_MiscPatch);
		DrawTexture(tex, pos, animated, screenalign, alpha, boxsize, itemAlign, flags, scale);
	}
	
	//============================================================================
	//
	//
	//
	//============================================================================

	void DrawIcon(int icontype, Vector2 pos, bool animated = false, double alpha = 1.0, int screenalign = ALIGN_TOP|ALIGN_LEFT, Vector2 boxsize = (-1, -1), int itemAlign = ALIGN_TOP|ALIGN_LEFT, int flags = 0)
	{
		TextureID texture;
		Vector2 applyscale = (1, 1);
		Inventory atype1, atype2;
		switch (icontype)
		{
			case ITYPE_PLAYERICON:
				texture = CPlayer.mo.ScoreIcon;
				break;
				
			case ITYPE_AMMO1:
			case ITYPE_AMMO2:
				[atype1, atype2] = GetCurrentAmmo();
				[texture, applyscale] = GetIcon(icontype == ITYPE_AMMO1? atype1 : atype2, flags, true);
				break;
				
			case ITYPE_ARMOR:
				[texture, applyscale] = GetIcon(CPlayer.mo.FindInventory("BasicArmor"), flags, false);
				break;
			
			case ITYPE_WEAPON:
				[texture, applyscale] = GetIcon(CPlayer.ReadyWeapon, flags, false);
				break;
				
			case ITYPE_SIGIL:
				[texture, applyscale] = GetIcon(CPlayer.mo.FindInventory("Sigil"), flags, false);
				break;
				
			case ITYPE_SELECTEDINVENTORY:
				if (CPlayer.mo.InvSel != NULL)
					texture = CPlayer.mo.InvSel.Icon;
				break;
		}
		DrawTexture(texture, pos, animated, screenalign, alpha, boxsize, itemAlign, flags, applyscale);
	}
	
	//============================================================================
	//
	//
	//
	//============================================================================

	void DrawHexenArmor(int armortype, String image, Vector2 pos, bool animated = false, double alpha = 1.0, int screenalign = ALIGN_TOP|ALIGN_LEFT, Vector2 boxsize = (-1, -1), int itemAlign = ALIGN_TOP|ALIGN_LEFT, int flags = 0)
	{
		let harmor = HexenArmor(statusBar.CPlayer.mo.FindInventory("HexenArmor"));
		if (harmor != NULL)
		{
			let slotval = harmor.Slots[armorType];
			let slotincr = harmor.SlotsIncrement[armorType];
			
			if (slotval > 0 && slotincr > 0)
			{
				//combine the alpha values
				alpha *= MIN(1., slotval / slotincr);
			}
			else return;
		}
		DrawImage(image, pos, animated, screenalign, alpha, boxsize, itemAlign, flags);
	}
	
	//============================================================================
	//
	//
	//
	//============================================================================

	void DrawInventoryIcon(class<Inventory> item, String image, Vector2 pos, bool animated = false, double alpha = 1.0, int screenalign = ALIGN_TOP|ALIGN_LEFT, Vector2 boxsize = (-1, -1), int itemAlign = ALIGN_TOP|ALIGN_LEFT, int flags = 0)
	{
		let texture = GetDefaultByType(item).Icon;
		if (texture.IsValid())
		{
			DrawTexture(texture, pos, animated, screenalign, alpha, boxsize, itemAlign, flags);
		}
	}
	
}

//============================================================================
//
// a generic value interpolator for status bar elements that can change
// gradually to their new value.
//
//============================================================================

class LinearValueInterpolator : Object
{
	int mCurrentValue;
	int mMaxChange;
	
	static LinearValueInterpolator Create(int startval, int maxchange)
	{
		let v = new("LinearValueInterpolator");
		v.mCurrentValue = startval;
		v.mMaxChange = maxchange;
		return v;
	}
	
	// This must be called peroiodically in the status bar's Tick function.
	// Do not call this in the Draw function because that may skip some frames!
	void Update(int destvalue)
	{
		if (mCurrentValue > destvalue)
		{
			mCurrentValue = max(destvalue, mCurrentValue - mMaxChange);
		}
		else
		{
			mCurrentValue = min(destvalue, mCurrentValue + mMaxChange);
		}
	}
	
	// This must be called in the draw function to retrieve the value for output.
	int GetValue()
	{
		return mCurrentValue;
	}
}

class DynamicValueInterpolator : Object
{
	int mCurrentValue;
	int mMinChange;
	double mChangeFactor;
	
	
	static DynamicValueInterpolator Create(int startval, double changefactor, int minchange)
	{
		let v = new("DynamicValueInterpolator");
		v.mCurrentValue = startval;
		v.mMinChange = minchange;
		v.mChangeFactor = changefactor;
		return v;
	}
	
	// This must be called peroiodically in the status bar's Tick function.
	// Do not call this in the Draw function because that may skip some frames!
	void Update(int destvalue)
	{
		int diff = int(max(abs(destvalue - mCurrentValue) * mChangeFactor, mMinChange));
		if (mCurrentValue > destvalue)
		{
			mCurrentValue = max(destvalue, mCurrentValue - diff);
		}
		else
		{
			mCurrentValue = min(destvalue, mCurrentValue + diff);
		}
	}
	
	// This must be called in the draw function to retrieve the value for output.
	int GetValue()
	{
		return mCurrentValue;
	}
}

