#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "DragASpr.h"
#include "Error.h"
#include "Event.h"
#include "EventMsg.h"
#include "File.h"
#include "Handler.h"
#include "KeyCodes.h"
#include "Menu.h"
#include "Resource.h"
#include "Str.h"
#include "SWI.h"
#include "Template.h"
#include "WimpSWIs.h"
#include "Window.h"

#include "appdata.h"
#include "control.h"
#include "fsutils.h"
#include "log.h"
#include "msgtrandyn.h"
#include "version.h"

#ifndef Territory_ConvertStandardDate
#define Territory_ConvertStandardDate 0x4304d
#endif

#ifdef MemCheck_MEMCHECK
#include "MemCheck:MemCheck.h"
#endif

static const int wanted_messages[] = {
  				message_DATALOAD,
  				/* message_DATALOADACK, */
  				/* message_DATASAVE, */
  				message_DATASAVEACK,
  				message_HELPREQUEST,
  				message_QUIT
};

msgtrans_filedesc *messages;

/* If RISC OS can't be bothered to provide a sensible way of reading the CSD
   I can't be bothered to rember it for restoring */
/*
static char csd_buffer[256];
static char *original_csd = csd_buffer + 2;

static void restore_csd(void)
{
  fsutils_set_dir(original_csd);
}
*/

/* Forward declarations needed for some funcs with circular references */
static BOOL key_handler(event_pollblock *e, void *h);
static BOOL dataload_handler(event_pollblock *e, void *h);
static BOOL start_drag_handler(event_pollblock *e, void *h);
static BOOL end_drag_handler(event_pollblock *e, void *h);
static BOOL datasave_ack_handler(event_pollblock *e, void *h);
static BOOL setup_option_buttons(event_pollblock *e, void *h);
static BOOL introduce_optionals(event_pollblock *e, void *h);
static BOOL main_welcome(event_pollblock *e, void *h);
static void next_disc(app_data *app);
static void next_item(app_data *app);
static void next_item_confirmed(app_data *, control_disc *, control_item *);

/* Causes an immediate redraw (ie without waiting for next Wimp_Poll)
   of a given icon, or -1 for whole window; probably won't use it,
   inserting intervening null polls instead
*/
/*
static void immediate_window_update(window_handle win, icon_handle icon)
{
}
*/
#define immediate_window_update(w, i)

static BOOL redraw_handler(event_pollblock *event, void *h)
{
  window_redrawblock redraw;
  BOOL more;
  app_data *app = h;

  redraw.window = event->data.openblock.window;
  Wimp_RedrawWindow(&redraw, &more);

  while (more)
  {
    /* Slider_Redraw is broken, so it hardly seems worth bothering */
    Slider_Redraw(app->disc_slider, &redraw.cliprect);
    Slider_Redraw(app->total_slider, &redraw.cliprect);
    Wimp_GetRectangle(&redraw, &more);
  }

  return TRUE;
}


static BOOL cancel_handler(event_pollblock *e, void *h)
{
  (void) h;

  if (e->type == event_CLICK && e->data.mouse.button.value == button_MENU)
    return FALSE;
  Event_CloseDown();
  return TRUE;
}

/* Checks whether filename icon contains a valid full pathname,
   and shades or unshades Next icon accordingly */
static BOOL check_filename_is_path(app_data *app)
{
  BOOL valid_path = FALSE;
  const char *text = Icon_GetTextPtr(app->win, icon_Filename);

  if (text)
  {
    const char *cp;

    for (cp = text; !iscntrl(*cp); ++cp)
    {
      if (*cp == '.' || *cp == ':')
      {
        valid_path = TRUE;
        break;
      }
    }
  }

  app_data_shade_next(app, !valid_path);

  return valid_path;
}

static BOOL reboot_handler(event_pollblock *e, void *h)
{
  key_block shutkey;
  app_data *app = h;
  (void) e;

  app_data_release_handlers(app);

  Error_Check(Wimp_GetCaretPosition(&shutkey.caret));
  shutkey.code = keycode_CTRL_SHIFT_F12;
  Error_Check(Wimp_SendMessage(event_KEY, (message_block *) &shutkey,
                               0, 0));

  return TRUE;
}

static BOOL finished_handler(event_pollblock *e, void *h)
{
  app_data *app = h;
  char *msg;
  (void) e;

  /* Delete installation scripts */
  msg = malloc(strlen(app->control->master_location_dir) + 10);
  if (!msg)
  {
    Error_Report(0, MSG_NOMEM);
    exit(1);
  }
  sprintf(msg, "%s.Scripts", app->control->master_location_dir);
  fsutils_wipe(msg);
  free(msg);

  Slider_SetValue(app->total_slider, ++app->control->installed_stages,
                  NULL, NULL);

  msg = MsgTransDyn_Lookup(messages,
                           app->control->reboot ? "RebootPrompt" : "Finished",
                           FALSE);
  app_data_show_message(app, msg);
  free(msg);

  app_data_set_filename_text(app, NULL);

  msg = MsgTransDyn_Lookup(messages,
                           app->control->reboot ? "Reboot" : "Finish",
                           FALSE);
  if (msg)
  {
    Icon_SetText(app->win, icon_Next, msg);
    free(msg);
  }

  app_data_shade_next(app, FALSE);

  Event_Release(event_NULL, event_ANY, event_ANY, finished_handler, app);
  app_data_set_next_handler(app,
              app->control->reboot ? reboot_handler : cancel_handler);

  return TRUE;
}

static void done_all_discs(app_data *app)
{
  app_data_set_draggable_icon(app, "!install");
  app_data_show_item_status(app, "DeletingScripts", NULL);
  Event_Claim(event_NULL, event_ANY, event_ANY, finished_handler, app);
}

static BOOL post_script_handler(event_pollblock *e, void *h)
{
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];

  (void) e;

  Slider_SetValue(app->disc_slider, ++disc->installed_stages, NULL, NULL);
  Slider_SetValue(app->total_slider, ++app->control->installed_stages,
                  NULL, NULL);

  Event_Release(event_NULL, event_ANY, event_ANY, post_script_handler, app);

  next_item(app);

  return TRUE;
}

static BOOL setup_script_handler(event_pollblock *e, void *h)
{
  char command[256];
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item = disc->items[disc->installed_items];
  control_location *location = app->control->locations[item->location];

  (void) e;

  sprintf(command, "%s.Scripts.%s %s%s",
          app->control->master_location_dir, item->script,
          location->pathname, item->location_leaf ? item->location_leaf : "");
  LOG("Script command %s\n", command);
  Wimp_StartTask(command);

  Event_Release(event_NULL, event_ANY, event_ANY, setup_script_handler, app);
  Event_Claim(event_NULL, event_ANY, event_ANY, post_script_handler, app);

  return TRUE;
}

static BOOL post_untar_handler(event_pollblock *e, void *h)
{
  char wipe[256];
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item = disc->items[disc->installed_items];
  control_location *location = app->control->locations[item->location];

  (void) e;

  sprintf(wipe, "%s%s.tarc",
          location->pathname,
          item->location_leaf ? item->location_leaf : "");
  fsutils_wipe(wipe);

  Slider_SetValue(app->disc_slider, ++disc->installed_stages, NULL, NULL);
  Slider_SetValue(app->total_slider, ++app->control->installed_stages,
                  NULL, NULL);

  Event_Release(event_NULL, event_ANY, event_ANY, post_untar_handler, app);

  if (item->script)
  {
    app_data_show_item_status(app, "InstallingItem", item->name);
    Event_Claim(event_NULL, event_ANY, event_ANY, setup_script_handler, app);
  }
  else
  {
    next_item(app);
  }

  return TRUE;
}

static BOOL setup_untar_handler(event_pollblock *e, void *h)
{
  char command[256];
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item = disc->items[disc->installed_items];
  control_location *location = app->control->locations[item->location];

  (void) e;

  sprintf(command, "%s%s",
          location->pathname, item->location_leaf ? item->location_leaf : "");
  Error_CheckFatal(fsutils_set_dir(command));
  sprintf(command, "%s.Scripts.tar -xf tarc",
          app->control->master_location_dir);
  LOG("tar command %s\n", command);
  Wimp_StartTask(command);

  Event_Release(event_NULL, event_ANY, event_ANY, setup_untar_handler, app);
  Event_Claim(event_NULL, event_ANY, event_ANY, post_untar_handler, app);

  return TRUE;
}

static BOOL post_bunzip_handler(event_pollblock *e, void *h)
{
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item = disc->items[disc->installed_items];
  control_location *location = app->control->locations[item->location];
  char wipe[256];

  (void) e;

  sprintf(wipe, "%s%s.bzipped", location->pathname,
          item->location_leaf ? item->location_leaf : "");
  LOG("Wiping %s\n", wipe);
  fsutils_wipe(wipe);

  Slider_SetValue(app->disc_slider, ++disc->installed_stages, NULL, NULL);
  Slider_SetValue(app->total_slider, ++app->control->installed_stages,
                  NULL, NULL);

  app_data_show_item_status(app, "ExtractingItem", item->name);

  Event_Release(event_NULL, event_ANY, event_ANY, post_bunzip_handler, app);
  Event_Claim(event_NULL, event_ANY, event_ANY, setup_untar_handler, app);

  return TRUE;
}

static BOOL setup_bunzip_handler(event_pollblock *e, void *h)
{
  char command[256];
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item = disc->items[disc->installed_items];
  control_location *location = app->control->locations[item->location];

  (void) e;

  sprintf(command, "%s.Scripts.bunzip2 %s -d -c %s%s.bzipped > %s%s.tarc",
          app->control->master_location_dir,
          app->lowmem ? "-s" : "",
          location->pathname,
          item->location_leaf ? item->location_leaf : "",
          location->pathname,
          item->location_leaf ? item->location_leaf : "");
  LOG("bunzip2 command %s\n", command);
  Wimp_StartTask(command);

  Event_Release(event_NULL, event_ANY, event_ANY, setup_bunzip_handler, app);
  Event_Claim(event_NULL, event_ANY, event_ANY, post_bunzip_handler, app);

  return TRUE;
}

static BOOL do_copy_handler(event_pollblock *e, void *h)
{
  char source[100];
  char dest[256];
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item = disc->items[disc->installed_items];
  control_location *location = app->control->locations[item->location];

  (void) e;

  sprintf(source, "<nstall$Dir>.Items.%s", item->name);
  sprintf(dest, "%s%s.bzipped", location->pathname,
          item->location_leaf ? item->location_leaf : "");
  fsutils_copy(source, dest);
  Slider_SetValue(app->disc_slider, ++disc->installed_stages, NULL, NULL);
  Slider_SetValue(app->total_slider, ++app->control->installed_stages,
                  NULL, NULL);
  app_data_show_item_status(app, "DecompressingItem", item->name);

  Event_Release(event_NULL, event_ANY, event_ANY, do_copy_handler, app);
  Event_Claim(event_NULL, event_ANY, event_ANY, setup_bunzip_handler, app);

  return TRUE;
}

static BOOL check_item(const char *leafname)
{
  BOOL result;
  char pathname[100];

  LOG("Checking disc for item %s: ", leafname);
  sprintf(pathname, "<nstall$Dir>.Items.%s", leafname);
  result = File_Exists(pathname);
  LOGQ("%d\n", result);
  return result;
}

static BOOL mount_handler(event_pollblock *e, void *h)
{
  app_data *app = h;
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item = disc->items[disc->installed_items];

  LOG("At start of mount_handler\n");
  control_enumerate(app->control);

  (void) e;

  LOG("New disc has been mounted, looking again for %s\n", item->name);

  Event_Release(event_NULL, event_ANY, event_ANY, mount_handler, h);

  if (check_item(item->name))
  {
    char *msg;

    LOG("Found it, getting on with it\n");

    app_data_release_handlers(app);

    msg = MsgTransDyn_Lookup(messages, "Installing", TRUE);
    if (!msg)
      exit(1);
    app_data_show_message(app, msg);
    free(msg);

    app_data_shade_next(app, FALSE);
    next_item_confirmed(app, disc, item);
  }
  else
  {
    LOG("Not found, try again\n");
    MsgTrans_Report(messages, "WrongDisc", FALSE);
    fsutils_set_dir("<nstall$Dir>");
    Wimp_StartTask("Dismount");
  }

  return TRUE;
}


static BOOL disc_click_handler(event_pollblock *e, void *h)
{
  (void) e;

  LOG("User thinks they've inserted correct disc, mounting\n");

  fsutils_set_dir("<nstall$Dir>");
  Wimp_StartTask("Mount");

  Event_Claim(event_NULL, event_ANY, event_ANY, mount_handler, h);

  return TRUE;
}

static void next_item_confirmed(app_data *app, control_disc *disc,
                                control_item *item)
{
  LOG("\nInstalling item %d '%s' from disc %d\n", disc->installed_items,
      item->name, app->control->installed_discs);

  app_data_set_draggable_icon(app, item->icon);
  app_data_show_item_status(app, "CopyingItem", item->name);

  Event_Claim(event_NULL, event_ANY, event_ANY, do_copy_handler, app);
}

static void next_item(app_data *app)
{
  control_disc *disc = app->control->discs[app->control->installed_discs];
  control_item *item;
  char *prompt;

  LOG("Moving on from disc item %d/%d\n",
       disc->installed_items, disc->num_items);

  if (++disc->installed_items >= disc->num_items)
  {
    next_disc(app);
    LOG("After calling next_disc\n");
    control_enumerate(app->control);
    return;
  }
  item = disc->items[disc->installed_items];

  LOGQ("\n");
  LOG("Just before looking for item %d %s\n",
       disc->installed_items, item);
  control_enumerate(app->control);
  if (check_item(item->name))
  {
    LOG("OK, installing it\n");
    next_item_confirmed(app, disc, item);
    return;
  }

  LOG("Not found, dismount and ask for next disc\n");
  fsutils_set_dir("<nstall$Dir>");
  Wimp_StartTask("Dismount");

  prompt = MsgTransDyn_LookupPS(messages, "InsertDisc", disc->human_name,
                                NULL, NULL, NULL, TRUE);
  if (!prompt)
    exit(1);
  app_data_show_message(app, prompt);
  free(prompt);
  app_data_set_filename_text(app, NULL);
  app_data_set_draggable_icon(app, "floppydisc");
  app_data_shade_next(app, FALSE);

  app_data_set_next_handler(app, disc_click_handler);
}

static void next_disc(app_data *app)
{
  do
  {
    if (++app->control->installed_discs >= app->control->num_discs)
    {
      done_all_discs(app);
      return;
    }
  } while (!app->control->discs[app->control->installed_discs]->num_items);

  LOG("\n\nStarting disc %d\n", app->control->installed_discs);

  app->disc_slider->limits.max =
       app->control->discs[app->control->installed_discs]->num_stages;
  Slider_SetValue(app->disc_slider,
          app->control->discs[app->control->installed_discs]->installed_stages,
          NULL, NULL);

  app->control->discs[app->control->installed_discs]->installed_items = -1;
  next_item(app);
}

static BOOL install_scripts_handler(event_pollblock *e, void *h)
{
  app_data *app = h;
  char *dest;
  (void) e;

  Event_Release(event_NULL, event_ANY, event_ANY, install_scripts_handler, app);

  /* Copy installation scripts */
  dest = malloc(strlen(app->control->master_location_dir) + 10);
  if (!dest)
  {
    Error_Report(0, MSG_NOMEM);
    exit(1);
  }
  sprintf(dest, "%s.Scripts", app->control->master_location_dir);
  if (!fsutils_copy("<nstall$Dir>.Scripts", dest))
  {
    exit(1);
  }
  free(dest);

  app_data_set_filename_text(app, NULL);
  Slider_SetValue(app->total_slider, ++app->control->installed_stages,
                  NULL, NULL);

  app->control->installed_discs = -1;
  next_disc(app);

  return TRUE;
}

static void start_installing(app_data *app)
{
  char *msg;

  /* From now on we can't go back to options so prune them */
  control_master_data_prune_items(app->control, TRUE);

  if (!control_master_data_populate_disc_items(app->control))
    exit(1);

  app_data_toggle_draggable(app, FALSE);

  control_master_data_count_stages(app->control);
  /* Add stages for copying and deleting the scripts */
  app->control->num_stages += 2;
  app->total_slider->limits.max = app->control->num_stages;

  app_data_shade_next(app, TRUE);
  immediate_window_update(app->win, icon_Next);

  app_data_set_draggable_icon(app, "!install");
  immediate_window_update(app->win, icon_Draggable);

  if (!control_master_data_lookup_master_location(app->control))
    exit(1);

  msg = MsgTransDyn_Lookup(messages, "Installing", TRUE);
  if (!msg)
    exit(1);
  app_data_show_message(app, msg);
  free(msg);
  immediate_window_update(app->win, icon_Message);

  app_data_filename_writable(app, FALSE);
  msg = MsgTransDyn_Lookup(messages, "CopyingScripts", FALSE);
  if (msg)
  {
    app_data_set_filename_text(app, msg);
    free(msg);
  }

  /* Make master location directory */
  if (!fsutils_mkdir(app->control->master_location_dir))
    exit(1);

  immediate_window_update(app->win, icon_Filename);

  Event_Claim(event_NULL, event_ANY, event_ANY, install_scripts_handler, app);
}

static void read_current_location(app_data *app)
{
  int loc_num = app->control->installed_locations;
  control_location *loc = app->control->locations[loc_num];

  LOG("Getting pathname for location %d\n", loc_num);
  free(loc->pathname);
  loc->pathname =
       /*malloc(strlencr(Icon_GetTextPtr(app->win, icon_Filename)) + 1);*/
       malloc(256);
  if (!loc->pathname)
  {
    Error_ReportFatal(0, MSG_NOMEM);
  }
  Icon_GetText(app->win, icon_Filename, loc->pathname);
  LOG("Read pathname '%s' for location %d\n", loc->pathname, loc_num);
}

static void setup_new_location(app_data *app)
{
  int loc_num = app->control->installed_locations;
  control_location *loc = app->control->locations[loc_num];

  LOG("Setting up location %d\n", loc_num);
  app_data_set_draggable_icon(app, loc->icon);
  app_data_show_message(app, loc->prompt);
  app_data_set_filename_text(app,
                             loc->pathname ? loc->pathname : loc->leafname);
  app_data_shade_next(app, !loc->pathname);
}

static void release_location_handlers(app_data *app)
{
  Event_Release(event_KEY, app->win, icon_Filename, key_handler, app);
  Event_Release(event_CLICK, app->win, icon_Draggable,
                start_drag_handler, app);
  Event_Release(event_USERDRAG, event_ANY, event_ANY, end_drag_handler, app);
  EventMsg_Release(message_DATALOAD, app->win, dataload_handler);
  EventMsg_Release(message_DATASAVEACK, event_ANY, datasave_ack_handler);
  app_data_release_handlers(app);
}

static BOOL next_location(event_pollblock *e, void *h)
{
  app_data *app = h;
  (void) e;

  LOG("next_location called for location %d\n",
      app->control->installed_locations);
  if (app->control->installed_locations >= app->control->num_locations)
  {
    Error_ReportFatal(0,
          "Next location handler called after all locations done\n");
  }

  if (app->control->installed_locations != -1)
  {
    read_current_location(app);
  }

  while (++app->control->installed_locations < app->control->num_locations &&
         app->control->locations[app->control->installed_locations]->optional
              == control_OptionalPrune)
  {
    LOG("Skipping pruned location %d\n", app->control->installed_locations);
  }

  if (app->control->installed_locations >= app->control->num_locations)
  {
    LOG("All locations processed\n");
    release_location_handlers(app);
    app_data_shade_back(app, TRUE);
    start_installing(app);
    return TRUE;
  }

  setup_new_location(app);

  return TRUE;
}

static BOOL back_location(event_pollblock *e, void *h)
{
  app_data *app = h;
  (void) e;

  LOG("back_location called for location %d\n",
      app->control->installed_locations);

  if (app->control->installed_locations != app->control->num_locations &&
    check_filename_is_path(app))
  {
    read_current_location(app);
  }

  while (--app->control->installed_locations >= 0 &&
         app->control->locations[app->control->installed_locations]->optional
              == control_OptionalPrune)
  {
    LOG("Skipping pruned location %d\n", app->control->installed_locations);
  }

  if (app->control->installed_locations < 0)
  {
    LOG("Going back to optionals or whatever\n");
    release_location_handlers(app);
    app_data_set_filename_text(app, NULL);
    app_data_filename_writable(app, FALSE);
    return control_master_data_count_optional_items(app->control) ?
           setup_option_buttons(e, h) : main_welcome(e, h);
  }

  setup_new_location(app);

  return TRUE;
}

static void start_locations(app_data *app)
{
  app_data_toggle_draggable(app, TRUE);
  app_data_filename_writable(app, TRUE);

  app->control->installed_locations = -1;

  Event_Claim(event_KEY, app->win, icon_Filename, key_handler, app);
  Event_Claim(event_CLICK, app->win, icon_Draggable, start_drag_handler, app);
  app_data_set_next_handler(app, next_location);
  app_data_set_back_handler(app, back_location);
  Event_Claim(event_USERDRAG, event_ANY, event_ANY, end_drag_handler, app);
  EventMsg_Claim(message_DATALOAD, app->win, dataload_handler, app);
  EventMsg_Claim(message_DATASAVEACK, event_ANY, datasave_ack_handler, app);

  next_location(NULL, app);
}

static BOOL start_locations_handler(event_pollblock *e, void *h)
{
  app_data *app = h;
  (void) e;

  app_data_release_handlers(app);

  start_locations(h);

  return TRUE;
}

static void read_and_delete_option_buttons(app_data *app)
{
  int i;

  /* Make 2 passes to be on safe side in case of icon renumbering which
     shouldn't actually happen */
  for (i = 0; i < app->control->num_items; ++i)
  {
    if (app->control->items[i]->opt_icon != -1)
    {
      app->control->items[i]->optional = Icon_GetSelect(app->win,
                                         app->control->items[i]->opt_icon)
               ? control_OptionalOn : control_OptionalOff;
    }
  }
  for (i = 0; i < app->control->num_items; ++i)
  {
    if (app->control->items[i]->opt_icon != -1)
    {
      LOG("Deleting icon %d for item %d '%s'\n",
          app->control->items[i]->opt_icon,
          i, app->control->items[i]->name);
      Error_Check(Wimp_DeleteIcon(app->win, app->control->items[i]->opt_icon));
      app->control->items[i]->opt_icon = -1;
    }
  }
  app_data_toggle_message_help(app, TRUE);
}

static BOOL back_from_option_buttons(event_pollblock *e, void *h)
{
  app_data *app = h;
  (void) e;

  read_and_delete_option_buttons(app);
  app_data_release_handlers(app);
  return introduce_optionals(e, h);
}

static BOOL read_option_buttons(event_pollblock *e, void *h)
{
  app_data *app = h;
  (void) e;

  read_and_delete_option_buttons(app);
  app_data_toggle_message_help(app, TRUE);
  app_data_release_handlers(app);
  LOG("Read option buttons, starting on locations\n");
  start_locations(h);

  return TRUE;
}

static BOOL setup_option_buttons(event_pollblock *e, void *h)
{
  icon_block micon;
  int num;
  int i;
  int section_height;
  app_data *app = h;
  (void) e;

  app_data_blank_message(app);
  num = control_master_data_count_optional_items(app->control);
  LOG("Creating option buttons for %d optional items\n", num);
  Wimp_GetIconState(app->win, icon_Message, &micon);
  section_height = (micon.workarearect.max.y - micon.workarearect.min.y) / num;
  for (i = 0; i < num; ++i)
  {
    icon_createblock oicon;
    /* Equation could be mathematically simplified but we mustn't use FP
       in case compiled code is incompatible with old FPE modules */
    int midline = micon.workarearect.max.y - section_height * i -
                  section_height / 2;
    int item = control_master_data_get_nth_optional_item(app->control, i);

    oicon.window = app->win;
    oicon.icondata.workarearect.min.x = micon.workarearect.min.x;
    oicon.icondata.workarearect.max.x = micon.workarearect.max.x;
    oicon.icondata.workarearect.min.y = midline - 22;
    oicon.icondata.workarearect.max.y = midline + 22;

    oicon.icondata.flags.value = 0;
    oicon.icondata.flags.data.text = 1;
    oicon.icondata.flags.data.sprite = 1;
    oicon.icondata.flags.data.vcentre = 1;
    oicon.icondata.flags.data.indirected = 1;
    oicon.icondata.flags.data.buttontype = iconbtype_RADIO;
    oicon.icondata.flags.data.selected = CONTROL_ITEM_OPTIONAL(app->control,
                                                               item)
                                         == control_OptionalOn;
    oicon.icondata.flags.data.foreground = colour_BLACK;
    oicon.icondata.flags.data.background = colour_GREY1;
    oicon.icondata.data.indirecttext.buffer = app->control->items[item]->name;
    oicon.icondata.data.indirecttext.validstring = (char *) "Soptoff,opton";
    oicon.icondata.data.indirecttext.bufflen =
          strlen(app->control->items[item]->name) + 1;
    Error_CheckFatal(Wimp_CreateIcon(&oicon,
                                     &app->control->items[item]->opt_icon));
    LOG("Optional %d is item %d '%s', icon %d\n",
        i, item,
        app->control->items[item]->name,
        app->control->items[item]->opt_icon);
  }

  LOGQ("\n");

  app_data_toggle_message_help(app, FALSE);

  app_data_release_handlers(app);
  app_data_set_next_handler(app, read_option_buttons);
  app_data_set_back_handler(app, back_from_option_buttons);

  return TRUE;
}

static BOOL introduce_optionals(event_pollblock *e, void *h)
{
  char *msg = MsgTransDyn_Lookup(messages, "OptionalsIntro", TRUE);
  app_data *app = h;
  (void) e;

  if (!msg)
    exit(1);
  app_data_show_message(app, msg);
  free(msg);

  app_data_release_handlers(app);
  app_data_set_next_handler(app, setup_option_buttons);
  app_data_shade_back(app, FALSE);
  app_data_set_back_handler(app, main_welcome);

  return TRUE;
}

static BOOL help_handler(event_pollblock *event, void *h)
{
  app_data *app = h;
  icon_handle icon = event->data.message.data.helprequest.where.icon;
  const char *help_text = NULL;

  if (icon < 0)
  {
    help_text = app->background_help;
  }
  else if (icon < icon_NumIcons)
  {
    help_text = app->icon_help[icon];
  }
  else
  {
    int i;

    for (i = 0; i < app->control->num_items; ++i)
    {
      if (icon == app->control->items[i]->opt_icon)
      {
        help_text = app->control->items[i]->desc;
      }
    }
  }

  if (!help_text)
    help_text = app->background_help;

  if (help_text)
  {
    event->data.message.header.yourref = event->data.message.header.myref;
    event->data.message.header.myref = 0;
    event->data.message.header.action = message_HELPREPLY;
    event->data.message.header.size = ((sizeof(message_header) +
                                        strlen(help_text) + 1) + 3)
                                      & ~3;
    strcpy(event->data.message.data.helpreply.text, help_text);
    Error_Check(Wimp_SendMessage(event_USERMESSAGE, &event->data.message,
                                 event->data.message.header.sender, -1));
  }

  return TRUE;
}

/* Show welcome message and set up handler for next stage */
static BOOL main_welcome(event_pollblock *e, void *h)
{
  app_data *app = h;
  char *welcome = control_msgtrans_lookup(app->control->msgs, "Welcome", TRUE);
  (void) e;

  if (!welcome)
    exit(1);
  app_data_show_message(app, welcome);
  free(welcome);

  app_data_release_handlers(app);
  app_data_shade_back(app, TRUE);
  app_data_set_next_handler(app,
              control_master_data_count_optional_items(app->control) ?
                      introduce_optionals : start_locations_handler);
  return TRUE;
}

static BOOL menu_select_handler(event_pollblock *e,void *h)
{
  mouse_block ptrinfo;
  (void) h;

  Wimp_GetPointerInfo(&ptrinfo);

  if (e->data.selection[0] == 1)
  {
    exit(0);
  }

  if (ptrinfo.button.data.adjust)
    Menu_ShowLast();

  return TRUE;
}


static BOOL menu_open_handler(event_pollblock *e, void *h)
{
  app_data *app = h;

  if (e->data.mouse.button.value != button_MENU)
    return FALSE;
  Menu_Show(app->menu, e->data.mouse.pos.x - 40, e->data.mouse.pos.y + 8);
  return TRUE;
}


int main()
{
  app_data *app;
  window_block *wdef;
  char datestr[40];

#ifdef MemCheck_MEMCHECK
  MemCheck_Init();
  MemCheck_InterceptSCLStringFunctions();
  /*Without this we get too many false alarms from stuff allocated by DeskLib*/
  MemCheck_SetReadChecking(0);
#endif

  Resource_Initialise(APPNAME);
  Resource_LoadSprites();

  Error_CheckFatal(MsgTrans_LoadFile(&messages, APPDIR ".Messages"));

  /* Read CSD for restoring later */
  /*
  Error_CheckFatal(SWI(3, 0, SWI_OS_GBPB, 6, 0, csd_buffer));
  original_csd[csd_buffer[1]] = 0;
  LOG("Read CSD %s\n", original_csd);
  atexit(restore_csd);
  */

  app = app_data_new();
  if (!app)
    exit(1);

  app->control = control_master_data_new_with_lookup(APPDIR ".Control");
  if (!app->control)
    exit(1);
  control_master_data_prune_items(app->control, FALSE);

  Event_Initialise3(APPNAME, 310, (int *) wanted_messages);
  EventMsg_Initialise();

  Template_Initialise();
  Template_UseSpriteArea(resource_sprites);
  Template_LoadFile("Templates");
  /* Template_UseSpriteArea and Template_LinkSpriteArea don't work for icons */
  wdef = Template_Find("main");
  if (!wdef)
    MsgTrans_Report(messages, "NoWin", TRUE);
  ((icon_block *) (wdef + 1))[icon_Draggable].data.indirectsprite.spritearea =
          resource_sprites;

  app->win = Window_CreateOrig("main");
  if (!app->win)
    MsgTrans_Report(messages, "NoWin", TRUE);

  app->proginfo = Window_CreateOrig("ProgInfo");
  if (!app->proginfo)
    MsgTrans_Report(messages, "NoProgInfo", TRUE);
  SWI(4, 0, Territory_ConvertStandardDate,
  	-1, &version_utc, datestr, sizeof(datestr));
  Icon_printf(app->proginfo, 0, "%s (%s)", version_string, datestr);

  app->menu = Menu_New(MsgTransDyn_Lookup(messages, "AppName", TRUE),
                       MsgTransDyn_Lookup(messages, "Menu", TRUE));
  Menu_AddSubWindow(app->menu, 0, app->proginfo);

  if (!app_data_create_sliders(app))
    exit(1);
  app_data_lookup_help(app);
  app_data_toggle_draggable(app, FALSE);


  Event_Claim(event_OPEN, app->win, event_ANY, Handler_OpenWindow, 0);
  Event_Claim(event_REDRAW, app->win, event_ANY, redraw_handler, app);
  EventMsg_Claim(message_HELPREQUEST, app->win, help_handler, app);
  /* Handle is used to distinguish between click and message */
  Event_Claim(event_CLICK, app->win, icon_Cancel, cancel_handler, app);
  EventMsg_Claim(message_QUIT, event_ANY, cancel_handler, 0);
  Event_Claim(event_CLICK, app->win, event_ANY, menu_open_handler, app);
  Event_Claim(event_MENU,event_ANY,event_ANY,menu_select_handler,0);

  Window_Show(app->win, open_CENTERED);

  app_data_filename_writable(app, FALSE);
  main_welcome(NULL, app);

  for(;;)
    Event_Poll();

  return 0;
}

static BOOL key_handler(event_pollblock *e, void *h)
{
  app_data *app = h;
  (void) e;

  check_filename_is_path(app);

  return TRUE;
}

static BOOL dataload_handler(event_pollblock *e, void *h)
{
  app_data *app = h;

  if (e->data.message.data.dataload.filetype != 0x1000)
  {
    MsgTrans_Report(messages, "NotDir", FALSE);
    return TRUE;
  }

  Str_MakeASCIIZ(e->data.message.data.dataload.filename, 212);
  Icon_printf(app->win, icon_Filename, "%s.%s",
              e->data.message.data.dataload.filename,
              app->control->locations[app->control->installed_locations]->
                   leafname);
  check_filename_is_path(app);

  return TRUE;
}

static BOOL start_drag_handler(event_pollblock *e, void *h)
{
  (void) h;

  if (e->data.mouse.button.value != button_DRAGADJUST &&
      e->data.mouse.button.value != button_DRAGSELECT)
  {
    return FALSE;
  }

  DragASprite_DragIcon(e->data.mouse.window, e->data.mouse.icon);

  return TRUE;
}

static BOOL end_drag_handler(event_pollblock *e, void *h)
{
  mouse_block ptrinfo;
  message_block message;
  app_data *app = h;
  const char *leafname =
        app->control->locations[app->control->installed_locations]->leafname;
  (void) e;

  LOG("Drag ended, ");

  Wimp_GetPointerInfo(&ptrinfo);
  message.header.size = sizeof(message_header) + sizeof(message_datasave);
  message.header.yourref = 0;
  message.header.action = message_DATASAVE;
  message.data.datasave.window = ptrinfo.window;
  message.data.datasave.icon = ptrinfo.icon;
  message.data.datasave.pos = ptrinfo.pos;
  message.data.datasave.estsize = 0;
  if (leafname[0] == '!')
    message.data.datasave.filetype = 0x2000;
  else
    message.data.datasave.filetype = 0x1000;
  strcpy(message.data.datasave.leafname, leafname);
  Error_Check(Wimp_SendMessage(event_USERMESSAGERECORDED, &message,
              ptrinfo.window, ptrinfo.icon));
  app->datasave_ref = message.header.myref;

  LOG("sending DataSave message with ref %d\n", app->datasave_ref);

  return TRUE;
}

static BOOL datasave_ack_handler(event_pollblock *e, void *h)
{
  app_data *app = h;

  LOG("Got DataSave ack message with ref %d\n", e->data.message.header.yourref);
  if (e->data.message.header.yourref != app->datasave_ref)
  {
    LOG("Wrong ref, ignoring\n");
    return FALSE;
  }

  /* Doesn't matter if terminator is NZ, because Icon_SetText uses strncpycr */
  app_data_set_filename_text(app, e->data.message.data.datasaveack.filename);
  check_filename_is_path(app);

  return TRUE;
}
