/*-*-C-*-
 *
 * ResEd shell: main
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "dbox.h"
#include "dragdrop.h"
#include "filetypes.h"
#include "focus.h"
#include "interactor.h"
#include "menu.h"
#include "registry.h"
#include "saveas.h"
#include "version.h"

#include "class.h"
#include "document.h"
#include "copy.h"
#include "fileinfo.h"
#include "genmsgs.h"
#include "help.h"
#include "icondefs.h"
#include "objflags.h"
#include "protocol.h"
#include "rename.h"


/* Globals */

char appdir[FILENAMELEN];       /* contents of Resed$Dir */
int taskhandle;
int iconhandle;
MsgRec msgs;                    /* global messages file */
Bool privateflag;


/* Statics */

static unsigned int eventmask = (BIT(EV_NULL_REASON_CODE) |
                                 BIT(EV_POINTER_LEAVING_WINDOW) |
                                 BIT(EV_POINTER_ENTERING_WINDOW) );
static MenuPtr iconmenu = NULL;
static WindowPtr infowin = NULL;
static WindowPtr exitwarnwin = NULL;


/* Constants */

#define ICONMENU_INFO 0
#define ICONMENU_NEW 1
#define ICONMENU_PALETTE 2
#define ICONMENU_QUIT 3


/*
 * Safe exit.
 */

static int sender = 0;
static unsigned int prequitflags;

static error * exit_interactor (unsigned int event, int *buf, void *closure, Bool *consumed)
{
    MouseClickPtr mouse = (MouseClickPtr) buf;
    WindowPtr win = (WindowPtr) buf;         /* only half there */
    MessagePtr mess = (MessagePtr) buf;

    if (buf == NULL)                     /* we are being asked to cancel */
    {
        swi (Wimp_CreateMenu,  R1, -1,  END);
        return NULL;
    }

    switch (event)
    {
    case EV_OPEN_WINDOW_REQUEST:
        if (win->handle == exitwarnwin->handle)
        {
            *consumed = TRUE;
            return swi (Wimp_OpenWindow, R1, win, END);
        }
        break;

    case EV_MOUSE_CLICK:
        if (mouse->windowhandle == exitwarnwin->handle)
        {
            *consumed = TRUE;
            switch (mouse->iconhandle)
            {
            case I_EXITWARN_DISCARD:
                interactor_cancel ();
                /* If it was a PreQuit message from a ShutDown, then continue with that protocol */
                if (sender != 0 && (prequitflags & BIT(0)) == 0)
                {
                    KeyPressRec key;
                    document_free_all ();
                    ER ( swi (Wimp_GetCaretPosition,  R1, &key.caret,  END) );
                    key.code = 0x1FC;     /* CTRL-SHIFT-F12 */
                    ER ( swi (Wimp_SendMessage,  R0, EV_KEY_PRESSED,  R1, &key,  R2, sender,  END) );
                    return NULL;
                }
                error_exit (0);
                /* NOTREACHED */
            case I_EXITWARN_CANCEL:
                interactor_cancel ();
            }
        }
        break;

    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        if (mess->code == MESSAGE_MENUS_DELETED &&
                            buf[5] == exitwarnwin->handle)
        {
            interactor_cancel();
            *consumed = TRUE;
        }
        break;
    }

    return NULL;
}


/*
 * If msg is NULL, the user is trying to quit.  If it's non-zero,
 * then it is the task handle of the task which is trying to perform
 * a pre-quit.
 */

static error * warn_exit (MessagePreQuitPtr prequit)
{
    int num = 0, i = 0;
    RegistryType type;
    void *closure;
    char *s = dbox_getstring (exitwarnwin, I_EXITWARN_MESSAGE);

#ifdef NOBBLE      /* never offer save opportunity in nobbled version */

    if (prequit)
        return NULL;
    else
        error_exit (0);

#endif

    if (prequit)
    {
        sender = prequit->header.taskhandle;
        prequitflags = prequit->header.size > 20 ? prequit->flags : 0;
    }
    else
        sender = 0;

    while ((i = registry_enumerate_windows (i, &type, NULL, &closure)) != 0)
    {
        if (type == Document)
        {
            DocumentPtr doc = (DocumentPtr) closure;
            if (doc->modified)
                num++;
        }
    }

    if (num == 0)
    {
        if (prequit)
            return NULL;
        else
            error_exit (0);
    }

    /* If it's a PreQuit, then halt the protocol */
    if (prequit)
    {
        prequit->header.yourref = prequit->header.myref;
        ER ( swi (Wimp_SendMessage,  R0, EV_USER_MESSAGE_ACKNOWLEDGE,  R1, prequit,  R2, sender,  END) );
    }

    if (exitwarnwin->handle <= 0)
    {
        ER ( swi (Wimp_CreateWindow,  R1, &exitwarnwin->visarea,
                  OUT,  R0, &exitwarnwin->handle,  END) );

        /* register window - for interactive help */
        ER ( registry_register_window(exitwarnwin->handle, ExitWarnDbox, (void *) exitwarnwin) );
    }
    sprintf (s, message_lookup (&msgs, num == 1 ? "ExitWarnS" : "ExitWarnP"), num);
    dbox_setstring (exitwarnwin, I_EXITWARN_MESSAGE, s);
    interactor_install (exit_interactor, NULL);
    ER ( swi (Wimp_CreateMenu,  R1, exitwarnwin->handle,
              R2, exitwarnwin->visarea.minx,  R3, exitwarnwin->visarea.maxy,  END) );
    return NULL;
}
    

/*
 * Handling the icon menu
 */

static error * create_iconmenu ()
{
    ER ( menu_create (4, message_lookup (&msgs, "IconMenu"), &iconmenu) );
    ER ( menu_entry (iconmenu, ICONMENU_INFO, message_lookup (&msgs, "Info"),
                     0, 0, -1, -1, (void *) infowin->handle) );
    ER ( menu_entry (iconmenu, ICONMENU_NEW, message_lookup (&msgs, "New"),
                     0, 0, -1, -1, NULL) );
    ER ( menu_entry (iconmenu, ICONMENU_PALETTE, message_lookup (&msgs, "Palette"),
                     0, 0, -1, -1, NULL) );
    ER ( menu_entry (iconmenu, ICONMENU_QUIT, message_lookup (&msgs, "Quit"),
                     0, 0, -1, -1, NULL) );
    ER ( menu_register (iconmenu, ICONBAR_MENU) );
    return NULL;
}


/*
 * Callback for the Icon Menu
 */

static error * iconmenu_cb (MenuPtr menu, int *buf, void *closure, Bool adjust)
{
    if (buf)
        switch (*buf)
        {
        DocumentPtr newdoc;
        case ICONMENU_NEW:
            ER ( document_create(&newdoc, "") );
            break;
        case ICONMENU_PALETTE:
            ER ( document_create_palette () );
            break;
        case ICONMENU_QUIT:
            return warn_exit (NULL);
            break;
        }
    return NULL;
}


/*
 * Respond to mouse click events in any window
 */

static error * mouse_click (int *buf)
{
    MouseClickPtr mouse = (MouseClickPtr) buf;
    unsigned int modifiers = wimp_read_modifiers ();

    if (mouse->windowhandle == -2)
    {
        DocumentPtr newdoc;
        switch (mouse->buttons)
        {
        case MB_CLICK(MB_SELECT):
            ER ( document_create(&newdoc, "") );
            break;
        case MB_CLICK(MB_MENU):
            ER ( menu_post (iconmenu, &mouse->position, TRUE, iconmenu_cb, NULL) );
            break;
        case MB_CLICK(MB_ADJUST):
            ER ( document_create_palette() );
            break;
        }
    }
    else
    {
        void *closure;
        RegistryType type = registry_lookup_window(mouse->windowhandle, &closure);
        switch (type)
        {
        case Document:
            ER ( document_mouse_click (mouse, modifiers, (DocumentPtr) closure) );
            break;
        }
    }
    return NULL;
}



/*
 * Handle keypresses.  ESCAPE cancels the current interactor.
 * Other keys may be handled by individual window types,
 * and unknown ones are passed back to the Wimp.
 */

static error * key_pressed (int *buf)
{
    KeyPressPtr key = (KeyPressPtr) buf;
    void *closure;
    Bool consumed = FALSE;

    if (key->code == 0x1b)
    {
        interactor_cancel ();
        consumed = TRUE;
    }
    else
    {
        switch (registry_lookup_window (key->caret.windowhandle, &closure))
        {
        case Document:
            ER ( document_key_pressed ((DocumentPtr) closure, key, &consumed) );
            break;
        }
    }

    if (!consumed)
    {
        ER ( swi (Wimp_ProcessKey,  R0, key->code,  END) );
    }
    return NULL;
}


/*
 * Someone is trying to do a DataSave to us.  If myref non-zero
 * then it's as a result of a dragdrop operation that we claimed.
 * If this is the case then the window-type-specific code
 * may need to remove ghost caret information, etc.
 *
 * If yourref is non-zero (equal to dragdrop_claimref())
 * then this DataSave is in repsonse to the MessageDragging protocol,
 * so dragdrop_cancel() should be called.  If yourref is 0
 * it's a regular DataSave that did not stem from MessageDragging.
 *
 * If the type is Resource, then attempt to use the Ram Transfer
 * protocol rather then the scrap file one.  If the originator
 * does not want to do Ram Transfer we revert to Scrap File.
 * The following variables are used to remember enough details
 * of the DataSave message so that we can make the DataSaveAck
 * message a proper reply to it, if RamFetch bounces.
 */

static int datasave_taskhandle;
static int datasave_myref;
static int datasave_estsize;
static int datasave_windowhandle;
static int datasave_iconhandle;
static PointRec datasave_position;
static int ramfetch_myref;
static char *rambuf = NULL;
static int rambuflen, rampos;

#define RAMBUFQUANTA 256


static error * datasave_message (MessageDataSavePtr req)
{
/*    dprintf("Recvd DATA_SAVE with yourref %d\n" _ req->header.yourref); */
    if (req->header.yourref != 0)
        dragdrop_cancel ();     /* causes window-specific code to undraw ghost caret etc */

    switch (registry_lookup_window (req->windowhandle, NULL))
    {
    case Document:
        break;
    default:
        if (!(req->windowhandle == -2 && req->iconhandle == iconhandle))
            return NULL;
    }

    if (req->filetype == FILETYPE_SPRITE || req->filetype == FILETYPE_TEXT)
    {
        req->header.yourref = req->header.myref;
        req->estsize = -1;              /* insecure destination */
        strcpy(req->leafname, "<Wimp$Scrap>");
        req->header.size = 256;
        req->header.messageid = MESSAGE_DATA_SAVE_ACK;
        ER ( swi (Wimp_SendMessage,
                  R0, EV_USER_MESSAGE,
                  R1, req,
                  R2, req->header.taskhandle,  END) );
    }
    else if (req->filetype == FILETYPE_RESOURCE)
    {
        MessageRamFetchRec fetch;

        /* Save all the info we will need so that if the RamFetch bounces we
         * will be able to try DataSaveAck instead
         */
        datasave_taskhandle = req->header.taskhandle;
        datasave_myref = req->header.myref;
        datasave_estsize = req->estsize;
        datasave_windowhandle = req->windowhandle;
        datasave_iconhandle = req->iconhandle;
        datasave_position = req->position;

        if (rambuf)
            free (rambuf);
        /*
         * Note the +1 below.  This is so that in the (hopefully) common
         * case, where the estsize is correct, only one exchange of messages
         * will take place and there will be no need to realloc rambuf
         */
        rambuflen = MAX (req->estsize + 1, RAMBUFQUANTA);
        rambuf = malloc (rambuflen);
        if (rambuf == NULL)
            return error_lookup ("NoMem");
        rampos = 0;

        /* Reply with a RamFetch message */
        fetch.header.size = sizeof (MessageRamFetchRec);
        fetch.header.yourref = req->header.myref;
        fetch.header.messageid = MESSAGE_RAM_FETCH;
        fetch.buffer = (unsigned int) rambuf;
        fetch.buflen = rambuflen;

        block
        {
            error *err = swi (Wimp_SendMessage,
                              R0, EV_USER_MESSAGE_RECORDED,
                              R1, &fetch,
                              R2, req->header.taskhandle,  END);
            ramfetch_myref = fetch.header.myref;
            return err;
        }
    }
    else
    {
/*        dprintf("Bounced file of type %d\n" _ req->filetype); */
    }
    return NULL;
}


/*
 * Handle a Ram Transmit message.  If the length quoted is
 * less than the amount of buffer space we offered, then
 * we have finished.  Otherwise, we want to ask for more,
 * extending the buffer a bit.
 */

static error * ramtransmit_message (MessageRamTransmitPtr transmit)
{
    error *err = NULL;
    DocumentPtr newdoc = NULL;

/*    dprintf("Recvd RAM_TRANSMIT with yourref %d\n" _ transmit->header.yourref); */
    
    /* The amount of space we offered was rambuflen - rampos.  If
     * the amount that the sender filled is less, then we are done.
     */

    if (transmit->numsent < rambuflen - rampos)
    {
        void *closure;

/*        dprintf("RamTransferred last chunk of %d bytes at offset %d, total length %d\n" _
                transmit->numsent _ rampos _ rampos + transmit->numsent); */

        /* Determine where the load is going */
        if (datasave_windowhandle == -2 && datasave_iconhandle == iconhandle)
        {
            /* Load to a new document.  Really oughtn't open window till we finish operation */
            EG ( fail, document_create(&newdoc, "") );
            datasave_windowhandle = newdoc->window.handle;
            datasave_iconhandle = -1;
        }

        if (registry_lookup_window (datasave_windowhandle, &closure) == Document)
        {
            DocumentPtr doc = (DocumentPtr) closure;
            
            if (doc->internal == FALSE)     /* ignore loads to palette */
            {
                EG ( fail, document_load (rambuf, rampos + transmit->numsent, doc, FALSE, newdoc == NULL, newdoc != NULL) );
                EG ( fail, document_modified (doc, newdoc == NULL || !doc->fullpath) );
            }
        }
        /* Fall through to the "fail" code */
    }

    /* If the message was "full" then there must be more to come,
     * or else the data was a perfect fit in the buffer and there
     * will be a zero-length message to follow.  In any case,
     * make the buffer bigger and let the sender have another go.
     */

    else if (transmit->numsent == rambuflen - rampos)
    {
        MessageRamFetchPtr fetch = (MessageRamFetchPtr) transmit;   /* Overlaid */

/*        dprintf("RamTransferred chunk of %d bytes at offset %d\n" _ transmit->numsent _ rampos); */

        rampos += transmit->numsent;
        rambuflen += RAMBUFQUANTA;
        rambuf = realloc (rambuf, rambuflen);
        if (rambuf == NULL)
        {
            err = error_lookup ("NoMem");
            goto fail;
        }

        /* Build reply in same store as message */

        fetch->header.yourref = transmit->header.myref;
        fetch->header.messageid = MESSAGE_RAM_FETCH;
        fetch->buffer = (unsigned int) rambuf + rampos;
        fetch->buflen = rambuflen - rampos;
        EG ( fail, swi (Wimp_SendMessage,  NONX,
                        R0, EV_USER_MESSAGE_RECORDED,
                        R1, fetch,
                        R2, transmit->header.taskhandle,  END) );

        return NULL;
    }

    /* Get here on error or if the transfer has been successfully completed */

 fail:
    if (err && newdoc)
    {
        newdoc->modified = FALSE;
        (void) document_close_window(newdoc);
    }
    if (rambuf)
        free (rambuf);
    rambuf = NULL;
    return err;
}


/*
 * If the initial RamFetch bounces, we revert to using the DataSave protocol.
 * If a subsequent RamFetch bounces, we just abort the transfer.
 */

static error * ramfetch_bounced (MessageRamFetchPtr fetch)
{
    if (rambuf)
        free (rambuf);
    rambuf = NULL;

    if (fetch->header.myref == ramfetch_myref)
    {
        MessageDataSaveRec ack;

/*        dprintf("Initial RamFetch message bounced, attempting scrap xfer\n"); */

        ack.header.size = sizeof (MessageDataSaveRec);
        ack.header.yourref = datasave_myref;
        ack.header.messageid = MESSAGE_DATA_SAVE_ACK;
        ack.windowhandle = datasave_windowhandle;
        ack.iconhandle = datasave_iconhandle;
        ack.position = datasave_position;
        ack.estsize = -1;       /* insecure destination */
        ack.filetype = FILETYPE_RESOURCE;
        strcpy(ack.leafname, "<Wimp$Scrap>");
        return swi (Wimp_SendMessage,
                    R0, EV_USER_MESSAGE,
                    R1, &ack,
                    R2, datasave_taskhandle,  END);
    }
    else
    {
/*        dprintf("Non-initial RamFetch message bounced, aborting xfer\n"); */
        return NULL;
    }
}

   

/*
 * Iconsprites the given sprite file, and inform any CSEs
 * that might have been affected.
 */

static Bool iconsprites (char *filename)
{
    Bool spritesloaded = FALSE;

    /* iconsprites */
    spritesloaded =
        (swi (Wimp_SpriteOp, R0, 11, R2, (int) filename, END) == NULL);

    if (spritesloaded)
        protocol_send_resed_sprites_changed ();

    return spritesloaded;
}

    
/*
 * Try to load any file called Sprites[<nn>] from the directory in which
 *  'filename' is found.
 */

static void load_any_sprites (char *filename)
{
    char *suffix;
    char *end;
    Bool spritesloaded = FALSE;
    char name[256];

    /* locate directory name */
    end = filename + strlen (filename);
    while (end > filename && *end != '.')
        end--;

    if (end == filename || (end - filename) > 248)
        return;

    *end = 0;
    sprintf(name, "%s.Sprites", filename);
    *end = '.';

    end = name + strlen(name);

    /* try, eg, Sprites22, first of all */
    if (swi (Wimp_ReadSysInfo, R0, 2,      /* sets suffix to, eg, "22" */
                          OUT, R0, &suffix, END) == NULL)
    {
        int objtype = 0;

        strcpy (end, suffix);

        /* set objtype = 1 if the suffixed file exists */
        swi (OS_File, R0, 17,
                      R1, (int) name,
                 OUT, R0, &objtype, END);

        if (objtype == 1)
            spritesloaded = iconsprites (name);
    }

    /* otherwise, try plain old Sprites */
    if (!spritesloaded)
    {
        *end = 0;
        spritesloaded = iconsprites (name);
    }

    return;
}


/*
 * Respond to a DataLoad message, either from the Filer or as a result
 * of a dragdrop interaction.
 */

static error * dataload_message (MessageDataLoadPtr req)
{
    error *err = NULL;
    void *closure;
    DocumentPtr newdoc = NULL;
    MessageDataLoadRec rep = *req;

    /* NOTE: it is necessary to ACK the DataLoad message first
     * to prevent the sender from seeing a bounce
     * message when we attempt to recover the document from the CSEs
     * in the genmsgs_import_file() function.  We then send the reply
     * proper at the end when we know the file was successfully
     * loaded.
     */

    rep.header.yourref = req->header.myref;
    rep.header.messageid = MESSAGE_DATA_LOAD_ACK;
    EG ( fail, swi (Wimp_SendMessage,  NONX,
                    R0, EV_USER_MESSAGE_ACKNOWLEDGE,
                    R1, &rep,
                    R2, rep.header.taskhandle,  END) );

    switch (req->filetype)
    {
    case FILETYPE_SPRITE:
        iconsprites (req->filename);
        break;

    case FILETYPE_RESOURCE:
        /* Determine where the load is going */
        if (req->windowhandle == -2 && req->iconhandle == iconhandle)
        {
            /* Load to a new document.  Really oughtn't open window till we finish operation */
            DocumentPtr olddoc = document_lookup_by_filename (req->filename);
            if (olddoc)
            {
                document_raise_window (olddoc);
                break;
            }
            EG ( fail, document_create(&newdoc, req->filename) );
            req->windowhandle = newdoc->window.handle;
            req->iconhandle = -1;
        }

        if (registry_lookup_window (req->windowhandle, &closure) == Document)
        {
            DocumentPtr doc = (DocumentPtr) closure;
            
            if (doc->internal)
                break;               /* ignore loads to palette */

            err = document_load_file (req->filename, doc, FALSE, newdoc == NULL, newdoc != NULL);
            if (err)
            {
                if (newdoc)
                {
                    newdoc->modified = FALSE;
                    (void) document_close_window(newdoc);
                }
                goto fail;
            }
            
            EG ( fail, document_modified ((DocumentPtr) closure, newdoc == NULL || !doc->fullpath) );
        }

        /* load any associated Sprites file */
        load_any_sprites (req->filename);

        break;

    case FILETYPE_TEXT:
        /* Determine where the load is going */
        if (registry_lookup_window (req->windowhandle, &closure) == Document)
        {
            DocumentPtr doc = (DocumentPtr) closure;
            
            if (doc->internal)
                break;               /* ignore loads to palette */

            EG ( fail, genmsgs_import_file (doc, req->filename) );
        }
        break;

    default:
        err = error_lookup ("BadType");
        goto fail;
    }

    /* Now send the acknowledgement message to the sender, if we successfully
     * loaded
     */

    rep.header.yourref = req->header.myref;
    rep.header.messageid = MESSAGE_DATA_LOAD_ACK;
    (void) swi (Wimp_SendMessage,  NONX,
                R0, EV_USER_MESSAGE,
                R1, &rep,
                R2, req->header.taskhandle,  END);

 fail:
    /* We ACKed the DataLoad message, so we have to delete the
     * scrap file even if we have failed.
     */

/*    dprintf("Filename is *(%s)\n", req->filename); */
    if (strcmp(req->filename, "<Wimp$Scrap>") == 0)
    {
/*        dprintf("deleting\n"); */
/*        system("%delete <Wimp$Scrap>"); */

        /* delete */
        swi (OS_File, R0, 6,
                      R1, (int) "<Wimp$Scrap>", END);
    }

    return err;
}


/*
 * Respond to a DataOpen message from the Filer.
 */

static error * dataopen_message (MessageDataLoadPtr req)
{
    DocumentPtr newdoc;
    DocumentPtr olddoc = document_lookup_by_filename (req->filename);
    error *err = NULL;

    if (req->filetype != FILETYPE_RESOURCE)
        return NULL;

    /* Ack this message */
    req->header.yourref = req->header.myref;
    req->header.messageid = MESSAGE_DATA_LOAD_ACK;
    (void) swi (Wimp_SendMessage,  NONX,
                R0, EV_USER_MESSAGE,
                R1, req,
                R2, req->header.taskhandle,  END);

    if (olddoc)
    {
        document_raise_window (olddoc);
        return NULL;
    }

    /* Load to a new document.  Really oughtn't open window till we finish operation */

    ER ( document_create(&newdoc, req->filename) );
    err = document_load_file (req->filename, newdoc, FALSE, FALSE, TRUE);
    if (err)
    {
        newdoc->modified = FALSE;
        (void) document_close_window(newdoc);
        return err;
    }

    /* load any associated sprites file */
    load_any_sprites (req->filename);

    return document_modified (newdoc, FALSE);
}


/*
 * Respond to a Save Desktop message from the Filer.
 */

static error * putbytes (const char *str, const int handle)
{
    return swi (OS_GBPB,  R0, 2,  R1, handle,  R2, str,  R3, strlen (str),  END);
}


static error * savedesktop_message (MessageSaveDesktopPtr req)
{
    error *err = NULL;

    EG ( fail, putbytes ("Run ", req->filehandle) );
    EG ( fail, putbytes (appdir, req->filehandle) );
    EG ( fail, putbytes ("\n", req->filehandle) );

fail:
    if (err)
    {
        /* Some error occurred; ack the message */
        req->header.yourref = req->header.myref;
        ER ( swi (Wimp_SendMessage,  R0, EV_USER_MESSAGE_ACKNOWLEDGE,  R1, req,  R2, req->header.taskhandle,  END) );
    }
    
    return err;
}


/*
 * Respond to all message events.  Rather annoyingly, we can't determine the
 * window/icon pair without reference to the message type, so the test
 * for the type is here rather than in the window-specific procs.
 */

static error * message (int event, int *buf)
{
    MessageHeaderPtr hdr = (MessageHeaderPtr) buf;
    Bool protocol;
    ER ( protocol_incoming_message (event, buf, &protocol) );
    if (protocol)
        return NULL;            /* handled by protocol message dispatcher */

    switch (event)
    {
    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        switch (hdr->messageid)
        {
        case MESSAGE_PREQUIT:
            return warn_exit ((MessagePreQuitPtr) buf);
        case MESSAGE_QUIT:
            error_exit (0);
            /* NOTREACHED */
        case MESSAGE_HELP_REQUEST:
            return help_message ((MessageHelpRequestPtr) buf);
        case MESSAGE_DRAGGING:
            return dragdrop_message_dragging ((MessageDraggingPtr) buf);
        case MESSAGE_DATA_LOAD:
            return dataload_message ((MessageDataLoadPtr) buf);
        case MESSAGE_DATA_SAVE:
            return datasave_message ((MessageDataSavePtr) buf);
        case MESSAGE_RAM_TRANSMIT:
            return ramtransmit_message ((MessageRamTransmitPtr) buf);
        case MESSAGE_MODE_CHANGE:
            return wimp_examine_mode ();
        case MESSAGE_DATA_OPEN:
            return dataopen_message ((MessageDataLoadPtr) buf);
        case MESSAGE_SAVE_DESKTOP:
            return savedesktop_message ((MessageSaveDesktopPtr) buf);
        case MESSAGE_CLAIM_ENTITY:
            return focus_message_claim_entity ((MessageClaimEntityPtr) buf);
        }
        break;
    case EV_USER_MESSAGE_ACKNOWLEDGE:
        switch (hdr->messageid)
        {
        case MESSAGE_RAM_FETCH:
            return ramfetch_bounced ((MessageRamFetchPtr) buf);
        }
        break;
    }
    return NULL;
}


/*
 *Install iconbar sprite
 */

static error * install_iconbar_sprite()
{
    int sx, sy, sm, px, py;
    CreateIconRec new;

    ER ( swi (Wimp_SpriteOp,  R0, 40,  R2, ICONNAME,
              OUT,   R3, &sx,  R4, &sy,  R6, &sm,   END) );

    ER ( swi (OS_ReadModeVariable,  R0, sm,  R1, 4,
              OUT,   R2, &px,   END) );

    ER ( swi (OS_ReadModeVariable,  R0, sm,  R1, 5,
              OUT,   R2, &py,   END) );

    sx <<= px; sy <<= py;

    new.windowhandle = -1;              /* App icon */
    new.icon.bbox.minx = 0;             /* Min x */
    new.icon.bbox.miny = 0;             /* Min y */
    new.icon.bbox.maxx = sx;            /* Max x */
    new.icon.bbox.maxy = sy;            /* Max y */
    new.icon.flags = IF_SPRITE | IF_HCENT | IF_INDIR |
        IF_FIELD(TYPE, 3) | IF_FIELD(FG, 7) | IF_FIELD(BG, 1);
    new.icon.data[0] = (int) ICONNAME;
    new.icon.data[1] = 1;               /* WIMP sprite area */
    new.icon.data[2] = strlen(ICONNAME);

    ER ( swi (Wimp_CreateIcon,  R1, &new,
              OUT,   R0, &iconhandle,  END) );
    return NULL;
}


/*
 * Crank up the Wimp
 */

static error * start_wimp ()
{
    int task;
    error *err;
    char *taskname = message_lookup(&msgs, "TaskName");
    int messages[] = {

        MESSAGE_DRAG_CLAIM,
        MESSAGE_DRAGGING,
        MESSAGE_CLAIM_ENTITY,

        MESSAGE_MENUS_DELETED,
        MESSAGE_MENU_WARNING,

        MESSAGE_DATA_SAVE,
        MESSAGE_DATA_SAVE_ACK,
        MESSAGE_DATA_LOAD,
        MESSAGE_DATA_LOAD_ACK,
        MESSAGE_DATA_OPEN,

        MESSAGE_RAM_FETCH,
        MESSAGE_RAM_TRANSMIT,

        MESSAGE_PREQUIT,
        MESSAGE_TASK_CLOSE_DOWN,
        MESSAGE_SAVE_DESKTOP,
        MESSAGE_HELP_REQUEST,
        MESSAGE_MODE_CHANGE,

        MESSAGE_RESED_OBJECT_LOAD,
        MESSAGE_RESED_OBJECT_LOADED,
        MESSAGE_RESED_OBJECT_SENDING,
        MESSAGE_RESED_OBJECT_SEND,
        MESSAGE_RESED_OBJECT_NAME_REQUEST,
        MESSAGE_RESED_OBJECT_MODIFIED,
        MESSAGE_RESED_OBJECT_CLOSED,

        0 };

    if (taskname == NULL)
        taskname = ICONNAME;            /* fallback */
    strncpy((char *)&task, "TASK", 4);
    err = swi (Wimp_Initialise,
               R0, 310,
               R1, task,
               R2, taskname,
               R3, messages,
               OUT,
               R1, &taskhandle,  END);
    return err;
}


/* 
 * Read in window prototypes for this module and others.
 */

static error * load_prototypes (void)
{
    char name[FILENAMELEN];
    strcpy(name, appdir);
    strcat(name, ".Templates");

    ER ( swi(Wimp_OpenTemplate, R1, name, END) );
    
    ER ( wimp_load_template("ExitWarn", &exitwarnwin) );
    ER ( wimp_load_template("Info", &infowin) );
    ER ( swi (Wimp_CreateWindow,  R1, &infowin->visarea,
              OUT,  R0, &infowin->handle,  END) );
    ER ( dbox_setstring (infowin, I_INFO_VERSION, version_string() ) );
    ER ( registry_register_window (infowin->handle, Info, (void *) infowin) );
    
    ER ( document_load_prototypes() );
    ER ( genmsgs_load_prototypes() );

    {
        int saveaswindowhandle;

        ER ( saveas_load_prototypes(&saveaswindowhandle) );

        /* register for interactive help */
        ER ( registry_register_window (saveaswindowhandle,
                                           SaveAsDbox, NULL) );
    }

    ER ( fileinfo_load_prototypes() );
    ER ( objflags_load_prototypes() );
    ER ( rename_load_prototypes() );
    ER ( copy_load_prototypes() );

    ER ( swi(Wimp_CloseTemplate, END) );
    return NULL;
}


/*
 * Respond to any open window request events
 */

static error * open_window_request (int *buf)
{
    WindowPtr win = (WindowPtr) buf;    /* PUN; it's only half there... */
    void *closure;
    RegistryType type = registry_lookup_window(win->handle, &closure);

    switch (type)
    {
    case Document:
        return document_open_window (win, (DocumentPtr)closure);
    case Info:
        infowin->visarea = win->visarea;
        infowin->scrolloffset = win->scrolloffset;
        infowin->behind = win->behind;
        return swi (Wimp_OpenWindow, R1, infowin, END);
    case FileInfo:
        return fileinfo_open_window (win);
    }
    return NULL;
}


/*
 * Respond to any close window request events
 */

static error * close_window_request (int *buf)
{
    WindowPtr win = (WindowPtr) buf;    /* PUN; it's only half there... */
    void *closure;
    RegistryType type = registry_lookup_window(win->handle, &closure);

    switch (type)
    {
    case Document:
        return document_close_window ((DocumentPtr)closure);
    }
    return NULL;
}


/*
 * Respond to any redraw window request events
 */

static error * redraw_window_request (int *buf)
{
    WindowRedrawPtr redraw = (WindowRedrawPtr) buf;
    void *closure;
    RegistryType type = registry_lookup_window(redraw->handle, &closure);
    
    switch (type)
    {
    case Document:
        ER ( document_redraw_window (redraw, (DocumentPtr)closure) );
        break;
    }
    return NULL;
}


static void usage ()
{
    fprintf (stderr, "usage: %s [filename]\n", message_lookup(&msgs, "ResEd"));
    error_exit (0);
}


/*
 * Get next Wimp event into *event, details filled in buf
 */

error * main_next_event (int *event, int *buf)
{
    unsigned int mask;

    mask = eventmask & interactor_event_mask();

    if (mask & BIT(EV_NULL_REASON_CODE)) /* NULL events disabled */
    {
        ER ( swi (Wimp_Poll,  R0, mask,  R1, buf,  OUT,  R0, event,  END) );
    }
    else
    {
        int after;
        ER ( swi (OS_ReadMonotonicTime,  OUT,  R0, &after,  END) );
        after += interactor_timeout();
        ER ( swi (Wimp_PollIdle,  R0, mask,  R1, buf,  R2, after,  OUT,  R0, event,  END) );
    }
    return NULL;
}


error * main_dispatch_event (int event, int *buf)
{
    Bool consumed;
    ER ( interactor_event (event, buf, &consumed) );
    if (!consumed)
        switch (event)
        {
        case EV_REDRAW_WINDOW_REQUEST:
            ER ( redraw_window_request (buf) );
            break;
        case EV_OPEN_WINDOW_REQUEST:
            ER ( open_window_request (buf) );
            break;
        case EV_CLOSE_WINDOW_REQUEST:
            ER ( close_window_request (buf) );
            break;
        case EV_MOUSE_CLICK:
            ER ( mouse_click(buf) );
            break ;
        case EV_KEY_PRESSED:
            ER ( key_pressed(buf) );
            break;
        case EV_USER_MESSAGE:
        case EV_USER_MESSAGE_RECORDED:
        case EV_USER_MESSAGE_ACKNOWLEDGE:
            ER ( message(event, buf) );
            break;
        }
    return NULL;
}


/*
 * Main
 */

int main (int argc, char **argv)
{
    char *initialfile = NULL;
    int buf[64], event;

    strcpy(appdir, getenv(APPDIR));               /* want to copy this before Wimp_Poll called */
    EE ( message_openfile (&msgs, "<" APPDIR ">.Messages", 256) );    /* APPDIR can't have changed yet */
    debug_file("<" APPDIR ">.logfile");

    /* set "private" flag iff the final argument is -private */
    privateflag = (strcmp (argv[argc-1], "-private") == 0);
    if (privateflag)
        argc--;

    if (argc == 2)
        initialfile = argv[1];
    else
    if (argc != 1)
        usage();
       
    EE ( wimp_examine_mode() );
    EE ( start_wimp () );
    EE ( install_iconbar_sprite() );
    EE ( load_prototypes () );
    EE ( create_iconmenu () );
    EE ( class_initialise () );

    /* Load initial file given on command line, if any */

    if (initialfile)
    {
        DocumentPtr newdoc;

        EE ( document_create(&newdoc, initialfile) );
        EE ( document_load_file (initialfile, newdoc, FALSE, FALSE, TRUE) );
        EE ( document_modified (newdoc, FALSE) );

        /* load any associated sprites file */
        load_any_sprites (initialfile);
    }

    /* Main loop */

    while (1)
    {
        EE ( main_next_event (&event, buf) );
        ED ( main_dispatch_event (event, buf) );
    }
}


