/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : SaveFile.c                             */
/*                                                 */
/* Purpose: Handle the Save File dialogue (actual  */
/*          file saving is done in Save.c). Relies */
/*          on there being only one Save File      */
/*          dialogue open at a time (it is a       */
/*          shared object).                        */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 03-Sep-97: Created.                    */
/***************************************************/

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

#include "swis.h"

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "toolbox.h"
#include "window.h"
#include "gadgets.h"

#include "svcprint.h"
#include "Global.h"
#include "MiscDefs.h"
#include "Utils.h"

#include "Browser.h"
#include "Fetch.h"  /* (For ISLINK macro) */
#include "Filetypes.h"
#include "Hotlist.h"
#include "Images.h"
#include "Menus.h"
#include "Object.h"
#include "Protocols.h"
#include "Save.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "SaveFile.h"

/* Local statics */

static browser_data * savefile_browser = NULL;
static HStream      * savefile_token   = NULL;
static int            savefile_type    = 0x000;

static ObjectId       window_id        = 0;
static ComponentId    parent_component = -1;
static ObjectId       ancestor_id      = 0;

/* Static function prototypes */

static int savefile_drag_ended (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int savefile_ok         (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int savefile_cancel     (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);

/*************************************************/
/* savefile_open_for()                           */
/*                                               */
/* Creates and opens a Save File dialogue for a  */
/* given browser, opening near the pointer.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the ancestor of the      */
/*             dialogue;                         */
/*                                               */
/*             Object ID to use as a parent, or  */
/*             0 for none.                       */
/*************************************************/

_kernel_oserror * savefile_open_for(browser_data * b, ObjectId parent)
{
  _kernel_oserror * e;
  ObjectId          id;

  /* Create the dialogue - as this is shared object it */
  /* will not be recreated if it already exists, but   */
  /* it's more efficient to not bother even trying!    */

  if (!window_id)
  {
    RetError(toolbox_create_object(0,
                                   "SaveFile",
                                   &id));
  }
  else id = window_id;

  /* Show it - the ToBeShown event does the rest */

  RetError(toolbox_show_object(Toolbox_ShowObject_AsMenu,
                               id,
                               Toolbox_ShowObject_AtPointer,
                               NULL,
                               parent,
                               -1));
  return NULL;
}

/*************************************************/
/* savefile_to_be_shown()                        */
/*                                               */
/* Fills in the Save File dialogue prior to      */
/* being shown, on the basis of the parent       */
/* component ID and ancestor object ID in the    */
/* event.                                        */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int savefile_to_be_shown(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  char           text[Limits_OS_Pathname];
  browser_data * b;
  int            first_time = !window_id;
  int            ok         = 0;
  ObjectId       pc         = idb->parent_component;

  /* If this is the first time we've been opened, do a few */
  /* initialisation bits and pieces                        */

  if (first_time)
  {
    /* Process the writable icon text */

    ChkError(windows_process_component_text(idb->self_id, SaveFileWrit, text, sizeof(text), 0, 1));

    /* The following ensures that we increment the usage */
    /* count for the dialogue so it is never deleted.    */
    /* Otherwise, we have to start keeping track of when */
    /* it goes and deal with event handlers etc. as      */
    /* appropriate. Creating the object like this only   */
    /* gives back the existing ID of what is already     */
    /* there, so this works out as taking up less time   */
    /* and less code than an alternative approach.       */

    ChkError(toolbox_create_object(0, "SaveFile", NULL));
  }

  /* Where did we come from? */

  ChkError(toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b));

  /* If the parent ID is the same as a toolbar, this was */
  /* from a toolbar button - in which case, use a parent */
  /* ID of FileSaveParent, as that is what we actually    */
  /* want to do.                                         */

  if (
       idb->parent_id == toolbars_get_upper(b) ||
       idb->parent_id == toolbars_get_lower(b)
     )
     pc = FileSaveParent;

  /* Deal with each parent menu item case */

  switch (pc)
  {
    case -1:
    {
      /* We came from a keyboard shortcut, so want to save the */
      /* current page source.                                  */

      pc = FileSaveFrame;

      /* Slight complication is that only an ancestor browser ever   */
      /* has the input focus, so we need to find the actual selected */
      /* frame that the user is going to think the dialogue is for.  */

      if (b->selected_frame) b = b->selected_frame;

      /* No 'break' - let this drop through... */
    }

    /* Save the page source */

    case FileSaveFrame: /* Fall through to FileSaveParent */
    case FileSaveParent:
    {
      /* For saving a frameset, work out the required browser_data struct */

      if (pc == FileSaveParent)
      {
        if      (b->parent      && b->parent     ->source) b = b->parent;
        else if (b->real_parent && b->real_parent->source) b = b->real_parent;
        else if (b->ancestor    && b->ancestor   ->source) b = b->ancestor;
      }

      /* Reset the transferred data counter */

      b->save_transferred = 0;

      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, browser_current_url(b)));

      /* Set the draggable sprite */

      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, b->page_is_text ? FileType_TEXT : FileType_HTML));

      /* Remember various details about the dialogue's source */

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    #ifndef REMOTE_HOTLIST

      case HotlistSaveHotlist:
      {
        ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, lookup_token("HotlistLeafname:Hotlist",0,0)));
        ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_HTML));

        ok = 1;
      }
      break;

      /* Save an object from the hotlist - a URL, directory, */
      /* or general selection                                */

      case MiscHotlistSaveObject:
      {
        hotlist_item * source = hotlist_find_selected_item();
        unsigned int   items  = hotlist_count_selected_items();

        if (items && source)
        {
          if (items == 1 && source->type == hl_url)
          {
            ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, source->data.url));
            ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_URI));
          }
          else
          {
            ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, lookup_token("HotlistLeafname:Hotlist",0,0)));
            ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_HTML));
          }

          ok = 1;
        }

        #ifdef TRACE

          else
          {
            erb.errnum = Utils_Error_Custom_Normal;

            sprintf(erb.errmess,
                    "Should have more than a selected hotlist item %p and item count %d in savefile_to_be_shown",
                    source,
                    items);

            ChkError(&erb);
          }

        #endif
      }
      break;

    #endif

    case ExportAsDraw:
    {
      ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, "(NotDone!)"));
      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_DRAW));
    }
    break;

    case ExportAsText:
    {
      ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, "(NotDone!)"));
      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_TEXT));
    }
    break;

    /* Save the current location as a URI file */

    case FileSaveCurrentLocation:
    {
      char * url = browser_current_url(b);

      if (!url) url = " ";

      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, url));
      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_URI));

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    /* Export the link the pointer was over when the menu opened */

    case ExportLink:
    {
      HStream * link = menus_main_opened_over();

      if (!link || !ISLINK(link))
      {
        #ifndef TRACE

          return 0;

        #else

          erb.errnum = Utils_Error_Custom_Normal;

          sprintf(erb.errmess,
                  "Menu token %p is not a link or has no anchor text in savefile_to_be_shown",
                  link);

          show_error_ret(&erb);

          return 0;

        #endif
      }

      /* Set the leafname and filetype */

      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, link->anchor));
      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_URI));

      /* Remember various details about the dialogue's source */

      savefile_browser = b;
      savefile_token   = link;

      ok = 1;
    }
    break;

    case ExportPicture:
    {
      HStream * image = menus_main_opened_over();

      if (
           !image ||
           !
           (
             (image->style & IMG) ||
             (
               image->tagno == TAG_INPUT &&
               HtmlINPUTtype(image) == inputtype_IMAGE
             )
             ||
             (
               ISOBJECT(image) &&
               object_token_is_image(b, image)
             )
           )
         )
      {
        #ifndef TRACE

          return 0;

        #else

          erb.errnum = Utils_Error_Custom_Normal;

          sprintf(erb.errmess,
                  "Menu token %p is not an image in savefile_to_be_shown",
                  image);

          show_error_ret(&erb);

          return 0;

        #endif
      }

      if (image->style & IMG)
      {
        ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, image->src));
      }
      else if (image->tagno == TAG_INPUT)
      {
        ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, (char *) HtmlINPUTsrc(image)));
      }
      else
      {
        ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, (char *) HtmlOBJECTdata(image)));
      }

      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_SPR));

      /* Remember various details about the dialogue's source */

      savefile_browser = b;
      savefile_token   = image;

      ok = 1;
    }
    break;

    case ExportBackground:
    {
      if (b->background_image == -1)
      {
        #ifndef TRACE

          return 0;

        #else

          erb.errnum = Utils_Error_Custom_Normal;

          StrNCpy0(erb.errmess,
                   "There is no background image on this page in savefile_to_be_shown");

          show_error_ret(&erb);

          return 0;

        #endif
      }

      ChkError(writablefield_set_value(0, idb->self_id, SaveFileWrit, "Background"));
      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_SPR));

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    #ifdef TRACE

      default:
      {
        erb.errnum = 0;
        StrNCpy0(erb.errmess,
                 "Save dialogue origin not understood in savefile_to_be_shown");

        show_error_ret(&erb);

        return 0;
      }
      break;

    #endif
  }

  /* If we were successful, install relevant event handlers etc., assuming */
  /* this is the first time the dialogue was opened.                       */

  if (ok)
  {
    parent_component   = pc;
    ancestor_id        = b->self_id;

    if (first_time)
    {
      window_id = idb->self_id;

      ChkError(event_register_toolbox_handler(window_id, Draggable_DragEnded, savefile_drag_ended, NULL));
      ChkError(event_register_toolbox_handler(window_id, ESaveFileOK,         savefile_ok,         NULL));
      ChkError(event_register_toolbox_handler(window_id, ESaveFileCancel,     savefile_cancel,     NULL));
    }
  }

  return 1;
}

/*************************************************/
/* savefile_set_leafname_from_url()              */
/*                                               */
/* Sets the leafname in the Save File dialogue   */
/* from the given URL, preserving whatever       */
/* path component may already have been present. */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Component ID of the writable      */
/*             gadget;                           */
/*                                               */
/*             Pointer to a null terminated URL  */
/*             to work with.                     */
/*************************************************/

_kernel_oserror * savefile_set_leafname_from_url(ObjectId object, ComponentId component, char * url)
{
  _kernel_oserror * e;
  char            * dot;
  char              path[Limits_OS_Pathname];

  /* See what filename is already in the dialogue */

  path[0] = 0; /* Important so that when we strcat the leafname later, */
               /* things will work even if the writable holds no text. */

  RetError(writablefield_get_value(0,
                                   object,
                                   component,
                                   path,
                                   sizeof(path),
                                   NULL));
  path[sizeof(path) - 1] = 0;

  /* If there's a leafname here, force a terminator in place of the '.' */

  dot = strrchr(path, '.');

  if (dot) *(++dot) = 0;
  else dot = path;

  /* So 'dot' points somewhere inside 'path' - tag on the leafname */

  urlutils_leafname_from_url(url, dot, sizeof(path) - ((int) path - (int) dot));

  /* Set the value */

  return writablefield_set_value(0,
                                 object,
                                 component,
                                 path);
}

/*************************************************/
/* savefile_set_filetype()                       */
/*                                               */
/* Sets the sprite of the draggable object in    */
/* the Save File dialogue according to the given */
/* filetype, and records that filetype in        */
/* savefile_type.                                */
/*                                               */
/* If the sprite cannot be found in the Wimp     */
/* sprite pool, 'file_xxx' is used instead.      */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Component ID of the draggable     */
/*             gadget;                           */
/*                                               */
/*             The filetype.                     */
/*************************************************/

_kernel_oserror * savefile_set_filetype(ObjectId object, ComponentId component, int type)
{
  char sprite[Limits_OS_SpriteName];
  int  len;

  savefile_type = type;

  /* Will it fit in the buffer? */

  len = utils_len_printf("file_%03x", type);

  /* If so, build the sprite name and tell the gadget to use that sprite */

  if (len < sizeof(sprite))
  {
    sprintf(sprite, "file_%03x", type);

    if (
         !_swix(Wimp_SpriteOp,
                _IN(0) | _IN(2),

                0x18, /* Select sprite */
                sprite)
       )
    {
      /* If it has been found, use this sprite */

      return draggable_set_sprite(0,
                                  object,
                                  component,
                                  sprite);
    }
    else
    {
      /* Otherwise use the generic 'file_xxx' instead */

      return draggable_set_sprite(0,
                                  object,
                                  component,
                                  "file_xxx");
    }
  }

  return NULL;
}

/*************************************************/
/* savefile_drag_ended()                         */
/*                                               */
/* Handle Draggable_DragEnded events from the    */
/* Save File dialogue draggable sprite gadget.   */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_drag_ended(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  DraggableDragEndedEvent * drag   = (DraggableDragEndedEvent *) event;
  protocols_saving          saving = protocols_saving_nothing;
  int                       size   = 4096; /* More or less arbitrary */
  WimpGetPointerInfoBlock   info;
  int                       window_handle;
  char                      path[Limits_OS_Pathname];
  char                    * leaf;
  void                    * extra  = savefile_token;

  /* If the user dragged back to the save dialogue, do nothing */

  ChkError(window_get_wimp_handle(0,
                                  window_id,
                                  &window_handle));

  if (window_handle == drag->window_handle) return 1;

  /* Get the pathname from the Save File dialogue. */

  ChkError(writablefield_get_value(0,
                                   window_id,
                                   SaveFileWrit,
                                   path,
                                   sizeof(path),
                                   NULL));

  path[sizeof(path) - 1] = 0;

  /* Point to the leafname component */

  leaf = strrchr(path, '.');
  if (!leaf) leaf = path;
  else       leaf ++;

  /* Work out the estimated data size */

  switch (parent_component)
  {
    /* Size of an HTML file */

    case FileSaveFrame:
    case FileSaveParent:
    {
      saving = protocols_saving_document_source;

      if (is_known_browser(savefile_browser)) size = save_source_size(savefile_browser);
      else                                    size = 0;
    }
    break;

    /* Size of a URI file for the current location */

    case FileSaveCurrentLocation:
    {
      char * url = browser_current_url(savefile_browser);

      if (!url) url = " ";

      saving = protocols_saving_link;
      size   = save_uri_size(url, 0);
    }
    break;

    /* Size of a URI file */

    case ExportLink:
    {
      saving = protocols_saving_link;

      if (savefile_token && savefile_token->anchor) size = save_uri_size(savefile_token->anchor, 0);
      else                                          size = 0;
    }
    break;

    /* Size of an image to export */

    case ExportPicture:
    {
      saving = protocols_saving_image_sprite;

      if (savefile_token && is_known_browser(savefile_browser)) size = image_sprite_size(savefile_browser, savefile_token);
      else                                                      size = 0;
    }
    break;

    /* Save a background image */

    case ExportBackground:
    {
      saving = protocols_saving_image_sprite;

      if (is_known_browser(savefile_browser)) size = image_sprite_size(savefile_browser, NULL);
      else                                    size = 0;
    }
    break;

    #ifndef REMOTE_HOTLIST

      /* Saving the entire hotlist */

      case HotlistSaveHotlist:
      {
        saving = protocols_saving_entire_hotlist;
      }
      break;

      /* Save an object from the hotlist - a URL, directory, */
      /* or general selection                                */

      case MiscHotlistSaveObject:
      {
        hotlist_item * source = hotlist_find_selected_item();
        unsigned int   items  = hotlist_count_selected_items();

        if (items && source)
        {
          if (items == 1 && source->type == hl_url)
          {
            saving = protocols_saving_hotlist_entry;
            extra  = (void *) source;
            size   = save_uri_size(source->data.url, 0);
          }
          else saving = protocols_saving_hotlist_selection;
        }
      }
      break;

    #endif

    /* For others, could leave size as 4096 - *but must set 'saving' appropriately* */
  }

  /* Send out the DataSave message */

  info.x             = drag->x;
  info.y             = drag->y;
  info.window_handle = drag->window_handle;
  info.icon_handle   = drag->icon_handle;

  ChkError(protocols_atats_send_data_save(savefile_browser,
                                          extra,
                                          leaf,
                                          size,
                                          savefile_type,
                                          saving,
                                          &info));
  return 1;
}

/*************************************************/
/* savefile_ok()                                 */
/*                                               */
/* Handles clicks on the 'OK' button in the      */
/* Save File dialogue.                           */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_ok(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  return 1;
}

/*************************************************/
/* savefile_cancel()                             */
/*                                               */
/* Handles clicks on the 'Cancel' button in the  */
/* Save File dialogue                            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  /* If we were fetching, stop the fetch */

  if (savefile_browser && is_known_browser(savefile_browser))
  {
    if (savefile_browser->save_link) fetch_stop(savefile_browser, 0);
  }

  /* We don't do anything sophisticated, like */
  /* restoring previous options here, as the  */
  /* dialogue is too simple for it to be      */
  /* worthwhile. Therefore, just close it.    */

  ChkError(savefile_close(0,0));

  return 1;
}

/*************************************************/
/* savefile_close()                              */
/*                                               */
/* If the Save File dialogue is opened, this     */
/* will close it.                                */
/*                                               */
/* Parameters: An object ID, or 0. If not zero,  */
/*             the ID must match the ancestor    */
/*             recorded when the dialogue was    */
/*             opened or no action is taken.     */
/*                                               */
/*             0 to close the dialogue, 1 to do  */
/*             everything except that.           */
/*************************************************/

_kernel_oserror * savefile_close(ObjectId ancestor, int do_not_close)
{
  _kernel_oserror * e = NULL;

  if (!window_id) return NULL;
  if (ancestor && ancestor != ancestor_id) return NULL;

  /* If required, close the dialogue */

  if (!do_not_close && !e)
  {
    /* If the dialogue came from a menu tree, collapse the tree */

    if (parent_component != -1) e = _swix(Wimp_CreateMenu, _IN(1), -1);

    /* Close the dialogue */

    if (!e) e = toolbox_hide_object(0, window_id);
  }

  parent_component = -1;
  ancestor_id      = 0;

  savefile_browser = NULL;
  savefile_token   = NULL;
  savefile_type    = 0x000;

  return e;
}

/*************************************************/
/* savefile_return_dialogue_info()               */
/*                                               */
/* Returns information on the Save File dialogue */
/* and its ancestor.                             */
/*                                               */
/* Parameters: Pointer to an ObjectId, in which  */
/*             the ID of the dialogue is placed; */
/*                                               */
/*             Pointer to an ObjectId, in which  */
/*             the ID of the ancestor window is  */
/*             placed.                           */
/*                                               */
/* Returns:    See parameters list, and note     */
/*             that the returned values will be  */
/*             0 and 0 if the Save File dialogue */
/*             is closed.                        */
/*                                               */
/* Assumes:    Either pointer may be NULL.       */
/*************************************************/

void savefile_return_dialogue_info(ObjectId * window, ObjectId * ancestor)
{
  if (window)   *window   = window_id;
  if (ancestor) *ancestor = ancestor_id;
}