#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include "alMain.h"
#include "alMidi.h"
#include "alError.h"
#include "alThunk.h"
#include "evtqueue.h"
#include "rwlock.h"
#include "alu.h"

#include "midi/base.h"


MidiSynth *SynthCreate(ALCdevice *device)
{
    MidiSynth *synth = FSynth_create(device);
    if(!synth) synth = DSynth_create(device);
    return synth;
}


AL_API void AL_APIENTRY alMidiSoundfontSOFT(ALuint id)
{
    alMidiSoundfontvSOFT(1, &id);
}

AL_API void AL_APIENTRY alMidiSoundfontvSOFT(ALsizei count, const ALuint *ids)
{
    ALCdevice *device;
    ALCcontext *context;
    MidiSynth *synth;
    ALenum err;

    context = GetContextRef();
    if(!context) return;

    if(count < 0)
        SET_ERROR_AND_GOTO(context, AL_INVALID_VALUE, done);

    device = context->Device;
    synth = device->Synth;

    WriteLock(&synth->Lock);
    if(synth->State == AL_PLAYING || synth->State == AL_PAUSED)
        alSetError(context, AL_INVALID_OPERATION);
    else
    {
        err = V(synth,selectSoundfonts)(context, count, ids);
        if(err != AL_NO_ERROR)
            alSetError(context, err);
    }
    WriteUnlock(&synth->Lock);

done:
    ALCcontext_DecRef(context);
}


AL_API void AL_APIENTRY alMidiEventSOFT(ALuint64SOFT time, ALenum event, ALsizei channel, ALsizei param1, ALsizei param2)
{
    ALCdevice *device;
    ALCcontext *context;
    ALenum err;

    context = GetContextRef();
    if(!context) return;

    if(!(event == AL_NOTEOFF_SOFT || event == AL_NOTEON_SOFT ||
         event == AL_KEYPRESSURE_SOFT || event == AL_CONTROLLERCHANGE_SOFT ||
         event == AL_PROGRAMCHANGE_SOFT || event == AL_CHANNELPRESSURE_SOFT ||
         event == AL_PITCHBEND_SOFT))
        SET_ERROR_AND_GOTO(context, AL_INVALID_ENUM, done);
    if(!(channel >= 0 && channel <= 15))
        SET_ERROR_AND_GOTO(context, AL_INVALID_VALUE, done);
    if(!(param1 >= 0 && param1 <= 127))
        SET_ERROR_AND_GOTO(context, AL_INVALID_VALUE, done);
    if(!(param2 >= 0 && param2 <= 127))
        SET_ERROR_AND_GOTO(context, AL_INVALID_VALUE, done);

    device = context->Device;
    ALCdevice_Lock(device);
    err = MidiSynth_insertEvent(device->Synth, time, event|channel, param1, param2);
    ALCdevice_Unlock(device);
    if(err != AL_NO_ERROR)
        alSetError(context, err);

done:
    ALCcontext_DecRef(context);
}

AL_API void AL_APIENTRY alMidiSysExSOFT(ALuint64SOFT time, const ALbyte *data, ALsizei size)
{
    ALCdevice *device;
    ALCcontext *context;
    ALenum err;
    ALsizei i;

    context = GetContextRef();
    if(!context) return;

    if(!data || size < 0)
        SET_ERROR_AND_GOTO(context, AL_INVALID_VALUE, done);
    for(i = 0;i < size;i++)
    {
        if((data[i]&0x80))
            SET_ERROR_AND_GOTO(context, AL_INVALID_VALUE, done);
    }

    device = context->Device;
    ALCdevice_Lock(device);
    err = MidiSynth_insertSysExEvent(device->Synth, time, data, size);
    ALCdevice_Unlock(device);
    if(err != AL_NO_ERROR)
        alSetError(context, err);

done:
    ALCcontext_DecRef(context);
}

AL_API void AL_APIENTRY alMidiPlaySOFT(void)
{
    ALCcontext *context;
    MidiSynth *synth;

    context = GetContextRef();
    if(!context) return;

    synth = context->Device->Synth;
    WriteLock(&synth->Lock);
    V(synth,setState)(AL_PLAYING);
    WriteUnlock(&synth->Lock);

    ALCcontext_DecRef(context);
}

AL_API void AL_APIENTRY alMidiPauseSOFT(void)
{
    ALCcontext *context;
    MidiSynth *synth;

    context = GetContextRef();
    if(!context) return;

    synth = context->Device->Synth;
    WriteLock(&synth->Lock);
    V(synth,setState)(AL_PAUSED);
    WriteUnlock(&synth->Lock);

    ALCcontext_DecRef(context);
}

AL_API void AL_APIENTRY alMidiStopSOFT(void)
{
    ALCdevice *device;
    ALCcontext *context;
    MidiSynth *synth;

    context = GetContextRef();
    if(!context) return;

    device = context->Device;
    synth = device->Synth;

    WriteLock(&synth->Lock);
    V(synth,setState)(AL_STOPPED);

    ALCdevice_Lock(device);
    V0(synth,stop)();
    ALCdevice_Unlock(device);
    WriteUnlock(&synth->Lock);

    ALCcontext_DecRef(context);
}

AL_API void AL_APIENTRY alMidiResetSOFT(void)
{
    ALCdevice *device;
    ALCcontext *context;
    MidiSynth *synth;

    context = GetContextRef();
    if(!context) return;

    device = context->Device;
    synth = device->Synth;

    WriteLock(&synth->Lock);
    V(synth,setState)(AL_INITIAL);

    ALCdevice_Lock(device);
    V0(synth,reset)();
    ALCdevice_Unlock(device);
    WriteUnlock(&synth->Lock);

    ALCcontext_DecRef(context);
}


AL_API void AL_APIENTRY alMidiGainSOFT(ALfloat value)
{
    ALCdevice *device;
    ALCcontext *context;

    context = GetContextRef();
    if(!context) return;

    if(!(value >= 0.0f && isfinite(value)))
        SET_ERROR_AND_GOTO(context, AL_INVALID_VALUE, done);

    device = context->Device;
    V(device->Synth,setGain)(value);

done:
    ALCcontext_DecRef(context);
}