/*
* SND_Scene.cpp
*
* The scene for sounds.
*
* $Id: SND_Scene.cpp,v 1.5 2003/07/16 18:23:46 michel Exp $
*
 * ***** BEGIN GPL/BL DUAL LICENSE BLOCK *****
 *
 * 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 2
 * of the License, or (at your option) any later version. The Blender
 * Foundation also sells licenses for use in proprietary software under
 * the Blender License.  See http://www.blender.org/BL/ for information
 * about this.
 *
 * 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, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): none yet.
 *
 * ***** END GPL/BL DUAL LICENSE BLOCK *****
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef WIN32
#pragma warning (disable:4786) // Get rid of stupid stl-visual compiler debug warning
#endif //WIN32

#include "SND_Scene.h"
#include "SND_DependKludge.h"
#include "SND_IAudioDevice.h"

#include <stdlib.h>
#include <iostream>

//static unsigned int tijd = 0;

SND_Scene::SND_Scene(SND_IAudioDevice* audiodevice)
					 : m_audiodevice(audiodevice)
{
	if (m_audiodevice)
		m_wavecache = m_audiodevice->GetWaveCache();

	if (!m_wavecache || !audiodevice)
	{
		m_audio = false;
	}
	else
	{
		//if so, go ahead!
		m_audio = true;
#ifdef ONTKEVER
		printf("SND_Scene::SND_Scene() m_audio == true\n");
#endif
		m_audiodevice->InitListener();
	}

	IsPlaybackWanted();
}



SND_Scene::~SND_Scene()
{
	StopAllObjects();
}



// check if audioplayback is wanted
bool SND_Scene::IsPlaybackWanted()
{
    /* Removed the functionality for checking if noaudio was provided on */
    /* the commandline. */
	if (m_audiodevice && m_wavecache)
	{
		m_audioplayback = true;
	}
	else
	{
		StopAllObjects();
		m_audioplayback = false;
	}

	return m_audioplayback;
}



int SND_Scene::LoadSample(const STR_String& samplename,
						   void* memlocation,
						   int size)
{
	int result = -1;

	if (m_audiodevice)
	{
		SND_WaveSlot* waveslot = m_audiodevice->LoadSample(samplename, memlocation, size);

		if (waveslot)
			result = waveslot->GetBuffer();
	}

	return result;
}



void SND_Scene::RemoveAllSamples()
{
	if (m_audio && m_audiodevice)
		m_audiodevice->RemoveAllSamples();
}



bool SND_Scene::CheckBuffer(SND_SoundObject* pObject)
{
	bool result = false;

	if (pObject && m_wavecache)
	{
		SND_WaveSlot* waveslot = m_wavecache->GetWaveSlot(pObject->GetSampleName());
		
		if (waveslot)
		{
			pObject->SetBuffer(waveslot->GetBuffer());

			result = true;
		}
	}

	return result;
}



bool SND_Scene::IsSampleLoaded(STR_String& samplename)
{
	bool result = false;

	if (samplename && m_wavecache)
	{
		SND_WaveSlot* waveslot = m_wavecache->GetWaveSlot(samplename);

		if (waveslot && waveslot->IsLoaded())
			result = true;
	}

	return result;
}



void SND_Scene::AddObject(SND_SoundObject* pObject)
{
	if (m_audio)
	{
		STR_String samplename = pObject->GetSampleName();
		SND_WaveSlot* slot = NULL;
		
		// don't add the object if no valid sample is referenced
		if (samplename != "")
		{	
			// check if the sample is already loaded
			slot = m_wavecache->GetWaveSlot(samplename);
		}
		
		if (slot)
		{
			pObject->SetBuffer(slot->GetBuffer());
			
			// needed for expected lifespan of the sample, but ain't necesary anymore i think
			MT_Scalar samplelength = slot->GetNumberOfSamples();
			MT_Scalar samplerate = slot->GetSampleRate();
			MT_Scalar soundlength = samplelength/samplerate;
			pObject->SetLength(soundlength);
			
			// add the object to the list
			m_soundobjects.insert((SND_SoundObject*)pObject);
		}
	}
}



void SND_Scene::SetListenerTransform(const MT_Vector3& pos,
									 const MT_Vector3& vel,
									 const MT_Matrix3x3& ori)
{
	if (m_audio)
	{
		GetListener()->SetPosition(pos);
		GetListener()->SetVelocity(vel);
		GetListener()->SetOrientation(ori);
	}
}



void SND_Scene::UpdateListener()
{
	// process the listener if modified
	if (m_listener.IsModified())
	{
		m_audiodevice->SetListenerGain(m_listener.GetGain());
		
		// fmod doesn't support dopplervelocity, so just use the dopplerfactor instead
#ifdef USE_FMOD
		m_audiodevice->SetDopplerFactor(m_listener.GetDopplerVelocity());
#else
		m_audiodevice->SetDopplerVelocity(m_listener.GetDopplerVelocity());
		m_audiodevice->SetDopplerFactor(m_listener.GetDopplerFactor());
#endif
		m_listener.SetModified(false);
	}
}



void SND_Scene::AddActiveObject(SND_SoundObject* pObject, MT_Scalar curtime)
{
	if (m_audio)
	{
		if (pObject)
		{
#ifdef ONTKEVER
			printf("SND_Scene::AddActiveObject\n");
#endif
			
			// first check if the object is already on the list
			if (pObject->IsActive())
			{
				pObject->SetTimeStamp(curtime);
				pObject->StartSound();
			}
			else
			{	
				pObject->SetTimeStamp(curtime);
				
				// compute the expected lifespan
				pObject->SetLifeSpan();
				
				// lets give the new active-to-be object an id
				if (m_audiodevice->GetNewId(pObject))
				{
					// and add the object
					m_activeobjects.addTail(pObject);
					pObject->StartSound();
					pObject->SetActive(true);
				}
			}
		}
	}
}



void SND_Scene::RemoveActiveObject(SND_SoundObject* pObject)
{
	if (m_audio)
	{
		if (pObject)
		{
#ifdef ONTKEVER
			printf("SND_Scene::RemoveActiveObject\n");
#endif
			// if inactive, remove it from the list
			if (pObject->IsActive())
			{	
				// first make sure it is stopped
				m_audiodevice->ClearId(pObject);
			}
		}
	}
}



void SND_Scene::UpdateActiveObects()
{
//	++tijd;

	SND_SoundObject* pObject;
	// update only the objects that need to be updated
	for (pObject = (SND_SoundObject*)m_activeobjects.getHead();
					!pObject->isTail();
					pObject = (SND_SoundObject*)pObject->getNext())
	{
		int id = pObject->GetId();
		
		if (id >= 0)
		{
			bool juststartedplaying = false;
#ifdef USE_FMOD
			// fmod wants these set before playing the sample
			if (pObject->IsModified())
			{
				m_audiodevice->SetObjectLoop(id, pObject->GetLoopMode());
				m_audiodevice->SetObjectLoopPoints(id, pObject->GetLoopStart(), pObject->GetLoopEnd());
			}

			// ok, properties Set. now see if it must play
			if (pObject->GetPlaystate() == SND_MUST_PLAY)
			{
				m_audiodevice->PlayObject(id);
				pObject->SetPlaystate(SND_PLAYING);
				pObject->InitRunning();
//				printf("start play: %d\n", tijd);
				juststartedplaying = true;
			}
#endif
			if (pObject->Is3D())
			{
				// Get the global positions and velocity vectors
				// of the listener and soundobject
				MT_Vector3 op = pObject->GetPosition();
				MT_Vector3 lp = m_listener.GetPosition();
				MT_Vector3 position = op - lp;
				
				// Calculate relative velocity in global coordinates
				// of the sound with respect to the listener.
				MT_Vector3 ov = pObject->GetVelocity();
				MT_Vector3 lv = m_listener.GetVelocity();
				MT_Vector3 velocity = ov - lv;
				
				// Now map the object position and velocity into 
				// the local coordinates of the listener.
				MT_Matrix3x3 lo = m_listener.GetOrientation();
				
				MT_Vector3 local_sound_pos = position * lo;
				MT_Vector3 local_sound_vel = velocity * lo;
				
				m_audiodevice->SetObjectTransform(
					id,
					local_sound_pos,
					local_sound_vel,
					pObject->GetOrientation(), // make relative to listener! 
					lp,
					pObject->GetRollOffFactor());
			}
			else
			{
				m_audiodevice->ObjectIs2D(id);
			}
			
			// update the situation
			if (pObject->IsModified())
			{
				m_audiodevice->SetObjectPitch(id, pObject->GetPitch());
				m_audiodevice->SetObjectGain(id, pObject->GetGain());
				m_audiodevice->SetObjectMinGain(id, pObject->GetMinGain());
				m_audiodevice->SetObjectMaxGain(id, pObject->GetMaxGain());
				m_audiodevice->SetObjectReferenceDistance(id, pObject->GetReferenceDistance());
				m_audiodevice->SetObjectRollOffFactor(id, pObject->GetRollOffFactor());
				m_audiodevice->SetObjectLoop(id, pObject->GetLoopMode());
				m_audiodevice->SetObjectLoopPoints(id, pObject->GetLoopStart(), pObject->GetLoopEnd());
				pObject->SetModified(false);
			}

			pObject->AddRunning();

#ifdef ONTKEVER				
			STR_String naam = pObject->GetObjectName();
			STR_String sample = pObject->GetSampleName();
			
			int id = pObject->GetId();
			int buffer = pObject->GetBuffer();
			
			float gain = pObject->GetGain();
			float pitch = pObject->GetPitch();
			float timestamp = pObject->GetTimestamp();
			
			printf("naam: %s, sample: %s \n", naam.Ptr(), sample.Ptr());
			printf("id: %d, buffer: %d \n", id, buffer);
			printf("gain: %f, pitch: %f, ts: %f \n\n", gain, pitch, timestamp);
#endif
#ifdef USE_OPENAL
			// ok, properties Set. now see if it must play
			if (pObject->GetPlaystate() == SND_MUST_PLAY)
			{
				m_audiodevice->PlayObject(id);
				pObject->SetPlaystate(SND_PLAYING);
				//break;
			}
#endif

			// check to see if the sound is still playing
			// if not: release its id
			int playstate = m_audiodevice->GetPlayState(id);
#ifdef ONTKEVER
			if (playstate != 2)
				printf("%d - ",playstate);
#endif

//			if ((playstate == SND_STOPPED && (!juststartedplaying) && !pObject->GetLoopMode() && pObject->IsRunning())
#if defined(WIN32) || defined(__APPLE__)
			if ((playstate == SND_STOPPED) && !pObject->GetLoopMode())
#else
			if (!pObject->GetLoopMode())
#endif
			{
//				printf("remove: %d\n", tijd);
				RemoveActiveObject(pObject);
			}
		}
	}
}



void SND_Scene::UpdateCD()
{
	if (m_audiodevice)
	{
		SND_CDObject* pCD = SND_CDObject::Instance();

		if (pCD)
		{
			int playstate = pCD->GetPlaystate();
			
			switch (playstate)
			{
			case SND_MUST_PLAY:
				{
					// initialize the cd only when you need it
					m_audiodevice->SetCDGain(pCD->GetGain());
					m_audiodevice->SetCDPlaymode(pCD->GetPlaymode());
					m_audiodevice->PlayCD(pCD->GetTrack());
					pCD->SetPlaystate(SND_PLAYING);
					pCD->SetUsed();
					break;
				}
			case SND_MUST_PAUSE:
				{
					m_audiodevice->PauseCD(true);
					pCD->SetPlaystate(SND_PAUSED);
					break;
				}
			case SND_MUST_RESUME:
				{
					m_audiodevice->PauseCD(false);
					pCD->SetPlaystate(SND_PLAYING);
					break;
				}
			case SND_MUST_STOP:
				{
					m_audiodevice->StopCD();
					pCD->SetPlaystate(SND_STOPPED);
					break;
				}
			default:
				{
				}
			}
			
			// this one is only for realtime modifying settings
			if (pCD->IsModified())
			{
				m_audiodevice->SetCDGain(pCD->GetGain());
				pCD->SetModified(false);
			}
		}
	}
}



void SND_Scene::Proceed()
{
	if (m_audio && m_audioplayback)
	{
		m_audiodevice->MakeCurrent();

		UpdateListener();
		UpdateActiveObects();
		UpdateCD();

//		m_audiodevice->UpdateDevice();
	}
}


void SND_Scene::DeleteObject(SND_SoundObject* pObject) 
{
#ifdef ONTKEVER
	printf("SND_Scene::DeleteObject\n");
#endif
	
	if (pObject)
	{
		if (m_audiodevice)
			m_audiodevice->ClearId(pObject);
		
		// must remove object from m_activeList
		std::set<SND_SoundObject*>::iterator set_it;
		set_it = m_soundobjects.find(pObject);
		
		if (set_it != m_soundobjects.end())
			m_soundobjects.erase(set_it);
		
		// release the memory
		delete pObject;
		pObject = NULL;
	}
}



void SND_Scene::RemoveAllObjects()
{
#ifdef ONTKEVER
	printf("SND_Scene::RemoveAllObjects\n");
#endif

	StopAllObjects();

	std::set<SND_SoundObject*>::iterator it = m_soundobjects.begin();

	while (it != m_soundobjects.end())
	{
		delete (*it);
		it++;
	}

	m_soundobjects.clear();
}



void SND_Scene::StopAllObjects()
{
	if (m_audio)
	{
#ifdef ONTKEVER
		printf("SND_Scene::StopAllObjects\n");
#endif
		
		SND_SoundObject* pObject;
		
		for (pObject = (SND_SoundObject*)m_activeobjects.getHead();
		!pObject->isTail();
		pObject = (SND_SoundObject*)pObject->getNext())
		{
			m_audiodevice->ClearId(pObject);
		}
	}
}



SND_SoundListener* SND_Scene::GetListener()
{
	return &m_listener;
}
