/*
 *   backend.c
 *   Copyright (C) 2022 David García Goñi <dagargo@gmail.com>
 *
 *   This file is part of Elektroid.
 *
 *   Elektroid 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.
 *
 *   Elektroid 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 Elektroid. If not, see <http://www.gnu.org/licenses/>.
 */

#include "backend.h"
#include "local.h"
#include "sample.h"
#include "preferences.h"
#include "utils.h"

// This can be allocated statically as the only function that uses it is synchronized.
static guint8 tmp_buffer[BE_MAX_BUFF_SIZE];

GSList *connectors = NULL;

// When sending a batch of SysEx messages we want the trasfer status to be controlled outside this function.
// This is what the update parameter is for.

gint backend_tx_sysex_int (struct backend *backend,
			   struct sysex_transfer *sysex_transfer,
			   struct controllable *controllable);

void backend_rx_drain_int (struct backend *);
void backend_destroy_int (struct backend *);
gint backend_init_int (struct backend *, const gchar *);
gboolean backend_check_int (struct backend *);
const gchar *backend_name ();
const gchar *backend_version ();
void backend_fill_devices_array (GArray *);

//Identity Request Universal Sysex message
static const guint8 BE_MIDI_IDENTITY_REQUEST[] =
  { 0xf0, 0x7e, 0x7f, 6, 1, 0xf7 };

void
sysex_transfer_set_status (struct sysex_transfer *sysex_transfer,
			   struct controllable *controllable,
			   enum sysex_transfer_status status)
{
  if (controllable)
    {
      g_mutex_lock (&controllable->mutex);
      sysex_transfer->status = status;
      g_mutex_unlock (&controllable->mutex);
    }
  else
    {
      sysex_transfer->status = status;
    }
}

enum sysex_transfer_status
sysex_transfer_get_status (struct sysex_transfer *sysex_transfer,
			   struct controllable *controllable)
{
  enum sysex_transfer_status status;
  g_mutex_lock (&controllable->mutex);
  status = sysex_transfer->status;
  g_mutex_unlock (&controllable->mutex);
  return status;
}

static void
sysex_transfer_init (struct sysex_transfer *sysex_transfer, gint timeout,
		     GByteArray *data, gboolean batch)
{
  sysex_transfer->status = SYSEX_TRANSFER_STATUS_INIT;
  sysex_transfer->timeout = timeout;
  sysex_transfer->time = 0;
  sysex_transfer->batch = batch;
  sysex_transfer->raw = data;
  sysex_transfer->err = 0;
}

void
sysex_transfer_init_tx (struct sysex_transfer *sysex_transfer,
			GByteArray *data)
{
  sysex_transfer_init (sysex_transfer, -1, data, FALSE);
}

void
sysex_transfer_init_rx (struct sysex_transfer *sysex_transfer, gint timeout,
			gboolean batch)
{
  sysex_transfer_init (sysex_transfer, timeout, NULL, batch);
}

void
sysex_transfer_init_tx_and_rx (struct sysex_transfer *sysex_transfer,
			       gint timeout, GByteArray *data)
{
  sysex_transfer_init (sysex_transfer, timeout, data, FALSE);
}

GByteArray *
sysex_transfer_steal (struct sysex_transfer *sysex_transfer)
{
  GByteArray *raw = sysex_transfer->raw;
  sysex_transfer->raw = NULL;
  return raw;
}

void
sysex_transfer_clear (struct sysex_transfer *sysex_transfer)
{
  GByteArray *raw = sysex_transfer_steal (sysex_transfer);
  if (raw)
    {
      g_byte_array_free (raw, TRUE);
    }
}

gdouble
backend_get_storage_stats_percent (struct backend_storage_stats *statfs)
{
  return (statfs->bsize - statfs->bfree) * 100.0 / statfs->bsize;
}

static gint
backend_get_fs_operations_id_comparator (gconstpointer a, gconstpointer b)
{
  const struct fs_operations *ops = a;
  return ops->id != *((guint32 *) b);
}

const struct fs_operations *
backend_get_fs_operations_by_id (struct backend *backend, guint32 id)
{
  GSList *e = g_slist_find_custom (backend->fs_ops, &id,
				   backend_get_fs_operations_id_comparator);
  return e ? e->data : NULL;
}

static gint
backend_get_fs_operations_name_comparator (gconstpointer a, gconstpointer b)
{
  const struct fs_operations *ops = a;
  return strcmp (ops->name, (gchar *) b);
}

const struct fs_operations *
backend_get_fs_operations_by_name (struct backend *backend, const gchar *name)
{
  GSList *e = g_slist_find_custom (backend->fs_ops, name,
				   backend_get_fs_operations_name_comparator);
  return e ? e->data : NULL;
}

void
backend_midi_handshake (struct backend *backend)
{
  GByteArray *tx_msg;
  GByteArray *rx_msg;
  gint offset;

  backend->name[0] = 0;
  backend->version[0] = 0;
  backend->description[0] = 0;
  backend->fs_ops = NULL;
  backend->upgrade_os = NULL;
  backend->get_storage_stats = NULL;
  memset (&backend->midi_info, 0, sizeof (struct backend_midi_info));

  tx_msg = g_byte_array_sized_new (sizeof (BE_MIDI_IDENTITY_REQUEST));
  g_byte_array_append (tx_msg, (guchar *) BE_MIDI_IDENTITY_REQUEST,
		       sizeof (BE_MIDI_IDENTITY_REQUEST));
  rx_msg = backend_tx_and_rx_sysex (backend, tx_msg,
				    BE_SYSEX_TIMEOUT_GUESS_MS);
  if (!rx_msg)
    {
      debug_print (1, "No MIDI identity reply");
      return;
    }

  if (rx_msg->data[4] == 2)
    {
      if (rx_msg->len == 15 || rx_msg->len == 17)
	{
	  offset = rx_msg->len - 15;
	  memset (backend->midi_info.company, 0, BE_COMPANY_LEN);
	  memcpy (backend->midi_info.company, &rx_msg->data[5],
		  rx_msg->len == 15 ? 1 : BE_COMPANY_LEN);
	  memcpy (backend->midi_info.family, &rx_msg->data[6 + offset],
		  BE_FAMILY_LEN);
	  memcpy (backend->midi_info.model, &rx_msg->data[8 + offset],
		  BE_MODEL_LEN);
	  memcpy (backend->midi_info.version, &rx_msg->data[10 + offset],
		  BE_VERSION_LEN);

	  snprintf (backend->name, LABEL_MAX,
		    "%02x-%02x-%02x %02x-%02x %02x-%02x",
		    backend->midi_info.company[0],
		    backend->midi_info.company[1],
		    backend->midi_info.company[2],
		    backend->midi_info.family[0],
		    backend->midi_info.family[1],
		    backend->midi_info.model[0], backend->midi_info.model[1]);
	  snprintf (backend->version, LABEL_MAX, "%d.%d.%d.%d",
		    backend->midi_info.version[0],
		    backend->midi_info.version[1],
		    backend->midi_info.version[2],
		    backend->midi_info.version[3]);
	  debug_print (1, "Detected device: %s %s", backend->name,
		       backend->version);
	}
      else
	{
	  debug_print (1, "Illegal MIDI identity reply length");
	}
    }
  else
    {
      debug_print (1, "Illegal SUB-ID2");
    }

  free_msg (rx_msg);

  g_usleep (BE_REST_TIME_US);
}

//Not synchronized

gint
backend_tx_sysex (struct backend *backend, struct sysex_transfer *transfer,
		  struct controllable *controllable)
{
  return backend_tx_sysex_int (backend, transfer, controllable);
}

//Synchronized

gint
backend_tx (struct backend *backend, GByteArray *tx_msg)
{
  struct sysex_transfer transfer;

  sysex_transfer_init_tx (&transfer, tx_msg);

  g_mutex_lock (&backend->mutex);
  backend_tx_sysex (backend, &transfer, NULL);
  g_mutex_unlock (&backend->mutex);

  sysex_transfer_clear (&transfer);

  return transfer.err;
}

GByteArray *
backend_rx (struct backend *backend, gint timeout,
	    struct controllable *controllable)
{
  struct sysex_transfer transfer;

  sysex_transfer_init_rx (&transfer, timeout, FALSE);

  g_mutex_lock (&backend->mutex);
  backend_rx_sysex (backend, &transfer, controllable);
  g_mutex_unlock (&backend->mutex);

  return sysex_transfer_steal (&transfer);
}

//Synchronized

gint
backend_tx_and_rx_sysex_transfer (struct backend *backend,
				  struct sysex_transfer *transfer,
				  struct controllable *controllable)
{
  g_mutex_lock (&backend->mutex);

  if (transfer->raw)
    {
      backend_tx_sysex (backend, transfer, controllable);
      sysex_transfer_clear (transfer);
    }

  if (!transfer->err)
    {
      backend_rx_sysex (backend, transfer, controllable);
    }

  g_mutex_unlock (&backend->mutex);

  return transfer->err;
}

//Synchronized
//A timeout of 0 means infinity; a timeout of -1 means the default timeout.

GByteArray *
backend_tx_and_rx_sysex (struct backend *backend, GByteArray *tx_msg,
			 gint timeout)
{
  struct sysex_transfer transfer;
  gint t = timeout == -1 ? BE_SYSEX_TIMEOUT_MS : timeout;
  sysex_transfer_init_tx_and_rx (&transfer, t, tx_msg);
  backend_tx_and_rx_sysex_transfer (backend, &transfer, NULL);
  return sysex_transfer_steal (&transfer);
}

void
backend_destroy_data (struct backend *backend)
{
  debug_print (1, "Destroying backend data...");
  g_free (backend->data);
  backend->data = NULL;
}

gint
backend_program_change (struct backend *backend, guint8 channel,
			guint8 program)
{
  gssize size;
  guint8 msg[2];

  msg[0] = 0xc0 | (channel & 0xf);
  msg[1] = program & 0x7f;

  debug_print (1, "Sending MIDI program %d...", msg[1]);

  if ((size = backend_tx_raw (backend, msg, 2)) < 0)
    {
      return size;
    }
  return 0;
}

gint
backend_send_3_byte_message (struct backend *backend, guint8 msg_type,
			     guint8 channel, guint8 d1, guint8 d2)
{
  gssize size;
  guint8 msg[3];

  msg[0] = msg_type | (channel & 0xf);
  msg[1] = d1 & 0x7f;
  msg[2] = d2 & 0x7f;

  debug_print (1, "Sending MIDI message: status %08x; data %d, %d...",
	       msg[0], msg[1], msg[2]);

  if ((size = backend_tx_raw (backend, msg, 3)) < 0)
    {
      return size;
    }
  return 0;
}

gint
backend_send_controller (struct backend *backend, guint8 channel,
			 guint8 controller, guint8 value)
{
  return backend_send_3_byte_message (backend, 0xb0, channel, controller,
				      value);
}

gint
backend_send_note_on (struct backend *backend, guint8 channel,
		      guint8 note, guint8 velocity)
{
  return backend_send_3_byte_message (backend, 0x90, channel, note, velocity);
}

gint
backend_send_note_off (struct backend *backend, guint8 channel,
		       guint8 note, guint8 velocity)
{
  return backend_send_3_byte_message (backend, 0x80, channel, note, velocity);
}


gint
backend_send_rpn (struct backend *backend, guint8 channel,
		  guint8 controller_msb, guint8 controller_lsb,
		  guint8 value_msb, guint8 value_lsb)
{
  gint err = backend_send_controller (backend, channel, 101, controller_msb);
  err |= backend_send_controller (backend, channel, 100, controller_lsb);
  err |= backend_send_controller (backend, channel, 6, value_msb);
  err |= backend_send_controller (backend, channel, 38, value_lsb);
  return err;
}

static gint
backend_init_midi (struct backend *backend, const gchar *id)
{
  debug_print (1, "Initializing backend (%s) to '%s'...",
	       backend_name (), id);
  backend->type = BE_TYPE_MIDI;
  gint err = backend_init_int (backend, id);
  if (!err)
    {
      g_mutex_lock (&backend->mutex);
      backend_rx_drain (backend);
      g_mutex_unlock (&backend->mutex);
    }

  if (preferences_get_boolean (PREF_KEY_STOP_DEVICE_WHEN_CONNECTING))
    {
      debug_print (1, "Stopping device...");
      if (backend_tx_raw (backend, (guint8 *) "\xfc", 1) < 0)
	{
	  error_print ("Error while stopping device");
	}
      g_usleep (BE_REST_TIME_US);
    }

  return err;
}

void
backend_destroy (struct backend *backend)
{
  debug_print (1, "Destroying backend...");

  if (backend->destroy_data)
    {
      backend->destroy_data (backend);
    }

  if (backend->type == BE_TYPE_MIDI)
    {
      backend_destroy_int (backend);
    }

  backend->upgrade_os = NULL;
  backend->get_storage_stats = NULL;
  backend->destroy_data = NULL;
  backend->type = BE_TYPE_NONE;
  g_slist_free (backend->fs_ops);
}

gboolean
backend_check (struct backend *backend)
{
  switch (backend->type)
    {
    case BE_TYPE_MIDI:
      return backend_check_int (backend);
    case BE_TYPE_SYSTEM:
    case BE_TYPE_NO_MIDI:
      return TRUE;
    default:
      return FALSE;
    }
}

static guint8 *
backend_get_sysex_start (guint8 *tmp, gssize *size)
{
  guint i, total = *size;
  guint8 *msg_start;

  //Everything is skipped until a 0xf0 is found. This includes every RT MIDI message.
  msg_start = tmp;
  for (i = 0; i < total; i++, msg_start++, (*size)--)
    {
      if (*msg_start == 0xf0)
	{
	  break;
	}
    }

  if (debug_level >= 4)
    {
      gchar *text = debug_get_hex_data (debug_level, tmp, i);
      debug_print (4, "Skipping non SysEx data (%d): %s", i, text);
      g_free (text);
    }

  return msg_start;
}

static gssize
backend_rx_raw_loop (struct backend *backend, struct sysex_transfer *transfer,
		     struct controllable *controllable)
{
  gchar *text;
  gssize rx_len;
  guint8 *msg_start;

  if (!backend->inputp)
    {
      error_print ("Input port is NULL");
      return -ENOTCONN;
    }

  debug_print (4, "Reading data...");

  while (1)
    {
      if (!CONTROLLABLE_IS_NULL_OR_ACTIVE (controllable))
	{
	  return -ECANCELED;
	}

      debug_print (6, "Checking timeout (%d ms, %d ms, %s mode)...",
		   transfer->time, transfer->timeout,
		   transfer->batch ? "batch" : "single");
      if (((transfer->batch &&
	    transfer->status == SYSEX_TRANSFER_STATUS_RECEIVING)
	   || !transfer->batch) && transfer->timeout > -1
	  && transfer->time >= transfer->timeout)
	{
	  debug_print (1, "Timeout (%d)", transfer->timeout);
	  gchar *text =
	    debug_get_hex_data (debug_level, backend->buffer->data,
				backend->buffer->len);
	  debug_print (4, "Internal buffer data (%u): %s",
		       backend->buffer->len, text);
	  g_free (text);
	  return -ETIMEDOUT;
	}

      rx_len = backend_rx_raw (backend, tmp_buffer, BE_MAX_BUFF_SIZE);
      if (rx_len < 0)
	{
	  return rx_len;
	}
      if (rx_len == 0)
	{
	  if ((transfer->batch &&
	       transfer->status == SYSEX_TRANSFER_STATUS_RECEIVING)
	      || !transfer->batch)
	    {
	      transfer->time += BE_POLL_TIMEOUT_MS;
	    }
	  continue;
	}

      if (debug_level >= 4)
	{
	  gchar *text = debug_get_hex_data (debug_level, tmp_buffer, rx_len);
	  debug_print (4, "Read data (%zd): %s", rx_len, text);
	  g_free (text);
	}

      if (backend->buffer->len)
	{
	  msg_start = tmp_buffer;
	}
      else
	{
	  msg_start = backend_get_sysex_start (tmp_buffer, &rx_len);
	}

      if (rx_len == 0)
	{
	  transfer->time += BE_POLL_TIMEOUT_MS;
	  continue;
	}

      if (rx_len > 0)
	{
	  guint8 *v = msg_start;
	  for (guint i = 0; i < rx_len; i++, v++)
	    {
	      if (*v == 0xf0 || *v == 0xf7 || (*v & 0xf0) != 0xf0)
		{
		  g_byte_array_append (backend->buffer, v, 1);
		}
	    }
	  if (debug_level >= 3)
	    {
	      text = debug_get_hex_data (debug_level, msg_start, rx_len);
	      debug_print (3, "Queued data (%zu): %s", rx_len, text);
	      g_free (text);
	    }
	  break;
	}
    }


  return backend->buffer->len;
}

//Access to this function must be synchronized.

gint
backend_rx_sysex (struct backend *backend, struct sysex_transfer *transfer,
		  struct controllable *controllable)
{
  gint next_check, len, i;
  guint8 *b;
  gssize rx_len;

  transfer->err = 0;
  transfer->time = 0;
  transfer->raw = g_byte_array_sized_new (BE_INT_BUFF_SIZE);
  sysex_transfer_set_status (transfer, controllable,
			     SYSEX_TRANSFER_STATUS_WAITING);

  next_check = 0;
  while (1)
    {
      if (backend->buffer->len == next_check)
	{
	  debug_print (4, "Reading from MIDI device...");
	  if (transfer->batch)
	    {
	      transfer->time = 0;
	    }
	  rx_len = backend_rx_raw_loop (backend, transfer, controllable);

	  if (rx_len == -ENODATA || rx_len == -ETIMEDOUT
	      || rx_len == -ECANCELED)
	    {
	      if (transfer->batch)
		{
		  break;
		}
	      else
		{
		  transfer->err = rx_len;
		  goto end;
		}
	    }
	  else if (rx_len < 0)
	    {
	      transfer->err = -EIO;
	      goto end;
	    }
	}
      else
	{
	  debug_print (4, "Reading from internal buffer...");
	}

      sysex_transfer_set_status (transfer, controllable,
				 SYSEX_TRANSFER_STATUS_RECEIVING);
      len = -1;
      b = backend->buffer->data + next_check;
      for (; next_check < backend->buffer->len; next_check++, b++)
	{
	  if (*b == 0xf7)
	    {
	      next_check++;
	      len = next_check;
	      break;
	    }
	}

      //We filter out any SysEx message not suitable.

      if (len > 0)
	{
	  //Filter out everything until an 0xf0 is found.
	  b = backend->buffer->data;
	  for (i = 0; i < len && *b != 0xf0; i++, b++);
	  if (i > 0 && debug_level >= 4)
	    {
	      gchar *text = debug_get_hex_data (debug_level,
						backend->buffer->data, i);
	      debug_print (4, "Skipping non SysEx data in buffer (%d): %s",
			   i, text);
	      g_free (text);
	    }
	  debug_print (3, "Copying %d bytes...", len - i);
	  g_byte_array_append (transfer->raw, b, len - i);

	  g_byte_array_remove_range (backend->buffer, 0, len);

	  transfer->err = 0;
	  next_check = 0;

	  //Filter empty message
	  if (transfer->raw->len == 2
	      && !memcmp (transfer->raw->data, "\xf0\xf7", 2))
	    {
	      debug_print (4, "Removing empty message...");
	      g_byte_array_remove_range (transfer->raw, 0, 2);
	      continue;
	    }

	  if (debug_level >= 4)
	    {
	      gchar *text =
		debug_get_hex_data (debug_level, transfer->raw->data,
				    transfer->raw->len);
	      debug_print (4, "Queued data (%d): %s",
			   transfer->raw->len, text);
	      g_free (text);
	    }
	}
      else
	{
	  debug_print (4, "No message in the queue. Continuing...");
	}

      if (transfer->raw->len && !transfer->batch)
	{
	  break;
	}
    }

end:
  if (!transfer->raw->len)
    {
      transfer->err = -ETIMEDOUT;
    }
  if (transfer->err)
    {
      free_msg (transfer->raw);
      transfer->raw = NULL;
    }
  else
    {
      if (debug_level >= 2)
	{
	  gchar *text = debug_get_hex_data (debug_level, transfer->raw->data,
					    transfer->raw->len);
	  debug_print (2, "Raw message received (%d): %s",
		       transfer->raw->len, text);
	  g_free (text);
	}

    }

  sysex_transfer_set_status (transfer, controllable,
			     SYSEX_TRANSFER_STATUS_FINISHED);

  return transfer->err;
}

//Access to this function must be synchronized.

void
backend_rx_drain (struct backend *backend)
{
  struct sysex_transfer transfer;

  sysex_transfer_init_rx (&transfer, 1000, FALSE);

  debug_print (2, "Draining buffers...");
  backend->buffer->len = 0;
  backend_rx_drain_int (backend);
  while (!backend_rx_sysex (backend, &transfer, NULL))
    {
      sysex_transfer_clear (&transfer);
    }
}

enum path_type
backend_get_path_type (struct backend *backend)
{
  return (!backend || backend->type == BE_TYPE_SYSTEM) ? PATH_SYSTEM :
    PATH_INTERNAL;
}

static gint
be_device_compare (gconstpointer a, gconstpointer b)
{
  const struct backend_device *da = a;
  const struct backend_device *db = b;

  if (da->type == db->type)
    {
      return strcmp (da->name, db->name);
    }
  else
    {
      return da->type < db->type ? -1 : da->type > db->type ? +1 : 0;
    }
}

GArray *
backend_get_devices ()
{
  guint id;
  GSList *c;
  GArray *devices;
  struct backend_device *backend_device;

  devices = g_array_new (FALSE, FALSE, sizeof (struct backend_device));

  //System
  backend_device = g_malloc (sizeof (struct backend_device));
  backend_device->type = BE_TYPE_SYSTEM;
  snprintf (backend_device->id, LABEL_MAX, "%s", BE_SYSTEM_ID);
  snprintf (backend_device->name, LABEL_MAX, "%s", g_get_host_name ());
  g_array_append_vals (devices, backend_device, 1);

  //Actually present devices
  backend_fill_devices_array (devices);

  //Devices that need a manual handshake. These may or may not be a present device.
  id = 0;
  c = connectors;
  while (c)
    {
      struct connector *connector = c->data;
      if (connector->type == CONNECTOR_TYPE_NO_MIDI)
	{
	  backend_device = g_malloc (sizeof (struct backend_device));
	  backend_device->type = BE_TYPE_NO_MIDI;
	  snprintf (backend_device->id, LABEL_MAX, "%s", connector->name);
	  snprintf (backend_device->name, LABEL_MAX, "%s",
		    connector->device_name);
	  g_array_append_vals (devices, backend_device, 1);
	  id++;
	}
      c = c->next;
    }

  g_array_sort (devices, be_device_compare);

  return devices;
}

// A handshake function might return these values:
// 0, the device matches the connector.
// -ENODEV, the device does not match the connector but we can continue with the next connector.
// Other negative errors are allowed but we will not continue with the remaining connectors.

gint
backend_init_connector (struct backend *backend,
			struct backend_device *device,
			const gchar *conn_name,
			struct controllable *controllable)
{
  gint err;
  GSList *list = NULL, *iterator;

  // First, check the non MIDI connector.
  if (device->type == BE_TYPE_SYSTEM || device->type == BE_TYPE_NO_MIDI)
    {
      for (iterator = connectors; iterator; iterator = iterator->next)
	{
	  const struct connector *c = iterator->data;

	  if (device->type == BE_TYPE_SYSTEM &&
	      c->type == CONNECTOR_TYPE_SYSTEM)
	    {
	      backend->conn_name = c->name;
	      backend->type = BE_TYPE_SYSTEM;
	      return c->handshake (backend);
	    }
	  else if (device->type == BE_TYPE_NO_MIDI &&
		   c->type == CONNECTOR_TYPE_NO_MIDI)
	    {
	      if (!strcmp (c->name, device->id))
		{
		  if (conn_name && strcmp (c->name, conn_name))
		    {
		      error_print ("Unexpected connector");
		      return -ENODEV;
		    }

		  backend->conn_name = c->name;
		  backend->type = BE_TYPE_NO_MIDI;
		  return c->handshake (backend);
		}
	    }
	}
    }

  // If the connector was not found. Let's try with the MIDI ones.
  err = backend_init_midi (backend, device->id);
  if (err)
    {
      return err;
    }

  // For the MIDI connectors, we sort them by regex as containing a regex allows for a quicker name check.
  // If the regex check fails, it needs to be added in the end.
  for (iterator = connectors; iterator; iterator = iterator->next)
    {
      struct connector *c = iterator->data;

      if (c->type != CONNECTOR_TYPE_MIDI)
	{
	  continue;
	}

      if (c->regex)
	{
	  GRegex *regex = g_regex_new (c->regex, G_REGEX_CASELESS,
				       0, NULL);
	  if (g_regex_match (regex, device->name, 0, NULL))
	    {
	      debug_print (1, "Connector %s matches the device", c->name);
	      list = g_slist_prepend (list, (void *) c);
	    }
	  else
	    {
	      list = g_slist_append (list, (void *) c);
	    }
	  g_regex_unref (regex);
	}
      else
	{
	  list = g_slist_append (list, (void *) c);
	}
    }

  debug_print (2, "Handshaking priority for the device \"%s\":",
	       device->name);
  for (iterator = list; iterator; iterator = iterator->next)
    {
      struct connector *c = iterator->data;
      debug_print (2, "%s", c->name);
    }

  if (!CONTROLLABLE_IS_NULL_OR_ACTIVE (controllable))
    {
      err = -ECANCELED;
      goto end;
    }

  if (!conn_name)
    {
      backend_midi_handshake (backend);
    }

  err = -ENODEV;
  for (iterator = list; iterator; iterator = iterator->next)
    {
      const struct connector *c = iterator->data;

      if (!CONTROLLABLE_IS_NULL_OR_ACTIVE (controllable))
	{
	  err = -ECANCELED;
	  goto end;
	}

      debug_print (1, "Testing %s connector (type %d)...", c->name, c->type);

      if (conn_name)
	{
	  if (!strcmp (conn_name, c->name))
	    {
	      backend_midi_handshake (backend);
	      err = c->handshake (backend);
	      if (!err)
		{
		  debug_print (1, "Using %s connector...", c->name);
		  backend->conn_name = c->name;
		}
	      goto end;
	    }
	}
      else
	{
	  err = c->handshake (backend);
	  if (err && err != -ENODEV)
	    {
	      goto end;
	    }

	  if (!err)
	    {
	      debug_print (1, "Using %s connector...", c->name);
	      backend->conn_name = c->name;
	      goto end;
	    }
	}
    }

  error_print ("No device recognized");

end:
  g_slist_free (list);
  if (err)
    {
      backend_destroy (backend);
    }
  return err;
}
