////////////////////////////////////////////////////////////////////////////////
//
// 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
//
// credits.cpp
// Project: Postal
//
// This module deals with displaying the credits.
//
// History:
//		12/05/96 MJR	Started.
//
//		12/19/96	JMI	Scope error in Credits().  Loop was waiting for terminators
//							one of which was lKey.  The lKey was being set via a call
//							to rspGetKey(&lKey).  But there were two lKeys.  One inside
//							and one outside the loop.  The one on the outside was 
//							evaluated and the one on the inside was being set to the
//							current key.
//
//		02/17/97	JMI	Making this obey rspGetQuitStatus().
//
//		07/05/97 MJR	Changed to RSP_BLACK_INDEX instead of 0.
//
//		07/16/97 BRH	Changed the credits screen to the "What's in the
//							release version of postal preview" screen for the
//							demo.  Took out blah blah blah, yada yada yada.
//
//		08/11/97	JRD	Transforming this module into a device for scolling text
//							which is intended to be used both by credits and by story.
//							It wil operate similar to cutscene in that all of it's
//							assets and memory usage is assumed temporary.  It will still
//							use the shell sak for assets.
//
//		08/11/97	JRD	Created a class for the purpose of accessing generic files
//							from within a sak directory.  Used to read in credits metafile.
//
//		08/20/97 BRH	Added music to credits. Unfortunately, I hardwired the music in
//							the function ScrollPage instead of Credits(), so the mustic will
//							play in any part of the game where text is scrolled.  Man, was I
//							"stupid" when I did that.
//
//		08/21/97	JMI	Changed call to Update() to UpdateSystem() and occurrences
//							of rspUpdateDisplay() to UpdateDisplay().
//
//		08/22/97	JRD	Attempted to make the credits locking safe.
//
//		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.
//
//		08/26/97	JRD	Moved the credit music into the credits function from the 
//							text scrolling function.
//
//		08/27/97	JRD	Moved end sound out of Scroll Page.  Made Scroll page not clear screen
//							at end.  Added special credit ending.
//
//		08/27/97 JRD	Removed text dependency on windows colors.  Made it so if credits
//							are aborted, ending is not shown.
//
//		08/28/97	JRD	Altered Credits() to allow varying the resources so 
//							different music could play in the final scene than
//							in the normal credits.
//
//		09/08/97 JRD	Had Bill add rspGetQuitStaus() to the list of keys and
//							mouse buttons that abort the credits.  Boy, did I look
//							"stupid" when someone hit Alt-F4 during the ending
//							sequence only to get stuck on the credits.  Thanks for
//							fixing that Bill, I owe you another trip to Las Vegas.
//
//		09/17/97 BRH	Added in a conditional compile optionfor the credits so
//							the shareware version will show the coming soon screen
//							instead of the credits.
//
//		06/24/01 MJR	Changed from obsolete macro to SHOW_EXIT_SCREEN as a way
//							to control what gets displayed when the player exits from
//							the game.  Also renamed macro to EXIT_BG and changed the
//							filename to "exit.bmp".
//
////////////////////////////////////////////////////////////////////////////////
// SCROLL PAGE HACK:  CURRENTLY USES COLOR 244 = {128,0,0}, 245 = {255,0,0} 

#include "RSPiX.h"
#include "credits.h"
#include "game.h"
#include "update.h"
#include "SampleMaster.h"
#include "CompileOptions.h"
#include "ORANGE/Parse/SimpleBatch.h"
#include "pvr.h"

// try to hook the sound scaling tables for the palette

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

#define EXIT_BG "credits/exit.bmp"

// default credits parameters... (serve as emergency backups...)
char	g_szBackground[256] = "credits/pile640.bmp";
char	g_szCredits[256] = "credits/credits.txt";
SampleMasterID*	g_psmidMusic = &g_smidCreditsMusak;

#define BG_X			(640 / 2 - pimBackground->m_sWidth / 2)
#define BG_Y			(480 / 2 - pimBackground->m_sHeight / 2)

#define CREDIT_TIME	30000

#define LEFT_X			200
#define SPACE_Y		30

#define MUSAK_START_TIME	0
#define MUSAK_END_TIME		0

#define	MIN_SCOLL_FRAME_MILLI	14		// Cap at 40 fps


extern int wideScreenWidth;

extern PVR::cRenderMgr *g_pvrRenderer;

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

static SampleMaster::SoundInstance	ms_siMusak;

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

////////////////////////////////////////////////////////////////////////////////
// Forward Declarations
////////////////////////////////////////////////////////////////////////////////
#define MAX_BACKGROUNDS 10

class	CBackgroundChange
	{
public:
	RImage*	m_pimPrevBackground;
	RImage*	m_pimNewBackground;
	int16_t	m_sImageCacheIndex;
	char	m_szNewName[64];			// resource name
	bool	m_bActive;					
	// All times in milliseconds...
	int32_t	m_lActivationTime;		// also delay time
	int32_t	m_lFadeOutTime;			//	also in delta time form
	int32_t	m_lBlackTime;				//	also in delta time form
	int32_t	m_lFadeInTime;				//	also in delta time form
	uint8_t	m_TransitionPalette[1024];
	typedef enum { Inactive,FadeOut,Black,FadeIn } BackState;
	BackState	m_eState;
	typedef enum { OnEnter,OnExit } ActivationType;
	ActivationType	m_eType;
		//--------------------------------------------------------
	void Clear()
		{
		m_pimPrevBackground = m_pimNewBackground = NULL;
		m_szNewName[0] = 0;
		m_bActive = false;
		m_lActivationTime = m_lFadeOutTime = m_lBlackTime = m_lFadeInTime = 0;
		m_eState = Inactive;
		m_sImageCacheIndex = -1;
		m_eType = OnEnter;
		}

	CBackgroundChange()
		{
		Clear();
		}

	~CBackgroundChange()
		{
		Clear();
		}
	//--------------------------------------------------------
	void	Activate(RImage* pimCurBack,RImage* pBackgrounds[])
		{			
		// set current times as delta from current:
		m_lActivationTime += rspGetMilliseconds();
		m_lFadeOutTime += m_lActivationTime;
		m_lBlackTime += m_lFadeOutTime;
		m_lFadeInTime += m_lBlackTime;

		m_pimPrevBackground = pimCurBack;
		m_pimNewBackground = pBackgrounds[m_sImageCacheIndex];
		if (!m_pimNewBackground) m_pimNewBackground = pimCurBack;

		m_eState = FadeOut;
		}
	};

class	CTextPhrase
	{
public:

	int16_t	m_sColorIndex;
	int16_t	m_sFontSize;
	int16_t	m_sLocalX;
	int16_t m_sLocalY;
	typedef	enum	{Left, Right, Center} Justify;
	Justify	m_eJust;
	char	m_szText[128];
	CTextPhrase*	m_pNext;
	CTextPhrase*	m_pPrev;
	//------------------------------
	CTextPhrase()
		{
		m_pNext = m_pPrev = NULL;
		m_sFontSize = 0;	// means use previous font size
		}

	~CTextPhrase()
		{
		m_pNext = m_pPrev = NULL;;
		}
	};

// Will actually create an image of itself
//
class	CTextChunk
	{
public:
	int32_t	m_lNumPhrases;
	CTextPhrase m_tHead; // bookends
	CTextPhrase m_tTail;
	int16_t	m_sGlobalTopY;
	int16_t	m_sGlobalBottomY;
//	RImage* m_pimCache;
	CTextChunk*	m_pNext;
	CTextChunk* m_pPrev;
	CBackgroundChange*	m_pChangeBackground;
	//--------------------------
	CTextChunk()
		{
		m_lNumPhrases = 0;
		m_tHead.m_pNext = &m_tTail;
		m_tTail.m_pPrev = &m_tHead;
		m_sGlobalBottomY = -1;
		//m_pimCache = NULL;
		m_pChangeBackground = NULL;
		}

	~CTextChunk()
		{
		CTextPhrase* pCur = m_tHead.m_pNext;
		while (pCur != &m_tTail)
			{
			CTextPhrase* pNext = pCur->m_pNext;
			delete pCur;
			pCur = pNext;
			}

		/*if (m_pimCache) 
			{
			delete m_pimCache;
			m_pimCache = NULL;
			}*/
		}
	//--------------------------
	// Must insert at tail to maintain order
	void	AddPhrase(CTextPhrase* pNew)
		{
		m_lNumPhrases++;
		pNew->m_pNext = &m_tTail;
		pNew->m_pPrev = m_tTail.m_pPrev;
		m_tTail.m_pPrev->m_pNext = pNew;
		m_tTail.m_pPrev = pNew;
		}

	// WILL NOT DELETE!
	void	RemovePhrase(CTextPhrase* pGone)
		{
		m_lNumPhrases--;
		pGone->m_pPrev->m_pNext = pGone->m_pNext;
		pGone->m_pNext->m_pPrev = pGone->m_pPrev;

		pGone->m_pNext = pGone->m_pPrev = NULL;
		}
	

	void	RenderChunk(int16_t sW,RPrint* pPrint, int16_t xOffset, int16_t yOffset)
		{		
		CTextPhrase* pCur = m_tHead.m_pNext;	
		while (pCur != &m_tTail)
			{
			
						
			if (pCur->m_sColorIndex == 245) 
			{
				pPrint->SetColorRGB(255, 0, 0);			
				pPrint->SetShadowColorRGB(128, 0, 0);			
			}						
			else	
			{
				pPrint->SetColorRGB(128, 0, 0);
			}
			
		
			pPrint->offsetX = xOffset;
			pPrint->offsetY = yOffset;

			if (pCur->m_sFontSize) pPrint->SetFont(pCur->m_sFontSize);

			int16_t sTabX = pCur->m_sLocalX;
			if (sTabX < 0) sTabX += sW;	// from the right...

			// each justification needed to select the RPrint cell to use:
			// RPrint doesn't seem to be justifying correctly...
			// I will attempt some of my own:
			//todo??		
			int wideScreenOffset = (640 - 640);	
			switch (pCur->m_eJust)
				{	
				int16_t sRadius;

				//--------------------------- Left Justify
				case CTextPhrase::Left: 
					pPrint->SetJustifyLeft(); 
					pPrint->SetColumn(0 ,0,sW,320);
					//pPrint->SetColumn(0 ,0,sW,m_pimCache->m_sHeight);
					//pPrint->printPVR(sTabX ,pCur->m_sLocalY,"%s",pCur->m_szText);					
					pPrint->printPVR(50,pCur->m_sLocalY,"%s", pCur->m_szText);
				break;
				//--------------------------- Right Justify
				case CTextPhrase::Right: 
					pPrint->SetJustifyRight(); 
					pPrint->SetColumn(0 ,0, sW,320);
					//pPrint->SetColumn(0,0,sTabX + 1 ,320);
//					pPrint->SetColumn(0,0,sTabX + 1 + wideScreenOffset/2,m_pimCache->m_sHeight);
					// trick print into thinking it's a new line!
					//pPrint->printPVR(-1,pCur->m_sLocalY,"%s",pCur->m_szText);
					pPrint->printPVR(200,pCur->m_sLocalY,"%s", pCur->m_szText);
				break;
				//--------------------------- Center Justify (about tab)
				case CTextPhrase::Center: 
					pPrint->SetJustifyCenter(); 
					sRadius = MIN(sTabX,int16_t(sW - sTabX));
					pPrint->SetColumn(sTabX - sRadius,0,	sRadius<<1, 320);
					// pPrint->SetColumn(sTabX - sRadius + wideScreenOffset,0,	sRadius<<1, m_pimCache->m_sHeight);
					// trick print into thinking it's a new line!
					pPrint->printPVR(-1,pCur->m_sLocalY,"%s",pCur->m_szText);

				break;
				}

			
			// undo justification:
			pPrint->SetColumn(0,0,sW, 320);		
			//---------------------------------
			pCur = pCur->m_pNext;
			}		
		}
	
	};

char*	pct = g_szCredits;

// The highest level:
class CScrollMaster
	{
public:
	int32_t			m_lGlobalHeight;
	int32_t			m_lCurrentTopY;
	int32_t			m_lCurrentBottomY;
	int32_t			m_lTotalChunks;
	//CTextChunk*	m_pTopActiveChunk;
	//CTextChunk*	m_pBottomActiveChunk;
	int32_t			m_lActivationTime;
	RRect			m_rDisplay;
	double		m_dScrollRate;	// screens/sec

	CTextChunk	m_cHead;	// bookends:
	CTextChunk	m_cTail;

	int16_t	m_sNumBackgrounds;
	char	m_szBackgroundNames[MAX_BACKGROUNDS][64];
	RImage*	m_pimBackgrounds[MAX_BACKGROUNDS];

	CBackgroundChange* m_pCurSceneChange;
	//--------------------------------------
	CScrollMaster()
		{
		m_lGlobalHeight = 0;
		m_lCurrentBottomY = 0;
		m_lTotalChunks = 0;
		//m_pTopActiveChunk = NULL;
		//m_pBottomActiveChunk = NULL;
		m_lActivationTime = 0;
		m_cHead.m_pNext = &m_cTail;
		m_cTail.m_pPrev = &m_cHead;
		//todo??
		m_rDisplay = RRect(0,40,640,360);
		m_dScrollRate = 0.1;	// 100 seconds per screen
		m_sNumBackgrounds = 0;
		m_pCurSceneChange = NULL;
		}

	~CScrollMaster()
		{
		CTextChunk* pCur = m_cHead.m_pNext;
		while (pCur != &m_cTail)
			{
			CTextChunk* pNext = pCur->m_pNext;
			delete pCur;
			pCur = pNext;
			}

		// release all of the backgrounds:
		int16_t i;

		for (i = 0; i < m_sNumBackgrounds;i++)
			{
			if (m_pimBackgrounds[i]) g_resmgrShell.Release(m_pimBackgrounds[i]);
			m_pimBackgrounds[i] = NULL;
			}
		}

	void	Configure(double dScrollRate,RRect* prWindow = NULL)
		{
		if (dScrollRate > 0.0) m_dScrollRate = dScrollRate;
		if (prWindow) m_rDisplay = *prWindow;
		}

	//--------------------------------
	void	AddChunk(CTextChunk* pNew)
		{
		m_lTotalChunks++;
		pNew->m_pNext = &m_cTail;
		pNew->m_pPrev = m_cTail.m_pPrev;
		m_cTail.m_pPrev->m_pNext = pNew;
		m_cTail.m_pPrev = pNew;

		m_lGlobalHeight = pNew->m_sGlobalBottomY + 1;
		}
	//--------------------------------
	void	Start(RPrint* pPrint)
	{
		// Activate first chunk:
		ASSERT(m_lTotalChunks >= 1);				
		m_lActivationTime = rspGetMilliseconds();
	}

	// will return FAILURE if scroll is over!
	int16_t Update(RPrint* pPrint)
		{
		// calculate current global scroll location:
		m_lCurrentBottomY = int32_t(double(rspGetMilliseconds() - m_lActivationTime)
			* m_dScrollRate * m_rDisplay.sH / 1000.0);
		m_lCurrentTopY = (m_lCurrentBottomY - m_rDisplay.sH);

		if (m_lCurrentTopY > m_lGlobalHeight) return FAILURE; // scroll over!

		CTextChunk *chunk = &m_cHead;
		do {
			int16_t yPosTop = chunk->m_sGlobalTopY - m_lCurrentTopY + m_rDisplay.sY;
			int16_t yPosBottom = chunk->m_sGlobalBottomY - m_lCurrentBottomY + m_rDisplay.sY;
			int16_t chunkHeight = chunk->m_sGlobalBottomY - chunk->m_sGlobalTopY;
			if (yPosTop >= (0 - chunkHeight) && yPosBottom <= 480)
			{
				chunk->RenderChunk(m_rDisplay.sW, pPrint, m_rDisplay.sX, chunk->m_sGlobalTopY - m_lCurrentTopY + m_rDisplay.sY);
			}											
			chunk = chunk->m_pNext;
		} while(chunk != &m_cTail) ;
	
		return SUCCESS;
		}
	
	//-------------------------------------------------------------
	// returns the index number for the resource bmp:
	int16_t	AddBackground(char* pszName)
		{		
		ASSERT(pszName);
		ASSERT(m_sNumBackgrounds < MAX_BACKGROUNDS);

		strcpy(m_szBackgroundNames[m_sNumBackgrounds],pszName);
		m_pimBackgrounds[m_sNumBackgrounds] = NULL;

		return m_sNumBackgrounds++;
		}
	};

extern int16_t sLoaded;

////////////////////////////////////////////////////////////////////////////////
// This is cheesy, but right now I'm using a global stream to load into.
////////////////////////////////////////////////////////////////////////////////
//CScrollMaster*	gpCurStream = NULL;

// For Res managing an ANSI file:
class	CFileTextInput
	{
public:

	RBatch	m_bf;
	//-----------------------
	CFileTextInput() 
		{
		m_pStream = NULL;
		};

	~CFileTextInput() 
		{
		m_bf.clear();	// don't let it close the file!
		if (m_pStream) delete m_pStream;
		};
	//-----------------------
	int16_t	ParseTextInput(FILE* fp);
	//-----------------------
	int16_t	Load(RFile* pFile) // so res manager can hook it!
		{
		FILE* fp = pFile->m_fs;
		//--------------------- do my load:

		ParseTextInput(fp);

		return SUCCESS;
		}

	int16_t	Load(FILE* fp) // so res manager can hook it!
		{
		//--------------------- do my load:

		ParseTextInput(fp);

		return SUCCESS;
		}

	CScrollMaster* m_pStream;
	};

////////////////////////////////////////////////////////////////////////////////
//	Tokenizing the script file into absolute strips:
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
//  ASSUME A VALID FILE STREAM
//
//	 This will parse and tokenize the incoming text...
//  It will not actually render it.
//
////////////////////////////////////////////////////////////////////////////////
int16_t	CFileTextInput::ParseTextInput(FILE* fp)
	{
	if (m_pStream)
		{
		TRACE("CFileTextInput::ParseTextInput: Stream in progress.\n");
		return FAILURE;
		}
	else
		m_pStream = new CScrollMaster;	// needs to be freed!

	// We need to be a little low level so we can force feed a 
	// file pointer to it.
	m_bf.clear();
	m_bf.m_fp = fp;
	m_bf.configure(" \t,;=({[",";/)}]",'`','/');
	char* pszToken;
	//gpCurStream = new CTextStream;

	int16_t sGlobalY = 0;
	int16_t sLocalY = 0;
	int16_t sCurStripTop = 0;
	int16_t	sMaxH = 0;
	bool	bNewChunk = true;
 
	int16_t sCurFontSize = 0;
	int16_t	sCurTabX = 0;
	CTextPhrase::Justify eCurJust = CTextPhrase::Left;
	int16_t sCurColor = 255;

	CTextChunk*	pChunk = new CTextChunk;
	CTextPhrase* pCurPhrase = new CTextPhrase;

	pChunk->m_sGlobalTopY = sCurStripTop;
	pChunk->m_sGlobalBottomY = sCurStripTop;

	while ((pszToken = m_bf.NextToken()) != NULL)
		{
		//TRACE("TOKEN = '%s'\n",pszToken);

		//-----------------------------------------------------
		if (!strcmp(pszToken,"color"))
			{
			sCurColor = atoi(m_bf.NextToken());

			continue;
			}
		//-----------------------------------------------------
		if (!strcmp(pszToken,"size"))
			{
			pCurPhrase->m_sFontSize = 0;

			int16_t sNewSize = atoi(m_bf.NextToken());

			// If we are starting a new chunk, we MUST give a font
			// size!

			if (bNewChunk || (sCurFontSize != sNewSize))
				{
				pCurPhrase->m_sFontSize = sNewSize;
				}

			sCurFontSize = sNewSize;
			bNewChunk = false;

			sMaxH = MAX(sMaxH,int16_t(sLocalY + sNewSize));

			continue;
			}
		//-----------------------------------------------------
		if (!strcmp(pszToken,"back"))
			{
			sLocalY -= atoi(m_bf.NextToken());

			if (sLocalY < 0)
				{
				TRACE("ERROR - you backed up above the strip!\n");
				sLocalY = 0;
				}
			continue;
			}
		//-----------------------------------------------------
		if (!strcmp(pszToken+1,"tab"))
			{
			pCurPhrase->m_sLocalX = sCurTabX = atoi(m_bf.NextToken());

			switch	(*pszToken)
				{
				case 'l': eCurJust = pCurPhrase->m_eJust = CTextPhrase::Left; break;
				case 'r': eCurJust = pCurPhrase->m_eJust = CTextPhrase::Right; break;
				case 'c': eCurJust = pCurPhrase->m_eJust = CTextPhrase::Center; break;
				}
			continue;
			}
		//-----------------------------------------------------
		if (!strcmp(pszToken,";")) // means new line (& should benew phrase)
			{
			sLocalY += sCurFontSize;
			sMaxH = MAX(sMaxH,int16_t(sLocalY + sCurFontSize));

			continue;
			}
		//-----------------------------------------------------
		if (!strcmp(pszToken,"end")) //  if in the sak, no EOF!
			{

			break; // done
			}
		//-----------------------------------------------------
		if (!strcmp(pszToken,"skip")) // signals a new chunk!
			{
			//TRACE("New chunk!\n");
			bNewChunk = true;

			sMaxH = MAX(sMaxH,int16_t(sLocalY + sCurFontSize));

			// Figure out how big chunk was:
			sGlobalY += MAX(sMaxH,sLocalY);
			pChunk->m_sGlobalBottomY = sGlobalY - 1;

			if (pChunk->m_sGlobalBottomY > pChunk->m_sGlobalTopY)
				{
				m_pStream->AddChunk(pChunk);
				}

			// create a new chunk
			int16_t sVal = atoi(m_bf.NextToken());
			sGlobalY += sVal;
			pChunk = new CTextChunk;
			pChunk->m_sGlobalTopY = pChunk->m_sGlobalBottomY = sGlobalY;
			sMaxH = 0;	// resetting stuff like this!
			sLocalY = 0;

			continue;
			}
		//-----------------------------------------------------
		if (!strcmp(pszToken,"scene")) // describe a scene change:
			{
			// first, parse through it all, then validate the information:

			// Good standards: (in milliseconds)
			int16_t sDelay = 0,sFadeInTime = 1000,sFadeOutTime = 1000;
			int16_t sBlackTime = 0, sOnExit = FALSE;
			char	szName[64] = {0,};

			// The order doesn't matter - go until ')'
			pszToken = m_bf.NextToken();

			while (strcmp(pszToken,")")) // until we hit a right parenthesis
				{
				if (!strcmp(pszToken,"delay"))
					{
					sDelay = atoi(m_bf.NextToken());
					}
				else if (!strcmp(pszToken,"black"))
					{
					sBlackTime = atoi(m_bf.NextToken());
					}
				else if (!strcmp(pszToken,"out"))
					{
					sFadeOutTime = atoi(m_bf.NextToken());
					}
				else if (!strcmp(pszToken,"in"))
					{
					sFadeInTime = atoi(m_bf.NextToken());
					}
				else if (!strcmp(pszToken,"exit"))
					{
					sOnExit = TRUE;
					}
				else if (!strcmp(pszToken,"name"))
					{
					strcpy(szName,m_bf.NextToken());
					}

				pszToken = m_bf.NextToken();
				}
			//........... Now put the data into a useful form 
			// and log it:
			CBackgroundChange* pNew = new CBackgroundChange;
			pNew->m_lActivationTime = sDelay;
			pNew->m_lFadeOutTime = sFadeOutTime;
			pNew->m_lBlackTime = sBlackTime;
			pNew->m_lFadeInTime = sFadeInTime;

			strcpy(pNew->m_szNewName,szName);
			if (sOnExit) pNew->m_eType = CBackgroundChange::OnExit;

			// Log into the stream:
			pNew->m_sImageCacheIndex = m_pStream->AddBackground(szName);

			// Add into chunk:
			ASSERT(!pChunk->m_pChangeBackground);

			pChunk->m_pChangeBackground = pNew;

			continue;
			}

		//-----------------------------------------------------
		// Assume default case is text to be printed, which 
		// also signals the creation of a new phrase...
		//-----------------------------------------------------
		if (*pCurPhrase->m_szText)
			{
			TRACE("Error - already text\n");
			}

		// Set latest states
		pCurPhrase->m_sLocalY = sLocalY;
		pCurPhrase->m_sColorIndex = sCurColor;

		strcpy(pCurPhrase->m_szText,pszToken);
		// Create a brand new phrase:

		pChunk->AddPhrase(pCurPhrase);
		CTextPhrase* pNewPhrase = new CTextPhrase;
		*pNewPhrase = *pCurPhrase;	// struct copy
		*pNewPhrase->m_szText = 0;
		pCurPhrase = pNewPhrase;

		continue;
		}

	if (pChunk) 
		{
		// Figure out how big the chunk was
		sGlobalY += MAX(sMaxH,sLocalY);
		pChunk->m_sGlobalBottomY = sGlobalY - 1;

		m_pStream->AddChunk(pChunk);
		}

	if (pCurPhrase)
		{
		delete pCurPhrase;
		pCurPhrase = NULL;
		}

	return SUCCESS;
	}

////////////////////////////////////////////////////////////////////////////////
//
//	General scrolling text screens: Returns FAILURE if assets couldn't load
// OR returns FAILURE if use aborts!
//
//	All resources are assumed from the shell sak.
//
////////////////////////////////////////////////////////////////////////////////
int16_t	ScrollPage(PVR::cTexture *bg, char* pszScrollScript,double dScrollRate,RRect *prWindow)
	{
	// Try to load resources:
	int16_t sResult;

	// Load text script:
	CFileTextInput*	pScript = NULL;	// must free it myself!

	// Try to override with our own file, because I'm too lazy to figure out how to regenerate .sak files.
	FILE *nonsak = fopen(FindCorrectFile("res/credits.txt", "rb"), "rb");
	if (nonsak != NULL)
		{
		pScript = new CFileTextInput;
		pScript->Load(nonsak);
		}

	if (pScript == NULL)
		{
		sResult = rspGetResource(&g_resmgrShell, pszScrollScript, &pScript);
		if (sResult != SUCCESS)
			{
			// try another sak:
			sResult = rspGetResource(&g_resmgrSamples, pszScrollScript, &pScript);
			if (sResult != SUCCESS)
				{
				TRACE("ScrollPage: Couldn't load script %s\n",pszScrollScript);
				delete bg;
			//	g_resmgrShell.Release(pimBackground); // dom't need it anymore
				return FAILURE; // couldn't load background
				}
			}
		}

	g_pvrRenderer->m_pGuiTextString->Flush();

	// Set standard font...
	RPrint print;	
	print.SetFont(24, &g_fontPostal);
	print.SetColor(255, 0 , 0);
	print.SetEffectAbs(RPrint::SHADOW_X,1);
	print.SetEffectAbs(RPrint::SHADOW_Y,1);

	pScript->m_pStream->Configure(dScrollRate,prWindow);
	pScript->m_pStream->Start(&print);

	// Wait until user input
	int32_t lKey = 0;
	int16_t sButtons = 0;
	int16_t sJoyPress = 0;

//******************  STATISTICAL ANALYSIS!
/*
long lTimeCount[256] = {0,}; // bucket sort
*/
	int32_t lRunningTime,lPrevTime;
	lRunningTime = lPrevTime = rspGetMilliseconds();
	g_pvrRenderer->FlushBatchedDrawQueues();	
	int16_t sOffsetY = ((g_GameSettings.m_cCutSceneUnderscan == true)  ? -20 : 0);

	while ( (pScript->m_pStream->Update(&print) == SUCCESS) && !lKey && !sButtons && !(rspGetQuitStatus()) && !sJoyPress)
		{
			
		// Show title image.
		g_pvrRenderer->DrawQuad(bg, 0, sOffsetY,640,480, PVR::cRenderMgr::LayerBg);
		g_pvrRenderer->DrawGuiText();
		
		g_pvrRenderer->DrawQuad(bg, 0, 0, 0, 1.f, .312f, 0, sOffsetY, 256, 80, PVR::cRenderMgr::LayerAlpha2);
		g_pvrRenderer->DrawQuad(bg, 1, 0, 0, 1.f, .312f, 256, sOffsetY, 256, 80, PVR::cRenderMgr::LayerAlpha2);
		g_pvrRenderer->DrawQuad(bg, 2, 0, 0, 1.f, .312f, 512, sOffsetY, 256, 80, PVR::cRenderMgr::LayerAlpha2);
		g_pvrRenderer->DrawQuad(bg, 3, 0, .562f, 1.f, .871f, 0, 400 + sOffsetY, 256, 80, PVR::cRenderMgr::LayerAlpha2);
		g_pvrRenderer->DrawQuad(bg, 4, 0, .562f, 1.f, .871f, 256, 400 + sOffsetY, 256, 80, PVR::cRenderMgr::LayerAlpha2);
		g_pvrRenderer->DrawQuad(bg, 5, 0, .562f, 1.f, .871f, 512, 400 + sOffsetY, 256, 80, PVR::cRenderMgr::LayerAlpha2);
	
		if (sOffsetY < 0)
		{
			g_pvrRenderer->DrawQuad(
				uint32_t(PACK_ARGB8888(255, 0, 0, 0)),
				int16_t(0), 
				int16_t(460),
				uint16_t(640), 20,
				PVR::cRenderMgr::LayerAlpha2
			);
		}		

		UpdateSystem();

		g_pvrRenderer->FlushBatchedDrawQueues();
		g_pvrRenderer->m_pGuiTextString->Flush();

		// Get key and button inputs			
		sJoyPress = (IsMapleInputButtonPressed() 
		|| IsMapleInputButtonPressed(1)
		|| IsMapleInputButtonPressed(2)
		|| IsMapleInputButtonPressed(3));

		// Restrict the frame rate to MAX_SCOLL_FRAME_MILLI
		while ( (lRunningTime - lPrevTime) < MIN_SCOLL_FRAME_MILLI)
			{
			lRunningTime = rspGetMilliseconds();
			}

		lPrevTime = lRunningTime;
		}

	if (nonsak != NULL)
		{
		delete pScript;
		fclose(nonsak);
		}
	else
		{
		g_resmgrShell.Release(pScript);
		}

	// Clean Up:

	// Clear and keyboard events
	//rspClearKeyEvents();
		
	if (lKey || sButtons || sJoyPress || rspGetQuitStatus()) return FAILURE;

	return SUCCESS;
	}

////////////////////////////////////////////////////////////////////////////////
//
// Display the credits - now with variable input
//
////////////////////////////////////////////////////////////////////////////////

// Returns 0 if successfull, non-zero otherwise
int16_t Credits(SampleMasterID* pMusic,
							char* pszBackground,
							char* pszCredits)					
{
	int16_t sResult = 0;

	SampleMasterID*	psmidMusic;
	char	szBackground[256];
	char	szCredits[256];

	AbortAllSamples();

	// Set defaults:
	psmidMusic = g_psmidMusic;
	strcpy(szBackground,g_szBackground);
	strcpy(szCredits,g_szCredits);

	// Set input parameters:
	if (pMusic) psmidMusic = pMusic;
	if (pszBackground) strcpy(szBackground,pszBackground);
	if (pszCredits) strcpy(szCredits,pszCredits);

	PVR::cTextureFactory::SetResMgr(&g_resmgrShell);
	PVR::cTexture *bg = PVR::cTextureFactory::FromKmg(szBackground, 3, 2);

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

	// Start credits music
	PlaySample(										// Returns nothing.
														// Does not fail.
		*psmidMusic,								// In:  Identifier of sample you want played.
		SampleMaster::Unspecified,				// In:  Sound Volume Category for user adjustment
		255,											// In:  Initial Sound Volume (0 - 255)
		&ms_siMusak,								// Out: Handle for adjusting sound volume
		NULL,											// Out: Sample duration in ms, if not NULL.
		MUSAK_START_TIME,							// In:  Where to loop back to in milliseconds.
														//	-1 indicates no looping (unless m_sLoop is
														// explicitly set).
		MUSAK_END_TIME,							// 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
	
	//------------------------------------------------------------------------------
	// Begin scrolling loop....
	//todo??
    RRect rect(0,80,640,320);

	if (ScrollPage(bg, szCredits,0.12,&rect) != SUCCESS)
		{
		// USER aborted!

		// Stop the musak
		if (ms_siMusak)
			{
			// Don't just allow it to finish.
			StopLoopingSample(ms_siMusak);
			// Cut it off.
			AbortSample(ms_siMusak);
			// Clear.
			ms_siMusak	= 0;
			// Play final sample that completes the cut off sound. ***
			}
								
		g_pvrRenderer->m_pGuiTextString->Flush();
		g_pvrRenderer->m_fGlobalRScale = 1.0f;
		g_pvrRenderer->m_fGlobalGScale = 1.0f;		
		g_pvrRenderer->m_fGlobalBScale = 1.0f;
		g_pvrRenderer->ClearScreen(true);						
		UpdateSystem();		
		delete bg;
		return SUCCESS;
		}

	// fade out the musak: cross fade the laughter
	SampleMaster::SoundInstance siLaughter;
	
	// Start silent laughter looping:
	PlaySample(
		g_smidDemonLaugh2,
		SampleMaster::Unspecified,
		0,
		&siLaughter,
		NULL,
		0,
		0,
		true);

	int32_t	lTimeToFade = (pMusic == &g_smidFinalSceneCredits ? 15000 : 3000);
	int32_t	lStartTime = rspGetMilliseconds();

	// Copy the palette:
	uint8_t PaletteCopy[256 * 3] = {0,};
	
	//rspGetPaletteEntries(10,236,PaletteCopy+30,PaletteCopy + 31,PaletteCopy + 32,3);

	//====================================================================
	int32_t lCurTime;

	while ( (lCurTime = rspGetMilliseconds() - lStartTime) < lTimeToFade)
		{

		int16_t sByteLevel = int16_t((255.0 * lCurTime) / lTimeToFade);
		if (sByteLevel > 255) sByteLevel = 255;

		// Update the sound volumes:
		SetInstanceVolume(ms_siMusak,255 - sByteLevel);
	
		if (siLaughter)
		SetInstanceVolume(siLaughter,sByteLevel);
	
		g_pvrRenderer->m_fGlobalRScale = 1.0f * (float(255 - sByteLevel)/256.f);
		g_pvrRenderer->m_fGlobalGScale = 1.0f * (float(255 - sByteLevel)/256.f);
		g_pvrRenderer->m_fGlobalBScale = 1.0f * (float(255 - sByteLevel)/256.f);

		g_pvrRenderer->FlushBatchedDrawQueues();
		g_pvrRenderer->DrawQuad(bg, 0,0,640,480, PVR::cRenderMgr::LayerBg);
		UpdateSystem();
		}

	//====================================================================

	// Stop the musak
	if (ms_siMusak)
		{
		// Don't just allow it to finish.
		StopLoopingSample(ms_siMusak);
		}

	// Stop the musak
	if (siLaughter)
		{
		// Don't just allow it to finish.
		StopLoopingSample(siLaughter);
		}

	// Stop the musak
	if (ms_siMusak)
		{
		// Cut it off.
		AbortSample(ms_siMusak);
		// Clear.
		ms_siMusak	= 0;
		// Play final sample that completes the cut off sound. ***
		}

	if (siLaughter)
		{
		while (IsSamplePlaying(siLaughter) == true)
			{
			UpdateSystem();
			}
		}

	//===========================================================================
	// Stop the musak
	if (siLaughter)
		{
		// Cut it off.
		AbortSample(siLaughter);
		// Clear.
		siLaughter	= 0;
		// Play final sample that completes the cut off sound. ***
		}
			
	g_pvrRenderer->m_pGuiTextString->Flush();
	g_pvrRenderer->m_fGlobalRScale = 1.0f;
	g_pvrRenderer->m_fGlobalGScale = 1.0f;
	g_pvrRenderer->m_fGlobalBScale = 1.0f;
	g_pvrRenderer->ClearScreen(true);					
	UpdateSystem();	
	
	delete bg;
	
	return SUCCESS;
}



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