/* Simple BBC News ticker client
 * (c) Darren Salt
 * GPL applies
 * $Id: wimputil.c,v 1.6 2004/08/01 16:35:35 ds Exp $
 */

/* System includes */

#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <swis.h>

/* Program includes */

#include "globals.h"

#include "configure.h"
#include "fetchers.h"
#include "wimp.h"
#include "wimpmenu.h"
#include "util.h"


/* Cached mode variables */
struct modeinfo_t mode = { 0 }, prevmode = { 0 };

static char **indirected_list = 0;
static int indirected_list_entries = 0;


/* Allocate space for an indirected icon */

static char *
malloc_indirected (size_t size)
{
  char *p = malloc (size);
  if (!p)
    return 0;
  indirected_list =
    realloc (indirected_list,
	     ((indirected_list_entries + 16) & ~15) * sizeof (char *));
  if (!indirected_list)
  {
    report_error ("Out of memory");
    exit (2);
  }
  indirected_list[indirected_list_entries++] = p;
  return p;
}


/* Free all indirected icon memory */

void
free_indirected_icons (void)
{
  int i = indirected_list_entries;
  if (i)
  {
    while (i)
      free (indirected_list[--i]);
    free (indirected_list);
    indirected_list = 0;
    indirected_list_entries = 0;
  }
}


/* Load a template and instantiate it */

int
load_template (const char template_name[])
{
  int handle, window_size, indir_size, found;
  struct wimp_createwindow_block *window;
  char *indirected;
  char name[13] = { 0 };

  strncpy (name, template_name, 12);
  erx (_swix
       (Wimp_LoadTemplate, _INR (1, 6) | _OUTR (1, 2) | _OUT (6), 0, 0, 0, -1,
	name, 0, &window_size, &indir_size, &found));
  if (!found)
  {
    report_error ("Missing template!");
    exit (2);
  }
  window = malloc (window_size);
  indirected = malloc_indirected (indir_size);
  if (!window || !indirected)
  {
    report_error ("Out of memory");
    exit (2);
  }
  erx (_swix
       (Wimp_LoadTemplate, _INR (1, 6), window, indirected,
	indirected + indir_size, -1, name, 0));
  window->sprite_area = sprites;
  if ((window->icon_flags & 0x103) == 0x102)
    window->title.is.sprite_area = (void *) sprites;
  erx (_swix (Wimp_CreateWindow, _IN (1) | _OUT (0), window, &handle));
  free (window);
  return handle;
}


/* Store mode dimensions & pixel size */

void
cache_mode_vars (void)
{
  static const int vduvars[] = { 4, 5, 11, 12, -1 };
  prevmode = mode;
  _swi (OS_ReadVduVariables, _INR (0, 1), vduvars, &mode);
  mode.max.x *= (mode.min.x = 1 << mode.min.x);
  mode.max.y *= (mode.min.y = 1 << mode.min.y);
  mode.size.x = mode.min.x + mode.max.x;
  mode.size.y = mode.min.y + mode.max.y;
  memcpy (sprites + 1, (mode.min.x == 1) ? spr11 : spr22, sizeof (spr22));
}


/* Get window furniture details etc. */

void
wimp_extend_11 (struct wimp_extend_11_block *extend)
{
  static const struct wimp_extend_11_block default_extend = {
    0, 2, 40, 40, 40, 38, 38, 0, 360, 0, 38, 38, 38, 0, 38, 360,
    38, 0, 38, 38, 0, 38, 398, 38, 0
  };
/* BUG in cc <= 5.53 */
/*  extend->window_handle = 0;
 *  if (!_swix (Wimp_Extend, _INR (0, 1), 11, extend))
 *    memcpy (extend, &default_extend, sizeof (struct wimp_extend_11_block));
 */
  *extend = default_extend;
  _swix (Wimp_Extend, _INR (0, 1), 11, extend);
}


/* Shades or unshades a specific menu entry.
 * (But first, the code to do a submenu.)
 */

static void
set_submenu_shade (void *menu, int available)
{
  struct menu_entry *item;

  if ((int) menu == wind.ticker)
    menu = servers_menu;
  else if ((int) menu == 1)
    menu = find_goto_menu (0);
  if ((int) menu < 0x8000 || (int) menu & 1)
    return;

  item = (struct menu_entry *) ((char *) menu + 28);

  do
  {
    if (available)
      item->icon_flags &= ~icn_SHADED_BIT;
    else
      item->icon_flags |= icn_SHADED_BIT;
    if (item->submenu)
      set_submenu_shade ((void *) item->submenu, available);
  }
  while (((item++)->menuflags & 128) == 0);
}


void
set_menu_shade (void *menu, int entry, int available)
{
  struct menu_entry *item = (struct menu_entry *) ((char *) menu + 28);

  if (available)
    item[entry].icon_flags &= ~icn_SHADED_BIT;
  else
    item[entry].icon_flags |= icn_SHADED_BIT;

  if (item[entry].submenu)
    set_submenu_shade ((void *) item[entry].submenu, available);
}


/* Get the width of a string, and add in an allowance for icon margins. */

int
get_text_width (const char text[])
{
  if (wimp_version >= 320)
    return _swi (Wimp_TextOp, _INR (0, 2), 1, text, 0) + 11 & ~3;
  return 16 * strlen (text) + 8;
}


/************************
 * Error reporting code *
 ************************/


void
report_errno (void)
{
  report_error (strerror (errno));
}


void
report_error (const char *msg)
{
  _kernel_oserror block;
  block.errnum = 0;
  block.errmess[sizeof (block.errmess) - 1] = '\0';
  strncpy (block.errmess, msg, sizeof (block.errmess) - 1);
  report_oserror (&block);
}


void
report_oserror (const _kernel_oserror * block)
{
  wimp_report_error (block, 1, taskname);
}


void
report_oserrno (void)
{
  _kernel_oserror *e = _kernel_last_oserror ();
  if (e)
    report_oserror (e);
  else
    report_errno ();
}


/*************
 * Utilities *
 *************/


/* Return the text pointer for an indirected icon */

char *
get_icon_text (int window, int icon)
{
  struct wimp_geticonstate_block block;
  wimp_get_icon_state (window, icon, &block);
  assert (block.icon_flags & icn_INDIRECTED_BIT);
  return (block.icon_flags & icn_INDIRECTED_BIT) ? block.contents.it.text : 0;
}


/* Return the maximum text length for an indirected icon */

size_t
get_icon_text_length (int window, int icon)
{
  struct wimp_geticonstate_block block;
  wimp_get_icon_state (window, icon, &block);
  assert (block.icon_flags & icn_INDIRECTED_BIT);
  return (block.icon_flags & icn_INDIRECTED_BIT) ? block.contents.it.
    text_len : 0;
}


/* Set the text for an indirected icon */

void
set_icon_text (int window, int icon, const char *text)
{
  union
  {
    struct wimp_geticonstate_block g;
    struct wimp_seticonstate_block s;
    struct wimp_getcaretposition_block c;
  }
  block;

  if (wimp_get_icon_state (window, icon, &block.g))
    return;

  if (~block.g.icon_flags & icn_INDIRECTED_BIT)
    return;
  block.g.contents.it.text[block.g.contents.it.text_len - 1] = '\0';
  strncpy (block.g.contents.it.text, text, block.g.contents.it.text_len - 1);
  block.s.clear = block.s.eor = 0;
  wimp_set_icon_state (&block.s);

  _swi (Wimp_GetCaretPosition, _IN (1), &block.c);
  if (block.c.window_handle == window && block.c.icon_handle == icon)
    set_caret_icon (window, icon);
}


/* Update an icon's flags (unless they're already in the requested state).
 * e == c == 0 is special - this is a redraw request.
 */

void
set_icon_state (int w, int i, unsigned int e, unsigned int c)
{
  union
  {
    struct wimp_geticonstate_block g;
    struct wimp_seticonstate_block s;
  }
  block;

  if (c || e)
    if (wimp_get_icon_state (w, i, &block.g)
)//	|| ((block.g.icon_flags & ~c) ^ e) == block.g.icon_flags)
      return;
  block.s.eor = e;
  block.s.clear = c;
  wimp_set_icon_state (&block.s);
}


/* Select or deselect an icon. */

void
tick_icon (int w, int i, int tick)
{
  set_icon_state (w, i, tick ? icn_SELECTED_BIT : 0, icn_SELECTED_BIT);
}


/* Shade or unshade an icon. */

void
shade_icon (int w, int i, int shade)
{
  set_icon_state (w, i, shade ? icn_SHADED_BIT : 0, icn_SHADED_BIT);
}


/* Return whether the given icon is selected. */

int
icon_ticked (int w, int i)
{
  struct wimp_geticonstate_block block;

  if (wimp_get_icon_state (w, i, &block))
    return 0;
  return block.icon_flags & icn_SELECTED_BIT ? 1 : 0;
}


/* Put the caret at the end of the text in the given icon. */

void
set_caret_icon (int window, int icon)
{
  _swi (Wimp_SetCaretPosition, _INR (0, 5), window, icon, -1, -1, -1,
	icon == -1 ? 0 : strlen (get_icon_text (window, icon)));
}


/* Centre a window (alter the block content)) */

void
centre_window (struct wimp_openwindow_block *block)
{
  block->max.x -= block->min.x;
  block->max.y -= block->min.y;
  block->min.x = (mode.size.x - block->max.x) / 2;
  block->min.y = (mode.size.y - block->max.y) / 2;
  block->max.x += block->min.x;
  block->max.y += block->min.y;
}


/* Open window and one pane. Blocks already set up. */
void
open_sub (struct wimp_openwindow_block *main,
	  struct wimp_openwindow_block *pane, int xoff, int yoff, int ext)
{
  wimp_open_window_ext (main, -1, 0);
  if (xoff != INT_MAX)
  {
    pane->max.x -= pane->min.x;
    pane->min.x = main->min.x + xoff;
    pane->max.x += pane->min.x;
    pane->min.y -= pane->max.y;
    pane->max.y = main->max.y - yoff;
    pane->min.y += pane->max.y;
  }
  wimp_open_window_ext (pane, main->window_handle, ext);
}


/**************
 * URL launch *
 **************/


/* ANT protocol (fallback) */

void
launch_url_ant (const char url[])
{
  poll_block.user.length = (24 + strlen (url)) & 0xFC;
  if (poll_block.user.length > 256)
  {
    report_error
      ("The URL is too long to be launched via the ANT launch protocol");
    return;
  }
  poll_block.user.msg_code = WIMP_MESSAGE_LAUNCH_URL;
  strcpy (poll_block.user.contents.bytes, url);
  wimp_send_message (18, &poll_block, 0, 0);
}


/* Acorn protocol (primary) */

void
launch_url (const char *url, int allow_low)
{
  char *dup = 0;
  int ret = 0, handle = 0;

  if (allow_low && opt.low_graphics)
  {
    char *p = strstr (url, "/hi/");
    if (p && (dup = malloc (strlen (url) + 2)) != 0)
    {
      strncpy (dup, url, p - url);
      strcpy (dup + (p - url), "/low/");
      strcat (dup, p + 4);
      url = dup;
    }
  }
  if (_swix
      (URI_Dispatch, _INR (0, 2) | _OUT (0) | _OUT (3), 1, url, task_handle,
       &ret, &handle) || (ret & 1))
    launch_url_ant (url);
  free (dup);
}


/* Read in a URL */

int
parse_url (int window, const char *file, int uri)
{
  static char default_title[MAX_SERVER_LABEL_LENGTH] = "";
  char *buffer = 0, *ptr, *title = default_title, *url = 0;
  int i, length;
  _kernel_oserror *err;
  struct server newserver = fetcher_rdf;

  err = _swix (OS_File, _INR (0, 1) | _OUT (0) | _OUT (4), 17, file, &i,
	       &length);
  if (err)
    goto error;
  if (i != 1 || !length)
    return 1;
  ptr = buffer = malloc (length + 1);
  if (!buffer)
  {
    report_error ("Out of memory");
    return 1;
  }

  buffer[length] = 0;
  if ((err = _swix (OS_File, _INR (0, 3), 16, file, buffer, 0)) != 0)
    goto error;

  if (uri)
  {
    char c;
    --ptr;
    for (i = 3; i; --i)
      if ((ptr = strchr (ptr + 1, '\n')) == 0)
	goto unknown;
    while (c = *++ptr, c == '\t' || c == ' ')
      ;
    url = ptr;
    if ((ptr = strchr (ptr, '\n')) == 0)
      ptr = url + strlen (url);
    else
    {
      char *p = ptr;
      *ptr = 0;
      while (c = *++p, c == '\t' || c == ' ')
        ;
      title = p;
      if ((p = strchr (p, '\n')) == 0)
        p = title + strlen (title);
      else
        *p = 0;
    }
  }
  else
  {
    url = buffer;
    if ((ptr = strchr (url, '\n')) == 0)
      ptr = url + strlen (url);
    else
      *ptr = 0;
  }

  if (strnicmp (url, "http://", 7))
    goto unknown;

  if (!*title || find_server (title) != -1)
    title = default_title;

  if (title == default_title)
  {
    for (i = 1; ; ++i)
    {
      sprintf (title, "Ticker %i", i);
      if (find_server (title) == -1)
        break;
    }
  }

  switch (parse_server (&newserver, title, "", url + 7))
  {
  case 1:
    report_error ("Invalid URL");
    return 1;
  case 2:
    report_error ("Invalid port number");
    return 1;
  /* 3: blank label; 4: label in use */
  }

  free (buffer);

  if (window == wind.servers)
    open_servers_window (1, 1, &newserver);
  else if (ticker_data.state != state_LOADING)
    set_fetcher (&newserver);

  return 0;

unknown:
  report_error ("This URI's protocol is not HTTP");
  free (buffer);
  return 1;

error:
  report_oserror (err);
  free (buffer);
  return 1;
}


/* Is either save window open? */

int
is_save_window_open (void)
{
  struct wimp_getwindowstate_block block;
  wimp_get_window_state (&block, wind.save_link);
  if (block.window_flags & 1 << 16)
    return 1;
  wimp_get_window_state (&block, wind.save_text);
  return block.window_flags & 1 << 16;
}


/* Is the given window open? */

int
is_window_open (int handle)
{
  struct wimp_getwindowstate_block block;
  wimp_get_window_state (&block, handle);
  return block.window_flags & 1 << 16;
}


/* Tell Pinboard to remove any ticker window icon */

void
pinboard_remove_ticker (void)
{
  union wimp_poll_block block;
  block.user.length = 24;
  block.user.msg_code = WIMP_MESSAGE_WINDOWCLOSED;
  block.user.contents.window_iconise.window_handle = wind.ticker_tbar;
  wimp_send_message (18, &block, 0, 0);
}
