/*
Copyright (C) 2009 Grame

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "JackCoreMidiDriver.h"
#include "JackGraphManager.h"
#include "JackServer.h"
#include "JackEngineControl.h"
#include "JackDriverLoader.h"

#include <mach/mach_time.h>
#include <assert.h>
#include <iostream>
#include <sstream>
#include <string>

namespace Jack
{

static MIDITimeStamp MIDIGetCurrentHostTime()
{
	return mach_absolute_time();
}

void JackCoreMidiDriver::ReadProcAux(const MIDIPacketList *pktlist, jack_ringbuffer_t* ringbuffer)
{
    // Write the number of packets
    size_t size = jack_ringbuffer_write(ringbuffer, (char*)&pktlist->numPackets, sizeof(UInt32));
    if (size != sizeof(UInt32)) {
        jack_error("ReadProc : ring buffer is full, skip events...");
        return;
    }    
    
    for (unsigned int i = 0; i < pktlist->numPackets; ++i) {
    
        MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
        
        // TODO : use timestamp
        
        // Check available size first..
        size = jack_ringbuffer_write_space(ringbuffer);
        if (size < (sizeof(UInt16) + packet->length)) {
           jack_error("ReadProc : ring buffer is full, skip events...");
           return;
        }
        // Write length of each packet first
        jack_ringbuffer_write(ringbuffer, (char*)&packet->length, sizeof(UInt16));
        // Write event actual data
        jack_ringbuffer_write(ringbuffer, (char*)packet->data, packet->length);
            
        packet = MIDIPacketNext(packet);
    }
}

void JackCoreMidiDriver::ReadProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon)
{
    jack_ringbuffer_t* ringbuffer = (jack_ringbuffer_t*)connRefCon;
    ReadProcAux(pktlist, ringbuffer);
}

void JackCoreMidiDriver::ReadVirtualProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon)
{
    jack_ringbuffer_t* ringbuffer = (jack_ringbuffer_t*)refCon;
    ReadProcAux(pktlist, ringbuffer);
}

void JackCoreMidiDriver::NotifyProc(const MIDINotification *message, void *refCon)
{
	jack_info("NotifyProc %d", message->messageID);
}

JackCoreMidiDriver::JackCoreMidiDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table)
    : JackMidiDriver(name, alias, engine, table), fMidiClient(NULL), fInputPort(NULL), fOutputPort(NULL), fRealCaptureChannels(0), fRealPlaybackChannels(0)
{}

JackCoreMidiDriver::~JackCoreMidiDriver()
{}

int JackCoreMidiDriver::Open(bool capturing,
                             bool playing,
                             int inchannels,
                             int outchannels,
                             bool monitor,
                             const char* capture_driver_name,
                             const char* playback_driver_name,
                             jack_nframes_t capture_latency,
                             jack_nframes_t playback_latency)
                    {
    OSStatus err;
	CFStringRef coutputStr;
	std::string str;
    
    // Get real input/output number
    fRealCaptureChannels = MIDIGetNumberOfSources();
    fRealPlaybackChannels = MIDIGetNumberOfDestinations();
    
    // Generic JackMidiDriver Open
    if (JackMidiDriver::Open(capturing, playing, inchannels + fRealCaptureChannels, outchannels + fRealPlaybackChannels, monitor, capture_driver_name, playback_driver_name, capture_latency, playback_latency) != 0)
        return -1;
    
    coutputStr = CFStringCreateWithCString(0, "JackMidi", CFStringGetSystemEncoding());
	err = MIDIClientCreate(coutputStr, NotifyProc, this, &fMidiClient);
	CFRelease(coutputStr);
	if (!fMidiClient) {
        jack_error("Cannot create CoreMidi client");
		goto error;
	}
    
   	err = MIDIInputPortCreate(fMidiClient, CFSTR("Input port"), ReadProc, this, &fInputPort);
   	if (!fInputPort) {
		jack_error("Cannot open CoreMidi in port\n");
		goto error;
	}

	err = MIDIOutputPortCreate(fMidiClient, CFSTR("Output port"), &fOutputPort);
	if (!fOutputPort) {
		jack_error("Cannot open CoreMidi out port\n");
		goto error;
	}
  	
    fMidiDestination = new MIDIEndpointRef[inchannels + fRealCaptureChannels];
    assert(fMidiDestination);
    
    // Virtual input
    for (int i = 0; i < inchannels; i++)  {
        std::stringstream num;
        num << i;
        str = "JackMidi" + num.str();
        coutputStr = CFStringCreateWithCString(0, str.c_str(), CFStringGetSystemEncoding());
        err = MIDIDestinationCreate(fMidiClient, coutputStr, ReadVirtualProc, fRingBuffer[i], &fMidiDestination[i]);
        CFRelease(coutputStr);
        if (!fMidiDestination[i]) {
            jack_error("Cannot create CoreMidi destination");
            goto error;
        }
    }
    
    // Real input
    for (int i = 0; i < fRealCaptureChannels; i++)  {
        fMidiDestination[i + inchannels] = MIDIGetSource(i);
        MIDIPortConnectSource(fInputPort, fMidiDestination[i + inchannels], fRingBuffer[i + inchannels]);
    }
	
    fMidiSource = new MIDIEndpointRef[outchannels + fRealPlaybackChannels];
    assert(fMidiSource);

    // Virtual output
    for (int i = 0; i < outchannels; i++)  {
        std::stringstream num;
        num << i;
        str = "JackMidi" + num.str();
        coutputStr = CFStringCreateWithCString(0, str.c_str(), CFStringGetSystemEncoding());
        err = MIDISourceCreate(fMidiClient, coutputStr, &fMidiSource[i]);
        CFRelease(coutputStr);
        if (!fMidiSource[i]) {
            jack_error("Cannot create CoreMidi source");
            goto error;
        }	
    }
    
     // Real output
    for (int i = 0; i < fRealPlaybackChannels; i++)  {
        fMidiSource[i + outchannels] = MIDIGetDestination(i);
    }
 
    return 0;
    
error:
    Close();
	return -1;
}
         
int JackCoreMidiDriver::Close()
{
    if (fInputPort)
		 MIDIPortDispose(fInputPort);
         
    if (fOutputPort) 
		MIDIPortDispose(fOutputPort);
    
    // Only dispose "virtual" endpoints
    for (int i = 0; i < fCaptureChannels - fRealCaptureChannels; i++)  {
        if (fMidiDestination)   
            MIDIEndpointDispose(fMidiDestination[i]);
    }
    delete[] fMidiDestination;
    
    // Only dispose "virtual" endpoints
    for (int i = 0; i < fPlaybackChannels - fRealPlaybackChannels; i++)  {
        if (fMidiSource[i]) 
            MIDIEndpointDispose(fMidiSource[i]);
    }
    delete[] fMidiSource;
     
	if (fMidiClient) 
        MIDIClientDispose(fMidiClient);
        
    return 0;
}

int JackCoreMidiDriver::Attach()
{
    OSStatus err;
    JackPort* port;
    CFStringRef pname;
    jack_port_id_t port_index;
    char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
    char endpoint_name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
    char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
    int i;

    jack_log("JackCoreMidiDriver::Attach fBufferSize = %ld fSampleRate = %ld", fEngineControl->fBufferSize, fEngineControl->fSampleRate);

    for (i = 0; i < fCaptureChannels; i++) {
    
        err = MIDIObjectGetStringProperty(fMidiDestination[i], kMIDIPropertyName, &pname);
        if (err == noErr) {
            CFStringGetCString(pname, endpoint_name, sizeof(endpoint_name), 0);
            CFRelease(pname);
            snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, endpoint_name, i + 1);
        } else {
            snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, fCaptureDriverName, i + 1);
        }
        
        snprintf(name, sizeof(name) - 1, "%s:capture_%d", fClientControl.fName, i + 1);
        if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, CaptureDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) {
            jack_error("driver: cannot register port for %s", name);
            return -1;
        }
        port = fGraphManager->GetPort(port_index);
        port->SetAlias(alias);
        fCapturePortList[i] = port_index;
        jack_log("JackCoreMidiDriver::Attach fCapturePortList[i] port_index = %ld", port_index);
    }

    for (i = 0; i < fPlaybackChannels; i++) {
        
        err = MIDIObjectGetStringProperty(fMidiSource[i], kMIDIPropertyName, &pname);
        if (err == noErr) {
            CFStringGetCString(pname, endpoint_name, sizeof(endpoint_name), 0);
            CFRelease(pname);
            snprintf(alias, sizeof(alias) - 1, "%s:%s:in%d", fAliasName, endpoint_name, i + 1);
        } else {
            snprintf(alias, sizeof(alias) - 1, "%s:%s:in%d", fAliasName, fPlaybackDriverName, i + 1);
        }
        
        snprintf(name, sizeof(name) - 1, "%s:playback_%d", fClientControl.fName, i + 1);
        if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, PlaybackDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) {
            jack_error("driver: cannot register port for %s", name);
            return -1;
        }
        port = fGraphManager->GetPort(port_index);
        port->SetAlias(alias);
        fPlaybackPortList[i] = port_index;
        jack_log("JackCoreMidiDriver::Attach fPlaybackPortList[i] port_index = %ld", port_index);
    }

    return 0;
}
int JackCoreMidiDriver::Read()
{
    for (int chan = 0; chan < fCaptureChannels; chan++)  {
    
        if (fGraphManager->GetConnectionsNum(fCapturePortList[chan]) > 0) {
        
            // Get JACK port
            JackMidiBuffer* midi_buffer = GetInputBuffer(chan);
   
            if (jack_ringbuffer_read_space(fRingBuffer[chan]) == 0) {
                // Reset buffer
                midi_buffer->Reset(midi_buffer->nframes);
            } else {
        
                while (jack_ringbuffer_read_space(fRingBuffer[chan]) > 0) {
            
                    // Read event number
                    int ev_count = 0;
                    jack_ringbuffer_read(fRingBuffer[chan], (char*)&ev_count, sizeof(int));
                
                    for (int j = 0; j < ev_count; j++)  {
                        // Read event length
                        UInt16 event_len;
                        jack_ringbuffer_read(fRingBuffer[chan], (char*)&event_len, sizeof(UInt16));
                        // Read event actual data
                        jack_midi_data_t* dest = midi_buffer->ReserveEvent(0, event_len);
                        jack_ringbuffer_read(fRingBuffer[chan], (char*)dest, event_len);
                    }
                }
            }
            
        } else {
            // Consume ring buffer
            jack_ringbuffer_read_advance(fRingBuffer[chan], jack_ringbuffer_read_space(fRingBuffer[chan]));
        }
    }
    return 0;
}

int JackCoreMidiDriver::Write()
{
    MIDIPacketList* pktlist = (MIDIPacketList*)fMIDIBuffer;
     
    for (int chan = 0; chan < fPlaybackChannels; chan++)  {
    
         if (fGraphManager->GetConnectionsNum(fPlaybackPortList[chan]) > 0) {
        
            MIDIPacket* packet = MIDIPacketListInit(pktlist);
            JackMidiBuffer* midi_buffer = GetOutputBuffer(chan);
            
            // TODO : use timestamp
            
            for (unsigned int j = 0; j < midi_buffer->event_count; j++) {
                JackMidiEvent* ev = &midi_buffer->events[j];
                packet = MIDIPacketListAdd(pktlist, sizeof(fMIDIBuffer), packet, MIDIGetCurrentHostTime(), ev->size, ev->GetData(midi_buffer));
            }
           
            if (packet) {
                if (chan < fPlaybackChannels - fRealPlaybackChannels) {
                    OSStatus err = MIDIReceived(fMidiSource[chan], pktlist);
                    if (err != noErr) 
                        jack_error("MIDIReceived error");
                } else {
                    OSStatus err = MIDISend(fOutputPort, fMidiSource[chan], pktlist);
                    if (err != noErr) 
                        jack_error("MIDISend error");
                }
            }
        }
    }
    
    return 0;
}

} // end of namespace

#ifdef __cplusplus
extern "C"
{
#endif

    SERVER_EXPORT jack_driver_desc_t * driver_get_descriptor() 
    {
        jack_driver_desc_t * desc;
        unsigned int i;

        desc = (jack_driver_desc_t*)calloc (1, sizeof (jack_driver_desc_t));
        strcpy(desc->name, "coremidi");                                     // size MUST be less then JACK_DRIVER_NAME_MAX + 1
        strcpy(desc->desc, "Apple CoreMIDI API based MIDI backend");      // size MUST be less then JACK_DRIVER_PARAM_DESC + 1

        desc->nparams = 2;
        desc->params = (jack_driver_param_desc_t*)calloc (desc->nparams, sizeof (jack_driver_param_desc_t));
        
        i = 0;
        strcpy(desc->params[i].name, "inchannels");
        desc->params[i].character = 'i';
        desc->params[i].type = JackDriverParamInt;
        desc->params[i].value.ui = 0;
        strcpy(desc->params[i].short_desc, "CoreMIDI virtual bus");
        strcpy(desc->params[i].long_desc, desc->params[i].short_desc);

        i++;
        strcpy(desc->params[i].name, "outchannels");
        desc->params[i].character = 'o';
        desc->params[i].type = JackDriverParamInt;
        desc->params[i].value.ui = 0;
        strcpy(desc->params[i].short_desc, "CoreMIDI virtual bus");
        strcpy(desc->params[i].long_desc, desc->params[i].short_desc);

        return desc;
    }

    SERVER_EXPORT Jack::JackDriverClientInterface* driver_initialize(Jack::JackLockedEngine* engine, Jack::JackSynchro* table, const JSList* params) 
    {
        const JSList * node;
        const jack_driver_param_t * param;
        int virtual_in = 0;
        int virtual_out = 0;

        for (node = params; node; node = jack_slist_next (node)) {
            param = (const jack_driver_param_t *) node->data;

            switch (param->character) {

                case 'i':
                    virtual_in = param->value.ui;
                    break;

                case 'o':
                    virtual_out = param->value.ui;
                    break;
                }
        }
      
        Jack::JackDriverClientInterface* driver = new Jack::JackCoreMidiDriver("system_midi", "coremidi", engine, table);
        if (driver->Open(1, 1, virtual_in, virtual_out, false, "in", "out", 0, 0) == 0) {
            return driver;
        } else {
            delete driver;
            return NULL;
        }
    }

#ifdef __cplusplus
}
#endif

