#include "precompiled.h"

#include <math.h>

#include "graphics/GameView.h"
#include "graphics/MiniPatch.h"
#include "graphics/Model.h"
#include "graphics/Terrain.h"
#include "graphics/TextureEntry.h"
#include "graphics/TextureManager.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "gui/MiniMap.h"
#include "lib/ogl.h"
#include "maths/Bound.h"
#include "ps/Game.h"
#include "ps/Interact.h"
#include "ps/Network/NetMessage.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include "scripting/GameEvents.h"
#include "simulation/Entity.h"
#include "simulation/LOSManager.h"


bool g_TerrainModified = false;

// used by GetMapSpaceCoords (precalculated as an optimization).
// this was formerly access via inline asm, which required it to be
// static data instead of a class member. that is no longer the case,
// but we leave it because this is slightly more efficient.
static float m_scaleX, m_scaleY;
extern bool g_mouse_buttons[1];


static unsigned int ScaleColor(unsigned int color, float x)
{
	unsigned int r = uint(float(color & 0xff) * x);
	unsigned int g = uint(float((color>>8) & 0xff) * x);
	unsigned int b = uint(float((color>>16) & 0xff) * x);
	return (0xff000000 | r | g<<8 | b<<16);
}

CMiniMap::CMiniMap()
	: m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0),
	m_LOSTexture(0), m_LOSData(0), m_UnitManager(0)
{
	AddSetting(GUIST_CColor,	"fov_wedge_color");
	AddSetting(GUIST_CStr,		"tooltip");
	AddSetting(GUIST_CStr,		"tooltip_style");
	m_Clicking = false;
}

CMiniMap::~CMiniMap()
{
	Destroy();
}

void CMiniMap::HandleMessage(const SGUIMessage &Message)
{
	switch(Message.type)
	{
	case GUIM_MOUSE_PRESS_LEFT:
		{
			SetCameraPos();
			m_Clicking = true;
			break;
		}
	case GUIM_MOUSE_RELEASE_LEFT:
		{
		SetCameraPos();
		m_Clicking = false;
		break;
		}
	case GUIM_MOUSE_ENTER:
		{
			g_Selection.m_mouseOverMM = true;
			break;
		}
	case GUIM_MOUSE_LEAVE:
		{
			g_Selection.m_mouseOverMM = false;
			break;
		}
	
	case GUIM_MOUSE_RELEASE_RIGHT:
		{
			CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1);
			break;
		}
	case GUIM_MOUSE_DBLCLICK_RIGHT:
		{
			CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2);
			break;
		}
	case GUIM_MOUSE_MOTION:
		{
			if (m_Clicking)
				SetCameraPos();
			break;
		}

	default:
		break;
	}	// switch
}

void CMiniMap::SetCameraPos()
{
	CTerrain *MMTerrain=g_Game->GetWorld()->GetTerrain();
	CVector3D CamOrient=m_Camera->m_Orientation.GetTranslation();

	//get center point of screen
	int x = (int)g_Renderer.GetWidth()/2.f, y = (int)g_Renderer.GetHeight()/2.f;
	CVector3D ScreenMiddle=m_Camera->GetWorldCoordinates(x,y);

	//Get Vector required to go from camera position to ScreenMiddle
	CVector3D TransVector;
	TransVector.X=CamOrient.X-ScreenMiddle.X;
	TransVector.Z=CamOrient.Z-ScreenMiddle.Z;
	//world position of where mouse clicked
	CVector3D Destination;
	CPos MousePos = GetMousePos();
	//X and Z according to proportion of mouse position and minimap
	Destination.X = CELL_SIZE * m_MapSize *
		( (MousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth() );
	Destination.Z = CELL_SIZE * m_MapSize * ( (m_CachedActualSize.bottom - MousePos.y) /
		m_CachedActualSize.GetHeight() );

	m_Camera->m_Orientation._14=Destination.X;
	m_Camera->m_Orientation._34=Destination.Z;
	m_Camera->m_Orientation._14+=TransVector.X;
	m_Camera->m_Orientation._34+=TransVector.Z;

	//Lock Y coord.  No risk of zoom exceeding limit-Y does not increase
	float Height=MMTerrain->getExactGroundLevel(
		m_Camera->m_Orientation._14, m_Camera->m_Orientation._34) + g_YMinOffset;

	if (m_Camera->m_Orientation._24 < Height)
	{
		m_Camera->m_Orientation._24=Height;
	}
	m_Camera->UpdateFrustum();
}
void CMiniMap::FireWorldClickEvent(uint button, int clicks)
{
	//debug_printf("FireWorldClickEvent: button %d, clicks %d\n", button, clicks);
	
	CPos MousePos = GetMousePos();
	CVector2D Destination;
	//X and Z according to proportion of mouse position and minimap
	Destination.x = CELL_SIZE * m_MapSize *
		( (MousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth() );
	Destination.y = CELL_SIZE * m_MapSize * ( (m_CachedActualSize.bottom - MousePos.y) /
		m_CachedActualSize.GetHeight() );

	g_JSGameEvents.FireWorldClick(
		button,
		clicks,
		NMT_Goto,
		-1,
		NMT_Run,
		-1, 
		NULL,
		Destination.x,
		Destination.y);
}

// render view rect : John M. Mena
// This sets up and draws the rectangle on the mini-map
// which represents the view of the camera in the world.
void CMiniMap::DrawViewRect()
{
	// Get correct world coordinates based off corner of screen start
	// at Bottom Left and going CW
	CVector3D hitPt[4];
	hitPt[0]=m_Camera->GetWorldCoordinates(0,g_Renderer.GetHeight());
	hitPt[1]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),g_Renderer.GetHeight());
	hitPt[2]=m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(),0);
	hitPt[3]=m_Camera->GetWorldCoordinates(0,0);

	float ViewRect[4][2];
	for (int i=0;i<4;i++) {
		// convert to minimap space
		float px=hitPt[i].X;
		float pz=hitPt[i].Z;
		ViewRect[i][0]=(m_CachedActualSize.GetWidth()*px/float(CELL_SIZE*m_MapSize));
		ViewRect[i][1]=(m_CachedActualSize.GetHeight()*pz/float(CELL_SIZE*m_MapSize));
	}

	// Enable Scissoring as to restrict the rectangle
	// to only the mini-map below by retrieving the mini-maps
	// screen coords.
	glScissor((int)m_CachedActualSize.left, 0, (int)m_CachedActualSize.right, (int)m_CachedActualSize.GetHeight());
	glEnable(GL_SCISSOR_TEST);
	glEnable(GL_LINE_SMOOTH);
	glLineWidth(2);
	glColor3f(1.0f, 0.3f, 0.3f);

	// Draw the viewing rectangle with the ScEd's conversion algorithm
	const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
	glBegin(GL_LINE_LOOP);
	glVertex2f(x+ViewRect[0][0], y-ViewRect[0][1]);
	glVertex2f(x+ViewRect[1][0], y-ViewRect[1][1]);
	glVertex2f(x+ViewRect[2][0], y-ViewRect[2][1]);
	glVertex2f(x+ViewRect[3][0], y-ViewRect[3][1]);
	glEnd();

	// restore state
	glDisable(GL_SCISSOR_TEST);
	glDisable(GL_POINT_SMOOTH);
	glLineWidth(1.0f);
}

void CMiniMap::Draw()
{
	// The terrain isn't actually initialized until the map is loaded, which
	// happens when the game is started, so abort until then.
	if(!(GetGUI() && g_Game && g_Game->IsGameStarted()))
		return;

	// Set our globals in case they hadn't been set before
	m_Camera      = g_Game->GetView()->GetCamera();
	m_Terrain     = g_Game->GetWorld()->GetTerrain();
	m_UnitManager = g_Game->GetWorld()->GetUnitManager();
	m_Width  = (u32)(m_CachedActualSize.right - m_CachedActualSize.left);
	m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top);
	m_MapSize = m_Terrain->GetVerticesPerSide();
	m_TextureSize = round_up_to_pow2(m_MapSize);

	m_scaleX = float(m_Width) / float(m_MapSize - 1);
	m_scaleY = float(m_Height) / float(m_MapSize - 1);

	if(!m_TerrainTexture)
		CreateTextures();

	// do not limit this as with LOS updates below - we must update
	// immediately after changes are reported because this flag will be
	// reset at the end of the frame.
	if(g_TerrainModified)
		RebuildTerrainTexture();

	// only update 10x / second
	// (note: since units only move a few pixels per second on the minimap,
	// we can get away with infrequent updates; this is slow, ~20ms)
	static double last_time;
	const double cur_time = get_time();
	if(cur_time - last_time > 100e-3)	// 10 updates/sec
	{
		last_time = cur_time;

		CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
		if(losMgr->m_LOSSetting != CLOSManager::ALL_VISIBLE)
			RebuildLOSTexture();
	}

	const float texCoordMax = ((float)m_MapSize - 1) / ((float)m_TextureSize);
	const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
	const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
	const float z = GetBufferedZ();

	// Draw the main textured quad
	g_Renderer.BindTexture(0, m_TerrainTexture);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(x, y, z);
	glTexCoord2f(texCoordMax, 0.0f);
	glVertex3f(x2, y, z);
	glTexCoord2f(texCoordMax, texCoordMax);
	glVertex3f(x2, y2, z);
	glTexCoord2f(0.0f, texCoordMax);
	glVertex3f(x, y2, z);
	glEnd();

	// Draw the LOS quad in black, using alpha values from the LOS texture
	g_Renderer.BindTexture(0, m_LOSTexture);
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PRIMARY_COLOR_ARB);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
	glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBegin(GL_QUADS);
	glColor3f(0.0f, 0.0f, 0.0f);
	glTexCoord2f(0.0f, 0.0f);
	glVertex3f(x, y, z);
	glTexCoord2f(texCoordMax, 0.0f);
	glVertex3f(x2, y, z);
	glTexCoord2f(texCoordMax, texCoordMax);
	glVertex3f(x2, y2, z);
	glTexCoord2f(0.0f, texCoordMax);
	glVertex3f(x, y2, z);
	glEnd();
	glDisable(GL_BLEND);

	// Draw unit points
	const std::vector<CUnit *> &units = m_UnitManager->GetUnits();
	std::vector<CUnit *>::const_iterator iter = units.begin();
	CUnit *unit = 0;
	CVector2D pos;
	CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_POINT_SMOOTH);
	glDisable(GL_TEXTURE_2D);
	glPointSize(3.0f);
	// REMOVED: glColor3f(1.0f, 1.0f, 1.0f);
	glBegin(GL_POINTS);
	for(; iter != units.end(); ++iter)
	{
		unit = (CUnit *)(*iter);
		if(unit && unit->GetEntity() && losMgr->GetUnitStatus(unit, g_Game->GetLocalPlayer()) != UNIT_HIDDEN)
		{
			CEntity* entity = unit->GetEntity();
			CStrW& type = entity->m_minimapType;

			if(type==L"Unit" || type==L"Structure" || type==L"Hero") {
				// Use the player colour
				const SPlayerColour& colour = unit->GetEntity()->GetPlayer()->GetColour();
				glColor3f(colour.r, colour.g, colour.b);
			}
			else {
				glColor3f(entity->m_minimapR/255.0f, entity->m_minimapG/255.0f, entity->m_minimapB/255.0f);
			}


			pos = GetMapSpaceCoords(unit->GetEntity()->m_position);

			glVertex3f(x + pos.x, y - pos.y, z);
		}
	}
	glEnd();

	DrawViewRect();

	// Reset everything back to normal
	glPointSize(1.0f);
	glDisable(GL_LINE_SMOOTH);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_DEPTH_TEST);
}

void CMiniMap::CreateTextures()
{
	Destroy();

	// Create terrain texture
	glGenTextures(1, (GLuint *)&m_TerrainTexture);
	g_Renderer.BindTexture(0, m_TerrainTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_TextureSize, m_TextureSize, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, 0);
	m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

	// Create LOS texture
	glGenTextures(1, (GLuint *)&m_LOSTexture);
	g_Renderer.BindTexture(0, m_LOSTexture);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, 0);
	m_LOSData = new u8[(m_MapSize - 1) * (m_MapSize - 1)];
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);

	// Rebuild and upload both of them
	RebuildTerrainTexture();
	RebuildLOSTexture();
}


void CMiniMap::RebuildTerrainTexture()
{
	u32 x = 0;
	u32 y = 0;
	u32 w = m_MapSize - 1;
	u32 h = m_MapSize - 1;
	float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;

	for(u32 j = 0; j < h; j++)
	{
		u32 *dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
		for(u32 i = 0; i < w; i++)
		{
			float avgHeight = ( m_Terrain->getVertexGroundLevel((int)i, (int)j)
					+ m_Terrain->getVertexGroundLevel((int)i+1, (int)j)
					+ m_Terrain->getVertexGroundLevel((int)i, (int)j+1)
					+ m_Terrain->getVertexGroundLevel((int)i+1, (int)j+1)
				) / 4.0f;

			if(avgHeight < waterHeight)
			{
				*dataPtr++ = 0xff304080;		// TODO: perhaps use the renderer's water color?
			}
			else
			{
				int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
				int val = (hmap / 3) + 170;

				u32 color = 0;

				CMiniPatch *mp = m_Terrain->GetTile(x + i, y + j);
				if(mp)
				{
					CTextureEntry *tex = mp->Tex1 ? g_TexMan.FindTexture(mp->Tex1) : 0;
					color = tex ? tex->GetBaseColor() : 0xffffffff;
				}
				else
				{
					color = 0xffffffff;
				}

				*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
			}
		}
	}

	// Upload the texture
	g_Renderer.BindTexture(0, m_TerrainTexture);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_BGRA_EXT, GL_UNSIGNED_BYTE, m_TerrainData);
}

TIMER_ADD_CLIENT(tc_minimap_rebuildlos);

void CMiniMap::RebuildLOSTexture()
{
	PROFILE_START("rebuild minimap: los");
	TIMER_ACCRUE(tc_minimap_rebuildlos);

	CLOSManager* losMgr = g_Game->GetWorld()->GetLOSManager();
	CPlayer* player = g_Game->GetLocalPlayer();

	u32 x = 0;
	u32 y = 0;
	u32 w = m_MapSize - 1;
	u32 h = m_MapSize - 1;

	for(u32 j = 0; j < h; j++)
	{
		u8 *dataPtr = m_LOSData + ((y + j) * (m_MapSize - 1)) + x;
		for(u32 i = 0; i < w; i++)
		{
			ELOSStatus status = losMgr->GetStatus((int) i, (int) j, player);
			if(status == LOS_UNEXPLORED)
			{
				*dataPtr++ = 0xff;
			}
			else if(status == LOS_EXPLORED)
			{
				*dataPtr++ = (u8) (0xff * 0.3f);
			}
			else {
				*dataPtr++ = 0;
			}
		}
	}

	// Upload the texture
	g_Renderer.BindTexture(0, m_LOSTexture);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_ALPHA, GL_UNSIGNED_BYTE, m_LOSData);

	PROFILE_END("rebuild minimap: los");
}

void CMiniMap::Destroy()
{
	if(m_TerrainTexture)
		glDeleteTextures(1, (GLuint *)&m_TerrainTexture);

	if(m_LOSTexture)
		glDeleteTextures(1, (GLuint *)&m_LOSTexture);

	delete[] m_TerrainData; m_TerrainData = 0;
	delete[] m_LOSData; m_LOSData = 0;
}

CVector2D CMiniMap::GetMapSpaceCoords(CVector3D worldPos)
{
	float x = rintf(worldPos.X / CELL_SIZE);
	float y = rintf(worldPos.Z / CELL_SIZE);
	// Entity's Z coordinate is really its longitudinal coordinate on the terrain

	// Calculate map space scale
	return CVector2D(x * m_scaleX, y * m_scaleY);
}
