//
// Copyright 2020 Electronic Arts Inc.
//
// TiberianDawn.DLL and RedAlert.dll and corresponding 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.

// TiberianDawn.DLL and RedAlert.dll and corresponding source code is distributed 
// in the hope that it will be useful, but with permitted additional restrictions 
// under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
// distributed with this program. You should have received a copy of the 
// GNU General Public License along with permitted additional restrictions 
// with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection

/* $Header:   F:\projects\c&c\vcs\code\theme.cpv   2.18   16 Oct 1995 16:51:10   JOE_BOSTIC  $ */
/***********************************************************************************************
 ***             C O N F I D E N T I A L  ---  W E S T W O O D   S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : Command & Conquer                                            *
 *                                                                                             *
 *                    File Name : THEME.CPP                                                    *
 *                                                                                             *
 *                   Programmer : Joe L. Bostic                                                *
 *                                                                                             *
 *                   Start Date : August 14, 1994                                              *
 *                                                                                             *
 *                  Last Update : May 29, 1995 [JLB]                                           *
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 *   ThemeClass::Scan -- Scans all scores for availability.                                    *
 *   ThemeClass::AI -- Process the theme engine and restart songs.                             *
 *   ThemeClass::Base_Name -- Fetches the base filename for the theme specified.               *
 *   ThemeClass::From_Name -- Determines theme number from specified name.                     *
 *   ThemeClass::Full_Name -- Retrieves the full score name.                                   *
 *   ThemeClass::Is_Allowed -- Checks to see if the specified theme is legal.                  *
 *   ThemeClass::Next_Song -- Calculates the next song number to play.                         *
 *   ThemeClass::Play_Song -- Starts the specified song play NOW.                              *
 *   ThemeClass::Queue_Song -- Queues the song to the play queue.                              *
 *   ThemeClass::Still_Playing -- Determines if music is still playing.                        *
 *   ThemeClass::Stop -- Stops the current theme from playing.                                 *
 *   ThemeClass::ThemeClass -- Default constructor for the theme manager class.                *
 *   ThemeClass::Theme_File_Name -- Constructs a filename for the specified theme.             *
 *   ThemeClass::Track_Length -- Caclulates the length of the song (in seconds).               *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include	"function.h"

#include	"theme.h"


/*
**	These are the actual filename list for the theme sample files.
*/
ThemeClass::ThemeControl ThemeClass::_themes[THEME_COUNT] = {
	{"AIRSTRIK",	TXT_THEME_AIRSTRIKE,	0,		200,	false,	false,false,true},
	{"80MX226M",	TXT_THEME_80MX,		0,		248,	false,	false,false,true},
	{"CHRG226M",	TXT_THEME_CHRG,		0,		256,	true,		false,false,true},
	{"CREP226M",	TXT_THEME_CREP,		0,		222,	true,		false,false,true},
	{"DRIL226M",	TXT_THEME_DRIL,		0,		272,	true,		false,false,true},
	{"DRON226M",	TXT_THEME_DRON,		0,		275,	true,		false,false,true},
	{"FIST226M",	TXT_THEME_FIST,		0,		212,	true,		false,false,true},
	{"RECN226M",	TXT_THEME_RECON,		0,		261,	true,		false,false,true},
	{"VOIC226M",	TXT_THEME_VOICE,		0,		306,	true,		false,false,true},
	{"HEAVYG",		TXT_THEME_HEAVYG,		0,		180,	true,		false,false,true},
	{"J1",			TXT_THEME_J1,			4,		187,	true,		false,false,true},
//	{"J1",			TXT_THEME_J1,			4,		187,	false,	false,false,true},
	{"JDI_V2",		TXT_THEME_JDI_V2,		5,		183,	true,		false,false,true},
	{"RADIO",		TXT_THEME_RADIO,		6,		183,	true,		false,false,true},
	{"RAIN",			TXT_THEME_RAIN,		7,		156,	true,		false,false,true},
	{"AOI",			TXT_THEME_AOI,			0,		168,	true,		true,	false,true},
	{"CCTHANG",		TXT_THEME_CCTHANG,	12,	193,	true,		false,false,true},
	{"DIE",			TXT_THEME_DIE,			11,	162,	false,	false,false,true},
	{"FWP",			TXT_THEME_FWP,			10,	53,	true,		false,false,true},
	{"IND",			TXT_THEME_IND,			1,		175,	true,		false,false,true},
	{"IND2",			TXT_THEME_IND2,		1,		38,	true,		false,false,true},
	{"JUSTDOIT",	TXT_THEME_JUSTDOIT,	9,		142,	true,		false,false,true},
	{"LINEFIRE",	TXT_THEME_LINEFIRE,	8,		125,	true,		false,false,true},
	{"MARCH",		TXT_THEME_MARCH,		7,		157,	true,		false,false,true},
	{"TARGET",		TXT_THEME_TARGET,		0,		173,	true,		false,false,true},
	{"NOMERCY",		TXT_THEME_NOMERCY,	2,		204,	true,		false,false,true},
	{"OTP",			TXT_THEME_OTP,			3,		182,	true,		false,false,true},
	{"PRP",			TXT_THEME_PRP,			4,		211,	true,		false,false,true},
	{"ROUT",			TXT_THEME_ROUT,		12,	121,	false,	true,	false,true},
	{"HEART",		TXT_THEME_HEART,		5,		206,	false,	true,	false,true},
	{"STOPTHEM",	TXT_THEME_STOPTHEM,	0,		190,	true,		false,false,true},
	{"TROUBLE",		TXT_THEME_TROUBLE,	6,		191,	true,		true,	false,true},
	{"WARFARE",		TXT_THEME_WARFARE,	0,		182,	true,		false,false,true},
	{"BEFEARED",	TXT_THEME_BEFEARED,	13,	164,	false,	true,	false,true},
	{"I_AM",			TXT_THEME_IAM,			6,		161,	false,	false,false,true},
	{"WIN1",			TXT_THEME_WIN1,		0,		41,	false,	true,	true,true},
	{"MAP1",			TXT_THEME_WIN1,		0,		61,	false,	false,true,true},
	{"VALKYRIE",	TXT_THEME_VALK,		0,		306,	false,	false,true,true},
};


/***********************************************************************************************
 * ThemeClass::Base_Name -- Fetches the base filename for the theme specified.                 *
 *                                                                                             *
 *    This routine is used to retrieve a pointer to the base filename for the theme            *
 *    specified.                                                                               *
 *                                                                                             *
 * INPUT:   theme -- The theme number to convert into a base filename.                         *
 *                                                                                             *
 * OUTPUT:  Returns with a pointer to the base filename for the theme specified. If the        *
 *          theme number is invalid, then a pointer to "No Theme" is returned instead.         *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   05/29/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
char const * ThemeClass::Base_Name(ThemeType theme) const
{
	if (theme != THEME_NONE) {
		return(_themes[theme].Name);
	}
	return("No theme");
}


/***********************************************************************************************
 * ThemeClass::ThemeClass -- Default constructor for the theme manager class.                  *
 *                                                                                             *
 *    This is the default constructor for the theme class object.                              *
 *                                                                                             *
 * INPUT:   none                                                                               *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/16/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
ThemeClass::ThemeClass(void)
{
	Current = -1;
	Score = THEME_NONE;
	Pending = THEME_NONE;
}


/***********************************************************************************************
 * ThemeClass::Full_Name -- Retrieves the full score name.                                     *
 *                                                                                             *
 *    This routine will fetch and return with a pointer to the full name of the theme          *
 *    specified.                                                                               *
 *                                                                                             *
 * INPUT:   theme -- The theme to fetch the full name for.                                     *
 *                                                                                             *
 * OUTPUT:  Returns with a pointer to the full name for this score. This pointer may point to  *
 *          EMS memory.                                                                        *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/16/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
char const * ThemeClass::Full_Name(ThemeType theme) const
{
	if (theme != THEME_NONE) {
		return(Text_String(_themes[theme].Fullname));
	}
	return(NULL);
}


/***********************************************************************************************
 * ThemeClass::AI -- Process the theme engine and restart songs.                               *
 *                                                                                             *
 *    This is a maintenance function that will restart an appropriate theme if the current one *
 *    has finished. This routine should be called frequently.                                  *
 *                                                                                             *
 * INPUT:   none                                                                               *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   09/08/1994 JLB : Created.                                                                 *
 *   01/23/1995 JLB : Picks new song just as it is about to play it.                           *
 *=============================================================================================*/
void ThemeClass::AI(void)
{
	if (SampleType && !Debug_Quiet) {
		if (ScoresPresent && Options.ScoreVolume && !Still_Playing() && Pending != THEME_NONE) {

			/*
			**	If the pending song needs to be picked, then pick it now.
			*/
			if (Pending == THEME_PICK_ANOTHER) {
				Pending = Next_Song(Score);
			}

			/*
			**	Start the song playing and then flag it so that a new song will
			**	be picked when this one ends.
			*/
			Play_Song(Pending);
			Pending = THEME_PICK_ANOTHER;
		}
		Sound_Callback();
	}
}


/***********************************************************************************************
 * ThemeClass::Next_Song -- Calculates the next song number to play.                           *
 *                                                                                             *
 *    use this routine to figure out what song number to play. It examines the option settings *
 *    for repeat and shuffle so that it can return the correct value.                          *
 *                                                                                             *
 * INPUT:   theme -- The origin (last) index. The new value is related to this for all but     *
 *                   the shuffling method of play.                                             *
 *                                                                                             *
 * OUTPUT:  Returns with the song number for the next song to play.                            *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/16/1995 JLB : Created.                                                                 *
 *   01/19/1995 JLB : Will not play the same song twice when in shuffle mode.                  *
 *=============================================================================================*/
ThemeType ThemeClass::Next_Song(ThemeType theme)
{
	if (theme == THEME_NONE) {
		theme = Next_Song(THEME_PICK_ANOTHER);
	} else {
		if (theme == THEME_PICK_ANOTHER || (!_themes[theme].Repeat && !Options.IsScoreRepeat)) {
			if (Options.IsScoreShuffle) {

				/*
				**	Shuffle the theme, but never pick the same theme that was just
				**	playing.
				*/
				ThemeType newtheme;
				do {
					newtheme = Sim_Random_Pick(THEME_FIRST, THEME_LAST);
				} while(newtheme == theme || !Is_Allowed(newtheme));
				theme = newtheme;

			} else {

				/*
				**	Sequential score playing.
				*/
				do {
					theme++;
					if (theme > THEME_LAST) {
						theme = THEME_FIRST;
					}
				} while(!Is_Allowed(theme));
			}
		}
	}
	return(theme);
}


/***********************************************************************************************
 * ThemeClass::Queue_Song -- Queues the song to the play queue.                                *
 *                                                                                             *
 *    This routine will cause the current song to fade and the specified song to start. This   *
 *    is the normal and friendly method of changing the current song.                          *
 *                                                                                             *
 * INPUT:   theme -- The song to start playing. If -1 is pssed in, then just the current song  *
 *                   is faded.                                                                 *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/16/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
void ThemeClass::Queue_Song(ThemeType theme)
{
	if (ScoresPresent && SampleType && !Debug_Quiet && (Pending == THEME_NONE || Pending == THEME_PICK_ANOTHER)) {
		if (!Options.ScoreVolume && theme != THEME_NONE) return;

		Pending = theme;
		Fade_Sample(Current, THEME_DELAY);
	}
}


/***********************************************************************************************
 * ThemeClass::Play_Song -- Starts the specified song play NOW.                                *
 *                                                                                             *
 *    This routine is used to start the specified theme playing right now. If there is already *
 *    a theme playing, it is cut short so that this one may start.                             *
 *                                                                                             *
 * INPUT:   theme -- The theme number to start playing.                                        *
 *                                                                                             *
 * OUTPUT:  Returns with the sample play handle.                                               *
 *                                                                                             *
 * WARNINGS:   This cuts off any current song in a abrubt manner. Only use this routine when   *
 *             necessary.                                                                      *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/16/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
int ThemeClass::Play_Song(ThemeType theme)
{
	if (ScoresPresent && SampleType && !Debug_Quiet && Options.ScoreVolume) {
		Stop();
		Score = theme;
		if (theme >= THEME_FIRST) {

#ifdef DEMO
			if (_themes[theme].Scenario != 99) {
				CCFileClass file(Theme_File_Name(theme));
				if (file.Is_Available()) {
					Current = File_Stream_Sample_Vol(Theme_File_Name(theme), 0xFF, true);
				} else {
					Current = -1;
				}
			} else {
				Current = -1;
			}
#else
			Current = File_Stream_Sample_Vol(Theme_File_Name(theme), 0xFF, true);
#endif
		}
	}
	return(Current);
}


/***********************************************************************************************
 * ThemeClass::Theme_File_Name -- Constructs a filename for the specified theme.               *
 *                                                                                             *
 *    This routine will construct (into a static buffer) a filename that matches the theme     *
 *    number specified. This constructed filename is returned as a pointer. The filename will  *
 *    remain valid until the next call to this routine.                                        *
 *                                                                                             *
 * INPUT:   theme -- The theme number to convert to a filename.                                *
 *                                                                                             *
 * OUTPUT:  Returns with a pointer to the constructed filename for the specified theme number. *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/16/1995 JLB : Created.                                                                 *
 *   05/09/1995 JLB : Theme variation support.                                                 *
 *=============================================================================================*/
char const * ThemeClass::Theme_File_Name(ThemeType theme)
{
	static char name[_MAX_FNAME+_MAX_EXT];

	if (_themes[theme].Variation && Special.IsVariation) {
		_makepath(name, NULL, NULL, _themes[theme].Name, ".VAR");
		CCFileClass file(name);
		if (file.Is_Available()) {
			return((char const *)(&name[0]));
		}
	}
	_makepath(name, NULL, NULL, _themes[theme].Name, ".AUD");
	return((char const *)(&name[0]));
}


/***********************************************************************************************
 * ThemeClass::Track_Length -- Caclulates the length of the song (in seconds).                 *
 *                                                                                             *
 *    Use this routine to calculate the length of the song. The length is determined by        *
 *    reading the header of the song and dividing the sample rate into the sample length.      *
 *                                                                                             *
 * INPUT:   theme -- The song number to examine to find its length.                            *
 *                                                                                             *
 * OUTPUT:  Returns with the length of the specified theme. This length is in the form of      *
 *          seconds.                                                                           *
 *                                                                                             *
 * WARNINGS:   This routine goes to disk to fetch this information. Don't call frivolously.    *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/16/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
int ThemeClass::Track_Length(ThemeType theme)
{
	if ((unsigned)theme < THEME_COUNT) {
		return(_themes[theme].Duration);
	}
	return(0);
}


/***********************************************************************************************
 * ThemeClass::Stop -- Stops the current theme from playing.                                   *
 *                                                                                             *
 *    Use this routine to stop the current theme. After this routine is called, no more music  *
 *    will play until the Start() function is called.                                          *
 *                                                                                             *
 * INPUT:   none                                                                               *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   09/08/1994 JLB : Created.                                                                 *
 *=============================================================================================*/
void ThemeClass::Stop(void)
{
	if (ScoresPresent && SampleType && !Debug_Quiet) {
		if (Current != -1) {
			Stop_Sample(Current);
			Current = -1;
			Score = THEME_NONE;
			Pending = THEME_NONE;
		}
	}
}


/***********************************************************************************************
 * ThemeClass::Still_Playing -- Determines if music is still playing.                          *
 *                                                                                             *
 *    Use this routine to determine if music is still playing.                                 *
 *                                                                                             *
 * INPUT:   none                                                                               *
 *                                                                                             *
 * OUTPUT:  bool; Is the music still audible?                                                  *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   12/20/1994 JLB : Created.                                                                 *
 *=============================================================================================*/
int ThemeClass::Still_Playing(void)
{
	if (ScoresPresent && SampleType && Current != -1 && !Debug_Quiet) {
		return(Sample_Status(Current));
	}
	return(false);
}


/***********************************************************************************************
 * ThemeClass::Is_Allowed -- Checks to see if the specified theme is legal.                    *
 *                                                                                             *
 *    Use this routine to determine if a theme is allowed to be played. A theme is not allowed *
 *    if the scenario is too early for that score, or the score only is allowed in special     *
 *    cases.                                                                                   *
 *                                                                                             *
 * INPUT:   index -- The score the check to see if it is allowed to play.                      *
 *                                                                                             *
 * OUTPUT:  Is the specified score allowed to play in the normal score playlist?               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   05/09/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
bool ThemeClass::Is_Allowed(ThemeType index) const
{
#ifdef DEMO
	char buffer[128];

	sprintf(buffer, "%s.AUD", Base_Name(index));
	CCFileClass file(buffer);
	if (_themes[index].Scenario == 99 || !file.Is_Available()) {
		_themes[index].Scenario = 99;
		return(false);
	}
#endif

	return(
		_themes[index].Available &&
		(_themes[index].Normal ||
//		(index == THEME_MAP1 && ScenarioInit) ||
		((Special.IsVariation && _themes[index].Variation && index != THEME_WIN1) &&
#ifndef DEMO
		(GameToPlay != GAME_NORMAL || _themes[index].Scenario <= (int)Scenario) &&
#endif
		(index != THEME_J1 || Special.IsJurassic))));
}


/***********************************************************************************************
 * ThemeClass::From_Name -- Determines theme number from specified name.                       *
 *                                                                                             *
 *    Use this routine to convert a name (either the base filename of the theme, or a partial  *
 *    substring of the full name) into the matching ThemeType value. Typical use of this is    *
 *    when parsing the INI file for theme control values.                                      *
 *                                                                                             *
 * INPUT:   name  -- Pointer to base filename of theme or a partial substring of the full      *
 *                   theme name.                                                               *
 *                                                                                             *
 * OUTPUT:  Returns with the matching theme number. If no match could be found, then           *
 *          THEME_NONE is returned.                                                            *
 *                                                                                             *
 * WARNINGS:   If a filename is specified the comparison is case insensitive. When scanning    *
 *             the full theme name, the comparison is case sensitive.                          *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   05/29/1995 JLB : Created.                                                                 *
 *=============================================================================================*/
ThemeType ThemeClass::From_Name(char const * name)
{
	if (name && strlen(name) > 0) {
		/*
		**	First search for an exact name match with the filename
		**	of the theme. This is guaranteed to be unique.
		*/
		ThemeType theme;
		for (theme = THEME_FIRST; theme < THEME_COUNT; theme++) {
			if (stricmp(_themes[theme].Name, name) == 0) {
				return(theme);
			}
		}

		/*
		**	If the filename scan failed to find a match, then scan for
		**	a substring within the full name of the score. This might
		**	yeild a match, but is not guaranteed to be unique.
		*/
		for (theme = THEME_FIRST; theme < THEME_COUNT; theme++) {
			if (strstr(Text_String(_themes[theme].Fullname), name) != NULL) {
				return(theme);
			}
		}
	}

	return(THEME_NONE);
}


/***********************************************************************************************
 * ThemeClass::Scan -- Scans all scores for availability.                                      *
 *                                                                                             *
 *    This routine should be called whenever a score mixfile is registered. It will scan       *
 *    to see if any score is unavailable. If this is the case, then the score will be so       *
 *    flagged in order not to appear on the play list. This condition is likely to occur       *
 *    when expansion mission disks contain a different score mix than the release version.     *
 *                                                                                             *
 * INPUT:   none                                                                               *
 *                                                                                             *
 * OUTPUT:  none                                                                               *
 *                                                                                             *
 * WARNINGS:   none                                                                            *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   01/04/1996 JLB : Created.                                                                 *
 *=============================================================================================*/
void ThemeClass::Scan(void)
{
	for (ThemeType theme = THEME_FIRST; theme < THEME_COUNT; theme++) {
//		if (theme == THEME_J1 && !Special.IsJurassic) {
//			_themes[theme].Available = false;
//		} else {
			_themes[theme].Available = CCFileClass(Theme_File_Name(theme)).Is_Available();
//		}
	}
}