/*
  lib.c  riscos wimp functions

  created  2/2022
*/

#include "common.h"
#include "wimp.h"
#include "lib.h"
#include "synth.h"
#include "main.h"


/*
 * itoa
 * ----
 */
char *itoa(int i)
{
  static char s[16];
  sprintf(s,"%d",i);
  return s;
}


/*
 * htoa
 * ----
 */
char* htoa(int n)
{
  static char str[12];
  char *s = str;
  int i,c;
  for(i = 28; i >= 0; i -= 4)
  {
    c = (n >> i) & 15;
    if(c > 9) c += 7;
    *s++ = c + '0';
  }
  *s = 0;
  return str;
}


/*
 * read_text_string
 * ------------------
 * Reads the text string of a writable icon
 */
void read_text_string(int win, int icon, char*str)
{
  _kernel_swi_regs r;
  int b[10];
  r.r[1] = (int)b;
  b[0] = win;
  b[1] = icon;
  _kernel_swi(Wimp_GetIconState, &r, &r);
  strncpy(str, (char *)(b[7]), NAME_LEN);
}


/*
 * read_numeric_value
 * ------------------
 * Reads the numeric value of a writable icon
 */
int read_numeric_value(int win, int icon)
{
  _kernel_swi_regs r;
  int b[10];
  r.r[1] = (int)b;
  b[0] = win;
  b[1] = icon;
  _kernel_swi(Wimp_GetIconState, &r, &r);
  return atoi((char *)(b[7]));
}


/*
 * icon_sprite_change
 * ------------------
 * can also change the validation string
 */
void icon_sprite_change(int window, int icon, char *name)
{
  int blk[12];
  _kernel_swi_regs regs;

  blk[0] = window;
  blk[1] = icon;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  _kernel_swi(Wimp_DeleteIcon, &regs, &regs);

  blk[8] = (int)name;
  blk[1] = blk[0];
  regs.r[0] = window;
  regs.r[1] = (int)&blk[1];
  _kernel_swi(Wimp_CreateIcon, &regs, &regs);

  _kernel_swi(Wimp_UpdateWindow, &regs, &regs);
  while (regs.r[0])
  {
    regs.r[1] = (int)&blk[1];
    _kernel_swi(Wimp_GetRectangle, &regs, &regs);
  }
}


/* icon_colour_change
 * ------------------
 * colour = bits 0..3 foreground, 4..7 background
 */
void icon_colour_change(int colour, int window, int icon)
{
  _kernel_swi_regs regs;
  int blk[4];

  blk[0] = window;
  blk[1] = icon;
  blk[2] = colour << 24;
  blk[3] = 255 << 24;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);
}


/*
 * icon_disabled_change
 * --------------------
 */
void icon_disabled_change(int state, int window, int icon)
{
  _kernel_swi_regs regs;
  int blk[4];

  blk[0] = window;
  blk[1] = icon;
  blk[2] = state << 22;
  blk[3] = 1 << 22;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);
}


/*
 * icon_state_change
 * -------------------
 */
void icon_state_change(int state, int window, int icon)
{
  _kernel_swi_regs regs;
  int blk[4];

  blk[0] = window;
  blk[1] = icon;
  blk[2] = state << 21;
  blk[3] = 1 << 21;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_SetIconState, &regs, &regs);
}


/*
 * icon_text_change
 * ----------------
 */
void icon_text_change(char *text, int window, int icon)
{
   _kernel_swi_regs regs;
   int blk[64];

   /* update string */
   blk[0] = window;
   blk[1] = icon;
   regs.r[1] = (int)blk;
   _kernel_swi(Wimp_GetIconState, &regs, &regs);
   strcpy((char *)blk[7], text);

   /* inform wimp */
   blk[2] = 0;
   blk[3] = 0;
   regs.r[1] = (int)blk;
   _kernel_swi(Wimp_SetIconState, &regs, &regs);
}


/*
 * set_filetype
 * ------------
 */
void set_filetype(char *filename, int filetype)
{
  _kernel_swi_regs regs;

  regs.r[0] = WRITE_CATINFO;
  regs.r[1] = (int)filename;
  regs.r[2] = filetype;
  _kernel_swi(OS_File, &regs, &regs);
}


/*
 * open_menu
 * ---------
 */
void open_menu(int x, int y, int menu_tag, int menu_struct)
{
  _kernel_swi_regs regs;

  // to recreate the menu if adjust is pressed
  static int prev_x, prev_y;
  if(x)
    prev_x = x;
  else if(prev_x)
    x = prev_x;
  else return;
  if(y)
    prev_y = y;
  else if(prev_y)
    y = prev_y;
  else return;

  regs.r[1] = menu_struct;
  regs.r[2] = x;
  regs.r[3] = y;
  _kernel_swi(Wimp_CreateMenu, &regs, &regs);

  ro.cur_menu = menu_tag;
}


/*
 * open_window
 * -----------
 */
void open_window(int window)
{
  _kernel_swi_regs regs;
  int blk[9];

  blk[0] = window;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  blk[7] = -1; // bring to front
  _kernel_swi(Wimp_OpenWindow, &regs, &regs);
}


/*
 * drag_start
 * ----------
 * Called when saving a file by dragging the icon.
 */
void drag_start(int window, int icon)
{
  _kernel_swi_regs  regs;
  int blk[9], box[10];
  char * spr;

  if((ro.save_type == SYN_SAVE_SET) || (ro.save_type == SYN_SAVE_FULL))
    spr = "file_ffd";
  else if(ro.save_type == (SYN_SAVE_SRC + 0x400))
    spr = "file_dfe";
  else
    spr = "file_fff";

  box[0] = window;
  box[1] = icon;
  regs.r[1] = (int)box;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);

  blk[0] = window;
  regs.r[1] = (int)blk;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);

  box[2] += blk[1] - blk[5];
  box[3] += blk[4] - blk[6];
  box[4] += blk[1] - blk[5];
  box[5] += blk[4] - blk[6];

  regs.r[0] = 0;
  regs.r[1] = 1;
  regs.r[2] = (int)spr;
  regs.r[3] = (int)&box[2];
  _kernel_swi(DragASprite_Start, &regs, &regs);

  ro.flags |= (1<<DRAG_ACTIVE);
}

/*
 * drag_return
 * -----------
 * called when a dragged file icon is dropped.
 */
void drag_return(void)
{
  _kernel_swi_regs  regs;
  int blk[16];

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

  blk[5] = blk[3]; /* window handle */
  blk[6] = blk[4]; /* icon handle */
  blk[7] = blk[0]; /* mouse x */
  blk[8] = blk[1]; /* mouse y */
  blk[0] = 56;
  blk[1] = 0;
  blk[2] = 0;
  blk[3] = 0;
  blk[4] = MESSAGE_DATASAVE;
  blk[9] = 256; /* size */
  blk[10] = DATA;
  strcpy((char *)&blk[11], ""); // we will add the leafname later

  if(ro.flags & (1<<DRAG_ACTIVE))
  {
    regs.r[0] = 17;
    regs.r[1] = (int)blk;
    regs.r[2] = blk[5]; /* window handle */
    _kernel_swi(Wimp_SendMessage, &regs, &regs);
    _kernel_swi(DragASprite_Stop, &regs, &regs);
  }
}


/*
  Progress bar and slider control functions
  -----------------------------------------
  2 functions,
    slider_get_posn,        Reads the pointer position and returns a value
                            within the given range.
    slider_display_value,   Displays the new progress bar or slider control
                            position.

  Both functions require the slider definition which contains,
    window,  Window handle.
    icon,    Bar/slider background icon handle, requires icon+1 to
             be the bar/slider control icon handle.
    lo,      The lower range limit.
    hi,      The higher range limit.
    flags,   Bit 0: orientation,  0 = horizontal,   1 = vertical.
             Bit 1: control type, 0 = progress bar, 1 = slider control.

  For horizontal sliders, lo == left, hi == right.
  For vertical sliders, lo == bottom, hi == top.
  lo can be larger than hi to reverse the range.
*/

/*
 * slider_get_posn
 * ---------------
 * Reads the pointer position and returns a value within the given range.
 * inputs:
 *  blk     address of pointer info
 *  slider  index to slider definition
 * returns:
 *  value between lo and hi
 */
int slider_get_posn(int blk[], int slider)
{
  _kernel_swi_regs regs;
  slider_t *s = &ro.slider.type[slider];
  int window = ro.slider.window;
  int icon = ro.slider.icon;
  int v = s->flags & VERTICAL; // 0 for horizontal, 1 for vertical
  int x = blk[v]; // ref left/bottom of screen

  blk[0] = window;
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetWindowState, &regs, &regs);
  x -= blk[1+(3 * v)] - blk[5 + v]; // ref left/top of window

  blk[0] = window;
  blk[1] = icon - 1; // slider background
  regs.r[1] = (int) blk;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);

  int value = x - blk[2 + v]; // ref left/bottom of bar extent
  int len = blk[4 + v] - blk[2 + v]; // width/height of bar background

  if(s->hi > s->lo)
  {
    value = (((s->hi - s->lo) * value) / len) + s->lo;
    if(value < s->lo) value = s->lo;
    else if(value > s->hi) value = s->hi;
  }
  else if(s->hi < s->lo)
  {
    value = (((s->lo - s->hi) * (len - value)) / len) + s->hi;
    if(value < s->hi) value = s->hi;
    else if(value > s->lo) value = s->lo;
  }
  else
    return 0;

  return value;
}

/*
 * slider_display_value
 * --------------------
 * Displays the new progress bar or slider control position
 * inputs:
 *   value   new value to be displayed
 *   slider  index to slider definition
 *   window  index to window handle array
 *   icon    icon number in window
 */
void slider_display_value(int value, int slider, int win, int icon)
{
  _kernel_swi_regs regs;
  slider_t *s = &ro.slider.type[slider];
  int window = ro.handle[win];
  int v = s->flags & VERTICAL; // 0 for horizontal, 1 for vertical
  int b[10];

  b[0] = window;
  b[1] = icon - 1; // slider background
  regs.r[1] = (int)b;
  _kernel_swi(Wimp_GetIconState, &regs, &regs);
  int min = b[2 + v];
  int max = b[4 + v];

  b[1] = icon; // slider button
  _kernel_swi(Wimp_GetIconState, &regs, &regs);

  int new, h = 0;

  if(s->hi == s->lo)
    new = min;
  else
  {
    if(s->hi > s->lo)
    {
      if(value < s->lo) value = s->lo;
      else if(value > s->hi) value = s->hi;
    }
    else if(s->hi < s->lo)
    {
      if(value > s->lo) value = s->lo;
      else if(value < s->hi) value = s->hi;
    }
    new = min + ((max - min) * (value - s->lo)) / (s->hi - s->lo);
  }

  regs.r[0] = b[0];
  regs.r[1] = b[1];
  regs.r[2] = b[2];
  regs.r[3] = b[3];
  regs.r[4] = b[4];
  regs.r[5] = b[5];

  if(s->flags & SLIDER_CONTROL) // slider control type
  {
    h = (b[4 + v] - b[2 + v]) / 2;
    regs.r[2 + v] = new - h;
    regs.r[4 + v] = new + h;
  }
  else // progress bar
    regs.r[4 + v] = new;

  _kernel_swi(Wimp_ResizeIcon, &regs, &regs);
  regs.r[0] = b[0];
  regs.r[1] = b[2];
  regs.r[2] = b[3];
  regs.r[3] = b[4];
  regs.r[4] = b[5];
  regs.r[1 + v] = min - h;
  regs.r[3 + v] = max + h;
  _kernel_swi(Wimp_ForceRedraw, &regs, &regs);
}


/*
 * report_error_number
 * -------------------
 */
void report_error_number(int err, int fatal)
{
  static char msg[80];

  if(msg_lookup("error", err, msg))
    report_error(msg, fatal);
  else
    report_error("Unknown error", fatal);
}


/*
 * report_error
 * ------------
 */
int report_error(const char *errmess, int flags)
{
  _kernel_oserror blk;
  _kernel_swi_regs regs;

  blk.errnum = 1;
  strcpy(blk.errmess, errmess);
  regs.r[0] = (int)&blk;
  regs.r[1] = flags;
  regs.r[2] = (int)APP_NAME;
  _kernel_swi(Wimp_ReportError, &regs, &regs);

  /* Returns 1 if OK selected, 2 if Cancel selected */
  return regs.r[1];
}


/*
 * read_vdu_vars
 * -------------
 * Reads screen parameters
 */
void read_vdu_vars(void)
{
  _kernel_swi_regs regs;
  int b[5];

  b[0] = 4; // xeig
  b[1] = 5; // yeig
  b[2] = 11; // width
  b[3] = 12; // height
  b[4] = -1;
  regs.r[0] = regs.r[1] = (int)b;
  _kernel_swi(OS_ReadVduVariables, &regs, &regs);
  ro.vdu.width = (b[2] + 1) << b[0]; // screen width in os units
  ro.vdu.height = (b[3] + 1) << b[1]; // screen height in os units
}


/*
 * loadsprites
 * -----------
 * returns sprite area address or null for an error.
 */
int *loadsprites(const char *sprites)
{
  _kernel_swi_regs regs;
    int * area = 0;

  regs.r[0] = READ_CATINFO;
  regs.r[1] = (int)sprites;
  _kernel_swi(OS_File, &regs, &regs);
  if(regs.r[0] != IS_FILE)
    return 0;

  area = malloc(regs.r[4]+4);
  area[0] = regs.r[4]+4;
  area[1] = 0;
  area[2] = 16;
  area[3] = 16;
  regs.r[0] = 10+256;
  regs.r[1] = (int)area;
  regs.r[2] = (int)sprites;
  _kernel_swi(OS_SpriteOp, &regs, &regs);

  // find address of waveform buffer
  ro.wave_plot = NULL;
  int *p = area + (area[2] / 4);
  int *end = area + (area[0] / 4);
  while((ro.wave_plot == NULL) && (p < end))
    if(strncmp("waveform", (char *)(p+1), 8) == 0)
      ro.wave_plot = (char *)(p + p[8] / 4);
    else
      p += p[0] / 4;

  return area;
}


/*
 * loadtemplate
 * ---------------
 * returns window handle or zero
 */
int loadtemplate(const char *template_name, int *sprites)
{
  _kernel_swi_regs regs;
  int name[4];
  int * buffer;
  char * workspace;

  strcpy((char *)name, template_name); // make sure it's 3 aligned words
  regs.r[1] = 0;  // find size
  regs.r[5] = (int)name;
  regs.r[6] = 0;
  _kernel_swi(Wimp_LoadTemplate, &regs, &regs);
  buffer = malloc(regs.r[1]);
  workspace = malloc(regs.r[2]);

  // load template
  regs.r[1] = (int)buffer;
  regs.r[3] = (int)workspace + regs.r[2];
  regs.r[2] = (int)workspace;
  regs.r[4] = -1;
  regs.r[6] = 0;
  _kernel_swi(Wimp_LoadTemplate, &regs, &regs);

  if(!regs.r[6])
    return 0;

  if(sprites)
    buffer[16] = (int)sprites;

  // if needed, centralise window
  if(ro.flags & (1<<CENTRALISE))
  {
    int ww = buffer[2] - buffer[0]; // window width
    int wh = buffer[3] - buffer[1]; // window height
    buffer[0] = (ro.vdu.width / 2) - (ww / 2);
    buffer[1] = (ro.vdu.height / 2) - (wh / 2);
    buffer[2] = buffer[0] + ww;
    buffer[3] = buffer[1] + wh;
  }

  // Create window
  regs.r[1] = (int)buffer;
  _kernel_swi(Wimp_CreateWindow, &regs, &regs);

  return regs.r[0];
}


static int msg_desc[5];
static char * msg_buf;

/*
 * msg_open
 * --------
 */
int msg_open(char * name)
{
  _kernel_swi_regs regs;

  if(msg_desc[4])
    return 1; // already open

  regs.r[1] = (int)name;
  if(_kernel_swi(MessageTrans_FileInfo, &regs, &regs))
    return 0; // error opening file

  if(regs.r[0] & 1)
    return 0; // already in memory

  if((msg_buf = malloc(regs.r[2])) == NULL)
    return 0; // out of memory

  regs.r[0] = (int)&msg_desc;
  regs.r[1] = (int)name;
  regs.r[2] = (int)msg_buf;
  _kernel_swi(MessageTrans_OpenFile, &regs, &regs);
  msg_desc[4] = 1;

  return 1;
}

/*
 * msg_close
 * ---------
 */
void msg_close(void)
{
  _kernel_swi_regs regs;

  regs.r[0] = (int)&msg_desc;
  _kernel_swi(MessageTrans_CloseFile, &regs, &regs);
  msg_desc[4] = 0;

  if(msg_buf)
  {
    free(msg_buf);
    msg_buf = NULL;
  }
}

/*
 * msg_lookup
 * ----------
 * Converts a message token to text. Requires the base token string and
 * a numeric qualifier.
 * If qualifier is negative, uses token directly with no appended numeric.
 */
int msg_lookup(char * token, int num, char * dest)
{
  _kernel_swi_regs regs;
  char tok[32];

  if(!msg_desc[4])
    return 0; // Messages file not opened

  regs.r[0] = (int)&msg_desc;
  if(num < 0)
    regs.r[1] = (int)token;
  else
  {
    sprintf(tok, "%s%d", token, num);
    regs.r[1] = (int)&tok;
  }
  regs.r[2] = (int)dest;
  regs.r[3] = 256;
  regs.r[4] = 0;
  regs.r[5] = 0;
  regs.r[6] = 0;
  regs.r[7] = 0;
  if(_kernel_swi(MessageTrans_Lookup, &regs, &regs))
    return 0;

  return 1;
}


/*
 * msg_load_menu
 * -------------
 * Updates menu text from the messages file
 * Returns the next free location in the string store (str)
 */
char *msg_load_menu(char *token, char *str, menu_t *menu)
{
  int i;

  if(!msg_lookup(token, -1, str))
    return str;

  // title
  menu->text = str;
  i = 0;
  while((*str != ',') && *str) { i++; str++; }
  menu->len = i;
  if(!*str)
    return str + 1;
  *str = 0;

  // items
  menu_item_t *item = menu->item;
  while(1)
  {
    str++; // skip ','
    item->text = str;
    i = 0;
    while((*str != ',') && *str) { i++; str++; }
    item->len = i;
    if(!*str)
      return str + 1;
    *str = 0;
    if(item->item_flags & 0x80)
      return str + 1;
    item++;
  }

  return 0; // should never happen
}


/*
 * create/update menus
 * ---------------------------------------
 */
static const menu_item_t def_item = {0,-1,IC_DEF,"",NULL,0};

/*
 * update_menu
 * -----------
 * unticks everything, then ticks the 'cur' item.
 */
void update_menu(menu_item_t *item, int num, int cur)
{
  int i;

  for(i=0; i<num; i++)
    item[i].item_flags &= ~IT_TICKED;
  item[cur].item_flags |= IT_TICKED;
}


/*
 * create_numeric_menu
 * -------------------
 * creates the menu items of a numerical list
 */
char *create_numeric_menu(menu_item_t *item, int num, const int *value, char *str)
{
  int i, n;

  for(i=0; i<num; i++)
  {
    n = sprintf(str, "%d", value[i]);
    item[i] = def_item;
    item[i].text = str;
    item[i].len = n;
    str += n + 1;
  }
  item[num-1].item_flags = 0x80;
  return str;
}


/*
 * create_sys_rate_menu
 * --------------------
 * Creates the system sample rate selection menu
 */
char *create_sys_rate_menu(menu_item_t *item, char *str)
{
  _kernel_swi_regs regs;
  int i, start, num, len;

  regs.r[0] = 0; // read number of available rates
  _kernel_swi(Sound_SampleRate, &regs, &regs);
  num = regs.r[1];

  // if there are too many rates, loose the lowest ones
  start = (num > MAX_SYS_RATES) ? num - MAX_SYS_RATES + 1 : 1;

  for(i=0; i<num; i++)
  {
    regs.r[0] = 2; // read rate from index
    regs.r[1] = i + start;
    _kernel_swi(Sound_SampleRate, &regs, &regs);
    len = sprintf(str, "%d", regs.r[2] >> 10);
    item[i] = def_item;
    item[i].text = str;
    item[i].len = len;
    str += len + 1;
  }
  item[0].item_flags |= 256;
  regs.r[0] = 1; // read current rate and index
  _kernel_swi(Sound_SampleRate, &regs, &regs);
  item[regs.r[1] - start].item_flags |= IT_TICKED;
  item[num - start].item_flags |= 128;
  ro.rate_start = start;
  return str;
}




