/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// Notes:
//
// - emuEngine has been replaced with a thread-safe wrapper class.
// 

#include <cstdlib>
#include <ctime>
using namespace std;

#include <qobject.h>

#include "Player.h"

#include "AudioDriver.h"
#include "AudioDrivers.h"
#include "ConfigC.h"
#include "ConfigFile.h"
#include "MainDialog.h"
#include "PlaylistDialog.h"
#include "Playlist.h"
#include "TimeLCD.h"
#include "wrapper/EmuWrapper.h"
#include "wrapper/SidTuneWrapper.h"

Player::Player()
{
    pEmuEngine = new EmuWrapper;
    pSidTuneWrapper = new SidTuneWrapper;
    pPlaylist = new Playlist;

    state.stopped = true;
    state.playing = state.paused = state.forwarding = false;
    state.ready = false;

    endOfSong = false;

    playlistDriven = false;
    loopPlaylist = false;
    randPlaylist = false;
    
    driverIsOpen = false;
    multiBufferSize = 2048;
    initAudioBufferSystem();
    
    srand(time(NULL));

    displayTimer.stop();
    connect(&displayTimer,SIGNAL( timeout() ),this,SLOT( timeJob() ));
    
    connect(this,SIGNAL( endOfCurrentSong() ),this,SLOT( handleEndOfCurrentSong() ));

    playJobActive = false;
    playThread.connectPlayer(this, &Player::playJob );
}

void Player::shutdown()
{
}

Player::~Player()
{
    freeAudioBufferSystem();
    
    delete pPlaylist;
    delete pSidTuneWrapper;
    delete pEmuEngine;
}

const QString& Player::getErrorStr()
{
    return errorString;
}

void Player::readConfig(ConfigFile* config)
{
    emuConfig myEmuConfig;
    myEmuConfig = config->getEmuConfig();
    pEmuEngine->setConfig(myEmuConfig);
}

void Player::writeConfig(ConfigFile* config)
{
}

void Player::link(MainDialog* mainDlg, TimeLCD* timeLCD, 
                  PlaylistDialog* playlistDlg)
{
    myMainDlg = mainDlg;  // not used
    myTimeLCD = timeLCD;
    myPlaylistDlg = playlistDlg;
}

bool Player::enableSongLengthDB(bool value)
{
    // does not do anything yet
    return value;
}

bool Player::init(const AudioConfig& audioConfig)
{
    myAudioConfig = audioConfig;
    if ( !openAudioDriver() )
    {  
        // Set system to inoperational state.
        return (state.ready = false);
    }
    closeAudioDriver();
    return init_main();
}

bool Player::init_main()
{
    // Now retrieve the possibly changed config from the driver.
    getAudioConfig();
    if ( myAudioConfig.bufSize != 0 )
        multiBufferSize = myAudioConfig.bufSize;
    else
        multiBufferSize = myAudioConfig.blockSize;
    state.ready = openAudioBufferSystem();
    
    // Translate config to EMU. It is assumed that the emulator
    // engine can output in all possible audio configs.
    
    emuConfig myEmuConfig = pEmuEngine->getConfig();
    if (myAudioConfig.precision == AudioConfig::BITS_16)
        myEmuConfig.bitsPerSample = SIDEMU_16BIT;
    else
        myEmuConfig.bitsPerSample = SIDEMU_8BIT;
    
    if (myAudioConfig.encoding == AudioConfig::SIGNED_PCM)
        myEmuConfig.sampleFormat = SIDEMU_SIGNED_PCM;
    else
        myEmuConfig.sampleFormat = SIDEMU_UNSIGNED_PCM;
    
    if (myAudioConfig.channels == AudioConfig::STEREO)
        myEmuConfig.channels = SIDEMU_STEREO;
    else
    {
        myEmuConfig.channels = SIDEMU_MONO;
        myEmuConfig.autoPanning = SIDEMU_NONE;
        // Max. mono can do is VOLCONTROL mode.
        if (myEmuConfig.volumeControl==SIDEMU_FULLPANNING ||
            myEmuConfig.volumeControl==SIDEMU_STEREOSURROUND)
            myEmuConfig.volumeControl = SIDEMU_NONE;
    }
    myEmuConfig.frequency = myAudioConfig.frequency;
    // Set the emuEngine to supported settings.
    pEmuEngine->setConfig(myEmuConfig);

    return state.ready;
}

Playlist* Player::getPlaylist() const
{
    return pPlaylist;
}

void Player::setPlaylistParams(const PlaylistItem& item)
{
    if ( &item==&curPlaylistItem )
        return;
    curPlaylistItem = item;
}

const PlaylistItem& Player::getCurPlaylistParams()
{
    return curPlaylistItem;
}

void Player::enablePlaylist(bool value)
{
    playlistDriven = value;
}

void Player::enablePlaylistLoop(bool value)
{
    loopPlaylist = value;
}

void Player::enablePlaylistRand(bool value)
{
    randPlaylist = value;
}

// slot
void Player::handleEndOfCurrentSong()
{
    if ( !playNextFromList(1) )
    {
        stop();
        emit stopPlaylistPlay();
    }
}

// slot
bool Player::playNextFromList(int delta)
{
    pPlaylist->lock();

    bool haveNext = false;
    uint count = pPlaylist->list.count();
    uint index = delta+pPlaylist->getCurrentPlayPos();
    
    if ( delta==0 )  // START
    {
        if ( randPlaylist )
        {
            float f = count;
            index = (uint)(f*rand()/(RAND_MAX+1.0));
        }
        else
        {
            index = 0;
        }
        haveNext = (index<count);
    }
    else  // PREV/NEXT
    {
        if ( randPlaylist && count>=1 )
        {
            // RANDOM
            float f = count;
            index = (uint)(f*rand()/(RAND_MAX+1.0));
            haveNext = true;
        }
        else if ( count>0 && index>=0 && index<=(count-1) )  // last is count-1
        {
            // NEXT/PREV
            haveNext = true;
        }
        else if ( loopPlaylist && count>=1 )
        {
            // LOOP
            if ( delta>0 )
                index = 0;        // TO BEGINNING
            else
                index = count-1;  // TO END
            haveNext = true;
        }
    }
    pPlaylist->unlock();

    if (haveNext)
    {
        pPlaylist->setCurrentPlayPos(index);
        pPlaylist->lock();
        PlaylistItem* pli = pPlaylist->list.at(index);
        curPlaylistItem = *pli;
        // baseDir is empty: curPlaylistItem.fileNameString.prepend(pPlaylist->baseDir);
        pPlaylist->unlock();
        // Emit the list items pointer, so player updates its currrent
        // item once it receives the corresponding signal.
        emit playListItem(*pli);
        emit playerPlayRequest(index);
    }
    return haveNext;
}

void Player::initSong(int song)
{
    pEmuEngine->initSong(pSidTuneWrapper->getSidTune(),song);
    pSidTuneWrapper->updateInfo();
}

void Player::initNextSong()
{
    initSong(pSidTuneWrapper->getCurrentSong()+1);
}

void Player::initPrevSong()
{
    initSong(pSidTuneWrapper->getCurrentSong()-1);
}

bool Player::haveSidTune() const
{
    return pSidTuneWrapper->getStatus();
}

bool Player::havePrevSong()
{
    return ( pSidTuneWrapper->getStatus() && 
             pSidTuneWrapper->getCurrentSong()>1 && 
             pSidTuneWrapper->getSongs()>1 );
}

bool Player::haveNextSong()
{
    return ( pSidTuneWrapper->getStatus() &&
             pSidTuneWrapper->getCurrentSong()<pSidTuneWrapper->getSongs() );
}

void Player::start()
{
    stop();

    if ( !openAudioDriver() || !pSidTuneWrapper->getStatus() )
        return;
    if ( !state.ready && !init_main() )  // temporary audio driver failure
        return;

    initSong( pSidTuneWrapper->getCurrentSong() );
    playNormalSpeed();
    endOfSong = false;

    // myFadeIn.set(myAudioConfig,curPlaylistItem.fadeInTime*1000);
    myFadeOut.set(myAudioConfig,curPlaylistItem.fadeout*1000);
                
    displayTimer.start(250,false);
                
    // preFillBuffers
    currentBuffer = 0;
    int lastBufferCount = 0;
    for (int i = 0; i < 2; i++)
    {
        fillBuffer(pBuffer[i],multiBufferSize);
        lastBufferCount += multiBufferSize;
        bufferCount[i] = lastBufferCount;
    }
    for (int i = 0; i < 2; i++)
    {
        playAudio(pBuffer[i],multiBufferSize);
    }
    
    playJobActive = state.playing = true;
    state.stopped = false;
    playThread.start();
}

void Player::pause(bool hard)
{
    if ( state.playing )
    {
        state.paused = true;
        playJobActive = false;
        displayTimer.stop();
//        pEmuEngine->stop();
        playThread.wait();
        resetAudioDriver();
        if (hard)
        {
            closeAudioDriver();
        }
    }
}

void Player::resume()
{
    if ( state.paused )
    {
        state.paused = false;
        if ( !driverIsOpen || !state.ready )  // pauseHard?
        {
            if ( !openAudioDriver() || !pSidTuneWrapper->getStatus() )
            {
                state.stopped = true;
                state.paused = state.forwarding = state.playing = false;
                playJobActive = false;
                return;
            }
            if ( !state.ready && !init_main() )  // temporary audio driver failure
                return;
        }
        displayTimer.start(250,false);
        playJobActive = state.playing;
        playThread.start();
    }
}

void Player::stop()
{
    if ( state.playing )
    {
        state.stopped = true;
        state.paused = state.forwarding = state.playing = false;
        playJobActive = false;
        displayTimer.stop();
        pEmuEngine->stop();
        playThread.wait();
        endOfSong = false;
        resetAudioDriver();
        closeAudioDriver();
    }
}

void Player::playFastForward()
{
#ifndef SID_WITH_SIDPLAY2
    extern bool sidEmuFastForwardReplay(int);
    sidEmuFastForwardReplay(10);   // calc 10%
#endif
    state.forwarding = true;
}

void Player::playNormalSpeed()
{
#ifndef SID_WITH_SIDPLAY2
    extern bool sidEmuFastForwardReplay(int);
    sidEmuFastForwardReplay(100);  // calc 100%
#endif
    state.forwarding = false;
}

bool Player::isReady() const
{
    return ( state.ready && pSidTuneWrapper->getStatus() );
}

bool Player::isPlaying() const
{
    return state.playing;
}

bool Player::isForwarding() const
{
    return state.forwarding;
}

bool Player::isPaused() const
{
    return state.paused;
}

bool Player::isStopped() const
{
    return state.stopped;
}

EmuWrapper* Player::getEmuEngine() const
{
    return pEmuEngine;
}

SidTuneMod* Player::getSidTune() const
{
    return pSidTuneWrapper->getSidTune();
}

SidTuneWrapper* Player::getSidTuneWrapper() const
{
    return pSidTuneWrapper;
}

// --------------------------------------------------- audio driver interface

bool Player::openAudioDriver()
{
    closeAudioDriver();
    audioDriverMutex.lock();
    driverIsOpen = AudioDrivers::getDriver()->open(myAudioConfig);
    if ( !driverIsOpen )
        errorString = AudioDrivers::getDriver()->getErrorString();
    audioDriverMutex.unlock();
    return driverIsOpen;
}

void Player::closeAudioDriver()
{
    audioDriverMutex.lock();
    if (driverIsOpen)
    {
        AudioDrivers::getDriver()->close();
        driverIsOpen = false;
    }
    audioDriverMutex.unlock();
}

void Player::resetAudioDriver()
{
    audioDriverMutex.lock();
    AudioDrivers::getDriver()->reset();
    audioDriverMutex.unlock();
}

const AudioConfig& Player::getAudioConfig()
{
    audioDriverMutex.lock();
    myAudioConfig = AudioDrivers::getDriver()->getConfig();
    audioDriverMutex.unlock();
    return myAudioConfig;
}

void Player::playAudio(void* pBuf, unsigned long int bufSize)
{
    audioDriverMutex.lock();
    AudioDrivers::getDriver()->play(pBuf,bufSize);
    audioDriverMutex.unlock();
}

void Player::initAudioBufferSystem()
{
    // Clear multi-buffering system.
    for (int i = 0; i < 2; i++)
    {
        bufferCount[i] = 0;
        pBuffer[i] = 0;
    }
}

void Player::freeAudioBufferSystem()
{
    for (int i = 0; i < 2; i++)
    {
        if (pBuffer[i] != 0)
            delete[] pBuffer[i];
    }
    initAudioBufferSystem();
}

bool Player::openAudioBufferSystem()
{
    freeAudioBufferSystem();
    
    bool success = true;
    for (int i = 0; i < 2; i++)
    {
        if ((pBuffer[i] = new unsigned char[multiBufferSize]) == 0)
        {
            success = false;
            break;
        }
    }
    return success;
}

// --------------------------------------------------------------------------

void Player::fillBuffer(unsigned char* pBuffer, unsigned long int bufferSize)
{
    pEmuEngine->fillBuffer(pSidTuneWrapper->getSidTune(),pBuffer,bufferSize);
    int secs = pEmuEngine->getSecondsThisSong();
//    if ( secs < (fadeInTime+1) )
//        myFadeIn.fade( pBuffer, bufferSize );
    if ( playlistDriven && curPlaylistItem.time && secs>=(curPlaylistItem.time-curPlaylistItem.fadeout) )
    {
        myFadeOut.fade( pBuffer, bufferSize );
        if ( secs>curPlaylistItem.time )
        {
            endOfSong = true;
        }
    }
}

void Player::timeJob()
{
    int secs = pEmuEngine->getSecondsThisSong();
    myTimeLCD->updateDisplay(secs);

    if ( endOfSong )
    {
        emit endOfCurrentSong();
    }
}

void Player::playJob(QThread* thread)
{
    while ( playJobActive )
    {
        if ( ++currentBuffer == 2 )
            currentBuffer = 0;
        fillBuffer(pBuffer[currentBuffer],multiBufferSize);
        playAudio(pBuffer[currentBuffer],multiBufferSize);
        
        if ( endOfSong )
        {
            break;
        }
        
        //msleep(5);
    }
}

// --------------------------------------------------------- thread sub-class

void PlayerThread::connectPlayer(Player* argPlayer, ptr2PlayerThreadFunc argFunc)
{
    player = argPlayer;
    playFunc = argFunc;
}

void PlayerThread::run()
{
    (player->*playFunc)(this);
}

