/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * Additional copyright for this file:
 * Copyright (C) 1999-2000 Revolution Software Ltd.
 * This code is based on source code created by Revolution Software,
 * used with permission.
 *
 * 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/>.
 *
 */

#include "engines/icb/event_manager.h"
#include "engines/icb/global_objects.h"
#include "engines/icb/mission.h"

#include "common/stream.h"

namespace ICB {

bool8 _event_manager::HasEventPending(int32 nObjectID) {
	// First check the ID is in range and the object is running.
	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::HasEventPending()", nObjectID);

	// If the object is not running, simply return false.
	if (!m_pbRunning[nObjectID] || m_pbSuspended[nObjectID])
		return (FALSE8);

	// Return the flag for the object.
	return (m_pEventLists[nObjectID].HasEventPending());
}

bool8 _event_manager::CheckEventWaitingForObject(int32 nObjectID, const char *pcEventName) {
	// First check the ID is in range and the object is running.
	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::CheckEventWaitingForObject()", nObjectID);

	// If the object is not running, simply return false.
	if (!m_pbRunning[nObjectID] || m_pbSuspended[nObjectID])
		return (FALSE8);

	// Check the object's event list.
	return (m_pEventLists[nObjectID].CheckEventWaiting(pcEventName));
}

void _event_manager::ShutDownEventProcessingForObject(int32 nObjectID) {
	// First check the ID is in range and the object is running.
	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::ShutDownEventProcessingForObject()", nObjectID);

	// Mark it shut down.
	m_pbRunning[nObjectID] = FALSE8;
}

void _event_manager::RegisterForEvent(int32 nObjectID, const char *pcEventName) {
	// First check the ID is in range and the object is running.
	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::RegisterForEvent()", nObjectID);

	// Add the event for the object.
	if (m_pbRunning[nObjectID])
		m_pEventLists[nObjectID].AddEventForObject(pcEventName);
}

void _event_manager::UnregisterForEvent(int32 nObjectID, const char *pcEventName) {
	// First check the ID is in range and the object is running.
	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::UnregisterForEvent()", nObjectID);

	// Make sure the object is running.
	if (m_pbRunning[nObjectID]) {
		// Remove event for the object.
		m_pEventLists[nObjectID].RemoveEventForObject(pcEventName);

		// This is a useful opportunity to do some cleaning up.  Having worked with the implementers, it
		// seems like desirable behaviour that all events get cleared when you unregister an object for one.
		m_pEventLists[nObjectID].ClearAllOutstandingEvents();
	}
}

void _event_manager::ClearAllEventsForObject(int32 nObjectID) {
	// First check the ID is in range and the object is running.
	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::ClearAllEventsForObject()", nObjectID);

	// Clear the object's events.
	if (m_pbRunning[nObjectID])
		m_pEventLists[nObjectID].ClearAllOutstandingEvents();
}

void _event_manager::SetSuspendFlagForObject(int32 nObjectID, bool8 bState) {
	// First check the ID is in range and the object is running.
	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::SetSuspendFlagForObject()", nObjectID);

	// Flag the object suspended or not.
	m_pbSuspended[nObjectID] = bState;
}

bool8 _event_manager::DidObjectSendLastNamedEvent(int32 nCallerID, int32 nObjectID, const char *pcEventName) const {
	if ((nCallerID < 0) || ((uint32)nCallerID >= m_nNumObjects))
		Fatal_error("Caller ID %d out of range in _event_manager::DidObjectSendLastEvent()", nCallerID);

	if ((nObjectID < 0) || ((uint32)nObjectID >= m_nNumObjects))
		Fatal_error("Object ID %d out of range in _event_manager::DidObjectSendLastEvent()", nObjectID);

	// Get the ID of last poster of named event.
	if (!m_pbRunning[nCallerID] || m_pbSuspended[nCallerID])
		return (FALSE8);
	else
		return (m_pEventLists[nCallerID].DidObjectSendLastNamedEvent(nObjectID, pcEventName));
}

int32 _event_manager::GetIDOfLastObjectToPostEvent(int32 nCallerID, const char *pcEventName) const {
	if ((nCallerID < 0) || ((uint32)nCallerID >= m_nNumObjects))
		Fatal_error("Caller ID %d out of range in _event_manager::GetIDOfLastObjectToPostEvent()", nCallerID);

	// Get the ID of last poster of named event.
	if (!m_pbRunning[nCallerID] || m_pbSuspended[nCallerID])
		return (FALSE8);
	else
		return (m_pEventLists[nCallerID].GetIDOfLastObjectToPostEvent(pcEventName));
}

void _event_manager::PostNamedEventToObject(const char *pcEventName, int32 nTargetID, int32 nSenderID) {
	if ((nSenderID < 0) || ((uint32)nSenderID >= m_nNumObjects))
		Fatal_error("Sender ID %d out of range in _event_manager::PostNamedEventToObject()", nSenderID);

	if ((nTargetID < 0) || ((uint32)nTargetID >= m_nNumObjects))
		Fatal_error("Target ID %d out of range in _event_manager::PostNamedEventToObject()", nTargetID);

	// Post the named event for one object only.
	if (m_pbRunning[nTargetID] || m_pbSuspended[nTargetID])
		m_pEventLists[nTargetID].PostNamedEvent(pcEventName, nSenderID);
}

bool8 _event_manager::IsObjectRegisteredForEvent(int32 nCallerID, const char *pcEventName) {
	if ((nCallerID < 0) || ((uint32)nCallerID >= m_nNumObjects))
		Fatal_error("Sender ID %d out of range in _event_manager::IsObjectRegisteredForEvent()", nCallerID);

	if (m_pbRunning[nCallerID])
		return (m_pEventLists[nCallerID].IsEventInList(pcEventName));
	else
		return (FALSE8);
}

void _event_manager::Initialise() {
	uint32 i;

	// Initialize the line-of-sight module.
	g_oLineOfSight->Initialise();

	// Add an entry for each of the objects in this session.
	m_nNumObjects = (uint8)MS->total_objects;
	memset(m_pbRunning, 0, MAX_session_objects * sizeof(bool8));
	memset(m_pbSuspended, 0, MAX_session_objects * sizeof(bool8));

	for (i = 0; i < m_nNumObjects; ++i) {
		// Create an event list for the object.
		m_pEventLists[i].SetNewObjectName(MS->logic_structs[i]->GetName());

		// Flag it running or not.
		if (MS->logic_structs[i]->ob_status == OB_STATUS_HELD)
			m_pbRunning[i] = FALSE8;
		else
			m_pbRunning[i] = TRUE8;
	}

	// To start with, no event timers are running.
	for (i = 0; i < EVENT_MANAGER_MAX_TIMERS; ++i)
		m_pbActiveTimers[i] = FALSE8;
}

void _event_manager::PostNamedEvent(const char *pcEventName, int32 nSenderID) {
	uint32 i;

	// Loop through each object in the session.
	for (i = 0; i < m_nNumObjects; ++i) {
		// Post the event only if the object is marked as running.
		if (m_pbRunning[i] && !m_pbSuspended[i])
			m_pEventLists[i].PostNamedEvent(pcEventName, nSenderID);
	}
}

void _event_manager::CycleEventManager() {
	uint32 i;
	uint32 nElapsed;

	// Loop for each timer.
	for (i = 0; i < EVENT_MANAGER_MAX_TIMERS; ++i) {
		// Watch out for unused slots.
		if (m_pbActiveTimers[i]) {
			// Process the timer.
			m_pEventTimers[i].SetEventTime(m_pEventTimers[i].GetEventTime() + 1);

			// If it has gone past the end then it has expired and so needs removing.
			if (m_pEventTimers[i].GetEventTime() > m_pEventTimers[i].GetEnd()) {
				// Delete this expired timer.
				m_pbActiveTimers[i] = FALSE8;
			} else {
				// Timer has not expired but might not even have started.
				if (m_pEventTimers[i].GetEventTime() >= m_pEventTimers[i].GetEnd()) {
					// Yes, it has started and has not expired.  So check if it matches some multiple of
					// the interval.
					nElapsed = m_pEventTimers[i].GetEventTime() - m_pEventTimers[i].GetStart();
					if ((m_pEventTimers[i].GetInterval() == 0) || ((nElapsed % m_pEventTimers[i].GetInterval()) == 0)) {
						// We need to generate an event.
						PostNamedEvent(m_pEventTimers[i].GetEventName(), m_pEventTimers[i].GetObjectID());
					}
				}
			}
		}
	}
}

void _event_manager::PostRepeatingEvent(const char *pcEventName, uint32 nStart, uint32 nInterval, uint32 nEnd) {
	uint32 i;

	// Look for a vacant slot.
	i = 0;
	while ((i < EVENT_MANAGER_MAX_TIMERS) && m_pbActiveTimers[i])
		++i;

	// If we ran off the end, we failed to find a spare slot.
	if (i == EVENT_MANAGER_MAX_TIMERS)
		Fatal_error("Run out of event timer slots in _event_manager::PostRepeatingEvent()");

	// Set up the timer.
	m_pEventTimers[i].SetEventName(pcEventName);
	m_pEventTimers[i].SetStart(nStart);
	m_pEventTimers[i].SetEnd(nEnd);
	m_pEventTimers[i].SetInterval(nInterval);
	m_pEventTimers[i].SetEventTime(0);

	// Flag it as running.
	m_pbActiveTimers[i] = TRUE8;
}

void _event_manager::Save(Common::WriteStream *stream) const {
	uint32 i;
	uint32 nNumActiveTimers = 0;

	// First, work out how many timers we are going to write.
	for (i = 0; i < EVENT_MANAGER_MAX_TIMERS; ++i) {
		if (m_pbActiveTimers[i])
			++nNumActiveTimers;
	}

	// Write the number of active event timers in the file.
	stream->writeUint32LE(nNumActiveTimers);

	// Now write the actual active event timers.
	for (i = 0; i < EVENT_MANAGER_MAX_TIMERS; ++i) {
		if (m_pbActiveTimers[i])
			m_pEventTimers[i].Save(stream);
	}
}

void _event_manager::Restore(Common::SeekableReadStream *stream) {
	uint32 i;
	uint32 nNumActiveTimers;
	_event_timer oEventTimer;

	// Make sure all timers are currently deactivated.  This is the same as overwriting them.
	for (i = 0; i < EVENT_MANAGER_MAX_TIMERS; ++i)
		m_pbActiveTimers[i] = FALSE8;

	// Find out how many timers we are going to read in.
	nNumActiveTimers = stream->readUint32LE();

	// Read them in.
	for (i = 0; i < nNumActiveTimers; ++i) {
		oEventTimer.Restore(stream);
		m_pEventTimers[i] = oEventTimer;
		m_pbActiveTimers[i] = TRUE8;
	}

	if (stream->err())
		Fatal_error("Error restoring event timers from save file");
}

} // End of namespace ICB
