#include "Sound.h"
#include "Player.h"

using namespace GSL;

#ifdef WIN32
extern GSLPlayer *_player;
void CALLBACK AudioOutProc( HWAVEOUT hOut, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 );
#endif

// Inhalt der Sound Klasse aus Sound.h
// Konstruktor
GSLSound::GSLSound( const SoundKopf &skpf )
    : Thread()
{
    kpf = skpf;
    dat = new Datei();
    dat->setDatei( kpf.pfad );
#ifdef WIN32
    status = 0;
    hAudioId = 0;
    uAudioDelay = 0;
    uAudioCount = 0;
    uAudioPlay = 16;
    hAudioEvent = 0;
    uAudioWrite = 0;
    uAudioStop = 0;
    lAudioDone = 0;
    uAudioWrOk = 0;
    iAudioThEnd = 0;
    linksV = 1;
    rechtsV = 1;
#endif
}

// Destruktor
GSLSound::~GSLSound()
{
#ifdef WIN32
    if( status )
        stopSound();
#endif
    dat->release();
}

// privat
int GSLSound::audioOpen()
{
#ifdef WIN32
    WAVEFORMATEX sAudioWm;
    sAudioWm.wFormatTag = WAVE_FORMAT_PCM;
    sAudioWm.nChannels = kpf.channels;
    sAudioWm.nSamplesPerSec = kpf.sampleRate;
    sAudioWm.nBlockAlign = ( ( 16 * kpf.channels ) + 7 ) >> 3;
    sAudioWm.nAvgBytesPerSec = kpf.sampleRate * sAudioWm.nBlockAlign;
    sAudioWm.wBitsPerSample = 16;
    sAudioWm.cbSize = 0;
    MMRESULT uRes = waveOutOpen( &hAudioId, WAVE_MAPPER, &sAudioWm, (DWORD_PTR)AudioOutProc, 0x00000000, CALLBACK_FUNCTION );
    if( uRes || !hAudioId )
    {
        memset( &sAudioWm, 0, sizeof( sAudioWm ) );
        return 1;
    }
    uAudioDelay = 0;
    uAudioWrite = 0;
    uAudioStop = 0;
    lAudioDone = 0;
    for( uAudioCount = 0; uAudioCount < 16; uAudioCount++ )
    {
        aAudioHdr[ uAudioCount ] = (LPWAVEHDR)GlobalAllocPtr( GMEM_MOVEABLE | GMEM_SHARE, sizeof( WAVEHDR ) + 0x4000 );
        if( !aAudioHdr[ uAudioCount ] )
            break;
        aAudioHdr[ uAudioCount ]->dwFlags = WHDR_DONE;
        aAudioHdr[ uAudioCount ]->lpData = (LPSTR)aAudioHdr[ uAudioCount ] + sizeof( WAVEHDR );
        aAudioHdr[ uAudioCount ]->dwBufferLength = 0x4000;
        aAudioHdr[ uAudioCount ]->dwUser = uAudioCount;
        aAudioHdr[ uAudioCount ]->dwBytesRecorded = 0;
        aAudioHdr[ uAudioCount ]->dwLoops = 1;
        aAudioHdr[ uAudioCount ]->lpNext = 0;
        aAudioPtr[ uAudioCount ] = (char *)( aAudioHdr[ uAudioCount ] ) + sizeof( WAVEHDR );
        if( !waveOutPrepareHeader( hAudioId, aAudioHdr[ uAudioCount ], sizeof( WAVEHDR ) ) )
            continue;
        GlobalFreePtr( (LPCVOID)aAudioHdr[ uAudioCount ] );
        break;
    }
    uAudioPlay = uAudioCount - 1;
    if( uAudioCount < 12 )
    {
        audioClose();
        return 2;
    }
    hAudioEvent = CreateEvent( 0, TRUE, TRUE, 0 );
    if( !hAudioEvent )
    {
        audioClose();
        return 3;
    }
    if( waveOutGetDevCaps( (UINT_PTR)hAudioId, (LPWAVEOUTCAPSA)&sAudioCaps, sizeof( WAVEOUTCAPS ) ) )
        memset( &sAudioCaps, 0, sizeof( sAudioCaps ) );
    waveOutReset( hAudioId );
    waveOutSetVolume( hAudioId, 0xFFFFFFFF );
#endif
    dat->open( Datei::Style::lesen );
#ifdef WIN32
    uAudioWrOk = 1;
#endif
    return 0;
}

int GSLSound::audioClose()
{
    int	 iErr = 0;
#ifdef WIN32
    if( !hAudioId )
        return 1;
    audioStop();
    while( uAudioCount > 0 )
    {
        uAudioCount--;
        if( waveOutUnprepareHeader( hAudioId, aAudioHdr[ uAudioCount ], sizeof( WAVEHDR ) ) )
            iErr = 1;
        if( GlobalFreePtr( (LPCVOID)aAudioHdr[ uAudioCount ] ) )
            iErr = 1;
        aAudioHdr[ uAudioCount ] = 0;
        aAudioPtr[ uAudioCount ] = 0;
    }
    if( waveOutClose( hAudioId ) )
        iErr = 1;
    if( !CloseHandle( hAudioEvent ) )
        iErr = 1;
    hAudioId = 0;
    uAudioWrite = 0;
    uAudioDelay = 0;
    uAudioPlay = uAudioCount - 1;
#endif
    dat->close();
    return iErr;
}

int GSLSound::audioStop()
{
#ifdef WIN32
    unsigned  uPos;
    if( !hAudioId )
        return 1;
    uAudioStop = 1;
    uAudioWrOk = 0;
    warteAufThread( 5000 );
    SetEvent( hAudioEvent );
    waveOutReset( hAudioId );
    if( uAudioWrite != 0 && uAudioPlay != uAudioCount - 1 )
    {
        for( uPos = 0; uPos < uAudioCount; uPos++ )						// clears all buffers 
        {
            memset( aAudioHdr[ uPos ]->lpData, 0, 0x4000 );
            break;
        }
    }
    uAudioPlay = uAudioCount - 1;
    uAudioWrite = 0;
    uAudioDelay = 0;
    SetEvent( hAudioEvent );
    uAudioWrOk = 1;
    if( isRunning() )									// close thread 
    {
        warteAufThread( 1000 );
        if( !iAudioThEnd )
        {
            warteAufThread( 1000 );
            if( run )
                ende();
            Sleep( 100 );
        }
    }
#endif
    return 0;
}

int GSLSound::audioSchreiben( unsigned int uCount )
{
#ifdef WIN32
    int	iDelta, i, iPos;
    WAVEHDR *pHeader;
    if( !uAudioWrOk )
        return 4;
    if( uCount > 0x4000 )
        return 2;
    if( WaitForSingleObject( hAudioEvent, 2000 ) == WAIT_FAILED )
        return 1;
    if( uAudioDelay < 8 )
    {
        aAudioSize[ uAudioDelay ] = uCount;
        uAudioDelay++;
        hAudioCs.lock();				// increase write pos 
        uAudioWrite++;
        if( uAudioWrite >= uAudioCount )
            uAudioWrite = 0;
        hAudioCs.unlock();
        return 0;
    }
    if( uAudioDelay == 8 )
    {
        uAudioDelay++;
        for( i = 8; i > 0; i-- )
        {
            iPos = uAudioWrite - i;
            if( iPos < 0 )iPos += uAudioCount;
            pHeader = aAudioHdr[ iPos ];
            pHeader->dwBufferLength = aAudioSize[ 8 - i ];
            if( waveOutWrite( hAudioId, pHeader, sizeof( WAVEHDR ) ) )
                return 3;
        }
    }
    pHeader = aAudioHdr[ uAudioWrite ];
    pHeader->dwBufferLength = uCount;
    if( waveOutWrite( hAudioId, pHeader, sizeof( WAVEHDR ) ) )
        return 3;
    hAudioCs.lock();
    uAudioWrite++;
    if( uAudioWrite >= uAudioCount )
        uAudioWrite = 0;
    iDelta = uAudioPlay - uAudioWrite;
    if( iDelta < 0 )
        iDelta += uAudioCount;
    if( iDelta < 2 )
        ResetEvent( hAudioEvent );
    hAudioCs.unlock();
#endif
    return 0;
}

int GSLSound::audioLesen( char *buff, int len )
{
    if( dat->getLPosition() < kpf.datPos )
        dat->setLPosition( kpf.datPos, 0 );
    if( dat->getLPosition() >= kpf.datEnd )
        return -1;
    if( dat->getLPosition() + len > kpf.datEnd )
        len = (int)( kpf.datEnd - dat->getLPosition() );
    if( len > 0 )
        dat->lese( buff, len );
#ifdef WIN32
    short *ucb = (short *)buff;
    for( int i = 0; i < len / 2; i++, ucb++ )
    {
        if( i % 2 == 1 )
            *ucb = (short)( *ucb * rechtsV );
        else
            *ucb = (short)( *ucb * linksV );
    }
#endif
    return len;
}

// nicht constant
void GSLSound::playSound()
{
#ifdef WIN32
    if( !status )
    {
        if( _player->addSound( dynamic_cast<GSLSound *>( getThis() ) ) )
            status = 1;
        if( !audioOpen() )
            start();
        else
        {
            _player->removeSound( this );
            status = 0;
        }
    }
    setPause( 0 );
#endif
}

void GSLSound::setPause( bool p )
{
#ifdef WIN32
    if( p )
    {
        if( !status )
            playSound();
        if( status == 1 )
        {
            waveOutPause( hAudioId );
            status = 2;
        }
    }
    else
    {
        if( status == 2 )
        {
            waveOutRestart( hAudioId );
            status = 1;
        }
    }
#endif
}

void GSLSound::stopSound()
{
#ifdef WIN32
    setPause( 0 );
    if( status == 1 )
    {
        status = 0;
        _player->removeSound( this );
        audioClose();
    }
#endif
}

void GSLSound::warteAufSound( int zeit )
{
    warteAufThread( zeit );
}

void GSLSound::setVolume( unsigned int links, unsigned int rechts )
{
#ifdef WIN32
    linksV = (float)links / 0xFFFF;
    rechtsV = (float)rechts / 0xFFFF;
#endif
}

#ifdef WIN32
void GSLSound::msg( UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2 )
{
    WAVEHDR *pHeader;
    int	iDelta;
    switch( uMsg )
    {
    case WOM_DONE:
        pHeader = (WAVEHDR *)dwParam1;
        hAudioCs.lock();
        InterlockedExchangeAdd( (unsigned __int64 *)& lAudioDone, (__int64)pHeader->dwBufferLength );
        iDelta = (int)( uAudioPlay - pHeader->dwUser );
        if( iDelta < 0 )
            iDelta += uAudioCount;
        if( iDelta > ( (int)uAudioCount >> 1 ) )
        {
            uAudioPlay = (unsigned int)pHeader->dwUser;
            if( uAudioPlay >= uAudioCount )
                uAudioPlay = 0;
            iDelta = uAudioPlay - uAudioWrite;
            if( iDelta < 0 )
                iDelta += uAudioCount;
            if( iDelta >= 2 )
                SetEvent( hAudioEvent );
        }
        hAudioCs.unlock();
        break;
    }
}
#endif

void GSLSound::thread()
{
#ifdef WIN32
    iAudioThEnd = 0;
    uAudioStop = 0;
    if( !hAudioId )
        return;
    int iCount;
    while( !uAudioStop && hAudioId )
    {
        iCount = audioLesen( aAudioPtr[ uAudioWrite ], 0x4000 );
        if( iCount <= 0 )
            break;
        audioSchreiben( iCount );
    }
    iAudioThEnd = 1;
    stopSound();
#endif
    run = 0;
}

// zum Speichern
void GSLSound::open()
{
#ifdef WIN32
    if( !status )
    {
        dat->open( Datei::Style::lesen );
        status = 3;
    }
#else
    dat->open( Datei::Style::lesen );
#endif
}

int GSLSound::getDaten( char *buffer, int len )
{
#ifdef WIN32
    if( status != 3 )
        return -1;
#endif
    return audioLesen( buffer, len );
}

void GSLSound::close()
{
#ifdef WIN32
    if( status == 3 )
    {
        dat->close();
        status = 0;
    }
#else
    dat->close();
#endif
}

bool GSLSound::istMono() const
{
    return kpf.channels == 1;
}

int GSLSound::getSampleRate() const
{
    return kpf.sampleRate;
}

__int64 GSLSound::getDatLength() const
{
    return kpf.datEnd - kpf.datPos;
}

// constant
#ifdef WIN32
HWAVEOUT GSLSound::getHandle() const
{
    return hAudioId;
}
#endif

// Reference Counting
Thread *GSLSound::getThis()
{
    return Thread::getThis();
}

Thread *GSLSound::release()
{
#ifdef WIN32
    if( ref == 2 && run )
        stopSound();
#endif
    return Thread::release();
}