/****************************************************************************
 * libwiigui
 *
 * Tantric 2009
 *
 * gui_sound.cpp
 *
 * decoder modification by ardi 2009
 *
 * GUI class definitions
 ***************************************************************************/

#include "gui.h"
#include <unistd.h>
#include "gecko.h"

#include "gui_sound_decoder.h"


#define BUFFER_SIZE 8192


/***************************************************************
 *
 * D E C O D E R – L I S T
 *
 *
 ***************************************************************/

GuiSoundDecoder::DecoderListEntry *GuiSoundDecoder::DecoderList = NULL;
GuiSoundDecoder::DecoderListEntry &GuiSoundDecoder::RegisterDecoder(DecoderListEntry &Decoder, GuiSoundDecoderCreate fnc) {
    if (Decoder.fnc != fnc) {
        Decoder.fnc = fnc;
        Decoder.next = DecoderList;
        DecoderList = &Decoder;
    }
    return Decoder;
}
GuiSoundDecoder *GuiSoundDecoder::GetDecoder(const u8 * snd, u32 len, bool snd_is_allocated) {
    for (DecoderListEntry *de = DecoderList; de; de=de->next) {
        GuiSoundDecoder *d = NULL;
        try {
            d = de->fnc(snd, len, snd_is_allocated);
        } catch (const char *error) {
            gprintf("%s", error);
        } catch (...) {}
        if (d) return d;
    }
    return NULL;
}


/***************************************************************
 *
 * D E C O D E R – T H R E A D
 *
 *
 ***************************************************************/

static GuiSound *GuiSoundPlayer[16] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};

static lwp_t GuiSoundDecoderThreadHandle	= LWP_THREAD_NULL;
static bool GuiSoundDecoderThreadRunning	= false;
static bool GuiSoundDecoderDataRquested	= false;

void *GuiSoundDecoderThread(void *args) {
    GuiSoundDecoderThreadRunning = true;
    do {
        if (GuiSoundDecoderDataRquested) {
            GuiSoundDecoderDataRquested = false;
            GuiSound **players = GuiSoundPlayer;
            for ( int i = 0; i < 16; ++i , ++players) {
                GuiSound *player = *players;
                if (player)
                    player->DecoderCallback();
            }
        }
        if (!GuiSoundDecoderDataRquested)
            usleep(50);
    } while (GuiSoundDecoderThreadRunning);
    return 0;
}

/***************************************************************
 *
 * A S N D – C A L L B A C K
 *
 *
 ***************************************************************/

void GuiSoundPlayerCallback(s32 Voice) {
    if (Voice >= 0 && Voice < 16 && GuiSoundPlayer[Voice]) {
        GuiSoundPlayer[Voice]->PlayerCallback();
        GuiSoundDecoderDataRquested = true;
    }
}

/***************************************************************
 *
 *   R A W - D E C O D E R
 *   Decoder for Raw-PCM-Datas (16bit Stereo 48kHz)
 *
 ***************************************************************/
class GuiSoundDecoderRAW : public GuiSoundDecoder {
protected:
    GuiSoundDecoderRAW(const u8 * snd, u32 len, bool snd_is_allocated) {
        pcm_start		= snd;
        is_allocated	= snd_is_allocated;
        pcm_end			= pcm_start+len;
        pos				= pcm_start;
        is_running		= false;

    }
public:
    ~GuiSoundDecoderRAW() {
        while (is_running) usleep(50);
        if (is_allocated) delete [] pcm_start;
    }
    static GuiSoundDecoder *Create(const u8 * snd, u32 len, bool snd_is_allocated) {
        try {
            return new GuiSoundDecoderRAW(snd, len, snd_is_allocated);
        } catch (...) {}
        return NULL;
    }
    s32 GetFormat() {
        return VOICE_STEREO_16BIT;
    }
    s32 GetSampleRate() {
        return 48000;
    }
    /*  Read reads data from stream to buffer
    	return:	>0 = readed bytes;
                0 = EOF;
    			<0 = Error;
    */
    int Read(u8 * buffer, int buffer_size) {
        if (pos >= pcm_end)
            return 0; // EOF

        is_running = true;
        if (pos + buffer_size > pcm_end)
            buffer_size = pcm_end-pos;
        memcpy(buffer, pos, buffer_size);
        pos += buffer_size;
        is_running = false;
        return buffer_size;
    }
    int Rewind() {
        pos = pcm_start;
        return 0;
    }
private:
    const u8		*pcm_start;
    const u8		*pcm_end;
    bool			 is_allocated;
    const u8		*pos;
    bool			is_running;

};

/***************************************************************
 *
 *   G u i S o u n d
 *
 *
 ***************************************************************/
#define GuiSoundBufferReady	0x01
#define GuiSoundBufferEOF	0x02
#define GuiSoundFinish		0x04
static int GuiSoundCount = 0;
/**
 * Constructor for the GuiSound class.
 */
GuiSound::GuiSound(const u8 *s, int l, int v/*=100*/, bool r/*=true*/, bool a/*=false*/) {
    if (GuiSoundCount++ == 0 || GuiSoundDecoderThreadHandle == LWP_THREAD_NULL) {
        LWP_CreateThread(&GuiSoundDecoderThreadHandle,GuiSoundDecoderThread,NULL,NULL,32*1024,80);
    }
    voice = -1;
    play_buffer[0]	= (u8*)memalign(32, BUFFER_SIZE*3);	// tripple-buffer first is played
    play_buffer[1]	= play_buffer[0] + BUFFER_SIZE;		//						second is waiting
    play_buffer[2]	= play_buffer[1] + BUFFER_SIZE;		//						third is decoding
    buffer_nr		= 0;			// current playbuffer
    buffer_pos		= 0;			// current idx to write in buffer
    buffer_ready	= false;
    buffer_eof		= false;
    loop			= false;		// play looped
    volume			= v;			// volume
    decoder			= NULL;
    if (play_buffer[0])			// playbuffer ok
        Load(s, l, r, a);
}
bool GuiSound::Load(const u8 *s, int l, bool r/*=false*/, bool a/*=false*/) {
    Stop();
    if (!play_buffer[0]) return false;
    GuiSoundDecoder *newDecoder = GuiSoundDecoder::GetDecoder(s, l, a);
    if (!newDecoder && r) newDecoder = GuiSoundDecoderRAW::Create(s, l, a);
    if (newDecoder) {
        delete decoder;
        decoder = newDecoder;
        return true;
    } else if (a)
        delete [] s;
    return false;
}
GuiSound::GuiSound(const char *p, int v/*=100*/) {
    if (GuiSoundCount++ == 0 || GuiSoundDecoderThreadHandle == LWP_THREAD_NULL) {
        LWP_CreateThread(&GuiSoundDecoderThreadHandle,GuiSoundDecoderThread,NULL,NULL,32*1024,80);
    }
    voice = -1;
    play_buffer[0]	= (u8*)memalign(32, BUFFER_SIZE*3);	// tripple-buffer first is played
    play_buffer[1]	= play_buffer[0] + BUFFER_SIZE;		//						second is waiting
    play_buffer[2]	= play_buffer[1] + BUFFER_SIZE;		//						third is decoding
    buffer_nr		= 0;					// current playbuffer
    buffer_pos		= 0;					// current idx to write in buffer
    buffer_ready	= false;
    buffer_eof		= false;
    loop			= false;				// play looped
    volume			= v;					// volume
    decoder			= NULL;
    if (play_buffer[0])			// playbuffer ok
        Load(p);
}
bool GuiSound::Load(const char *p) {
    Stop();							// stop playing
    if (!play_buffer[0]) return false;

    bool ret = false;
    voice = -2;						// -2 marks loading from file
    u32 filesize = 0;
    u8 *buffer = NULL;
    size_t result;

    FILE * pFile = fopen (p, "rb");
    if (pFile) {
        // get file size:
        fseek (pFile , 0 , SEEK_END);
        filesize = ftell (pFile);
        fseek (pFile , 0 , SEEK_SET);

        // allocate memory to contain the whole file:
        buffer = new(std::nothrow) u8[filesize];
        if (buffer) {
            // copy the file into the buffer:
            result = fread (buffer, 1, filesize, pFile);
            if (result == filesize)
                ret= Load(buffer, filesize, false, true);
            else
                delete [] buffer;
        }
        fclose (pFile);
    }
    return ret;
}

/**
 * Destructor for the GuiSound class.
 */
GuiSound::~GuiSound() {
    if (!loop)	while (voice >= 0) usleep(50);
    Stop();
    if (--GuiSoundCount == 0 && GuiSoundDecoderThreadHandle != LWP_THREAD_NULL) {
        GuiSoundDecoderThreadRunning = false;
        LWP_JoinThread(GuiSoundDecoderThreadHandle,NULL);
        GuiSoundDecoderThreadHandle = LWP_THREAD_NULL;
    }
    delete decoder;
    free(play_buffer[0]);
}

void GuiSound::Play() {
    Stop();									// stop playing if it played
    if (!play_buffer[0]) return;
    if (!decoder) return;					// no decoder or no play_buffer -> no playing
    // initialize the buffer
    buffer_nr		= 0; 					// allways starts with buffer 0
    buffer_pos		= 0;					// reset position
    buffer_ready	= false;
    buffer_eof		= false;
    decoder->Rewind();						// play from begin
    DecoderCallback();						// fill first buffer;
    if (!buffer_ready || buffer_eof)			// if first buffer not ready -> no play
        return;
    voice = ASND_GetFirstUnusedVoice();
    if (voice >= 0) {
        s32 vol			= (255*volume)/100;
        s32 format		= decoder->GetFormat();
        s32 samplerate	= decoder->GetSampleRate();
        s32 first_pos	= buffer_pos;
        // switch to next buffer
        buffer_nr		= 1;
        buffer_pos		= 0;
        buffer_ready	= false;
        buffer_eof		= false;
        DecoderCallback();						// fill second buffer;
        GuiSoundPlayer[voice] = this;			// activate Callbacks for this voice
        // Play the voice
        ASND_SetVoice(voice, format, samplerate, 0, play_buffer[0], first_pos, vol, vol, GuiSoundPlayerCallback);
    }
}
/*
int GuiSound::PlayOggFile(char * path)
{
	if(Load(path))
		Play();
	return 1;
}
*/
void GuiSound::Stop() {
    if (voice < 0) return ;
    GuiSoundPlayer[voice] = NULL; // disable Callbacks
    SND_StopVoice(voice);
    voice = -1;
}

void GuiSound::Pause() {
    if (voice < 0) return ;
    ASND_PauseVoice(voice, 1);
}

void GuiSound::Resume() {
    if (voice < 0) return ;
    ASND_PauseVoice(voice, 0);
}

bool GuiSound::IsPlaying() {
    return voice >= 0;
}

void GuiSound::SetVolume(int vol) {
    volume = vol;
    if (voice < 0) return ;
    int newvol = 255*(volume/100.0);
    ASND_ChangeVolumeVoice(voice, newvol, newvol);
}

void GuiSound::SetLoop(bool l) {
    loop = l;
}

void GuiSound::DecoderCallback() {
    if (buffer_ready || buffer_eof) // if buffer ready or EOF -> nothing
        return;
    bool error=false;
    while (buffer_pos < BUFFER_SIZE) {
        int ret = decoder->Read(&play_buffer[buffer_nr][buffer_pos], BUFFER_SIZE-buffer_pos);
        if (ret > 0)
            buffer_pos += ret;					// ok -> fill the buffer more
        else if (ret == 0) {					// EOF from decoder
            if (loop)
                decoder->Rewind();				// if loop -> rewind and fill the buffer more
            else if (buffer_pos)
                break;							// has data in buffer -> play the buffer
            else
                buffer_eof = true;				// no data in buffer -> return EOF
            return;
        } else if (ret < 0) {					// an ERROR
            if (buffer_pos)
                break;							// has data in buffer -> play the buffer
            else if (loop) {
                if (!error) {					// if no prev error
                    decoder->Rewind();			// if loop -> rewind
                    error = true;				// set error-state
                    continue;					// and fill the buffer more
                }
                buffer_eof = true;				// has prev error -> error in first block -> return EOF
                return;
            } else {
                buffer_eof = true;				// no loop -> return EOF
                return;
            }
        }
        error = false;							// clear error-state
    }
    buffer_ready = true;
}
void GuiSound::PlayerCallback() {
    if (buffer_eof) {							// if EOF
        if (ASND_TestPointer(voice, play_buffer[(buffer_nr+2)%3])==0)	// test prev. Buffer
            Stop();
    } else if (buffer_ready) {			// if buffer ready
        if (ASND_AddVoice(voice, play_buffer[buffer_nr], buffer_pos)==SND_OK) {	// add buffer
            // next buffer
            buffer_nr	= (buffer_nr+1)%3;
            buffer_pos	= 0;
            buffer_ready= false;
            buffer_eof	= false;
        }
    }
}