////////////////////////////////////////////////////////////////////////////////
//
// Copyright 2016 RWS Inc, All Rights Reserved
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of version 2 of the GNU General Public License as published by
// the Free Software Foundation
//
// This program 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 this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
// cutscene.cpp
// Project: Postal
//
// This module handles all the cut-scenes.
//
// History:
//		04/18/97 MJR	Started.
//
//		04/20/07 BRH	Problem compiling this file due to RPrint print being
//							defined more than once.  It may have just been cut and
//							pasted code.
//
//		04/21/97 MJR	Created real version of CutScene().
//
//		04/22/97 MJR	Testing and tuning.
//
//		04/26/97 JRD	Added a swirling blur effect to the cutscenes
//
//		04/27/97 JRD	Merged CSwirl::Configure with CutScene to allow greater flxibility
//
//		06/08/97 MJR	Changed interface in order to properly clean everything
//							up without memory leaks.
//
//		06/11/97	JMI	Changed so CutSceneStart() does not require a prefs file
//							and section name but instead opens and creates them using
//							a single realm number that is now passed in.
//
//		06/12/97 MJR	Renamed m_pszRealm to m_pszRealmPrefsFile.
//
//		06/14/97 MJR	Fixed problem regarding default multialpha name and what
//							happens if none gets loaded (i.e. - it crashed).
//
//					MJR	Added support for sepearate network sections in realms
//							prefs file.
//
//		06/17/97	JMI	Added NULL in call to PlaySample() corresponding to new
//							param.
//
//		07/05/97 MJR	Changed to RSP_BLACK_INDEX instead of 0.
//
//		07/14/97 BRH	Added challenge mode parameter to CutSceneStart so that
//							the proper text can be displayed for the Gauntlet.
//
//		07/17/97	JMI	Changed RSnd*'s to SampleMaster::SoundInstances.
//							Now uses new SampleMaster interface for volume and play
//							instance reference.
//
//		07/18/97 MJR	Got progress bars working in a ROUGH sort of way.  They
//							still need some tuning to look better and we need to save
//							and load the "total bytes" (aka cumm units) for each
//							realm to the prefs file.
//
//		07/18/97	JMI	Got rid of bogus immitation PlaySample functions.
//							Now there is one PlaySample() function.  Also, you now
//							MUST specify a category and you don't have to specify a
//							SoundInstance ptr to specify a volume.
//
//		07/20/97	JMI	Added NUM_ELEMENTS() macro as alternative to
//							(sizeof(a)/sizeof(a[0]) ).
//							Changed asWavyY to be of type short (was no specific type
//							and, therefore, int before).
//							Fix parenthesis bug in CutScene_RFileCallback() with index
//							that caused it to hit -1.
//
//		08/18/97 BRH	Changed default cutscene from cut00 to abstract.
//
//		08/18/97	JMI	Now works with the four different combinations of missing
//							assets I could conceive.
//
//		08/20/97	JMI	Now uses source clipping when copying the progress bar
//							area out of m_pimBGLayer for safety (normally they should
//							be the correct size).
//
//		08/21/97	JMI	Changed call to Update() to UpdateSystem() and occurrences
//							of rspUpdateDisplay() to UpdateDisplay().
//
//		08/22/97 BRH	Added ability to load a different sound file for each
//							cutscene, specified in the realms.ini file.
//
//		08/22/97	JMI	Changed calls to UpdateDisplay() back to rspUpdateDisplay()
//							since we no longer need UpdateDisplay() now that we are
//							using rspLock/Unlock* functions properly.
//							Also, now set the PlaySample() call to purge the sample
//							when done.
//
//		08/27/97	JRD	Adding a compact stand along Martini effect for us with the
//							end of the game called MartiniDo
//
//		08/27/97	JRD	Made MartiniDo WAY to easy for Bill to use, by putting up
//							with him justpassing the whole screen buffer, me doing a
//							general lock on it, creating a temporary bmp, and copying
//							it in.  Happy Bill?
//
//		08/28/97 JRD	Refined martini effect and added fade out option.
//
//		08/30/97 BRH	Fixed path for loading the realms.ini file.  It now loads
//							from the CD path rather thant the HD path.
//
//		09/08/97 BRH	I just learned about rspGetQuitStatus() and found it to
//							be so much fun, that I just started adding it to every
//							function I could find, including MartiniDo()
//
//		09/11/97	JMI	Added some protection for when the cutscene image is not
//							available.  Only tested for missing cutscenes with CompUSA
//							version though which doesn't let you get to the martini
//							effect so that may still not work without a BMP (although,
//							I did put in the appropriate checking, it may still have
//							side effects of using different numbers than the real
//							cutscene would (like say a width of 0) ).
//
//		09/24/97	JMI	Now initializes bFemalePain member of m_musicID.  This
//							member indicates whether the sample is of a female in pain
//							which some countries (so far just UK) don't want in the
//							game.
//
//		10/07/97	JMI	Changed bFemalePain to usDescFlags.
//
//		06/03/98 BRH	Had to change the path for the realms.ini file back to
//							loading from the HD for the add-on pack since the
//							new cutscenes are now only stored on the HD while the
//							original PostalCD is in their CD drive (it only has
//							the old cutscene images).
//
//		02/04/00 MJR	Changed so that it now checks the HD first and the VD
//							second when trying to load bg's and multialphas.  This
//							was done to fix problems with the Japan Add On.
//
////////////////////////////////////////////////////////////////////////////////

#include "RSPiX.h"
#include "cutscene.h"
#include "game.h"
#include "update.h"
#include "SampleMaster.h"
#include "AlphaAnimType.h"
#include "pvr.h"
#include <kmg/kmg.h>

////////////////////////////////////////////////////////////////////////////////
// Macros/types/etc.
////////////////////////////////////////////////////////////////////////////////

#define FONT_FILE "res/fonts/smash.fnt"

#define DEFAULT_TITLE "Loading..."
#define DEFAULT_BG "res/cutscene/abstract.bmp"
#define DEFAULT_TEXT ""
#define DEFAULT_MUSIC "psychedelic1.wav"

#define BG_X (640 / 2 - 640 / 2)
#define BG_Y (480 / 2 - 480 / 2)

#define NAME_Y 85
#define TEXT_Y (280 - 48)

#define MARGIN 24

#define BLOOD_FORE_R 128 //200
#define BLOOD_FORE_G 0	 //10
#define BLOOD_FORE_B 0	 //10

#define FONT_FORE_R 255 //180
#define FONT_FORE_G 0	//10
#define FONT_FORE_B 0	//10

#define FONT_SHAD_R 0
#define FONT_SHAD_G 0
#define FONT_SHAD_B 0

// Starting position for progress bar
#define PROGRESS_BAR_START_X 165
#define PROGRESS_BAR_START_Y 460

// Width of progress bar
#define PROGRESS_BAR_WIDTH 310
#define PROGRESS_BAR_HEIGHT 8

// Random values for creating spread of pixels (it does +/- these values!!!!)
#define PROGRESS_BAR_RANDOM_X 7
#define PROGRESS_BAR_RANDOM_Y 2

// Number of pixels to plot each time
#define PROGRESS_BAR_DENSITY 10

// How often to update the progress bar (in milliseconds)
#define PROGRESS_BAR_UPDATE_TIME 250

// Area of the screen that needs to be updated (keep 16-byte aligned for speed!!!)
#define PROGRESS_BOX_X 96
#define PROGRESS_BOX_Y 440
#define PROGRESS_BOX_WIDTH 448
#define PROGRESS_BOX_HEIGHT 40

// Determines the number of elements in the passed array at compile time.
#define NUM_ELEMENTS(a) (sizeof(a) / sizeof(a[0]))

// "Letterbox" row/colum sizes
#define LETTERBOX_ROW 64
#define LETTERBOX_COL 24

// Cutscene image dimensions
#define CUTSCENE_WIDTH 640
#define CUTSCENE_HEIGHT 480

#define DIFFICULTY_EASY_CUTOFF 4
#define DIFFICULTY_MEDIUM_CUTOFF 7

////////////////////////////////////////////////////////////////////////////////
//
// Externs
//
////////////////////////////////////////////////////////////////////////////////
extern PVR::cRenderMgr *g_pvrRenderer;
extern RResMgr g_resmgrSamples;

////////////////////////////////////////////////////////////////////////////////
//
//
////////////////////////////////////////////////////////////////////////////////
class CCutSceneInfo
{
public:
	//-----------------------------------------
	RFont *m_pFont;
	char m_szTitle[256];
	char m_szText[4096];
	char m_szMusic[RSP_MAX_PATH];
	uint8_t m_ucForeText;
	uint8_t m_ucShadowText;
	bool doAlpha = false;
	int16_t m_sDelW;
	int16_t m_sDelH;
	int32_t m_lTotalBytes;
	int32_t m_lTimeToUpdate;
	int32_t m_lBytesSoFar;
	U8 m_u8BloodColor;
	int16_t m_sLastDistance;
	SampleMasterID m_musicID;
	//-----------------------------------------

	void Clear()
	{
		m_pFont = NULL;
		m_szTitle[0] = 0;
		m_szText[0] = 0;
		m_szMusic[0] = 0;
		m_ucForeText = 0;
		m_ucShadowText = 0;
		m_sDelW = 0;
		m_sDelH = 0;
		m_lTotalBytes = 0;
		m_lTimeToUpdate = 0;
		m_lBytesSoFar = 0;
		m_u8BloodColor = 0;
		m_sLastDistance = 0;
		m_musicID = g_smidNil;
	}

	CCutSceneInfo()
	{
		Clear();
	}

	~CCutSceneInfo()
	{
		Clear();
	}
};

////////////////////////////////////////////////////////////////////////////////
//
//		MartiniDo - this is a greatly simplified version of the cutscene Martini
//
//		It allows the user a few parameters and then does the effect for a set
//		amount of time, regardless of user input.  There are no text overlays.
//		The program will blacken the screen when done.
//
////////////////////////////////////////////////////////////////////////////////
int16_t MartiniDo(int16_t sStartX,					 // logical start position of image
				  int16_t sStartY,					 // NOTE: it will be clipped so won't actually hit this point!
				  int32_t lMilliLen,				 // how long to do the effect
				  int16_t sRadius,					 // Your tuning pleasure
				  int32_t lSpinTime,				 // in milliseconds
				  int32_t lSwayTime,				 // in milliseconds
													 // RRect *prCenter,					 // if not NULL, use this portion of the image only!
				  int32_t lFadeTime,				 // fade to black, in milliseconds.
				  SampleMaster::SoundInstance siFade // to make sound fade out
)
{

	//==============================================================================
	// Set up clipping parameters based on radius:
	RRect rClip(sStartX, sStartY, CUTSCENE_WIDTH, CUTSCENE_HEIGHT);

	rClip.sW -= sRadius << 1;
	rClip.sH -= sRadius << 1;
	//==============================================================================

	int32_t lStartTime = rspGetMilliseconds();
	int32_t lCurrentTime = -1;

	int32_t lStartFadeTime = lMilliLen - lFadeTime; // lCurrentTime is relative to lStartTime

	// Attempt to do a fade out while swirling....
	while (((lCurrentTime = (rspGetMilliseconds() - lStartTime)) < lMilliLen) && !(rspGetQuitStatus()))
	{
		int32_t lCurrentFadeTime = lCurrentTime - lStartFadeTime;
		//==============================================================================
		// Figure out stage of motion:
		//==============================================================================
		int16_t sDeg = int16_t((360 * lCurrentTime) / lSpinTime);
		sDeg = rspMod360(sDeg);
		int16_t sSwayDeg = int16_t((360 * lCurrentTime) / lSwayTime);
		sSwayDeg = rspMod360(sSwayDeg);

		int16_t sR = int16_t(sRadius * rspSin(sSwayDeg));
		int16_t sOffX = int16_t(rspCos(sDeg) * sR);
		int16_t sOffY = int16_t(rspSin(sDeg) * sR);
		//==============================================================================
		// Draw the frame!

		PVR::cTexture *pTex = g_pvrRenderer->renderTarget;
		if (pTex != nullptr)
		{
			// 1) Put original image down upon target:
			g_pvrRenderer->FlushBatchedDrawQueues();

			g_pvrRenderer->DrawQuad(
				pTex,
				rClip.sX - sOffX,
				rClip.sY - sOffY,
				CUTSCENE_WIDTH, CUTSCENE_HEIGHT);

			// Mask over clipped area with a black border.
			g_pvrRenderer->DrawLetterBoxOverlay(LETTERBOX_ROW, LETTERBOX_ROW, LETTERBOX_COL, LETTERBOX_COL, ((g_GameSettings.m_cCutSceneUnderscan == true) ? -40 : 0));

			// 2) Draw the alpha layer on top at 50% opacity.
			g_pvrRenderer->DrawQuad(
				pTex,
				rClip.sX + sOffX,
				rClip.sY + sOffY,
				CUTSCENE_WIDTH, CUTSCENE_HEIGHT,
				PVR::cRenderMgr::LayerSprite2,
				0.5f);
		}
		
		//---------------------------------------
		// Do a fade out:
		if (lCurrentFadeTime > 0)
		{
			int16_t sByteLevel = int16_t((255.0 * lCurrentFadeTime) / lFadeTime);
			float fIntensity = 1.f * (float(255 - sByteLevel) / 256.f);
			g_pvrRenderer->m_fGlobalRScale = fIntensity;
			g_pvrRenderer->m_fGlobalGScale = fIntensity;
			g_pvrRenderer->m_fGlobalBScale = fIntensity;
			if (siFade) // adjust sound volume:
			{
				SetInstanceVolume(siFade, 255 - sByteLevel);
			}
		}


		//---------------------------------------
		UpdateSystem();
	}
	//==============================================================================
	// Now do a fade out
	//==============================================================================
	
	return SUCCESS;
}

////////////////////////////////////////////////////////////////////////////////
//
//
////////////////////////////////////////////////////////////////////////////////
class CSwirlMe
{
public:
	//------------------------------------------------------------------
	int16_t m_sCenX; // radius in pixels
	int16_t m_sRadX;
	int16_t m_sCenY; // radius in pixels
	int16_t m_sRadY;
	double m_dCenA; // Alpha level (0.0 to 1.0)
	double m_dRadA;
	int32_t m_lCycleTimeX; // in milliseconds (Min, Max, Min)
	int32_t m_lCycleTimeY; // in milliseconds (Min, Max, Min)
	int32_t m_lCycleTimeA; //
	int32_t m_lTimeSpin;
	int32_t m_lBaseTime;
	int32_t m_lLastFrameTime;
	float m_fBtnScale;
	RRect m_rClip; // To offset and limit the effect to a rect:
	SampleMaster::SoundInstance m_siSound;
	CCutSceneInfo *m_pCut;
	//------------------------------------------------------------------

	////////////////////////////////////////////////////////////////////////////
	//		Make next frame of effect
	////////////////////////////////////////////////////////////////////////////
	int16_t MakeFrame(SampleMasterID *psmid)
	{
		if (!IsSamplePlaying(*psmid))
		{
			PlaySample(					   // Returns nothing.
										   // Does not fail.
				*psmid,					   // In:  Identifier of sample you want played.
				SampleMaster::Unspecified, // In:  Sound Volume Category for user adjustment
				255,					   // In:  Initial Sound Volume (0 - 255)
				&m_siSound,				   // Out: Handle for adjusting sound volume
				NULL,					   // Out: Sample duration in ms, if not NULL.
				0,						   // In:  Where to loop back to in milliseconds.
										   //	-1 indicates no looping (unless m_sLoop is
										   // explicitly set).
				0,						   // In:  Where to loop back from in milliseconds.
										   // In:  If less than 1, the end + lLoopEndTime is used.
				true);					   // In:  Call ReleaseAndPurge rather than Release after playing
		}

		if (!m_lBaseTime)
			m_lBaseTime = rspGetMilliseconds();
	
		int32_t lDeltaTime = rspGetMilliseconds() - m_lBaseTime;

		int16_t sDegX = (360 * lDeltaTime) / m_lCycleTimeX;
		int16_t sDegY = (360 * lDeltaTime) / m_lCycleTimeY;
		int16_t sDegAlpha = (360 * lDeltaTime) / m_lCycleTimeA;
		int16_t sDegSpin = (360 * lDeltaTime) / m_lTimeSpin;

		sDegX = rspMod360(sDegX);
		sDegY = rspMod360(sDegY);
		sDegAlpha = rspMod360(sDegAlpha);
		sDegSpin = rspMod360(sDegSpin);

		int16_t sRx = m_sCenX + int16_t(rspSin(sDegX) * m_sRadX);
		int16_t sRy = m_sCenY + int16_t(rspSin(sDegY) * m_sRadY);

		if (sRx < 0)
			sRx = 0;
		if (sRy < 0)
			sRy = 0;

		int16_t sOffX = int16_t(rspCos(sDegSpin) * sRx);
		int16_t sOffY = -int16_t(rspSin(sDegSpin) * sRy);
		sOffX >>= 1;
		sOffY >>= 1;

		double dAlpha = m_dCenA + rspSin(sDegAlpha) * m_dRadA;

		//----------------------- Use global alpha --------------------
		RRect rSafeClip = m_rClip;

		rSafeClip.sX += m_sRadX;
		rSafeClip.sY += m_sRadY;

		rSafeClip.sW -= (m_sRadX << 1);
		rSafeClip.sH -= (m_sRadY << 1);

		//******************************************************************************
		//  THIS IS A COMPLETE HACK - IT IS A LAST MINUTE ATTEMPT TO ADJUST FOR THE
		//  CHANGE TO THE CUT SCENE ASSEST WHICH ADDED BLACK STRIPS TO THE TOP AND
		//  BOTTOM OF THE SCREEN....
		//******************************************************************************
		rSafeClip.sY += 40; // HARD CODED FOR POSTAL!
		rSafeClip.sH -= 80; // HARD CODED FOR POSTAL!
							//******************************************************************************

		PVR::cTexture *pTex = g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_CUTSCENE_BG);
		int16_t sYoffset = ((g_GameSettings.m_cCutSceneUnderscan == true) ? -40 : 0);
		if (pTex != nullptr)
		{			
			g_pvrRenderer->FlushBatchedDrawQueues();

			// 1) Put original image down upon target:
			g_pvrRenderer->DrawQuad(
				pTex,
				m_rClip.sX - sOffX,
				(m_rClip.sY - sOffY) + sYoffset,
				m_rClip.sW,
				m_rClip.sH);

			// Mask over clipped area with a black border.
			g_pvrRenderer->DrawLetterBoxOverlay(LETTERBOX_ROW, LETTERBOX_ROW, LETTERBOX_COL, LETTERBOX_COL, sYoffset);

			// 2) Alpha Blit Upon it:
			if (m_pCut->doAlpha)
			{
				g_pvrRenderer->DrawQuad(
					pTex,
					m_rClip.sX + sOffX,
					m_rClip.sY + sOffY + sYoffset,
					m_rClip.sW,
					m_rClip.sH,
					PVR::cRenderMgr::LayerSprite2,
					0.5f);
			}

			// 3) GENLOCK the text:
			g_pvrRenderer->DrawGuiText();

			// 4) You're done - let the app update the screen -> he can use
			// the given function or do it himself!
		}

		m_fBtnScale += float(rspGetMilliseconds() - m_lLastFrameTime) * 0.0006f;
	
		m_lLastFrameTime = rspGetMilliseconds();

// todo: magic numbers etc.
		if (m_fBtnScale > 1.25f)
		{
			m_fBtnScale = 1.f;
		}
		
		MAPLE::cMapleController *pController = rspGetJoy(0);
		if (pController->m_cControlSchemeId == 4)
		{				
			g_pvrRenderer->DrawQuad(
			g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_CONTROLLER),
			3,
			0.519f, 0.117f,
			0.691f, 0.429f,
			580, 380 + sYoffset,
			int16_t(15.f * m_fBtnScale),
			int16_t(28.f * m_fBtnScale),
			PVR::cRenderMgr::LayerSprite3, 
			0.99f);		
		}
		else
		{
		float btnW = 20.f * m_fBtnScale;
		g_pvrRenderer->DrawQuad(
			g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_CONTROLLER),
			1,
			0.476f, 0.531f,
			0.726f, 0.777f,
			580, 380 + sYoffset,
			int16_t(btnW),
			int16_t(btnW),
			PVR::cRenderMgr::LayerSprite3, 
			0.99f);
		}

		return SUCCESS;
	}

	////////////////////////////////////////////////////////////////////////////
	//		Configure effect
	////////////////////////////////////////////////////////////////////////////
	int16_t
	Configure(
		int32_t lTimeSpin,
		int16_t sMinX, int16_t sMaxX, int32_t lTimeX,
		int16_t sMinY, int16_t sMaxY, int32_t lTimeY,
		double dMinA, double dMaxA, int32_t lTimeA,
		int16_t sX, int16_t sY, int16_t sW, int16_t sH)
	{
		ASSERT(m_pCut);
		ASSERT(lTimeX > 0);
		ASSERT(lTimeY > 0);
		ASSERT(lTimeA > 0);
		ASSERT(lTimeSpin > 0);
		ASSERT((dMinA >= 0.0) && (dMinA <= 1.0));
		ASSERT((dMaxA >= 0.0) && (dMaxA <= 1.0));
		ASSERT(sW > 0);
		ASSERT(sH > 0);
		//----------------------------------------------
		m_sRadX = (sMaxX - sMinX) >> 1;
		m_sCenX = sMinX + m_sRadX;
		m_sRadY = (sMaxY - sMinY) >> 1;
		m_sCenY = sMinY + m_sRadY;
		m_dRadA = (dMaxA - dMinA) / 2.0;
		m_dCenA = dMinA + m_dRadA;
		m_lCycleTimeX = lTimeX;
		m_lCycleTimeY = lTimeY;
		m_lCycleTimeA = lTimeA;
		m_lTimeSpin = lTimeSpin;

		m_rClip.sX = sX + (m_pCut->m_sDelW >> 1);
		m_rClip.sY = sY + (m_pCut->m_sDelH >> 1);
		m_rClip.sW = sW - m_pCut->m_sDelW;
		m_rClip.sH = sH - m_pCut->m_sDelH;

		m_lBaseTime = 0;
		m_lLastFrameTime = 0;

		m_fBtnScale = 1.f;

		return SUCCESS;
	}

	////////////////////////////////////////////////////////////////////////////
	//		Erase
	////////////////////////////////////////////////////////////////////////////
	void Erase()
	{
		m_sCenX = m_sCenY = m_sRadX = m_sRadY =
			m_lTimeSpin = m_lCycleTimeX = m_lCycleTimeY = m_lCycleTimeA = 0;
		m_dCenA = m_dRadA = 0.0;
		m_rClip = RRect(0, 0, 0, 0);
		m_siSound = NULL;
	}

	////////////////////////////////////////////////////////////////////////////
	//		CSwirlMe
	////////////////////////////////////////////////////////////////////////////
	CSwirlMe(
		CCutSceneInfo *pCut)
	{
		m_pCut = pCut; // YOU must free the cut scene info - we won't clear it!
		Erase();
	}

	////////////////////////////////////////////////////////////////////////////
	//		~CSwirlMe
	////////////////////////////////////////////////////////////////////////////
	~CSwirlMe()
	{
		if (m_siSound != 0)
			AbortSample(m_siSound);
		m_siSound = 0;
		m_pCut = NULL; // we don't free this - you do!
	}

	////////////////////////////////////////////////////////////////////////////
	//		Update (optional) - from screen buffer only!
	////////////////////////////////////////////////////////////////////////////
	void Update()
	{
		rspUpdateDisplay(m_rClip.sX, m_rClip.sY, m_rClip.sW, m_rClip.sH);
	}

	////////////////////////////////////////////////////////////////////////////
	//		Clear
	////////////////////////////////////////////////////////////////////////////
	void Clear()
	{
		Erase();
	}
};

////////////////////////////////////////////////////////////////////////////////
// Variables/data
////////////////////////////////////////////////////////////////////////////////

static CCutSceneInfo *ms_pCut = NULL;
static CSwirlMe *pSwirl = NULL;

////////////////////////////////////////////////////////////////////////////////
// Function prototypes
////////////////////////////////////////////////////////////////////////////////

static void CutScene_RFileCallback(int32_t lBytes);

////////////////////////////////////////////////////////////////////////////////
//
// Start cutscene.
//
// If 'simple' mode is true, a simple background is displayed with the name
// of the realm file printed on it.
//
// Otherwise, the variables in the specified section of the specified prefs file
// determine what text is printed on top of the background.
//
// If a special effect is desired, CutSceneConfig() must be called, followed
// by repeated calls to CutSceneUpdate().
//
// CutSceneEnd() must be called when the cutscene is no longer needed.
//
////////////////////////////////////////////////////////////////////////////////
extern PVR::cRenderMgr *g_pvrRenderer;

extern void CutSceneStart(
	bool bSimple,				// In:  Set to 'true' for simple mode
	const RString *pstrSection, // In:  Section to use for this realm
	const RString *pstrEntry,	// In:  Entry to use for this realm
	int16_t sBorderX,
	int16_t sBorderY)
{
	// This is used for more than just paths, so it has a larger size!
	char szText[2048];

	int16_t sYoffset = ((g_GameSettings.m_cCutSceneUnderscan == true)  ? -40 : 0);

	// Init stuff
	ms_pCut = new CCutSceneInfo;

	g_pvrRenderer->m_pGuiTextString->Flush();
	g_pvrRenderer->GetQueue(PVR::cRenderMgr::LayerSprite1)->Unlock();
	g_pvrRenderer->FlushBatchedDrawQueues();
	g_pvrRenderer->m_TextureRepository.Clear();

	g_pvrRenderer->m_fGlobalRScale = 1.0f;
	g_pvrRenderer->m_fGlobalGScale = 1.0f;
	g_pvrRenderer->m_fGlobalBScale = 1.0f;

	// Open the realm prefs file
	RPrefs prefsRealm;
	if (prefsRealm.Open(FullPathCD(g_GameSettings.m_pszRealmPrefsFile), "rt") != 0)
	{
		TRACE("CutSceneStart(): Error opening prefs file.\n");
		return;
	}

	//------------------------------------------------------------------------------
	// Get BG
	//------------------------------------------------------------------------------

	// In simple mode, use default bg.  Otherwise, get bg from prefs file.
	if (bSimple)
	{
		strcpy(szText, DEFAULT_BG);
	}
	else
	{
		prefsRealm.GetVal(*pstrSection, "Bg", DEFAULT_BG, szText);
		if ((strlen(szText) + 1) >= RSP_MAX_PATH)
		{
			TRACE("CutScene(): Bg file name/path too long: '%s'!\n", szText);
			strcpy(szText, DEFAULT_BG);
		}
	}

	//------------------------------------------------------------------------------
	// Get tables used for color matching
	//------------------------------------------------------------------------------

	int16_t sEntrySize = g_pvrRenderer->m_pPaletteMenu->m_sPalEntrySize;
	int16_t sStartIndex = 0;

	// Set palette.
	rspSetPaletteEntries(
		0,
		230,
		g_pvrRenderer->m_pPaletteMenu->Red(sStartIndex),
		g_pvrRenderer->m_pPaletteMenu->Green(sStartIndex),
		g_pvrRenderer->m_pPaletteMenu->Blue(sStartIndex),
		sEntrySize);

	// Update hardware.
	rspUpdatePalette();

	U8 au8Red[256];
	U8 au8Green[256];
	U8 au8Blue[256];
	rspGetPaletteEntries(0, 256, au8Red, au8Green, au8Blue, sizeof(U8));

	//------------------------------------------------------------------------------
	// Generate multialpha
	//------------------------------------------------------------------------------
	ms_pCut->doAlpha = true;

	//------------------------------------------------------------------------------
	// Get font
	//------------------------------------------------------------------------------
	ms_pCut->m_pFont = &g_fontPostal;

	// Find closest matches for the desired colors
	ms_pCut->m_ucForeText = rspMatchColorRGB(
		FONT_FORE_R, FONT_FORE_G, FONT_FORE_B, 10, 236, au8Red, au8Green, au8Blue, sizeof(U8));
	ms_pCut->m_ucShadowText = rspMatchColorRGB(
		FONT_SHAD_R, FONT_SHAD_G, FONT_SHAD_B, 10, 236, au8Red, au8Green, au8Blue, sizeof(U8));

	// Setup print
	RPrint print;

	//------------------------------------------------------------------------------
	// Get title
	//------------------------------------------------------------------------------

	// In simple mode, use default title.  Otherwise, get title from prefs file.
	if (bSimple)
	{
		strcpy(ms_pCut->m_szTitle, DEFAULT_TITLE);
	}
	else
	{
		prefsRealm.GetVal(*pstrSection, "Title", DEFAULT_TITLE, ms_pCut->m_szTitle);
		if ((strlen(ms_pCut->m_szTitle) + 1) >= sizeof(ms_pCut->m_szTitle))
		{
			TRACE("CutScene(): Title too long: '%s'!\n", ms_pCut->m_szTitle);
			strcpy(ms_pCut->m_szTitle, DEFAULT_TITLE);
		}
	}

	//------------------------------------------------------------------------------
	// Get music
	//------------------------------------------------------------------------------

	// In simple mode, use the default music, otherwise get the music from prefs file
	if (bSimple)
	{
		strcpy(ms_pCut->m_szMusic, DEFAULT_MUSIC);
	}
	else
	{
		prefsRealm.GetVal(*pstrSection, "Music", DEFAULT_MUSIC, ms_pCut->m_szMusic);
		if ((strlen(ms_pCut->m_szMusic) + 1) >= sizeof(ms_pCut->m_szMusic))
		{
			TRACE("CutScene(): Music filename too long: '%s'!\n", ms_pCut->m_szMusic);
			strcpy(ms_pCut->m_szMusic, DEFAULT_MUSIC);
		}
	}

	// Set the resource name for the sample
	ms_pCut->m_musicID.pszId = ms_pCut->m_szMusic;	

	//------------------------------------------------------------------------------
	//	Create the Text overlay layer (GENLOCK!)
	//------------------------------------------------------------------------------

	// Create text layer
	print.SetFont(48, ms_pCut->m_pFont);
	print.SetColor(ms_pCut->m_ucForeText, 0, ms_pCut->m_ucShadowText);
	print.SetEffectAbs(RPrint::SHADOW_X, 2);
	print.SetEffectAbs(RPrint::SHADOW_Y, 2);
	print.offsetX = -(((CUTSCENE_WIDTH - print.GetWidth(ms_pCut->m_szTitle)) / 2));
	print.offsetY = 0;
	print.SetColumn(0, 0, CUTSCENE_WIDTH, CUTSCENE_HEIGHT);

	print.printPVR(
		((CUTSCENE_WIDTH - print.GetWidth(ms_pCut->m_szTitle)) / 2),
		NAME_Y, "%s",
		ms_pCut->m_szTitle);

	// Get text to be displayed
	if (bSimple)
	{
		strcpy(ms_pCut->m_szText, DEFAULT_TEXT);
	}
	else
	{
		switch (g_GameSettings.m_cActiveLanguage)
		{
			case 0:
				prefsRealm.GetVal(*pstrSection, "TextEN", DEFAULT_TEXT, ms_pCut->m_szText);
			break;
			case 1:
				prefsRealm.GetVal(*pstrSection, "TextDE", DEFAULT_TEXT, ms_pCut->m_szText);
			break;
			case 2:				
				prefsRealm.GetVal(*pstrSection, "TextFR", DEFAULT_TEXT, ms_pCut->m_szText);
			break;
			case 3:				
				prefsRealm.GetVal(*pstrSection, "TextES", DEFAULT_TEXT, ms_pCut->m_szText);
			break;
			case 4:				
				prefsRealm.GetVal(*pstrSection, "TextNE", DEFAULT_TEXT, ms_pCut->m_szText);			
			break;
			case 5:				
				prefsRealm.GetVal(*pstrSection, "TextIT", DEFAULT_TEXT, ms_pCut->m_szText);			
			break;
		}

		if ((strlen(ms_pCut->m_szText) + 1) >= sizeof(ms_pCut->m_szText))
		{
			TRACE("CutScene(): Text too long: '%s'!\n", ms_pCut->m_szText);
			strcpy(ms_pCut->m_szText, DEFAULT_TEXT);
		}
	}

	// Create cropping rectangle
	// Try to compensate by the way the POSTAL cut scene assets were hosed
	RRect rCrop;
	rCrop = RRect(
		sBorderX,
		sBorderY + 40, // there were horizontal strips implanted
		CUTSCENE_WIDTH - (sBorderX * 2),
		CUTSCENE_HEIGHT - (sBorderY * 2) - 80);

	// Create text layer
	print.SetFont(28, ms_pCut->m_pFont);
	print.SetColor(ms_pCut->m_ucForeText, 0, ms_pCut->m_ucShadowText);
	print.SetEffectAbs(RPrint::SHADOW_X, 1);
	print.SetEffectAbs(RPrint::SHADOW_Y, 1);
	print.SetWordWrap(TRUE);
	print.SetColumn(rCrop.sX + MARGIN, rCrop.sY, rCrop.sW - (MARGIN * 2), rCrop.sH);

	print.offsetX = -((320.0f + float(sBorderX)) - ((float)rCrop.sW / 2.0f));

	print.offsetY = 0;
	print.printPVR(0, TEXT_Y + ((sYoffset < 0) ? -80 : -40), "%s", ms_pCut->m_szText);

	//------------------------------------------------------------------------------
	// Update the actual screen now that everything is ready
	//------------------------------------------------------------------------------

	////////////////////////////////////////////////////////////

	pvr_set_pal_entry(RSP_WHITE_INDEX, PACK_ARGB8888(0, 0, 0, 0));

	// Delete any existing cutscene texture.
	g_pvrRenderer->m_TextureRepository.Delete(PVR_TEXTURE_CUTSCENE_BG);

	// Load the new cutscene texture and draw it.
	PVR::cTextureFactory::UnsetResMgr();
	PVR::cTexture *pTex = g_pvrRenderer->m_TextureRepository.Add(PVR_TEXTURE_CUTSCENE_BG, PVR::cTextureFactory::FromKmg(FullPathCD(szText), 3, 2));
	g_pvrRenderer->DrawQuad(
		pTex,
		0,
		sYoffset,
		CUTSCENE_WIDTH,
		CUTSCENE_HEIGHT,
		PVR::cRenderMgr::LayerBg);

	// Mask over clipped area with a black border.
	g_pvrRenderer->DrawLetterBoxOverlay(LETTERBOX_ROW, LETTERBOX_ROW, LETTERBOX_COL, LETTERBOX_COL, sYoffset);

	// Draw cutscene text, if any.
	g_pvrRenderer->DrawGuiText();

	// Render to the screen.
	g_pvrRenderer->RenderBatchedFrame();

	// Release composite buffer now that we are done.
	rspUnlockBuffer();

	//------------------------------------------------------------------------------
	// Setup stuff for the progress bar
	//------------------------------------------------------------------------------

	// Hook the RFile callback that tells us how much data is about to be read/written
	RFile::ms_criticall = CutScene_RFileCallback;

	// Reset various stuff
	ms_pCut->m_lBytesSoFar = 0;
	int32_t val;
	prefsRealm.GetVal(*pstrSection, "Bytes", int32_t(12000000), &val); 
	ms_pCut->m_lTotalBytes = val;	
	ms_pCut->m_lTimeToUpdate = rspGetMilliseconds();
	ms_pCut->m_sLastDistance = 0;

	// Find good blood color
	ms_pCut->m_u8BloodColor = rspMatchColorRGB(
		BLOOD_FORE_R, BLOOD_FORE_G, BLOOD_FORE_B, 1, 254, au8Red, au8Green, au8Blue, sizeof(U8));

	// Get the sound sak for the current level/difficulty.
	char sakName[100];

	uint8_t numSampleSets = 0;
	prefsRealm.GetVal(*pstrSection, "NumSampleSets", uint8_t(0), &numSampleSets);	
				
	// todo
	if (numSampleSets > 0)
	{		
		uint8_t cStoreAllInAica = 0;
		g_ucMinimalSampleSetEnabled = true;
		srand(rspGetMilliseconds());
		g_ucMinimalSampleSet = 1;
		sprintf(sakName,"SampleSet%d", g_ucMinimalSampleSet);
		prefsRealm.GetVal(*pstrSection, sakName, DEFAULT_BG, sakName);				
		prefsRealm.GetVal(*pstrSection, "StoreAllInAica", uint8_t(0), &cStoreAllInAica);
		CAicaSoundPack::m_bStoreAllInAICA = cStoreAllInAica ? true: false;
	}
	else
	{
		CAicaSoundPack::m_bStoreAllInAICA = false;

		// Easy.
		if (g_GameSettings.m_sDifficulty < DIFFICULTY_EASY_CUTOFF)
		{
			prefsRealm.GetVal(*pstrSection, "SoundSakEasy", DEFAULT_BG, sakName);
		}
		// Medium.
		else if (g_GameSettings.m_sDifficulty < DIFFICULTY_MEDIUM_CUTOFF)
		{
			prefsRealm.GetVal(*pstrSection, "SoundSakMedium", DEFAULT_BG, sakName);
		}
		// Hard.
		else
		{
			prefsRealm.GetVal(*pstrSection, "SoundSakHard", DEFAULT_BG, sakName);
		}
	}
		
	if (g_GameSettings.m_sAudioLanguage == JAPANESE_AUDIO)
	{
		char tmp[150];
		for (int8_t i1 = 0, i2 = 0; i1 <= strlen(sakName); i1++, i2++)
		{
			if (sakName[i1] == '.')
			{
				tmp[i2++] = '-';
				tmp[i2++] = 'j';
				tmp[i2] = '.';					
			}
			else
			{
				tmp[i2] = sakName[i1];
			}
		}
		strcpy(sakName, tmp);
	}

	// The sound sak has been changed - reload accordingly.
	if (!strstr(g_resmgrSamples.GetBasePath(), sakName))
	{		
		g_resmgrSamples.SetBasePath(sakName);
		g_resmgrSamples.Purge();
		g_resmgrSamples.CloseSak();		
		g_resmgrSamples.OpenSak(sakName);
	}

	// Cache the following samples if running the end of game demo sequence.
	if (g_bLastLevelDemo)
	{
		CacheSample(g_smidEndingAudio);
		CacheSample(g_smidFinalScene);
		CacheSample(g_smidFinalSceneCredits);
	}

	CacheSample(g_smidGeneralBeep);
	CacheSample(g_smidMenuItemSelect);	
	
	//------------------------------------------------------------------------------
	// Close prefs file.
	//------------------------------------------------------------------------------
	prefsRealm.Close();	
}

////////////////////////////////////////////////////////////////////////////
//
// Configure cutscene effect
//
////////////////////////////////////////////////////////////////////////////
extern int16_t CutSceneConfig(
	int32_t lTimeSpin,
	int16_t sMinX, int16_t sMaxX, int32_t lTimeX,
	int16_t sMinY, int16_t sMaxY, int32_t lTimeY,
	double dMinA, double dMaxA, int32_t lTimeA,
	int16_t sX, int16_t sY, int16_t sW, int16_t sH)
{
	ASSERT(ms_pCut);
	pSwirl = new CSwirlMe(ms_pCut);

	// Clear RFile hook so nothing gets drawn after this
	RFile::ms_criticall = 0;

	return pSwirl->Configure(
		lTimeSpin,
		sMinX, sMaxX, lTimeX,
		sMinY, sMaxY, lTimeY,
		dMinA, dMaxA, lTimeA,
		sX, sY, sW, sH);
}

////////////////////////////////////////////////////////////////////////////
//
// Update cutscene effect
//
////////////////////////////////////////////////////////////////////////////
extern void CutSceneUpdate(void)
{
	ASSERT(ms_pCut);
	ASSERT(pSwirl);

	pSwirl->MakeFrame(&(ms_pCut->m_musicID));
	pSwirl->Update();
}

////////////////////////////////////////////////////////////////////////////////
//
// Clean up after the cutscene.  It is safe to call this multiple times.
//
////////////////////////////////////////////////////////////////////////////////
extern void CutSceneEnd(void)
{	
	// Fill the screen with black
	g_pvrRenderer->FlushBatchedDrawQueues();
	g_pvrRenderer->ClearScreen();
	UpdateSystem();

	delete ms_pCut;
	ms_pCut = 0;

	delete pSwirl;
	pSwirl = 0;

	// Delete the cutscene image and clear the screen
	g_pvrRenderer->m_TextureRepository.Delete(PVR_TEXTURE_CUTSCENE_BG);

	// Clear RFile hook
	RFile::ms_criticall = 0;
}

////////////////////////////////////////////////////////////////////////////////
//
// Our RFile callback
//
////////////////////////////////////////////////////////////////////////////////
static void CutScene_RFileCallback(int32_t lBytes)
{	
	if (ms_pCut)
	{		
		int16_t sYoffset = ((g_GameSettings.m_cCutSceneUnderscan == true)  ? -40 : 0);
		PVR::cTexture *progBar = g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_PROGRESS_BAR);

		// Update bytes so far
		ms_pCut->m_lBytesSoFar += lBytes;

		// Check if time for an update
		int32_t lNow = rspGetMilliseconds();
		if ((lNow - ms_pCut->m_lTimeToUpdate) > PROGRESS_BAR_UPDATE_TIME)
		{
			g_pvrRenderer->FlushBatchedDrawQueues();

			// Mask over clipped area with a black border, leaving a little for the loading bar;
			g_pvrRenderer->DrawLetterBoxOverlay(LETTERBOX_ROW, 0, LETTERBOX_COL, LETTERBOX_COL, sYoffset);
			g_pvrRenderer->DrawQuad(uint32_t(PACK_ARGB8888(255, 0, 0, 0)),
									int16_t(LETTERBOX_COL), 
									int16_t(CUTSCENE_HEIGHT - LETTERBOX_ROW) + sYoffset,
									CUTSCENE_WIDTH - LETTERBOX_COL, 34,
									PVR::cRenderMgr::LayerSprite2);

			// Draw the cutscene image.
			PVR::cTexture *pTex = g_pvrRenderer->m_TextureRepository.Get(PVR_TEXTURE_CUTSCENE_BG);
			if (pTex != nullptr)
			{
				g_pvrRenderer->DrawQuad(
					pTex,
					0, 
					sYoffset,
					640,
					400,
					PVR::cRenderMgr::LayerBg);
			}

			// Draw the cutscene text, if any.
			g_pvrRenderer->DrawGuiText();

			// Get percentage that's been loaded so far (result is from 0 to 1)
			double dPercentage = (double)ms_pCut->m_lBytesSoFar / (double)ms_pCut->m_lTotalBytes;

			if (dPercentage >= 1.0)
			{
				dPercentage = 1.0;
			}

			int16_t sDistance = (int16_t)((double)PROGRESS_BAR_WIDTH * dPercentage);

			// Save for next time
			ms_pCut->m_sLastDistance = sDistance;

			// Update progress bar area
			g_pvrRenderer->DrawQuad(progBar, 0,
				0.f, 0.f,				
				0.605f * dPercentage,
				1.f,
				int16_t(PROGRESS_BAR_START_X), 
				int16_t(PROGRESS_BAR_START_Y) + sYoffset, 
				sDistance, 
				PROGRESS_BAR_HEIGHT, 
				PVR::cRenderMgr::LayerSprite2);

			// Do an update
			UpdateSystem();

			// Save new time
			ms_pCut->m_lTimeToUpdate = lNow;
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
// EOF
////////////////////////////////////////////////////////////////////////////////
