#ifdef _HAVE_PORTMIDI_
/*
 * portmidibackend.c
 * PortMidi backend.
 *
 * for Denemo, a gtk+ frontend to GNU Lilypond
 * Copyright (C) 2011  Dominic Sacré
 *
 * 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 3 of the License, or
 * (at your option) any later version.
 */

#include "portmidibackend.h"
#include "portmidiutil.h"
#include "midi.h"

#include <portmidi.h>
#include <porttime.h>
#include <glib.h>


#define TIMER_RESOLUTION 5
#define INPUT_BUFFER_SIZE 0
#define OUTPUT_BUFFER_SIZE 256


static PmStream *input_stream = NULL;
static PmStream *output_stream = NULL;

static gboolean initialized = FALSE;

static gboolean reset = FALSE;

static double playback_start_time;


static int portmidi_destroy();


static void process_midi(PtTimestamp timestamp, void *user_data) {
  if (!g_atomic_int_get(&initialized)) {
    return;
  }

  if (input_stream) {
    while (Pm_Poll(input_stream) == TRUE) {
      PmEvent event;

      int r = Pm_Read(input_stream, &event, 1);

      // we should only ever get one event.
      // if we get a sysex, just skip it.
      if (r != 1 || (Pm_MessageStatus(event.message) & 0xf0) == 0xf0) {
        continue;
      }

      unsigned char buffer[3] = {
        Pm_MessageStatus(event.message),
        Pm_MessageData1(event.message),
        Pm_MessageData2(event.message)
      };

      input_midi_event(MIDI_BACKEND, 0, buffer);
    }
  }

return;
/*the subsequent code is for output to a MIDI backend, but it does not handle time like the portaudio backend, and will need more work to allow the pause mechanism to work. */
  GTimeVal tv;
  g_get_current_time(&tv);
  double now = (double)tv.tv_sec + tv.tv_usec / 1000000.0;
  double playback_time = now - playback_start_time;

  unsigned char event_data[3];
  size_t event_length;
  double event_time;

  double until_time = playback_time + TIMER_RESOLUTION / 1000.0;

  if (reset && output_stream) {
    int n;
    for (n = 0; n < 16; ++n) {
      long message = Pm_Message(MIDI_CONTROL_CHANGE | n, 123, 0);
      Pm_WriteShort(output_stream, 0, message);
    }
    reset = FALSE;
  }

  while (read_event_from_queue(MIDI_BACKEND, event_data, &event_length, &event_time, until_time)) {
    if (output_stream) {
      long message = Pm_Message(event_data[0], event_data[1], event_data[2]);
      Pm_WriteShort(output_stream, 0, message);
    }
  }

  if (is_playing()) {
    update_playback_time(TIMEBASE_PRIO_MIDI, playback_time);
  }
}


static int portmidi_initialize(DenemoPrefs *config) {
  g_print("initializing PortMidi backend\n");

  PtError pterr = Pt_Start(TIMER_RESOLUTION, &process_midi, NULL);
  if (pterr != ptNoError) {
    g_warning("couldn't start timer\n");
    return -1;
  }

  PmError err;
  int id;
  PmDeviceInfo const *info;

  err = Pm_InitializeWrapper();
  if (err != pmNoError) {
    g_warning("couldn't initialize PortMidi\n");
    portmidi_destroy();
    return -1;
  }

  if (g_strcmp0(config->portmidi_input_device->str, "none") != 0) {
    id = get_portmidi_device_id(config->portmidi_input_device->str, FALSE);

    info = Pm_GetDeviceInfo(id);
    if (info == NULL) {
      g_warning("no input device\n");
      portmidi_destroy();
      return -1;
    }

    g_print("opening input device '%s: %s'\n", info->interf, info->name);

    err = Pm_OpenInput(&input_stream, id, NULL, INPUT_BUFFER_SIZE, NULL, NULL);
    if (err != pmNoError) {
      g_warning("couldn't open input stream\n");
      portmidi_destroy();
      return -1;
    }
  }
  else {
    g_print("input device is disabled\n");

    input_stream = NULL;
  }


  if (g_strcmp0(config->portmidi_output_device->str, "none") != 0) {
    id = get_portmidi_device_id(config->portmidi_output_device->str, TRUE);

    info = Pm_GetDeviceInfo(id);
    if (info == NULL) {
      g_warning("no output device\n");
      portmidi_destroy();
      return -1;
    }

    g_print("opening output device '%s: %s'\n", info->interf, info->name);

    err = Pm_OpenOutput(&output_stream, id, NULL, OUTPUT_BUFFER_SIZE, NULL, NULL, 0);
    if (err != pmNoError) {
      g_warning("couldn't open output stream\n");
      portmidi_destroy();
      return -1;
    }
  }
  else {
    g_print("output device is disabled\n");

    output_stream = NULL;
  }

  g_atomic_int_set(&initialized, TRUE);

  return 0;
}


static int portmidi_destroy() {
  g_print("destroying PortMidi backend\n");

  Pt_Stop();

  g_atomic_int_set(&initialized, FALSE);

  if (input_stream) {
    Pm_Close(input_stream);
  }
  if (output_stream) {
    Pm_Close(output_stream);
  }

  Pm_TerminateWrapper();

  return 0;
}


static int portmidi_reconfigure(DenemoPrefs *config) {
  portmidi_destroy();
  return portmidi_initialize(config);
}


static int portmidi_start_playing() {
  GTimeVal tv;
  g_get_current_time(&tv);
  playback_start_time = (double)tv.tv_sec + tv.tv_usec / 1000000.0;
  playback_start_time -= get_playback_time();
  return 0;
}


static int portmidi_stop_playing() {
  reset = TRUE;
  return 0;
}


static int portmidi_panic() {
  reset = TRUE;
  return 0;
}


backend_t portmidi_backend = {
  portmidi_initialize,
  portmidi_destroy,
  portmidi_reconfigure,
  portmidi_start_playing,
  portmidi_stop_playing,
  portmidi_panic,
};

#endif //_HAVE_PORTMIDI_
