/*
**	Command & Conquer Generals(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program 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.
**
**	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, see <http://www.gnu.org/licenses/>.
*/

////////////////////////////////////////////////////////////////////////////////
//																																						//
//  (c) 2001-2003 Electronic Arts Inc.																				//
//																																						//
////////////////////////////////////////////////////////////////////////////////

// GameClient/Eva.cpp /////////////////////////////////////////////////////////////////////////////

#include "PreRTS.h"	// This must go first in EVERY cpp file int the GameEngine
#include "GameClient/Eva.h"

#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "GameLogic/GameLogic.h"

#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif

//-------------------------------------------------------------------------------------------------
const char *TheEvaMessageNames[] = 
{
	"LOWPOWER",
	"INSUFFICIENTFUNDS",
	"SUPERWEAPONDETECTED_PARTICLECANNON",
	"SUPERWEAPONDETECTED_NUKE",
	"SUPERWEAPONDETECTED_SCUDSTORM",
	"SUPERWEAPONLAUNCHED_PARTICLECANNON",
	"SUPERWEAPONLAUNCHED_NUKE",
	"SUPERWEAPONLAUNCHED_SCUDSTORM",
	"BUILDINGLOST",
	"BASEUNDERATTACK",
	"ALLYUNDERATTACK",
	"BEACONDETECTED",
	"UNITLOST",
	"GENERALLEVELUP",
	"VEHICLESTOLEN",
	"BUILDINGSTOLEN",
	"CASHSTOLEN",
	"UPGRADECOMPLETE",
	"BUILDINGBEINGSTOLEN",
	"EVA_INVALID",
};

//------------------------------------------------------------------------------ INI::parseEvaEvent
void INI::parseEvaEvent( INI* ini )
{
	AsciiString name;

	// read the name
	const char* c = ini->getNextToken();
	name.set( c );	
	
	EvaCheckInfo *check = TheEva->newEvaCheckInfo( name );
	if (!check) {
		// could be null because it already exists.
		return;
	}

	// parse the ini definition
	ini->initFromINI( check, check->getFieldParse() );
}

//----------------------------------------------------------------------------------- EvaSideSounds
static void parseSideSoundsList( INI *ini, void *instance, void *store, const void* userData )
{
	std::vector<EvaSideSounds> *sounds = (std::vector<EvaSideSounds>*) store;
	EvaSideSounds newSounds;

	ini->initFromINI( &newSounds, newSounds.getFieldParse() );

	// This could be made more efficient, but to be honest, it shouldn't be that slow.
	sounds->push_back(newSounds);
}

//----------------------------------------------------------------------------------- EvaSideSounds
const FieldParse EvaSideSounds::s_evaSideSounds[] =
{
	{ "Side",									INI::parseAsciiString,					NULL,			offsetof( EvaSideSounds, m_side) },
	{ "Sounds",								INI::parseSoundsList,						NULL,			offsetof( EvaSideSounds, m_soundNames) },
	{ 0, 0, 0, 0 },
};

//------------------------------------------------------------------------------------ EvaCheckInfo
EvaCheckInfo::EvaCheckInfo() :
	m_message(EVA_COUNT),
	m_priority(0), // lowest of all priorities
	m_framesBetweenChecks(900),	// 30 seconds at 30 fps
	m_framesToExpire(150) // 5 seconds at 30 fps
{

	
}

//-------------------------------------------------------------------------------------------------
const FieldParse EvaCheckInfo::s_evaEventInfo[] = 
{
	{ "Priority",							INI::parseUnsignedInt,					NULL,			offsetof( EvaCheckInfo, m_priority ) },
	{ "TimeBetweenChecksMS",	INI::parseDurationUnsignedInt,	NULL,			offsetof( EvaCheckInfo, m_framesBetweenChecks ) },
	{ "ExpirationTimeMS",			INI::parseDurationUnsignedInt,	NULL,			offsetof( EvaCheckInfo, m_framesToExpire) },
	{ "SideSounds",						parseSideSoundsList,						NULL,			offsetof( EvaCheckInfo, m_evaSideSounds ) },
	{ 0, 0, 0, 0 },

};


//-------------------------------------------------------------------------------------------------
const ShouldPlayFunc Eva::s_shouldPlayFuncs[] = 
{
	Eva::shouldPlayLowPower,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,
	Eva::shouldPlayGenericHandler,	
	Eva::shouldPlayGenericHandler,	
	NULL,
};

//-------------------------------------------------------------------------------------------------
EvaCheck::EvaCheck() : 
	m_evaInfo(NULL), 
	m_triggeredOnFrame(TRIGGEREDON_NOT),
	m_timeForNextCheck(NEXT_CHECK_NOW), 
	m_alreadyPlayed(FALSE)
{

}

//-------------------------------------------------------------------------------------------------
Eva::Eva() : 
	m_localPlayer(NULL),
	m_previousBuildingCount(0),
	m_previousUnitCount(0),
	m_enabled(TRUE)
{

	for (Int i = 0; i < EVA_COUNT; ++i) {
		m_shouldPlay[i] = FALSE;
	}
}

//-------------------------------------------------------------------------------------------------
Eva::~Eva()
{
	EvaCheckInfoPtrVecIt it;
	for (it = m_allCheckInfos.begin(); it != m_allCheckInfos.end(); ++it) {
		if (*it)
			(*it)->deleteInstance();
	}
}

//-------------------------------------------------------------------------------------------------
void Eva::init()
{
	// parse the INI here, etc.
	INI ini;
	ini.load( AsciiString( "Data\\INI\\Eva.ini" ), INI_LOAD_OVERWRITE, NULL);
}

//-------------------------------------------------------------------------------------------------
void Eva::reset()
{
	m_previousUnitCount = 0;
	m_previousBuildingCount = 0;

	// remove all pending counters, etc, here.
	EvaCheckVecIt it;
	for (it = m_checks.begin(); it != m_checks.end(); /* empty */) {
		it = m_checks.erase(it);
	}

	// remove all things flagged as "need to play"
	for (Int i = 0; i < EVA_COUNT; ++i) {
		m_shouldPlay[i] = FALSE;
	}	

	// If we were previously disabled, re-enable ourselves.
	m_enabled = TRUE;
}

//-------------------------------------------------------------------------------------------------
void Eva::update()
{
	if (!m_enabled) {
		return;
	}

	m_localPlayer = ThePlayerList->getLocalPlayer();
	UnsignedInt frame = TheGameLogic->getFrame();

	// Don't update for the first few frames. This way, we don't have to deal with our initial power
	// being 0, etc.
	if (frame < 2) {
		return;
	}

	for (Int mesg = (Int)EVA_FIRST; mesg < (Int)EVA_COUNT; ++mesg) {
		if (isTimeForCheck((EvaMessage)mesg, frame)) {
			if (messageShouldPlay((EvaMessage)mesg, frame)) {
				playMessage((EvaMessage)mesg, frame);
			}
		}
	}

	processPlayingMessages(frame);	
	m_localPlayer = NULL;

	// Reset all of the flags that have been set to true that haven't actually been probed, because
	// they will need to trigger again to be valid messages.
	for (Int i = EVA_FIRST; i < EVA_COUNT; ++i) {
		m_shouldPlay[i] = FALSE;
	}
}

//-------------------------------------------------------------------------------------------------
EvaMessage Eva::nameToMessage(const AsciiString& name)
{
	for (Int i = EVA_FIRST; i < EVA_COUNT; ++i) {
		if (name.compareNoCase(TheEvaMessageNames[i]) == 0) {
			return (EvaMessage) i;
		}
	}

	DEBUG_CRASH(("Invalid requested Eva message translation :%s: jkmcd", name.str()));
	return EVA_COUNT;
}

//-------------------------------------------------------------------------------------------------
AsciiString Eva::messageToName(EvaMessage message)
{
	if (message >= EVA_FIRST && message < EVA_COUNT)
		return TheEvaMessageNames[message];

	DEBUG_CRASH(("Invalid requested Eva message translation. jkmcd"));
	return AsciiString::TheEmptyString;
}

//-------------------------------------------------------------------------------------------------
EvaCheckInfo *Eva::newEvaCheckInfo(AsciiString name)
{
	EvaMessage mesg = nameToMessage(name);

	// Only return a new one if there isn't an existing one.
	EvaCheckInfoPtrVecIt it;
	for (it = m_allCheckInfos.begin(); it != m_allCheckInfos.end(); ++it) {
		if (*it && (*it)->m_message == mesg)
			return NULL;
	}

	EvaCheckInfo *checkInfo = newInstance(EvaCheckInfo);
	m_allCheckInfos.push_back(checkInfo);
	checkInfo->m_message = mesg;
	return checkInfo;
}

//-------------------------------------------------------------------------------------------------
const EvaCheckInfo *Eva::getEvaCheckInfo(AsciiString name)
{
	EvaMessage mesg = nameToMessage(name);

	// Only return a new one if there isn't an existing one.
	EvaCheckInfoPtrVecIt it;
	for (it = m_allCheckInfos.begin(); it != m_allCheckInfos.end(); ++it) {
		if (*it && (*it)->m_message == mesg)
			return *it;
	}

	return NULL;
}

//-------------------------------------------------------------------------------------------------
void Eva::setShouldPlay(EvaMessage messageToPlay)
{
	m_shouldPlay[messageToPlay] = TRUE;
}

//-------------------------------------------------------------------------------------------------
void Eva::setEvaEnabled(Bool enabled)
{
	// clear out any waiting messages.
	for (Int i = EVA_FIRST; i < EVA_COUNT; ++i) {
		m_shouldPlay[i] = FALSE;
	}
	m_enabled = enabled; 
}

//-------------------------------------------------------------------------------------------------
Bool Eva::isTimeForCheck(EvaMessage messageToTest, UnsignedInt currentFrame) const
{
	EvaCheckVec::const_iterator it;
	for (it = m_checks.begin(); it != m_checks.end(); ++it) {
		if (it->m_evaInfo->m_message == messageToTest) {
			return FALSE;
		}
	}

	return TRUE;
}

//-------------------------------------------------------------------------------------------------
Bool Eva::messageShouldPlay(EvaMessage messageToTest, UnsignedInt currentFrame) const
{
	if (m_localPlayer == NULL) {
		return FALSE;
	}

	m_messageBeingTested = messageToTest;
	return s_shouldPlayFuncs[messageToTest](m_localPlayer);
}

//-------------------------------------------------------------------------------------------------
Bool Eva::shouldPlayLowPower( Player *localPlayer )
{

	// @todo make eva sensitive to whether player can do anything about it...
	// "Low power, Low power, Low power, yadda yadda yadda..."
	//const ThingTemplate *chinaReactorTemplate = findTemplate(;
	//const ThingTemplate *americanReactorTemplate;
	//if ( chinaReactorTemplate && americanReactorTemplate )
	//{
	//	if ( ! (localPlayer->canBuild(chinaReactorTemplate) || localPlayer->canBuild(americanReactorTemplate)) )
	//		return FALSE
	//}


	return !localPlayer->getEnergy()->hasSufficientPower();
}

//-------------------------------------------------------------------------------------------------
Bool Eva::shouldPlayGenericHandler( Player * )
{
	if (TheEva->m_shouldPlay[TheEva->m_messageBeingTested]) {
		TheEva->m_shouldPlay[TheEva->m_messageBeingTested] = FALSE;
		return TRUE;
	}
	return FALSE;
}

//-------------------------------------------------------------------------------------------------
void Eva::playMessage(EvaMessage messageToTest, UnsignedInt currentFrame)
{
	EvaCheck check;
	check.m_evaInfo = getEvaCheckInfo(Eva::messageToName(messageToTest));
	if (!check.m_evaInfo) {
		return;
	}

	check.m_timeForNextCheck = currentFrame + check.m_evaInfo->m_framesBetweenChecks;
	check.m_triggeredOnFrame = currentFrame;
	check.m_alreadyPlayed = FALSE;

	m_checks.push_back(check);
}

//-------------------------------------------------------------------------------------------------
void Eva::processPlayingMessages(UnsignedInt currentFrame)
{
	// First pass, remove all the objects that can check after this frame.
	EvaCheckVecIt it;
	for (it = m_checks.begin(); it != m_checks.end(); /* empty */) {
		// These are requests that will be available next frame because they've played.
		if (it->m_timeForNextCheck <= currentFrame + 1 && it->m_alreadyPlayed) {
			it = m_checks.erase(it);
			continue;
		}

		// These are requests that never got a chance to play and have since expired.
		if (it->m_triggeredOnFrame + it->m_evaInfo->m_framesToExpire <= currentFrame && !it->m_alreadyPlayed) {
			it = m_checks.erase(it);
			continue;
		}

		++it;
	}

	// It's possible, although unlikely, that we removed everything in the list.
	if (m_checks.begin() == m_checks.end()) {
		return;
	}

	// If we're currently playing some audio, we're done.
	if (m_evaSpeech.isCurrentlyPlaying()) {
		return;
	}

	// Okay. No one is currently playing anything, so lets find an event and trigger it.
	EvaCheckVecIt storedIt = m_checks.end();

	UnsignedInt highestPriority = 0;
	for (it = m_checks.begin(); it != m_checks.end(); ++it) {
		if (it->m_evaInfo->m_priority > highestPriority && !it->m_alreadyPlayed) {
			storedIt = it;
			highestPriority = it->m_evaInfo->m_priority;
		}
	}

	// There wasn't anything waiting to play.
	if (storedIt == m_checks.end()) {
		return;
	}

	// We've got a winner!
	AsciiString side = ThePlayerList->getLocalPlayer()->getSide();
	Int numSides = storedIt->m_evaInfo->m_evaSideSounds.size();

	for (Int i = 0; i < numSides; ++i) {
		if (side.compareNoCase(storedIt->m_evaInfo->m_evaSideSounds[i].m_side) == 0) {
			// Its this one.
			if (storedIt->m_evaInfo->m_evaSideSounds[i].m_soundNames.size() > 0) {
				Int soundToPlay = GameClientRandomValue(0, storedIt->m_evaInfo->m_evaSideSounds[i].m_soundNames.size() - 1);
				m_evaSpeech.setEventName(storedIt->m_evaInfo->m_evaSideSounds[i].m_soundNames[soundToPlay]);
			} else {
				// clear it.
				m_evaSpeech.setEventName(AsciiString::TheEmptyString);
			}
		}
	}

	// Update the entry
	storedIt->m_alreadyPlayed = true;
	storedIt->m_timeForNextCheck = currentFrame + storedIt->m_evaInfo->m_framesBetweenChecks;
	
	// Now that we correctly filter messages, we need to set the player index for who should hear the
	// sound to the local player.
	m_evaSpeech.setPlayerIndex(m_localPlayer->getPlayerIndex());

	m_evaSpeech.setPlayingHandle(TheAudio->addAudioEvent(&m_evaSpeech));
}

//-------------------------------------------------------------------------------------------------
Eva *TheEva = NULL;

