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

	Implementation of base audio renderer class

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

#include "IAudioRenderer.h"
#include "AudioQueue.h"
#include "RegAccess.h"

#include <avifile.h>
#include <cpuinfo.h>
#include <utils.h>
#include <iostream>
#include <cstdio>

using namespace std;

#undef Debug
#define Debug if(0)

#define __MODULE__ "IAudioRenderer"

template<class T> inline T mymax(const T x, const T y)
{
    return (x > y) ? x : y;
}

IAudioRenderer::IAudioRenderer(IAviReadStream* as, WAVEFORMATEX& Owf)
{
    m_bInitialized = false;
    m_bPaused = true;
    m_bQuit = false;
    m_dAudioRealpos = 0.0;
    m_lAudioTime = 0;
    m_lTimeStart = 0;
    m_pucLocalFrame = 0;
    m_pQueue = 0;
    m_fVolume = 1.0;
    m_Owf = Owf;
    m_dPauseTime = 0.0;

    try {
	m_pAudiostream = as;

	if (!m_pAudiostream)
	    throw FATAL("NULL m_pAudiostream");

	int audio_status = m_pAudiostream->StartStreaming();
	if (audio_status != 0)
	    throw FATAL("Failed to start streaming");

        // needed for bitrate in nAvgBytesPerSec
	WAVEFORMATEX Inwf;
	m_pAudiostream->GetAudioFormatInfo(&Inwf, 0);
	m_pAudiostream->GetOutputFormat(&m_Iwf, sizeof(m_Iwf));

	if (m_Owf.nChannels == 0)
            m_Owf.nChannels = m_Iwf.nChannels;

	if (m_Owf.nSamplesPerSec == 0)
            m_Owf.nSamplesPerSec = m_Iwf.nSamplesPerSec;

	if (m_Owf.wBitsPerSample == 0)
	    m_Owf.wBitsPerSample = m_Iwf.wBitsPerSample;

	sprintf(m_pcAudioDesc, " %s   %dkbit/s %dHz %s",
                wave_type(Inwf.wFormatTag),
		(int)Inwf.nAvgBytesPerSec / 128, (int)m_Iwf.nSamplesPerSec,
		(m_Iwf.nChannels != 1) ? "stereo" : "mono");

	Debug cout << "IAudioRenderer: Audio format " << m_Owf.nSamplesPerSec
	    << "/" << m_Owf.wBitsPerSample
	    << "/" << ((m_Owf.nChannels!=1)?"stereo":"mono")
	    << endl;

	m_pQueue = new AudioQueue(m_Iwf, m_Owf);
    }
    catch(FatalError& error)
    {
        delete m_pQueue;
	throw;
    }
}

IAudioRenderer::~IAudioRenderer()
{
    m_Mutex.Lock();
    m_pQueue->Lock();
    delete m_pucLocalFrame;
    delete m_pQueue;
    m_pQueue = 0;
}

void IAudioRenderer::Extract(double video_time)
{
    static const size_t sound_size_max = 176400U;
    // 1s of stereo 16b 44kHz sound

    if (m_bQuit)
	return;

    if (m_pQueue->GetSize() > sound_size_max)
    {
	//cout << "Audio stream is full" << endl;
	avi_usleep(10000);
	return;
    }

    const size_t one_frame_sound = mymax(20000U, m_pAudiostream->GetFrameSize());
    size_t ocnt;

    {
	Locker locker(m_Mutex);

	if (Eof())
	{
	    cout << "Audio stream finished" << endl;
	    //pause(1);
	    //m_bInitialized = false;
	    //m_bPaused = false;
	    m_pQueue->Clear();
	    return;
	}

	if (!m_pucLocalFrame)
	    m_pucLocalFrame = new uint8_t[one_frame_sound];

	size_t samples;
	m_pAudiostream->ReadFrames(m_pucLocalFrame, one_frame_sound,
				   one_frame_sound, samples, ocnt);

	if (samples > one_frame_sound)
	    cout<<"OOPS: samples > one_frame_sound at "<<__FILE__<<endl;
	if (ocnt > one_frame_sound)
	    cout<<"OOPS: ocnt > one_frame_sound at "<<__FILE__<<endl;
	Debug cout<<"doAudioExtract: Read "<<samples<<" samples ( "<<ocnt<<" bytes )"<<endl;

	// reseek skiping is handled by AudioQueue
	// we can't just simply remove the packet as this makes
	// the sound asynchronous
	//if (m_iReseekSkip > 0)
	//{
	//    m_iReseekSkip--;
	//    return;
	//}
    }
    m_pQueue->Push(m_pucLocalFrame, ocnt);
}

double IAudioRenderer::GetBufferTime() const
{
    if (!m_bInitialized)
        return 0.0;

    double btime = m_pQueue->GetBufferTime();

    Debug cout << "IAudioRenderer: BufferTime: " << btime  << " Queue size: "
	<< m_pQueue->GetSize() << " Atime " << m_lAudioTime << endl;

    return btime;
}

void IAudioRenderer::Clear()
{
    Locker locker(m_Mutex);
    //cout << "IAudioRenderer: CLEAR" << endl;
    m_pQueue->Clear();
}

const char* IAudioRenderer::GetAudioFormat() const
{
    return m_pcAudioDesc;
}

double IAudioRenderer::GetCacheSize() const
{
    return m_pAudiostream->CacheSize();
}

double IAudioRenderer::GetLengthTime() const
{
    return m_pAudiostream->GetLengthTime();
}

double IAudioRenderer::GetTime()
{
    if (m_dPauseTime)
	return m_dPauseTime;

    double actual_time = m_dAudioRealpos;

    if (m_lTimeStart == 0)
    {
	if (m_lAudioTime == 0)
	{
	    m_lAudioTime++; // prevent this setting again
	    // check again with lock
	    Debug cout << "Reset m_dAudioRealpos = " << m_dAudioRealpos
		<< "  (stream: " << m_pAudiostream->GetTime()
		<< " buffered " << GetBufferTime()
		<< " async " << m_fAsync << endl;
	}
    }
    else
	actual_time += to_float(longcount(), m_lTimeStart);
	//actual_time = m_pAudiostream->GetTime() - GetBufferTime()
	//    + to_float(longcount(), m_lAudioTime);

    //cout << "stream: " << m_pAudiostream->GetTime()
    //    << " buffered " << GetBufferTime()
    //    << " atime " << actual_time  << endl;


    actual_time -= m_fAsync;
    return (actual_time > 0) ? actual_time : 0.0;
}

bool IAudioRenderer::Eof() const
{
    return m_pAudiostream->Eof() && (GetBufferTime() < 0.01);
}

HRESULT IAudioRenderer::Pause(bool state)
{
    Locker locker(m_Mutex);

    //cout << "Pausing:  " << state << "   init " << m_bInitialized << endl;
    if (!m_bInitialized)
	return -1;

    if (m_bPaused != state)
    {
	m_bPaused = state;
        if (state)
	    Wake(); // in case audio renderer waits for new data
	pause((state) ? 1 : 0);
	if (state)
            m_dPauseTime = GetTime();
    }
    return 0;
}

HRESULT IAudioRenderer::Reseek(double pos)
{
    Locker locker(m_Mutex);

    int hr = m_pAudiostream->SeekTime(pos + m_fAsync);
    if (!m_bInitialized || hr != 0)
	return -1;

    // do not skip sound when seeking to the begining
    // this sound should be clean
    m_bInitialized = false;
    Wake(); // sets m_bInitialized = false;
    m_pQueue->Clear();
    m_lAudioTime = m_lTimeStart = 0;
    m_dAudioRealpos = m_pAudiostream->GetTime();
    m_dPauseTime = 0.0;
    m_bInitialized = true;
    return 0;
}

HRESULT IAudioRenderer::Skip(double video_time)
{
    Locker locker(m_Mutex);
    return m_pAudiostream->SkipTo(video_time);
}

void IAudioRenderer::SetAsync(float async)
{
    m_fAsync = async;
}

HRESULT IAudioRenderer::SetVolume(float volume)
{
    if (volume < 0.0 || volume > 1.0)
	return -1;

    m_fVolume = volume;
    return 0;
}

HRESULT IAudioRenderer::SetPlayingRate(int rate)
{
    return -1;
}

HRESULT IAudioRenderer::SetResamplingRate(int rate)
{
    return -1;
}

void IAudioRenderer::Start()
{
    {
	Locker locker(m_Mutex);
	if (m_bInitialized)
	{
	    Debug cout << "IAudioRenderer: can't start(), already started" << endl;
	    return;
	}
	m_bInitialized = true;
    }
    Reseek(0);
    Pause(false);
}

void IAudioRenderer::Stop()
{
    {
	Locker locker(m_Mutex);
	if (!m_bInitialized)
	    return;
	m_pQueue->Clear();
    }
    Pause(true);
    m_bInitialized = false;
}

void IAudioRenderer::Wake()
{
    m_pQueue->Broadcast();
}

/// to be overloaded
void IAudioRenderer::pause(int)
{
}

void IAudioRenderer::reset()
{
}
