/*
  MidiMan - driver management for midi support

  drivers.c
  ---------
  3/4/23
*/

#include "wimp.h"
#include "main.h"
#include "lib.h"
#include "drivers.h"
#include "modules.h"
#include "routes.h"


// sources menu
menu_t sources_menu =
{
  "Sources",NULL,7, 7,2,7,0,0,44,0,
  {
    {0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},
    {0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0}
  }
};

// destinations menu
menu_t destinations_menu =
{
  "Destinations",NULL,12, 7,2,7,0,0,44,0,
  {
    {0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},
    {0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0},{0}
  }
};

driver_t driver[DRIVER_LIMIT];
int drivers; // number of installed drivers
int senders; // number of drivers that can send
int receivers; // number of drivers that can receive
unsigned char snd[DRIVER_LIMIT]; // driver numbers of senders
unsigned char rcv[DRIVER_LIMIT]; // driver number of receivers
int redeye_icon; // driver info button that is red, or 0 if none
int cream_driver_icon; // driver title icon that is cream, or 0 if none
int in_win; // true if mouse is in the drivers window visible area


/*
 * connect
 * -------
 */
void connect(int action, int src, int dst)
{
  _kernel_swi_regs regs;

  regs.r[0] = action;
  regs.r[1] = src;
  regs.r[2] = dst;
  _kernel_swi(MIDISupport_Connect, &regs, &regs);
}


/*
 * update_connection_totals
 * ------------------------
 * Updates the icons showing the number of source and destination connection totals
 */
void update_connection_totals(void)
{
  int i,j,k;

  for(i=0; i<drivers; i++)
  {
    icon_text_change(itoa(count_ones(*driver[i].map)), ro.handle[WIN_DRIVERS], ICON_DST_NUM+(8*i));
    for(j=k=0; j<drivers; j++)
      if(*driver[j].map & (1<<(driver[i].number - 1)))
        k++;
    icon_text_change(itoa(k), ro.handle[WIN_DRIVERS], ICON_SRC_NUM+(8*i));
  }
  update_routes();
}


/*
 * update_info_icons
 * -----------------
 * Updates the driver info window icons.
 * Expects blk to contain info window state.
 * This is a separate function to save duplicating the code for the route map window.
 */
void update_info_icons(int *blk, int parent, driver_t *d)
{
  _kernel_swi_regs regs;

  int win = blk[0];
  icon_text_change(d->name, win, ICON_INFO_NAME);
  char s[8];
  sprintf(s, "%d.%02d", d->version / 100, d->version % 100);
  icon_text_change(s, win, ICON_INFO_VERS);
  icon_text_change(d->date, win, ICON_INFO_DATE);
  icon_state_change((d->flags >> CAN_SEND) & 1, win, ICON_INFO_SEND);
  icon_state_change((d->flags >> CAN_RECEIVE) & 1, win, ICON_INFO_RECV);
  icon_state_change((d->flags >> REQUIRES_BUFFERING) & 1, win, ICON_INFO_BUFF);
  icon_state_change((d->flags >> CAN_RECEIVE_COMMAND) & 1, win, ICON_INFO_RXUN);
  // position window next to parent
  int b[9];
  b[0] = parent;
  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  int y = blk[4] - blk[2];
  int x = blk[3] - blk[1];
  blk[1] = b[3] + 40;
  blk[2] = b[4] - y;
  blk[3] = b[3] + 40 + x;
  blk[4] = b[4];
  blk[7] = -1;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_OpenWindow, &regs, &regs);
}


/*
 * driver_info_window
 * ------------------
 * SELECT opens window, ADJUST toggles open/close
 * window is updated before opening, to the right of the drivers window
 */
static void driver_info_window(int mouse, int window, int icon, driver_t *d)
{
  _kernel_swi_regs regs;
  int blk[9];

  regs.r[1] = (int)blk;
  blk[0] = ro.handle[WIN_DRV_INFO];
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  int open = (blk[8] >> 16) & 1;

  if((mouse == MOUSE_SELECT) || !open || (icon != redeye_icon))
  {
    if(redeye_icon)
      icon_sprite_change(window, redeye_icon, "Sinfo");
    icon_sprite_change(window, icon, "Sredeye");
    redeye_icon = icon;
    update_info_icons(blk, window, d);
  }
  else if((mouse == MOUSE_ADJUST) && open)
  {
    if(redeye_icon)
      icon_sprite_change(window, redeye_icon, "Sinfo");
    _kernel_swi(Wimp_CloseWindow, &regs, &regs);
    redeye_icon = 0;
  }
}


/*
 * set_drivers_window_extent
 * -------------------------
 * n - number of drivers to display, 4 to 32
 */
static void set_drivers_window_extent(int n)
{
  static char buff[DRIVER_LIMIT-4][24],
              src[DRIVER_LIMIT-4][4],
              dst[DRIVER_LIMIT-4][4]; // additional indirected text
  _kernel_swi_regs regs;
  int blk[2070];
  blk[0] = ro.handle[WIN_DRIVERS];
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
  int icons = blk[22];

  if(n < 4) n = 4;
  else if(n > 32) n = 32;

  int rows = icons / 8;
  int shift = 64 * rows;
  while(n > rows)
  {
    int *icon = &blk[23]; // address of 1st row
    // create a driver icon row
    int i,j;
    for(i=0; i<8; i++)
    {
      regs.r[0] = icons;
      regs.r[1] = (int)blk;
      blk[0] = ro.handle[WIN_DRIVERS];
      for(j=0; j<8; j++)
        blk[j+1] = icon[j];
      blk[2] -= shift;
      blk[4] -= shift;
      if(i == ICON_DRV_NAME)
        blk[6] = (int)&buff[rows-4];
      else if(i == ICON_SRC_NUM)
        blk[6] = (int)&src[rows-4];
      else if(i == ICON_DST_NUM)
        blk[6] = (int)&dst[rows-4];
      _kernel_swi(Wimp_CreateIcon, &regs, &regs);
      icons++;
      icon += 8;
    }
    shift += 64;
    rows++;
  }
  // resize window
  regs.r[0] = ro.handle[WIN_DRIVERS];
  regs.r[1] = (int)&blk[11];
  blk[12] = blk[14] - 92 - (n * 64); // y min
  _kernel_swi(Wimp_SetExtent, &regs, &regs);
  // if it's open, re-open it
  regs.r[1] = (int)blk;
  blk[0] = ro.handle[WIN_DRIVERS];
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  if(blk[8] & (1<<16))
  {
    blk[7] = ro.handle[WIN_DRV_PANE];
    _kernel_swi(Wimp_OpenWindow, &regs, &regs);
  }
}


/*
 * name_or_product
 * ---------------
 * Returns a pointer to either the driver name or if it's available,
 * the associated product string.
 * This is only needed for the USB driver module because it's nicer to
 * display the remote device name rather than the USB port number.
 */
char *name_or_product(driver_t *dr)
{
  char *norp = dr->name;
    if(dr->product != 0)
      if(dr->product[0] != 0)
        norp = dr->product;

  return norp;
}


/*
 * init_driver_info
 * ---------------
 */
void init_driver_info(void)
{
  _kernel_swi_regs regs;
  int i,d,n;
  driver_t *dr;

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

  // get information for all installed drivers
  n = 0;
  for(d=1; d<=DRIVER_LIMIT; d++)
  {
    regs.r[1] = d;
    if(!_kernel_swi(MIDISupport_DriverInfo, &regs, &regs))
    {
      dr = &driver[n];
      dr->number = d;
      dr->name = (char *)regs.r[0];
      dr->version = regs.r[1];
      dr->date = (char *)regs.r[2];
      dr->flags = regs.r[3];
      dr->map = (unsigned int *)regs.r[4];
      dr->limit = regs.r[5];
      dr->product = (char *)regs.r[6];
      dr->module = -1; // set to 'no associated module' until modules are scanned
      n++;
    }
  }
  drivers = n;

  // update drivers window
  set_drivers_window_extent(n);
  int win = ro.handle[WIN_DRIVERS];
  for(i=0; i<n; i++)
  {
    dr = &driver[i];
    icon_text_change(name_or_product(dr), win, ICON_DRV_NAME+(8*i));
    icon_disabled_change(!(dr->flags & (1<<CAN_SEND)), win, ICON_DST_ARROW+(8*i));
    icon_disabled_change(!(dr->flags & (1<<CAN_SEND)), win, ICON_DST_NUM+(8*i));
    icon_disabled_change(!(dr->flags & (1<<CAN_SEND)), win, ICON_DST_MENU+(8*i));
    icon_disabled_change(!(dr->flags & (1<<CAN_RECEIVE)), win, ICON_SRC_ARROW+(8*i));
    icon_disabled_change(!(dr->flags & (1<<CAN_RECEIVE)), win, ICON_SRC_NUM+(8*i));
    icon_disabled_change(!(dr->flags & (1<<CAN_RECEIVE)), win, ICON_SRC_MENU+(8*i));
  }
  update_connection_totals();

  const menu_item_t def_item = {0,-1,IC_DEF,"",NULL,0};

  // update source menu with drivers that can send
  senders = 0;
  for(i=d=0; d<n; d++)
  {
    dr = &driver[d];
    if(dr->flags & (1<<CAN_SEND))
    {
      snd[i] = d;
      sources_menu.item[i] = def_item;
      char *str = name_or_product(dr);
      sources_menu.item[i].text = str;
      sources_menu.item[i++].len = strlen(str) + 1;
    }
  }
  sources_menu.item[0].item_flags = 256;
  sources_menu.item[i-1].item_flags = 128;
  senders = i;

  // update destination menu with drivers that can receive
  receivers = 0;
  for(i=d=0; d<n; d++)
  {
    dr = &driver[d];
    if(dr->flags & (1<<CAN_RECEIVE))
    {
      rcv[i] = d;
      destinations_menu.item[i] = def_item;
      char *str = name_or_product(dr);
      destinations_menu.item[i].text = str;
      destinations_menu.item[i++].len = strlen(str) + 1;
    }
  }
  destinations_menu.item[0].item_flags = 256;
  destinations_menu.item[i-1].item_flags = 128;
  receivers = i;
}


/*
 * drivers_mouse_click
 * -------------------
 * Returns non-zero if handled here.
 */
int drivers_mouse_click(int *blk, int *icon, int state)
{
  if(blk[3] == ro.handle[WIN_DRIVERS])
  {
    if((blk[2] == MOUSE_SELECT) || (blk[2] == MOUSE_ADJUST))
    {
      ro.cur_menu_driver = blk[4] / 8;
      driver_t *d = &driver[blk[4] / 8];
      int i;
      switch (blk[4] & 7)
      {
        case ICON_DRV_INFO:
          driver_info_window(blk[2], blk[3], blk[4], d);
          break;

        case ICON_SRC_MENU:
          for(i=0; i<senders; i++)
            if(*driver[snd[i]].map & (1<<(driver[blk[4] / 8].number - 1)))
              sources_menu.item[i].item_flags |= IT_TICKED;
            else
              sources_menu.item[i].item_flags &= ~IT_TICKED;
          open_menu(blk[0] - 64, blk[1], MENU_SOURCES, (int)&sources_menu);
          icon_background_change(blk[3], blk[4] + 3, 12);
          cream_driver_icon = blk[4] + 3;
          break;

        case ICON_DST_MENU:
          for(i=0; i<receivers; i++)
            if(*d->map & (1<<(driver[rcv[i]].number - 1)))
              destinations_menu.item[i].item_flags |= IT_TICKED;
            else
              destinations_menu.item[i].item_flags &= ~IT_TICKED;
          open_menu(blk[0] - 64, blk[1], MENU_DESTINATIONS, (int)&destinations_menu);
          icon_background_change(blk[3], blk[4] - 4, 12);
          cream_driver_icon = blk[4] - 4;
          break;
/*
        case ICON_SRC_NUM:
        case ICON_DST_NUM:
          drag_start(blk[3], blk[4], blk[2]);
          break;*/
      }
    }
    else if(blk[2] == MOUSE_MENU)
      open_menu(blk[0] - 64, blk[1], MENU_DRIVERS, (int)&drivers_menu);

  }
  else
    return 0;

  return 1;
}


/*
 * drivers_menu_selection
 * ----------------------
 * blk = menu tree
 * b = pointer info
 */
int drivers_menu_selection(int *blk, int *b)
{
  int i;
  _kernel_swi_regs regs;

  switch(ro.cur_menu)
  {
    case MENU_DRIVERS:
      switch(blk[0])
      {
        case 0: // panic
          if(blk[1] == -1)
            action_panic();
          else
          {
            ro.panic ^= (1 << blk[1]);
            for(i=0; i<PANIC_OPTIONS; i++)
              panic_menu.item[i].item_flags = (panic_menu.item[i].item_flags & ~IT_TICKED) | ((ro.panic >> i) & 1);
          }
          break;

        case 1: // hotspots
          ro.hotspots ^= 1;
          drivers_menu.item[1].item_flags = (drivers_menu.item[1].item_flags & ~IT_TICKED) | ro.hotspots;
          break;

        case 2: // modules
          open_window_pane(1, WIN_MODULES, blk, &regs);
          break;

        case 3: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Guide");
          break;
      }
      if(b[2] == MOUSE_ADJUST)
        open_menu(0, 0, ro.cur_menu, (int)&drivers_menu);
      break;

    case MENU_SOURCES:
      if(*driver[snd[blk[0]]].map & (1 << (driver[ro.cur_menu_driver].number - 1)))
      { // disconnect
        sources_menu.item[blk[0]].item_flags &= ~IT_TICKED;
        connect(CNT_REMOVE, driver[snd[blk[0]]].number, driver[ro.cur_menu_driver].number);
      }
      else
      { // connect
        sources_menu.item[blk[0]].item_flags |= IT_TICKED;
        connect(CNT_CONNECT, driver[snd[blk[0]]].number, driver[ro.cur_menu_driver].number);
      }
      update_connection_totals();

      if(b[2] == MOUSE_ADJUST)
        open_menu(0, 0, ro.cur_menu, (int)&sources_menu);
      break;

    case MENU_DESTINATIONS:
      if(*driver[ro.cur_menu_driver].map & (1 << (driver[rcv[blk[0]]].number - 1)))
      { // disconnect
        destinations_menu.item[blk[0]].item_flags &= ~IT_TICKED;
        connect(CNT_REMOVE, driver[ro.cur_menu_driver].number, driver[rcv[blk[0]]].number);
      }
      else
      { // connect
        destinations_menu.item[blk[0]].item_flags |= IT_TICKED;
        connect(CNT_CONNECT, driver[ro.cur_menu_driver].number, driver[rcv[blk[0]]].number);
      }
      update_connection_totals();

      if(b[2] == MOUSE_ADJUST)
        open_menu(0, 0, ro.cur_menu, (int)&destinations_menu);
      break;

    default:
      return 0;
  }
  return 1;
}


/*
 * create_popup
 * ------------
 * sources = popup type, 1 sources, 0 destinations
 * drv = driver number requesting the popup
 */
static void create_popup(int sources, int drv, int x, int y)
{
  _kernel_swi_regs regs;
  int i,j;
  char *name[DRIVER_LIMIT];

  for(i=j=0; i<drivers; i++)
  {
    driver_t *dr = &driver[i];
    int connected;
    if(sources)
      connected = (*dr->map >> (driver[drv].number - 1)) & 1;
    else // destinations
      connected = (*driver[drv].map >> (dr->number - 1)) & 1;

    if(connected)
      name[j++] = name_or_product(dr);
  }
  // j is the number of items in the popup, name[] are their names

  static char buff[DRIVER_LIMIT-1][24]; // additional indirected text
  int blk[280];
  blk[0] = ro.handle[WIN_CONNECTED];
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
  strcpy((char *)blk[19], (sources) ? "Sources" : "Destinations"); // window title
  int icons = blk[22]; // between 1 and 32
  // as there is 1 icon per row, icons = number of rows

  #define ICON_HEIGHT 44
  int shift = ICON_HEIGHT * icons;
  int *icon = &blk[23]; // icon address of 1st row
  // create a new driver icon row
  blk[0] = ro.handle[WIN_CONNECTED];
  for(i=0; i<8; i++)
    blk[i+1] = icon[i];
  regs.r[0] = 0;
  regs.r[1] = (int)blk;
  while(j > icons)
  {
    blk[2] -= shift;
    blk[4] -= shift;
    blk[6] = (int)&buff[icons - 1];
    _kernel_swi(Wimp_CreateIcon, &regs, &regs);
    icons++;
  }

  for(i=0; i<j; i++)
    icon_text_change(name[i], ro.handle[WIN_CONNECTED], i);

  // resize and open window
  regs.r[0] = ro.handle[WIN_CONNECTED];
  regs.r[1] = (int)&blk[11];
  blk[12] = blk[14] - (j * 44); // y min
  _kernel_swi(Wimp_SetExtent, &regs, &regs);

  regs.r[1] = (int)blk;
  blk[0] = ro.handle[WIN_CONNECTED];
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  if(sources)
  { // position to the left
    blk[1] = x - (blk[3] - blk[1]);
    blk[3] = x;
  }
  else // destinations
  { // position to the right
    blk[3] = x + (blk[3] - blk[1]);
    blk[1] = x;
  }
  blk[2] = y - (j * ICON_HEIGHT);
  blk[4] = y;
  _kernel_swi(Wimp_OpenWindow, &regs, &regs);
}


/*
 * hotspots
 * --------
 */
void hotspots(int *blk)
{
  _kernel_swi_regs regs;
  int i,j,b[5];
  static int src,dst; // current open popup
  if(!in_win)
    return;

  // check if popup needs opening or closing
  int wb[9];
  if(!ro.hotspots)
  {
    if(src > 0)
    {
      icon_sprite_change(ro.handle[WIN_DRIVERS], src, "Sbl_ra");
      regs.r[1] = (int)wb;
      wb[0] = ro.handle[WIN_CONNECTED];
      _kernel_swi(Wimp_CloseWindow, &regs, &regs);
      src = 0;
    }
    else if(dst > 0)
    {
      icon_sprite_change(ro.handle[WIN_DRIVERS], dst, "Sgn_ra");
      regs.r[1] = (int)wb;
      wb[0] = ro.handle[WIN_CONNECTED];
      _kernel_swi(Wimp_CloseWindow, &regs, &regs);
      dst = 0;
    }
    return;
  }

  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);
  blk[0] = ro.handle[WIN_DRIVERS];

  // get icon bounding box in screen co-ordinates
  int ib[10];
  regs.r[1] = (int)ib;
  ib[0] = ro.handle[WIN_DRIVERS];
  ib[1] = b[4];
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  regs.r[1] = (int)wb;
  wb[0] = ro.handle[WIN_DRIVERS];
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  ib[2] += wb[1];
  ib[3] += wb[4] - wb[6];
  ib[4] += wb[1];
  ib[5] += wb[4] - wb[6];

  i = b[4] / 8; // driver number

  if((b[4] & 7) == ICON_SRC_ARROW)
  {
    if(src == 0)
      for(j=0; j<drivers; j++)
        if(*driver[j].map & (1<<(driver[i].number - 1))) // driver has at least 1 source
        {
          icon_sprite_change(ro.handle[WIN_DRIVERS], b[4], "Sor_ra");
          create_popup(1, i, ib[2] -20, ib[5]);
          src = b[4];
          break;
        }
  }
  else if((b[4] & 7) == ICON_DST_ARROW)
  {
    if(dst == 0)
      if(*driver[i].map != 0) // driver has at least 1 destination
      {
        icon_sprite_change(ro.handle[WIN_DRIVERS], b[4], "Sor_ra");
        create_popup(0, i, ib[4] + 20, ib[5]);
        dst = b[4];
      }
  }
  else if(src > 0)
  {
    icon_sprite_change(ro.handle[WIN_DRIVERS], src, "Sbl_ra");
    regs.r[1] = (int)wb;
    wb[0] = ro.handle[WIN_CONNECTED];
    _kernel_swi(Wimp_CloseWindow, &regs, &regs);
    src = 0;
  }
  else if(dst > 0)
  {
    icon_sprite_change(ro.handle[WIN_DRIVERS], dst, "Sgn_ra");
    regs.r[1] = (int)wb;
    wb[0] = ro.handle[WIN_CONNECTED];
    _kernel_swi(Wimp_CloseWindow, &regs, &regs);
    dst = 0;
  }
}




