/*
    ####             #    #     # #
    #   #            #    #       #          The FreeWare C library for
    #   #  ##   ###  #  # #     # ###             RISC OS machines
    #   # #  # #     # #  #     # #  #   ___________________________________
    #   # ####  ###  ##   #     # #  #
    #   # #        # # #  #     # #  #    Please refer to the accompanying
    ####   ### ####  #  # ##### # ###    documentation for conditions of use
    ________________________________________________________________________

    File:    Save.Save.c
    Author:  Copyright  1994 Julian Smith and Jason Howat
    Version: 1.03 (14 Nov 1995)
    Purpose: Automated handling of save-as window
    History: 1.01 (13 Jun 1994) - JS - Added support for filetypes
             1.02 (18 Jun 1994) - JH - Changed sense of value returned by
                  Desk_save_filesaver to be in line with rest of DeskLib.
                  Desk_save_filsaver uses filename instead of stream.  Tidied code
                  to use core routines and proper DeskLib conventions.  Added
                  Desk_Save__KeyHandler to check for RETURN key in text icon.  For
                  a RETURN key or OK click, added check that filename isn't
                  just a leafname.  Changed Desk_Save__DragIconClickHandler to use
                  Desk_Icon_StartSolidDrag.  Changed several routines to make
                  messages in their own blocks -- they used to corrupt
                  Desk_Event_lastevent.  In Desk_Save__MessageHandler the initial
                  reference check excludes unacknowledged messages that were
                  returned.
             1.03 (14 Nov 1995) JS Made SDLS compatible.
*/

#include "Desk.Event.h"
#include "Desk.WimpSWIs.h"
#include "Desk.Icon.h"
#include "Desk.Keycodes.h"
#include "Desk.Error.h"
#include "Desk.Save.h"
#include "Desk.Str.h"
#include "Desk.File.h"
#include "Desk.DeskMem.h"

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


void Desk_Save_SetFiletype(Desk_save_saveblock *saveblock, int filetype)
{
  saveblock->filetype = filetype;
  Desk_Icon_FileIcon(saveblock->window, saveblock->dragsprite, filetype);
}



static void Desk_Save__CleanIconText(Desk_window_handle window, Desk_icon_handle icon)
/* used to clean up writable-icon text - these seem to be
 * terminated by '\n', which is no use to <string.h> functions.
 */
{
  Desk_icon_block iconblock;

  Desk_Wimp_eGetIconState(window, icon, &iconblock);
  Desk_Str_MakeASCIIZ(iconblock.data.indirecttext.buffer,
                 iconblock.data.indirecttext.bufflen-1);
}


static void Desk_Save__ResetSaveBlock(Desk_save_saveblock *saveblock)
{
  saveblock->Desk_ram_progress               = 0;
  saveblock->Desk_last_message_ref           = -1;
  saveblock->flags.data.Desk_we_are_dragging = Desk_bool_FALSE;
}


static Desk_bool Desk_Save__SaveDataToFilename(char *filename, Desk_save_saveblock *saveblock)
{
  Desk_bool Desk_save_ok;

  Desk_save_ok = saveblock->filesaver(filename, saveblock->ref);

  if(Desk_save_ok)
    Desk_File_SetType(filename, saveblock->filetype);
  else
    saveblock->resulthandler(Desk_save_FILESAVERFAILED, saveblock);

  return Desk_save_ok;
}


static void Desk_Save__CloseMenuOrSaveWindow(Desk_save_saveblock *saveblock)
  /* A save has been completed with 'Select'.
   * If we were part of a menu, close menu.
   * If we were a save window, close this window.
   * If none of the above, our window contains other things, (it might be the
   *   main document window with a 'Quick save' icon), so do nothing.
   * The Desk_Event_ handlers will be released by Desk_Save_CloseHandler and
   * Desk_Save_MenusDeletedHandler, so we don't release them here.
   */
{
  if(saveblock->flags.data.Desk_is_menu)
    Desk_Wimp_eCreateMenu((Desk_menu_block *)-1, 0, 0);  /* close menu */

  if(saveblock->flags.data.Desk_is_save_window)
    Desk_Wimp_eCloseWindow(saveblock->window);      /* close window */
}


Desk_SDLS_PtrFn(
	static,
	Desk_bool,
	Desk_Save__OKIconHandler(Desk_event_pollblock *event, void *ref)
	)
/*static Desk_bool Desk_Save__OKIconHandler(Desk_event_pollblock *event, void *ref)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
{
  Desk_icon_block     iconblock;
  Desk_save_saveblock *saveblock = (Desk_save_saveblock *)ref;
  char           *filename;

  Desk_Wimp_eGetIconState(saveblock->window, saveblock->filenameicon, &iconblock);
  filename = iconblock.data.indirecttext.buffer;

  if(LeafName(filename) != filename)    /* not just a leafname in icon */
  {
    Desk_Save__SaveDataToFilename(iconblock.data.indirecttext.buffer, saveblock);
    Desk_Save__ResetSaveBlock(saveblock);

    if((event->type == Desk_event_CLICK && event->data.mouse.button.data.select) ||
        event->type == Desk_event_KEY)
      Desk_Save__CloseMenuOrSaveWindow(saveblock);
  }
  else
    Desk_Error_Report(0, "To save, drag the icon to a directory display");

  return Desk_bool_TRUE;
}


Desk_SDLS_PtrFn(
	static,
	Desk_bool,
	Desk_Save__KeyHandler(Desk_event_pollblock *event, void *ref)
	)
/*static Desk_bool Desk_Save__KeyHandler(Desk_event_pollblock *event, void *ref)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
{
  if(event->data.key.code == Desk_keycode_RETURN)
    return Desk_Save__OKIconHandler(event, ref);

  return Desk_bool_FALSE;
}


Desk_SDLS_PtrFn(
	static,
	Desk_bool,
	Desk_Save__CancelIconHandler(Desk_event_pollblock *event, void *ref)
	)
/*static Desk_bool Desk_Save__CancelIconHandler(Desk_event_pollblock *event, void *ref)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
{
  Desk_UNUSED_ARG(event);

  Desk_Save__CloseMenuOrSaveWindow((Desk_save_saveblock *)ref);
  return Desk_bool_TRUE;
}


Desk_SDLS_PtrFn(
	static,
	Desk_bool,
	Desk_Save__DragIconClickHandler(Desk_event_pollblock *event, void *ref)
	)
/*static Desk_bool Desk_Save__DragIconClickHandler(Desk_event_pollblock *event, void *ref)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
{
  Desk_save_saveblock *saveblock = (Desk_save_saveblock *)ref;

  if(!event->data.mouse.button.data.dragselect &&
     !event->data.mouse.button.data.dragadjust)
    return Desk_bool_FALSE;

  saveblock->flags.data.Desk_quit_after_save =
                                      event->data.mouse.button.data.dragselect;

  saveblock->flags.data.Desk_we_are_dragging = Desk_bool_TRUE;

  Desk_Icon_StartSolidDrag(saveblock->window, saveblock->dragsprite);

  return Desk_bool_TRUE;
}


Desk_SDLS_PtrFn(
	static,
	Desk_bool,
	Desk_Save__UserDragHandler(Desk_event_pollblock *event, void *ref)
	)
/*static Desk_bool Desk_Save__UserDragHandler(Desk_event_pollblock *event, void *ref)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
{
  Desk_save_saveblock *saveblock = (Desk_save_saveblock *)ref;
  Desk_mouse_block    ptr;
  Desk_message_block  msg;

  Desk_UNUSED_ARG(event);

  /* Is the drag nothing to do with the save drag-icon? */
  if(!saveblock->flags.data.Desk_we_are_dragging)
    return Desk_bool_FALSE;

  saveblock->flags.data.Desk_we_are_dragging = Desk_bool_FALSE;

  Desk_Wimp_eGetPointerInfo(&ptr);

  msg.header.size            = 80;
  msg.header.yourref         = 0;
  msg.header.action          = Desk_message_DATASAVE;
  msg.data.datasave.window   = ptr.window;
  msg.data.datasave.icon     = ptr.icon;
  msg.data.datasave.pos      = ptr.pos;
  msg.data.datasave.estsize  = saveblock->estimatedsize;
  msg.data.datasave.filetype = saveblock->filetype;

  strcpy(msg.data.datasave.leafname,
         LeafName(Desk_Icon_GetTextPtr(saveblock->window, saveblock->filenameicon)));

  Desk_Wimp_eSendMessage(Desk_event_SENDWANTACK, &msg, ptr.window, ptr.icon);

  saveblock->Desk_last_message_ref = msg.header.myref;

  return Desk_bool_TRUE;
}


Desk_SDLS_PtrFn(
	static,
	Desk_bool,
	Desk_Save__MenusDeletedOrWindowClosedHandler(Desk_event_pollblock *event, void *ref)
	)
/*static Desk_bool Desk_Save__MenusDeletedOrWindowClosedHandler(Desk_event_pollblock *event, void *ref)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
{
  Desk_save_saveblock *saveblock = (Desk_save_saveblock *)ref;

  /* Don't release handlers when menu/window is closed */
  if(!saveblock->flags.data.Desk_release_after)
    return Desk_bool_FALSE;

  if((event->type == Desk_event_SEND && saveblock->flags.data.Desk_is_menu &&
      event->data.message.header.action == Desk_message_MENUSDELETED) ||
     (event->type == Desk_event_CLOSE  && saveblock->flags.data.Desk_is_save_window))
  {
    Desk_Save_ReleaseSaveHandlers(ref);
    Desk_DeskMem_Free(saveblock);
  }

  return Desk_bool_FALSE;
}

Desk_SDLS_PtrFn(
	static,
	Desk_bool,
	Desk_Save__MessageHandler(Desk_event_pollblock *event, void *ref)
	)
/*static Desk_bool Desk_Save__MessageHandler(Desk_event_pollblock *event, void *ref)*/
/* cc warns about extern ... not declared in header in SDLS compiles	*/
{
  Desk_save_saveblock *saveblock = (Desk_save_saveblock *)ref;
  Desk_message_block  *msg = &event->data.message;

  if(event->type != Desk_event_ACK &&
     msg->header.yourref != saveblock->Desk_last_message_ref)
    return Desk_bool_FALSE;
  /* we are only interested in replies to our previous messages...
     or unacknowledged messages */

  if(msg->header.action == Desk_message_DATASAVE)
  {
    /* User has dragged onto useless part of the desktop...
     * This could be our original Desk_message_SENDWANTACK being sent to us
     * because the sprite was dragged onto one of *our task's* windows,
     * or a Desk_message_ACK sent to us by the WIMP because nobody responded
     * to our original message.
     */

    return Desk_bool_FALSE;  /*  the app might want to deal with this
                    *  message to save into itself.
                    */
  }

  if(msg->header.action == Desk_message_DATASAVEACK)
  {
    Desk_bool Desk_save_ok;

    Desk_save_ok = Desk_Save__SaveDataToFilename(msg->data.datasaveack.filename,
                                       saveblock);
    if(Desk_save_ok)
    {
      Desk_message_block reply;

      memcpy(&reply, msg, sizeof(Desk_message_block));

      reply.header.action = Desk_message_DATALOAD;
      reply.data.dataload.size = 49;
      reply.header.yourref = reply.header.myref;

      Desk_Wimp_eSendMessage(Desk_event_SENDWANTACK, &reply, reply.header.sender, 0);

      saveblock->Desk_last_message_ref = reply.header.myref;
    }
    else
      Desk_Save__ResetSaveBlock(saveblock);

    return Desk_bool_TRUE;
  }

  if(msg->header.action == Desk_message_RAMFETCH)
  {
    /* If we ignore Desk_message_RAMFETCH, the receiving task should
     * try again using a transfer through <Wimp$Scrap>.
     */
    int           byteswritten;
    Desk_bool          Desk_last_ramtransfer;
    Desk_message_block reply;

    if(saveblock->ramsaver == NULL)
      return Desk_bool_TRUE;

    byteswritten = (saveblock->ramsaver)(Desk_Event_taskhandle, saveblock->ref,
                                         msg->header.sender,
                                         msg->data.ramfetch.buffer,
                                         msg->data.ramfetch.buffsize,
                                         saveblock->Desk_ram_progress);

    Desk_last_ramtransfer = byteswritten < msg->data.ramfetch.buffsize;
    saveblock->Desk_ram_progress += byteswritten;

    if(byteswritten < 0)
    {
      (saveblock->resulthandler)(Desk_save_RAMSAVERFAILED, saveblock);
      Desk_Save__ResetSaveBlock(saveblock);
      return Desk_bool_TRUE;
    }

    memcpy(&reply, msg, sizeof(Desk_message_block));
    reply.header.yourref = reply.header.myref;
    reply.header.action  = Desk_message_RAMTRANSMIT;
    reply.data.ramtransmit.byteswritten = byteswritten;

    Desk_Wimp_eSendMessage((Desk_last_ramtransfer) ? Desk_event_SEND : Desk_event_SENDWANTACK,
                     &reply, reply.header.sender, 0);

    saveblock->Desk_last_message_ref = reply.header.myref;

    if(Desk_last_ramtransfer)
    {
      Desk_Save__ResetSaveBlock(saveblock);
      if(saveblock->flags.data.Desk_quit_after_save)
        Desk_Save__CloseMenuOrSaveWindow(saveblock);

      (saveblock->resulthandler)(Desk_save_SAVEOK, saveblock->ref);
    }

    return Desk_bool_TRUE;
  }

  if(msg->header.action == Desk_message_RAMTRANSMIT)
  {
    /* This is our message being returned to us by the Wimp  */
    Desk_Save__ResetSaveBlock(saveblock);
    (saveblock->resulthandler)(Desk_save_RECEIVERFAILED, saveblock->ref);
    return Desk_bool_TRUE;
  }

  if(msg->header.action == Desk_message_DATALOADACK)
  {
    /* everything OK  */
    Desk_Save__ResetSaveBlock(saveblock);
    if(saveblock->flags.data.Desk_quit_after_save)
      Desk_Save__CloseMenuOrSaveWindow(saveblock);

    (saveblock->resulthandler)(Desk_save_SAVEOK, saveblock->ref);
    return Desk_bool_TRUE;
  }

  if(msg->header.action == Desk_message_DATALOAD)
  {
    /* This is our message being returned to us by the Wimp  */
    Desk_Error_Report(0, "Received DATALOAD back - scrap file not loaded...");
    Desk_Save__ResetSaveBlock(saveblock);

    (saveblock->resulthandler)(Desk_save_RECEIVERFAILED, saveblock->ref);
    return Desk_bool_TRUE;
  }

  return Desk_bool_FALSE;
}


static void Desk_Save__DefaultResultHandler(Desk_save_result result, void *ref)
{
  Desk_UNUSED_ARG(ref);

  switch(result)
  {
    case Desk_save_RECEIVERFAILED:
      Desk_Error_Report(0, "Receiver failed to load scrap file");
      break;

    case Desk_save_FILESAVERFAILED:
      Desk_Error_Report(0, "Filer-saver failed");
      break;

    case Desk_save_RAMSAVERFAILED:
      Desk_Error_Report(0, "RAM-saver failed");
      break;
  }
}




static void Desk_Save__EventClaimOrRelease(Desk_save_saveblock *saveblock,
                                      Desk_event_claimorreleasefn fn)
{
  if(saveblock->dragsprite >= 0)
  {
    (fn)(Desk_event_CLICK, saveblock->window, saveblock->dragsprite,
         Desk_SDLS_dllEntry( Desk_Save__DragIconClickHandler), saveblock);

    (fn)(Desk_event_USERDRAG, Desk_event_ANY, Desk_event_ANY,
         Desk_SDLS_dllEntry( Desk_Save__UserDragHandler), saveblock);
  }

  if(saveblock->okbutton >= 0)
    (fn)(Desk_event_CLICK, saveblock->window, saveblock->okbutton,
         Desk_SDLS_dllEntry( Desk_Save__OKIconHandler), saveblock);

  if(saveblock->cancelbutton >= 0)
    (fn)(Desk_event_CLICK, saveblock->window, saveblock->cancelbutton,
         Desk_SDLS_dllEntry( Desk_Save__CancelIconHandler), saveblock);

  (fn)(Desk_event_KEY, saveblock->window, saveblock->filenameicon,
       Desk_SDLS_dllEntry( Desk_Save__KeyHandler), saveblock);

  (fn)(Desk_event_CLOSE, saveblock->window, Desk_event_ANY,
       Desk_SDLS_dllEntry( Desk_Save__MenusDeletedOrWindowClosedHandler), saveblock);

  (fn)(Desk_event_SEND, Desk_event_ANY, Desk_event_ANY,
       Desk_SDLS_dllEntry( Desk_Save__MenusDeletedOrWindowClosedHandler), saveblock);

  /* we always want to check whether to release the handlers
   * when a menu has been closed because flags.data.Desk_is_menu
   * may be changed after Desk_Save_InitSaveWindowHandler
   */

  (fn)(Desk_event_SEND, Desk_event_ANY, Desk_event_ANY, Desk_SDLS_dllEntry( Desk_Save__MessageHandler), saveblock);
  (fn)(Desk_event_SENDWANTACK, Desk_event_ANY, Desk_event_ANY,
       Desk_SDLS_dllEntry( Desk_Save__MessageHandler), saveblock);
  (fn)(Desk_event_ACK, Desk_event_ANY, Desk_event_ANY, Desk_SDLS_dllEntry( Desk_Save__MessageHandler), saveblock);
}



Desk_save_saveblock *Desk_Save_InitSaveWindowHandler(Desk_window_handle      window,
                                           Desk_bool               Desk_is_menu,
                                           Desk_bool               Desk_is_save_window,
                                           Desk_bool               Desk_release_after,
                                           Desk_icon_handle        dragsprite,
                                           Desk_icon_handle        okbutton,
                                           Desk_icon_handle        cancelbutton,
                                           Desk_icon_handle        filenameicon,
                                           Desk_save_filesaver     filesaver,
                                           Desk_save_ramsaver      ramsaver,
                                           Desk_save_resulthandler resulthandler,
                                           size_t             estimatedsize,
                                           int                filetype,
                                           void         *ref)
{
  Desk_save_saveblock *saveblock;

  if(!filesaver)
  {
    /* Must have a file-saver  */
    Desk_Error_Report(0, "Desk_Save_InitSaveWindowHandler called with NULL filesaver");
    return NULL;
  }

  saveblock = Desk_DeskMem_XMalloc(sizeof(Desk_save_saveblock));
  if(!saveblock)
  {
    Desk_Error_OutOfMemory(Desk_bool_FALSE, "save window");
    return NULL;
  }

  saveblock->window                    = window;
  saveblock->flags.data.Desk_is_menu        = Desk_is_menu;
  saveblock->flags.data.Desk_is_save_window = Desk_is_save_window;
  saveblock->flags.data.Desk_release_after  = Desk_release_after;
  saveblock->dragsprite                = dragsprite;
  saveblock->okbutton                  = okbutton;
  saveblock->cancelbutton              = cancelbutton;
  saveblock->filenameicon              = filenameicon;
  saveblock->filesaver                 = filesaver;
  saveblock->ramsaver                  = ramsaver;
  saveblock->resulthandler             = resulthandler;
  saveblock->estimatedsize             = estimatedsize;
  saveblock->ref                       = ref;

  Desk_Save_SetFiletype(saveblock, filetype);

  Desk_Save__ResetSaveBlock(saveblock);

  if(saveblock->resulthandler == NULL)
    saveblock->resulthandler = Desk_Save__DefaultResultHandler;

  Desk_Save__CleanIconText(saveblock->window, saveblock->filenameicon);
  /*  Make sure the terminator of the filename is '\0' - templates
   *  seem to come with a '\n' as terminator.
   */

  Desk_Save__EventClaimOrRelease(saveblock, Desk_Event_Claim);

  return saveblock;
}


void Desk_Save_ReleaseSaveHandlers(Desk_save_saveblock *saveblock)
{
  Desk_Save__EventClaimOrRelease(saveblock, Desk_Event_Release);
}
