/*************************************************************************
Crytek Source File.
Copyright (C), Crytek Studios, 2001-2004.
-------------------------------------------------------------------------
$Id$
$DateTime$

-------------------------------------------------------------------------
History:
- 28:10:2005   16:00 : Created by Márcio Martins

*************************************************************************/
#include "StdAfx.h"
#include "IronSight.h"
#include "Player.h"


//------------------------------------------------------------------------
CIronSight::CIronSight()
: m_pWeapon(0),
	m_savedFoVScale(0.0f),
	m_zoomed(false),
	m_zoomingIn(false),
	m_zoomTimer(0.0f),
	m_zoomTime(0.0f),
	m_focus(1.0f),
	m_startFoV(0.0f),
	m_endFoV(0.0f),
	m_smooth(0.0f),
	m_enabled(true)
{
}

//------------------------------------------------------------------------
CIronSight::~CIronSight()
{
}

//------------------------------------------------------------------------
void CIronSight::Init(IWeapon *pWeapon, const struct IItemParamsNode *params)
{
	m_pWeapon = static_cast<CWeapon *>(pWeapon);

	ResetParams(params);
}

//------------------------------------------------------------------------
void CIronSight::Update(float frameTime, uint frameId)
{
	float doft = 1.0f;
	if (!m_zoomed)
		doft = 0.0f;

	if (m_zoomTime > 0.0f)	// zoomTime is set to 0.0 when zooming ends
	{
		float t = CLAMP(1.0f-m_zoomTimer/m_zoomTime, 0.0f, 1.0f);
		float fovScale;

		if (m_smooth)
		{
			if (m_startFoV > m_endFoV)
				doft = t;
			else
				doft = 1.0f-t;

			fovScale = m_startFoV+t*(m_endFoV-m_startFoV);
		}
		else
		{
			fovScale = m_startFoV;
			if (t>=1.0f)
				fovScale = m_endFoV;
		}

		OnZoomStep(m_startFoV>m_endFoV, t);

		SetActorFoVScale(fovScale, true, true, true);

		if (m_zoomparams.dof)
			UpdateDepthOfField(frameTime, doft);

		if (t>=1.0f)
		{
			if (m_zoomingIn)
				m_zoomed = true;
			else
				m_zoomed = false;

			m_zoomTime = 0.0f;
		}

		ICVar *i_offset_right=GetISystem()->GetIConsole()->GetCVar("i_offset_right");
		ICVar *i_offset_front=GetISystem()->GetIConsole()->GetCVar("i_offset_front");
		ICVar *i_offset_up=GetISystem()->GetIConsole()->GetCVar("i_offset_up");
		float right = i_offset_right->GetFVal();
		float front = i_offset_front->GetFVal();
		float up = i_offset_up->GetFVal();

		float s=CLAMP(t,0,1);
		if (!m_zoomingIn)
			s=1-s;

		Vec3 dummy(-right*s, -front*s, -up*s);

		m_pWeapon->SetDummyOffset(dummy);
	}

	if (m_zoomparams.dof)
		UpdateDepthOfField(frameTime, doft);

	if (m_zoomTimer>0.0f || m_zoomed)
	{
		m_zoomTimer -= frameTime;
		if (m_zoomTimer<0.0f)
			m_zoomTimer=0.0f;		

		if (!m_zoomparams.dof_mask.empty())
		{
			if (m_focus < 1.0f)
			{
				m_focus += frameTime/(0.5f);
			}
			else if (m_focus > 1.0f)
			{
				m_focus = 1.0f;
			}

			float t=m_zoomTimer/m_zoomTime;
			if (m_zoomingIn && m_zoomTime > 0.0f)
			{
				t=1.0f-max(t, 0.0f);
				GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_BlurAmount", t);
			}

			GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_FocusMin", m_focus*m_focus*1.0f);
			GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_FocusMax", m_focus*100.0f);
			GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_FocusLimit", m_focus*1000.0f);
		}
	}
}

//------------------------------------------------------------------------
void CIronSight::Destroy()
{
}

//------------------------------------------------------------------------
void CIronSight::ResetParams(const struct IItemParamsNode *params)
{
	const IItemParamsNode *zoom = params?params->GetChild("zoom"):0;
	const IItemParamsNode *actions = params?params->GetChild("actions"):0;

	m_zoomparams.Reset(zoom);
	m_actions.Reset(actions);
}

//------------------------------------------------------------------------
void CIronSight::PatchParams(const struct IItemParamsNode *patch)
{
	const IItemParamsNode *zoom = patch->GetChild("zoom");
	const IItemParamsNode *actions = patch->GetChild("actions");

	m_zoomparams.Reset(zoom, false);
	m_actions.Reset(actions, false);
}

//------------------------------------------------------------------------
void CIronSight::Activate(bool activate)
{
	if (!activate && m_zoomed && m_zoomingIn && !m_zoomparams.dof_mask.empty())
		ClearDoF();

	m_zoomed = false;
	m_zoomingIn = false;

	m_currentStep = 1;
	
	SetActorFoVScale(1.0f, true, true, true);
	SetActorSpeedScale(1.0f);

	ResetTurnOff();

	m_pWeapon->SetDummyOffset(Vec3(0,0,0));
}

//------------------------------------------------------------------------
bool CIronSight::CanZoom() const
{
	return true;
}

//------------------------------------------------------------------------
void CIronSight::StartZoom()
{
	if (m_pWeapon->IsBusy())
		return;
	CActor *pActor = m_pWeapon->GetOwnerActor();
	if (pActor)
		pActor->GetIntensityMonitor()->Enable(false);
	if (!m_zoomed)
	{
		EnterZoom(m_zoomparams.zoom_in_time, m_zoomparams.layer.c_str(), true);
		m_currentStep = 1;
	}
	else
	{
		int currentStep = m_currentStep;
		int nextStep = currentStep+1;

		if (nextStep > m_zoomparams.stages.size())
		{
			LeaveZoom(m_zoomparams.zoom_out_time, true);
			nextStep = 1;
		}
		else
		{
			float oFoV = GetZoomFoVScale(currentStep);
			float tFoV = GetZoomFoVScale(nextStep);

			ZoomIn(m_zoomparams.stage_time, oFoV, tFoV, true);

			m_currentStep = nextStep;
		}
	}
}

//------------------------------------------------------------------------
void CIronSight::StopZoom()
{
}


//------------------------------------------------------------------------
void CIronSight::ZoomIn()
{
	if (m_pWeapon->IsBusy())
		return;

	if (!m_zoomed)
	{
		EnterZoom(m_zoomparams.zoom_in_time, m_zoomparams.layer.c_str(), true);
		m_currentStep = 1;
	}
	else
	{
		int currentStep = m_currentStep;
		int nextStep = currentStep+1;

		if (nextStep > m_zoomparams.stages.size())
			return;
		else
		{
			float oFoV = GetZoomFoVScale(currentStep);
			float tFoV = GetZoomFoVScale(nextStep);

			ZoomIn(m_zoomparams.stage_time, oFoV, tFoV, true);

			m_currentStep = nextStep;
		}
	}
}

//------------------------------------------------------------------------
void CIronSight::ZoomOut()
{
	if (m_pWeapon->IsBusy())
		return;

	if (!m_zoomed)
	{
		EnterZoom(m_zoomparams.zoom_in_time, m_zoomparams.layer.c_str(), true);
		m_currentStep = 1;
	}
	else
	{
		int currentStep = m_currentStep;
		int nextStep = currentStep-1;

		if (nextStep < 1)
			return;
		else
		{
			float oFoV = GetZoomFoVScale(currentStep);
			float tFoV = GetZoomFoVScale(nextStep);

			ZoomIn(m_zoomparams.stage_time, oFoV, tFoV, true);

			m_currentStep = nextStep;
		}
	}
}

//------------------------------------------------------------------------
bool CIronSight::IsZoomed() const
{
	return m_zoomed;
}

//------------------------------------------------------------------------
bool CIronSight::IsZooming() const
{
	return m_zoomTimer>0.0f;
}

//------------------------------------------------------------------------
void CIronSight::Enable(bool enable)
{
	m_enabled = enable;
}

//------------------------------------------------------------------------
bool CIronSight::IsEnabled() const
{
	return m_enabled;
}

//------------------------------------------------------------------------
void CIronSight::EnterZoom(float time, const char *zoom_layer, bool smooth)
{
	ResetTurnOff();
	OnEnterZoom();
	SetActorSpeedScale(0.35f);

	m_pWeapon->FadeCrosshair(1.0f, 0.0f, WEAPON_FADECROSSHAIR_ZOOM);

	float oFoV = GetZoomFoVScale(0);
	float tFoV = GetZoomFoVScale(1);

	ZoomIn(time, oFoV, tFoV, smooth);

	m_pWeapon->SetBusy(true);
	m_pWeapon->PlayAction(m_actions.zoom_in.c_str(), 0, false, CItem::eIPAF_Default | CItem::eIPAF_RepeatLastFrame);

	struct EnterZoomAction: public ISchedulerAction
	{
		EnterZoomAction(CIronSight *_ironSight): ironSight(_ironSight) {};
		CIronSight *ironSight;

		virtual void execute(CItem *pWeapon)
		{
			pWeapon->SetBusy(false);
			pWeapon->PlayLayer(ironSight->m_zoomparams.layer.c_str(), CItem::eIPAF_Default|CItem::eIPAF_NoBlend);
			
			ironSight->m_zoomed = true;
			ironSight->OnZoomedIn();
		}
	};
	m_pWeapon->GetScheduler()->TimerAction((uint)(m_zoomparams.zoom_in_time*1000), new EnterZoomAction(this), false);
}

//------------------------------------------------------------------------
void CIronSight::LeaveZoom(float time, bool smooth)
{
	ResetTurnOff();
	OnLeaveZoom();
	SetActorSpeedScale(1.0f);

	m_pWeapon->FadeCrosshair(0.0f, 1.0f, WEAPON_FADECROSSHAIR_ZOOM);

	float oFoV = GetZoomFoVScale(0);
	float tFoV = GetZoomFoVScale(1);

	ZoomOut(time, tFoV, oFoV, smooth);

	m_pWeapon->SetBusy(true);
	m_pWeapon->StopLayer(m_zoomparams.layer.c_str(), CItem::eIPAF_Default|CItem::eIPAF_NoBlend);
	m_pWeapon->PlayAction(m_actions.zoom_out.c_str(), 0, false, CItem::eIPAF_Default|CItem::eIPAF_NoBlend);

	struct LeaveZoomAction: public ISchedulerAction
	{
		LeaveZoomAction(CIronSight *_ironSight): ironSight(_ironSight) {};
		CIronSight *ironSight;

		virtual void execute(CItem *pWeapon)
		{
			pWeapon->SetBusy(false);

			ironSight->m_zoomed = false;
			ironSight->OnZoomedOut();
		}
	};

	m_pWeapon->GetScheduler()->TimerAction((uint)(m_zoomparams.zoom_in_time*1000), new LeaveZoomAction(this), false);
}

//------------------------------------------------------------------------
void CIronSight::ResetTurnOff()
{
	m_savedFoVScale = 0.0f;
	m_pWeapon->StopLayer(m_zoomparams.layer.c_str(), CItem::eIPAF_Default|CItem::eIPAF_NoBlend);
}

//------------------------------------------------------------------------
void CIronSight::TurnOff(bool enable, bool smooth, bool anim)
{
	if (!enable && (m_savedFoVScale > 0.0f))
	{
		OnEnterZoom();
	
		float oFoV = GetZoomFoVScale(0);
		float tFoV = m_savedFoVScale;

		SetActorSpeedScale(0.35f);

		ZoomIn(m_zoomparams.zoom_out_time, oFoV, tFoV, smooth);

		if (anim)
			m_pWeapon->PlayAction(m_actions.zoom_in.c_str());

		struct DisableTurnOffAction: public ISchedulerAction
		{
			DisableTurnOffAction(CIronSight *_ironSight): ironSight(_ironSight) {};
			CIronSight *ironSight;

			virtual void execute(CItem *pWeapon)
			{
				pWeapon->PlayLayer(ironSight->m_zoomparams.layer.c_str(), CItem::eIPAF_Default|CItem::eIPAF_NoBlend);
				
				ironSight->m_zoomed = true;
				ironSight->OnZoomedIn();

				pWeapon->SetBusy(false);
			}
		};
		m_pWeapon->GetScheduler()->TimerAction((uint)(m_zoomparams.zoom_out_time*1000), new DisableTurnOffAction(this), false);
		m_savedFoVScale = 0.0f;
	}
	else if (m_zoomed && enable)
	{
		m_pWeapon->SetBusy(true);
		m_savedFoVScale = GetActorFoVScale();

		OnLeaveZoom();

		float oFoV = GetZoomFoVScale(0);
		float tFoV = m_savedFoVScale;

		SetActorSpeedScale(1);
		ZoomOut(m_zoomparams.zoom_out_time, tFoV, oFoV, smooth);

		m_pWeapon->StopLayer(m_zoomparams.layer.c_str(), CItem::eIPAF_Default|CItem::eIPAF_NoBlend);

		if (anim)
			m_pWeapon->PlayAction(m_actions.zoom_out.c_str());

		struct EnableTurnOffAction: public ISchedulerAction
		{
			EnableTurnOffAction(CIronSight *_ironSight): ironSight(_ironSight) {};
			CIronSight *ironSight;

			virtual void execute(CItem *pWeapon)
			{
				ironSight->m_zoomed = false;
				ironSight->OnZoomedOut();
			}
		};
		m_pWeapon->GetScheduler()->TimerAction((uint)(m_zoomparams.zoom_out_time*1000), new EnableTurnOffAction(this), false);
	}
}

//------------------------------------------------------------------------
void CIronSight::ZoomIn(float time, float from, float to, bool smooth)
{
	m_zoomTime = time;
	m_zoomTimer = m_zoomTime;
	m_startFoV = from;
	m_endFoV = to;
	m_smooth = smooth;

	float totalFoV = abs(m_endFoV-m_startFoV);
	float ownerFoV = GetActorFoVScale();

	m_startFoV = ownerFoV;
	
	float deltaFoV = abs(m_endFoV-m_startFoV)/totalFoV;
	
	if (deltaFoV < totalFoV)
	{
		m_zoomTime = (deltaFoV/totalFoV)*time;
		m_zoomTimer = m_zoomTime;
	}

	m_zoomingIn = true;
}

//------------------------------------------------------------------------
void CIronSight::ZoomOut(float time, float from, float to, bool smooth)
{

	m_zoomTimer = time;
	m_zoomTime = m_zoomTimer;

	m_startFoV = from;
	m_endFoV = to;
	m_smooth = smooth;


	float totalFoV = abs(m_endFoV-m_startFoV);
	float ownerFoV = GetActorFoVScale();

	m_startFoV = ownerFoV;

	float deltaFoV = abs(m_endFoV-m_startFoV);

	if (deltaFoV < totalFoV)
	{
		m_zoomTime = (deltaFoV/totalFoV)*time;
		m_zoomTimer = m_zoomTime;
	}

	m_zoomingIn = false;
}

//------------------------------------------------------------------------
void CIronSight::OnEnterZoom()
{
}

//------------------------------------------------------------------------
void CIronSight::OnZoomedIn()
{
	if (!m_zoomparams.dof_mask.empty())
	{
		m_focus = 1.0f;

		GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_FocusRange", -1.0f);
		GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_UseMask", 1.0f);
		GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_MaskTexName", m_zoomparams.dof_mask);
		GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_BlurAmount", 1.0f);
		GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_Active", 1.0f);
	}
}

//------------------------------------------------------------------------
void CIronSight::OnLeaveZoom()
{
}

//------------------------------------------------------------------------
void CIronSight::OnZoomedOut()
{
	ClearDoF();
	CActor *pActor = m_pWeapon->GetOwnerActor();
	if (pActor)
	{
		pActor->GetIntensityMonitor()->Enable(true);
		pActor->GetIntensityMonitor()->ChangeFOV(1.0f, 0.0f);
	}
}

//------------------------------------------------------------------------
void CIronSight::OnZoomStep(bool zoomingIn, float t)
{
	m_focus = 0.0f;
}

//------------------------------------------------------------------------
void CIronSight::SetActorFoVScale(float fovScale, bool sens,bool recoil, bool hbob)
{
	if (!m_pWeapon->GetOwnerActor())
		return;

	SActorParams *pActorParams = m_pWeapon->GetOwnerActor()->GetActorParams();
	if (!pActorParams)
		return;

	pActorParams->viewFoVScale = fovScale;

	if (sens)
		pActorParams->viewSensitivity = GetSensitivityFromFoVScale(fovScale);

	if (hbob)
	{
		float mult = GetHBobFromFoVScale(fovScale);
		pActorParams->weaponInertiaMultiplier = mult;
		pActorParams->weaponBobbingMultiplier = mult;
		pActorParams->headBobbingMultiplier = mult;
	}

	//if (recoil)
	//	m_pWeapon->SetRecoilScale(GetRecoilFromFoVScale(fovScale));
}

//------------------------------------------------------------------------
float CIronSight::GetActorFoVScale() const
{
	if (!m_pWeapon->GetOwnerActor())
		return 1.0f;

	SActorParams *pActorParams = m_pWeapon->GetOwnerActor()->GetActorParams();
	if (!pActorParams)
		return 1.0f;

	return pActorParams->viewFoVScale;
}


//------------------------------------------------------------------------
void CIronSight::SetActorSpeedScale(float scale)
{
	if (!m_pWeapon->GetOwnerActor())
		return;

	SPlayerParams *pActorParams = static_cast<SPlayerParams *>(m_pWeapon->GetOwnerActor()->GetActorParams());
	if (!pActorParams)
		return;

	pActorParams->speedMultiplier = scale;
}

//------------------------------------------------------------------------
float CIronSight::GetActorSpeedScale() const
{
	if (!m_pWeapon->GetOwnerActor())
		return 1.0f;

	SPlayerParams *pActorParams = static_cast<SPlayerParams *>(m_pWeapon->GetOwnerActor()->GetActorParams());
	if (!pActorParams)
		return 1.0f;

	return pActorParams->speedMultiplier;
}

//------------------------------------------------------------------------
float CIronSight::GetSensitivityFromFoVScale(float scale) const
{
	float mag = GetMagFromFoVScale(scale);
	if (mag<=0.99f)
		return 1.0f;

	return 1.0f/(mag*m_zoomparams.sensitivity_ratio);
}

//------------------------------------------------------------------------
float CIronSight::GetHBobFromFoVScale(float scale) const
{
	float mag = GetMagFromFoVScale(scale);
	if (mag<=1.001f)
		return 1.0f;

	return 1.0f/(mag*m_zoomparams.hbob_ratio);
}

//------------------------------------------------------------------------
float CIronSight::GetRecoilFromFoVScale(float scale) const
{
	float mag = GetMagFromFoVScale(scale);
	if (mag<=1.001f)
		return 1.0f;

	return 1.0f/(mag*m_zoomparams.recoil_ratio);
}

//------------------------------------------------------------------------
float CIronSight::GetMagFromFoVScale(float scale) const
{
	assert(scale>0.0f);
	return 1.0f/scale;
}

//------------------------------------------------------------------------
float CIronSight::GetFoVScaleFromMag(float mag) const
{
	assert(mag>0.0f);
	if (mag >= 1.0f)
		return 1.0f/mag;
	else
		return 1.0f;
}

//------------------------------------------------------------------------
float CIronSight::GetZoomFoVScale(int step) const
{
	if (!step)
		return 1.0f;

	return 1.0f/m_zoomparams.stages[step-1];
}

//------------------------------------------------------------------------
void CIronSight::ClearDoF()
{
	GetISystem()->GetI3DEngine()->SetPostProcessFxParam("Dof_Active", 0.0f);
}

//------------------------------------------------------------------------
void CIronSight::ExitZoom()
{
	if (m_zoomed || m_zoomTime>0.0f)
	{
		LeaveZoom(m_zoomparams.zoom_out_time, true);
		m_currentStep = 1;
	}
}