#ifdef _WIN32
	#include <windows.h>
	#include <winuser.h>
	#include <stdio.h>
#endif

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

#if LUA_VERSION_NUM >= 502
#  define new_lib(L, l) (luaL_newlib(L, l))
#else
#  define new_lib(L, l) (lua_newtable(L), luaL_register(L, NULL, l))
#endif

#include "midiwire.h"
#include "mmsystem.h"

static int midiwire_sleep (lua_State *L) {
	int ms = luaL_checkinteger(L, 1);
	sleep(ms);
	return 0;
}

/* DEVICES */

static int midiwire_device_count (lua_State *L) {
    int devices_count = midiOutGetNumDevs();
    lua_pushinteger(L, devices_count);
    return 1;
}

static int midiwire_name (lua_State *L) {
    MIDIOUTCAPSA moc;
    int port = luaL_checkinteger(L, 1);

    if (!midiOutGetDevCapsA(port, &moc, sizeof(MIDIOUTCAPSA))) {
        lua_pushstring(L, moc.szPname);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

/* OUTPUT */

static int midiwire_open_out (lua_State *L) {
    unsigned long result;
    HMIDIOUT      outHandle;
    int port = luaL_checkinteger(L, 1);

    result = midiOutOpen(&outHandle, port, 0, 0, CALLBACK_NULL);
    if (result != 0)
    {
       luaL_error(L, "There was an error opening MIDI output!");
       return 0;
    }
    lua_pushlightuserdata (L, outHandle);
    return 1;
}

static int midiwire_close_out (lua_State *L) {
    unsigned long result;
    HMIDIOUT      outHandle;
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);

	outHandle = lua_touserdata (L, 1);

	midiOutClose(outHandle);

    return 0;
}

static int midiwire_reset (lua_State *L) {
    unsigned long result;
    HMIDIOUT      outHandle;
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
	
	outHandle = lua_touserdata (L, 1);
	
	midiOutReset(outHandle);
   
    return 0;
}

static int midiwire_drum (lua_State *L) {
    unsigned long result;
    HMIDIOUT      outHandle;
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
	int drum = luaL_checkinteger(L, 2);
	
	outHandle = lua_touserdata (L, 1);
	
	union { unsigned long word; unsigned char data[4]; } message;
   // message.data[0] = command byte of the MIDI message, for example: 0x90
   // message.data[1] = first data byte of the MIDI message, for example: 60
   // message.data[2] = second data byte of the MIDI message, for example 100
   // message.data[3] = not used for any MIDI messages, so set to 0
   message.data[0] = 0x99;  // MIDI note-on message (requires to data bytes)
   message.data[1] = drum;    // MIDI note-on message: Key number (60 = middle C)
   message.data[2] = 127;   // MIDI note-on message: Key velocity (100 = loud)
   message.data[3] = 0;     // Unused parameter
	
	midiOutShortMsg(outHandle, message.word);
   
    return 0;
}

static int midiwire_note_on (lua_State *L) {
    unsigned long result;
    HMIDIOUT      outHandle;
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
	int note = luaL_checkinteger(L, 2);
	int velocity = luaL_checkinteger(L, 3);
	int channel = 0;
	if (lua_gettop (L)==4) {
		channel = luaL_checkinteger(L, 4) - 1;
	}
	
	outHandle = lua_touserdata (L, 1);
	
	union { unsigned long word; unsigned char data[4]; } message;
   // message.data[0] = command byte of the MIDI message, for example: 0x90
   // message.data[1] = first data byte of the MIDI message, for example: 60
   // message.data[2] = second data byte of the MIDI message, for example 100
   // message.data[3] = not used for any MIDI messages, so set to 0
   message.data[0] = 0x90 + channel;  // MIDI note-on message (requires to data bytes)
   message.data[1] = note;    // MIDI note-on message: Key number (60 = middle C)
   message.data[2] = velocity;   // MIDI note-on message: Key velocity (100 = loud)
   message.data[3] = 0;     // Unused parameter
	
	midiOutShortMsg(outHandle, message.word);
   
    return 0;
}

static int midiwire_note_off (lua_State *L) {
    unsigned long result;
    HMIDIOUT      outHandle;
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
	int note = luaL_checkinteger(L, 2);
	int channel = 0;
	if (lua_gettop (L)==3) {
		channel = luaL_checkinteger(L, 3) - 1;
	}
	
	outHandle = lua_touserdata (L, 1);
	
	union { unsigned long word; unsigned char data[4]; } message;
   message.data[0] = 0x80 + channel;  // MIDI note-on message (requires to data bytes)
   message.data[1] = note;    // MIDI note-on message: Key number (60 = middle C)
   message.data[2] = 0;   // MIDI note-on message: Key velocity (100 = loud)
   message.data[3] = 0;     // Unused parameter
	
	midiOutShortMsg(outHandle, message.word);
   
    return 0;
}

static int midiwire_program_change (lua_State *L) {
    unsigned long result;
    HMIDIOUT      outHandle;
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
	int program = luaL_checkinteger(L, 2) - 1;
	int channel = 0;
	if (lua_gettop (L)==3) {
		channel = luaL_checkinteger(L, 3) - 1;
	}
	
	outHandle = lua_touserdata (L, 1);
	
	union { unsigned long word; unsigned char data[4]; } message;
   message.data[0] = 0xC0 + channel;  // MIDI note-on message (requires to data bytes)
   message.data[1] = program;    // MIDI note-on message: Key number (60 = middle C)
   message.data[2] = 0;   // MIDI note-on message: Key velocity (100 = loud)
   message.data[3] = 0;     // Unused parameter
	
	midiOutShortMsg(outHandle, message.word);
   
    return 0;
}

/* INPUT */

void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
	switch(wMsg) {
	case MIM_OPEN:
		printf("wMsg=MIM_OPEN\n");
		break;
	case MIM_CLOSE:
		printf("wMsg=MIM_CLOSE\n");
		break;
	case MIM_DATA:
		printf("wMsg=MIM_DATA, dwInstance=%08x, dwParam1=%08x, dwParam2=%08x\n", dwInstance, dwParam1, dwParam2);
		break;
	case MIM_LONGDATA:
		printf("wMsg=MIM_LONGDATA\n"); 
		break;
	case MIM_ERROR:
		printf("wMsg=MIM_ERROR\n");
		break;
	case MIM_LONGERROR:
		printf("wMsg=MIM_LONGERROR\n");
		break;
	case MIM_MOREDATA:
		printf("wMsg=MIM_MOREDATA\n");
		break;
	default:
		printf("wMsg = unknown\n");
		break;
	}
	return;
}

void PrintMidiInDevices()
{
	UINT nMidiDeviceNum;
	MIDIINCAPS caps;

	nMidiDeviceNum = midiInGetNumDevs();
	if (nMidiDeviceNum == 0) {
		fprintf(stderr, "midiInGetNumDevs() return 0...");
		return;
	}

	printf("== PrintMidiDevices() == \n");
	unsigned int i;
	for (i = 0; i < nMidiDeviceNum; ++i) {
		midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
		printf("\t%d : name = %s\n", i, caps.szPname);
	}
	printf("=====\n");
}

// https://gist.github.com/yoggy/1485181

// TODO - inputy jsou jinak číslované
// po otevření musím zavolat midiInStart
// pro uzavření midiInStop & midiInClose

static int midiwire_open_in (lua_State *L) {
	PrintMidiInDevices();

    unsigned long result;
    HMIDIIN       inHandle;
    int port = luaL_checkinteger(L, 1);

    result = midiInOpen(&inHandle, port, (DWORD)(void*)MidiInProc, 0, CALLBACK_FUNCTION);
    if (result != MMSYSERR_NOERROR)
    {
		lua_pushnil (L);
		lua_pushinteger (L, result);
       return 2;
    }

    midiInStart(inHandle);
    if (result != MMSYSERR_NOERROR)
    {
    	lua_pushnil (L);
    	lua_pushinteger (L, result);
    	return 2;
    }

    lua_pushlightuserdata (L, inHandle);
    return 1;
}


/* FUNCS */

static const struct luaL_Reg midiwire[] = {
	{"sleep", midiwire_sleep},
	{"device_count", midiwire_device_count},
	{"name", midiwire_name},
	{"open_out", midiwire_open_out},
	{"close_out", midiwire_close_out},
	{"note_on", midiwire_note_on},
	{"note_off", midiwire_note_off},
	{"drum", midiwire_drum},
	{"program_change", midiwire_program_change},
	{"reset", midiwire_reset},
	{"open_in", midiwire_open_in},
	{NULL, NULL},
};

MIDIWIRE_EXPORT int luaopen_midiwire (lua_State *L) {
	new_lib (L, midiwire);
	lua_pushvalue(L, -1);
	lua_setglobal(L, "midiwire");
	return 1;
}
