#include "precompiled.h"

#include "MathUtil.h"
#include "EditorData.h"
#include "ui/UIGlobals.h"
#include "ToolManager.h"
#include "ObjectManager.h"
#include "UnitManager.h"
#include "TextureManager.h"
#include "TextureEntry.h"
#include "Model.h"
#include "SkeletonAnimManager.h"
#include "Unit.h"

#include "ogl.h"
#include "lib/res/graphics/tex.h"
#include "time.h"

#include "BaseEntityCollection.h"
#include "Entity.h"
#include "EntityHandles.h"
#include "EntityManager.h"
#include "ConfigDB.h"
#include "Scheduler.h"
#include "Loader.h"

#include "XML/XML.h"

#include "Game.h"
#include "GameAttributes.h"

const int NUM_ALPHA_MAPS = 14;
Handle AlphaMaps[NUM_ALPHA_MAPS];

extern CLightEnv	g_LightEnv;
CMiniMap			g_MiniMap;
CEditorData			g_EditorData;

CEditorData::CEditorData()
{
	m_ModelMatrix.SetIdentity();
	m_ScenarioName="Scenario01";
}

void CEditorData::SetMode(EditMode mode)
{ 
	if (m_Mode==TEST_MODE && mode!=TEST_MODE) StopTestMode();
	m_Mode=mode; 
	if (m_Mode==TEST_MODE) StartTestMode();
}

bool CEditorData::InitScene()
{
	// setup default lighting environment
	g_LightEnv.m_SunColor=RGBColor(1,1,1);
	g_LightEnv.m_Rotation=DEGTORAD(270);
	g_LightEnv.m_Elevation=DEGTORAD(45);
	g_LightEnv.m_TerrainAmbientColor=RGBColor(0,0,0);
	g_LightEnv.m_UnitsAmbientColor=RGBColor(0.4f,0.4f,0.4f);
	g_Renderer.SetLightEnv(&g_LightEnv);

	// load the default
	if (!LoadTerrain("temp/terrain.png")) return false;
	
	// get default texture to apply to terrain
	CTextureEntry* texture=g_TexMan.FindTexture("aaa.dds");

	CTerrain* terrain = g_Game->GetWorld()->GetTerrain();

	// cover entire terrain with default texture
	u32 patchesPerSide=terrain->GetPatchesPerSide();
	for (uint pj=0; pj<patchesPerSide; pj++) {
		for (uint pi=0; pi<patchesPerSide; pi++) {
			
			CPatch* patch=terrain->GetPatch(pi,pj);
			
			for (int j=0;j<PATCH_SIZE;j++) {
				for (int i=0;i<PATCH_SIZE;i++) {
					patch->m_MiniPatches[j][i].Tex1=texture ? texture->GetHandle() : 0;
				}
			}
		}
	}

	// setup camera
	InitCamera();

	// build the terrain plane
	float h=128*HEIGHT_SCALE;
	u32 mapSize=terrain->GetVerticesPerSide();
	CVector3D pt0(0,h,0),pt1(float(CELL_SIZE*mapSize),h,0),pt2(0,h,float(CELL_SIZE*mapSize));
	m_TerrainPlane.Set(pt0,pt1,pt2);
	m_TerrainPlane.Normalize();

	return true;
}


struct TGAHeader {
	// header stuff
	unsigned char  iif_size;            
	unsigned char  cmap_type;           
	unsigned char  image_type;          
	unsigned char  pad[5];

	// origin : unused
	unsigned short d_x_origin;
	unsigned short d_y_origin;
	
	// dimensions
	unsigned short width;
	unsigned short height;

	// bits per pixel : 16, 24 or 32
	unsigned char  bpp;          

	// image descriptor : Bits 3-0: size of alpha channel
	//					  Bit 4: must be 0 (reserved)
	//					  Bit 5: should be 0 (origin)
	//					  Bits 6-7: should be 0 (interleaving)
   unsigned char image_descriptor;    
};

static bool saveTGA(const char* filename,int width,int height,unsigned char* data) 
{
	FILE* fp=fopen(filename,"wb");
	if (!fp) return false;

	// fill file header
	TGAHeader header;
	header.iif_size=0;
	header.cmap_type=0;
	header.image_type=2;
	memset(header.pad,0,sizeof(header.pad));
	header.d_x_origin=0;
	header.d_y_origin=0;
	header.width=width;
	header.height=height;
	header.bpp=24;
	header.image_descriptor=0;

	if (fwrite(&header,sizeof(TGAHeader),1,fp)!=1) {
		fclose(fp);
		return false;
	}

	// write data 
	if (fwrite(data,width*height*3,1,fp)!=1) {
		fclose(fp);
		return false;
	}

	// return success ..
    fclose(fp);
	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// Init: perform one time initialisation of the editor
bool CEditorData::Init()
{
	// Set attributes for the game
	g_GameAttributes.m_MapFile = L""; // start without a map

	for (int i=1; i<8; ++i) 
		g_GameAttributes.GetSlot(i)->AssignLocal();

	// Set up the actual game
	g_Game = new CGame();
	PSRETURN ret = g_Game->StartGame(&g_GameAttributes);
	if (ret != PSRETURN_OK)
	{
		// Failed to start the game
		delete g_Game;
		g_Game = NULL;
		return false;
	}

	LDR_NonprogressiveLoad();

	// create the scene - terrain, camera, light environment etc
	if (!InitScene()) return false;

	// set up an empty minimap
	g_MiniMap.Initialise();

	// set up the info box
	m_InfoBox.Initialise();

	return true;
}

/////////////////////////////////////////////////////////////////////////////////////////////////
// Terminate: close down the editor (destroy singletons in reverse order to construction)
void CEditorData::Terminate()
{
}

void CEditorData::InitCamera() 
{
	g_NaviCam.GetCamera().SetProjection(1.0f,10000.0f,DEGTORAD(90));
	g_NaviCam.GetCamera().m_Orientation.SetIdentity();
#ifdef TOPDOWNVIEW
	g_NaviCam.GetCamera().m_Orientation.RotateX(DEGTORAD(90));
	g_NaviCam.GetCamera().m_Orientation.Translate(CELL_SIZE*250*0.5, 80, CELL_SIZE*250*0.5);
#else
	g_NaviCam.GetCamera().m_Orientation.RotateX(DEGTORAD(40));
	g_NaviCam.GetCamera().m_Orientation.RotateY(DEGTORAD(-45));
	g_NaviCam.GetCamera().m_Orientation.Translate(600, 200, 125);
#endif

	OnCameraChanged();
}

void CEditorData::OnCameraChanged() 
{
	int width=g_Renderer.GetWidth();
	int height=g_Renderer.GetHeight();

	// resize viewport
	SViewPort viewport;
	viewport.m_X=0;
	viewport.m_Y=0;
	viewport.m_Width=width;
	viewport.m_Height=height;
	g_NaviCam.GetCamera().SetViewPort(&viewport);

	// rebuild object camera
	m_ObjectCamera.SetViewPort(&viewport);
	m_ObjectCamera.SetProjection(1.0f,10000.0f,DEGTORAD(90));


	// recalculate projection matrix
	g_NaviCam.GetCamera().SetProjection(1.0f,10000.0f,DEGTORAD(20));

	// update viewing frustum
	g_NaviCam.GetCamera().UpdateFrustum();

	// calculate intersection of camera stabbing lines with terrain plane

	// get points of back plane of frustum in camera space
	float aspect=height>0 ? float(width)/float(height) : 1.0f;
	float zfar=g_NaviCam.GetCamera().GetFarPlane();
	CVector3D cPts[4];
	float x=zfar*float(tan(g_NaviCam.GetCamera().GetFOV()*aspect*0.5));
	float y=zfar*float(tan(g_NaviCam.GetCamera().GetFOV()*0.5));
	cPts[0].X=-x;
	cPts[0].Y=-y;
	cPts[0].Z=zfar;
	cPts[1].X=x;
	cPts[1].Y=-y;
	cPts[1].Z=zfar;
	cPts[2].X=x;
	cPts[2].Y=y;
	cPts[2].Z=zfar;
	cPts[3].X=-x;
	cPts[3].Y=y;
	cPts[3].Z=zfar;

	// transform to world space
	CVector3D wPts[4];
	for (int i=0;i<4;i++) {
		wPts[i]=g_NaviCam.GetCamera().m_Orientation.Transform(cPts[i]);
	}

	// now intersect a ray from the camera through each point 
	CVector3D rayOrigin=g_NaviCam.GetCamera().m_Orientation.GetTranslation();
	CVector3D rayDir=g_NaviCam.GetCamera().m_Orientation.GetIn();

	CVector3D hitPt[4];
	for (int i=0;i<4;i++) {
		CVector3D rayDir=wPts[i]-rayOrigin;
		rayDir.Normalize();

		// get intersection point
		m_TerrainPlane.FindRayIntersection(rayOrigin,rayDir,&hitPt[i]);
	}

	CTerrain* terrain = g_Game->GetWorld()->GetTerrain();

	for (int i=0;i<4;i++) {
		// convert to minimap space
		float px=hitPt[i].X;
		float pz=hitPt[i].Z;
		g_MiniMap.m_ViewRect[i][0]=(197*px/float(CELL_SIZE*terrain->GetVerticesPerSide()));
		g_MiniMap.m_ViewRect[i][1]=197*pz/float(CELL_SIZE*terrain->GetVerticesPerSide());
	}
}

void CEditorData::RenderTerrain()
{
	CTerrain* terrain = g_Game->GetWorld()->GetTerrain();

	CFrustum frustum=g_NaviCam.GetCamera().GetFrustum();
	u32 patchesPerSide=	g_Game->GetWorld()->GetTerrain()->GetPatchesPerSide();
	for (uint j=0; j<patchesPerSide; j++) {
		for (uint i=0; i<patchesPerSide; i++) {
			CPatch* patch=terrain->GetPatch(i,j);
			if (frustum.IsBoxVisible (CVector3D(0,0,0),patch->GetBounds())) {
				g_Renderer.Submit(patch);
			}
		}
	}
}

void CEditorData::OnScreenShot(const char* filename)
{
	g_Renderer.SetClearColor(0);
	g_Renderer.BeginFrame();
	g_Renderer.SetCamera(g_NaviCam.GetCamera());
	
	RenderWorld();

	g_Renderer.EndFrame();

	int width=g_Renderer.GetWidth();
	int height=g_Renderer.GetHeight();
	unsigned char* data=new unsigned char[width*height*3];

	glReadBuffer(GL_BACK);
	glReadPixels(0,0,width,height,GL_BGR_EXT,GL_UNSIGNED_BYTE,data);	

	saveTGA(filename,width,height,data);
	
	delete[] data;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// SubmitModelRecursive: recurse down given model, submitting it and all its descendants to the 
// renderer
void SubmitModelRecursive(CModel* model)
{
	g_Renderer.Submit(model);

	const std::vector<CModel::Prop>& props=model->GetProps();
	for (uint i=0;i<props.size();i++) {
		SubmitModelRecursive(props[i].m_Model);
	}
}

/////////////////////////////////////////////////////////////////////////////////////////////
// RenderNoCull: render absolutely everything to a blank frame to force renderer
// to load required assets 
void CEditorData::RenderNoCull()
{
	g_Renderer.BeginFrame();
	g_Renderer.SetCamera(g_NaviCam.GetCamera());

	uint i,j;
	const std::vector<CUnit*>& units=g_UnitMan.GetUnits();
	for (i=0;i<units.size();++i) {
		SubmitModelRecursive(units[i]->GetModel());
	}
	
	CTerrain* terrain = g_Game->GetWorld()->GetTerrain();

	u32 patchesPerSide=terrain->GetPatchesPerSide();
	for (j=0; j<patchesPerSide; j++) {
		for (i=0; i<patchesPerSide; i++) {
			CPatch* patch=terrain->GetPatch(i,j);
			g_Renderer.Submit(patch);
		}
	}

	g_Renderer.FlushFrame();
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	g_Renderer.EndFrame();
}

void CEditorData::RenderModels()
{
	CFrustum frustum=g_NaviCam.GetCamera().GetFrustum();

	const std::vector<CUnit*>& units=g_UnitMan.GetUnits();
	uint i;
	for (i=0;i<units.size();++i) {
		if (frustum.IsBoxVisible (CVector3D(0,0,0),units[i]->GetModel()->GetBounds())) {
			SubmitModelRecursive(units[i]->GetModel());
		}
	}
}

void CEditorData::RenderWorld()
{
	// render terrain
	RenderTerrain();

	// render all the units
	RenderModels();
	
	// flush prior to rendering overlays etc
	g_Renderer.FlushFrame();
}

void CEditorData::RenderObEdGrid()
{
	int i;
	const int numSteps=32;
	const CVector3D start(-numSteps*CELL_SIZE/2,0,-numSteps*CELL_SIZE/2);
	const CVector3D end(numSteps*CELL_SIZE/2,0,numSteps*CELL_SIZE/2);

	glDisable(GL_TEXTURE_2D);

	glDepthMask(0);
	glColor4f(0.5f,0.5f,0.5f,0.35f);
	glLineWidth(1.0f);
	
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);

	glBegin(GL_LINES);
	for (i=0;i<=numSteps;i++) {
		if (i%8==0) continue;
		CVector3D v0(start.X+(i*(end.X-start.X))/float(numSteps),start.Y,start.Z);
		glVertex3fv(&v0.X);

		CVector3D v1(v0.X,end.Y,end.Z);
		glVertex3fv(&v1.X);
	}

	for (i=0;i<=numSteps;i++) {
		if (i%8==0) continue;
		CVector3D v0(start.X,start.Y,start.Z+(i*(end.Z-start.Z))/float(numSteps));
		glVertex3fv(&v0.X);

		CVector3D v1(end.X,end.Y,v0.Z);
		glVertex3fv(&v1.X);
	}
	glEnd();

	glDisable(GL_BLEND);

	glColor3f(0,0,0.5);
	glLineWidth(2.0f);

	glBegin(GL_LINES);
	for (i=0;i<=numSteps;i+=8) {
		CVector3D v0(start.X+(i*(end.X-start.X))/float(numSteps),start.Y,start.Z);
		glVertex3fv(&v0.X);

		CVector3D v1(v0.X,end.Y,end.Z);
		glVertex3fv(&v1.X);
	}

	for (i=0;i<=numSteps;i+=8) {
		CVector3D v0(start.X,start.Y,start.Z+(i*(end.Z-start.Z))/float(numSteps));
		glVertex3fv(&v0.X);

		CVector3D v1(end.X,end.Y,v0.Z);
		glVertex3fv(&v1.X);
	}
	glEnd();
	glDepthMask(1);
}

void CEditorData::OnDraw()
{	
	if (m_Mode==SCENARIO_EDIT || m_Mode==TEST_MODE) {
		g_Renderer.SetClearColor(0x00000000);
		g_Renderer.BeginFrame();

		// setup camera
		g_Renderer.SetCamera(g_NaviCam.GetCamera());

		// render base terrain plus models
		RenderWorld();

		if (m_Mode!=TEST_MODE) {
			// render the active tool
			g_ToolMan.OnDraw();
		}

		// flush prior to rendering overlays ..
		g_Renderer.FlushFrame();
		
		// .. and here's the overlays
		g_MiniMap.Render();
		m_InfoBox.Render();

		const std::vector<CUnit*>& units=g_UnitMan.GetUnits();
		for (size_t i=0;i<units.size();++i)
			if (units[i]->GetEntity())
				units[i]->GetEntity()->renderSelectionOutline();

	} else {
		g_Renderer.SetClearColor(0x00453015);
		g_Renderer.BeginFrame();
		
//		CObjectEntry* selobject=g_ObjMan.GetSelectedObject();
//		if (selobject && selobject->m_Model) {
//			// setup camera such that object is in the centre of the viewport
//			m_ModelMatrix.SetIdentity();
//			selobject->m_Model->SetTransform(m_ModelMatrix);
//
//			const CBound& bound=selobject->m_Model->GetBounds();
//			CVector3D pt((bound[0].X+bound[1].X)*0.5f,(bound[0].Y+bound[1].Y)*0.5f,bound[0].Z);
//			
//			float hfov=tan(DEGTORAD(45));
//			float vfov=hfov/g_Renderer.GetAspect();
//			float zx=(bound[1].X-bound[0].X)*0.5f/hfov;
//			float zy=(bound[1].Y-bound[0].Y)*0.5f/vfov;
//			float z=zx>zy ? zx : zy;
//			z=(z+1)*2;
//
//			m_ObjectCamera.m_Orientation.SetIdentity();
//			m_ObjectCamera.m_Orientation.Translate(pt.X,pt.Y,-z);
//
//			g_Renderer.SetCamera(m_ObjectCamera);
//
//			RenderObEdGrid();
//
//			g_Renderer.Submit(selobject->m_Model);
//		}

		// flush prior to rendering overlays ..
		g_Renderer.FlushFrame();
		
		// .. and here's the overlays
		m_InfoBox.Render();
	}

	g_Renderer.EndFrame();

	// notify info box frame is complete; gives it chance to accumulate stats, check
	// fps, etc
	m_InfoBox.OnFrameComplete();
}


bool CEditorData::LoadTerrain(const char* filename)
{
	Tex t;
	if(tex_load(filename, &t) < 0)
	{
		char buf[1024];
		sprintf(buf,"Failed to load \"%s\"",filename);
		ErrorBox(buf);
		return false;
	}

	uint width=t.w, height=t.h, bpp=t.bpp;
	void *ptr=tex_get_data(&t);

	// rescale the texture to fit to the nearest of the 4 possible map sizes
	u32 mapsize=9;	// assume smallest map
	if (width>11*PATCH_SIZE+1) mapsize=11;
	if (width>13*PATCH_SIZE+1) mapsize=13;
	if (width>17*PATCH_SIZE+1) mapsize=17;

	u32 targetsize=mapsize*PATCH_SIZE+1;
	unsigned char* data=new unsigned char[targetsize*targetsize*bpp/8];
	u32 fmt=(bpp==8) ? GL_RED : ((bpp==24) ? GL_RGB : GL_RGBA);
	gluScaleImage(fmt,width,height,GL_UNSIGNED_BYTE,ptr,
		targetsize,targetsize,GL_UNSIGNED_BYTE,data);

	// build 16 bit heightmap from red channel of texture
	u16* heightmap=new u16[targetsize*targetsize];
	int stride=bpp/8;
	u16* hmptr=heightmap;

	// get src of copy
	const u8* dataptr = (bpp==8) ? data : ((bpp==24) ? data+2 : data+3);
	
	// build heightmap
	for (uint j=0;j<targetsize;++j) {
		for (uint i=0;i<targetsize;++i) {
			*hmptr=(*dataptr) << 8;
			hmptr++;
			dataptr+=stride;
		}
	}

	// rebuild terrain
	g_Game->GetWorld()->GetTerrain()->Resize(mapsize);
	g_Game->GetWorld()->GetTerrain()->SetHeightMap(heightmap);
	
	// clean up
	delete[] data;
	delete[] heightmap;
	(void)tex_free(&t);

	// re-initialise minimap - terrain size may have changed
	g_MiniMap.Initialise();

	return true;
}

// UpdateWorld: update time dependent data in the world to account for changes over 
// the given time (in s)
void CEditorData::UpdateWorld(float time)
{
	if (m_Mode==SCENARIO_EDIT || m_Mode==TEST_MODE) {
		g_EntityManager.interpolateAll(0.f);
		const std::vector<CUnit*>& units=g_UnitMan.GetUnits();
		for (uint i=0;i<units.size();++i) {
			units[i]->GetModel()->Update(time);
		}
//		if (m_Mode==TEST_MODE) {
//			g_EntityManager.updateAll( time );
//		}
	} else {
//		CObjectEntry* selobject=g_ObjMan.GetSelectedObject();
//		if (selobject && selobject->m_Model) {
//			selobject->m_Model->Update(time);
//		}
	}	
}

void CEditorData::StartTestMode()
{	
	// initialise entities
	g_EntityManager.InitializeAll();
}

void CEditorData::StopTestMode()
{	
	// make all units idle again
	const std::vector<CUnit*>& units=g_UnitMan.GetUnits();
	for (uint i=0;i<units.size();++i) {
		units[i]->SetRandomAnimation("idle");
	}
}