/*
  !MidiKey
  Provides a keyboard note entry and nonitors channel activity.

 main.c   riscos main entry
 created 23/12/21
*/

#include "wimp.h"
#include "lib.h"
#include "midi_spec.h"
#include "main.h"
#include "kbd_dsplay.h"
#include "channel.h"

// iconbar menu
static menu_t iconbar_menu =
{
  "MidiKey",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Info",       NULL,  4},
    {  0,-1,IC_DEF,"Keyboard...",NULL, 11},
    {  0,-1,IC_DEF,"Channels...",NULL, 11},
    {  0,-1,IC_DEF,"Help...",    NULL,  7},
    {128,-1,IC_DEF,"Quit",       NULL,  4}
  }
};
#define MENU_ICONBAR_ITEMS 5

// keyboard menu
static menu_t keyboard_menu =
{
  "Keyboard",NULL,8, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Key highlight timer",NULL,19},
    {  0,-1,IC_DEF,"Send keys to Editor",NULL,19},
    {  0,-1,IC_DEF,"Channels...",        NULL,11},
    {  0,-1,IC_DEF,"Help...",            NULL, 7},
    {128,-1,IC_DEF,"Quit",               NULL, 4}
  }
};

// monitor menu
static menu_t monitor_menu =
{
  "Monitor",NULL,7, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"None",       NULL,  4},
    {  0,-1,IC_DEF,"Melodic",    NULL,  7},
    {  2,-1,IC_DEF,"Percussion", NULL, 10},
    {  0,-1,IC_DEF,"Channel 1",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 2",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 3",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 4",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 5",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 6",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 7",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 8",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 9",  NULL,  9},
    {  0,-1,IC_DEF,"Channel 10", NULL, 10},
    {  0,-1,IC_DEF,"Channel 11", NULL, 10},
    {  0,-1,IC_DEF,"Channel 12", NULL, 10},
    {  0,-1,IC_DEF,"Channel 13", NULL, 10},
    {  0,-1,IC_DEF,"Channel 14", NULL, 10},
    {  0,-1,IC_DEF,"Channel 15", NULL, 10},
    {128,-1,IC_DEF,"Channel 16", NULL, 10}
  }
};

// channel menu
menu_t channel_menu =
{
  "Channel ??",NULL,9, 7,2,7,0,0,44,0,
  {
    {256,-1,IC_DEF,"Hold Pedal",      NULL, 10},
    {  0,-1,IC_DEF,"Portamento",      NULL, 10},
    {  0,-1,IC_DEF,"Sustenuto Pedal", NULL, 15},
    {  0,-1,IC_DEF,"Soft Pedal",      NULL, 10},
    {  0,-1,IC_DEF,"Legato Pedal",    NULL, 12},
    {  2,-1,IC_DEF,"Hold Pedal 2",    NULL, 12},
    {128,-1,IC_DEF,"Reset display",   NULL, 13}
  }
};

ro_glbs_t ro =
{
  0,     // task handle
  {0},   // window handles
  0,     // current menu
  0,     // flags
  {
    -1,  // window that has caret
    -1   // icon that has caret
  },
  {
    0,   // currently selected slider window
    0,   // currently selected slider icon
    { // slider definitions
      {  0,16383, HORIZONTAL | SLIDER_CONTROL}  // SLDR_CHN_VOL,  channel volume, also expression & pitch wheel
    }
  }
};


void create_driver(void);
void remove_driver(void);
void key_press(int *blk);


/*
 * 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("keyboard_m", s, &keyboard_menu);
  s = msg_load_menu("monitor_m",  s, &monitor_menu);
  s = msg_load_menu("channel_m",  s, &channel_menu);

  iconbar_menu.item[0].sub = ro.handle[WIN_INFO];
}


/* update_monitor_selection
 * ------------------------
 */
static void update_monitor_selection(void)
{
  int i;
  for(i=0; i<NUM_MIDI_CHANS; i++)
  {
    if( midi.flags & (1 << (i + MONITOR_CHAN)))
      monitor_menu.item[i+3].item_flags |= IT_TICKED;
    else
      monitor_menu.item[i+3].item_flags &= ~IT_TICKED;
  }
  int win = ro.handle[WIN_KBD];
  if((midi.flags & (0xffff << MONITOR_CHAN)) == (0xffff << MONITOR_CHAN))
    icon_text_change("All channels", win, ICON_MON_CHANS);
  else if((midi.flags & (0xffff << MONITOR_CHAN)) == (0x0200 << MONITOR_CHAN))
    icon_text_change("Percussion channel", win, ICON_MON_CHANS);
  else if((midi.flags & (0xffff << MONITOR_CHAN)) == (0xfdff << MONITOR_CHAN))
    icon_text_change("All Melodic channels", win, ICON_MON_CHANS);
  else if((midi.flags & (0xffff << MONITOR_CHAN)) == 0)
    icon_text_change("Off", win, ICON_MON_CHANS);
  else
    icon_text_change("Selected channels", win, ICON_MON_CHANS);
}


/*
 * update_kbd_controls
 * -------------------
 */
void update_kbd_controls(void)
{
  int win = ro.handle[WIN_KBD];
  icon_text_change(itoa(midi.out.channel + 1), win, ICON_OUT_CHAN_VAL);
  icon_text_change(itoa(midi.out.velocity), win, ICON_OUT_VELO_VAL);
  icon_text_change(itoa(midi.out.bank_hi), win, ICON_BNK_HI_VAL);
  icon_text_change(itoa(midi.out.bank_lo), win, ICON_BNK_LO_VAL);
  icon_text_change(itoa(midi.out.program), win, ICON_BNK_PRG_VAL);
  keyboard_menu.item[0].item_flags = (keyboard_menu.item[0].item_flags & ~IT_TICKED) | ((midi.flags >> KEY_TIMER) & 1);
  keyboard_menu.item[1].item_flags = (keyboard_menu.item[1].item_flags & ~IT_TICKED) | ((midi.flags >> KBD_EDITOR) & 1);
  update_monitor_selection();
  icon_text_change(itoa(midi.shift), win, ICON_SHIFT_VAL);
}


/*
 * slider_control
 * --------------
 * Handles dragging of slider controls
 */
static void slider_control(int blk[])
{
  if(!ro.slider.icon)
    return;

  static int delay;
  if(++delay < 10) // no need to action every poll idle
    return;
  delay = 0;

  _kernel_swi_regs regs;

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

  if(blk[2] == 0) // button released
  {
    icon_state_change(0, ro.slider.window, ro.slider.icon);
    ro.slider.icon = 0;
    return;
  }

  chn_slider_control(blk);
}


/*
 * mouse_click
 * -----------
 */
static void mouse_click(int blk[])
{
  _kernel_swi_regs regs;
  int 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;

  // this catches unentered values in a writable icon when the mouse is clicked away from them
  if(blk[2] != MOUSE_MENU)
    if((b[6] & (15<<12)) == (14<<12)) // writable, drag
      if(ro.caret.window >= 0)
        if((ro.caret.window != b[0]) || (ro.caret.icon != b[1]))
        {
          b[0] = ro.caret.window;
          b[1] = ro.caret.icon;
          b[6] = '\r';
          key_press(b);
          return;
        }

  // 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 keyboard window
        open_window(ro.handle[WIN_KBD]);
        regs.r[0] = ro.handle[WIN_KBD];
        regs.r[1] = -1;
        regs.r[2] = 0;
        regs.r[3] = 0;
        regs.r[4] = 40 | (1<<25); // invisible
        regs.r[5] = 0;
        _kernel_swi(Wimp_SetCaretPosition, &regs, &regs);
        break;

      case MOUSE_ADJUST: // open channels window
        open_window(ro.handle[WIN_CHANNELS]);
        break;
    }
  }

  // Keyboard window
  else if(blk[3] == ro.handle[WIN_KBD])
  {

    if(blk[4] == ICON_MON_MENU)
      open_menu(blk[0] - 64, blk[1], MENU_MONITOR, (int)&monitor_menu);

    else if(blk[2] == MOUSE_MENU)
      open_menu(blk[0] - 64, blk[1], MENU_KEYBOARD, (int)&keyboard_menu);

    else
    {
      int inc = 0;
      if(blk[2] == MOUSE_SELECT)
        inc = 1;
      else if (blk[2] == MOUSE_ADJUST)
        inc = -1;

      switch(blk[4])
      {
        case ICON_OUT_CHAN_DEC:
          inc = -inc;
        case ICON_OUT_CHAN_INC:
          midi.out.channel = (midi.out.channel + inc) & 15;
          icon_text_change(itoa(midi.out.channel + 1), blk[3], ICON_OUT_CHAN_VAL);
          break;

        case ICON_OUT_VELO_DEC:
          inc = -inc;
        case ICON_OUT_VELO_INC:
          midi.out.velocity = (midi.out.velocity + inc) & 127;
          icon_text_change(itoa(midi.out.velocity), blk[3], ICON_OUT_VELO_VAL);
          break;

        case ICON_BNK_HI_DEC:
          inc = -inc;
        case ICON_BNK_HI_INC:
          midi.out.bank_hi = (midi.out.bank_hi + inc) & 127;
          icon_text_change(itoa(midi.out.bank_hi), blk[3], ICON_BNK_HI_VAL);
          break;

        case ICON_BNK_LO_DEC:
          inc = -inc;
        case ICON_BNK_LO_INC:
          midi.out.bank_lo = (midi.out.bank_lo + inc) & 127;
          icon_text_change(itoa(midi.out.bank_lo), blk[3], ICON_BNK_LO_VAL);
          break;

        case ICON_BNK_PRG_DEC:
          inc = -inc;
        case ICON_BNK_PRG_INC:
          midi.out.program = (midi.out.program + inc) & 127;
          icon_text_change(itoa(midi.out.program), blk[3], ICON_BNK_PRG_VAL);
          break;

        case ICON_BNK_SEND:
          b[0] = ro.caret.window;
          b[1] = ro.caret.icon;
          b[6] = '\r';
          key_press(b);

          midi_out_port(CONTROL | midi.out.channel, BANK_hi, midi.out.bank_hi, 3);
          midi_out_port(CONTROL | midi.out.channel, BANK_lo, midi.out.bank_lo, 3);
          midi_out_port(PROGRAM | midi.out.channel, midi.out.program, 0, 2);
          break;

        case ICON_SHIFT_DEC:
          inc = -inc;
        case ICON_SHIFT_INC:
          midi.shift += inc;
          LIMIT(-2, midi.shift, 2);
          set_bottom_key();
          icon_text_change(itoa(midi.shift), blk[3], ICON_SHIFT_VAL);
          break;

      }
    }
  }

  else if(chn_mouse_click(blk, state));
}


/*
 * keyboard_entry
 * --------------
 * Processes mouse activity when over the keyboard window
 */
static void keyboard_entry(int *blk)
{
  _kernel_swi_regs regs;

  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetPointerInfo, &regs, &regs);
  int x = blk[0];
  int y = blk[1];
  int buttons = blk[2];
  int window = blk[3];
  blk[0] = ro.handle[WIN_KBD];
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  if(window == blk[0])
  {
    if((x >= blk[1]) && (x < blk[3]) && (y >= blk[2]) && (y < blk[4])) // pointer within visible area
    {
      x = x - blk[1] + blk[5];
      y = blk[4] - blk[6] - y - 1;
      static int prev_x, prev_y, prev_but;
      if(!(prev_but & MOUSE_SELECT) && (buttons & MOUSE_SELECT)) // left down
        KeyEntry(LBUTTONDOWN, x, y, buttons);
      else if(!(prev_but & MOUSE_ADJUST) && (buttons & MOUSE_ADJUST)) // right down
        KeyEntry(RBUTTONDOWN, x, y, buttons);
      else if((prev_but & MOUSE_SELECT) && !(buttons & MOUSE_SELECT)) // left up
        KeyEntry(LBUTTONUP, x, y, buttons);
      else if((prev_x != x) || (prev_y != y)) // moved
        KeyEntry(MOUSEMOVE, x, y, buttons);

      prev_x = x;
      prev_y = y;
      prev_but = buttons;
    }
  }

  key_scan(0); // scan the keyboard for keys used as note entry, (continues until all scanned keys are released)
}


/*
 * key_press
 * ---------
 */
void key_press(int *blk)
{
  _kernel_swi_regs regs;
  regs.r[1] = (int)blk;
  char *p = "";
  int n = 0;
  int key = blk[6];

  if((key == 0x18e) || (key == 0x18f) || (key == 0x18a) || (key == 0x19a)) // down, up, tab, shift+tab
    key = '\r';

  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  if((blk[6] & 0x101) == 0x101) // indirected text
  {
    p = (char *)(blk[7]);
    n = atoi(p);
  }

  ro.caret.window = blk[0];
  ro.caret.icon = blk[1];

  // keyboard
  if(blk[0] == ro.handle[WIN_KBD])
  {
    if(key == '\r')
      switch(blk[1])
      {
        case ICON_OUT_CHAN_VAL:
          LIMIT(1, n, 16);
          midi.out.channel = n - 1;
          icon_text_change(itoa(n), blk[0], ICON_OUT_CHAN_VAL);
          break;

        case ICON_OUT_VELO_VAL:
          LIMIT(0, n, 127);
          midi.out.velocity = n;
          icon_text_change(itoa(n), blk[0], ICON_OUT_VELO_VAL);
          break;

        case ICON_BNK_HI_VAL:
          LIMIT(0, n, 127);
          midi.out.bank_hi = n;
          icon_text_change(itoa(n), blk[0], ICON_BNK_HI_VAL);
          break;

        case ICON_BNK_LO_VAL:
          LIMIT(0, n, 127);
          midi.out.bank_lo = n;
          icon_text_change(itoa(n), blk[0], ICON_BNK_LO_VAL);
          break;

        case ICON_BNK_PRG_VAL:
          LIMIT(0, n, 127);
          midi.out.program = n;
          icon_text_change(itoa(n), blk[0], ICON_BNK_PRG_VAL);
          break;
      }

    if(blk[1] == -1) // key pressed within keyboard srea
      key_scan(1); // start scanning the keyboard
  }

  // channels
  else if(chn_key_press(blk, key, p));

  else
  {
    regs.r[0] = key;
    _kernel_swi(Wimp_ProcessKey, &regs, &regs);
  }
}


/*
 * lose_caret
 * ----------
 */
static void lose_caret(int blk[])
{
  ro.caret.window = -1;
  ro.caret.icon = -1;
  blk[6] = '\r';
  key_press(blk);
}


/*
 * gain_caret
 * ----------
 */
static void gain_caret(int blk[])
{
  ro.caret.window = blk[0];
  ro.caret.icon = blk[1];
}


/*
 * menu_selection
 * --------------
 */
static void menu_selection(int blk[])
{
  int b[5];
  _kernel_swi_regs regs;

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

  switch(ro.cur_menu)
  {
    case MENU_ICONBAR:
      switch(blk[0])
      {
        case 1: // open keyboard window
          open_window(ro.handle[WIN_KBD]);
          regs.r[0] = ro.handle[WIN_KBD];
          regs.r[1] = -1;
          regs.r[2] = 0;
          regs.r[3] = 0;
          regs.r[4] = 40 | (1<<25); // invisible
          regs.r[5] = 0;
          _kernel_swi(Wimp_SetCaretPosition, &regs, &regs);
          break;

        case 2: // open channels window
          open_window(ro.handle[WIN_CHANNELS]);
          break;

        case 3: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.guide/htm");
          break;

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

    case MENU_KEYBOARD:
      switch(blk[0])
      {
        case 0: // highlight timer
          midi.flags ^= (1 << KEY_TIMER);
          keyboard_menu.item[0].item_flags = (keyboard_menu.item[0].item_flags & ~IT_TICKED) | ((midi.flags >> KEY_TIMER) & 1);
          break;

        case 1: // keys drive editor
          midi.flags ^= (1 << KBD_EDITOR);
          keyboard_menu.item[1].item_flags = (keyboard_menu.item[1].item_flags & ~IT_TICKED) | ((midi.flags >> KBD_EDITOR) & 1);
          break;

        case 2: // open channels window
          open_window(ro.handle[WIN_CHANNELS]);
          break;

        case 3: // help
          _kernel_oscli("Filer_Run "RESOURCES_DIR".Docs.guide/htm");
          break;

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

    case MENU_MONITOR:
      switch(blk[0])
      {
        case 0: // none
          midi.flags &= ~MON_CHAN_MASK;
          break;

        case 1: // pitched, all channels except percussion
          midi.flags = (midi.flags & ~MON_CHAN_MASK) | (0xfdff << MONITOR_CHAN);
          break;

        case 2: // percussion channel only
          midi.flags = (midi.flags & ~MON_CHAN_MASK) | (0x0200 << MONITOR_CHAN);
          break;

        default: // toggle individual channel
          midi.flags ^= (1 << (blk[0] - 3 + MONITOR_CHAN));
          break;
      }
      clear_kbd_display();
      update_monitor_selection();
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&monitor_menu);
      break;

    case MENU_CHANNEL:
      switch(blk[0])
      {
        case 6: // reset channel display
          init_chan_window();
          break;

        default: // pedal switch's control
        {
          // toggle selected item and send to midi out
          int ch = midi.menu_chan;
          midi.chan[ch].switches ^= (1 << blk[0]);
          int on = (midi.chan[ch].switches >> blk[0]) & 1;
          channel_menu.item[blk[0]].item_flags = (channel_menu.item[blk[0]].item_flags & ~IT_TICKED) | on;
          midi_out_port(CONTROL + ch, HOLD_PEDAL_on + blk[0], on << 6, 3);

          // update channel number background highlight
          int col = 0x17; // normal background
          if(midi.chan[ch].switches & (1 << SWT_PORTAMENTO)) col = 0xe7; // orange
          else if(midi.chan[ch].switches & (1 << SWT_HOLD_PEDAL)) col = 0xa0; // green
          else if(midi.chan[ch].switches & (1 << SWT_SOFT_PEDAL)) col = 0x97; // yellow
          else if(midi.chan[ch].switches & (1 << SWT_SUSTENUTO_PEDAL)) col = 0xf7; // lt. blue
          else if(midi.chan[ch].switches & (1 << SWT_LEGATO_PEDAL)) col = 0xb0; // red
          else if(midi.chan[ch].switches & (1 << SWT_HOLD_2_PEDAL)) col = 0x80; // dk. blue
          icon_colour_change(col, ro.handle[WIN_CHANNELS], ICON_CHN_PRG_NAME + (CHN_ROW_ICONS * ch));
        }
        break;
      }
      if(b[2] == 1) // adjust
        open_menu(0, 0, ro.cur_menu, (int)&channel_menu);
      break;


  }
}

/*
 * 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_KBD])  // Keyboard window
  {
    if((ok = msg_lookup("keyboard_wh", b[9], (char *)&b[5])) == 0)
      ok = msg_lookup("keyboard_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_CHANNELS])  // Channels window
  {
    int icon = CHN_ROW_START + ((b[9] - CHN_ROW_START) % CHN_ROW_ICONS);
    if((ok = msg_lookup("channels_wh", icon, (char *)&b[5])) == 0)
      ok = msg_lookup("channels_wh", -1, (char *)&b[5]);
  }
  else if(b[8] == ro.handle[WIN_INFO])  // Info window
  {
    ok = msg_lookup("info_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]);
        break;

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

      case MENU_MONITOR:
        if((ok = msg_lookup("monitor_mh", blk[0], (char *)&b[5])) == 0)
          ok = msg_lookup("monitor_mh", -1, (char *)&b[5]);
        break;

      case MENU_CHANNEL:
        if((ok = msg_lookup("channel_mh", blk[0], (char *)&b[5])) == 0)
          ok = msg_lookup("channel_mh", -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)
{
  switch (blk[4]) // message action
  {
    case MESSAGE_QUIT:
      ro.flags |= (1<<QUIT);
      break;

    case MESSAGE_MODECHANGE:
      init_kbd_display();
      break;

    case MESSAGE_HELPREQUEST:
      msg_help(blk, ro.cur_menu);
      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
  } tmpl[NUM_WINDOWS] =
  { {"Info",0},
    {"Keyboard",1},
    {"Channels",1}
  };
  _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(RESOURCES_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)) == 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();
  }

  remove_driver();

  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 err = 0;

  // find out if we are already running, 2 instances are allowed
  regs.r[0] = 0;
  while(regs.r[0] >= 0)
  {
    regs.r[1] = (int)blk;
    regs.r[2] = 4*sizeof(int);
    _kernel_swi(TaskManager_EnumerateTasks, &regs, &regs);
    if(strcmp((char *)blk[1], "MidiKey") == 0)
      ro.flags |= (1<<RUNNING);
    if(strcmp((char *)blk[1], "MidiKey2") == 0)
      ro.flags |= (1<<RUNNING2);
  }
  if((ro.flags & (3<<RUNNING)) == (3<<RUNNING))
    return 0; // return if 2 instances running

  // 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] = (ro.flags & (1<<RUNNING)) ? (int)"MidiKey2" : (int)"MidiKey";
  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], "!midikey");
  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_kbd_display();
      init_chan_window();
      update_kbd_controls();
      create_driver();

      if(ro.flags & (1<<RUNNING))
      {
        // qualify window titles for second instance
        regs.r[1] = (int)blk | 1; // don't return icons
        blk[0] = ro.handle[WIN_KBD];
        _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
        strncpy((char *)(blk[19]), "Keyboard(2)", blk[21]);
        regs.r[1] = (int)blk | 1; // don't return icons
        blk[0] = ro.handle[WIN_CHANNELS];
        _kernel_swi(Wimp_GetWindowInfo, &regs, &regs);
        strncpy((char *)(blk[19]), "Channel Settings(2)", blk[21]);
      }

      while (!(ro.flags & (1<<QUIT)))
      {
        _kernel_swi(OS_ReadMonotonicTime, &regs, &regs);
        regs.r[2] = regs.r[0] + 1;
        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
            keyboard_entry(blk);    // mouse activity in keyboard window
            key_highlight_timer();
            slider_control(blk);    // dragging sliders
            midi_in_port();         // received midi data
            break;

          case 1: // redraw window
            if(blk[0] == ro.handle[WIN_KBD])
            {
              _kernel_swi(Wimp_RedrawWindow, &regs, &regs);
              while (regs.r[0])
              {
                plot_frame(blk);
                regs.r[1] = (int)blk;
                _kernel_swi(Wimp_GetRectangle, &regs, &regs);
              }
            }
            break;

          case 2: // open window
            _kernel_swi(Wimp_OpenWindow, &regs, &regs);
            if(blk[0] == ro.handle[WIN_KBD])
            {
              regs.r[0] = ro.handle[WIN_KBD];
              regs.r[1] = -1;
              regs.r[2] = 0;
              regs.r[3] = 0;
              regs.r[4] = 40 | (1<<25); // invisible
              regs.r[5] = 0;
              _kernel_swi(Wimp_SetCaretPosition, &regs, &regs);

              blk[0] = ro.caret.window;
              blk[1] = ro.caret.icon;
              blk[6] = '\r';
              key_press(blk);
            }
            break;

          case 3: //close window
            _kernel_swi(Wimp_CloseWindow, &regs, &regs);
            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 11: lose_caret(blk); break;
          case 12: gain_caret(blk); break;
          case 17:
          case 18:
          case 19: wimp_msg(blk, regs.r[0]); break;
        }
      }
    }
  }

  return 0;
}

