/*
  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 "serial.h"

// Service calls
#define Service_MIDI           0x58

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

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

// upcalls
#define UpCallV                    29
#define UpCall_DeviceRxDataPresent 15

mod_t mod;

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(serial.port[0].open) reg1port(0, driver1_info, driver1_init, driver1_receive, pw);
  if(serial.port[1].open) reg1port(1, driver2_info, driver2_init, driver2_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;

  // find the available ports
  int n = serial_find();

  // open the ports
  for(i=0; i<n; i++)
    serial_open(i);

  // Claim the upcall vector to handle 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 buffered transmit data
  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);

  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 serial ports
  int i;
  for(i=0; i<MAX_PORTS; i++)
    serial_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_command
 * --------------
 */
_kernel_oserror *module_command(const char *arg_string, int argc, int number, void *pw)
{
  switch(number)
  {
    case CMD_SerialMidi:
      if(argc > 0)
        read_options(argc, arg_string, pw);
      break;
  }

  return NULL;
}


/*
 * driver_info_handler
 * -------------------
 */
_kernel_oserror *driver_info_handler(int port, _kernel_swi_regs *r)
{
  r->r[0] = (int)serial.port[port].device;
  r->r[1] = Module_VersionNumber;
  r->r[2] = (int)Module_Date;

  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);
}


/*
 * driver_init_handler
 * -------------------
 */
_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;
}

/*
 * driver_receive_handlers
 * -----------------------
 * Send a command to the serial port.
 * 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)
 *
 * if (room to send) && (buffer empty)
 *  send data directly
 * else
 *  buffer data
 *
 * port: 0..1, the outgoing serial port number
 */
static _kernel_oserror *driver_receive_handler(int port, unsigned int cmd)
{
  int n, i, free;

  n = (cmd >> 24) & 3;
  stream_t *s = &serial.port[port].stream[TX];

  free = serial_status(s->handle);

  if((n <= free) && (s->wr == s->rd))
  {
    // send directly
    serial_write(s->handle, (char *)&cmd, n);

    if(mod.debug & (1<<DBG_MSG))
      if(mod.log)
      {
        fprintf(mod.log, "%u:%d: [",mod.ticks, port);
        for(i=0; i<n; i++)
          fprintf(mod.log, "%02X%s", (cmd >> (8*i)) & 0xff, (i < (n-1)) ? " " : "");
        fprintf(mod.log, "] %d sent direct, %d free\n", n, free - n);
      }
  }
  else // buffer data
  {
    int data = cmd;
    for(i=0; i<n; i++)
    {
      s->buf[s->wr++] = data;
      s->wr &= BUFF_LEN - 1;
      data >>= 8;
    }

    int used = s->wr - s->rd;
    if(used < 0)
      used += BUFF_LEN;

    if(mod.debug & (1<<DBG_MSG))
      if(mod.log)
      {
        fprintf(mod.log, "%u:%d: [",mod.ticks, port);
        for(i=0; i<n; i++)
          fprintf(mod.log, "%02X%s", (cmd >> (8*i)) & 0xff, (i < (n-1)) ? " " : "");
        fprintf(mod.log, "] %d buffered, %d used\n", n, used);
      }
  }

  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]);
}


/*
 * timer_callback_handler
 * ----------------------
 * This is called by the timer module.
 * For Tx, sends as many buffered bytes as it can to the send buffer.
 */
_kernel_oserror *timer_callback_handler(_kernel_swi_regs *r, void *pw)
{
  int i, n, used;
  mod.ticks = r->r[1];

  for(i=0; i<MAX_PORTS; i++)
    if(serial.port[i].open)
    {
      stream_t *s = &serial.port[i].stream[TX];

      used = s->wr - s->rd;
      if(used < 0)
        used += BUFF_LEN;

      if(used > 0)
      {
        int free = serial_status(s->handle);

        if(free > 0)
        {
          if(used > free)
            n = free;
          else
            n = used;

          if((s->rd + n) >= BUFF_LEN) // wrap?
          {
            // 2 blocks
            int blk1 = BUFF_LEN - s->rd;
            serial_write(s->handle, s->buf + s->rd, blk1);
            serial_write(s->handle, s->buf, n - blk1);
          }
          else // single block
            serial_write(s->handle, s->buf + s->rd, n);
          s->rd = (s->rd + n) & (BUFF_LEN - 1);

          if(mod.debug & (1<<DBG_MSG))
            if(mod.log)fprintf(mod.log, "%u:%d: %d buffered, %d sent, free %d\n", mod.ticks, i, used - n, n, free - n);
        }
      }
    }

  return NULL;
}


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

  int i, j, n;
  _kernel_swi_regs regs;

  if(mod.callbacks > 0)
  {
    mod.callbacks = 0;
    for(i=0; i<MAX_PORTS; i++)
      if(serial.port[i].open)
      {
        stream_t *s = &serial.port[i].stream[RX];
        n = s->wr - s->rd;
        if(n < 0)
          n += BUFF_LEN;

        if(mod.debug & (1<<DBG_MSG))
          if(mod.log)fprintf(mod.log, "%u:%d: %d received\n", mod.ticks, i, n);

        if(!mod.support.receive)
          return NULL;

        for(j=0; j<n; j++)
        {
          if((regs.r[0] = stream_to_cmd(i, s->buf[s->rd++])) != 0)
          {
            if(mod.debug & (1<<DBG_MSG))
              if(mod.log)
              {
                int k, cc = regs.r[0];
                int nn = (cc >> 24) & 3;
                fprintf(mod.log, "%u:%d: [", mod.ticks, i);
                for(k=0; k<nn; k++)
                  fprintf(mod.log, "%02X%s", (cc >> (k*8)) & 0xff, (k < (nn-1)) ? " " : "");
                fprintf(mod.log, "] received\n");

              }
            regs.r[1] = mod.driver_number[i];
            module_call(mod.support.receive, &regs, mod.support.pw);
          }
          s->rd &= BUFF_LEN - 1;
        }
      }
  }
  return 0;
}


/*
 * upcallv_handler
 * ---------------
 * This handles received data.
 * It empties the rx buffer of data, 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(serial.port[i].open)
      {
        stream_t *s = &serial.port[i].stream[RX];
        if(r->r[1] == s->handle)
        {
          if((n = serial_status(s->handle)) != 0)
          {
            if((s->wr + n) >= BUFF_LEN) // wrap?
            {
              // 2 blocks
              int blk1 = BUFF_LEN - s->wr;
                serial_read(s->handle, s->buf + s->wr, blk1);
                serial_read(s->handle, s->buf, n - blk1);
            }
            else // single block
              serial_read(s->handle, s->buf + s->wr, n);
            s->wr = (s->wr + n) & (BUFF_LEN - 1);
          }

          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;
}


/*
 * service_callback_handler
 * ------------------------
 */
_kernel_oserror *service_callback_handler(_kernel_swi_regs *r, void *pw)
{
  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;

  }

  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;

    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);
}


/*
 * 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 port details
          serial_report();
          break;

        case 'O': // open log file
          if(mod.log)
            printf("Log file already open\n");
          else
          {
            mod.log = fopen("logM", "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 >= ' ');
}

