/* main.c for installer */

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

#include "DeskLib:Error.h"
#include "DeskLib:Event.h"
#include "DeskLib:EventMsg.h"
#include "DeskLib:Handler.h"
#include "DeskLib:Hourglass.h"
#include "DeskLib:Icon.h"
#include "DeskLib:KernelSWIs.h"
#include "DeskLib:MsgTrans.h"
#include "DeskLib:Resource.h"
#include "DeskLib:SWI.h"
#include "DeskLib:Template.h"
#include "DeskLib:WimpSWIs.h"
#include "DeskLib:Window.h"

#define APPNAME "nstall"
#define APPDIR "<nstall$Dir>"
#define SYSTEM "!System"
#define SYSLEAF ".!System"

/* Flags for OS_FSControl 26 */
const int CopyFlags = 	(1 << 12) |	/* Newer */
			(1 << 1)  |	/* Force */
			(1 << 0);	/* Recurse */

const int our_messages[] = {
  				message_DATALOAD,
  				message_DATALOADACK,
  				message_DATASAVE,
  				message_DATASAVEACK,
  				message_QUIT
};

msgtrans_filedesc *fd;

/* Current Set Directory */
char csd[256];

/* Total number of discs to install excluding System */
int total_discs;

/* Stage reached (disc number or 0 for !System) */
int stage;

/* Whether drags are allowed */
BOOL allow_drag = TRUE;

/* Leafname of thing being dragged */
char drag_leaf[12];

/* Ref for DataSave message */
int myref;

window_handle main_window;
typedef enum {
  Drag,
  Message,
  Path,
  Next,
  Cancel
} main_icons;

void exit_handler()
{
  MsgTrans_LoseFile(fd);
  /* Restore CSD */
  SWI(2, 0, SWI_OS_FSControl, 0, csd + 1);
}

BOOL cancel_quit(event_pollblock *e, void *h)
{
  if (e->data.mouse.button.value == button_MENU)
    return FALSE;
  Event_CloseDown();
  return TRUE;
}

/* Set various icons */
void TellUser(char *m)
{
/*
  icon_block istate;
  window_redrawblock redraw;
  BOOL more;
*/

  Icon_SetText(main_window, Message, m);
  /* Allow icon to be updated */
/*
  Wimp_GetIconState(main_window, Message, &istate);
  redraw.window = main_window;
  redraw.rect = istate.workarearect;
  Wimp_UpdateWindow(&redraw, &more);
  while (more)
  {
    Wimp_PlotIcon(&istate);
    Wimp_GetRectangle(&redraw, &more);
  }
*/
  event_mask.data.null = 0;
  Event_Poll();
  event_mask.data.null = 1;
}

#define TellPath(p) Icon_SetText(main_window, Path, (p))

void SetDrag(char *spritename)
{
  char *p = Icon_GetTextPtr(main_window, Drag);
  if (!p)
    Error_Report(0, "Can't write draggable sprite name");
  strcpy(p, spritename);
  Icon_ForceWindowRedraw(main_window, Drag);
}

/* Fade or unfade Next button */
#define FadeCont(o) Icon_SetShade(main_window, Next, (o))

/* Make a path name ADFS::*.$. where * is disc title */
#define PathFromTitle(t, b) sprintf((b), "ADFS::%s.$.", (t))

/* Prompt user for next disc, or to tell where to install */
void next_stage(void);

/* Copy from source to dest and boot if desired */
BOOL install(char *source, char *dest, BOOL boot)
{
  char *leaf;
  if (stage == 0)
  {
    /* Add wildcard leaves to avoid stamping !System */
    strcat(source, ".*");
    strcat(dest, ".*");
  }
  if (Error_Check(SWI(4, 0, SWI_OS_FSControl, 26,
  	source, dest, CopyFlags)))
    return FALSE;
  if (stage == 0)
  {
    leaf = ".!Run";
    /* Unset System$Path to allow !System to be rebooted */
    SWI(5, 0, SWI_OS_SetVarVal, "System$Path", 0, -1, 0, 0);
    /* Remove wildcard from dest */
    dest[strlen(dest)-2] = 0;
  }
  else
    leaf = ".!Boot";
  if (boot)
  {
    strcat(dest, leaf);
    Error_Check(Wimp_StartTask(dest));
  }
  return TRUE;
}

/* Found correct disc, now start installing */
void disc_is_in()
{
  static char system_dir[256];
  char buffer[256];
  char tag[16];
  BOOL failed = FALSE;
  MsgTrans_Lookup(fd, "Wait", buffer, 256);
  TellUser(buffer);
  Hourglass_On();
  if (stage == 0)
  {
    int obj_type;
    MsgTrans_Lookup(fd, "SysTitle", tag, 16);
    PathFromTitle(tag, buffer);
    strcat(buffer, SYSTEM);
    Icon_GetText(main_window, Path, system_dir);
/*Error_Report(0, "Testing for existence of \"%s\"", system_dir);*/
    /* Create "!System" directory if necessary */
    SWI(2, 1, SWI_OS_File, 5, system_dir, &obj_type);
    if (obj_type != 2 && obj_type != 3)
    {
      /* Dest "!System" doesn't exist as directory so create it */
      *strrchr(system_dir, '.') = 0;
/*Error_Report(0, "Doesn't exist, setting CSD to \"%s\"", system_dir);*/
      if (Error_Check(SWI(2, 0, SWI_OS_FSControl, 0, system_dir)))
{
/*Error_Report(0, "Couldn't set CSD");*/
        failed = TRUE;
}
      else
{
/*Error_Report(0, "Creating directory \"%s\"", SYSTEM);*/
      if (Error_Check(SWI(5, 0, SWI_OS_File, 8, SYSTEM, 0,0, 0)))
        failed = TRUE;
}
      strcat(system_dir, SYSLEAF);
    }
    if (!failed)
    {
      if (!install(buffer, system_dir, TRUE))
        failed = TRUE;
      /* Strip leaf from system dir */
      *strrchr(system_dir, '.') = 0;
      /* Twice (!) because installation adds .!Boot */
      *strrchr(system_dir, '.') = 0;
    }
  }
  else
  {
    int items;
    int item;
    /* Create dest directory if disc 1 */
    if (stage == 1)
    {
      Icon_GetText(main_window, Path, buffer);
      /* Set CSD to parent of install directory */
      *strrchr(buffer, '.') = 0;
      if (Error_Check(SWI(2, 0, SWI_OS_FSControl, 0, buffer)))
        failed = TRUE;
      else if (Error_Check(SWI(5, 0, SWI_OS_File, 8, drag_leaf, 0,0, 0)))
        failed = TRUE;
    }
    if (!failed)
    {
      sprintf(tag, "Items%d", stage);
      MsgTrans_Lookup(fd, tag, buffer, 256);
      sscanf(buffer, "%d", &items);
      for (item = 1; item <= items; ++item)
      {
        char leaf[32];
        char source[128];
        BOOL dot, ntsys;
        char *leafp = leaf;
        sprintf(tag, "Title%d", stage);
        MsgTrans_Lookup(fd, tag, leaf, 32);
        PathFromTitle(leaf, source);
        sprintf(tag, "Disc%dItem%d", stage, item);
        MsgTrans_Lookup(fd, tag, leaf, 32);
        ntsys = (leaf[0] == '*');
        if (ntsys)
          ++leafp;
        dot = (leafp[0] == '.');
        strcat(source, dot ? leafp + 1 : leafp);
        if (ntsys)
          strcpy(buffer, system_dir);
        else
          Icon_GetText(main_window, Path, buffer);
        if (!dot)
          strcat(buffer, ".");
        strcat(buffer, leafp);
        if (!install(source, buffer, dot))
        {
          failed = TRUE;
          break;
        }
      }
    }
  }
  OS_CLI("Dismount 0");
  Hourglass_Off();
  if (failed)
  {
    MsgTrans_Lookup(fd, "Failed", buffer, 256);
    TellUser(buffer);
    FadeCont(1);
  }
  else
    next_stage();
}

/* Check whether the correct disc is present
   titletag is msg tag for disc title */
BOOL check_disc(char *titletag)
{
  char buffer[128];
  char title[12];
  Error_CheckFatal(OS_CLI("ADFS"));
  if (OS_CLI("Mount 0"))
    return FALSE;
  /* Set CSD to floppy */
  if (SWI(2, 0, SWI_OS_FSControl, 0, "ADFS::0.$"))
    return FALSE;
  /* Check disc title */
  if (Error_Check(SWI(3, 0, SWI_OS_GBPB, 5, 0, buffer)))
    return FALSE;

  buffer[buffer[0] + 1] = 0;

  MsgTrans_Lookup(fd, titletag, title, 12);
  return (strcmp(title, buffer + 1) == 0);
}

/* User has clicked Next to start installing, if handle != 0 this is
   after being prompted  to insert a disc */
BOOL clicked_go(event_pollblock *event, void *hanndle);

/* Prompt for a disc to be inserted */
/* titletag = msg tag for disc title, nametag = msgtag for long disc name
   again is TRUE if this is a retry */
void prompt_disc(char *titletag, char *nametag, BOOL again)
{
  char name[64];
  char message[256];
  check_disc(titletag);
  if (check_disc(titletag))
  {
    disc_is_in();
    return;
  }
  OS_CLI("Dismount 0");
  MsgTrans_Lookup(fd, nametag, name, 64);
  MsgTrans_LookupPS(fd, again?"WrongIns":"Insert", message, 256, name, 0,0,0);
  TellUser(message);
  Event_Claim(event_CLICK, main_window, Next, clicked_go, (void *) TRUE);
}

/* Check whether there is a valid known system */
BOOL valid_system(char *mastsys, int bufsize)
{
  char newsys[100];
  char systitle[12];

  if (!OS_ReadVarVal("System$Dir", mastsys, bufsize))
    return FALSE;
  MsgTrans_Lookup(fd, "SysTitle", systitle, 12);
  PathFromTitle(systitle, newsys);
  return (strcmp(mastsys, newsys) != 0);
}

/* Sets path icon and takes further action depending on stage */
void set_path(const char *path)
{
  Icon_SetText(main_window, Path, (char *) path);
  FadeCont(0);
  Event_Claim(event_CLICK, main_window, Next, clicked_go, 0);
}

/* Handle !System being dragged in */
BOOL drag_in(event_pollblock *event, void *h)
{
  char *leaf;
  if (event->data.message.data.dataload.filetype != 0x2000)
    return TRUE;
  leaf = strrchr(event->data.message.data.dataload.filename, '.');
  if (!leaf++)
    return TRUE;
  if (strcmp(leaf, SYSTEM))
    return TRUE;
  set_path(event->data.message.data.dataload.filename);
  return TRUE;
}

/* User has clicked Next to start installing, if handle != 0 this is
   after being prompted  to insert a disc */
BOOL clicked_go(event_pollblock *event, void *handle)
{
  char title[16];
  char name[16];
  allow_drag = FALSE;
  Event_Release(event_CLICK, main_window, Next, clicked_go, handle);
  if (stage == 0)
  {
    EventMsg_Release(message_DATALOAD, event_ANY, drag_in);
    strcpy(title, "SysTitle");
    strcpy(name, "SysDisc");
  }
  else
  {
    sprintf(title, "Title%d", stage);
    sprintf(name, "Disc%d", stage);
  }
  SetDrag("!install");
  prompt_disc(title, name, (BOOL) handle);
  return TRUE;
}

/* Handle filer replying to save message */
BOOL save_reply(event_pollblock *event, void *h)
{
  if (event->data.message.header.yourref != myref)
    return FALSE;
  set_path(event->data.message.data.datasaveack.filename);
  return TRUE;
}

/* Ask user to check !System */
void do_system()
{
  char txtbuf[256];
  char mastsys[256];
  if (valid_system(mastsys, 256))
  {
    MsgTrans_Lookup(fd, "FoundSys", txtbuf, 256);
    set_path(mastsys);
  }
  else
  {
    MsgTrans_Lookup(fd, "NoSys", txtbuf, 256);
    FadeCont(1);
  }
  TellUser(txtbuf);
  SetDrag(SYSTEM);  /* I think we can get away with abusing sprite's case */
  strcpy(drag_leaf, SYSTEM);
  EventMsg_Claim(message_DATALOAD, main_window, drag_in, 0);
}

BOOL open_and_quit(event_pollblock *event, void *h)
{
  char fod[256];

  strcpy(fod, "Filer_OpenDir ");
  Icon_GetText(main_window, Path, fod + strlen(fod));
  Wimp_StartTask(fod);
  Event_CloseDown();
  return TRUE;
}

void next_stage()
{
  char buffer[256];
  if (++stage > total_discs)
  {
    MsgTrans_Lookup(fd, "Complete", buffer, 256);
    TellUser(buffer);
    Event_Claim(event_CLICK, main_window, Next, open_and_quit, 0);
  }
  else if (stage != 1)
  {
    char titletag[16];
    char nametag[16];
    sprintf(titletag, "Title%d", stage);
    sprintf(nametag, "Disc%d", stage);
    prompt_disc(titletag, nametag, FALSE);
  }
  else
  {
    allow_drag = 1;
    FadeCont(1);
    MsgTrans_Lookup(fd, "DragPrompt", buffer, 256);
    TellUser(buffer);
    SetDrag("directory");
    MsgTrans_Lookup(fd, "DirLeaf", drag_leaf, sizeof(drag_leaf));
    TellPath(drag_leaf);
  }
}

/* Handles drag mouse events on draggable icon */
BOOL start_drag(event_pollblock *event, void *h)
{
  if (event->data.mouse.button.value != button_DRAGADJUST &&
  	event->data.mouse.button.value != button_DRAGSELECT)
    return FALSE;
  if (allow_drag)
    DragASprite_DragIcon(main_window, Drag);
  return TRUE;
}

BOOL drag_complete(event_pollblock *event, void *h)
{
  mouse_block ptrinfo;
  message_block message;
  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 (stage == 0)
    message.data.datasave.filetype = 0x2000;
  else
    message.data.datasave.filetype = 0x1000;
  strcpy(message.data.datasave.leafname, drag_leaf);
  Error_Check(Wimp_SendMessage(event_USERMESSAGERECORDED, &message,
  	ptrinfo.window, ptrinfo.icon));
  myref = message.header.myref;
  return TRUE;
}

int main()
{
  char dosys[4];

  Resource_Initialise(APPNAME);
  Event_Initialise3(APPNAME, 310, (int *) our_messages);
  EventMsg_Initialise();

  /* Read CSD */
  Error_CheckFatal(SWI(3, 0, SWI_OS_GBPB, 6, 0, csd));
  csd[csd[0] + 1] = 0;

  Error_CheckFatal(MsgTrans_LoadFile(&fd, APPDIR ".Messages"));
  atexit(exit_handler);
  Template_Initialise();
  Template_LoadFile("Templates");
  main_window = Window_CreateOrig("main");
  if (!main_window)
    MsgTrans_Report(fd, "NoWin", 1);

  Event_Claim(event_OPEN, main_window, event_ANY, Handler_OpenWindow, 0);
  Event_Claim(event_REDRAW, main_window, event_ANY, Handler_NullRedraw, 0);
  Event_Claim(event_CLICK, main_window, Cancel, cancel_quit, 0);
  Event_Claim(event_CLICK, main_window, Drag, start_drag, 0);
  Event_Claim(event_USERDRAG, event_ANY, event_ANY, drag_complete, 0);
  EventMsg_Claim(message_DATASAVEACK, event_ANY, save_reply, 0);

  MsgTrans_Lookup(fd, "Discs", dosys, 4);
  sscanf(dosys, "%d", &total_discs);
  MsgTrans_Lookup(fd, "System", dosys, 4);
  stage = 0;
  if (dosys[0] != 'N')
    do_system();
  else if (total_discs)
    next_stage();

  Window_Show(main_window, open_CENTERED);

  for(;;)
    Event_Poll();
}
