/**
* Melee Hud
* @version 7-14-03
* @author Joseph 'Pcjoe' Florencio
*/

#include "hud.h"
#include "parsemsg.h"
#include "const.h"
#include "cl_util.h"
#include "r_efx.h"
#include "pm_defs.h"
#include "pmtrace.h"
#include "event_api.h"
#include "triangleapi.h"
#include "math.h"

// Types of melee hud elements
#define M_ARROW_HUD			1
#define M_FLASH_HUD			2
#define M_FLASHUPDATE_HUD	3
#define M_INIT_HUD			4
#define M_ARROW_HUD_END		5
#define M_ARROW_HUD_SWITCH	6
#define M_ARROW_HUD_DEL		7

// Offset
#define BAR_OFFSET640 4
#define BAR_OFFSET320 2
#define RINGWIDTH640 4
#define RINGWIDTH320 3

// Sprite size
#define ARROW_SIZE		32
#define BLOCK_SIZE		32
#define BLOCK_SHIFT		BLOCK_SIZE*2
#define ARROWS_PER_ROW	4
#define BLOCK_PER_ROW	4
#define CROSSFADE_START	1
#define CROSSFADE_DURR	0.5

using namespace std;

// Advanced Melee Directions
enum
{	PUNCH_UP = 0,
	PUNCH_UPPERLEFT,
	PUNCH_LEFT,
	PUNCH_LOWERLEFT,
	PUNCH_DOWN,
	PUNCH_LOWERRIGHT,
	PUNCH_RIGHT,
	PUNCH_UPPERRIGHT,
	KICK_UP,
	KICK_UPPERLEFT,
	KICK_LEFT,
	KICK_LOWERLEFT,
	KICK_DOWN,
	KICK_LOWERRIGHT,
	KICK_RIGHT,
	KICK_UPPERRIGHT,
	PUNCH_NODIR,
	KICK_NODIR,
};

// Block status
enum
{	BLOCK_NONE=0,
	BLOCK_TRUE,
	BLOCK_FAIL
};

DECLARE_MESSAGE(m_MeleeHud, MeleeAtkHud);

int CHudMelee::Init(void)
{	
	HOOK_MESSAGE(MeleeAtkHud);
	
	Reset();

	gHUD.AddHudElem(this);
	m_iFlags |= HUD_ACTIVE;

	return 1;
}

void CHudMelee::Reset(void)
{	m_blEnabled = false;
	m_iType = 0;
	m_iFlashNum = 0;
	m_flWeakRange =	0;
	m_flMedRange = 0;
	m_flStrongRange = 0;
	m_flStunTime = 0;
	RemoveAll();
}

int CHudMelee::VidInit(void)
{	m_HUD_chargebar = gHUD.GetSpriteIndex( "mainbar" );
	m_HUD_chargeline = gHUD.GetSpriteIndex( "chargeline");
	m_HUD_greenbar = gHUD.GetSpriteIndex( "greenbar" );
	m_HUD_bluebar = gHUD.GetSpriteIndex( "bluebar" );
	m_HUD_redbar = gHUD.GetSpriteIndex( "redbar" );
	m_HUD_greybar = gHUD.GetSpriteIndex( "greybar" );
	m_HUD_Arrows = gHUD.GetSpriteIndex( "arrows" );
	m_HUD_Blocks = gHUD.GetSpriteIndex( "blocks" );
	m_HUD_UBlocks = gHUD.GetSpriteIndex( "blocku" );

	return 1;
}

/*
* Get sprite height/width
*/
int CHudMelee::GetSpriteHeight( char szTexture[] )
{
	// Get Height of sprite
	int iSprite = gHUD.GetSpriteIndex( szTexture );
	int iHeight = gHUD.GetSpriteRect(iSprite).bottom - gHUD.GetSpriteRect(iSprite).top;

	return iHeight;
}

int CHudMelee::GetSpriteWidth( char szTexture[] )
{
	// Get Width of sprite
	int iSprite = gHUD.GetSpriteIndex( szTexture );
	int iWidth = gHUD.GetSpriteRect(iSprite).right - gHUD.GetSpriteRect(iSprite).left;

	return iWidth;
}

int CHudMelee::GetSpriteHeight( int iSprite )
{
	// Get Height of sprite
	int iHeight = gHUD.GetSpriteRect(iSprite).bottom - gHUD.GetSpriteRect(iSprite).top;

	return iHeight;
}

int CHudMelee::GetSpriteWidth( int iSprite )
{
	// Get Width of sprite
	int iWidth = gHUD.GetSpriteRect(iSprite).right - gHUD.GetSpriteRect(iSprite).left;

	return iWidth;
}

/*
* Get Combo Time
*/
float CHudMelee::GetCmbTime( list<melee_hud_s>::iterator hudelem )
{	float flTime = 0;
	/*modified by harSens
	if(hudelem != NULL)
	*/
	if(hudelem != m_MeleeHud.end())
	{	// If we're not 'finished' with this direction, use the local client time
		if(hudelem->blFinished && hudelem->iType != M_FLASH_HUD)
		{	flTime = hudelem->flEndTime - hudelem->flStartTime;
		}
		else
		{	hudelem->flEndTime = gEngfuncs.GetClientTime();
			flTime = hudelem->flEndTime - hudelem->flStartTime;
		}
	}
	return flTime;
}

/*
* Add element
*/
void CHudMelee::AddElement( int type, int direction )
{
	melee_hud_s NewHud;

	// set varibles
	NewHud.iType = type;
	NewHud.iBlockStatus = BLOCK_NONE;
	NewHud.iDirection = direction;
	NewHud.x = 0;
	NewHud.y = 0;
	NewHud.blFinished = false;
	NewHud.flStartTime = gEngfuncs.GetClientTime();
	NewHud.flEndTime = gEngfuncs.GetClientTime();

	// Only add ID's for flash huds
	if(type == M_FLASH_HUD)
	{	NewHud.iID = m_iFlashNum;
		m_iFlashNum++;
	}
	else
	{	NewHud.iID = 0;
	}

	// "finish" old combo ( if not done so already )
	// Also set start time of this combo to end time of last combo
	list <melee_hud_s>::iterator old = GetHudElem();
	/* modified by harSens
	if(old != NULL)
	*/
	if(old != m_MeleeHud.end())
	{	if(!old->blFinished && old->iType == M_ARROW_HUD)
		{	old->blFinished = true;
		}
	}

	m_MeleeHud.push_back(NewHud);
}

/*
* Remove all
*/
void CHudMelee::RemoveAll( void )
{	m_MeleeHud.clear();
}


/*
* Get current hud elem
*/
list<melee_hud_s>::iterator CHudMelee::GetHudElem( void )
{	list <melee_hud_s>::iterator first = m_MeleeHud.begin();
	list <melee_hud_s>::iterator last = m_MeleeHud.end();
	while(first != last)
	{	list <melee_hud_s>::iterator after = first;
		after++;
		if(after == last)
		{	return first;
		}
		first++;
	}
	/* modified by harSens
	return NULL;
	*/
	return m_MeleeHud.end();
}

/*
* Activate Hud
*/
bool CHudMelee::ActivateHud( int type )
{	
	// Not enabled? enable it with the proper type
	if(!m_blEnabled)
	{	m_blEnabled = true;
		
		// Set type
		m_iType = type;
	}
	else
	{	// We're already enabled. Only return false if our activated type doesnt match the type of the sent msg
		if(type != m_iType)
		{	return false;
		}
	}
	return true;
}

/*
* Get Block Sprite
*/
void CHudMelee::GetBlockSprite( list<melee_hud_s>::iterator MeleeHud, int &iSprite, wrect_t &rect )
{	
	iSprite=0;
	int iBlockSprX=0, iBlockSprY=0;

	// Get Direction
	int iDirection = MeleeHud->iDirection;
	if(iDirection >= KICK_UP)
	{	iDirection -= KICK_UP;
	}

	// Go to the next row of sprites ( 4 per row )
	if(MeleeHud->iDirection != PUNCH_NODIR)
	{	
		iSprite = m_HUD_Blocks;
		if(iDirection >= BLOCK_PER_ROW)
		{	iDirection -= BLOCK_PER_ROW;
			iBlockSprY += BLOCK_SIZE;
		}
		iBlockSprX = BLOCK_SIZE * iDirection;
	}
	// Random block directions
	else
	{	
		iSprite = m_HUD_UBlocks;
		// Go to filled in block sprite
		if(MeleeHud->iBlockStatus != BLOCK_NONE)
		{	iBlockSprX += BLOCK_SIZE;
		}
	}

	// Set rect
//	rect = gHUD.GetSpriteRect(iSprite);
	rect.left = iBlockSprX;
	rect.top = iBlockSprY;
	rect.right = iBlockSprX + BLOCK_SIZE;
	rect.bottom = iBlockSprY + BLOCK_SIZE;

	// Go to filled in block sprite
	if((MeleeHud->iDirection != PUNCH_NODIR) && (MeleeHud->iBlockStatus != BLOCK_NONE))
	{	rect.top += BLOCK_SHIFT;
		rect.bottom += BLOCK_SHIFT;
	}
}

/*
* Get Crossfade Sprite
*/
float CHudMelee::CrossFade( list<melee_hud_s>::iterator MeleeHud, int &iSprite, wrect_t &rect )
{	iSprite = 0;
	if(MeleeHud->iDirection == PUNCH_NODIR)
	{	return 0;
	}

	if((MeleeHud->flStartTime + CROSSFADE_START) < gEngfuncs.GetClientTime())
	{	// Crossfade is done, end
		if((MeleeHud->flStartTime + CROSSFADE_START + CROSSFADE_DURR) < gEngfuncs.GetClientTime())
		{	MeleeHud->iDirection = PUNCH_NODIR;
			return 0;
		}

		// Set Sprite
		iSprite = m_HUD_UBlocks;

		// Set Rect
		int iStartX=0;
		if(MeleeHud->iBlockStatus != BLOCK_NONE)
		{	iStartX += BLOCK_SIZE;
		}
		rect.top = 0;
		rect.bottom = BLOCK_SIZE;
		rect.left = iStartX;
		rect.right = iStartX + BLOCK_SIZE;

		// Calculate crossfade
		return ((gEngfuncs.GetClientTime()-(MeleeHud->flStartTime+CROSSFADE_START))/CROSSFADE_DURR);
	}
	return 0;
}

int CHudMelee::Draw(float fltime)
{	if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) ))
	{	return 1;
	}

	if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) )
	{	return 1;
	}

	if(!m_blEnabled || !m_iType)
	{	return 1;
	}

	cl_entity_t *pPlayer = gEngfuncs.GetLocalPlayer();
	if(!pPlayer)
	{	return 1;
	}

	// We have our stuff enabled, lets start off by drawing the hud bar
	
	switch(m_iType)
	{
	case M_ARROW_HUD:
		{
			// Code copied from "attackcharge.cpp"
			int iWidth, iHeight;
			float flX;

			iWidth = GetSpriteWidth(m_HUD_chargebar);
			iHeight = GetSpriteHeight(m_HUD_chargebar);
			
			int r,g,b,x,y;
			UnpackRGB(r,g,b, RGB_WHITEISH);
			x = (ScreenWidth - iWidth) * 0.5;
			
			//make sure it doesn't overlap the hud
			if (x<iWidth)
			{	x  = iWidth * 1.25;
			}

			flX = (float)x;

			y = ScreenHeight - iHeight;
			SPR_Set(gHUD.GetSprite(m_HUD_chargebar), r, g, b);
			SPR_Draw(0, x, y, &gHUD.GetSpriteRect(m_HUD_chargebar));
			// End of copied code

			if(ScreenWidth > 640)
			{	flX += BAR_OFFSET640;
				y += BAR_OFFSET640;
			}
			else
			{	flX += BAR_OFFSET320;
				y += BAR_OFFSET320;
			}

			float flOrigX = flX;

			// Draw colored bars + arrows
			list <melee_hud_s>::iterator MeleeHud = m_MeleeHud.begin();
			list <melee_hud_s>::iterator end = m_MeleeHud.end();

			float flTotalPercent=0;
			int iInnerBarWidth = GetSpriteWidth(m_HUD_greenbar);	// green=blue=red=grey
			int iNum=0;

			while(MeleeHud != end)
			{	if(MeleeHud->iType != m_iType)
				{	MeleeHud++;
					continue;
				}

				/*
				* Draw colored bars
				*/

				float flComboTime, flPercent;
				int iSprite, iDirection;
				wrect_t newrect, oldrect;
				
				// Get time for the combo
				flComboTime = GetCmbTime(MeleeHud);

				// Get percent for this peice
				flPercent = flComboTime / m_flStunTime;

				// Add to total percent
				flTotalPercent += flPercent;

				// choose which bar we're going to use
				if((MeleeHud->iDirection == PUNCH_NODIR) || (MeleeHud->iDirection == KICK_NODIR))	// grey
				{	iSprite = m_HUD_greybar;
				}
				else if(flComboTime < m_flWeakRange)	// weak
				{	iSprite = m_HUD_greenbar;
				}
				else if(flComboTime < m_flMedRange)	// med
				{	iSprite = m_HUD_bluebar;
				}
				else	// strong
				{	iSprite = m_HUD_redbar;
				}

				//flX
				float flBarLength = iInnerBarWidth * flPercent;

				// we need a unit before we start rendering it
				if(flBarLength > 1)
				{
					bool blEnd = false;

					// Set Rects
					oldrect = gHUD.GetSpriteRect(iSprite);
					newrect = oldrect;

					// Make sure bars dont overfill
					if((fabs(flX-flOrigX) + flBarLength) > iInnerBarWidth)
					{	flBarLength = iInnerBarWidth - (flX-flOrigX);
						blEnd = true;
					}
					// Modify rect to the correct size
					newrect.right = flBarLength;
					
					SPR_Set(gHUD.GetSprite(iSprite), r, g, b);
					SPR_Draw(0, flX, y, &newrect);

					// Draw separator line
					if(iNum)
					{	SPR_Set(gHUD.GetSprite(m_HUD_chargeline), r, g, b);
						SPR_DrawHoles(0, flX, y, &gHUD.GetSpriteRect(m_HUD_chargeline));
					}

					/*
					* Draw Arrows
					*/ 
					iDirection = MeleeHud->iDirection;
					if((iDirection != PUNCH_NODIR) && (iDirection != KICK_NODIR))
					{
						int iDir = iDirection;
						int iArrowSprX=0, iArrowSprY=0;
						bool blKick = false;
						wrect_t arrowrect;

						// Set to Kick
						if(iDir >= KICK_UP)
						{	iDir -= KICK_UP;
							blKick = true;
						}

						// Go to the next row of sprites ( 4 per row )
						if(iDir >= ARROWS_PER_ROW)
						{	iDir -= ARROWS_PER_ROW;
							iArrowSprY += ARROW_SIZE;
						}

						iArrowSprX = ARROW_SIZE * iDir;

						// Move Y coord down for kick sprites
						if(blKick)
						{	iArrowSprY += ARROW_SIZE*2;
						}

						// Set rect
						arrowrect = gHUD.GetSpriteRect(m_HUD_Arrows);
						arrowrect.left = iArrowSprX;
						arrowrect.top = iArrowSprY;
						arrowrect.right = iArrowSprX + ARROW_SIZE;
						arrowrect.bottom = iArrowSprY + ARROW_SIZE;

						int iArrowWidth = ARROW_SIZE;
						int iArrowHeight = ARROW_SIZE;

						int iArrowX=0, iArrowY;

						iArrowX = int((flX + flBarLength/2) - iArrowWidth/2);
						iArrowY = (ScreenHeight - iHeight) - iArrowHeight;

						SPR_Set(gHUD.GetSprite(m_HUD_Arrows), r, g, b);
						SPR_DrawHoles(0, iArrowX, iArrowY, &arrowrect);
					}

					// Bar is full, end early
					if(blEnd)
					{	break;
					}

					iNum++;
				}

				// Add onto X value
				flX += flBarLength;

				MeleeHud++;
			}

			break;
		}
	case M_FLASH_HUD:
		{	list <melee_hud_s>::iterator MeleeHud = m_MeleeHud.begin();
			list <melee_hud_s>::iterator end = m_MeleeHud.end();

			int iStartX = 20;
			int iStartY = ScreenHeight/4;
			bool blFirst = true;

			while(MeleeHud != end)
			{
				if(MeleeHud->iType != m_iType)
				{	MeleeHud++;
					continue;
				}

				float flAlpha = 1;

				// Delete expired member
				if(MeleeHud->blFinished)
				{	if((gEngfuncs.GetClientTime() - MeleeHud->flStartTime) > 0.5)
					{	MeleeHud = m_MeleeHud.erase(MeleeHud);
						continue;
					}

					flAlpha = 1-((gEngfuncs.GetClientTime() - MeleeHud->flStartTime) / 0.5);
				}

				// Get starting origin
				if(MeleeHud->x == 0 && MeleeHud->y == 0 && blFirst)
				{	MeleeHud->x = iStartX;
					MeleeHud->y = iStartY;
				}
				else if(blFirst)
				{	// Move sprites to the left if we have open space
					if(MeleeHud->x > iStartX)
					{	MeleeHud->x -= gHUD.m_flTimeDelta * BLOCK_SIZE*2;
						if(MeleeHud->x < iStartX)
							MeleeHud->x = iStartX;
					}
					iStartX = MeleeHud->x;
					iStartY = MeleeHud->y;
				}
				else
				{	MeleeHud->x = iStartX;
					MeleeHud->y = iStartY;
				}

				/*
				* Draw Blocking directions
				*/ 
				int iColor=0;
				int iSprite=0;
				int iCrossSprite=0;
				float flCmbTime = GetCmbTime(MeleeHud);
				wrect_t blockingrect;
				wrect_t crossrect;

				// Get sprite and rect for block
				float flCrossPercent = CrossFade( MeleeHud, iCrossSprite, crossrect );
				GetBlockSprite( MeleeHud, iSprite, blockingrect );

				// Set Color
				if(iCrossSprite)
				{	iColor = 220 * (flAlpha * (1-flCrossPercent));
				}
				else
				{	iColor = 220 * flAlpha;
				}

				// Change sprite color
				if(MeleeHud->iBlockStatus == BLOCK_TRUE)
				{	SPR_Set(gHUD.GetSprite(iSprite), 0, iColor, 0);
				}
				else if(MeleeHud->iBlockStatus == BLOCK_FAIL)
				{	SPR_Set(gHUD.GetSprite(iSprite), iColor, 0, 0);
				}
				else
				{	SPR_Set(gHUD.GetSprite(iSprite), iColor, iColor, iColor);
				}
				SPR_DrawAdditive(0, MeleeHud->x, MeleeHud->y, &blockingrect);
				
				// Draw crossfade
				if(iCrossSprite)
				{	iColor = 220 * flAlpha * flCrossPercent;

					if(MeleeHud->iBlockStatus == BLOCK_TRUE)
					{	SPR_Set(gHUD.GetSprite(iCrossSprite), 0, iColor, 0);
					}
					else if(MeleeHud->iBlockStatus == BLOCK_FAIL)
					{	SPR_Set(gHUD.GetSprite(iCrossSprite), iColor, 0, 0);
					}
					else
					{	SPR_Set(gHUD.GetSprite(iCrossSprite), iColor, iColor, iColor);
					}

					SPR_DrawAdditive(0, MeleeHud->x, MeleeHud->y, &crossrect);
				}
					
				iStartX += BLOCK_SIZE;
				blFirst = false;

				MeleeHud++;
			}

			break;
		}
	}
	
	return 1;
}

#define MSG_FLOAT_SCALE	100

int CHudMelee::MsgFunc_MeleeAtkHud( const char *pszName, int iSize, void *pbuf )
{
	int iType, iDirection;
	float flDurration;

	BEGIN_READ( pbuf, iSize );

	iType = READ_BYTE();

	switch(iType)
	{
	// Remove + Disable hud elements
	case 0:
		{	Reset();
			break;
		}
	case M_ARROW_HUD:
	case M_FLASH_HUD:
		{	
			iDirection = READ_BYTE();

			// Activate drawing for hud
			if(ActivateHud(iType))
			{	AddElement( iType, iDirection );
			}
			else
			{	ConsolePrint("ERROR: Can't add blockflash\n");
			}
			break;
		}
	case M_FLASHUPDATE_HUD:
		{	int iStatus = READ_BYTE();
			int iNumID = READ_BYTE();
			
			list <melee_hud_s>::iterator start = m_MeleeHud.begin();
			list <melee_hud_s>::iterator end = m_MeleeHud.end();
			while(start != end)
			{	// Set blocking status
				if(!start->blFinished && start->iType == M_FLASH_HUD && start->iBlockStatus == BLOCK_NONE && start->iID == iNumID)
				{	start->iBlockStatus = iStatus;
					start->blFinished = true;
					start->flStartTime = gEngfuncs.GetClientTime();
					break;
				}
				start++;
			}

			break;
		}
	case M_ARROW_HUD_END:
		{
			iDirection = READ_BYTE();
			flDurration = READ_SHORT();
			flDurration /= MSG_FLOAT_SCALE;

			// 'finish' attack
			list <melee_hud_s>::iterator Current = GetHudElem();
			/* modified by harSens
			if(Current != NULL)
			*/
			if(Current != m_MeleeHud.end())
			{	// Same directions, end this move
				if(Current->iDirection == iDirection)
				{	Current->blFinished = true;
					Current->flEndTime = Current->flStartTime + flDurration;
				}
			}

			break;
		}
		// Switch direction / attacks
	case M_ARROW_HUD_SWITCH:
		{
			iDirection = READ_BYTE();

			list <melee_hud_s>::iterator Current = GetHudElem();
			/* modified by harSens
			if(Current != NULL)
			*/
			if(Current != m_MeleeHud.end())
			{	Current->iDirection = iDirection;
			}

			break;
		}
	case M_ARROW_HUD_DEL:
		{	
			/* modified by harSens
			// Delete latest direction
			m_MeleeHud.erase(GetHudElem());
			*/
			list <melee_hud_s>::iterator Current = GetHudElem();
			if(Current != m_MeleeHud.end())
			{
				m_MeleeHud.erase(Current);
			}
			break;
		}
	case M_INIT_HUD:
		{	// Information used for hud
			m_flWeakRange =	READ_SHORT();
			m_flMedRange = READ_SHORT();
			m_flStrongRange = READ_SHORT();
			m_flStunTime = READ_SHORT();

			m_flWeakRange /= MSG_FLOAT_SCALE;
			m_flMedRange /= MSG_FLOAT_SCALE;
			m_flStrongRange /= MSG_FLOAT_SCALE;
			m_flStunTime /= MSG_FLOAT_SCALE;

			// Enable hud
			m_iType = M_ARROW_HUD;
			m_blEnabled = true;
			RemoveAll();
			break;
		}
	}

	return 1;
}
