#include "../Symphony/RuSymphony_Base.h"

#include "../Core/Stream/RuStreamIO.h"

#pragma unmanaged

// ************************************************************************************************************************************************************

ruRTTI_DEFINE_SUBCLASS(CRuSymphony, IRuObject)
ruHEAP_DEFINE_SUBCLASS(CRuSymphony, "CRuSymphony", "IRuObject")
ruCLASSGUID_DEFINE(CRuSymphony, 0x6136F6FF, 0x768844e4, 0x9B1E91BE, 0x694C20AD)

// ************************************************************************************************************************************************************

CRuSymphony::SoundDescriptor::SoundDescriptor()
:	m_resourceName(NULL),
	m_soundDataSize(0),
	m_soundData(NULL)
{
}

CRuSymphony::SoundDescriptor::~SoundDescriptor()
{
	delete[] m_resourceName;
	delete[] m_soundData;
}

// ************************************************************************************************************************************************************

CRuSymphony::CRuSymphony()
:	m_digDriver(0),
	m_numSampleHandles(32),
	m_sampleDescriptors(NULL),
	m_soundDescriptorCacheSize(16777216),
	m_resourcePath(NULL),
	m_defaults_3D_MinDist(50.0f),
	m_defaults_3D_MaxDist(800.0f)
{
}

CRuSymphony::~CRuSymphony()
{
	delete[] m_sampleDescriptors;
	delete[] m_resourcePath;

	delete m_soundDescriptorHash;
	delete m_soundDescriptorQueue;
}

BOOL CRuSymphony::Startup()
{
	// Set MSS redistributables directory
	AIL_set_redist_directory("redist");

	// Startup
	AIL_startup();

	// Open digital sound driver
	m_digDriver = AIL_open_digital_driver(44100, 16, MSS_MC_STEREO, 0);

	// Allocate array to store sample handles
	m_sampleDescriptors = ruNEW SampleDescriptor [m_numSampleHandles];

	// Populate sample handles
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		m_sampleDescriptors[i].m_sampleHandle = AIL_allocate_sample_handle(m_digDriver);
		m_sampleDescriptors[i].m_sampleData = NULL;
		m_sampleDescriptors[i].m_sampleDataSize = 0;
	}

	// Allocate sound descriptor cache
	m_soundDescriptorHash = ruNEW CRuStringHash<SoundDescriptor *>(1024);
	m_soundDescriptorQueue = ruNEW CRuLRUQueue<PTRVALUE, SoundDescriptor *>(m_soundDescriptorCacheSize, 1024);

	return TRUE;
}

BOOL CRuSymphony::Shutdown()
{
	// Shutdown
	AIL_shutdown();

	ClearSoundDescriptorHash();
	ClearSoundDescriptorQueue();

	return TRUE;
}

const char *CRuSymphony::GetResourcePath()
{
	if(m_resourcePath)
		return m_resourcePath;

	return "";
}

void CRuSymphony::SetResourcePath(const char *resourcePath)
{
	delete[] m_resourcePath;
	m_resourcePath = ruNEW char [strlen(resourcePath) + 1];
	strcpy(m_resourcePath, resourcePath);
}

BOOL CRuSymphony::SetListenerPosition(REAL x, REAL y, REAL z)
{
	AIL_set_listener_3D_position(m_digDriver, x, y, z);

	return TRUE;
}

BOOL CRuSymphony::SetListenerOrientation(REAL xAt, REAL yAt, REAL zAt, REAL xUp, REAL yUp, REAL zUp)
{
	AIL_set_listener_3D_orientation(m_digDriver, xAt, yAt, zAt, xUp, yUp, zUp);

//	AIL_set_room_type(m_digDriver, ENVIRONMENT_AUDITORIUM);
	AIL_set_digital_master_reverb(m_digDriver, 2.00f, 0.000f, 0.8f);

	return TRUE;
}

BOOL CRuSymphony::SetDefault3DSoundDistances(REAL minDist, REAL maxDist)
{
	m_defaults_3D_MinDist = minDist;
	m_defaults_3D_MaxDist = maxDist;

	return TRUE;
}

BOOL CRuSymphony::PlaySoundByPath(const char *soundPath)
{
	SampleDescriptor *sampleDesc = GetAvailableSampleDescriptor();

	if(sampleDesc)
	{
		SoundDescriptor *soundDesc = GetSoundDescriptor(soundPath);

		if(soundDesc)
		{
			// Fill sample descriptor with sound data
			sampleDesc->m_sampleData = soundDesc->m_soundData;
			sampleDesc->m_sampleDataSize = soundDesc->m_soundDataSize;

			// Feed sample data to MSS
			INT32 parseCode = AIL_set_named_sample_file(sampleDesc->m_sampleHandle, soundPath, sampleDesc->m_sampleData, sampleDesc->m_sampleDataSize, 0);
			AIL_start_sample(sampleDesc->m_sampleHandle);
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuSymphony::Play3DSoundByPath(const char *soundPath, REAL x, REAL y, REAL z)
{
	SampleDescriptor *sampleDesc = GetAvailableSampleDescriptor();

	if(sampleDesc)
	{
		SoundDescriptor *soundDesc = GetSoundDescriptor(soundPath);

		if(soundDesc)
		{
			// Fill sample descriptor with sound data
			sampleDesc->m_sampleData = soundDesc->m_soundData;
			sampleDesc->m_sampleDataSize = soundDesc->m_soundDataSize;

			// Feed sample data to MSS
			INT32 parseCode = AIL_set_named_sample_file(sampleDesc->m_sampleHandle, soundPath, sampleDesc->m_sampleData, sampleDesc->m_sampleDataSize, 0);
			AIL_set_sample_3D_position(sampleDesc->m_sampleHandle, x, y, z);
			AIL_set_sample_3D_distances(sampleDesc->m_sampleHandle, m_defaults_3D_MaxDist, m_defaults_3D_MinDist, TRUE);

			AIL_set_sample_reverb_levels(sampleDesc->m_sampleHandle, 1.0f, 0.3f);

			AIL_start_sample(sampleDesc->m_sampleHandle);
		}

		return TRUE;
	}

	return FALSE;
}

BOOL CRuSymphony::PlaySoundByTag(const char *soundTag)
{
	return TRUE;
}

CRuSymphony::SampleDescriptor *CRuSymphony::GetAvailableSampleDescriptor()
{
	// Find an available sample handle
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		if(AIL_sample_status(m_sampleDescriptors[i].m_sampleHandle) == SMP_DONE)
		{
			return &m_sampleDescriptors[i];
		}
	}

	return NULL;
}

CRuSymphony::SoundDescriptor *CRuSymphony::GetSoundDescriptor(const char *soundPath)
{
	SoundDescriptor *soundDesc = NULL;

	// Attempt to retrieve sound descriptor from the cache
	if(m_soundDescriptorHash->Get(soundPath, soundDesc))
	{
		return soundDesc;
	}

	// Load sound from disk
	const char *resourcePath = GetResourcePath();
	char *fullPath = ruNEW char [strlen(soundPath) + strlen(resourcePath) + 2];
	strcpy(fullPath, resourcePath);
	strcat(fullPath, "\\");
	strcat(fullPath, soundPath);

	soundDesc = ruNEW SoundDescriptor();

	soundDesc->m_resourceName = ruNEW char [strlen(soundPath) + 1];
	strcpy(soundDesc->m_resourceName, soundPath);

	CRuFileStream *sampleStream = ruNEW CRuFileStream();
	sampleStream->Open(fullPath);

	soundDesc->m_soundDataSize = sampleStream->GetStreamSize();
	soundDesc->m_soundData = ruNEW BYTE [soundDesc->m_soundDataSize];

	sampleStream->Read(soundDesc->m_soundData, soundDesc->m_soundDataSize);

	delete sampleStream;

	delete[] fullPath;

	// Generate hash key & code for the sound descriptor
	PTRVALUE hashKey = reinterpret_cast<PTRVALUE>(soundDesc);
	DWORD hashCode = RuGlobalHashGen().GetHashCode(hashKey);

	// Reclaim space from cache if necessary
	INT32 reclaimCount = 0, maxReclaimCount = m_soundDescriptorQueue->Count();
	SoundDescriptor *reclaimedSoundDesc;
	while(m_soundDescriptorQueue->Reclaim(hashKey, hashCode, soundDesc->m_soundDataSize, reclaimedSoundDesc))
	{
		if(reclaimedSoundDesc)
		{
			if(IsSoundDescriptorInUse(reclaimedSoundDesc))
			{
				// Re-insert descriptor into queue
				PTRVALUE reclaimedHashKey = reinterpret_cast<PTRVALUE>(reclaimedSoundDesc);
				DWORD reclaimedHashCode = RuGlobalHashGen().GetHashCode(reclaimedHashKey);
				m_soundDescriptorQueue->Add(reclaimedHashKey, reclaimedHashCode, reclaimedSoundDesc, reclaimedSoundDesc->m_soundDataSize);
			}
			else
			{
				// Remove sound descriptor from descriptor hash
				m_soundDescriptorHash->Remove(reclaimedSoundDesc->m_resourceName);

				// Free sound descriptor
				delete reclaimedSoundDesc;
			}
		}

		// If the maximum allowed reclaim attempts have been exceeded, abort
		if(reclaimCount >= maxReclaimCount)
			break;

		// Increment reclaim attempt count
		++reclaimCount;
	}

	// Add the collision object to the queue
	if(m_soundDescriptorQueue->Add(hashKey, hashCode, soundDesc, soundDesc->m_soundDataSize) == FALSE)
	{
		delete soundDesc;
		return NULL;
	}

	// Insert sound descriptor into cache
	m_soundDescriptorHash->Add(soundDesc->m_resourceName, soundDesc);

	return soundDesc;
}

BOOL CRuSymphony::IsSoundDescriptorInUse(CRuSymphony::SoundDescriptor *soundDesc)
{
	// Iterate through all sample handles
	for(INT32 i = 0; i < m_numSampleHandles; ++i)
	{
		// If the sound is playing through an active sample, return true
		if(AIL_sample_status(m_sampleDescriptors[i].m_sampleHandle) != SMP_DONE)
		{
			if(m_sampleDescriptors[i].m_sampleData == soundDesc->m_soundData)
			{
				return TRUE;
			}
		}
	}

	return FALSE;
}

BOOL CRuSymphony::ClearSoundDescriptorHash()
{
	// Iterate through each sound descriptor and destroy
	m_soundDescriptorHash->ForAllElements(boost::bind(&CRuSymphony::ClearSoundDescriptorHashCallback, this, _1, _2));

	// Clear all texture entries
	m_soundDescriptorHash->Clear();

	return TRUE;
}

BOOL CRuSymphony::ClearSoundDescriptorHashCallback(const void *key, void *data)
{
	SoundDescriptor *soundDesc = *((SoundDescriptor **) data);
	delete soundDesc;

	return TRUE;
}

BOOL CRuSymphony::ClearSoundDescriptorQueue()
{
	// Iterate through each sound descriptor and destroy
	m_soundDescriptorQueue->ForAllElements(boost::bind(&CRuSymphony::ClearSoundDescriptorQueueCallback, this, _1, _2));

	// Clear all texture entries
	m_soundDescriptorQueue->Clear();

	return TRUE;
}

BOOL CRuSymphony::ClearSoundDescriptorQueueCallback(const void *key, void *data)
{
	return TRUE;
}

// ************************************************************************************************************************************************************

CRuSymphony *g_ruSymphony = NULL;

// ************************************************************************************************************************************************************

#pragma managed
