#include <stdio.h>
#include <windows.h>
#include <mmsystem.h>
#include <stdint.h>
#include "podule_api.h"
#include "midi.h"

typedef struct midi_t
{
        int pos, len;
        uint32_t command;
        int insysex;
        uint8_t buffer[1024+2];

        HMIDIOUT out_device;
        HMIDIIN in_device;

        void (*receive)(void *p, uint8_t val);
        
        void *p;
} midi_t;

void midi_close();

void midi_get_dev_name(int num, char *s);
void midi_in_get_dev_name(int num, char *s);

static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);

void *midi_init(void *p, void (*receive)(void *p, uint8_t val), void (*log)(const char *format, ...), const podule_callbacks_t *podule_callbacks, podule_t *podule)
{
        midi_t *midi;
        MMRESULT hr;
        char name[256];
        int c;
        const char *device;
        int midi_in_dev_nr, midi_out_dev_nr;
        
        midi = malloc(sizeof(midi_t));
        memset(midi, 0, sizeof(midi_t));
        
        device = podule_callbacks->config_get_string(podule, "midi_in_device", "0");
        sscanf(device, "%i", &midi_in_dev_nr);
	if (log)
	        log("midi_init: midi_in_dev_nr=%i\n", midi_in_dev_nr);
        device = podule_callbacks->config_get_string(podule, "midi_out_device", "0");
        sscanf(device, "%i", &midi_out_dev_nr);
	if (log)
	        log("midi_init: midi_out_dev_nr=%i\n", midi_out_dev_nr);

        midi->p = p;
        midi->receive = receive;
        
        if (log)
                log("num_out_devs=%i\n", midiOutGetNumDevs());
        for (c = 0; c < midiOutGetNumDevs(); c++)
        {
                midi_get_dev_name(c, name);
                if (log)
                        log("name%i = %s\n", c, name);
        }

        if (log)
                log("num_in_devs=%i\n", midiInGetNumDevs());
        for (c = 0; c < midiInGetNumDevs(); c++)
        {
                midi_in_get_dev_name(c, name);
                if (log)
                        log("name%i = %s\n", c, name);
        }

        hr = midiOutOpen(&midi->out_device, midi_out_dev_nr, 0,
           0, CALLBACK_NULL);
        if (hr != MMSYSERR_NOERROR) {
//                lark_log("midiOutOpen error - %08X\n",hr);
                midi_out_dev_nr = 0;
                hr = midiOutOpen(&midi->out_device, midi_out_dev_nr, 0,
                   0, CALLBACK_NULL);
                if (hr != MMSYSERR_NOERROR) {
//                        lark_log("midiOutOpen error - %08X\n",hr);
                        return midi;
                }
        }
        midiOutReset(midi->out_device);

        hr = midiInOpen(&midi->in_device, midi_in_dev_nr, (DWORD)(void *)midi_in_callback,
           (DWORD)midi, CALLBACK_FUNCTION);
        if (hr != MMSYSERR_NOERROR) {
//                lark_log("midiInOpen error - %08X\n",hr);
                midi_in_dev_nr = 0;
                hr = midiInOpen(&midi->in_device, midi_in_dev_nr, (DWORD)(void *)midi_in_callback,
                   (DWORD)midi, CALLBACK_FUNCTION);
                if (hr != MMSYSERR_NOERROR) {
//                        lark_log("midiInOpen error - %08X\n",hr);
                        return midi;
                }
        }
        midiInStart(midi->in_device);
        
        return midi;
}

void midi_close(void *p)
{
        midi_t *midi = p;
        
        if (midi->in_device != NULL)
        {
                midiInReset(midi->in_device);
                midiInClose(midi->in_device);
                midi->in_device = NULL;
        }
        if (midi->out_device != NULL)
        {
                midiOutReset(midi->out_device);
                midiOutClose(midi->out_device);
                midi->out_device = NULL;
        }
}

int midi_get_num_devs()
{
        return midiOutGetNumDevs();
}
void midi_get_dev_name(int num, char *s)
{
        MIDIOUTCAPS caps;

        midiOutGetDevCaps(num, &caps, sizeof(caps));
        strcpy(s, caps.szPname);
}
void midi_in_get_dev_name(int num, char *s)
{
        MIDIINCAPS caps;

        midiInGetDevCaps(num, &caps, sizeof(caps));
        strcpy(s, caps.szPname);
}

static int midi_lengths[8] = {3, 3, 3, 3, 2, 2, 3, 1};

static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
        midi_t *midi = (midi_t *)dwInstance;
        int length;
        MIDIHDR *hdr;
        int c;
        
//        lark_log("midi_in_callback %i\n", wMsg);
        
        switch (wMsg)
        {
                case MIM_DATA:
                length = midi_lengths[(dwParam1 & 0x70) >> 4];
/*                lark_log("midi_in_callback: %02x %02x %02x (%i)\n",
                        dwParam1 & 0xFF,
                        (dwParam1 >> 8) & 0xFF,
                        (dwParam1 >> 16) & 0xFF,
                        length);*/
                midi->receive(midi->p, dwParam1 & 0xFF);
                if (length >= 2)
                        midi->receive(midi->p, (dwParam1 >> 8) & 0xFF);
                if (length >= 3)
                        midi->receive(midi->p, (dwParam1 >> 16) & 0xFF);
                break;
                
                case MIM_LONGDATA:
                hdr = (MIDIHDR *)dwParam1;
                for (c = 0; c < hdr->dwBytesRecorded; c++)
                        midi->receive(midi->p, hdr->lpData[c]);
                break;
        }
}

static void midi_send(midi_t *midi)
{
        MIDIHDR hdr;

        hdr.lpData = (LPSTR)midi->buffer;
        hdr.dwBufferLength = midi->pos;
        hdr.dwFlags = 0;
        
/*        pclog("Sending sysex : ");
        for (c = 0; c < midi_pos; c++)
                pclog("%02x ", midi_sysex_data[c]);
        pclog("\n");*/
        
        midiOutPrepareHeader(midi->out_device, &hdr, sizeof(MIDIHDR));
        midiOutLongMsg(midi->out_device, &hdr, sizeof(MIDIHDR));
        
        midi->insysex = 0;
}

void midi_write(void *p, uint8_t val)
{
        midi_t *midi = p;
        
//        lark_log("midi_write: val=%02x\n", val);
        if ((val & 0x80) && !(val == 0xf7 && midi->insysex))
        {
                midi->pos = 0;
                midi->len = midi_lengths[(val >> 4) & 7];
                midi->command = 0;
                if (val == 0xf0)
                        midi->insysex = 1;
        }

        midi->buffer[midi->pos++] = val;

        if (midi->insysex)
        {
                if (val == 0xf7 || midi->pos >= 1024+2)
                        midi_send(midi);
                return;
        }
                        
        if (midi->len)
        {
                if (midi->pos > midi->len)
                {
                    /* It's a repeated command */
                    midi->pos = 2;
                    midi->buffer[1] = val;
                }
            
                if (midi->pos == midi->len)
                        midi_send(midi);
        }
}

podule_config_selection_t *midi_out_devices_config(void)
{
        int nr_devs = midiOutGetNumDevs();
        podule_config_selection_t *sel = malloc(sizeof(podule_config_selection_t) * (nr_devs+2));
        podule_config_selection_t *sel_p = sel;
        char *midi_dev_text = malloc(65536);
        int c;

        strcpy(midi_dev_text, "None");
        sel_p->description = midi_dev_text;
        sel_p->value = -1;
        sel_p++;
        midi_dev_text += strlen(midi_dev_text)+1;

        for (c = 0; c < nr_devs; c++)
        {
                midi_get_dev_name(c, midi_dev_text);
                sel_p->description = midi_dev_text;
                sel_p->value = c;
                sel_p++;

                midi_dev_text += strlen(midi_dev_text)+1;
        }

        strcpy(midi_dev_text, "");
        sel_p->description = midi_dev_text;

        return sel;
}
podule_config_selection_t *midi_in_devices_config(void)
{
        int nr_devs = midiInGetNumDevs();
        podule_config_selection_t *sel = malloc(sizeof(podule_config_selection_t) * (nr_devs+2));
        podule_config_selection_t *sel_p = sel;
        char *midi_dev_text = malloc(65536);
        int c;
        
        strcpy(midi_dev_text, "None");
        sel_p->description = midi_dev_text;
        sel_p->value = -1;
        sel_p++;
        midi_dev_text += strlen(midi_dev_text)+1;
                
        for (c = 0; c < nr_devs; c++)
        {
                midi_in_get_dev_name(c, midi_dev_text);
                sel_p->description = midi_dev_text;
                sel_p->value = c;
                sel_p++;

                midi_dev_text += strlen(midi_dev_text)+1;
        }

        strcpy(midi_dev_text, "");
        sel_p->description = midi_dev_text;

        return sel;
}
