/*
  module.c
  --------
  Module code

  8/3/23 created
*/

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


// Serivice calls
#define Service_MIDI           0x58
#define Service_MIDIAlive         0
#define Service_MIDIDying         1
#define Service_MIDISupportAlive  4
#define Service_MIDISupportDying  5

// misc
#define USER_MESSAGE        17 // wimp poll reason code

// message numbers
#define MESSAGE_INSTALL   0x4ee80
#define MESSAGE_REMOVE    0x4ee81
#define MESSAGE_CONNECT   0x4ee86

// Driver flags
#define CAN_SEND             1
#define CAN_RECEIVE          2
#define REQUIRES_BUFFERING   4
#define CAN_RECEIVE_COMMAND  8

#define BUFF_LEN           256 // received commands buffer (for each registered driver)

void module_call(int fn, _kernel_swi_regs *regs, int pw); // veneer.s
int stream_to_cmd(int driver, int data); // midi.c
static void read_options(int argc, const char *arg_string, void *pw);

typedef struct driver_s
{
  char *name;
  int version;
  char *date;
  char *product; // null pointer or additional product name supplied by usbmidi which could be a null string
  unsigned int flags; // zero if driver slot empty (no driver registered)
  int info;    // )
  int init;    // ) addresses of driver call functions
  int receive; // )
  int pw; // driver module private word (r12 value)

  unsigned int map; // bitmap of drivers that this driver is sending to
  int activity; // for senders, increments for every event sent
  int rd; // buffer read index
  int wr; // buffer write index
  int shift; // used when reading bytes from a multibyte command
  unsigned int buff[BUFF_LEN];
} driver_t;

static driver_t driver[DRIVER_MAX];
static int driver_limit; // number of registered drivers

enum
{
  ERR_ACTION,
  ERR_DRIVER,
  ERR_SOURCE,
  ERR_DESTINATION,
  ERR_CANT_SEND,
  ERR_CANT_RECEIVE
};

static const char * const errstr[] =
{
  "Invalid action",
  "Invalid driver",
  "Invalid source driver",
  "Invalid destination driver",
  "Source driver cannot send",
  "Destination driver cannot receive"
};

mod_t mod;


/*
 * send_message
 * ------------
 */
static void send_message(int code, int driver, int dst, int *map)
{
  _kernel_swi_regs regs;
  int blk[8];

  blk[0] = sizeof(blk);
  blk[1] = 0;
  blk[2] = 0;
  blk[3] = 0;
  blk[4] = code;
  blk[5] = driver;
  blk[6] = dst;
  blk[7] = (int)map;

  regs.r[0] = USER_MESSAGE;
  regs.r[1] = (int)blk;
  regs.r[2] = 0; // broadcast
  _kernel_swi(Wimp_SendMessage, &regs, &regs);
}


/*
 * copy_string
 * -----------
 * Makes a local copy of name and version/date strings of installed drivers as
 * they could be provided by applications as well as modules.
 */
static char *copy_string(char * str)
{
  static char buff[4096];
  static int ix;

  char *start = buff + ix;
  while(*str)
    buff[ix++] = *str++;
  buff[ix++] = 0;

  return start;
}


/*
 * get_oserror
 * -----------
 */
static _kernel_oserror *get_oserror(int err)
{
  static _kernel_oserror oserr;

  oserr.errnum = ERROR_BASE; // as original module
  strcpy(oserr.errmess, errstr[err]);

  return &oserr;
}


/*
 * name2number
 * -----------
 */
static int name2number(const char * name)
{
  int i;

  for(i=0; i<driver_limit; i++)
    if(driver[i].flags != 0)
    {
      char *p = driver[i].name;
      if(strncmp(name, p, strlen(p)) == 0)
        return i;
    }

  return -1; // name not found
}


/*
 * connect
 * -------
 * action: 0 = read map, 1 = connect src to dst, 2 = disconnect src from dst.
 * 'map' will be returned for all actions unless passed map address is NULL.
 */
static _kernel_oserror *connect(int action, int src, int dst, int *map)
{
  if((action < 0) || (action > 2))
    return get_oserror(ERR_ACTION);
  if((src < 0) || (src >= driver_limit))
    return get_oserror(ERR_SOURCE);
  if((dst < 0) || (dst >= driver_limit))
    return get_oserror(ERR_DESTINATION);
  if((driver[src].flags & CAN_SEND) == 0)
    return get_oserror(ERR_CANT_SEND);
  if((driver[dst].flags & CAN_RECEIVE) == 0)
    return get_oserror(ERR_CANT_RECEIVE);

  if(action == 1) // connect
    driver[src].map |= (1 << dst);
  else if(action == 2) // remove
    driver[src].map &= ~(1 << dst);

  if((action == 1) || (action == 2))
    send_message(MESSAGE_CONNECT, src + 1, (dst + 1) | (action << 30), (int *)&driver[src].map);

  if(map)
    *map = (int)&driver[src].map;

  return NULL;
}


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

  driver_limit = DRIVER_MAX;
  for(i=0; i<driver_limit; i++)
    driver[i].flags= 0;

  // Issue startup service call
  // This will trigger any loaded driver modules to register if they
  // didn't register when they initialised. Which will happen if drivers
  // have been RMLoad'ed before this module has been.
  regs.r[0] = Service_MIDISupportAlive;
  regs.r[1] = Service_MIDI;
  _kernel_swi(OS_ServiceCall, &regs, &regs);

  return NULL;
}


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

  regs.r[0] = Service_MIDISupportDying;
  regs.r[1] = Service_MIDI;
  _kernel_swi(OS_ServiceCall, &regs, &regs);

  return NULL;
}


/*
 * module_command
 * --------------
 */
_kernel_oserror *module_command(const char *arg_string, int argc, int number, void *pw)
{
  int src, dst;
  _kernel_oserror *err = NULL;

  switch(number)
  {
    case CMD_MIDIMap:
      printf("MIDI Support Driver Map\n\n");
      for(src=0; src < driver_limit; src++)
        if(driver[src].flags != 0)
        {
          printf("  %s ( Version:%d:%02d Date:%s )\n", driver[src].name,
                      driver[src].version / 100, driver[src].version % 100, driver[src].date);
          for(dst=0; dst < driver_limit; dst++)
            if(driver[src].flags != 0)
              if(driver[src].map & (1 << dst))
                printf("   Connected to: %s\n", driver[dst].name);
          printf("\n");
        }
      break;

    case CMD_MIDIConnect:
    case CMD_MIDIDisconnect:
    {
      while(*arg_string == ' ') arg_string++;
      src = name2number(arg_string);
      while(*arg_string != ' ') arg_string++;
      while(*arg_string == ' ') arg_string++;
      dst = name2number(arg_string);
      if(!(err = connect(number, src, dst, NULL)))
        send_message(MESSAGE_CONNECT, src + 1, (dst + 1) | (number << 30), (int *)&driver[src].map);
      break;
    }

    case CMD_MidiSdebug:
      if(argc > 0)
        read_options(argc, arg_string, pw);
      break;
  }

  return err;
}


/*
 * module_swi
 * ----------
 */
_kernel_oserror *module_swi(int number, _kernel_swi_regs *r, void *pw)
{
  switch(number + MIDISupport_00)
  {
    case MIDISupport_InstallDriver:
    {
      int i;
      for(i=0; i<driver_limit; i++)
        if(driver[i].flags == 0)
          break;
      if(i < driver_limit)
        if((r->r[0] != 0) && (r->r[1] != 0))
        {
          driver_t *d = &driver[i];
          d->flags   = r->r[0];
          d->info    = r->r[1];
          d->init    = r->r[2];
          d->receive = r->r[3];
          d->pw      = r->r[4];
          module_call(d->info, r, d->pw);
          d->name = copy_string((char *)r->r[0]);
          d->version = r->r[1];
          d->date = copy_string((char *)r->r[2]);
          if((r->r[3] - (r->r[0]) == 12)) // check for additional product name supplied by usbmidi
            d->product = copy_string((char *)r->r[3]);
          else
            d->product = 0;
          d->rd = d->wr = d->map = 0;
          // return data
          r->r[0] = i + 1;
          r->r[1] = (int)&midis_receive;
          r->r[2] = (int)&midis_givedata;
          r->r[3] = (int)pw;
          send_message(MESSAGE_INSTALL, i + 1, driver_limit, NULL);
        }
      break;
    }

    case MIDISupport_RemoveDriver:
    {
      int d = r->r[1] - 1;
      if((d < 0) || (d >= driver_limit))
        return get_oserror(ERR_DRIVER);
      if(driver[d].flags == 0)
        return get_oserror(ERR_DRIVER);
      driver[d].flags = 0;
      int i;
      for(i=0; i<driver_limit; i++)
        driver[i].map &= ~(1 << d);
      send_message(MESSAGE_REMOVE, r->r[1], driver_limit, NULL);
      break;
    }

    case MIDISupport_DriverInfo:
    {
      int d = r->r[1] - 1;
      if((d < 0) || (d >= driver_limit))
        return get_oserror(ERR_DRIVER);
      if(driver[d].flags == 0)
        return get_oserror(ERR_DRIVER);
      r->r[0] = (int)driver[d].name;
      r->r[1] = driver[d].version;
      r->r[2] = (int)driver[d].date;
      r->r[3] = driver[d].flags;
      r->r[4] = (int)&driver[d].map;
      r->r[5] = driver_limit;
      r->r[6] = (int)driver[d].product;
      break;
    }

    case MIDISupport_CreateDriver:
    {
      int i;
      for(i=0; i<driver_limit; i++)
        if(driver[i].flags == 0)
          break;
      if(i < driver_limit)
        if((r->r[0] != 0) && (r->r[1] != 0))
        {
          driver_t *d = &driver[i];
          d->flags   = r->r[0];
          d->info    = 0;
          d->init    = 0;
          d->receive = 0;
          d->pw      = 0;
          d->product = 0;
          d->name    = copy_string((char *)r->r[1]);
          d->version = r->r[2];
          d->date    = copy_string((char *)r->r[3]);
          d->rd = d->wr = d->map = 0;
          // return data
          r->r[0] = i + 1;
          r->r[1] = (int)&midis_receive;
          r->r[2] = (int)&midis_givedata;
          r->r[3] = (int)pw;
          send_message(MESSAGE_INSTALL, i + 1, driver_limit, NULL);
        }
      break;
    }

    case MIDISupport_Send: // Send FROM the driver in r1
      if(r->r[0] == 0) // send a byte
        r->r[0] = r->r[2] | (1<<24);

      else if(r->r[0] == 1) // send a command
        r->r[0] = r->r[2];

      else if(r->r[0] == 2) // send a block
      {
        int i = r->r[3];
        char *p = (char *)(r->r[2]);
        while(i--)
        {
          r->r[0] = *p++ | (1<<24);
          midis_receive_handler(r, pw);
        }
        break;
      }
      else
        break;
      return midis_receive_handler(r, pw);

    case MIDISupport_Receive: // retrieve data from receive buffer of driver in r1
      if(r->r[0] == 0)
        r->r[0] = -1;
      else
        r->r[0] = r->r[2];
      return midis_givedata_handler(r, pw);

    case MIDISupport_Connect:
      return connect(r->r[0], r->r[1] - 1, r->r[2] - 1, &r->r[1]);

    case MIDISupport_ConnectName:
      return connect(r->r[0], name2number((char *)r->r[1]), name2number((char *)r->r[2]), &r->r[1]);

    case MIDISupport_GetAddress:
      r->r[1] = (int)&midis_receive;
      r->r[2] = (int)&midis_givedata;
      r->r[3] = (int)pw;
      break;

    case MIDISupport_Insert: // Send TO the driver in r1
    {
      driver_t *d = &driver[r->r[1] - 1];
      if(d->flags & CAN_RECEIVE)
      {
        if(r->r[0] == 0) // insert a byte
          r->r[0] = r->r[2] | (1<<24);

        else if(r->r[0] == 1) // insert a command
          r->r[0] = r->r[2];

        else if(r->r[0] == 2) // insert a block
        {
          if(d->receive)
          {
            int i = r->r[3];
            char *p = (char *)(r->r[2]);
            while(i--)
            {
              r->r[0] = *p++ | (1<<24);
              if(d->receive)
                module_call(d->receive, r, d->pw);
              if(mod.debug & (1<<DBG_MSG))
                if(mod.log)fprintf(mod.log, "? -> %08X -> %d\n", r->r[0], r->r[1]);
            }
          }
        }
        else
          break;
        if(d->receive)
          module_call(d->receive, r, d->pw);
        if(mod.debug & (1<<DBG_MSG))
          if(mod.log)fprintf(mod.log, "? -> %08X -> %d\n", r->r[0], r->r[1]);
      }
      break;
    }
  }

  return NULL;
}


/*
 * midis_receive_handler
 * ---------------------
 */
_kernel_oserror *midis_receive_handler(_kernel_swi_regs *r, void *pw)
{
  int i, cmd;
  int d = r->r[1] - 1;

  if((d < 0) || (d >= driver_limit))
    return get_oserror(ERR_DRIVER);

  int map = driver[d].map;
  driver[d].activity++;

  for(i=0; i<driver_limit; i++)
  {
    if(map & (1 << i))
    {
      if(!(driver[i].flags & CAN_RECEIVE_COMMAND))
      {
        if(!(cmd = stream_to_cmd(d, r->r[0])))
          return NULL;
        r->r[0] = cmd;
      }

      if(driver[i].flags & REQUIRES_BUFFERING)
      {
        int write = driver[i].wr;
        int read = driver[i].rd;
        int next = (write < (BUFF_LEN-1)) ? write + 1 : 0;
        driver[i].buff[write] = r->r[0];
        driver[i].wr = next;
        if(next == read) // if the buffer is full ...
          driver[i].rd = (read < (BUFF_LEN-1)) ? read + 1 : 0;  // loose the oldest character
      }

      if(driver[i].receive)
        module_call(driver[i].receive, r, driver[i].pw);

      if(mod.debug & (1<<DBG_MSG))
        if(mod.log)fprintf(mod.log, "%d -> %08X -> %d\n", d+1, r->r[0], i+1);
    }
  }

  return NULL;
}


/*
 * midis_givedata_handler
 * ----------------------
 * input:
 * r0 = function code, 2=give command, 1=give byte, 0=reset, -1=buffer status
 * r1 = driver number
 * output:
 * r0 = data, command or byte, 0 if no more data
 */
_kernel_oserror *midis_givedata_handler(_kernel_swi_regs *r, void *pw)
{
  int d = r->r[1] - 1;
  int write = driver[d].wr;
  int read = driver[d].rd;

  switch(r->r[0])
  {
    case -1: // give buffer status
    {
      int n = write - read;
      if(n < 0)
        n += BUFF_LEN;
      r->r[0] = n;
      break;
    }

    case 0: // reset
      driver[d].rd = driver[d].wr = 0;
      break;

    case 1: // give byte
      if(read == write) // empty
        r->r[0] = 0;
      else
      {
        int cmd = driver[d].buff[read];
        r->r[0] = ((cmd >> driver[d].shift) & 0xff) | (1 << 24);
        driver[d].shift += 8;
        int len = (cmd >> 24) & 3;
        if(driver[d].shift > ((len - 1) * 8))
        { // read all bytes in this command
          driver[d].rd = (read < (BUFF_LEN-1)) ? read + 1 : 0;
          driver[d].shift = 0; // next byte to read in next command
        }
      }
      break;

    case 2: // give command
      if(read == write) // empty
        r->r[0] = 0;
      else
      {
        r->r[0] = driver[d].buff[read];
        driver[d].rd = (read < (BUFF_LEN-1)) ? read + 1 : 0;
        driver[d].shift = 0; // next byte to read in next command
      }
      break;

    default:
      return NULL;
  }

  if(mod.debug & (1<<DBG_MSG))
    if(r->r[0] != 0)
      if(mod.log)fprintf(mod.log, "? -> %08X -> %d\n", r->r[0], r->r[1]);

  return NULL;
}


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

