/*
  MidiMan - driver management for midi support

 main.c   riscos main entry

 craeted 23/12/21
*/

#include <time.h>
#include "wimp.h"
#include "main.h"
#include "lib.h"
#include "drivers.h"
#include "modules.h"
#include "routes.h"

// panic menu
menu_t panic_menu =
{
  "Panic Opts",NULL,10, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Notes Off", NULL, 9},
    {  0,-1,IC_DEF,"Ctrls Off", NULL, 9},
    {  0,-1,IC_DEF,"GM1 reset", NULL, 9},
    {  0,-1,IC_DEF,"GM2 reset", NULL, 9},
    {  0,-1,IC_DEF,"XG reset",  NULL, 8},
    {128,-1,IC_DEF,"GS reset",  NULL, 8}
  }
};

// iconbar menu
menu_t iconbar_menu =
{
  "MidiMan",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Info",        NULL,  4},
    {  0,-1,IC_DEF,"Apps...",     NULL,  7},
    {  0,-1,IC_DEF,"Drivers...",  NULL, 10},
    {  0,-1,IC_DEF,"Route Map...", NULL, 12},
    {  0,-1,IC_DEF,"Modules...",  NULL, 10},
    {  0,-1,IC_DEF,"Save Config", NULL, 11},
    {  0,(int)&panic_menu,IC_DEF,"Panic",NULL,5},
    {  0,-1,IC_DEF,"Help...",     NULL,  7},
    {128,-1,IC_DEF,"Quit",        NULL,  4}
  }
};
#define MENU_ICONBAR_ITEMS 9

// drivers menu
menu_t drivers_menu =
{
  "Drivers",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,(int)&panic_menu,IC_DEF,"Panic",NULL,5},
    {  2,-1,IC_DEF,"Hotspots",    NULL,  8},
    {  0,-1,IC_DEF,"Modules...",  NULL, 10},
    {128,-1,IC_DEF,"Help...",     NULL,  7}
  }
};

// route colours menus
extern char col_str[4][8];
static menu_t col0_menu =
{
  "Background",NULL,10, 7,2,7,0,0,44,0,
  {{0x184,-1,IC_DEF,col_str[0],NULL,8}}
};
static menu_t col1_menu =
{
  "When Idle",NULL,10, 7,2,7,0,0,44,0,
  {{0x184,-1,IC_DEF,col_str[1],NULL,8}}
};
static menu_t col2_menu =
{
  "When Active",NULL,11, 7,2,7,0,0,44,0,
  {{0x184,-1,IC_DEF,col_str[2],NULL,8}}
};
static menu_t col3_menu =
{
  "Heavy traffic",NULL,13, 7,2,7,0,0,44,0,
  {{0x184,-1,IC_DEF,col_str[3],NULL,8}}
};
static menu_t colours_menu =
{
  "Map colours",NULL,11, 7,2,7,0,0,44,0,
  {
    {256,(int)&col0_menu,IC_DEF,"Background",NULL,10},
    {  0,(int)&col1_menu,IC_DEF,"When Idle",NULL,10},
    {  0,(int)&col2_menu,IC_DEF,"When Active",NULL,6},
    {128,(int)&col3_menu,IC_DEF,"Heavy traffic",NULL,13}
  }
};

// route map menu
menu_t route_map_menu =
{
  "Route Map",NULL,9, 7,2,7,0,0,44,0,
  {
    {256,(int)&panic_menu,IC_DEF,"Panic",NULL,5},
    {  0,-1,IC_DEF,"Clear All",   NULL,  9},
    {  0,(int)&colours_menu,IC_DEF,"Map colours",NULL,13},
    {  2,-1,IC_DEF,"Save Config", NULL, 11},
    {  0,-1,IC_DEF,"Modules...",  NULL, 10},
    {128,-1,IC_DEF,"Help...",     NULL,  7}
  }
};


ro_glbs_t ro =
{
  0,     // task handle
  {0},   // window handles
  0,     // current menu
  0,     // current menu driver item
  0,     // flags
  (1<<PANIC_ALL_NOTES_OFF) | (1<<PANIC_GM1_RESET), // panic options
  1,     // hotspots flag
  NULL,  // pointer to route map
  {0}    // vdu vars
};

// swi numbers
#define MIDIPlay_Control   0x45353
#define USBMidi_Control    0xCDEC0

// player option bits
#define PLY_NOSYSEX  0
// usb option bits
#define USB_LIMIT_TX 0

extern int col[4];
extern char col_str[4][8];


/*
 * module_index
 * ------------
 * Returns the index in the modules array for a given name
 * or -1 if not found.
 */
int module_index(char *name)
{
  int i;
  for(i=0; i<modules; i++)
    if(strcmp(module[i].title, name) == 0)
      return i;
  return -1;
}


/*
 * driver_index
 * ------------
 * Returns the index in the drivers array for a given name
 * or -1 if not found.
 */
int driver_index(char *name)
{
  int i;
  for(i=0; i<drivers; i++)
    if(strcmp(driver[i].name, name) == 0)
      return i;

  return -1;
}


/*
 * action_panic
 * ------------
 * Sends the selected panic command to all drivers that can receive
 */
void action_panic(void)
{
  _kernel_swi_regs regs;
  int i, j, k;
  const int all_notes_off[] = {0x02007BB0}; // all notes off (basic channel)
  const int reset_ctrls[] = {0x020079B0}; // reset controllers (basic channel)
  const int gm1_on[] = {0x037F7EF0, 0x03F70109}; // GM level 1
  const int gm2_on[] = {0x037F7EF0, 0x03F70309}; // GM level 2
  const int xg_on[] = {0x031043F0, 0x0300004C, 0x03F7007E}; // Yamaha XG
  const int gs_on[] = {0x031041F0, 0x03401242, 0x03007F00, 0x0200F741}; // Roland GS
  const struct
  {
    int len;
    const int *msg;
  } panic_msg[] = {{1,all_notes_off},{1,reset_ctrls},{2,gm1_on},{2,gm2_on},{3,xg_on},{4,gs_on}};

  for(i=0; i<DRIVER_LIMIT; i++)
    if(driver[i].flags & ((1<<CAN_RECEIVE) | (1<<CAN_RECEIVE_COMMAND)))
      for(j=0; j<PANIC_OPTIONS; j++)
        if(ro.panic & (1<<j))
          for(k=0; k<panic_msg[j].len; k++)
          {
            regs.r[0] = 1; // insert a command
            regs.r[1] = i + 1;
            regs.r[2] = panic_msg[j].msg[k];
            _kernel_swi(MIDISupport_Insert, &regs, &regs);
          }
}


/*
 * update_player_opt_win
 * ---------------------
 */
void update_player_opt_win(void)
{
  _kernel_swi_regs regs;
  int i, win = ro.handle[WIN_PLAYER_OPT];

  regs.r[0] = 0;
  _kernel_swi(MIDIPlay_Control, &regs, &regs);
  icon_text_change(itoa(regs.r[0]), win, ICON_PLAYER_TEMPO);
  icon_text_change(itoa(regs.r[1]), win, ICON_PLAYER_PITCH);
  icon_state_change((regs.r[2] >> PLY_NOSYSEX) & 1, win, ICON_NO_SYSEX);

  i = module_index("MIDIPlay");
  if(i >= 0)
    icon_state_change((module[i].flags >> MOD_SAVE) & 1, win, ICON_PLAYER_SAVE);
}


/*
 * update_usb_opt_win
 * ------------------
 */
void update_usb_opt_win(void)
{
  _kernel_swi_regs regs;
  int i, win = ro.handle[WIN_USB_OPT];

  regs.r[0] = 0;
  _kernel_swi(USBMidi_Control, &regs, &regs);
  icon_state_change((regs.r[0] >> USB_LIMIT_TX) & 1, win, ICON_LIMIT_TX);

  i = module_index("USBMidi");
  if(i >= 0)
    icon_state_change((module[i].flags >> MOD_SAVE) & 1, win, ICON_USB_SAVE);
}


/*
 * clear_connections
 * -----------------
 * Clears all routing connections
 * There is no need to use the midi support swi to check each connection
 * because we are just clearing everything.
 */
void clear_connections(void)
{
  int i;

  for(i=0; i<drivers; i++)
    *driver[i].map = 0; // clears the map in the midi support module directly
  update_connection_totals();
}


/*
 * read_config
 * -----------
 * drv = >0 (driver number) when called from wimp message install
 *     = 0 when just setting save flags at startup
 *     = -1 when a config file is dropped on a window
 * returns non zero on error
 */
static int read_config(const char* filename, int drv)
{
  FILE *f;
  _kernel_swi_regs regs;
  char s[256], src[64], dst[64];
  enum {ST_NULL,ST_INSTALL,ST_CONNECT,ST_PLAYER,ST_USB} state = ST_NULL;
  int i, j, value, temp[4];

  if(drv == 0)
    for(i=0; i<4; i++)
      sprintf(col_str[i], "%d", col[i]); // load startup default colour strings


  if((f = fopen(filename,"r")) == 0)
    return -1;

  fgets(s, 255, f);
  if(strncmp(s, "# MIDI Support configuration", 28) != 0) // first line must be this
  {
    fclose(f);
    return -1;
  }

  if(drv == -1) // start with a clear map before loading a routing file
    clear_connections();

  while(fgets(s, 255, f))
  {
    *strchr(s, '\n') = 0; // replace line end with null

    if(s[0] != '#')
    {
      if(strncmp(s, "Install:", 8) == 0)
        state = ST_INSTALL;
      else if(strncmp(s, "Connect:", 8) == 0)
        state = ST_CONNECT;
      else if(strncmp(s, "PlayerOpts:", 11) == 0)
        state = ST_PLAYER;
      else if(strncmp(s, "USBOpts:", 8) == 0)
        state = ST_USB;
      else if(sscanf(s, "PanicOpts: %d", &value) == 1)
      {
        if(drv == 0)
        {
          ro.panic = value; // only do this at startup
          for(i=0; i<PANIC_OPTIONS; i++)
            if(ro.panic & (1<<i))
              panic_menu.item[i].item_flags = (panic_menu.item[i].item_flags & ~IT_TICKED) | ((ro.panic >> i) & 1);
        }
      }
      else if(sscanf(s, "Hotspots: %d", &value) == 1)
      {
        ro.hotspots = value;
        drivers_menu.item[1].item_flags = (drivers_menu.item[1].item_flags & ~IT_TICKED) | ro.hotspots;
      }
      else if(sscanf(s, "Colours: %d,%d,%d,%d", &temp[0], &temp[1], &temp[2], &temp[3]) == 4)
      {
        for(i=0; i<4; i++)
          sprintf(col_str[i], "%d", col[i] = temp[i]);
      }
      else
        switch(state)
        {
          case ST_NULL:
          case ST_INSTALL: // nothing to be done here
            break;

          case ST_CONNECT: // routing file loaded, or newly installed driver may need connecting
            if(drv == 0)
              break;
            if(sscanf(s, " %s to %s", src , dst) == 2)
            {
              int isrc = driver_index(src);
              int idst = driver_index(dst);
              if((isrc >= 0) && (idst >= 0))
                if((drv == -1) || (drv == driver[isrc].number) || (drv == driver[idst].number))
                  *driver[isrc].map |= (1 << idst);
            }
            break;

          case ST_PLAYER:
            if((i = driver_index("MIDIPlay")) == -1)
              break;
            if(((j = module_index("MIDIPlay")) != -1) && (drv == 0))
              module[j].flags |= (1<<MOD_SAVE);
            if(drv != driver[i].number)
              break;
            if(sscanf(s, " Tempo: %d", &value) == 1)
              regs.r[0] = 1;
            else if(sscanf(s, " Pitch: %d", &value) == 1)
              regs.r[0] = 2;
            else if(sscanf(s, " Options: %d", &value) == 1)
              regs.r[0] = 3;
            else
              break;
            if(regs.r[0] < 3)
              regs.r[1] = value;
            else
            {
              regs.r[1] = 3;
              regs.r[2] = value;
            }
            _kernel_swi(MIDIPlay_Control, &regs, &regs);
            break;

          case ST_USB:
            if((j = module_index("USBMidi")) == -1)
              break;
            if(drv == 0)
              module[j].flags |= (1<<MOD_SAVE);
            if((module[j].flags & (1<<MOD_LOADED)) == 0)
              break;

            if(sscanf(s, " Options: %d", &value) == 1)
              regs.r[0] = 1;
            else
              break;
            regs.r[1] = 1;
            regs.r[2] = value;
            _kernel_swi(USBMidi_Control, &regs, &regs);
            break;
        }
    }
  }
  fclose(f);
  update_connection_totals();
  activity(1); // map display update
  return 0;
}


/*
 * save_config
 * -----------
 */
void save_config(char *filename)
{
  FILE *f;
  _kernel_swi_regs regs;
  int i, src, dst;

  if((f = fopen(filename, "w")) != NULL)
  {
    time_t t = time(0);
    struct tm *p = localtime(&t); // (for gmt (utc) use gmtime)
    fprintf(f, "# MIDI Support configuration\n# %d/%d/%d %02d:%02d:%02d\n",
      p->tm_mday, p->tm_mon + 1, p->tm_year + 1900, p->tm_hour, p->tm_min, p->tm_sec);

    fprintf(f, "\nInstall:\n");
    for(i=0; i<modules; i++)
      if(module[i].flags & (1<<MOD_LOADED))
        fprintf(f, "  %s\n", module[i].filename);

    fprintf(f, "\nConnect:\n");
    for(src=0; src < drivers; src++)
      if(driver[src].flags != 0)
        for(dst=0; dst < drivers; dst++)
          if(driver[dst].flags != 0)
            if(*driver[src].map & (1 << dst))
              fprintf(f, "  %s to %s\n",  driver[src].name, driver[dst].name);

    fprintf(f, "\nColours:    %d,%d,%d,%d\n", col[0], col[1], col[2], col[3]);
    fprintf(f, "\nPanicOpts:  %d\n", ro.panic);
    fprintf(f, "\nHotspots:   %d\n", ro.hotspots);

    // save module options for those that require it

    i = module_index("MIDIPlay");
    if(i >= 0)
      if(module[i].flags & (1<<MOD_SAVE))
      {
        fprintf(f, "\nPlayerOpts:\n");
        regs.r[0] = 0;
        _kernel_swi(MIDIPlay_Control, &regs, &regs);
        fprintf(f, "  Tempo:    %d\n", regs.r[0]);
        fprintf(f, "  Pitch:    %d\n", regs.r[1]);
        fprintf(f, "  Options:  %d\n", regs.r[2]);
      }

    i = module_index("USBMidi");
    if(i >= 0)
      if(module[i].flags & (1<<MOD_SAVE))
      {
        fprintf(f, "\nUSBOpts:\n");
        regs.r[0] = 0;
        _kernel_swi(USBMidi_Control, &regs, &regs);
        fprintf(f, "  Options:  %d\n", regs.r[0]);
      }

    fclose(f);
  }
}


/*
 * read_menus
 * ----------
 * load menu text from Messages file if possible
 */
static void read_menus(void)
{
  static char menu_strings[1024]; // holds all menu strings
  char *s = menu_strings;

  s = msg_load_menu("iconbar_m",  s, &iconbar_menu);
  s = msg_load_menu("sources_m",  s, &sources_menu);
  s = msg_load_menu("destinations_m",  s, &destinations_menu);
  s = msg_load_menu("panic_m", s, &panic_menu);
  s = msg_load_menu("drivers_m", s, &drivers_menu);
  s = msg_load_menu("route_map_m", s ,&route_map_menu);
  s = msg_load_menu("colours_m", s ,&colours_menu);
  s = msg_load_menu("back_m", s ,&col0_menu);
  s = msg_load_menu("idle_m", s ,&col1_menu);
  s = msg_load_menu("active_m", s ,&col2_menu);
  s = msg_load_menu("heavy_m", s ,&col3_menu);

//  char p[32];
//  sprintf(p,"menu strings length = %d", s - menu_strings);
//  report_error(p,0);

  iconbar_menu.item[0].sub = ro.handle[WIN_PROG_INFO];
  route_map_menu.item[3].sub = ro.handle[WIN_SAVE];
}


/*
 * open_window_pane
 * ----------------
 * Opens a window and it's associated pane.
 * If front is set, opens in front, else requires wimp poll block for open window
 * and opens at current stack position
 */
void open_window_pane(int front, int win, int *blk, _kernel_swi_regs *r)
{
  if(front)
  {
    r->r[1] = (int)blk;
    blk[0] = ro.handle[win];
    _kernel_swi(Wimp_GetWindowState, r, r);
    blk[7] = -1; // bring to front
  }
  else
    _kernel_swi(Wimp_OpenWindow, r, r);

  blk[0] = ro.handle[win + 1];
  _kernel_swi(Wimp_OpenWindow, r, r);
  blk[0] = ro.handle[win];
  _kernel_swi(Wimp_GetWindowState, r, r);
  blk[7] = ro.handle[win + 1];
  _kernel_swi(Wimp_OpenWindow, r, r);
}


/*
 * mouse_click
 * -----------
 */
static void mouse_click(int blk[])
{
  _kernel_swi_regs regs;
  int i, b[10];

  b[0] = blk[3]; // window
  b[1] = blk[4]; // icon
  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  int state = (b[6] >> 21) & 1;

  // convert icon bounding box to screen co-ordinates
  int w[9];
  w[0] = blk[3];
  regs.r[1] = (int)w;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  b[2] += w[1];
  b[3] += w[4] - w[6];
  b[4] += w[1];
  b[5] += w[4] - w[6];

  // Iconbar
  if(blk[3] == ICONBAR)
  {
    switch(blk[2])
    {
      case MOUSE_MENU:
        open_menu(blk[0] - 64, (44*MENU_ICONBAR_ITEMS)+96, MENU_ICONBAR, (int)&iconbar_menu);
        break;

      case MOUSE_SELECT:
        open_window_pane(1, WIN_DRIVERS, blk, &regs);
        break;

      case MOUSE_ADJUST:
        open_window_pane(1, WIN_MODULES, blk, &regs);
        break;
    }
  }

  // player options
  else if(blk[3] == ro.handle[WIN_PLAYER_OPT])
  {
    if((blk[2] == MOUSE_SELECT) || (blk[2] == MOUSE_ADJUST))
    {
      switch(blk[4])
      {
        case ICON_PLAYER_SAVE:
          i = module_index("MIDIPlay");
          if(i >= 0)
            module[i].flags = (module[i].flags & ~(1<<MOD_SAVE)) | (state << MOD_SAVE);
          break;

        case ICON_NO_SYSEX:
          regs.r[0] = 3;
          regs.r[1] = 1 << PLY_NOSYSEX;
          regs.r[2] = state << PLY_NOSYSEX;
          _kernel_swi(MIDIPlay_Control, &regs, &regs);
          break;
      }
    }
  }

  // usb options
  else if(blk[3] == ro.handle[WIN_USB_OPT])
  {
    if((blk[2] == MOUSE_SELECT) || (blk[2] == MOUSE_ADJUST))
    {
      switch(blk[4])
      {
        case ICON_USB_SAVE:
          i = module_index("USBMidi");
          if(i >= 0)
            module[i].flags = (module[i].flags & ~(1<<MOD_SAVE)) | (state << MOD_SAVE);
          break;

        case ICON_LIMIT_TX:
          regs.r[0] = 1;
          regs.r[1] = 1 << USB_LIMIT_TX;
          regs.r[2] = state << USB_LIMIT_TX;
          _kernel_swi(USBMidi_Control, &regs, &regs);
          break;
      }
    }
  }

  // Driver routing window
  else if(drivers_mouse_click(blk, b, state));

  // Modules window
  else if(modules_mouse_click(blk, state));

  // Route Map window
  else if(route_map_mouse_click(blk, state));
}


/*
 * key_press
 * ---------
 */
void key_press(int *blk)
{
  _kernel_swi_regs regs;
  int key = blk[6];
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);

  char *p = (char *)(blk[7]);
  int n = atoi(p);

  // player options
  if(blk[0] == ro.handle[WIN_PLAYER_OPT])
  {
    if(key == '\r')
    {
      switch(blk[1])
      {
        case ICON_PLAYER_TEMPO:
          if(n < 10) n = 10;
          else if(n > 1000) n = 1000;
          regs.r[0] = 1;
          regs.r[1] = n;
          _kernel_swi(MIDIPlay_Control, &regs, &regs);
          break;

        case ICON_PLAYER_PITCH:
          if(n < -12) n = -12;
          else if(n > 12) n = 12;
          regs.r[0] = 2;
          regs.r[1] = n;
          _kernel_swi(MIDIPlay_Control, &regs, &regs);
          break;
      }
    }
  }

  regs.r[0] = key;
}

/*
 * menu_selection
 * --------------
 */
static void menu_selection(int blk[])
{
  int i, b[5];
  _kernel_swi_regs regs;
  char s[128];

  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);

  switch(ro.cur_menu)
  {
    case MENU_ICONBAR:
      switch(blk[0])
      {
        case 1: // apps folder
          sprintf(s, "Filer_OpenDir <MidiSupport$Dir>.Apps %d %d", b[0], b[1]);
          _kernel_oscli(s);
          break;

        case 2: // drivers
          open_window_pane(1, WIN_DRIVERS, blk, &regs);
          break;

        case 3: // Route Map
          update_routes();
          open_window_pane(1, WIN_ROUTE_MAP, blk, &regs);
          break;

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

        case 5: // save config
          save_config("<MIDISupport$Dir>.!Config");
          break;

        case 6: // 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 7: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Guide");
          break;

        case 8: // quit
          ro.flags |= (1<<QUIT);
          break;
      }
      if(b[2] == MOUSE_ADJUST)
        open_menu(0, 0, ro.cur_menu, (int)&iconbar_menu);
      break;

    default:
      if(drivers_menu_selection(blk, b));

      else if(route_map_menu_selection(blk, b));

      break;
  }

  if(b[2] != MOUSE_ADJUST)
    if(cream_driver_icon)
    {
      icon_background_change(ro.handle[WIN_DRIVERS], cream_driver_icon, 1);
      cream_driver_icon = 0;
    }
}


/*
 * msg_help
 * --------
 * Supplies help text. Requires the HelpRequest message block and current menu tag.
 */
static void msg_help(int *b, int cur_menu)
{
  _kernel_swi_regs regs;
  int blk[10], ok = 0;

  if(b[8] == ro.handle[WIN_DRIVERS])  // Drivers window
  {
    if((ok = msg_lookup("drivers_wh", b[9] & 7, (char *)&b[5])) == 0)
      ok = msg_lookup("drivers_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_DRV_INFO])  // Drivers Info window
  {
    if((ok = msg_lookup("drvinfo_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("drvinfo_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_MODULES])  // Modules window
  {
    if((ok = msg_lookup("modules_wh", b[9] % 3, (char *)&b[5])) == 0)
      ok = msg_lookup("modules_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_ROUTE_MAP])  // Route Map window
  {
    if(b[9] == ICON_ROUTE_MAP)
      ok = msg_lookup("route_map_wh", ICON_ROUTE_MAP, (char *)&b[5]);
    else
      ok = msg_lookup("route_map_wh",  b[9] & 1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_PROG_INFO])  // Program Info window
  {
    if((ok = msg_lookup("info_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("info_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_PLAYER_OPT])  // Player Options window
  {
    if((ok = msg_lookup("playeropt_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("player_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_USB_OPT])  // USB Options window
  {
    if((ok = msg_lookup("usbopt_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("usb_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_SAVE])  // Save routes window
  {
    if((ok = msg_lookup("save_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("save_wh", -1, (char *)&b[5]);
  }
  // unknown window, check for a menu
  else if(b[8] != -2)
  {
    regs.r[0] = 1;
    regs.r[1] = (int)blk;
    regs.r[2] = b[8]; // window handle
    regs.r[3] = b[9]; // icon handle
    _kernel_swi(Wimp_GetMenuState, &regs, &regs);
    switch(cur_menu)
    {
      case MENU_ICONBAR:
        ok = msg_lookup("iconbar_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
          if(blk[0] == 6)
            ok = msg_lookup("panic_mh", blk[1], (char *)&b[5]);
        break;

      case MENU_SOURCES:
        ok = msg_lookup("sources_mh", blk[0], (char *)&b[5]);
        break;

      case MENU_DESTINATIONS:
        ok = msg_lookup("destinations_mh", blk[0], (char *)&b[5]);
        break;

      case MENU_DRIVERS:
        ok = msg_lookup("drivers_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
          if(blk[0] == 1)
            ok = msg_lookup("panic_mh", blk[1], (char *)&b[5]);
        break;

      case MENU_ROUTE_MAP:
        ok = msg_lookup("route_map_mh", blk[0], (char *)&b[5]);
        if(ok && (blk[1] != -1))
        {
          if(blk[0] == 0)
            ok = msg_lookup("panic_mh", blk[1], (char *)&b[5]);
          else if(blk[0] == 2)
            ok = msg_lookup("colours_mh", blk[1], (char *)&b[5]);
        }
        break;
    }
  }

  if(!ok)
    return; // no help available

  // send help text to Help application
  regs.r[0] = 17;     // User message
  regs.r[1] = (int)b;
  regs.r[2] = b[1];   // sender task handle
  b[0] = 256; // buffer length
  b[3] = b[2];
  b[4] = MESSAGE_HELPREPLY;
  _kernel_swi(Wimp_SendMessage, &regs, &regs);
}


/*
 * wimp_msg
 * --------
 * Handles all messages
 */
static void wimp_msg(int blk[], int msg)
{
  _kernel_swi_regs regs;

  switch (blk[4]) // message action
  {
    case MESSAGE_QUIT:
      ro.flags |= (1<<QUIT);
      break;

    case MESSAGE_DATALOAD: // file dropped on window or Iconbar icon
      switch (blk[10])
      {
        case TEXT: // load connections config
          read_config((char *)&blk[11], -1);
          break;

        default: return;
      }

      if(msg == 18)
      {
        regs.r[0] = 19;                // user message acknowledge
        regs.r[1] = (int)blk;
        regs.r[2] = blk[1];            // task handle of sender
        blk[3] = blk[2];               // my ref
        blk[4] = MESSAGE_DATALOADACK;  // action
        _kernel_swi(Wimp_SendMessage, &regs, &regs);
      }
      break;

    case MESSAGE_DATASAVEACK: // save config
      save_config((char *)&blk[11]);
      break;

    case MESSAGE_HELPREQUEST:
      msg_help(blk, ro.cur_menu);
      break;

    case Message_MenusDeleted:
      if(cream_driver_icon)
      {
        icon_background_change(ro.handle[WIN_DRIVERS], cream_driver_icon, 1);
        cream_driver_icon = 0;
      }
      break;

    // midi support messages

    case MESSAGE_INSTALL:
    case MESSAGE_REMOVE:
      init_driver_info();
      init_module_info();
      if(blk[4] == MESSAGE_INSTALL)
        read_config("<MIDISupport$Dir>.!Config", blk[5]); // reconnect newly installed module
      break;

    case MESSAGE_CONNECT:
      update_connection_totals(); // a new connection has been made
      break;
  }
}


/*
 * loadtemplates
 * -------------
 * returns 0 if ok or error number
 */
static int loadtemplates(char * name)
{
  const struct template_s
  {
    const char *name;
    int spr; // true if sprites required
    int mem; // true if needs extra memory
  } tmpl[NUM_WINDOWS] =
  { {"Connected",0,2048},
    {"Drivers",1,8192},
    {"DrvPane",0,0},
    {"DrvrInfo",1,0},
    {"Modules",1,4096},
    {"ModPane",0,0},
    {"ProgInfo",0,0},
    {"PlayerOpt",0,0},
    {"USBOpt",0,0},
    {"RouteMap",1,0},
    {"RoutePane",0,0},
    {"Save",0,0}
  };
  _kernel_swi_regs regs;
  int *sprites, i, err = 0;

  regs.r[0] = READ_CATINFO;
  regs.r[1] = (int)name;
  _kernel_swi(OS_FILE,&regs,&regs);
  if(regs.r[0] != IS_FILE)
    err = 10; // Cannot open templates file
  else
  {
    regs.r[1] = (int)name;
    _kernel_swi(Wimp_OpenTemplate, &regs, &regs);

    if((sprites = loadsprites(APP_DIR".Sprites")) == 0)
      err = 11; // Cannot open Sprites file
    else
      for(i=0; i<NUM_WINDOWS; i++)
        if((ro.handle[i] = loadtemplate(tmpl[i].name, (tmpl[i].spr) ? sprites : 0, tmpl[i].mem)) == 0)
        {
          err = 12 + i; // Cannot find template
          break;
        }
    _kernel_swi(Wimp_CloseTemplate, &regs, &regs);
  }

  return err;
}


/*
 * terminate_app
 * -------------
 */
static void terminate_app(void)
{
  _kernel_swi_regs regs;

  if(ro.flags & (1<<MESSAGES_OPEN))
  {
//    if(ro.flags & (1<<TEMPLATES_OK))
//    {
//    }
    msg_close();
  }

  regs.r[0] = ro.task_handle;
  regs.r[1] = *(int *)"TASK";
  _kernel_swi(Wimp_CloseDown, &regs, &regs);
}


/*
 * main
 * ----
 * Main Wimp Poll loop.
 */
int main(int argc, char *argv[])
{
  _kernel_swi_regs regs;
  int blk[64];
  int msglist[] = {0};
  int handle = 0;
  int err = 0;

  // exit if we are already running
  regs.r[0] = 0;
  while((regs.r[0] >= 0) && !handle)
  {
    regs.r[1] = (int)blk;
    regs.r[2] = 4*sizeof(int);
    _kernel_swi(TaskManager_EnumerateTasks, &regs, &regs);
    if(strcmp((char *)blk[1], APP_NAME) == 0)
      return 0;
  }

  // read command line options
  int i = 0;
  while(++i < argc)
    if(argv[i][0] == '-') // option
      switch (argv[i][1])
      {
        case 'c': // centralise windows on startup
          ro.flags |= (1<<CENTRALISE);
          break;
      }

  regs.r[0] = 350;
  regs.r[1] = *(int *)"TASK";
  regs.r[2] = (int)APP_NAME;
  regs.r[3] = (int)msglist;
  _kernel_swi(Wimp_Initialise, &regs, &regs);
  ro.task_handle = regs.r[1];

  /* create iconbar icon */
  blk[0] = -1; // r/h icon
  blk[1] = 0;
  blk[2] = 0;
  blk[3] = 68;
  blk[4] = 68;
  blk[5] = 0x0000301a;
  strcpy((char *)&blk[6], "!"APP_SPRITE);
  regs.r[0] = 0;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_CreateIcon, &regs, &regs);

  atexit(terminate_app);

  if(!msg_open(RESOURCES_DIR".Messages"))
    report_error("Cannot open messages file", 1);
  else
  {
    ro.flags |= (1<<MESSAGES_OPEN);
    read_vdu_vars();
    if((err = loadtemplates(RESOURCES_DIR".Templates")) != 0)
      report_error_number(err, 1);
    else
    {
      ro.flags |= (1<<TEMPLATES_OK);
      read_menus();
      init_driver_info();
      init_module_info();
      read_config("<MIDISupport$Dir>.!Config", 0); // sets MOD_SAVE flags

      while (!(ro.flags & (1<<QUIT)))
      {
        _kernel_swi(OS_ReadMonotonicTime, &regs, &regs);
        regs.r[2] = regs.r[0] + 2;
        regs.r[0] = 0; // poll mask
        regs.r[1] = (int)blk;
        _kernel_swi(Wimp_PollIdle, &regs, &regs);
        switch (regs.r[0]) // result
        {
          case 0: // null reason
            hotspots(blk);
            activity(0);
            break;

          case 1: // redraw window
            break;

          case 2: // open window
            if(blk[0] == ro.handle[WIN_DRIVERS])
              open_window_pane(0, WIN_DRIVERS, blk, &regs);
            else if(blk[0] == ro.handle[WIN_MODULES])
              open_window_pane(0, WIN_MODULES, blk, &regs);
            else if(blk[0] == ro.handle[WIN_ROUTE_MAP])
              open_window_pane(0, WIN_ROUTE_MAP, blk, &regs);
            else
              _kernel_swi(Wimp_OpenWindow, &regs, &regs);
            break;

          case 3: //close window
            _kernel_swi(Wimp_CloseWindow, &regs, &regs);
            if(blk[0] == ro.handle[WIN_DRIVERS])
            {
              blk[0] = ro.handle[WIN_DRV_PANE];
              _kernel_swi(Wimp_CloseWindow, &regs, &regs);
            }
            else if(blk[0] == ro.handle[WIN_MODULES])
            {
              blk[0] = ro.handle[WIN_MOD_PANE];
              _kernel_swi(Wimp_CloseWindow, &regs, &regs);
            }
            else if(blk[0] == ro.handle[WIN_ROUTE_MAP])
            {
              blk[0] = ro.handle[WIN_ROUTE_PANE];
              _kernel_swi(Wimp_CloseWindow, &regs, &regs);
            }
            else if(blk[0] == ro.handle[WIN_DRV_INFO])
            {
              if(redeye_icon != 0)
              {
                icon_sprite_change(ro.handle[WIN_DRIVERS], redeye_icon, "Sinfo");
                redeye_icon = 0;
              }
            }
            break;

          case 4: // mouse leaving window
          case 5: // mouse entering window
            if(blk[0] == ro.handle[WIN_DRIVERS])
              in_win = (regs.r[0] == 5);
              break;

          case 6: mouse_click(blk);    break;
          case 7: drag_return(); break;
          case 8: key_press(blk); break;
          case 9: menu_selection(blk); break;
          case 17:
          case 18:
          case 19: wimp_msg(blk, regs.r[0]); break;
        }
      }
    }
  }

  return 0;
}

