/*
 * devices.c -- Routines for managing multiple MIDI devices
 *
 * Copyright 1998 - John E. Stone
 *                  j.stone@acm.org
 *                  johns@cs.umr.edu
 *
 * $Id: devices.c,v 1.20 1999/10/01 07:05:44 johns Exp $
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "seq.h"
#include "devices.h"

#include "nulldrv.h"     /* NULL driver headers                        */
#include "ms124w.h"      /* Midiator MS-124W  Driver                   */
#include "ms124t.h"      /* Midiator MS-124T  Driver                   */
#include "filedrv.h"     /* File or FIFO type devices                  */
#include "filetdrv.h"    /* File or FIFO type devices what have timing */
#include "sc7drv.h"      /* Roland SC-7 Serial Port Driver             */

/* Only include SGI drivers when compiling on an SGI system */
#ifdef __sgi
#include "sgidrv.h"      /* SGI driver headers             */
#endif

/* Any pre-loading etc should be done here before the config file is read */
/* This routine should register drivers by name in a table, so that they  */
/* are in a runtime extensible table before the config file is read.      */
/* In theory, this will allow plug-in device support later on..           */

int InitMIDIDrivers(MidiSystem * midisystem) {
  if (midisystem == NULL)
    return -1;

  midisystem->numdrivers = 0;
  midisystem->numdevices = 0;
  midisystem->ifds = (fd_set *) malloc(sizeof(fd_set));
  FD_ZERO(((fd_set *) midisystem->ifds));  /* clear whole fdset.. */

  /* built-in driver inits go here 
   * Call the drvinst() code for each of the builtin drivers.
   * Don't forget to add the appropriate includes up top as well.
   */   

  /* NULL driver, it just eats MIDI events.. */
  nulldrv_drvinst(AddMIDIDriver, midisystem);

  /* Midiator MS-124W serial interface */
  ms124w_drvinst(AddMIDIDriver, midisystem);

  /* Midiator MS-124W serial interface */
  ms124t_drvinst(AddMIDIDriver, midisystem);

  /* Device File or FIFO based devices, Open Sound System */
  filedrv_drvinst(AddMIDIDriver, midisystem);

  /* FIFO based device, using its own timing info */
  filetdrv_drvinst(AddMIDIDriver, midisystem);

  /* Roland SC-7 Serial Port Driver */
  sc7drv_drvinst(AddMIDIDriver, midisystem);

#ifdef __sgi
  /* SGI MIDI library driver */
  sgidrv_drvinst(AddMIDIDriver, midisystem);
#endif

  /* Add plugin stuff here 
   *
   * Step 1: Get list of plugins 
   *
   * Step 2: For each plugin, use dlopen() and dlsym() or equivalent to 
   *         find the symbol "plugin_drvinst" function 
   *
   * Step 3: We should probably have some method of getting a version 
   *         number from the plugin, so that if the API changes substantially, 
   *         loading older plugins will fail.. Just a sanity check step 
   *
   * Step 4: Once we've done any paranoia checking, go ahead and 
   *         call the plugin_drvinst() function we found with 
   *         dlsym.  
   */

  return 0;
}

int AddMIDIDriver(MidiSystem * midisystem, MidiDriver devinfo) {
  if (midisystem == NULL)
    return -1; 
  
  midisystem->drivers[midisystem->numdrivers] = devinfo;
  midisystem->numdrivers++;

  return 0;
}


int ConfigReadString(char *string, FILE * ifp, int maxsize) {
  char c;
  int i, rc;

  rc = -1;

  while (!feof(ifp)) {
    if (fgetc(ifp) == '"')
      break;
  }
  
  memset(string, 0, maxsize);
  i = 0;
  while ((!feof(ifp)) && (i < (maxsize - 1))) {
    if ((c = fgetc(ifp)) == '"')
      return 0;

    string[i] = c;
    i++;
  }

  return rc;
}


int ConfigMIDIDevices(char * configfile, MidiSystem * midisystem) {
  int i;
  FILE * ifp;

  if (configfile == NULL || midisystem == NULL)
    return -1;

  if (midisystem->numdrivers <= 0) {
    printf("No MIDI Drivers Registered.\n");
    return -1;
  }

  printf("Registered MIDI Drivers:\n");
  for (i=0; i<midisystem->numdrivers; i++) {
    printf("%20s - ", midisystem->drivers[i].typename);
    printf("%s\n", midisystem->drivers[i].version);
  } 

  OpenMIDIDevice("NULL", "", midisystem); 
  midisystem->numdevices = 0;
  for (i=0; i<MAX_DEVICES; i++) {
    midisystem->devices[i] = midisystem->devices[0];
  }
  midisystem->numdevices = 0;

  ifp = fopen(configfile, "r");
  if (ifp == NULL)
    return -1;

  while (1) {
    char drvname[80];
    char drvparms[4096]; 
    if (ConfigReadString(drvname, ifp, 80) != 0)
      break;
    if (ConfigReadString(drvparms, ifp, 4096) != 0)
      break;

    if (OpenMIDIDevice(drvname, drvparms, midisystem) != 0) 
      printf("Driver failed to init: %s %s\n", drvname, drvparms);
  }
   
  fclose(ifp);

  printf("\n");
  printf("%d MIDI Ports Enabled:\n", midisystem->numdevices);
  if (midisystem->numdevices > 0) {
    for (i=0; i<midisystem->numdevices; i++) {
      printf("  Port %d: %20s\n", i,  midisystem->devices[i].typename);
    }
    printf("\n");
  }
  else { 
    return -1; /* Error, no MIDI ports enabled in config file */
  } 

  return 0;
}



int OpenMIDIDevice(char * name, char * parms, MidiSystem * sys) {
  int i, rc;
   
  if (sys == NULL)
    return -1;

  if (sys->numdrivers <= 0)
    return -1;

  for (i=0; i<sys->numdrivers; i++) {
    if (!strcmp(sys->drivers[i].typename, name)) {
      sys->devices[sys->numdevices].typename = sys->drivers[i].typename;
      sys->devices[sys->numdevices].version = sys->drivers[i].version;
      sys->devices[sys->numdevices].flags = sys->drivers[i].flags;
      sys->devices[sys->numdevices].openfunc = sys->drivers[i].openfunc;
      sys->devices[sys->numdevices].writefunc = sys->drivers[i].writefunc;
      sys->devices[sys->numdevices].readfunc = sys->drivers[i].readfunc;
      sys->devices[sys->numdevices].closefunc = sys->drivers[i].closefunc;

      sys->devices[sys->numdevices].iofd = -1;

      rc = sys->drivers[i].openfunc(parms, 
             &sys->devices[sys->numdevices].devicehandle, 
             &sys->devices[sys->numdevices].iofd); 

      if (rc == 0) {
        if (sys->devices[sys->numdevices].iofd != -1) {
          FD_SET(sys->devices[sys->numdevices].iofd, ((fd_set *) sys->ifds));
        }

        sys->numdevices++;
      }

      return rc; 
    }   
  }

  return -1;
}


int CloseMIDIDevices(MidiSystem * midisystem) {
  if (midisystem == NULL)
    return -1;

  return 0;
}


int MIDIDeviceWrite(MidiSystem * sys, MidiIOEvent * ev) {
  if (ev->port >= MAX_DEVICES)
    return -1;

  if (!(sys->devices[ev->port].flags & DEVICE_HAS_TIMER)) {
#if defined(NO_NANOSLEEP) 
    if (ev->delay.tv_sec > 0 || ev->delay.tv_nsec > 1000) {
      struct timeval t;

      t.tv_sec = ev->delay.tv_sec;
      t.tv_usec = ev->delay.tv_nsec / 1000;
      
      select(0, NULL, NULL, NULL, &t);
    }
#elif defined(USE_GETTIMEOFDAY)
    if (ev->delay.tv_sec > 0 || ev->delay.tv_nsec > 1000) {
       struct timeval t, t2, d, n;
       long usec, usecdiff;
       long sec, secdiff;

       d.tv_sec = 0;        /* delay for select */
       d.tv_usec = 5000;    /* delay for select */

       gettimeofday(&t, NULL);
       n.tv_sec = t.tv_sec + ev->delay.tv_sec +
                  ((t.tv_usec + (ev->delay.tv_nsec / 1000)) / 1000000);
       n.tv_usec = (t.tv_usec + (ev->delay.tv_nsec  / 1000)) % 1000000;
 
       sec = ev->delay.tv_sec;
       usec = ev->delay.tv_nsec / 1000;

       while ((secdiff = (n.tv_sec - t2.tv_sec)) > 0) {
         if ((sec - secdiff) > 0) { 
           select(0, NULL, NULL, NULL, &d); 
         }
         gettimeofday(&t2, NULL);
       }

       while ((usecdiff = (n.tv_usec - t2.tv_usec)) > 1000) {
         if ((usec - usecdiff) > 10000) {
           select(0, NULL, NULL, NULL, &d); 
         }    
         gettimeofday(&t2, NULL);
       }
    }
#else
    if (ev->delay.tv_sec > 0 || ev->delay.tv_nsec > 0) {
      struct timespec t;
      t.tv_sec = ev->delay.tv_sec;
      t.tv_nsec = ev->delay.tv_nsec;
  
      nanosleep(&t, NULL); 
    }
#endif
  }
  
  /* actually make the driver do something.. */
  return sys->devices[ev->port].writefunc(sys->devices[ev->port].devicehandle, ev);
}


int MIDIDeviceRead(MidiSystem * sys, MidiIOEvent * ev) {
  struct timeval t;
  int port, rc, iofd;
  fd_set fds;

  rc = -1;
  while (rc < 0) {

    fds = *((fd_set *) sys->ifds);

    select(FD_SETSIZE, &fds, NULL, NULL, NULL);
    gettimeofday(&t, NULL); 

    /* find out which port the data is on, from the file descriptor */
    for (port=0; port<sys->numdevices; port++) {
      if ((iofd = sys->devices[port].iofd) != -1) {
        if (FD_ISSET(sys->devices[port].iofd, &fds)) {
          rc = sys->devices[port].readfunc(sys->devices[port].devicehandle, ev);
          break;
        }
      }
    } 
  }

  ev->port = port;
  ev->delay.tv_sec = t.tv_sec;
  ev->delay.tv_nsec = t.tv_usec * 1000;
  
  return 0;
}

