/*
  module.c
  --------
  USB Midi driver module code

  8/3/23 created
*/

#include <stdio.h>
#include <stdlib.h>
#include <kernel.h>
#include <swis.h>
#include "modhdr.h"
#include "module.h"
#include "midi.h"
#include "usb.h"

// Serivice calls
#define Service_MIDI           0x58
#define Service_DeviceDead     0x79
#define Service_USB            0xD2

// Sub reason codes
#define Service_MIDIAlive         0
#define Service_MIDIDying         1
#define Service_MIDISupportAlive  4
#define Service_MIDISupportDying  5
#define Service_USBNewDevice      0

// midi support driver flags
#define CAN_SEND                  1
#define CAN_RECEIVE               2
#define REQUIRES_BUFFERING        4
#define CAN_RECEIVE_COMMAND       8

// misc
#define UpCallV                    29
#define UpCall_DeviceRxDataPresent 15

mod_t mod;

// Midi Event buffers

// Output (send) circular buffer.
// Midi files often need a few hundred commands queued to send.
#define EV_OUT_LEN 2048 // power of 2, events not bytes
typedef struct ev_out_s
{
  unsigned int buff[EV_OUT_LEN];
  int rd,wr;
} ev_out_t;

static ev_out_t event_out[MAX_PORTS];

// Input (receive) circular buffer.
#define EV_IN_LEN 256 // power of 2, events not bytes
typedef struct ev_in_s
{
  unsigned int buff[EV_IN_LEN];
  int rd,wr;
} ev_in_t;

static ev_in_t event_in[MAX_PORTS];

static void read_options(int argc, const char *arg_string, void *pw);


/*
 * driver_register
 * ---------------
 * Registers us with the midi support module
 */
static void reg1port(int port, void *info, void *init, void *receive, void *pw)
{
  _kernel_swi_regs regs;

  regs.r[0] = CAN_SEND | CAN_RECEIVE | CAN_RECEIVE_COMMAND;
  regs.r[1] = (int)info;
  regs.r[2] = (int)init;
  regs.r[3] = (int)receive;
  regs.r[4] = (int)pw;
  if(_kernel_swi(MIDISupport_InstallDriver, &regs, &regs) == NULL)
  {
    mod.registered[port] = TRUE;
    mod.driver_number[port] = regs.r[0];
    mod.support.receive = regs.r[1];
    mod.support.give = regs.r[2];
    mod.support.pw = regs.r[3];
  }
}
static void driver_register(void *pw)
{
  if(usb.port[0].open) reg1port(0, driver1_info, driver1_init, driver1_receive, pw);
  if(usb.port[1].open) reg1port(1, driver2_info, driver2_init, driver2_receive, pw);
  if(usb.port[2].open) reg1port(2, driver3_info, driver3_init, driver3_receive, pw);
  if(usb.port[3].open) reg1port(3, driver4_info, driver4_init, driver4_receive, pw);
}


/*
 * driver_deregister
 * -----------------
 */
static void driver_deregister(void *pw)
{
  _kernel_swi_regs regs;
  int i;

  for(i=0; i<MAX_PORTS; i++)
    if(mod.registered[i])
    {
      regs.r[1] = mod.driver_number[i];
      _kernel_swi(MIDISupport_RemoveDriver, &regs, &regs);
      mod.registered[i] = FALSE;
    }

}


/*
 * module_init
 * -----------
 */
_kernel_oserror *module_init(const char *tail, int podule_base, void *pw)
{
  _kernel_oserror *err = NULL;
  _kernel_swi_regs regs;
  int i;

  mod.ports = usb_find();

  for(i=0; i<mod.ports; i++)
    usb_open(i);

  // Claim the upcall vector to deal with IN received data
  regs.r[0] = UpCallV;
  regs.r[1] = (int)upcallv;
  regs.r[2] = (int)pw;
  _kernel_swi(OS_Claim, &regs, &regs);

  // Register with timer to handle OUT endpoint queues
  regs.r[0] = (int)timer_callback;
  regs.r[1] = (int)pw;
  regs.r[2] = 1; // period, 1ms
  regs.r[3] = (int)Module_Title;
  if((err = _kernel_swi(MTimer_Register, &regs, &regs)))
    return err;

  // If Midi Support is loaded, register with it, if not we will
  // register when we receive its startup service call.
  regs.r[0] = 18;
  regs.r[1] = (int)"MIDISupport";
  if(!_kernel_swi(OS_Module, &regs, &regs))
    driver_register(pw);

  mod.options = (1<<OPT_LIMIT_TX);

  return NULL;
}


/*
 * module_term
 * -----------
 */
_kernel_oserror *module_term(int fatal, int podule_base, void *pw)
{
  _kernel_swi_regs regs;

  // If we are registered with Midi Support, remove ourselves
  driver_deregister(pw);

  // remove our timer entry
  regs.r[0] = (int)timer_callback;
  _kernel_swi(MTimer_Remove, &regs, &regs);

  // Close all open usb ports
  int i;
  for(i=0; i<MAX_PORTS; i++)
    usb_close(i);

  // remove callback if pending
  if(mod.callbacks > 0)
  {
    regs.r[0] = (int)upcall_callback;
    regs.r[1] = (int)pw;
    _kernel_swi(OS_RemoveCallBack, &regs, &regs);
  }

  // release the upcall vector
  regs.r[0] = UpCallV;
  regs.r[1] = (int)upcallv;
  regs.r[2] = (int)pw;
  _kernel_swi(OS_Release,&regs,&regs);

  return NULL;
}


/*
 * module_swi
 * ----------
 */
_kernel_oserror *module_swi(int number, _kernel_swi_regs *r, void *pw)
{
  int r1 = r->r[1], r2 = r->r[2];

  switch(number + USBMidi_00)
  {
    case USBMidi_Control:
      switch(r->r[0])
      {
        case 1: // Options ( = &~n ^m ) 1 bit: limit tx
          r1 &= 1;
          r2 &= 1;
          mod.options = (mod.options & ~r1) ^ r2;
          break;
      }
      r->r[0] = mod.options;
      break;
  }

  return NULL;
}


/*
 * module_command
 * --------------
 */
_kernel_oserror *module_command(const char *arg_string, int argc, int number, void *pw)
{
  switch(number)
  {
    case CMD_USBMidi:
      if(argc > 0)
        read_options(argc, arg_string, pw);
      break;
  }

  return NULL;
}


/*
 * driver_info_handlers
 * --------------------
 */
static _kernel_oserror *driver_info_handler(int port, _kernel_swi_regs *r)
{
  sprintf(usb.port[port].name, "USBPort%d", port);
  r->r[0] = (int)usb.port[port].name;
  r->r[1] = Module_VersionNumber;
  r->r[2] = (int)Module_Date;
  r->r[3] = (int)usb.port[port].product; // this is an addition to the original midisupport

  return NULL;
}
_kernel_oserror *driver1_info_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_info_handler(0, r);
}
_kernel_oserror *driver2_info_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_info_handler(1, r);
}
_kernel_oserror *driver3_info_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_info_handler(2, r);
}
_kernel_oserror *driver4_info_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_info_handler(3, r);
}


/*
 * driver_init_handlers
 * --------------------
 */
_kernel_oserror *driver1_init_handler(_kernel_swi_regs *r, void *pw)
{
  return NULL;
}
_kernel_oserror *driver2_init_handler(_kernel_swi_regs *r, void *pw)
{
  return NULL;
}
_kernel_oserror *driver3_init_handler(_kernel_swi_regs *r, void *pw)
{
  return NULL;
}
_kernel_oserror *driver4_init_handler(_kernel_swi_regs *r, void *pw)
{
  return NULL;
}


/*
 * driver_receive_handlers
 * -----------------------
 * Send a command to the USB interface. It puts the usb events in a buffer
 * from which a limited number will be sent to the usb driver every usb frame
 * by the timer handler code.
 * cmd format:
 *  data byte 1 usually the status, bits 0-7
 *  data byte 2, bits 8-15
 *  data byte 3, bits 16-23
 *  number of bytes, bits 24,25 (1..3)
 *
 * port: 0..3, the outgoing usb port number
 */
static _kernel_oserror *driver_receive_handler(int port, unsigned int cmd)
{
  unsigned int event;
  int i, n;

  if(!(event = cmd_to_event(port, cmd)))
  {
    if((mod.debug & (1<<DBG_MSG)) && mod.log)
    {
      fprintf(mod.log, "%u: %d -> [", mod.ticks, port);
      n = (cmd >> 24) & 3;
      for(i=0; i<n; i++)
        fprintf(mod.log, "%02X%s", (cmd >> (i*8)) & 0xff, (i < (n-1)) ? " " : "]\n");
    }
    return NULL;
  }
  else
  {
    if((mod.debug & (1<<DBG_MSG)) && mod.log)
    {
      fprintf(mod.log, "%u: %d -> [", mod.ticks, port);
      n = (cmd >> 24) & 3;
      for(i=0; i<n; i++)
        fprintf(mod.log, "%02X%s", (cmd >> (i*8)) & 0xff, (i < (n-1)) ? " " : "");
      fprintf(mod.log, "] -> event %08X\n", event);
    }
  }

  ev_out_t *e = &event_out[port];

  // put event into buffer, if there's room
  if(e->rd != ((e->wr + 1) & (EV_OUT_LEN-1)))
  {
    e->buff[e->wr++] = event;
    e->wr &= EV_OUT_LEN - 1;
  }
  else if(mod.debug & (1<<DBG_BUF))
    if(mod.log)fprintf(mod.log, "OUT buffer overflow\n");

  if(mod.debug & (1<<DBG_BUF))
    if(mod.log)fprintf(mod.log, "OUT rd %d, wr %d\n", e->rd, e->wr);

  return NULL;
}
_kernel_oserror *driver1_receive_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_receive_handler(0, r->r[0]);
}
_kernel_oserror *driver2_receive_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_receive_handler(1, r->r[0]);
}
_kernel_oserror *driver3_receive_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_receive_handler(2, r->r[0]);
}
_kernel_oserror *driver4_receive_handler(_kernel_swi_regs *r, void *pw)
{
  return driver_receive_handler(3, r->r[0]);
}


/*
 * service_callback_handler
 * ------------------------
 */
_kernel_oserror *service_callback_handler(_kernel_swi_regs *r, void *pw)
{
//  _kernel_swi_regs regs;
//  _kernel_oserror *err = NULL;
  int i;

  switch(mod.service_num)
  {
    case Service_MIDI:
      if(r->r[0] == Service_MIDISupportAlive)
        driver_register(pw);
      else // Service_MIDISupportDying
      {    // No need to deregister, because the support module is dying.
           // Just need to stop using the support module function calls.
        for(i=0; i<MAX_PORTS; i++)
          mod.registered[i] = 0;
        mod.support.receive = 0;
      }
      break;

    case Service_DeviceDead:
      usb_dead_device(mod.service_data);
      break;

    case Service_USB:
      // new device found, open and register it
      if((i = usb_open_device()) != -1)
      {
        if(i == 0)reg1port(0, driver1_info, driver1_init, driver1_receive, pw);
        else if(i == 1)reg1port(1, driver2_info, driver2_init, driver2_receive, pw);
        else if(i == 2)reg1port(2, driver3_info, driver3_init, driver3_receive, pw);
        else if(i == 3)reg1port(3, driver4_info, driver4_init, driver4_receive, pw);
      }
      break;
  }

  mod.service_num = 0;
  return NULL;
}


/*
 * service_call
 * ------------
 * Handles service calls, only does the minimum here, the rest in a callback
 */
void service_call(int service, _kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;

  switch(service)
  {
    case Service_MIDI: // midi support starting, stopping
      if((r->r[0] == Service_MIDISupportAlive) ||
         (r->r[0] == Service_MIDISupportDying))
      {
        mod.service_data = r->r[0];
        break; // goto add callback
      }
      return;

    case Service_DeviceDead: // device unplugged
      // r2 = device driver handle
      // r3 -> device name
      if(usb_check_device((char *)r->r[3])) // check name
      {
        // our device has been unplugged
        mod.service_data = r->r[2];
        break; // goto add callback
      }
      return;

    case Service_USB: // device plugged in
      if(r->r[0] == Service_USBNewDevice)
      {
        // r2 -> USBServiceCall descriptor block
        if(usb_new_device(r->r[2])) // check descriptors
          break; // goto add callback
      }
      return;

    default:
      return;
  }
  // add callback to complete the tasks
  mod.service_num = service;
  regs.r[0] = (int)service_callback;
  regs.r[1] = (int)pw;
  _kernel_swi(OS_AddCallBack, &regs, &regs);
}


/*
 * timer_callback_handler
 * ----------------------
 * This is called by the midi support timer function.
 * It handles usb packets to send, and it needs to be called at the usb frame
 * rate of 1kHz.
 * Events are removed from the queue and sent to usb driver.
 * The idea is to limit the number of events per usb frame.
 */
_kernel_oserror *timer_callback_handler(_kernel_swi_regs *r, void *pw)
{
  int i,j,n, temp[16]; // to accomodate an endpoint of 64 bytes
  _kernel_swi_regs regs;
  mod.ticks = r->r[1];

  for(i=0; i<MAX_PORTS; i++)
    if(usb.port[i].open)
    {
      if((mod.debug & (1<<DBG_BUF)) && mod.log)
      {
        regs.r[0] = usb.port[i].endpoint[IN].handle.buffer;
        _kernel_swi(Buffer_GetInfo, &regs, &regs);
        int rd = regs.r[4], wr = regs.r[3];
        regs.r[0] = usb.port[i].endpoint[OUT].handle.buffer;
        _kernel_swi(Buffer_GetInfo, &regs, &regs);
        fprintf(mod.log, "%u: %d(%d %d) (%d %d)\n", mod.ticks, i, regs.r[4], regs.r[3], rd, wr);
      }

      // transfer the limited number of events to the OUT buffer
      int blk = (mod.options & (1<<OPT_LIMIT_TX)) ? 4 : usb.port[i].endpoint[OUT].size / 4;
      ev_out_t *e = &event_out[i];
      n = e->wr - e->rd;
      if(n < 0)
        n += EV_OUT_LEN;
      if(n > blk)
        n = blk;

      if(n)
      {
        for(j=0; j<n; j++)
        {
          temp[j] = e->buff[e->rd++];
          e->rd &= EV_OUT_LEN - 1;
        }

        if(mod.debug & (1<<DBG_MSG))
          if(mod.log)
          {
            fprintf(mod.log, "%u: ", mod.ticks);
            for(j=0; j<n; j++)
              fprintf(mod.log, "%08X%c", temp[j], ((j&7)==7) ? '\n' : ' ');
            fprintf(mod.log, "-> %d\n", i);
          }

        struct buffer_s *b = &mod.buffer;
        struct handle_s *h = &usb.port[i].endpoint[OUT].handle;
        if(b->service)
        {
          regs.r[0] = BUF_FREE_SPACE;
          regs.r[1] = h->buff_id;
          module_call(b->service, &regs, b->pw);

          if(regs.r[2] >= (4 * n))
          {
            regs.r[0] = BUF_INSERT_BLOCK;
            regs.r[1] = h->buff_id;
            regs.r[2] = (int)temp;
            regs.r[3] = 4 * n;
            module_call(b->service, &regs, b->pw);
          }
        }
      }
      usb_wakeup_rx(i);
    }

  return NULL;
}


/*
 * upcall_callback_handler
 * -----------------------
 * This handles received usb packets.
 * It converts the buffered events to midi commands and sends them to midi support.
 */
_kernel_oserror *upcall_callback_handler(_kernel_swi_regs *r, void *pw)
{

  int i;
  _kernel_swi_regs regs;

  if(mod.callbacks > 0)
  {
    mod.callbacks = 0;

    for(i=0; i<MAX_PORTS; i++)
      if(usb.port[i].open)
      {
        ev_in_t *e = &event_in[i];

        while(e->rd != e->wr)
        {
          unsigned int event = e->buff[e->rd++];
          regs.r[0] = event_to_cmd(event);
          e->rd &= EV_IN_LEN - 1;

          if(mod.log)
          {
            fprintf(mod.log, "%u: %d <- [", mod.ticks, i);
            int j, n = (regs.r[0] >> 24) & 3;
            for(j=0; j<n; j++)
              fprintf(mod.log, "%02X%s", (regs.r[0] >> (j*8)) & 0xff, (j < (n-1)) ? " " : "");
            fprintf(mod.log, "] <- event %08X\n", event);

            if(mod.debug & (1<<DBG_BUF))
              if(mod.log)fprintf(mod.log, "IN rd %d, wr %d\n", e->rd, e->wr);
          }

          regs.r[1] = mod.driver_number[i];
          if(mod.support.receive)
            module_call(mod.support.receive, &regs, mod.support.pw);
        }
      }
  }

  return 0;
}


/*
 * upcallv_handler
 * ---------------
 * This handles received usb packets.
 * It empties the usb rx buffer of events, then adds a callback to complete
 * the task.
 */
int upcallv_handler(_kernel_swi_regs *r, void *pw)
{
  _kernel_swi_regs regs;
  int i,n;

  if(r->r[0] == UpCall_DeviceRxDataPresent)
    for(i=0; i<MAX_PORTS; i++)
      if(usb.port[i].open)
      {
        struct handle_s *h = &usb.port[i].endpoint[IN].handle;

        if(r->r[1] == h->fileswitch)
        {
          struct buffer_s *b = &mod.buffer;
          ev_in_t *e = &event_in[i];
          if(b->service)
            do
            {
              regs.r[0] = BUF_USED_SPACE;
              regs.r[1] = h->buff_id;
              module_call(b->service, &regs, b->pw);
              n = regs.r[2]; // BYTES to remove

              if(n)
              {
                regs.r[0] = BUF_REMOVE_BLOCK;
                // the buffer is circular, check for wrap
                if((e->wr + (n / 4)) >= EV_IN_LEN)
                {
                  // 2 blocks
                  int blk1 = (EV_IN_LEN - e->wr) * 4;
                  regs.r[2] = (int)&e->buff[e->wr];
                  regs.r[3] = blk1;
                  module_call(b->service, &regs, b->pw);
                  regs.r[2] = (int)e->buff;
                  regs.r[3] = n - blk1;
                  module_call(b->service, &regs, b->pw);
                  e->wr = (e->wr + (n / 4)) & (EV_IN_LEN - 1);
                }
                else // single block
                {
                  regs.r[2] = (int)&e->buff[e->wr];
                  regs.r[3] = n;
                  module_call(b->service, &regs, b->pw);
                  e->wr += n / 4;
                }
              }
            }
            while(n);
          usb_wakeup_rx(i);

          if(mod.callbacks++ == 0)
          {
            regs.r[0] = (int)upcall_callback;
            regs.r[1] = (int)pw;
            _kernel_swi(OS_AddCallBack, &regs, &regs);
          }

          return VECTOR_CLAIM;
        }
      }
  return VECTOR_PASSON;
}


/*
 * read_options
 * ------------
 * Reads options for the *USBMidi command
 */
static void read_options(int argc, const char *arg_string, void *pw)
{
  char c;

  do
  {
    while((c = *arg_string++) == ' ');
    if(c == '-')
      switch((c = *arg_string++) & ~0x20)
      {
        case 'I': // display usb port details, -i+ gives detailed info
          usb_report(*arg_string == '+');
          printf("options = %d\n", mod.options);
          break;

        case 'O': // open log file
          if(mod.log)
            printf("Log file already open\n");
          else
          {
            mod.log = fopen("logU", "w"); // log file opened in the current directory
            if(!mod.log)
              printf("Couldn't open Log file\n");
            else
              printf("Log file opened\n");
          }
          break;

        case 'C': // close log file
          if(mod.log)
          {
            fclose(mod.log);
            printf("Log file closed\n");
            mod.log = 0;
          }
          else
            printf("Log file not open\n");
          break;

        case 'D': // debug options
          mod.debug = atoi(arg_string);
          break;

        default: printf("unknown opion (-%c)\n", c);
      }
  }
  while(c >= ' ');
}

