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

	Audio queue implementation

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

#include "AudioQueue.h"
#include "IAudioResampler.h"
#include "AudioCleaner.h"

#if 1
// slower but has much better precision
#define RESAMPLERSTEREO AudioFpHQResamplerStereo
#define RESAMPLERMONO AudioFpHQResamplerMono
#include "AudioFpHQResampler.h"
#else
// faster but not very good method
#define RESAMPLERSTEREO AudioIntResamplerStereo
#define RESAMPLERMONO AudioIntResamplerMono
#include "AudioIntResampler.h"
#endif

#include <utils.h>

#include <unistd.h> //write

#include <iostream>
#include <cstring> //memcpy
#include <cstdio>

#include <cpuinfo.h>

#undef Debug
#define Debug if (0)
// using different strategy - caller is responsible for holding lock!

using namespace std;

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

AudioQueue::AudioQueue(WAVEFORMATEX& Iwf, WAVEFORMATEX& Owf)
{
    m_uiFrameIn = m_uiFrameOut = m_uiFrameSize = 0;
    m_dRemains = 0.0;
    m_pResampler = 0;
    m_bCleared = false;
    m_Iwf = Iwf;
    m_Owf = Owf;

    m_uiBytesPerSec = (m_Owf.nChannels * (m_Owf.wBitsPerSample / 8)
		       * m_Owf.nSamplesPerSec);

    m_uiFrameMax = 256*1024;//(unsigned int) (m_uiBytesPerSec * BUFFER_TIME) & ~0xff;
    //cout << "Audio buffer size: " << m_uiFrameMax << endl;
    m_pcAudioFrame = new unsigned char[2 * m_uiFrameMax];

    size_t clearsz = (m_uiBytesPerSec / 10) & ~3U;  // 0.1 sec
    m_pCleaner = CreateAudioCleaner(m_Owf.nChannels, m_Owf.wBitsPerSample,
				    clearsz);
}

AudioQueue::~AudioQueue()
{
    delete m_pCleaner;
    delete m_pResampler;
    delete m_pcAudioFrame;
}

int AudioQueue::Broadcast()
{
    Locker locker(m_Mutex);
    return m_Cond.Broadcast();
}

void AudioQueue::Clear()
{
    Locker locker(m_Mutex);

    Debug cout << "AudioQueue: clear " << endl;

    m_uiFrameIn = (m_pCleaner) ? m_pCleaner->soundOff(m_pcAudioFrame,
						      m_pcAudioFrame + m_uiFrameOut) : 0;
    m_uiFrameSize = m_uiFrameIn;
    m_uiFrameOut = 0;
    m_bCleared = true;

    m_Cond.Broadcast();
}

/*  ineffective */
size_t AudioQueue::Resample(void* dest, const void* src, size_t src_size)
{
    if (m_Iwf.wBitsPerSample != m_Owf.wBitsPerSample
	|| m_Iwf.nChannels != m_Owf.nChannels
	|| (m_Iwf.nChannels != 1 && m_Iwf.nChannels != 2))
    {
	cerr << "AudioQueue::resample()  unsupported resampling conversion!" << endl
	    << "From:  bps: " << m_Iwf.wBitsPerSample
	    << "  ch: " << m_Iwf.nChannels
	    << "  To:  bps: " << m_Owf.wBitsPerSample
	    << "  ch: " << m_Owf.nChannels << endl;
        return 0;
    }

    if (!m_pResampler || (int)m_pResampler->getBitsPerSample() != m_Owf.wBitsPerSample)
    {
	delete m_pResampler;

	m_pResampler = CreateHQResampler(m_Owf.nChannels, m_Owf.wBitsPerSample);

	if (!m_pResampler)
	{
            cerr << "AudioQueue::resample()  creation of resampler failed" << endl;
	    return 0;
	}
    }
    double ndest_size = (double) src_size / m_Iwf.nSamplesPerSec * m_Owf.nSamplesPerSec
	/ (m_Owf.wBitsPerSample / 8 * m_Owf.nChannels);

    src_size /= (m_Iwf.wBitsPerSample / 8 * m_Iwf.nChannels);
    size_t dest_size = (size_t) ndest_size;

    m_dRemains += (ndest_size - dest_size);
    if (m_dRemains > 1.0)
    {
	m_dRemains -= 1.0;
        dest_size++;
    }

    //cout << "dest_size " << dest_size << "    orig " << ndest_size << endl;

    Debug cout << "AudioQueue::resample()  freq: " << src_size <<  "  ->   " << dest_size << endl;
    if (dest_size > 0)
	m_pResampler->resample(dest, src, dest_size, src_size);

    //cout << "resampled " << src_size<< " to " << dest_size
    //    << " >>> " << src_size << "   b:" << m_Owf.wBitsPerSample
    //    << "  ch:" << m_Owf.nChannels << endl;
    return (size_t) (dest_size * (m_Owf.wBitsPerSample / 8) * m_Owf.nChannels);
}

int AudioQueue::Push(const void* data, size_t count)
{
    Locker locker(m_Mutex);

    Debug cout << "AudioQueue::push()" << endl;

    size_t new_count = count / m_Iwf.nSamplesPerSec * m_Owf.nSamplesPerSec + 10;
    unsigned int new_pos = m_uiFrameIn + new_count;
    // check if we could fit into the buffer
    if (new_pos > 2 * m_uiFrameMax)
    {
	new_count = 2 * m_uiFrameMax - m_uiFrameIn;
        count = new_count * m_Iwf.nSamplesPerSec / m_Owf.nSamplesPerSec;
    }

    //cout << "queue copy " << count << "   sz " << new_count << "  fi " << m_uiFrameIn << endl;
    memory_lock();

    if (m_Iwf.nSamplesPerSec == m_Owf.nSamplesPerSec)
    {
	memcpy(m_pcAudioFrame + m_uiFrameIn, data, count);
        new_count = count;
    }
    else
	new_count = Resample(m_pcAudioFrame + m_uiFrameIn, data, count);

#if 0
    // checking if the sample continues correctly
    short *p = (short*) (m_pcAudioFrame + ((m_uiFrameIn > 64) ? m_uiFrameIn : 64));

    for (int i = -32; i < 32; i++)
    {
	if (!(i % 8))
	    printf("\n%4d ", i);
	//printf("  0x%4.4x", abs(p[i] - p[i-2]) & 0xffff);
	printf("  0x%4.4x", p[i] & 0xffff);
    }
    cout << endl;
#endif

    new_pos = m_uiFrameIn + new_count; // this is now final result
    while (new_pos > m_uiFrameMax)
    {
	//cout << "************** queue xxx copy " << (new_pos - m_uiFrameMax) << endl;
	memcpy(m_pcAudioFrame, m_pcAudioFrame + m_uiFrameMax,
	       new_pos - m_uiFrameMax);
	new_pos = new_pos - m_uiFrameMax;
    }
    if (m_bCleared && m_pCleaner && new_count > 0)
    {
	m_pCleaner->soundOn(m_pcAudioFrame + m_uiFrameIn, new_count);
	m_bCleared = false;
    }
    memory_unlock();
    m_uiFrameIn = new_pos;
    m_uiFrameSize += new_count;

    m_Cond.Broadcast();

    return 0;
}

int AudioQueue::Read(void* data, size_t count, const IAudioMix* amix)
{
    Locker locker(m_Mutex);
    Debug cout << "AudioQueue: read" << endl;

    memory_lock();
    while (count > 0)
    {
	if (m_uiFrameOut == m_uiFrameMax)
	    m_uiFrameOut = 0;

	unsigned int step = mymin(count, (m_uiFrameMax - m_uiFrameOut));
	static unsigned int las = 0;

	//printf("Step %d   %p    %d   diff:%d    %d\n", step, m_pcAudioFrame, m_uiFrameOut,
	//       m_uiFrameOut - las, count);
	las = m_uiFrameOut;

        if (amix == 0)
	    memcpy(data, m_pcAudioFrame + m_uiFrameOut, step);
	else
	    amix->Mix(data, m_pcAudioFrame + m_uiFrameOut, step);

	(char*)data += step;
	m_uiFrameOut += step;
	if (m_uiFrameSize > step)
	    m_uiFrameSize -= step;
	else
	{
	    m_uiFrameSize = 0;
            break;
	}
	count -= step;
    }
    memory_unlock();

    m_Cond.Broadcast();

    return 0;
}

int AudioQueue::Unread(size_t count)
{
    Locker locker(m_Mutex);

    m_uiFrameOut -= count;

    while (m_uiFrameOut < 0)
	m_uiFrameOut+=m_uiFrameMax;

    m_uiFrameSize = (m_uiFrameOut <= m_uiFrameIn) ?
	(m_uiFrameIn-m_uiFrameOut) : (m_uiFrameMax + m_uiFrameIn - m_uiFrameOut);

    m_Cond.Broadcast();

    return 0;
}

int AudioQueue::Write(int fd) //writes some data
{
    unsigned int startpos, count, tmp;
#ifdef __FreeBSD__
    const unsigned int AUDIO_BUFFER_SIZE = 2048;
#else
    const unsigned int AUDIO_BUFFER_SIZE = 4096;
#endif

    Debug cout << "AudioQueue: write" << endl;
    m_Mutex.Lock();
    count = AUDIO_BUFFER_SIZE;

    if ((m_uiFrameIn > m_uiFrameOut) && (m_uiFrameIn - m_uiFrameOut < AUDIO_BUFFER_SIZE))
	count = m_uiFrameIn - m_uiFrameOut;

    //printf("1: in %d   out %d   size %d\n", m_uiFrameIn, m_uiFrameOut, size);
    if ((m_uiFrameIn < m_uiFrameOut) && (m_uiFrameMax - m_uiFrameOut < AUDIO_BUFFER_SIZE))
	count = m_uiFrameMax - m_uiFrameOut;
    //printf("2: max %d       size %d\n", m_uiFrameMax, size);

    tmp = m_uiFrameIn - m_uiFrameOut;
    while (tmp < 0)
	tmp += m_uiFrameMax;

    if (tmp != m_uiFrameSize)
    {
	//printf("OOPS: m_iFrameSize!=tmp(%d,%d,%d)\n", m_uiFrameOut, m_uiFrameIn, m_iFrameSize);
    }
    startpos = m_uiFrameOut;
    if (m_uiFrameOut == m_uiFrameMax)
	m_uiFrameOut = 0;

    //printf("AudioQueue: write1  s:%d fd:%d  frame:%p sp:%d\n:",
    //size, fd, m_pcAudioFrame, startpos);
    m_Mutex.Unlock();

    int answer = ::write(fd, m_pcAudioFrame + startpos, count);

    if (answer <= 0)
        //sound card doesn't accept any more data ( e.g. DMA buffer is full )
	return answer;

    Locker locker(m_Mutex);
    m_Cond.Broadcast();

    if (m_uiFrameSize == 0)
    {
	Debug printf("AudioQueue::Write() Warning: audio queue drain\n");
	return 0;
    }

    m_uiFrameOut += answer;

    // we really allow buffer clear()  operation between locks
    // I don't see problem here
    if (m_uiFrameSize < (unsigned int)answer)
    {
	Debug printf("AudioQueue::Write() OOPS: wrote %d bytes, buffer size %d\n", answer, m_uiFrameSize);
    }

    if (m_uiFrameSize > (unsigned int)answer)
	m_uiFrameSize -= answer;
    else
	m_uiFrameSize = 0;

    if (m_uiFrameSize == 0)
    {
	Debug cerr << "AudioQueue::Write() Warning: audio queue drain" << endl;
	return answer;
    }

    return answer;
}
