/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, 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/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include <direct.h>
#include "HeadSpin.h"
#include "hsTimer.h"
#include "hsGeometry3.h"
#include "plgDispatch.h"
#include "plProfile.h"

#include "plWin32Sound.h"
#include "plWin32StreamingSound.h"
#include "plDSoundBuffer.h"
#include "plAudioSystem.h"

#include "plAudioCore/plAudioFileReader.h"
#include "plAudioCore/plSoundBuffer.h"
#include "plAudioCore/plSoundDeswizzler.h"
#include "pnMessage/plSoundMsg.h"
#include "pnMessage/plEventCallbackMsg.h"
#include "plStatusLog/plStatusLog.h"

#define STREAMING_UPDATE_MS 200

plProfile_Extern(MemSounds);
plProfile_CreateAsynchTimer( "Stream Shove Time", "Sound", StreamSndShoveTime );
plProfile_CreateAsynchTimer( "Stream Swizzle Time", "Sound", StreamSwizzleTime );
plProfile_Extern( SoundLoadTime );

plWin32StreamingSound::plWin32StreamingSound() :
fDataStream(nil),
fBlankBufferFillCounter(0),
fDeswizzler(nil),
fStreamType(kNoStream),
fLastStreamingUpdate(0),
fStopping(false),
fPlayWhenStopped(false),
fStartPos(0)
{
	fBufferLengthInSecs = plgAudioSys::GetStreamingBufferSize();
}

plWin32StreamingSound::~plWin32StreamingSound()
{
	/// Call before we delete our dataStream
	DeActivate();
	IUnloadDataBuffer();

	delete fDataStream;
	fDataStream = nil;
	fSrcFilename[ 0 ] = 0;

	delete fDeswizzler;
}

void plWin32StreamingSound::DeActivate()
{
	plWin32Sound::DeActivate();
}

// Change the filename used by this streaming sound, so we can play a different file
void plWin32StreamingSound::SetFilename(const char *filename, bool isCompressed)
{	
	fNewFilename = filename;
	fIsCompressed = isCompressed;
	fFailed = false;	// just in case the last sound failed to load turn this off so it can try to load the new sound
}

//////////////////////////////////////////////////////////////
//	Override, 'cause we don't want to actually LOAD the sound for streaming,
//	just make sure it's decompressed and such and ready to stream.
plSoundBuffer::ELoadReturnVal plWin32StreamingSound::IPreLoadBuffer( hsBool playWhenLoaded, hsBool isIncidental /* = false */ )
{
	if(fPlayWhenStopped)
		return plSoundBuffer::kPending;
	hsBool sfxPath = fNewFilename.size() ? false : true;
	
	if( fDataStream != nil && fNewFilename.size() == 0)
		return plSoundBuffer::kSuccess;		// Already loaded
		
	if(!ILoadDataBuffer())
	{
		return plSoundBuffer::kError;
	}
	plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();		
	if(!buffer)
		return plSoundBuffer::kError;
	
	// The databuffer also needs to know if the source is compressed or not
	if(fNewFilename.length())
	{
		buffer->SetFileName(fNewFilename.c_str());
		buffer->SetFlag(plSoundBuffer::kStreamCompressed, fIsCompressed);
		fNewFilename.clear();
		if(fReallyPlaying)
		{
			fPlayWhenStopped = true;
			return plSoundBuffer::kPending;
		}
	}

	if( buffer->IsValid() )
	{
		plAudioFileReader::StreamType type = plAudioFileReader::kStreamNative;
		fStreamType = kStreamCompressed;

		if(!buffer->HasFlag(plSoundBuffer::kStreamCompressed))
		{
			if(buffer->GetDataLengthInSecs() > plgAudioSys::GetStreamFromRAMCutoff( ))
			{
				fStreamType = kStreamFromDisk;
				type = plAudioFileReader::kStreamWAV;
			}
			else
			{
				fStreamType = kStreamFromRAM;
				type = plAudioFileReader::kStreamRAM;
			}
		}

		if(!fStartPos)
		{
			if(buffer->AsyncLoad(type, isIncidental ? 0 : STREAMING_BUFFERS * STREAM_BUFFER_SIZE ) == plSoundBuffer::kPending)
			{
				fPlayWhenLoaded = playWhenLoaded;
				fLoading = true;
				return plSoundBuffer::kPending;
			}
		}
		
		char str[ 256 ];
		strncpy( fSrcFilename, buffer->GetFileName(), sizeof( fSrcFilename ) );
		bool streamCompressed = (buffer->HasFlag(plSoundBuffer::kStreamCompressed) != 0);

		delete fDataStream;
		fDataStream = buffer->GetAudioReader();
		if(!fDataStream)
		{
			plAudioCore::ChannelSelect select = buffer->GetReaderSelect();

			bool streamCompressed = (buffer->HasFlag(plSoundBuffer::kStreamCompressed) != 0);

			/// Open da file
			CHAR strPath[ MAX_PATH ];
				
			_getcwd(strPath, MAX_PATH);
			if(sfxPath)
				strcat( strPath, "\\sfx\\" );
			else
			{
				// if we've changing the filename don't append 'sfx', just append a '\' since a path to the folder is expected
				strcat( strPath, "\\");
			}
			strcat( strPath, fSrcFilename );
			fDataStream = plAudioFileReader::CreateReader(strPath, select,type);
		}			
		
		if( fDataStream == nil || !fDataStream->IsValid() )
		{
			delete fDataStream;
			fDataStream = nil;
			return plSoundBuffer::kError;
		}

		sprintf( str, "   Readied file %s for streaming", fSrcFilename );
		IPrintDbgMessage( str );
		
		// dont free sound data until we have a chance to use it in load sound

		return fDataStream ? plSoundBuffer::kSuccess : plSoundBuffer::kError;
	}
		
	plStatusLog::AddLineS("audio.log", "EnsureLoadable failed for streaming sound %d", fDataBufferKey->GetName());
	return plSoundBuffer::kError;
}

void plWin32StreamingSound::IFreeBuffers( void )
{
	plWin32Sound::IFreeBuffers();
	if( fLoadFromDiskOnDemand && !IsPropertySet( kPropLoadOnlyOnCall ) )
	{
		// if the audio system has just restarted, dont delete the datastream. This way we can pick up where we left off instead of restarting the sound.
		if(!plgAudioSys::IsRestarting())
		{
			// we are deleting the stream, we must release the sound data.
			FreeSoundData();	
			delete fDataStream;
			fDataStream = nil;
		}
		fSrcFilename[ 0 ] = 0;
	}
}

///////////////////////////////////////////////////////////////////
//	Overload from plSound. Basically sets up the streaming file and fills the
//	first half of our buffer. We'll fill the rest of the buffer as we get
//	notifications for such.

hsBool plWin32StreamingSound::LoadSound( hsBool is3D )
{
	if( fFailed )
		return false;
	if( !plgAudioSys::Active() || fDSoundBuffer )
		return false;

	if( fPriority > plgAudioSys::GetPriorityCutoff() )
		return false;	// Don't set the failed flag, just return

	// Debug flag #1
	if( is3D && fChannelSelect > 0 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableRightSelect ) )
	{
		// Force a fail
		fFailed = true;
		return false;	
	}
	plSoundBuffer::ELoadReturnVal retVal = IPreLoadBuffer(true);
	if(retVal == plSoundBuffer::kPending)
		return true;

	if( retVal == plSoundBuffer::kError )
	{
		char str[ 256 ];
		sprintf( str, "Unable to open streaming source %s", fDataBufferKey->GetName() );
		IPrintDbgMessage( str, true );
		fFailed = true;
		return false;
	}

	SetProperty( kPropIs3DSound, is3D );

	plWAVHeader	header = fDataStream->GetHeader();
	UInt32 bufferSize = (UInt32)(fBufferLengthInSecs * header.fAvgBytesPerSec); 

	// Debug flag #2
	if( is3D && fChannelSelect == 0 && header.fNumChannels > 1 && plgAudioSys::IsDebugFlagSet( plgAudioSys::kDisableLeftSelect ) )
	{
		// Force a fail
		fFailed = true;
		return false;
	}

	if( header.fNumChannels > 1 && is3D )
	{
		// We can only do a single channel of 3D sound. So copy over one (later)
		bufferSize				/= header.fNumChannels;
		header.fBlockAlign		/= header.fNumChannels;
		header.fAvgBytesPerSec	/= header.fNumChannels;
		header.fNumChannels = 1;
	}

	// Actually create the buffer now (always looping)
	fDSoundBuffer = TRACKED_NEW plDSoundBuffer( bufferSize, header, is3D, IsPropertySet(kPropLooping), false, true );
	if( !fDSoundBuffer->IsValid() )
	{
		fDataStream->Close();
		delete fDataStream;
		fDataStream = nil;

		delete fDSoundBuffer;
		fDSoundBuffer = nil;

		char str[256];
		sprintf(str, "Can't create sound buffer for %s.wav. This could happen if the wav file is a stereo file. Stereo files are not supported on 3D sounds. If the file is not stereo then please report this error.", GetFileName());
		IPrintDbgMessage( str, true );
		fFailed = true;
		return false;
	}

	fTotalBytes = (UInt32)bufferSize;
	plProfile_NewMem(MemSounds, fTotalBytes);

	plSoundBuffer *buffer = (plSoundBuffer *)fDataBufferKey->ObjectIsLoaded();		
	if(!buffer)
		return false;

	bool setupSource = true;
	if(!buffer->GetData() || fStartPos)
	{ 
		if(fStartPos && fStartPos <= fDataStream->NumBytesLeft())
		{
			fDataStream->SetPosition(fStartPos);
			plStatusLog::AddLineS("syncaudio.log", "startpos %d", fStartPos);
		}

		// if we get here we are not starting from the beginning of the sound. We still have an audio loaded and need to pick up where we left off
		if(!fDSoundBuffer->SetupStreamingSource(fDataStream))
		{
			setupSource = false;
		}
	}
	else
	{
		// this sound is starting from the beginning. Get the data and start it.
		if(!fDSoundBuffer->SetupStreamingSource(buffer->GetData(), buffer->GetAsyncLoadLength()))
		{
			setupSource = false;
		}
	}
	
	if(!setupSource)
	{
		fDataStream->Close();
		delete fDataStream;
		fDataStream = nil;
		delete fDSoundBuffer;
		fDSoundBuffer = nil;

		plStatusLog::AddLineS("audio.log", "Could not play streaming sound, no voices left %s", GetKeyName());
		return false;
	}
	FreeSoundData();
	
	IRefreshEAXSettings( true );

	// Debug info
	char str[ 256 ];
	sprintf( str, "   Streaming %s.", fSrcFilename);
	IPrintDbgMessage( str );

	plStatusLog::AddLineS( "audioTimes.log", 0xffffffff, "Streaming %4.2f secs of %s", fDataStream->GetLengthInSecs(), GetKey()->GetUoid().GetObjectName() );

	// Get pertinent info
	SetLength( (hsScalar)fDataStream->GetLengthInSecs() );

	// Set up our deswizzler, if necessary
	delete fDeswizzler;
	if( fDataStream->GetHeader().fNumChannels != header.fNumChannels )
		fDeswizzler = TRACKED_NEW plSoundDeswizzler( (UInt32)(fBufferLengthInSecs * fDataStream->GetHeader().fAvgBytesPerSec), 
											 (UInt8)(fDataStream->GetHeader().fNumChannels), 
											 header.fBitsPerSample / 8 );
	else
		fDeswizzler = nil;

	// LEAVE THE WAV FILE OPEN! (We *are* streaming, after all :)
	return true;
}

void plWin32StreamingSound::IStreamUpdate()
{
	if(!fReallyPlaying)
		return;

	if(hsTimer::GetMilliSeconds() - fLastStreamingUpdate < STREAMING_UPDATE_MS)	// filter out update requests so we aren't doing this more that we need to 
		return;

	plProfile_BeginTiming( StreamSndShoveTime );
	if(fDSoundBuffer)
	{
		if(fDSoundBuffer->BuffersQueued() == 0 && fDataStream->NumBytesLeft() == 0 && !fDSoundBuffer->IsLooping())
		{
			// If we are fading out it's possible that we will hit this multiple times causing this sound to try and fade out multiple times, never allowing it to actually stop
			if(!fStopping)
			{
				fStopping = true;
				Stop();
				plProfile_EndTiming( StreamSndShoveTime );
			}
			return;
		}
		
		if(!fDSoundBuffer->StreamingFillBuffer(fDataStream))
		{
			plStatusLog::AddLineS("audio.log", "%s Streaming buffer fill failed", GetKeyName());
		}
	}
	plProfile_EndTiming( StreamSndShoveTime );
}

void plWin32StreamingSound::Update()
{
	plWin32Sound::Update();
	IStreamUpdate();
}

void plWin32StreamingSound::IDerivedActuallyPlay( void )
{
	fStopping = false;
	if( !fReallyPlaying )
	{
		for(;;)
		{
			if(fSynchedStartTimeSec)
			{
				// if we are synching to another sound this is our latency time
				fDSoundBuffer->SetTimeOffsetSec((float)(hsTimer::GetSeconds() - fSynchedStartTimeSec));
				fSynchedStartTimeSec = 0;
			}

			if(IsPropertySet(kPropIncidental))
			{
				if(fIncidentalsPlaying >= MAX_INCIDENTALS)
					break;
				++fIncidentalsPlaying;
			}
			fDSoundBuffer->Play();
			fReallyPlaying = true;
			break;
		}
	}

	/// Send start callbacks
	plSoundEvent	*event = IFindEvent( plSoundEvent::kStart );
	if( event != nil )
		event->SendCallbacks();
}

void plWin32StreamingSound::IActuallyStop()
{	
	fStopping = false;
	plWin32Sound::IActuallyStop();
	if(fPlayWhenStopped)
	{
		fPlayWhenStopped = false;
		Play();
	}
}

unsigned plWin32StreamingSound::GetByteOffset()
{
	if(fDataStream && fDSoundBuffer)
	{	
		unsigned bytesQueued = fDSoundBuffer->BuffersQueued() * STREAM_BUFFER_SIZE;
		unsigned offset = fDSoundBuffer->GetByteOffset();
		long byteoffset = ((fDataStream->GetDataSize() - fDataStream->NumBytesLeft()) - bytesQueued) + offset;
		
		return byteoffset < 0 ? fDataStream->GetDataSize() - abs(byteoffset) : byteoffset;
	}
	return 0;
}

float plWin32StreamingSound::GetActualTimeSec()
{
	if(fDataStream && fDSoundBuffer)
		return fDSoundBuffer->BytePosToMSecs(fDataStream->NumBytesLeft()) / 1000.0f;
	return 0.0f;
}

void plWin32StreamingSound::SetStartPos(unsigned bytes)
{
	fStartPos = bytes;
}

void plWin32StreamingSound::ISetActualTime( double t )
{
	//fStartTimeSec = 0;
	if(fDataStream && fDSoundBuffer)
		fDataStream->SetPosition(fDSoundBuffer->GetBufferBytePos((float)t));
	//else
	//	fStartTimeSec = t;
}

hsBool plWin32StreamingSound::MsgReceive( plMessage* pMsg )
{
	return plWin32Sound::MsgReceive( pMsg );
}

void plWin32StreamingSound::IRemoveCallback( plEventCallbackMsg *pCBMsg )
{
	plWin32Sound::IRemoveCallback( pCBMsg );
}

void plWin32StreamingSound::IAddCallback( plEventCallbackMsg *pCBMsg )
{
	if( plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStop &&
		plSoundEvent::GetTypeFromCallbackMsg( pCBMsg ) != plSoundEvent::kStart )
	{
		hsAssert( false, "Streaming sounds only support start and stop callbacks at this time." );
		return;
	}
	plWin32Sound::IAddCallback( pCBMsg );
}