/*-*-C-*-
 * SaveAs - implements a singly-instantiated SaveAs dialogue with
 * the new Save Selection toggle.
 */

#include "resed.h"

#include "swicall.h"
#include "wimp.h"

#include "dbox.h"
#include "interactor.h"
#include "saveas.h"


static WindowPtr window;
static SaveAsCallback callback = NULL;
static Bool standalone;
static Bool pendingdelete;
static int filetype;
static char *addr;
static int size;
static Bool dragasprite, dragging;

static char lastfname[FILENAMELEN];          /* valid when Selection toggle is TRUE */

static char origfname[FILENAMELEN];   /* preserves filename passed at open
                                         time to restore after ADJ/CANCEL */

#define I_SAVEAS_OK 0
#define I_SAVEAS_CANCEL 1
#define I_SAVEAS_FILENAME 2
#define I_SAVEAS_SPRITE 3
#define I_SAVEAS_SELECTION 4


void settype (char *filename, int filetype)
{
/*    char buf[256];                                          */
/*    sprintf (buf, "%%settype %s %x", filename, filetype);   */
/*    system (buf); 
                                          */
    /* settype */
    swi (OS_File, R0, 18,
                  R1, (int) filename,
                  R2, filetype, END);
}


static void delete (char *filename)
{
/*    char buf[256];                                   */
/*    sprintf (buf, "%%delete %s", filename);          */
/*    dprintf("******** calling system (%s)\n" _ buf); */
/*    system (buf);
                                    */
    /* delete */
    swi (OS_File, R0, 6,
                  R1, (int) filename, END);
}


/*
 * Load templates, etc.
 *
 * If handle != NULL, *handle is set to the window's handle - in case there
 *  is a need to register it (for interactive help, for example).
 */

error * saveas_load_prototypes (int *handle)
{
   ER ( wimp_load_template("SaveAs", &window) );
   ER ( swi (Wimp_CreateWindow,  R1, &window->visarea,
             OUT,  R0, &window->handle,  END) );

   if (handle != NULL)
       *handle = window->handle;

   return NULL;
}


/* Attempt to save under the given absolute path */

static error * save_absolute (char *path, void *closure)
{
    error *err = NULL;
    FILE *f = NULL;

    addr = NULL;
    size = 0;

    if (strchr(path, '.') == NULL && strcmp(path, "<Wimp$Scrap>") != 0)
        return error_lookup ("PleaseDrag");

    f = fopen (path, "w");
    if (!f)
    {
        EG ( finish, error_lookup ("CantWrite", path) );
    }

    EG ( finish, (*callback) (SaveAsGetSize, dbox_getbutton(window, I_SAVEAS_SELECTION),
                              &addr, &size, closure) );
    EG ( finish, (*callback) (SaveAsGetBlock, dbox_getbutton(window, I_SAVEAS_SELECTION),
                              &addr, &size, closure) );

    if (fwrite ((void *) addr, 1, size, f) < size)
    {
        EG ( finish, error_lookup ("CantWrite", path) );
    }
    fclose (f);
    f = NULL;
    (void) settype (path, filetype);

finish:
    if (f)
        fclose (f);
    if (addr)
        (void) (*callback) (SaveAsFreeBlock, dbox_getbutton(window, I_SAVEAS_SELECTION),
                            &addr, &size, closure);
    return err;
}


/*
 * Interactor for the saveas window.  Expects to be pushed on top of the
 * menu.c interactor, so needs to be careful about popping itself correctly.
 */

static error * saveas_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;
    MessageDataSavePtr save = (MessageDataSavePtr) buf;
    MessageDataLoadPtr load = (MessageDataLoadPtr) buf;
    MessageRamFetchPtr fetch = (MessageRamFetchPtr) buf;
    MessageRamTransmitPtr transmit = (MessageRamTransmitPtr) buf;

    /* State for RAM transfer */
    static char *rambuf = NULL;
    static int ramnum = 0, rampos = 0;
    static int datasaveref = -1;
    static int taskhandle = -1;                 /* MY taskhandle */

    KeyPressPtr key = (KeyPressPtr) buf;
    error *err = NULL;

    if (buf == NULL)                     /* we are being asked to cancel */
    {
        if (dragging)
        {
            if (dragasprite)
                swi (DragASprite_Stop, END);
            else
                swi (Wimp_DragBox,  R1, 0,  END);
            dragging = FALSE;
        }
        if (standalone)
            swi (Wimp_CreateMenu,  R1, -1,  END);
        if (rambuf)
        {
            (void) (*callback) (SaveAsFreeBlock, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                &rambuf, &ramnum, closure);
            rambuf = NULL;
        }
        return NULL;
    }

/* dprintf("in saveas_interactor %d\n" _ event); */

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

    case EV_KEY_PRESSED:
        if (key->caret.windowhandle == window->handle)
        {
            unsigned int modifiers = wimp_read_modifiers ();

            *consumed = TRUE;

            switch (key->code)
            {
            case 0x0d:   /* RETURN */
                {
                    char *path = dbox_getstring (window, I_SAVEAS_FILENAME);
                    err = save_absolute (path, closure);
                    if (err)
                    {
                        error_box (err);
                        err = NULL;
                        break;
                    }

                    (void) (*callback) (SaveAsSuccess, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                        &path, &size, closure);
                    if (pendingdelete)
                        (void) (*callback) (SaveAsDelete, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                            &path, &size, closure);
                    if (pendingdelete || (modifiers & MODIFIER_SHIFT) == 0)
                        interactor_cancel ();
                }
                break;

            case 0x1b:   /* ESCAPE */
                if ((modifiers & MODIFIER_SHIFT) == 0)
                    interactor_cancel();
                else
                {
                    /* restore entry state of dbox */
                    dbox_setstring (window, I_SAVEAS_FILENAME, origfname);
                    dbox_setbutton (window, I_SAVEAS_SELECTION, FALSE);
                }
                break;

            default:
                *consumed = FALSE;
                break;
            }
        }
        break;

    case EV_MOUSE_CLICK:
        if (mouse->windowhandle == window->handle)
        {
            *consumed = TRUE;
            switch (mouse->iconhandle)
            {
            case I_SAVEAS_OK:
                if (mouse->buttons == MB_CLICK(MB_SELECT) ||
                    mouse->buttons == MB_CLICK(MB_ADJUST))
                {
                    char *path = dbox_getstring (window, I_SAVEAS_FILENAME);
                    err = save_absolute (path, closure);
                    if (err)
                    {
                        error_box (err);
                        err = NULL;
                    }
                    else
                    {
                        (void) (*callback) (SaveAsSuccess, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                            &path, &size, closure);
                        if (pendingdelete)
                            (void) (*callback) (SaveAsDelete, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                                &path, &size, closure);
                        if (pendingdelete || mouse->buttons == MB_CLICK(MB_SELECT))
                            interactor_cancel ();
                    }
                }
                break;
            case I_SAVEAS_CANCEL:
                if (mouse->buttons == MB_CLICK(MB_SELECT))
                    interactor_cancel();
                else if (mouse->buttons == MB_CLICK(MB_ADJUST))
                {
                    /* restore entry state of dbox */
                    dbox_setstring (window, I_SAVEAS_FILENAME, origfname);
                    dbox_setbutton (window, I_SAVEAS_SELECTION, FALSE);
                }
                break;
            case I_SAVEAS_SPRITE:
                if (mouse->buttons == MB_DRAG(MB_SELECT))
                {
                    /* Start the drag */
                    int cmos;
                    IconPtr sprite;

                    dragasprite = (swi (OS_Byte,  R0, 161,  R1, 0x1C,  OUT,  R2, &cmos,  END) == NULL &&
                                   (cmos & BIT(1)));
                    swi (Wimp_GetWindowState,  R1, window,  END);
                    sprite = window->icons;
                    sprite += I_SAVEAS_SPRITE;

                    if (dragasprite)
                    {
                        RectRec bbox;
                        wimp_convert_rect (WorkToScreen, window, &sprite->bbox, &bbox);
                        ER ( swi (DragASprite_Start,  R0, BIT(7),  R1, 1,
                                  R2, dbox_getstring (window, I_SAVEAS_SPRITE),
                                  R3, &bbox,  END) );
                    }
                    else
                    {
                        DragBoxRec drag;
                        drag.type = 5;
                        drag.initial = sprite->bbox;
                        drag.constrain.minx = drag.constrain.miny = -1000000;
                        drag.constrain.maxx = drag.constrain.maxy = 1000000;
                        wimp_convert_rect (WorkToScreen, window, &drag.initial, &drag.initial);
                        ER ( swi (Wimp_DragBox,  R1, &drag,  END) );
                    }
                    dragging = TRUE;
                }
                break;
            case I_SAVEAS_SELECTION:
                if (dbox_getbutton(window, I_SAVEAS_SELECTION))
                {
                    strcpy(lastfname, dbox_getstring(window, I_SAVEAS_FILENAME));
                    dbox_setstring (window, I_SAVEAS_FILENAME, message_lookup(NULL, "Selection"));
                }
                else
                    dbox_setstring (window, I_SAVEAS_FILENAME, lastfname);
                break;
            }
        }
        break;

    case EV_USER_DRAG_BOX:
/*        dprintf("Dropped!\n"); */
        *consumed = TRUE;
        block
        {
            PointerInfoRec pointer;
            char *name = dbox_getstring (window, I_SAVEAS_FILENAME);
            char *leaf = strrchr (name, '.');
            if (dragasprite)
                swi (DragASprite_Stop, END);
            dragging = FALSE;
            swi (Wimp_GetPointerInfo,  R1, &pointer,  END);
            if (pointer.windowhandle == window->handle)
                return NULL;    /* ignore drops on our own window */
            ER ( (*callback) (SaveAsGetSize, dbox_getbutton(window, I_SAVEAS_SELECTION),
                              &addr, &size, closure) );
            save->header.size = sizeof(MessageDataSaveRec);
            save->header.yourref = 0;
            save->header.messageid = MESSAGE_DATA_SAVE;
            save->windowhandle = pointer.windowhandle;
            save->iconhandle = pointer.iconhandle;
            save->position = pointer.position;
            save->estsize = size;
            save->filetype = filetype;
            sprintf (save->leafname, "%.*s", sizeof(save->leafname) - 1, leaf ? leaf + 1 : name);
            err = swi (Wimp_SendMessage,
                       R0, EV_USER_MESSAGE_RECORDED,
                       R1, save,
                       R2, save->windowhandle,
                       R3, save->iconhandle,  END);
            datasaveref = save->header.myref;
            taskhandle = save->header.taskhandle; /* MY task handle */
            return err;
        }
        break;
        
    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        if (mess->code == MESSAGE_MENUS_DELETED)
        {
            if (buf[5] == window->handle && standalone)
            {
                interactor_cancel();
                *consumed = TRUE;
            }
        }
        else if (save->header.messageid == MESSAGE_DATA_SAVE_ACK)
        {
/*            dprintf("Data Save Ack\n"); */
            *consumed = TRUE;
            ER ( save_absolute (save->leafname, closure) );
            load->header.yourref = save->header.myref;
            load->header.messageid = MESSAGE_DATA_LOAD;
            return swi (Wimp_SendMessage,
                        R0, EV_USER_MESSAGE_RECORDED,
                        R1, load,
                        R2, save->header.taskhandle,  END);
        }
        else if (load->header.messageid == MESSAGE_DATA_LOAD_ACK)
        {
            if (load->estsize != -1)
            {
                char *path = load->filename;
                (void) (*callback) (SaveAsSuccess, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                    &path, &size, closure);
                if (pendingdelete)
                    (void) (*callback) (SaveAsDelete, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                        &path, &size, closure);
            }
            *consumed = TRUE;
            interactor_cancel();
        }
        else if (fetch->header.messageid == MESSAGE_RAM_FETCH)
        {
            int num;
            Bool full = FALSE;
/*            dprintf("RamFetch received, myref is %d yourref is %d and datasaveref is %d\n",
                    fetch->header.myref, fetch->header.yourref, datasaveref); */
            *consumed = TRUE;
            if (fetch->header.yourref == datasaveref)
            {
                /* First time round, set up rambuf, and set up pointers and counters.
                 * If we have any problems, then bounce the RamFetch message.
                 */
/*                dprintf("Getting size\n"); */
                EG ( fail, (*callback) (SaveAsGetSize, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                          &rambuf, &ramnum, closure) );
/*                dprintf("Buffer size %d\n", ramnum); */
                EG ( fail, (*callback) (SaveAsGetBlock, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                          &rambuf, &ramnum, closure) );
                if (rambuf == NULL)
                    return NULL;      /* sender will try DataSaveAck */
/*                dprintf("Allocated and filled scratch buffer for RAM xfer\n"); */
                rampos = 0;
            }
            /* Send the next batch of data along */
            num = ramnum - rampos;
            if (num >= fetch->buflen)
            {
                num = fetch->buflen;
                full = TRUE;
            }
            EG ( fail, swi (Wimp_TransferBlock,
                            R0, taskhandle,
                            R1, rambuf + rampos,
                            R2, fetch->header.taskhandle,
                            R3, fetch->buffer,
                            R4, num,  END) );

/*            dprintf ("Wimp_TransferBlock of %d bytes to receiver starting at position %d\n" _ num _ rampos); */

            transmit->header.yourref = fetch->header.myref;
            transmit->header.messageid = MESSAGE_RAM_TRANSMIT;
            transmit->numsent = num;
            
            rampos += num;
            
            EG ( fail, swi (Wimp_SendMessage, R0, full ? EV_USER_MESSAGE_RECORDED : EV_USER_MESSAGE,
                                              R1, transmit,
                                              R2, fetch->header.taskhandle,  END) );
            if (!full)
            {
/*                dprintf("Ram Transfer ended normally\n"); */
                interactor_cancel();
                if (pendingdelete)
                    (void) (*callback) (SaveAsDelete, dbox_getbutton(window, I_SAVEAS_SELECTION),
                                        NULL, &ramnum, closure);
                /* RAM transfers never result in the file becoming "safe" */
            }
        }
        break;

    case EV_USER_MESSAGE_ACKNOWLEDGE:
/*        dprintf("CODE %d\n" _ save->header.messageid); */
        if (save->header.messageid == MESSAGE_DATA_SAVE)
        {
/*            dprintf("Message bounced: %d; datasave cancelled\n" _ save->header.messageid); */
            *consumed = TRUE;
            interactor_cancel();                /* fail quietly */
            return NULL;
        }
        else if (load->header.messageid == MESSAGE_DATA_LOAD)
        {
/*            dprintf("Message bounced: %d; datasave cancelled\n" _ save->header.messageid); */
            delete (load->filename);
            err = error_lookup ("XferFailed");  /* fail noisily */
            goto fail;
        }
        else if (transmit->header.messageid == MESSAGE_RAM_TRANSMIT)
        {
/*            dprintf("Message bounced: %d; ram xfer cancelled\n" _ transmit->header.messageid); */
            *consumed = TRUE;
            interactor_cancel();                /* fail quietly */
            return NULL;
        }
        break;
    }

 fail:
    if (err)
        interactor_cancel();
    return err;
}



/*
 * Open saveas window in specified location.
 */

error * saveas_open (Bool stand,             /* TRUE if not a submenu */
                     Bool selallowed,        /* whether the Selection toggle is available */
                     Bool delete,            /* Deliver SaveAsDelete reason to cb */
                     PointPtr position,
                     char *filename,         /* initial filename */
                     int type,               /* filetype */
                     SaveAsCallback cb,
                     void *cls)              /* passed to callback */
{
    char spritename[40];
    standalone = stand;
    pendingdelete = delete;
    filetype = type;
    sprintf (spritename, "file_%x", filetype);
    dbox_setstring (window, I_SAVEAS_FILENAME, filename);
    dbox_setstring (window, I_SAVEAS_SPRITE, spritename);
    
    dbox_setbutton (window, I_SAVEAS_SELECTION, FALSE);
    dbox_shade (window, I_SAVEAS_SELECTION, !selallowed);
                    
    callback = cb;

    /* save original filename for restoration after ADJ/CANCEL */
    strcpy (origfname, dbox_getstring (window, I_SAVEAS_FILENAME));
 
/* dprintf("In saveas_open, cb=%x\n" _ (int) cb); */

    dragging = FALSE;

    if (standalone)
    {
        interactor_install (saveas_interactor, cls);
        ER ( swi (Wimp_CreateMenu,  R1, window->handle,  R2, position->x,  R3, position->y,  END) );
    }
    else
    {
        ER ( swi (Wimp_CreateSubMenu,  R1, window->handle,  R2, position->x,  R3, position->y,  END) );
        interactor_push (saveas_interactor, cls);
    }

    return NULL;
}
