//===========================================================================//
// File:	AudioRenderer.cpp                                                //
// Project: MUNGA           Brick: Video Renderer 	                         //
// Contents: Abstracted Base Video Renderer								     //
//---------------------------------------------------------------------------//
//   Date    Who  Modification                                               //
// --------  ---  ---------------------------------------------------------- //
// 03/04/97  JTR  Initial coding.                                            //
//---------------------------------------------------------------------------//
// Copyright (C) 1997, Virtual World Entertainment, Inc.                     //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//

#include "AdeptHeaders.hpp"

#include "ComponentHeaders.hpp"
#include "RendererManager.hpp"
#include "AudioRenderer.hpp"
#include "Application.hpp"
#include "Interface.hpp"
#include "Map.hpp"
#include "Application.hpp"
#include "SpatializedChannel.hpp"
#include "AudioCommand.hpp"
#include "AudioSample.hpp"
#include "AudioFXComponent.hpp"
#include "EarComponent.hpp"
#include "VOChannel.hpp"

Stuff::Scalar ChannelType::s_musicVolume = 1.0f;
Stuff::Scalar ChannelType::s_sfxVolume = 1.0f;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
ChannelType::ChannelType():
	m_queued(false),
	m_volume(1.0f)
{
	m_type = -1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ChannelType::SetChannelActive(AudioChannel *channel)
{
	Check_Object(this);
	Check_Object(channel);
	Verify(m_type != -1);

	Verify(m_inactiveChannels.IsPlugMember(channel));
	m_inactiveChannels.RemovePlug(channel);
	Verify(!m_activeChannels.IsPlugMember(channel));
	m_activeChannels.Add(channel);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ChannelType::SetChannelInactive(AudioChannel *channel)
{
	Check_Object(this);
	Check_Object(channel);
	Verify(m_type != -1);

	Verify(m_activeChannels.IsPlugMember(channel));
	m_activeChannels.RemovePlug(channel);
	Verify(!m_inactiveChannels.IsPlugMember(channel));
	m_inactiveChannels.Add(channel);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ChannelType::SetVolume(Scalar volume)
{
	Check_Object(this);
	Verify(m_type != -1);

	if (m_type == AudioRenderer::MusicType)
		volume *= s_musicVolume;
	else if (m_type == AudioRenderer::GeneralType)
		volume *= s_sfxVolume;
	m_volume = volume;
	ChainIteratorOf<AudioChannel*> actives(&m_activeChannels);
	AudioChannel *channel;
	while ((channel = actives.ReadAndNext()) != NULL)
	{
		Check_Object(channel);
		channel->SetVolume();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ChannelType::StopAll()
{
	Check_Object(this);
	Verify(m_type != -1);

	ChainIteratorOf<AudioChannel*> actives(&m_activeChannels);
	AudioChannel *channel;
	while ((channel = actives.ReadAndNext()) != NULL)
	{
		Check_Object(channel);
		channel->Stop();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ChannelType::PauseAll()
{
	Check_Object(this);

	ChainIteratorOf<AudioChannel*> actives(&m_activeChannels);
	AudioChannel *channel;
	while ((channel = actives.ReadAndNext()) != NULL)
	{
		Check_Object(channel);
		gosAudio_SetChannelPlayMode(channel->m_channel, gosAudio_Pause);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void ChannelType::ResumeAll()
{
	Check_Object(this);

	ChainIteratorOf<AudioChannel*> actives(&m_activeChannels);
	AudioChannel *channel;
	while ((channel = actives.ReadAndNext()) != NULL)
	{
		Check_Object(channel);
		gosAudio_SetChannelPlayMode(channel->m_channel, gosAudio_Continue);
	}
}

//##########################################################################
//###########################    AudioRenderer    ##########################
//##########################################################################

AudioRenderer::ClassData*
	AudioRenderer::DefaultData = NULL;
AudioRenderer
	*AudioRenderer::Instance = NULL;
HGOSHEAP
	AudioRenderer::s_Heap = NULL;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::InitializeClass()
{
	Verify(!s_Heap);
	s_Heap = gos_CreateMemoryHeap("Audio Renderer", 0, g_LibraryHeap);
	Check_Pointer(s_Heap);

	Verify(!DefaultData);
	DefaultData =
		new ClassData(
			AudioRendererClassID,
			"Adept::AudioRenderer",
			BaseClass::DefaultData,
			0,
			NULL
		);
	Check_Object(DefaultData);
	ChannelType::s_musicVolume = 1.0f;
	ChannelType::s_sfxVolume = 1.0f;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::TerminateClass()
{
	Unregister_Object(DefaultData);
	delete DefaultData;
	DefaultData = NULL;

	Check_Pointer(s_Heap);
	gos_DestroyMemoryHeap(s_Heap);
	s_Heap = NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AudioRenderer::AudioRenderer():
	Renderer(
		DefaultData,
		RendererManager::AudioRendererType,
		"AudioRenderer"
	),
	m_channels(Environment.soundChannels),
	m_sampleCache(101,NULL,true),
	m_channelTypes(TypeCount),
	iface(NULL)
{
	Check_Pointer(this);

	//
	//----------------------------
	// Zero out the channel arrays
	//----------------------------
	//
	memset(m_channels.GetData(), 0, m_channels.GetSize());
	Verify(m_channels.GetLength() >= 32);
	m_cacheSize = 0;

	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AudioRenderer::~AudioRenderer()
{
	Check_Object(this);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::SetRendererStatus(RendererStatus status)
{
	Check_Object(this);
	switch (status)
	{
	case ActiveRendererStatus:
		{
			//
			//----------------
			// Setup the mixer
			//----------------
			//
			gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_Rolloff, 1.0f);
			gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_Reverb, 1.0f);
			gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_Decay, 0.1f);
			gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_Distance, 1.0f);
			gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_Doppler, 1.0f);
			gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_Volume, 1.0f);

			//
			//---------------------------
			// Make the voiceover channel
			//---------------------------
			//
			int channel_id = 0;
			AudioChannel *channel;
			m_channelTypes[VOType].m_queued = true;
			m_channelTypes[VOType].m_volume = 0.0f;
			m_channelTypes[VOType].m_type = VOType;
			channel = new VOChannel(channel_id, VOType);
			Check_Object(channel);
			m_channels[channel_id] = channel;
			m_channelTypes[VOType].m_inactiveChannels.Add(channel);
			channel_id = 2;

			//
			//--------------------------------
			// Allocate the two music channels
			//--------------------------------
			//
			m_channelTypes[MusicType].m_type = MusicType;
			m_channelTypes[MusicType].m_volume = ChannelType::s_musicVolume;
			for (int i=0; i<2; ++i)
			{
				channel =
					new AudioChannel(
						AudioChannel::DefaultData,
						channel_id,
						MusicType,
						gosAudio_Volume
					);
				Check_Object(channel);
				m_channels[channel_id++] = channel;
				m_channelTypes[MusicType].m_inactiveChannels.Add(channel);
			}
			Verify(channel_id == 4);

			//
			//-----------------------
			// Make the betty channel
			//-----------------------
			//
			m_channelTypes[BettyType].m_queued = true;
			m_channelTypes[BettyType].m_type = BettyType;
			m_channelTypes[BettyType].m_volume = 1.0f;
			channel =
				new AudioChannel(
					AudioChannel::DefaultData,
					channel_id,
					BettyType,
					gosAudio_Common|gosAudio_Volume
				);
			Check_Object(channel);
			m_channels[channel_id++] = channel;
			m_channelTypes[BettyType].m_inactiveChannels.Add(channel);
			Verify(channel_id == 5);

			//
			//---------------------------------
			// Make the six mechanical channels
			//---------------------------------
			//
			m_channelTypes[MechanicalType].m_type = MechanicalType;
			m_channelTypes[MechanicalType].m_volume = 1.0f;
			for (i=0; i<6; ++i)
			{
				channel =
					new AudioChannel(
						AudioChannel::DefaultData,
						channel_id,
						MechanicalType,
						gosAudio_Common|gosAudio_Volume
					);
				Check_Object(channel);
				m_channels[channel_id++] = channel;
				m_channelTypes[MechanicalType].m_inactiveChannels.Add(channel);
			}
			Verify(channel_id == 11);

			//
			//------------------------------
			// Allocate the general channels
			//------------------------------
			//
			m_channelTypes[GeneralType].m_type = GeneralType;
			m_channelTypes[GeneralType].m_volume = ChannelType::s_sfxVolume;
			int remainder = Environment.soundChannels - channel_id;
			for (i=0; i<remainder; ++i)
			{
				channel = new SpatializedChannel(SpatializedChannel::DefaultData, channel_id, GeneralType);
				Check_Object(channel);
				m_channels[channel_id++] = channel;
				m_channelTypes[GeneralType].m_inactiveChannels.Add(channel);
			}
			Verify(channel_id == Environment.soundChannels);

#if defined(_ARMOR)
			for (i=0; i<TypeCount; ++i)
			{
				ChannelType &type = m_channelTypes[i];
				Verify(type.m_activeChannels.IsEmpty());
				Verify(type.m_queuedCommands.IsEmpty());
			}
			Verify(m_executingCommands.IsEmpty());
#endif
		}
		break;

	//
	//--------------------------------------------------------------------------------
	// If we are going inactive, stop all the channels.  Then tell the channeltypes to
	// destroy all looped commands
	//--------------------------------------------------------------------------------
	//
	case InactiveRendererStatus:
		{
			for (int i=0; i<m_channels.GetLength(); ++i)
			{
				if (m_channels[i])
				{
					Check_Object(m_channels[i]);
					if (m_channels[i]->m_command)
						m_channels[i]->Stop();
					delete m_channels[i];
					m_channels[i] = NULL;
				}
			}
			Verify(m_channelTypes.GetLength() == TypeCount);
			for (i=0; i<TypeCount; ++i)
			{
				ChannelType &type = m_channelTypes[i];
				Verify(type.m_activeChannels.IsEmpty());
				type.m_queuedCommands.DeletePlugs();
				type.m_loopCommands.DeletePlugs();
			}
			m_sampleCache.DeletePlugs();
			m_executingCommands.DeletePlugs();
			m_newCommands.DeletePlugs();
		}
		break;
	}

	BaseClass::SetRendererStatus(status);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::PauseAll()
{
	for (int i=0; i<TypeCount; ++i)
		m_channelTypes[i].PauseAll();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::ResumeAll()
{
	for (int i=0; i<TypeCount; ++i)
		m_channelTypes[i].ResumeAll();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
AudioSample* AudioRenderer::UseSample(const ResourceID &handle_id)
{
	Check_Object(this);

	AudioSample *sample = m_sampleCache.Find(handle_id);
	if (!sample)
	{
		Resource hint(handle_id);
		Verify(hint.DoesResourceExist());
		sample = new AudioSample(&hint);
		Check_Object(sample);
		m_sampleCache.AddValue(sample, handle_id);
	}
	Check_Object(sample);
	return sample;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::ExecuteImplementation(Time target_render_time)
{
	//
	//-----------------------------------
	// Execute the executeable components
	//-----------------------------------
	//
	LOG_BLOCK("Update Renderers::Audio");
	BaseClass::ExecuteImplementation(target_render_time);

	//
	//---------------------------------------------------------------------------------------
	// Find where the ear is in the world, then tell GOS about it, negating X to convert from
	// right handed system to left handed one
	//---------------------------------------------------------------------------------------
	//
	Interface *intface = GetInterface();
	if (!intface)
		return;
	Check_Object(intface);
	LinearMatrix4D ear_to_world = GetInterface()->GetLocalToWorld();
	Point3D ear_in_world(ear_to_world);
	UnitVector3D ear_foward_in_world;
	ear_to_world.GetLocalForwardInWorld(&ear_foward_in_world);
	UnitVector3D ear_up_in_world;
	ear_to_world.GetLocalUpInWorld(&ear_up_in_world);
	gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_Position, -ear_in_world.x, ear_in_world.y, ear_in_world.z);
	gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_FrontOrientation, -ear_foward_in_world.x, ear_foward_in_world.y, ear_foward_in_world.z);
	gosAudio_SetChannelSlider(gosAudio_Mixer, gosAudio_TopOrientation, -ear_up_in_world.x, ear_up_in_world.y, ear_up_in_world.z);

	//
	//----------------------------
	// Execute the existing sounds
	//----------------------------
	//
	{
		AUDIO_RENDER("Execute");
		ChainIteratorOf<AudioCommand*> commands(&m_executingCommands);
		AudioCommand *command;
		while ((command = commands.ReadAndNext()) != NULL)
		{
			Check_Object(command);
			AudioChannel *channel = command->m_channel;
			Check_Object(channel);
			channel->Execute(target_render_time);
		}
	}

	//
	//---------------------------
	// Process the audio commands
	//---------------------------
	//
	{
		AUDIO_RENDER("Execute::NewCommands");
		ChainIteratorOf<AudioCommand*> commands(&m_newCommands);
		AudioCommand *command;
		while ((command = commands.GetCurrent()) != NULL)
		{
			Check_Object(command);
			commands.Remove();
			AudioSample *sample = m_sampleCache.Find(command->m_handleResourceID);
			Check_Object(sample);
			if (command->ConnectToChannel(sample))
			{
				m_executingCommands.Add(command);
				command->m_minRestartDelay *= sample->m_sampleInfo.fDuration;
				command->m_minReplaceDelay *= sample->m_sampleInfo.fDuration;
			}
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::FlushUnusedCache()
{
	Check_Object(this);

	ChainIteratorOf<AudioSample*> oldies(&m_unusedSamples);
	AudioSample *sample;
	while ((sample = oldies.ReadAndNext()) != NULL && m_cacheSize > 0x200000)
	{
		Check_Object(sample);
		m_cacheSize -= sample->m_sampleInfo.dwSizeInBytes;
		delete sample;
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Component* AudioRenderer::CreateComponent(
	ClassID component_class,
	MemoryStream *stream,
	RendererComponentWeb *rend_web,
	Entity *entity
)
{
	AudioComponentWeb *web = Cast_Object(AudioComponentWeb*, rend_web);
	Component *component;
	switch (component_class)
	{
		case EarComponentClassID:
			component = EarComponent::Make(stream, rend_web);
			break;

		case AudioFXComponentClassID:
			component = AudioFXComponent::Make(stream, web);
			break;

		default:
			component =
				BaseClass::CreateComponent(
					component_class,
					stream,
					rend_web,
					entity
				);
			break;
	}
	return component;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::TestInstance()
{
	Verify(IsDerivedFrom(DefaultData));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::EntityIsInteresting(
	Entity *entity,
	bool render_me)
{
	Check_Object(this);
	Check_Object(entity);
	AUDIO_LOAD("EntityIsInteresting");

	//
	//----------------------------------------------------------------------
	// If this object already has a web, we just have to turn the web on/off
	//----------------------------------------------------------------------
	//
	AudioComponentWeb *current_web =
		static_cast<AudioComponentWeb*>(entity->GetComponentWeb(rendererType));
	if (current_web)
	{
		Check_Object(current_web);
		current_web->SetInterest(render_me);
		if (!render_me)
			return;
	}

	//
	//--------------------------------------
	// Create a component web for the entity
	//--------------------------------------
	//
	const ResourceID &resources =
		entity->GetRendererDataResourceID(rendererType);
	if (!resources || !render_me)
		return;

	gos_PushCurrentHeap(s_Heap);
	AudioComponentWeb* web =
		new AudioComponentWeb(
			AudioComponentWeb::DefaultData,
   			entity,
   			this,
   			resources,
   			NULL
   		);
	Check_Object(web);
	entity->SetComponentWeb(rendererType, web);

	//
	//-------------------------------------------------
	// Now, load up the web from the specified resource
	//-------------------------------------------------
	//
	Resource script(resources);
	web->LoadFromStream(&script);

	//
	//-----------------------
	// Execute the components
	//-----------------------
	//
	{
		AUDIO_LOAD("EntityIsInteresting::Execute");
		web->ExecuteWatcherComponents();
	}
	gos_PopCurrentHeap();
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void AudioRenderer::EntityIsUninteresting(Entity *entity)
{
}
