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

	 SunAudioRenderer implementation

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

#include "SunAudioRenderer.h"
#include "RegAccess.h"
#include <avifile.h>
#include <cpuinfo.h>

#include <sys/ioctl.h>
#include <sys/audioio.h>
#include <unistd.h>
#include <stropts.h>
#include <errno.h>
#include <fcntl.h>
#include <cstdio>

#define __MODULE__ "SunAudioRenderer"

using namespace std;

SunAudioRenderer::SunAudioRenderer(IAviReadStream* as)
    :IAudioRenderer()
{
    try
    {
	m_pAudiostream = as;

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

	m_pAudiostream->GetOutputFormat(&m_Owf, sizeof m_Owf);
	sprintf(m_pAudioDesc, " %dkbit/s %dHz %s",
		(int)m_Owf.nAvgBytesPerSec/128,
		(int)m_Owf.nSamplesPerSec,
		m_Owf.nChannels != 1 ? "stereo" : "mono");

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

	m_iAudioFd = open("/dev/audio", O_WRONLY|O_NDELAY);
	if(m_iAudioFd < 0)
     	    throw FATAL("Can't open audio device");

	m_bForce44KHz = RegAccess::ReadForce44KHz();
	if(m_Owf.nSamplesPerSec == 44100 && m_bForce44KHz)
	    m_bForce44KHz = false;
	
	audio_info_t audio_info;
	AUDIO_INITINFO(&audio_info);
	audio_info.play.channels    = m_Owf.nChannels;
	audio_info.play.precision   = m_Owf.wBitsPerSample;
	audio_info.play.sample_rate = m_bForce44KHz
	    ? 44100
	    : m_Owf.nSamplesPerSec;
	audio_info.play.encoding    = m_Owf.wBitsPerSample == 8
	    ? AUDIO_ENCODING_LINEAR8
	    : AUDIO_ENCODING_LINEAR;
	audio_info.play.samples     = 0;
	if(ioctl(m_iAudioFd, AUDIO_SETINFO, &audio_info) < 0)
	{
	    char failure_msg[80];
	    sprintf(failure_msg,
		    "ioctl(AUDIO_SETINFO) failed: chnl=%d, prec=%d, rate=%d, enc=%d",
		    audio_info.play.channels,
		    audio_info.play.precision,
		    audio_info.play.sample_rate,
		    audio_info.play.encoding);
	    throw FATAL(failure_msg);
	}
    }
    catch(FatalError& error)
    {
	if(m_iAudioFd > 0)
	    ::close(m_iAudioFd);
	m_iAudioFd = -1;
	m_pAudiostream = 0;
	throw;
    }

    m_iSamplesSent = 0;

    pthread_create(&m_AudioThread, NULL, doAudioOut, (void*)this);
}

SunAudioRenderer::~SunAudioRenderer()
{
    Debug cout<<"Destroying audio renderer"<<endl;
    m_bQuit = true;
    m_pQueue->broadcast();
    pthread_join(m_AudioThread, NULL);
    reset();
    ::close(m_iAudioFd);
    Debug cout<<"Destroy() successful"<<endl;
}

double SunAudioRenderer::bufferTime()
{
    double rate = m_bForce44KHz ? 44100. : m_Owf.nSamplesPerSec;
    double frame_time = double(m_pQueue->size()) / m_Owf.nChannels / 
	(m_Owf.wBitsPerSample/8);

    audio_info_t audio_info;
    if (m_bInitialized && ::ioctl(m_iAudioFd, AUDIO_GETINFO, &audio_info) == 0)
    {
	frame_time += (m_iSamplesSent - audio_info.play.samples);
	Debug printf("SAMPLES queue %d, device %d - %d = %d, time = %f\n",
		     m_pQueue->size(),
		     m_iSamplesSent, audio_info.play.samples,
		     m_iSamplesSent - audio_info.play.samples,
		     frame_time / rate);
    }
    return frame_time / rate;
}

void SunAudioRenderer::reset()
{
    if (m_iAudioFd)
    {
       /*
        * clear data in the STREAMS queue, and wait for
        * playback end.  Make sure the audio device is not
	* 'paused' state, else the AUDIO_DRAIN ioctl hangs
	* forever.
        */
       ioctl(m_iAudioFd, I_FLUSH, FLUSHW);

       audio_info_t audio_info;
       AUDIO_INITINFO(&audio_info);
       audio_info.play.pause = 0;
       ioctl(m_iAudioFd, AUDIO_SETINFO, &audio_info);

       ioctl(m_iAudioFd, AUDIO_DRAIN, 0);

       AUDIO_INITINFO(&audio_info);
       audio_info.play.samples = 0;
       ioctl(m_iAudioFd, AUDIO_SETINFO, &audio_info);
       m_iSamplesSent = 0;
    }
}

void SunAudioRenderer::setVolume(float volume)
{
    if (volume < 0 || volume > 1)
	return;

    audio_info_t audio_info;
    AUDIO_INITINFO(&audio_info);
    audio_info.play.gain = int(AUDIO_MAX_GAIN * volume);
    // not yet, initial 100% volume setting is much too loud
    // (at least on my SB16pci :-)
    //ioctl(m_iAudioFd, AUDIO_SETINFO, &audio_info);

    m_fVolume = volume;
}

void SunAudioRenderer::pause(int state)
{
    Debug cout << "SunAudioRenderer::pause " << state << endl;

    audio_info_t audio_info;
    AUDIO_INITINFO(&audio_info);
    audio_info.play.pause = state;
    ::ioctl(m_iAudioFd, AUDIO_SETINFO, &audio_info);

    double rate = m_bForce44KHz ? 44100. : m_Owf.nSamplesPerSec;
    double frame_time = double(m_pQueue->size()) / m_Owf.nChannels / 
	(m_Owf.wBitsPerSample/8);
    Debug cout << "Samples buffered in audio_queue " <<  frame_time/rate << endl;
}

void* SunAudioRenderer::doAudioOut(void* arg)
{
    SunAudioRenderer& a = *(SunAudioRenderer*) arg;

    while (!a.m_bQuit)
    {
	if (a.m_bPaused)
	{
	    avi_usleep(100000);
//	    cout<<"m_bPaused"<<endl;
	    continue;
	}
	static int reset_dev = 0;
	if (!a.m_bInitialized)
	{
	    //cout<<"Not m_bInitialized"<<endl;
	    if (reset_dev)
	    {
		Debug cout << "reset device" << endl;
		a.m_pQueue->clear();
		a.reset();
		reset_dev = 0;
	    }
	    avi_usleep(100000);
            continue;
        }
	else
	    reset_dev = 1;

	if (a.m_pQueue->size() == 0 && a.m_pQueue->wait(a.m_bQuit) == 0)
	{
	    //wait() was interrupted, maybe by signal or m_bQuit
	    continue;
	}

	int result = a.m_pQueue->write(a.m_iAudioFd);
	if (result == 0 || (result < 0 && errno == EAGAIN))
	    avi_usleep(10000);
	else if (result < 0)
	    perror("AudioQueue::write");
	else
	{
	    unsigned samples = result / a.m_Owf.nChannels / 
		(a.m_Owf.wBitsPerSample/8);

	    Locker locker(a.m_Mutex);
	    a.m_iSamplesSent += samples;
	    a.m_lAudioTime = longcount();
	    double stream_time = a.m_pAudiostream->GetTime();
	    double buffer_time = a.bufferTime();
	    a.m_dAudioRealpos = stream_time - buffer_time;
	    Debug cout << "TIME aStream " << stream_time
		       << " buffered " << buffer_time
		       << " audio real pos " << a.m_dAudioRealpos << endl;
	}
    }
    Debug cout<<"Exiting audio thread"<<endl;
    return 0;
}
