/*-*-C-*-
 * Misc CSE protocol support
 */

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

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"
#include "registry.h"

#include "format.h"
#include "relocate.h"
#include "objedit.h"

#include "object.h"
#include "protocol.h"



/* OUTGOING MESSAGES */


/*
 * We are about to close an object.  Shell should clear it's "I am editing
 * this object" flag in response to this.  This function is called by
 * objedit_close_object().
 */

error * protocol_send_resed_object_closed (ObjectPtr object)
{
    MessageResEdObjectClosedRec closed;

    closed.header.yourref = 0;
    closed.header.size = sizeof (closed);
    closed.header.messageid = MESSAGE_RESED_OBJECT_CLOSED;
    closed.flags = 0;
    closed.documentID = object->documentID;
    closed.objectID = object->objectID;
    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, &closed,
                R2, parenttaskhandle,  END);
}


/*
 * We call this when we want to proactively send an object back to the shell,
 * normally when the user has clicked on the OK icon.
 * The shell will respond with RESED_OBJECT_LOADED, which is handled
 * by protocol_incoming_message().
 * If object->pendingclose is set, then objedit_close_object(object, TRUE)
 * will be called if and when the send is successful.
 */

error * protocol_send_resed_object_sending (ObjectPtr object)
{
    MessageResEdObjectSendingRec sending;
    int bodysize, stringsize, msgsize, numrelocs;
    int size = objedit_object_size (object, &bodysize, &stringsize,
                                            &msgsize, &numrelocs);
    error *err;

    if (object->objectdata)
        free(object->objectdata);
    object->objectdata = malloc (size);
    if (object->objectdata == NULL)
    {
        /* Oh dear!  We have nowhere to store the data to send it back.
         * Show the user an error message, and cancel the pending close flag.
         */
        object->pendingclose = FALSE;
        return error_lookup ("NoMem");
    }
    else
    {
        err = objedit_save_object_to_memory (object, object->objectdata,
                                   bodysize, stringsize, msgsize, numrelocs);
        if (err)
        {
            /* Something went wrong.  Free up the temp buffer, clear
             * the pendingclose flags and return an error
             */
            free (object->objectdata);
            object->objectdata = NULL;
            object->pendingclose = FALSE;
            return err;
        }
    }
    sending.header.yourref = 0;
    sending.header.size = sizeof (sending);
    sending.header.messageid = MESSAGE_RESED_OBJECT_SENDING;
    sending.flags = 0;
    sending.documentID = object->documentID;
    sending.objectID = object->objectID;
    sending.address = (unsigned int) object->objectdata;
    sending.size = size;     /* address and size valid only if flags == 0 */
    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE_RECORDED,
                R1, &sending,
                R2, parenttaskhandle,  END);
}
    

/*
 * This function determines whether any drop action is defined for when an
 *  object is dropped onto the given window/icon pair.
 *
 * If not, the result is NULL.
 *
 * If so, the result is a pointer to a record which defines the action to be
 *  taken, and *objptr will point to the Object into whose dialogue box the
 *  drop has taken place.
 */

static DropDetailsPtr locate_drop_action (
    int handle,   /* window handle */
    int icon,     /* icon handle   */
    ObjectPtr *objptr
)
{
    ObjectPtr object;
    RegistryType type = registry_lookup_window (handle, (void **) &object);
    DropDefPtr dropdef;

    /* drop destination must be an object's properties dialogue box */
    if (type != ObjectDbox)
        return NULL;

    /* see if there is an "object dropped" action defined for this icon */
    dropdef = object->def->drops;
    while (dropdef->icon != -2)
    {
        if (dropdef->icon == icon && dropdef->details->type == OBJECT_DROP)
        {
            *objptr = object;
            return dropdef->details;
        }

        dropdef++;
    }

    return NULL;
}


/*
 * We have received a DataSave message.  The only thing we accept is
 * something being dragged from our Document window of our parent shell.
 * We determine this by replying to the DataSave message with
 * a RESED_OBJECT_NAME_REQUEST message.  If the sender was our
 * shell it will reply with RESED_OBJECT_NAME, enabling us to
 * fill in a name field in our dbox.
 */

error * protocol_send_resed_object_name_request (MessageDataSavePtr save)
{
    MessageResEdObjectNameRequestRec req;
    int handle = save->windowhandle;
    int icon = save->iconhandle;
    ObjectPtr object;

    /* check to see if further action is necessary */
    if (locate_drop_action (handle, icon, &object) == NULL)
        return NULL;

    /* send out request for object's name */
    req.header.yourref = save->header.myref;
    req.header.size = sizeof (req);
    req.header.messageid = MESSAGE_RESED_OBJECT_NAME_REQUEST;
    req.flags = 0;
    req.documentID = object->documentID;
    req.objectID = object->objectID;
    req.windowhandle = handle;
    req.iconhandle = icon;

    return swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, &req,
                R2, save->header.taskhandle,  END);
}



/* INCOMING MESSAGES */


/*
 * Use the document and object IDs from an incoming message to
 * locate our corresponding ObjectPtr.  Return it, or NULL if not found.
 */

static ObjectPtr locate_object (Opaque documentID, Opaque objectID)
{
    int i = 0;
    RegistryType type;
    void *closure;

    while ((i = registry_enumerate_windows (i, &type, NULL, &closure)) != 0)
        if (type == ObjectDbox)
        {
            ObjectPtr object = (ObjectPtr) closure;
            if ( object->documentID == documentID &&
                 object->objectID == objectID )
                return object;
        }
    return NULL;
}


/*
 * The shell is asking us to open an object or raise it.  Fetch the
 * data using Wimp_TransferBlock and get it loaded into an appropriate
 * dialogue box.
 */

static error * received_resed_object_load (MessageResEdObjectLoadPtr load)
{
    ObjectPtr object = locate_object (load->documentID, load->objectID);
    MessageResEdObjectLoadedPtr loaded = (MessageResEdObjectLoadedPtr) load;
    char *obj = NULL;
    error *err;
    ObjectDefPtr def = object_find_def (load->class);

    /*
     * Note: def == NULL means that we have been sent an object whose class
     *  we do not know; this is treated specially.
     */

    /*
     * If we already have this loaded, and the force bit is clear,
     * then just raise the window editing window, and reply with
     * "success" regardless of whether an error occurred.
     */

    if (object && (load->flags & BIT(0)) == 0)
    {
        err = object_open_dbox (object, TRUE);
        loaded->flags = 0;
        goto reply;
    }

    /*
     * Check that the version of a known object is one which we can handle;
     *  if not, we shall treat it as an unknown object.
     */

    if (def && load->version != def->version)  /* if not current version */
        switch (def->class)
        {

/***  eg if version 100 of the scale class can still be processed ...

        case CLASS_SCALE:
            switch (load->version)
            {
            case 100:
                break;

            default:
                goto versionerror;
            }
            break;
***/

        /* only the versions above can be handled by this version of !Misc */
        default:
/***
        versionerror:
***/
            {
                char ver[20];
                sprintf (ver, "%d.%02d", load->version/100,
                                         load->version%100);

                /* treat unknown versions as unknown objects */
                error_box (error_lookup ("BadVersion", ver));
                def = NULL;
            }
        }

    /* allocate space for the unrelocated template and copy it across */
    obj = malloc (load->size);
    if (obj == NULL)
    {
        loaded->flags = BIT(1);
        loaded->errcode = OBJECT_LOADED_ERROR_NOMEM;
        err = error_lookup ("NoMem");
        goto reply;
    }

    err = swi (Wimp_TransferBlock,
               R0, load->header.taskhandle,
               R1, load->address,
               R2, taskhandle,
               R3, obj,
               R4, load->size,  END);
    if (err)
    {
        loaded->flags = BIT(0);
        loaded->errcode = OBJECT_LOADED_ERROR_NONFATAL;
        goto reply;
    }

    /* pre-process earlier object versions where necessary */
    if (def)
        switch (def->class)
        {

/***  eg if version 100 of the scale class can still be processed ...

            case CLASS_SCALE:
                switch (load->version)
                {
                case 100:
         < alter obj so that it looks like a current version scale object >
                    break;

                default:
                    break;
                }
                break;
***/
     
        default:
            break;
        }

    /* construct a suitable definition if the object is unknown */
    if (def == NULL)
    {
        ResourceFileObjectTemplateHeaderPtr templ =
            (ResourceFileObjectTemplateHeaderPtr) obj;
        int size = templ->hdr.bodysize;
        int offset = templ->relocationtableoffset;

        def = object_create_def (templ->hdr.class, size,
                    (offset < 0) ? NULL :
                                   (RelocationTablePtr) (obj + offset));

    }

    /* check no problems while creating definition */
    if (def == NULL)
    {
        err = error_lookup ("NoMem");
        loaded->flags = BIT(0);
        loaded->errcode = OBJECT_LOADED_ERROR_NONFATAL;
        goto reply;
    }

    /* preserve version number in definition */
    def->version = load->version;

    /*
     * If object is NULL, then objedit_load creates a new ObjectRec.
     * If object is non-NULL, it loads the new data into an existing one.
     * If it returns an error, the dbox is not open.
     */

    err = objedit_load ( object,
                         (ResourceFileObjectTemplateHeaderPtr) obj,
                         load,
                         def );
    if (err)
    {
        loaded->flags = BIT(0);
        loaded->errcode = OBJECT_LOADED_ERROR_NONFATAL;
        goto reply;
    }

    loaded->flags = 0;          /* Success! */

reply:

    /*
     * If an error occurred, loaded->{flags, errcode} have already been
     * set up correctly.  Formulate the reply message in the same memory as
     * the incoming one.
     */

    free (obj);
    loaded->header.yourref = load->header.myref;
    loaded->header.size = sizeof (MessageResEdObjectLoadedRec);
    loaded->header.messageid = MESSAGE_RESED_OBJECT_LOADED;
    (void) swi (Wimp_SendMessage,
                R0, EV_USER_MESSAGE,
                R1, loaded,
                R2, load->header.taskhandle,  END);

    /* Finally raise any error that we suffered, because the shell
     * will not tell the user.
     */
    return err;
}


/*
 * The shell is requesting the data associated with some object.
 * Send it along.
 *
 * This arises only when new messages have been imported.
 */

static error * received_resed_object_send (MessageResEdObjectSendPtr send)
{
    ObjectPtr object = locate_object (send->documentID, send->objectID);
    MessageResEdObjectSendingRec sending;
    int size = 0;
    error *err = NULL;

    if (object == NULL)
    {
        err = error_lookup ("UnkObj");
        sending.errcode = OBJECT_SENDING_ERROR_UNKNOWN;
    }
    else
    {
        int bodysize, stringsize, msgsize, numrelocs;

        size = objedit_object_size (object, &bodysize, &stringsize,
                                            &msgsize, &numrelocs);

        if (object->objectdata)
            free(object->objectdata);
        object->objectdata = malloc (size);

        if (object->objectdata == NULL)
        {
            /* Bother!  We have nowhere to store the data to send it back.
             * Show the user an error message, and reply with an error
             * indication to the shell.
             */
            sending.errcode = OBJECT_SENDING_ERROR_NOMEM;
            err = error_lookup ("NoMem");
        }
        else
        {
            err = objedit_save_object_to_memory (object, object->objectdata,
                                   bodysize, stringsize, msgsize, numrelocs);
            if (err)
            {
                free (object->objectdata);
                object->objectdata = NULL;
                sending.errcode = OBJECT_SENDING_ERROR_NONFATAL;
            }
        }
    }

    /* set 'pendingclose' flag if shell has requested that the object be
       deleted after a successful transfer */
    if (err == NULL && (send->flags & BIT(0)) != 0)
        object->pendingclose = TRUE;

    /* Formulate reply */
    sending.header.size = sizeof(MessageResEdObjectSendingRec);
    sending.header.yourref = send->header.myref;
    sending.header.messageid = MESSAGE_RESED_OBJECT_SENDING;
    sending.flags = err ? BIT(0) : 0;
    sending.documentID = send->documentID;
    sending.objectID = send->objectID;
    sending.address = (unsigned int) object->objectdata;
    sending.size = size;     /* address and size valid only if flags == 0 */
    (void) swi (Wimp_SendMessage,
                R0, err ? EV_USER_MESSAGE : EV_USER_MESSAGE_RECORDED,
                R1, &sending,
                R2, send->header.taskhandle,  END);

    return err;
}
    

/*
 * The shell has loaded the object we were sending it - or else generated an
 * error (in which case the user has already been told).  Free up our
 * temporary space and if the window is waiting to be closed, then close it
 * (unless there was an error indication in the message).
 */

static error * received_resed_object_loaded (
                        MessageResEdObjectLoadedPtr loaded )
{
    ObjectPtr object = locate_object (loaded->documentID, loaded->objectID);

    if (object)
    {
        if (object->objectdata)
            free(object->objectdata);
        object->objectdata = NULL;

        /* Check the error indication.  If there was no error, then
         * go ahead and close the object if our pendingclose is set.
         */

        if ((loaded->flags & BIT(0)) == 0)
        {
            if (object->pendingclose)
            {
                /* The TRUE means "send a RESED_OBJECT_CLOSED message to
                   the shell" */
                return objedit_close_object (object, TRUE);
            }
        }

        /* otherwise, re-initialise the dbox */
        object->pendingclose = FALSE;
        object_init_dbox (object);
    }

    return  NULL;
}


/*
 * The shell is telling us that an object's name has changed.
 */

static error * received_resed_object_renamed (
                           MessageResEdObjectRenamedPtr rename )
{
    ObjectPtr object = locate_object (rename->documentID, rename->objectID );

    if (object)
        return objedit_rename_object (object, rename->name);
    else
        return NULL;
}


/*
 * The shell is telling us that an object it thinks we have open
 * is being deleted.  Close any window representing that window,
 * and free up resources etc.  Do not reply to the message.
 */

static error * received_resed_object_deleted (
                              MessageResEdObjectDeletedPtr delete )
{
    ObjectPtr object = locate_object (delete->documentID, delete->objectID );

    if (object)
        /* The FALSE means "don't send a RESED_OBJECT_CLOSED message to the
           shell" */
        return objedit_close_object (object, FALSE);
    else
        return NULL;
}


/*
 * The shell is telling us the name of an object.  This will be in response
 * to us sending RESED_OBJECT_NAME_REQUEST in reply to a DataSave message.
 */

static error * received_resed_object_name (MessageResEdObjectNamePtr name)
{
    ObjectPtr object;
    DropDetailsPtr dropdef = locate_drop_action (name->windowhandle,
                                                 name->iconhandle, &object);

    /* this shouldn't happen */
    if (dropdef == NULL)
        return NULL;

    /* check that there was no error */
    if (name->flags == 0)
        return object_object_drop (object, dropdef, name->class, name->name);
    else
        return NULL;
}


/*
 * Our attempt to send an object to the shell bounced.  Clear up
 * the temporary buffer that the object has allocated, and continue.
 */

static error * bounced_resed_object_sending (
                             MessageResEdObjectSendingPtr sending )
{
    ObjectPtr object = locate_object (sending->documentID,
                                      sending->objectID );

    if (object)
    {
        if (object->objectdata) free(object->objectdata);
        object->objectdata = NULL;
        object->pendingclose = FALSE;
    }

    return NULL;
}


/*
 * Protocol message dispatcher.  The main event handling code uses this
 * to handle all ResEd shell<->CSE protocol messages.  Buf points
 * to a 64-word Wimp event buffer.  The event code is in 'event'.
 * Sets *handled to indicate to the caller whether the event has
 * been handled or not.
 */

error * protocol_incoming_message (int event, int *buf, Bool *handled)
{
    MessageHeaderPtr hdr = (MessageHeaderPtr) buf;
    if (handled) *handled = TRUE;
    switch (event)
    {
    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        switch (hdr->messageid)
        {
        case MESSAGE_RESED_OBJECT_LOAD:
            return received_resed_object_load (
                                   (MessageResEdObjectLoadPtr) buf );
        case MESSAGE_RESED_OBJECT_SEND:
            return received_resed_object_send (
                                   (MessageResEdObjectSendPtr) buf );
        case MESSAGE_RESED_OBJECT_LOADED:
            return received_resed_object_loaded (
                                   (MessageResEdObjectLoadedPtr) buf );
        case MESSAGE_RESED_OBJECT_RENAMED:
            return received_resed_object_renamed (
                                   (MessageResEdObjectRenamedPtr) buf );
        case MESSAGE_RESED_OBJECT_DELETED:
            return received_resed_object_deleted (
                                   (MessageResEdObjectDeletedPtr) buf );
        case MESSAGE_RESED_OBJECT_NAME:
            return received_resed_object_name (
                                   (MessageResEdObjectNamePtr) buf );
        }
        break;
    case EV_USER_MESSAGE_ACKNOWLEDGE:
        switch (hdr->messageid)
        {
        case MESSAGE_RESED_OBJECT_SENDING:
            return bounced_resed_object_sending (
                                   (MessageResEdObjectSendingPtr) buf );
        }
        break;
    }
    if (handled) *handled = FALSE;
    return NULL;
}
