/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "sys/platform.h"
#include "gamesys/SysCvar.h"
#include "script/Script_Thread.h"
#include "Player.h"

#include "Camera.h"

/*
===============================================================================

  idCamera

  Base class for cameras

===============================================================================
*/

ABSTRACT_DECLARATION( idEntity, idCamera )
END_CLASS

/*
=====================
idCamera::Spawn
=====================
*/
void idCamera::Spawn( void ) {
}

/*
=====================
idCamera::GetRenderView
=====================
*/
renderView_t *idCamera::GetRenderView() {
	renderView_t *rv = idEntity::GetRenderView();
	GetViewParms( rv );
	return rv;
}

/***********************************************************************

  idCameraView

***********************************************************************/
const idEventDef EV_Camera_SetAttachments( "<getattachments>", NULL );

CLASS_DECLARATION( idCamera, idCameraView )
	EVENT( EV_Activate,				idCameraView::Event_Activate )
	EVENT( EV_Camera_SetAttachments, idCameraView::Event_SetAttachments )
END_CLASS


/*
===============
idCameraView::idCameraView
================
*/
idCameraView::idCameraView() {
	fov = 90.0f;
	attachedTo = NULL;
	attachedView = NULL;
}

/*
===============
idCameraView::Save
================
*/
void idCameraView::Save( idSaveGame *savefile ) const {
	savefile->WriteFloat( fov );
	savefile->WriteObject( attachedTo );
	savefile->WriteObject( attachedView );
}

/*
===============
idCameraView::Restore
================
*/
void idCameraView::Restore( idRestoreGame *savefile ) {
	savefile->ReadFloat( fov );
	savefile->ReadObject( reinterpret_cast<idClass *&>( attachedTo ) );
	savefile->ReadObject( reinterpret_cast<idClass *&>( attachedView ) );
}

/*
===============
idCameraView::Event_SetAttachments
================
*/
void idCameraView::Event_SetAttachments(  ) {
	SetAttachment( &attachedTo, "attachedTo" );
	SetAttachment( &attachedView, "attachedView" );
}

/*
===============
idCameraView::Event_Activate
================
*/
void idCameraView::Event_Activate( idEntity *activator ) {
	if (spawnArgs.GetBool("trigger")) {
		if (gameLocal.GetCamera() != this) {
			if ( g_debugCinematic.GetBool() ) {
				gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() );
			}

			gameLocal.SetCamera(this);
		} else {
			if ( g_debugCinematic.GetBool() ) {
				gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
			}
			gameLocal.SetCamera(NULL);
		}
	}
}

/*
=====================
idCameraView::Stop
=====================
*/
void idCameraView::Stop( void ) {
	if ( g_debugCinematic.GetBool() ) {
		gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
	}
	gameLocal.SetCamera(NULL);
	ActivateTargets( gameLocal.GetLocalPlayer() );
}


/*
=====================
idCameraView::Spawn
=====================
*/
void idCameraView::SetAttachment( idEntity **e, const char *p  ) {
	const char *cam = spawnArgs.GetString( p );
	if ( strlen ( cam ) ) {
		*e = gameLocal.FindEntity( cam );
	}
}


/*
=====================
idCameraView::Spawn
=====================
*/
void idCameraView::Spawn( void ) {
	// if no target specified use ourself
	const char *cam = spawnArgs.GetString("cameraTarget");
	if ( strlen ( cam ) == 0) {
		spawnArgs.Set("cameraTarget", spawnArgs.GetString("name"));
	}
	fov = spawnArgs.GetFloat("fov", "90");

	PostEventMS( &EV_Camera_SetAttachments, 0 );

	UpdateChangeableSpawnArgs(NULL);
}

/*
=====================
idCameraView::GetViewParms
=====================
*/
void idCameraView::GetViewParms( renderView_t *view ) {
	assert( view );

	if (view == NULL) {
		return;
	}

	idVec3 dir;
	idEntity *ent;

	if ( attachedTo ) {
		ent = attachedTo;
	} else {
		ent = this;
	}

	view->vieworg = ent->GetPhysics()->GetOrigin();
	if ( attachedView ) {
		dir = attachedView->GetPhysics()->GetOrigin() - view->vieworg;
		dir.Normalize();
		view->viewaxis = dir.ToMat3();
	} else {
		view->viewaxis = ent->GetPhysics()->GetAxis();
	}

	gameLocal.CalcFov( fov, view->fov_x, view->fov_y );
}

/*
===============================================================================

  idCameraAnim

===============================================================================
*/

const idEventDef EV_Camera_Start( "start", NULL );
const idEventDef EV_Camera_Stop( "stop", NULL );

CLASS_DECLARATION( idCamera, idCameraAnim )
	EVENT( EV_Thread_SetCallback,	idCameraAnim::Event_SetCallback )
	EVENT( EV_Camera_Stop,			idCameraAnim::Event_Stop )
	EVENT( EV_Camera_Start,			idCameraAnim::Event_Start )
	EVENT( EV_Activate,				idCameraAnim::Event_Activate )
END_CLASS


/*
=====================
idCameraAnim::idCameraAnim
=====================
*/
idCameraAnim::idCameraAnim() {
	threadNum = 0;
	offset.Zero();
	frameRate = 0;
	cycle = 1;
	starttime = 0;
	activator = NULL;

}

/*
=====================
idCameraAnim::~idCameraAnim
=====================
*/
idCameraAnim::~idCameraAnim() {
	if ( gameLocal.GetCamera() == this ) {
		gameLocal.SetCamera( NULL );
	}
}

/*
===============
idCameraAnim::Save
================
*/
void idCameraAnim::Save( idSaveGame *savefile ) const {
	savefile->WriteInt( threadNum );
	savefile->WriteVec3( offset );
	savefile->WriteInt( frameRate );
	savefile->WriteInt( starttime );
	savefile->WriteInt( cycle );
	activator.Save( savefile );
}

/*
===============
idCameraAnim::Restore
================
*/
void idCameraAnim::Restore( idRestoreGame *savefile ) {
	savefile->ReadInt( threadNum );
	savefile->ReadVec3( offset );
	savefile->ReadInt( frameRate );
	savefile->ReadInt( starttime );
	savefile->ReadInt( cycle );
	activator.Restore( savefile );

	LoadAnim();
}

/*
=====================
idCameraAnim::Spawn
=====================
*/
void idCameraAnim::Spawn( void ) {
	if ( spawnArgs.GetVector( "old_origin", "0 0 0", offset ) ) {
		offset = GetPhysics()->GetOrigin() - offset;
	} else {
		offset.Zero();
	}

	// always think during cinematics
	cinematic = true;

	LoadAnim();
}

/*
================
idCameraAnim::Load
================
*/
void idCameraAnim::LoadAnim( void ) {
	int			version;
	idLexer		parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT );
	idToken		token;
	int			numFrames;
	int			numCuts;
	int			i;
	idStr		filename;
	const char	*key;

	key = spawnArgs.GetString( "anim" );
	if ( !key ) {
		gameLocal.Error( "Missing 'anim' key on '%s'", name.c_str() );
	}

	filename = spawnArgs.GetString( va( "anim %s", key ) );
	if ( !filename.Length() ) {
		gameLocal.Error( "Missing 'anim %s' key on '%s'", key, name.c_str() );
	}

	filename.SetFileExtension( MD5_CAMERA_EXT );
	if ( !parser.LoadFile( filename ) ) {
		gameLocal.Error( "Unable to load '%s' on '%s'", filename.c_str(), name.c_str() );
	}

	cameraCuts.Clear();
	cameraCuts.SetGranularity( 1 );
	camera.Clear();
	camera.SetGranularity( 1 );

	parser.ExpectTokenString( MD5_VERSION_STRING );
	version = parser.ParseInt();
	if ( version != MD5_VERSION ) {
		parser.Error( "Invalid version %d.  Should be version %d\n", version, MD5_VERSION );
	}

	// skip the commandline
	parser.ExpectTokenString( "commandline" );
	parser.ReadToken( &token );

	// parse num frames
	parser.ExpectTokenString( "numFrames" );
	numFrames = parser.ParseInt();
	if ( numFrames <= 0 ) {
		parser.Error( "Invalid number of frames: %d", numFrames );
	}

	// parse framerate
	parser.ExpectTokenString( "frameRate" );
	frameRate = parser.ParseInt();
	if ( frameRate <= 0 ) {
		parser.Error( "Invalid framerate: %d", frameRate );
	}

	// parse num cuts
	parser.ExpectTokenString( "numCuts" );
	numCuts = parser.ParseInt();
	if ( ( numCuts < 0 ) || ( numCuts > numFrames ) ) {
		parser.Error( "Invalid number of camera cuts: %d", numCuts );
	}

	// parse the camera cuts
	parser.ExpectTokenString( "cuts" );
	parser.ExpectTokenString( "{" );
	cameraCuts.SetNum( numCuts );
	for( i = 0; i < numCuts; i++ ) {
		cameraCuts[ i ] = parser.ParseInt();
		if ( ( cameraCuts[ i ] < 1 ) || ( cameraCuts[ i ] >= numFrames ) ) {
			parser.Error( "Invalid camera cut" );
		}
	}
	parser.ExpectTokenString( "}" );

	// parse the camera frames
	parser.ExpectTokenString( "camera" );
	parser.ExpectTokenString( "{" );
	camera.SetNum( numFrames );
	for( i = 0; i < numFrames; i++ ) {
		parser.Parse1DMatrix( 3, camera[ i ].t.ToFloatPtr() );
		parser.Parse1DMatrix( 3, camera[ i ].q.ToFloatPtr() );
		camera[ i ].fov = parser.ParseFloat();
	}
	parser.ExpectTokenString( "}" );

#if 0
	if ( !gameLocal.GetLocalPlayer() ) {
		return;
	}

	idDebugGraph gGraph;
	idDebugGraph tGraph;
	idDebugGraph qGraph;
	idDebugGraph dtGraph;
	idDebugGraph dqGraph;
	gGraph.SetNumSamples( numFrames );
	tGraph.SetNumSamples( numFrames );
	qGraph.SetNumSamples( numFrames );
	dtGraph.SetNumSamples( numFrames );
	dqGraph.SetNumSamples( numFrames );

	gameLocal.Printf( "\n\ndelta vec:\n" );
	float diff_t, last_t, t;
	float diff_q, last_q, q;
	diff_t = last_t = 0.0f;
	diff_q = last_q = 0.0f;
	for( i = 1; i < numFrames; i++ ) {
		t = ( camera[ i ].t - camera[ i - 1 ].t ).Length();
		q = ( camera[ i ].q.ToQuat() - camera[ i - 1 ].q.ToQuat() ).Length();
		diff_t = t - last_t;
		diff_q = q - last_q;
		gGraph.AddValue( ( i % 10 ) == 0 );
		tGraph.AddValue( t );
		qGraph.AddValue( q );
		dtGraph.AddValue( diff_t );
		dqGraph.AddValue( diff_q );

		gameLocal.Printf( "%d: %.8f  :  %.8f,     %.8f  :  %.8f\n", i, t, diff_t, q, diff_q  );
		last_t = t;
		last_q = q;
	}

	gGraph.Draw( colorBlue, 300.0f );
	tGraph.Draw( colorOrange, 60.0f );
	dtGraph.Draw( colorYellow, 6000.0f );
	qGraph.Draw( colorGreen, 60.0f );
	dqGraph.Draw( colorCyan, 6000.0f );
#endif
}

/*
===============
idCameraAnim::Start
================
*/
void idCameraAnim::Start( void ) {
	cycle = spawnArgs.GetInt( "cycle" );
	if ( !cycle ) {
		cycle = 1;
	}

	if ( g_debugCinematic.GetBool() ) {
		gameLocal.Printf( "%d: '%s' start\n", gameLocal.framenum, GetName() );
	}

	starttime = gameLocal.time;
	gameLocal.SetCamera( this );
	BecomeActive( TH_THINK );

	// if the player has already created the renderview for this frame, have him update it again so that the camera starts this frame
	if ( gameLocal.GetLocalPlayer()->GetRenderView()->time == gameLocal.time ) {
		gameLocal.GetLocalPlayer()->CalculateRenderView();
	}
}

/*
=====================
idCameraAnim::Stop
=====================
*/
void idCameraAnim::Stop( void ) {
	if ( gameLocal.GetCamera() == this ) {
		if ( g_debugCinematic.GetBool() ) {
			gameLocal.Printf( "%d: '%s' stop\n", gameLocal.framenum, GetName() );
		}

		BecomeInactive( TH_THINK );
		gameLocal.SetCamera( NULL );
		if ( threadNum ) {
			idThread::ObjectMoveDone( threadNum, this );
			threadNum = 0;
		}
		ActivateTargets( activator.GetEntity() );
	}
}

/*
=====================
idCameraAnim::Think
=====================
*/
void idCameraAnim::Think( void ) {
	int frame;
	int frameTime;

	if ( thinkFlags & TH_THINK ) {
		// check if we're done in the Think function when the cinematic is being skipped (idCameraAnim::GetViewParms isn't called when skipping cinematics).
		if ( !gameLocal.skipCinematic ) {
			return;
		}

		if ( camera.Num() < 2 ) {
			// 1 frame anims never end
			return;
		}

		if ( frameRate == USERCMD_HZ ) {
			frameTime	= gameLocal.time - starttime;
			frame		= frameTime / gameLocal.msec;
		} else {
			frameTime	= ( gameLocal.time - starttime ) * frameRate;
			frame		= frameTime / 1000;
		}

		if ( frame > camera.Num() + cameraCuts.Num() - 2 ) {
			if ( cycle > 0 ) {
				cycle--;
			}

			if ( cycle != 0 ) {
				// advance start time so that we loop
				starttime += ( ( camera.Num() - cameraCuts.Num() ) * 1000 ) / frameRate;
			} else {
				Stop();
			}
		}
	}
}

/*
=====================
idCameraAnim::GetViewParms
=====================
*/
void idCameraAnim::GetViewParms( renderView_t *view ) {
	int				realFrame;
	int				frame;
	int				frameTime;
	float			lerp;
	float			invlerp;
	cameraFrame_t	*camFrame;
	int				i;
	int				cut;
	idQuat			q1, q2, q3;

	assert( view );
	if ( !view ) {
		return;
	}

	if ( camera.Num() == 0 ) {
		// we most likely are in the middle of a restore
		// FIXME: it would be better to fix it so this doesn't get called during a restore
		return;
	}

#ifdef _D3XP
	SetTimeState ts( timeGroup );
#endif

	if ( frameRate == USERCMD_HZ ) {
		frameTime	= gameLocal.time - starttime;
		frame		= frameTime / gameLocal.msec;
		lerp		= 0.0f;
	} else {
		frameTime	= ( gameLocal.time - starttime ) * frameRate;
		frame		= frameTime / 1000;
		lerp		= ( frameTime % 1000 ) * 0.001f;
	}

	// skip any frames where camera cuts occur
	realFrame = frame;
	cut = 0;
	for( i = 0; i < cameraCuts.Num(); i++ ) {
		if ( frame < cameraCuts[ i ] ) {
			break;
		}
		frame++;
		cut++;
	}

	if ( g_debugCinematic.GetBool() ) {
		int prevFrameTime	= ( gameLocal.time - starttime - gameLocal.msec ) * frameRate;
		int prevFrame		= prevFrameTime / 1000;
		int prevCut;

		prevCut = 0;
		for( i = 0; i < cameraCuts.Num(); i++ ) {
			if ( prevFrame < cameraCuts[ i ] ) {
				break;
			}
			prevFrame++;
			prevCut++;
		}

		if ( prevCut != cut ) {
			gameLocal.Printf( "%d: '%s' cut %d\n", gameLocal.framenum, GetName(), cut );
		}
	}

	// clamp to the first frame.  also check if this is a one frame anim.  one frame anims would end immediately,
	// but since they're mainly used for static cams anyway, just stay on it infinitely.
	if ( ( frame < 0 ) || ( camera.Num() < 2 ) ) {
		view->viewaxis = camera[ 0 ].q.ToQuat().ToMat3();
		view->vieworg = camera[ 0 ].t + offset;
		view->fov_x = camera[ 0 ].fov;
	} else if ( frame > camera.Num() - 2 ) {
		if ( cycle > 0 ) {
			cycle--;
		}

		if ( cycle != 0 ) {
			// advance start time so that we loop
			starttime += ( ( camera.Num() - cameraCuts.Num() ) * 1000 ) / frameRate;
			GetViewParms( view );
			return;
		}

		Stop();
		if ( gameLocal.GetCamera() != NULL ) {
			// we activated another camera when we stopped, so get it's viewparms instead
			gameLocal.GetCamera()->GetViewParms( view );
			return;
		} else {
			// just use our last frame
			camFrame = &camera[ camera.Num() - 1 ];
			view->viewaxis = camFrame->q.ToQuat().ToMat3();
			view->vieworg = camFrame->t + offset;
			view->fov_x = camFrame->fov;
		}
	} else if ( lerp == 0.0f ) {
		camFrame = &camera[ frame ];
		view->viewaxis = camFrame[ 0 ].q.ToMat3();
		view->vieworg = camFrame[ 0 ].t + offset;
		view->fov_x = camFrame[ 0 ].fov;
	} else {
		camFrame = &camera[ frame ];
		invlerp = 1.0f - lerp;
		q1 = camFrame[ 0 ].q.ToQuat();
		q2 = camFrame[ 1 ].q.ToQuat();
		q3.Slerp( q1, q2, lerp );
		view->viewaxis = q3.ToMat3();
		view->vieworg = camFrame[ 0 ].t * invlerp + camFrame[ 1 ].t * lerp + offset;
		view->fov_x = camFrame[ 0 ].fov * invlerp + camFrame[ 1 ].fov * lerp;
	}

	gameLocal.CalcFov( view->fov_x, view->fov_x, view->fov_y );

	// setup the pvs for this frame
	UpdatePVSAreas( view->vieworg );

#if 0
	static int lastFrame = 0;
	static idVec3 lastFrameVec( 0.0f, 0.0f, 0.0f );
	if ( gameLocal.time != lastFrame ) {
		gameRenderWorld->DebugBounds( colorCyan, idBounds( view->vieworg ).Expand( 16.0f ), vec3_origin, gameLocal.msec );
		gameRenderWorld->DebugLine( colorRed, view->vieworg, view->vieworg + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false );
		gameRenderWorld->DebugLine( colorCyan, lastFrameVec, view->vieworg, 10000, false );
		gameRenderWorld->DebugLine( colorYellow, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 66.0f, 10000, false );
		gameRenderWorld->DebugLine( colorOrange, view->vieworg + view->viewaxis[ 0 ] * 64.0f, view->vieworg + view->viewaxis[ 0 ] * 64.0f + idVec3( 0.0f, 0.0f, 2.0f ), 10000, false );
		lastFrameVec = view->vieworg;
		lastFrame = gameLocal.time;
	}
#endif

	if ( g_showcamerainfo.GetBool() ) {
		gameLocal.Printf( "^5Frame: ^7%d/%d\n\n\n", realFrame + 1, camera.Num() - cameraCuts.Num() );
	}
}

/*
===============
idCameraAnim::Event_Activate
================
*/
void idCameraAnim::Event_Activate( idEntity *_activator ) {
	activator = _activator;
	if ( thinkFlags & TH_THINK ) {
		Stop();
	} else {
		Start();
	}
}

/*
===============
idCameraAnim::Event_Start
================
*/
void idCameraAnim::Event_Start( void ) {
	Start();
}

/*
===============
idCameraAnim::Event_Stop
================
*/
void idCameraAnim::Event_Stop( void ) {
	Stop();
}

/*
================
idCameraAnim::Event_SetCallback
================
*/
void idCameraAnim::Event_SetCallback( void ) {
	if ( ( gameLocal.GetCamera() == this ) && !threadNum ) {
		threadNum = idThread::CurrentThreadNum();
		idThread::ReturnInt( true );
	} else {
		idThread::ReturnInt( false );
	}
}