/*
 * Purpose: USB MIDI streaming interface support
 */
/*
 * This file is part of Open Sound System
 *
 * Copyright (C) 4Front Technologies 1996-2007.
 *
 * This software is released under CDDL 1.0 source license for operating systems
 * that have their full source code available under the BSD or CDDL licenses.
 *
 * See the COPYING file included in the main directory of this source
 * distribution. Please contact sales@opensound.com for further info.
 */

/*
 * NOTE! This is not the development version of this file. Please download the
 * sources of the latest Open Sound System version from 
 * http://developer.opensound.com/sources if you plan to make any changes
 * to the software.
 */

#include "oss_config.h"
#include "ossusb.h"

#define RECBUF_SIZE	32
#define PLAYBUF_SIZE	32

/*
 * MS Class-Specific Interface Descriptor Subtypes
 */
#define MS_HEADER		0x01
#define MIDI_IN_JACK		0x02
#define MIDI_OUT_JACK		0x03
#define ELEMENT			0x04

/* Jack types */
#define JT_EMBEDDED		0x01
#define JT_EXTERNAL		0x02

static int usb_midi_start_input (ossusb_devc * devc, ossusb_midic * midic);

static void
record_callback (udi_usb_request_t * request, void *arg)
{
  ossusb_midic *midic = arg;
  ossusb_devc *devc = midic->devc;
  int i, l;

  l = udi_usb_request_actlen (request);
  if (l == 0)
    goto restart;

  for (i = 0; i < l; i++)
    cmn_err (CE_CONT, "%02x ", devc->recbuf[i]);
  cmn_err (CE_CONT, "\n");

restart:
  usb_midi_start_input (devc, midic);
}

static int
usb_midi_start_input (ossusb_devc * devc, ossusb_midic * midic)
{
  oss_native_word flags;
  int err = 0;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  if ((err =
       udi_usb_submit_request (devc->input_pipe, record_callback, midic,
			       midic->in_endpoint_handle,
			       UDI_USBXFER_BULK_READ, devc->recbuf,
			       RECBUF_SIZE)) < 0)
    {
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return err;
}

static int usb_midi_start_output (ossusb_devc * devc, ossusb_midic * midic);

static void
play_callback (udi_usb_request_t * request, void *arg)
{
  ossusb_midic *midic = arg;
  ossusb_devc *devc = midic->devc;

  devc->output_busy = 0;
  usb_midi_start_output (devc, midic);
}

static int
usb_midi_start_output (ossusb_devc * devc, ossusb_midic * midic)
{
  oss_native_word flags;
  int err = 0, l = 0;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);

  l = devc->q_nbytes;
  if (l > PLAYBUF_SIZE)
    l = PLAYBUF_SIZE;

  if (l == 0 || devc->output_busy)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return 0;
    }

  memcpy (devc->playbuf, devc->queue, l);

  if ((err =
       udi_usb_submit_request (devc->output_pipe, play_callback, midic,
			       midic->out_endpoint_handle,
			       UDI_USBXFER_BULK_WRITE, devc->playbuf, l)) < 0)
    {
    }
  else
    devc->output_busy = 1;

  /* Remove the vbytes from the queue */
  if (l >= devc->q_nbytes)
    devc->q_nbytes = 0;
  else
    {
      int n = devc->q_nbytes - l;
      memcpy (devc->queue, devc->queue + l, n);
      devc->q_nbytes -= l;
    }
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  return err;
}

static void
usb_midi_close (int dev, int mode)
{
  oss_native_word flags;
  ossusb_midic *midic;
  ossusb_devc *devc;

  midic = midi_devs[dev]->devc;
  devc = midic->devc;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  midic->open_mode = 0;
  midic->midi_input_intr = NULL;
  midic->midi_output_intr = NULL;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if (mode & OPEN_READ)
    {
      if (devc->input_pipe != NULL)
	udi_usb_cancel_request (devc->input_pipe);
      devc->input_pipe = NULL;

      if (devc->recbuf != NULL)
	CONTIG_FREE (devc->osdev, devc->recbuf, RECBUF_SIZE);
      devc->recbuf = NULL;
      if (devc->playbuf != NULL)
	CONTIG_FREE (devc->osdev, devc->playbuf, PLAYBUF_SIZE);
      devc->playbuf = NULL;
      udi_close_endpoint (midic->in_endpoint_handle);
    }

  if (mode & OPEN_WRITE)
    {
      udi_usb_cancel_request (devc->output_pipe);
      udi_close_endpoint (midic->out_endpoint_handle);
    }
}

static int
usb_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte,
	       oss_midi_inputbuf_t inputbuf, oss_midi_outputintr_t outputintr)
{
  oss_native_word flags, phaddr;
  ossusb_midic *midic;
  ossusb_devc *devc;

  midic = midi_devs[dev]->devc;
  devc = midic->devc;

  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if (midic->open_mode)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return -EBUSY;
    }

  midic->open_mode = mode;
  midic->midi_input_intr = inputbyte;
  midic->midi_output_intr = outputintr;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  if (mode & OPEN_WRITE)
    {
      if ((midic->out_endpoint_handle =
	   udi_open_endpoint (midic->usbdev,
			      midic->out_endpoint_desc)) == NULL)
	{
	  usb_midi_close (dev, mode);
	  cmn_err (CE_WARN, "Cannot open midi output pipe\n");
	  return -ENOMEM;
	}

      if ((devc->output_pipe =
	   udi_usb_alloc_request (midic->usbdev, midic->out_endpoint_handle,
				  1, UDI_USBXFER_BULK_WRITE)) == NULL)
	{
	  cmn_err (CE_CONT, "oss usbmidi: Failed to allocate output pipe\n");
	}

      devc->playbuf =
	CONTIG_MALLOC (devc->osdev, PLAYBUF_SIZE, MEMLIMIT_32BITS, &phaddr);

      devc->output_busy = 0;
      devc->q_nbytes = 0;
    }

  if (mode & OPEN_READ)
    {
      int err;

      if ((midic->in_endpoint_handle =
	   udi_open_endpoint (midic->usbdev,
			      midic->in_endpoint_desc)) == NULL)
	{
	  usb_midi_close (dev, mode);
	  cmn_err (CE_WARN, "Cannot open midi input pipe\n");
	  return -ENOMEM;
	}
      if ((devc->input_pipe =
	   udi_usb_alloc_request (midic->usbdev, midic->in_endpoint_handle, 1,
				  UDI_USBXFER_BULK_READ)) == NULL)
	{
	  cmn_err (CE_CONT, "oss usbmidi: Failed to allocate input pipe\n");
	}
      devc->recbuf =
	CONTIG_MALLOC (devc->osdev, RECBUF_SIZE, MEMLIMIT_32BITS, &phaddr);

      if ((err = usb_midi_start_input (devc, midic)) < 0)
	{
	  cmn_err (CE_CONT, "oss usbmidi: Input error %d\n", err);
	  usb_midi_close (dev, mode);
	  return -EIO;
	}
    }

  return 0;
}

static int
usb_midi_out (int dev, unsigned char data)
{
  ossusb_midic *midic = midi_devs[dev]->devc;
  ossusb_devc *devc;
  oss_native_word flags;
  unsigned char *buf;

  devc = midic->devc;

  cmn_err (CE_CONT, "Send %02x\n", data);
  MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
  if ((devc->q_nbytes + 4) >= Q_MAX)
    {
      MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
      return 0;
    }

  buf = devc->queue + devc->q_nbytes;

  memset (buf, 0, 4);
  buf[0] = 0x0f;
  buf[1] = data;

  devc->q_nbytes += 4;
  MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);

  usb_midi_start_output (devc, midic);
  return 1;
}

static int
usb_midi_ioctl (int dev, unsigned cmd, ioctl_arg arg)
{
  return -EINVAL;
}

static int
usb_midi_buffer_status (int dev)
{
  return 0;
}

static midi_driver_t usb_midi_driver = {
  usb_midi_open,
  usb_midi_close,
  usb_midi_ioctl,
  usb_midi_out,
};

ossusb_devc *
ossusb_init_midistream (ossusb_devc * devc, udi_usb_devc * usbdev, int inum,
			int reinit)
{
  int old, i, n;
  int p, l;
  char tmp[64];
  unsigned char *in_endpoints[16], *out_endpoints[16];
  void *in_endpoint_desc = NULL, *out_endpoint_desc = NULL;
  int num_in_endpoints = 0, num_out_endpoints = 0;
  int cin = 0, cout = 0;
  ossusb_midic *midic;
  unsigned char *desc, *d;
  int desc_len;

  old = 0;

  for (i = 0; i < 32; i++)
    if ((desc = udi_usbdev_get_endpoint (usbdev, 0, i, &desc_len)) != NULL)
      {
	unsigned char *ep;

	if (desc_len > 100)
	  desc_len = 100;

	cmn_err (CE_CONT, "Endpoint %d (%d)", i, desc_len);
	ossusb_dump_desc (desc, desc_len);
	ep = desc;

	if (desc[2] & 0x80)
	  {
	    in_endpoints[num_in_endpoints++] = ep;
	  }
	else
	  {
	    out_endpoints[num_out_endpoints++] = ep;
	  }
      }

  cmn_err (CE_CONT, "%d input endpoints: ", num_in_endpoints);
  for (i = 0; i < num_in_endpoints; i++)
    cmn_err (CE_CONT, "%02x ", in_endpoints[i][2]);
  cmn_err (CE_CONT, "\n");
  cmn_err (CE_CONT, "%d input endpoints: ", num_out_endpoints);
  for (i = 0; i < num_out_endpoints; i++)
    cmn_err (CE_CONT, "%02x ", out_endpoints[i][2]);
  cmn_err (CE_CONT, "\n");

  cmn_err (CE_CONT, "USB MIDI stream\n");
  desc = udi_usbdev_get_altsetting (usbdev, 0, &desc_len);
  if (desc == NULL || desc_len < 3)
    {
      cmn_err (CE_CONT, "oss usbmidi: bad altsetting\n");
      return NULL;
    }
  // ossusb_dump_desc (desc, desc_len);
  p = 0;
  while (p < desc_len)
    {
      d = desc + p;

      l = *d;
      //if (usb_trace > 1)
      {
	cmn_err (CE_CONT, "MIDI streaming desc: ");
	for (i = 0; i < l; i++)
	  cmn_err (CE_CONT, "%02x ", d[i]);
	cmn_err (CE_CONT, "\n");
      }

      if (d[1] != CS_INTERFACE)
	{
	  cmn_err (CE_CONT, "oss usbmidi: Unrecognized descriptor: \n");
	  ossusb_dump_desc (d, l);
	  p += l;
	  continue;
	}

      switch (d[2])
	{
	case MS_HEADER:
	  cmn_err (CE_CONT, "MS_HEADER: ");
	  cmn_err (CE_CONT, "v%x.%02x ", d[3], d[4]);
	  break;

	case MIDI_IN_JACK:
	  cmn_err (CE_CONT, "MIDI_IN_JACK: ");
	  cmn_err (CE_CONT, "Type %d, ID %02x, iJack %d\n", d[3], d[4], d[5]);
	  in_endpoint_desc = in_endpoints[cin];
	  if (cin < num_in_endpoints - 1)
	    cin++;
	  break;

	case MIDI_OUT_JACK:
	  cmn_err (CE_CONT, "MIDI_OUT_JACK: ");
	  cmn_err (CE_CONT, "Type %d, ID %02x, iJack %d\n", d[3], d[4], d[5]);
	  n = d[5];
	  cmn_err (CE_CONT, "\t%d inputs: ", n);
	  for (i = 0; i < n; i++)
	    cmn_err (CE_CONT, "%02x/%02x ", d[6 + i * 2], d[6 + i * 2 + 1]);
	  cmn_err (CE_CONT, "\n");
	  out_endpoint_desc = out_endpoints[cout];
	  if (cout < num_out_endpoints - 1)
	    cout++;
	  break;

	case ELEMENT:
	  cmn_err (CE_CONT, "ELEMENT\n");
	  break;
	}

      p += l;
    }


#if 1
  if (reinit)
    for (i = 0; i < devc->num_mididevs; i++)
      if (devc->midic[i].in_endpoint_desc == in_endpoint_desc)	/* Already registered this */
	if (devc->midic[i].out_endpoint_desc == out_endpoint_desc)	/* Already registered this */
	  {
	    return devc;
	  }
#endif

  midic = &devc->midic[devc->num_mididevs];

  devc->osdev = devc->osdev;
  midic->devc = devc;
  midic->usbdev = usbdev;
  midic->in_endpoint_desc = in_endpoint_desc;
  midic->out_endpoint_desc = out_endpoint_desc;

  strcpy (tmp, devc->dev_name);

  midic->midi_dev =
    oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "USBMIDI", tmp,
			 &usb_midi_driver, sizeof (midi_driver_t),
			 NULL /* &std_midi_synth */ ,
			 0, midic, devc->osdev);
  devc->num_mididevs++;

  return devc;
}

void
ossusb_disconnect_midistream (ossusb_devc * devc)
{
}
