// #### midipy 2.0 revision 1.0
// @@@@
// #### for ALT139 by sim[¿]lato ###
// #### purpose getting midi input stream in Python && Blender (http://www.python.org && http://www.blender.nl) ###
// #### makes use of OSC libs (Open Sound Control)(http://www.hammersound.net) ###
// #### further changes: midioutput

#include <Python.h>
#include <algorithm>
#include <OSC_MIDIInput.h>
#include <OSC_MIDIOutput.h>

//--------------------------------------------------------------------
PyObject *_midimsg=NULL;
OSC_MIDIMessage _message;
static PyObject* getError(OSC_ERR err);
bool _initIn;
bool _initOut;
bool _fmsg;
int _numInDevices;
int _numOutDevices;
int _inDevice=-1;
int _outDevice=-1;

std::vector<int> chan_vector(0);

//--------------------------------------------------------------------

/*
	##################
		midi in
	##################
*/

// class definition for overiding MidiInput(OSC_MIDIMessage message)
class mymidipy : public OSC_MIDIInput
{
public:
	virtual void MidiInput(OSC_MIDIMessage message)
	{
		_message=message;
		_fmsg = true;
	}
};

//--------------------------------------------------------------------
mymidipy _midiin;
OSC_MIDIOutput _midiout;
//--------------------------------------------------------------------

// ### set channels to receive
static PyObject *setChannel(PyObject *self, PyObject *args)
{
	int start_chan, end_chan;
	char* parse;
	char* ret;
	PyObject *py_parse;
	PyObject *list;

	if(!PyArg_ParseTuple(args, "s|O", &parse, &py_parse))
		return NULL;

	if(strstr(parse, "all") || strstr(parse, "All") || strstr(parse, "ALL"))
	{ 
		start_chan=1;
		end_chan=16;
		chan_vector.clear();
		chan_vector.resize(end_chan);
		for(int i=0; i<=chan_vector.size()-1;i++)
			chan_vector[i]= start_chan++;
		
		list=PyList_New(chan_vector.size());
		for(i=0;i<=chan_vector.size()-1;i++)
			PyList_SetItem(list, i, PyInt_FromLong(chan_vector[i]));
		return Py_BuildValue("O", list);
	}
	else if(ret=strstr(parse, "..."))
	{
		int i=atoi(&parse[0]);
		itoa(i, ret, 10);
		int j=atoi(&parse[3+strlen(ret)]);
		if(i<1||i>16 || j<1||j>16 || i>j)
			return Py_BuildValue("s", "values out of range");
		start_chan=i;
		end_chan=j;
		chan_vector.clear();
		chan_vector.resize(end_chan-start_chan+1);
		for(i=0; i<=chan_vector.size()-1;i++)
			chan_vector[i]= start_chan++;

		list=PyList_New(chan_vector.size());
		for(i=0;i<=chan_vector.size()-1;i++)
			PyList_SetItem(list, i, PyInt_FromLong(chan_vector[i]));
		return Py_BuildValue("O", list);

	}
	else if(strstr(parse, "num") || strstr(parse, "Num") || strstr(parse, "NUM"))
	{
		if(py_parse==NULL)
			return Py_BuildValue("s", "wrong number of arguments");
		else if(!PyList_Check(py_parse))
			return Py_BuildValue("s", "second argument should be of type PyList");
		else
		{
			const size_t len=PyList_Size(py_parse);
			if(len>16)
				return Py_BuildValue("s", "maximum of 16 items in list, if you need all channels use \'all\'");
			chan_vector.resize(len);
			for(int i=0;i<=len-1;i++)
			{
				PyObject *item;
				if(!PyInt_Check(item=PyList_GetItem(py_parse,i)))
					return Py_BuildValue("s", "only arguments of type PyInt are allowed");
				int x = PyInt_AsLong(item);
				if(x<1||x>16) 
					return Py_BuildValue("s", "only values between 1 and 16 are allowed");
				chan_vector[i]=x;
			}

			std::sort(chan_vector.begin(), chan_vector.end());
			std::unique(chan_vector.begin(), chan_vector.end());

			list=PyList_New(chan_vector.size());
			for(i=0;i<=chan_vector.size()-1;i++)
				PyList_SetItem(list, i, PyInt_FromLong(chan_vector[i]));

			return Py_BuildValue("O", list);
		}
	}
	return Py_BuildValue("s", "usage: all(parse all channels); num,[list](parse only channels in list); start....end(parse channels start to end)");
}


// ### callback function
static PyObject *returnMidiMsg(PyObject *self, PyObject *args)
{		
	if(_fmsg)
	{
		for(int i=0; i<=chan_vector.size()-1; i++)
			if(_message.GetChannel()+1==chan_vector[i])	
			{
				if(!_message.GetStatusByte())
					continue;
				_fmsg=false;
				return Py_BuildValue("[i,i,i,i,i]", _message.GetChannel()+1, 
											_message.GetStatusByte(),
											_message.GetDataByte1(),
											_message.GetDataByte2(),
											(unsigned)_message.GetTimeStamp());
				break;
			}
	}

	return Py_BuildValue("i", -1);
}

// ### flush midi queue
static PyObject *flush(PyObject *self, PyObject *args)
{
	_message.SetChannel(0);
	_message.SetStatusByte(0);
	_message.SetDataByte1(0);
	_message.SetDataByte2(0);
	_fmsg=false;

	return Py_BuildValue("i", 1);
}

// ### get number of midi in devices and return as PyObject
static PyObject *getNumMidiInDevices(PyObject *self, PyObject *args)
{
	int _numInDevices = _midiin.GetNumDevices();
	return (!_numInDevices) ? NULL : Py_BuildValue("i", _numInDevices);
}


// ### get a list of midi in  devices and return as PyObject
static PyObject *getMidiInDevice(PyObject *self, PyObject *args)
{
	if (!_initIn)
	{
	OSC_StringArray oscList;
	PyObject *pyList;
	pyList = (_numInDevices) ? PyList_New(_numInDevices) : PyList_New(_numInDevices=_midiin.GetNumDevices());
	_midiin.GetListOfDevices(oscList, false);

	for(unsigned int i=0;i<=_numInDevices-1;i++)
	{
		PyList_SetItem(pyList, i, PyString_FromString(oscList[i].data()));
	}

	return Py_BuildValue("O", pyList);
	}
	return Py_BuildValue("i", -1);;
}

// ### select a device
static PyObject *openInDevice(PyObject *self, PyObject *args)
{
	if (!_initIn || _inDevice!=-1)
	{
	OSC_ERR err;
	const char *name;
	int id=0;
	
	if (!PyArg_ParseTuple(args, "s", &name))															  
		return NULL;

	err = _midiin.GetDevID(name, id);
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	err = _midiin.Open(id);
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	_inDevice = id;
	return Py_BuildValue("O", PyString_FromString("opened in midi device"));
	}
	return Py_BuildValue("i", -1);
}

// ### close device
static PyObject *closeInDevice(PyObject *self, PyObject *args)
{
	OSC_ERR err;
	err = _midiin.Stop();
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	err = _midiin.Close();
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	_inDevice=-1;
	_initIn=false;
	return Py_BuildValue("O", PyString_FromString("midi in device closed"));
}

// ### start In device
static PyObject *startInDevice(PyObject *self, PyObject *args)
{
	if(!_initIn)
	{
	OSC_ERR err;
	err = _midiin.Start();
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	_initIn=true;
	return Py_BuildValue("O", PyString_FromString("midi in device started"));
	}
	return Py_BuildValue("i", -1);
}

// ### stop in device
static PyObject *stopInDevice(PyObject *self, PyObject *args)
{
	OSC_ERR err;
	err = _midiin.Stop();
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	_initIn=false;
	return Py_BuildValue("O", PyString_FromString("midi in device stopped"));
}

/*
	##################
		midi out
	##################
*/

// ### get number of midi out devices and return as PyObject
static PyObject *getNumMidiOutDevices(PyObject *self, PyObject *args)
{
	int _numOutDevices = _midiout.GetNumDevices();
	return (!_numOutDevices) ? NULL : Py_BuildValue("i", _numOutDevices);
}

// ### get a list of midi out  devices and return as PyObject
static PyObject *getMidiOutDevice(PyObject *self, PyObject *args)
{
	if (!_initOut)
	{
	OSC_StringArray oscList;
	PyObject *pyList;
	pyList = (_numOutDevices) ? PyList_New(_numOutDevices) : PyList_New(_numOutDevices=_midiout.GetNumDevices());
	_midiout.GetListOfDevices(oscList, false);

	for(unsigned int i=0;i<=_numOutDevices-1;i++)
	{
		PyList_SetItem(pyList, i, PyString_FromString(oscList[i].data()));
	}

	return Py_BuildValue("O", pyList);
	}
	return Py_BuildValue("i", -1);;
}

// ### select a device
static PyObject *openOutDevice(PyObject *self, PyObject *args)
{
	if (!_initOut || _outDevice!=-1)
	{
	OSC_ERR err;
	const char *name;
	int id=0;
	
	if (!PyArg_ParseTuple(args, "s", &name))															  
		return NULL;

	err = _midiout.GetDevID(name, id);
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	err = _midiout.Open(id);
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	_outDevice = id;
	return Py_BuildValue("O", PyString_FromString("opened out midi device"));
	}
	return Py_BuildValue("i", -1);
}

// ### close device
static PyObject *closeOutDevice(PyObject *self, PyObject *args)
{
	OSC_ERR err;
	err = _midiout.Close();
		if(err != OSC_ERR_OK) return Py_BuildValue("O", getError(err));
	_outDevice=-1;
	_initOut=false;
	return Py_BuildValue("O", PyString_FromString("midi out device closed"));
}


// ### send midi msg
static PyObject *sendMidiMsg(PyObject *self, PyObject *args)
{
	int status, data1, data2;
	if(!PyArg_ParseTuple(args, "iii", &status, &data1, &data2))
		return NULL;

	if(status<0) return Py_BuildValue("s", "status byte out of range");
	if(data1<0 || data1>127 || data2<0 || data2>127) return Py_BuildValue("s", "data(1||2) byte out of range");

	_midiout.SendMIDIMessage((OSC_MIDIDataShort)status, (OSC_MIDIDataShort)data1, (OSC_MIDIDataShort)data2);
	return Py_BuildValue("(iii)", status, data1, data2);
}

// --------------------------------------------------------------------------------------------

// ### register our methods e.d.
static PyMethodDef midipy_Methods[] = {
// midiInput ---- #### ¿
	{ "getNumMidiInDevices", getNumMidiInDevices, METH_VARARGS, "int getNumMidiInDevices()" },
	{ "getMidiInDevice", getMidiInDevice, METH_VARARGS, "PyList* getMidiInDevice()" },
	{ "openInDevice", openInDevice, METH_VARARGS, "openInDevice(name)" },
	{ "startInDevice", startInDevice, METH_VARARGS, "startInDevice()" },
	{ "stopInDevice", stopInDevice, METH_VARARGS, "stopInDevice()" },
	{ "closeInDevice", closeInDevice, METH_VARARGS, "closeInDevice()" },
// midiOutput ---- #### ¿
	{ "getNumMidiOutDevices", getNumMidiOutDevices, METH_VARARGS, "int getNumMidiOutDevices()" },
	{ "getMidiOutDevice", getMidiOutDevice, METH_VARARGS, "PyList* getMidiOutDevice()" },	
	{ "openOutDevice", openOutDevice, METH_VARARGS, "openInDevice(name)" },
	{ "closeOutDevice", closeOutDevice, METH_VARARGS, "closeOutDevice()" },
// midiMsg ---- ### ¿
	{ "setChannel", setChannel, METH_VARARGS, "(\'all\'), (\'1...16\'), (\'num\',[1,4,5,7])" },
	{ "returnMidiMsg", returnMidiMsg, METH_VARARGS, "returnMidiMsg()" },
	{ "sendMidiMsg", sendMidiMsg, METH_VARARGS, "sendMidiMsg(status, data1, data2)" },
	{ "flush", flush, METH_VARARGS, "flush()" },
	{NULL, NULL, 0, NULL }
};

// ### init function must be 'extern "C"' cause we compile as c++ code
extern "C" {
	void initmidipy()
	{
		PyObject *mod;
		mod = Py_InitModule("midipy", midipy_Methods);
	}
}
//-------------------------------------------------------------------------------------------

static PyObject* getError(OSC_ERR err)
{
	std::string msg;

	switch(err) 
	{
	case OSC_ERR_FAIL:
		msg = "osc_err_fail";
		break;
	case OSC_ERR_ILLEGAL_DEVICE_ID:
		msg = "illegal device id";
		break;
	case OSC_ERR_ILLEGAL_DEVICE_NAME:
		msg = "illegal device name";
		break;
	case OSC_ERR_MM:
		msg = _midiin.GetLastMMErrorDescription();
		break;
	case OSC_ERR_TIMER_UNAVAILABLE:
		msg = "timer unavailable";
		break;
	default:
		msg = "unkown error";
	}
	return PyString_FromString(msg.data());
}
