#include "audiomanager.h"

audiomanager::sound::sound(std::string fname, audiomanager* prnt){
    playnum = 0;
    filename = fname;
    data = 0;
    attenuate = 0;
    playing = false;
    looping = false;
    isnull = filename == "";
    parent = prnt;
    ripwid = 0;
    rippos = 0;
}
audiomanager::sound::~sound(){
    if( data != 0 ) uncache();
}
void audiomanager::sound::setrip(double val){
    if( val > .01 || val < .01 )
    ripwid = val;
    else ripwid = 0;
    rippos = 0;
    //printf("%f\n", ripwid);
}
void audiomanager::sound::play( int which ){
    if(playing == (which != 0)) return;
    if( isnull ) return;
    if( data == 0 )
        uncache();
    if( isnull || data == 0 ) return;
    playnum += which!=0;
    playing = which != 0;
    if( playing && playnum == 1 ){
        parent->playing->push( this );
    }
}
void audiomanager::sound::seek( int whence, size_t offset ){
    if( isnull || data == 0 ) return;
    size_t cpos = data->samps.pos();
    data->samps.rewind();
    switch (whence){
        case SEEK_SET: cpos = offset; break;
        case SEEK_CUR: cpos += offset; break;
        #ifdef SEEK_END
            case SEEK_END: cpos = data->samps.totallength() - offset; break;
        #endif
    }
    if( cpos < 0 ) cpos = 0;
    if( cpos > data->samps.totallength()) cpos = data->samps.totallength();
    cpos /= 4;
    cpos *= 4;
    data->samps.pullreadonly(0, cpos);
}
void audiomanager::sound::loop( int which ){
    if( isnull ) return;
    looping = which != 0;
}
void audiomanager::sound::set_attenuation( int a ){
    if( isnull ) return;
    attenuate = a;
}
void audiomanager::sound::cache(const char* fname){
    if( isnull || data == 0 ) return;
    delete data;
    data = 0;
}
void audiomanager::sound::uncache(){
    if( isnull || data != 0) return;
    data = load_sound( filename.c_str() );
}
decoder* audiomanager::sound::getdata(){
    return data;
}

void audiomanager::sound::pull_samples(int16_t* buffer, size_t len, bool replace){
    //ripwid = -.5;
    if( isnull ) return;
    if( !playing ) {
            return;
    }
    if( data == 0 )
        uncache();
    if( isnull || data == 0 ) return;
    int16_t dsamp[2] = {0,0};

    for( int i = 0; i < len; i += 2 ){
        if( looping && data->samps.eof() ){
            data->samps.rewind();
        }
        int32_t newsamp = replace?0:buffer[i];
        int32_t newsamp2 = replace?0:buffer[i+1];

        //int16_t dsamp = 0;
        if( ripwid >= 0 ){
            if( !data->samps.eof() )
                if( rippos <= 0 ){
                    data->samps.pull( &dsamp, sizeof( dsamp ) );
                } else rippos -= 1;

            if( rippos <= 0 ) rippos += ripwid;
        } else {
            int16_t dummysamp = 0;
            if( !data->samps.eof() ){
                while( rippos <= 0 ){
                    data->samps.pullreadonly( 0, sizeof( dsamp ) );
                    rippos += 1;
                }
                data->samps.pull( &dsamp, sizeof( dsamp ) );
            }

            if( rippos >= 0 ) rippos += ripwid;
        }
        //if( ++rippos >= ripwid * 2 ) rippos = 0;
        newsamp += dsamp[0];
        newsamp2 += dsamp[1];

        newsamp = newsamp > 32767 ? 32767 : newsamp < -32767 ? -32767 : newsamp;
        newsamp2 = newsamp2 > 32767 ? 32767 : newsamp2 < -32767 ? -32767 : newsamp2;

        buffer[i] = newsamp;
        buffer[i+1] = newsamp2;

    }
}

#if SOUND_SYSTEM == SDL
    void audiomanager::sdlmixaudio(void *a, Uint8 *stream, int len){
        audiomanager* self = (audiomanager*) a;
        memset( stream, 0, len );
        self->playing->rewind();
        sound* s = self->playing->next();
        while( s != 0 ){
            if( s->playing == false ){
                self->playing->rmcurr();
                s->playnum--;
            }
            else{
                s->pull_samples( (int16_t*)stream, len/sizeof(int16_t), false );
            }
            s = self->playing->next();
        }
    }
#elif SOUND_SYSTEM == WINMM
    int audiomanager::winmmsoundloop( void* sel ){

        audiomanager* self = (audiomanager*) sel;
        HWAVEOUT soundout = self->winmmsoundout;
        char* b1[3] = {(char*) malloc( 2048*4 ),(char*) malloc( 2048*4 ),0};
        struct{
            void operator()(char* stream, audiomanager* self ){

                int len = 2048*4;
                memset( stream, 0, len );
                self->playing->rewind();
                sound* s = self->playing->next();
                while( s != 0 ){
                    if( s->playing == false ){
                        self->playing->rmcurr();
                    }
                    else{
                        s->pull_samples( (int16_t*)stream, len/sizeof(int16_t), false );
                    }
                    s = self->playing->next();
                }

            }
        } populate;

        WAVEHDR z[2];
        z[0].lpData = b1[0];
        populate( b1[0], self );
        z[0].dwBufferLength = 2048*4;
        z[0].dwFlags = 0;
        z[0].dwLoops = 0;
        z[0].dwUser = 0;
        waveOutPrepareHeader( soundout, &z[0], sizeof( z[0] ) );
        waveOutWrite( soundout, &z[0], sizeof( z[0] ) );
        int o = 0;
        while( true ){
                if( *(self->winmmsignal) == 1 ){
                    delete self->winmmsignal;
                    ::free( b1[0] );
                    ::free( b1[1] );

                    return -1;
                }

            o = !o;

            z[o].lpData = b1[o];
            populate( b1[o], self );
            z[o].dwBufferLength = 2048*4;
            z[o].dwFlags = 0;
            z[o].dwLoops = 0;
            z[o].dwUser = 0;
            waveOutPrepareHeader( soundout, &(z[o]), sizeof( z[o] ) ) ;
            waveOutWrite( soundout, &(z[o]), sizeof( z[o] ) );
            while( ((z[!o].dwFlags) & (WHDR_DONE)) == 0 ){ Sleep(1); }
            waveOutUnprepareHeader( soundout, &(z[!o]), sizeof( z[!o] ) );
        }

    }
#elif SOUND_SYSTEM == ALSA
    #error
#endif

audiomanager::audiomanager(){
    nullsound = new sound("", this);
    playing = new linkedlist();
    #if SOUND_SYSTEM == SDL
        SDL_AudioSpec fmt;

        fmt.freq = 44100;
        fmt.format = AUDIO_S16;
        fmt.channels = 2;
        fmt.samples = 1024;
        fmt.callback = sdlmixaudio;
        fmt.userdata = (void*) this;

        if ( SDL_OpenAudio(&fmt, NULL) < 0 ) {
            printf("There was an error opening the audio device\n"); return;
            return;
        }
        SDL_PauseAudio(0);
    #elif SOUND_SYSTEM == WINMM
        unsigned long result;
        HWAVEOUT      outHandle;
        WAVEFORMATEX  waveFormat;

        waveFormat.wFormatTag = WAVE_FORMAT_PCM;
        waveFormat.nChannels = 2;
        waveFormat.nSamplesPerSec = 44100;
        waveFormat.wBitsPerSample = 16;
        waveFormat.nBlockAlign = waveFormat.nChannels * (waveFormat.wBitsPerSample/8);
        waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
        waveFormat.cbSize = 0;

        result = waveOutOpen(&outHandle, WAVE_MAPPER, &waveFormat, 0, 0, CALLBACK_WINDOW);
        if (result)
        {
           printf("There was an error opening the audio device\n"); return;
        }
        winmmsignal = new int;
        *winmmsignal = 0;
        winmmsoundout = outHandle;
        SDL_CreateThread( winmmsoundloop, (void*) this );

    #elif SOUND_SYSTEM == ALSA
        #error
    #endif
    ucol = true;

}
audiomanager::~audiomanager(){
    #if SOUND_MANAGER == WINMM
        *winmmsignal = 1;
    #endif

}
void audiomanager::uncacheonload(int which){
    ucol = which!=0;
}
int audiomanager::load( const char* fname ){
    sounds.push_back( new sound( fname, this ) );
    if( ucol )
    sounds[sounds.size()-1]->uncache();
    return sounds.size()-1;
}
void audiomanager::free( int id ){
    if( id < 0 || id >= sounds.size() ) return;
    if( sounds[id] == nullsound ) return;
    delete sounds[id];
    sounds[id] = nullsound;
}
audiomanager::sound* audiomanager::operator[](int index){
    if( index < 0 || index >= sounds.size() ) return nullsound;
    return sounds[index];
}
void audiomanager::replace( int id, const char* fname ){
    if( id < 0 || id >= sounds.size() ) return;
    free( id );
    sounds[id] = new sound( fname, this );
}


audiomanager::linkedlist::linkedlist(){
    first = last = curr = 0;
}
audiomanager::linkedlist::~linkedlist(){
    clear();
}
audiomanager::sound* audiomanager::linkedlist::next(){
    if( curr == 0 ) return 0;
    sound* ret = curr->a;
    curr = curr->n;
    return ret;
}
void audiomanager::linkedlist::rmcurr(){
    if( curr == 0 ) return;
    if( curr->n != 0 ) curr->n->p = curr->p;
    if( curr->p != 0 ) curr->p->n = curr->n;
    node* nxt = curr->n;
    if( last == curr ) last = curr;
    if( first == curr ) first = curr;
    delete curr;
    curr = nxt;
    len--;
}
void audiomanager::linkedlist::rewind(){
    curr = first;
}
void audiomanager::linkedlist::clear(){
    node* a = first;
    first = last = 0;
    len = 0;
    while( a != 0 ){
        node* nxt = a->n;
        delete a;
        a = nxt;
    }

}
void audiomanager::linkedlist::push( audiomanager::sound* val ){
    node* newnode = new node;
    newnode->a = val;
    newnode->p = last;
    newnode->n = 0;
    if( last != 0 ) last->n = newnode;
    last = newnode;
    if( first == 0 ) first = newnode;
    len++;
}
