#include "SdlAudioRenderer.h"
#include <avifile.h>
#include <cpuinfo.h>
#include <utils.h>
#include <math.h>
#include <iostream>

using namespace std;

#undef Debug
#define Debug if(0)

#define __MODULE__ "SdlAudioRenderer"

//#define USE_ALOCK

SdlAudioRenderer::SdlAudioRenderer(IAviReadStream* as, WAVEFORMATEX& Owf,
				   unsigned int useFreq = 0)
    :IAudioRenderer(as, Owf)
{
    try
    {
	SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);

	SDL_AudioSpec spec;
	if (useFreq)
	    spec.freq = useFreq;
	else
	    spec.freq = m_Owf.nSamplesPerSec;
	spec.format = (m_Owf.wBitsPerSample == 16) ? AUDIO_S16 : AUDIO_U8;
	spec.samples = (spec.freq > 22000) ? 4096 : 2048;
	// shorter buffer generates more interrupts
	// and would give us more precise timing -
	// on the other hand it's more sensitive to the system load
        // for slowly moving sound - shorter buffer is better
	spec.channels = m_Owf.nChannels;
	spec.callback = fillAudio;
	spec.userdata = this;
	if (SDL_OpenAudio(&spec, &m_Spec) < 0)
	{
	    Debug cout << "SdlAudioRenderer: Fatal error: " << SDL_GetError() << endl;
	    throw FATAL("Couldn't open audio device");
	}
#ifdef USE_ALOCK
	SDL_PauseAudio(0);
	SDL_LockAudio();
#endif
	Debug cout<<"SdlAudioRenderer: Error: "<<SDL_GetError()<<endl;
	m_fAsync = 0.0f;
	m_dSpecTime = m_Spec.size/(double) m_pQueue->GetBytesPerSec();
	//cout << "SpecTime: " << m_Spec.freq << "  " << m_dSpecTime << endl;
	Debug cout << "SdlAudioRenderer: audio buffer size = " << m_Spec.size << " freq: " << m_Spec.freq  << endl;
    }
    catch(FatalError& error)
    {
	m_pAudiostream = 0;
	throw;
    }
}

SdlAudioRenderer::~SdlAudioRenderer()
{
    Debug cout << "SdlAudioRenderer: Destroy()" << endl;
    m_bQuit = true;
    m_bInitialized = false;
    m_pQueue->Broadcast();
#ifdef USE_ALOCK
    cout << "Destr audio" << endl;
    SDL_UnlockAudio();
#endif
    SDL_CloseAudio();
    Debug cout << "SdlAudioRenderer: Destroy() successful" << endl;
}

void SdlAudioRenderer::fillAudio(void* userdata, unsigned char* stream, int len)
{
    Debug cout << "SdlAudioRenderer: fillAudio()" << endl;

    if (!userdata)
	return;

    SdlAudioRenderer& a = *(SdlAudioRenderer*)userdata;

    while (!a.m_bQuit && a.m_bInitialized && !a.m_bPaused)
    {
	Debug cout << "SdlAudioRenderer: before read: realpos " << a.m_dAudioRealpos << ", diff " << to_float(longcount(), a.m_lAudioTime) << endl;

	if (a.m_pQueue->GetSize() < unsigned(len))
	{
	    if (a.m_pAudiostream->Eof())
	    {
		memset(stream, 0, len);
                len = a.m_pQueue->GetSize();
                if (len == 0)
		    break;
                continue;
	    }
	    Debug cout<<"SdlAudioRenderer: waiting: size "<<a.m_pQueue->GetSize()<<", len "<<len<<endl;
	    //cout << "wait" << endl;
	    a.m_pQueue->Lock();
	    if (a.m_pQueue->GetSize() < unsigned(len)
		&& !a.m_bQuit && a.m_bInitialized && !a.m_bPaused)
		a.m_pQueue->Wait();
	    a.m_pQueue->Unlock();
	    continue;
	}

	a.m_pQueue->Read((char*)stream, len, &a.m_Audiomix);

        int64_t ct = longcount();
	if (a.m_dPauseTime > 0)
	{
	    a.m_lTimeStart += ct - a.m_lAudioTime;
	    //Debug cout << "Restart after Pause" << endl;
	    if (to_float(ct, a.m_lAudioTime) > a.m_dSpecTime)
	    {
		//cout << "PAUSE fix  " << to_float(ct, a.m_lAudioTime) << endl;
		a.m_dAudioRealpos += a.m_dSpecTime;
	    }
            a.m_dPauseTime = 0.0;
	}
	a.m_lAudioTime = ct;
	if (a.m_lTimeStart == 0)
	{
	    a.m_lTimeStart = ct;
	    //a.m_dAudioRealpos -= a.m_dSpecTime;
	}

#if 0
	// some gcc compiler doesn't like to compile int64 output for ostream
        // we don't need this code so its commented out
	Debug
	{
	    static uint64_t mylast = 0;
	    float dd = to_float(longcount(), mylast);
	    mylast = longcount();
	    cout << "SdlAudioRenderer: after read: stream pos " << a.m_pAudiostream->GetTime()
		<< ", async " << a.m_fAsync
		<< ", spec_size " << a.m_Spec.size
		<< ", m_pQueue " << a.m_pQueue->size()
		<< ", realpos " << a.m_dAudioRealpos
		<< ", channels " << a.m_Owf.nChannels
		<< ", bitspersample " << a.m_Owf.wBitsPerSample
		<< ", persec " << a.m_Owf.nSamplesPerSec
		<< endl;
	}
#endif
	break;
    }
}

void SdlAudioRenderer::pause(int v)
{
#ifdef USE_ALOCK
    cout << "PAUSE " << v << endl;
    if (v)
	SDL_LockAudio();
    else
    {
	m_lTimeStart += (longcount() - m_lAudioTime);
	SDL_UnlockAudio();
    }
    cout << "PAUSE done" << endl;
#else
    SDL_PauseAudio(v);
#endif
}

HRESULT  SdlAudioRenderer::SetVolume(float volume)
{
    HRESULT hr = IAudioRenderer::SetVolume(volume);
    if (hr == 0)
	m_Audiomix.SetVolume(m_fVolume);
    return hr;
}

// SdlAudioMix implementation

SdlAudioMix::SdlAudioMix()
{
    SetVolume();
}

void SdlAudioMix::Mix(void* data, const void* src, size_t n) const
{
    // SDL is broken here - it really should use const pointer for src
    if (m_iVolume != SDL_MIX_MAXVOLUME)
	SDL_MixAudio((Uint8*) data, (Uint8*) src, n, m_iVolume);
    else
	memcpy(data, src, n);
}
