/* 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   : Save.c                                 */
/*                                                 */
/* Purpose: Save functions for the browser.        */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 04-Dec-96: Created.                    */
/***************************************************/

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

#include "swis.h"
#include "flex.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 "menu.h"
#include "saveas.h"

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

#include "ChoiceDefs.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "Filetypes.h"
#include "Fontmanage.h"
#include "Hotlist.h"
#include "Protocols.h"
#include "URLutils.h"

#include "Save.h"

/* Static function prototypes */

static int  save_save_string  (FILE * file, char * string);
static int  save_save_colour  (FILE * file, int colour);
static int  save_save_number  (FILE * file, int number);
static int  save_save_yes_no  (FILE * file, int yes);
static int  save_save_general (FILE * file, int how_many, const int numbers[], const char * strings[], int match, int def);

/* Locals */

static char * last_path = NULL;

/*************************************************/
/* save_record_path()                            */
/*                                               */
/* Record the given pathname for future use. No  */
/* errors are raised if the memory allocation to */
/* do this fails.                                */
/*                                               */
/* Note that <Wimp$Scrap>... and                 */
/* <Wimp$ScrapDir>... are two exceptions - paths */
/* starting with these are not recorded.         */
/*                                               */
/* Parameters: Pointer to a pathname to record.  */
/*************************************************/

void save_record_path(const char * path)
{
  if (!path) return;

  /* Check for the special cases */

  if (!strncmp(path, "<Wimp$Scrap>", 12) || !strncmp(path, "<Wimp$ScrapDir>", 15)) return;

  /* Otherwise, record the path, failing silently if */
  /* we can't claim enough memory for it.            */

  free(last_path);

  last_path = malloc(strlen(path) + 1);

  if (last_path) strcpy(last_path, path);
}

/*************************************************/
/* save_return_last_path()                       */
/*                                               */
/* Used mostly for filling in writables of save  */
/* dialogues with paths based on previous saves. */
/*                                               */
/* Returns:    Pointer to the last pathname that */
/*             was used for saving, or NULL if   */
/*             no such record is available.      */
/*************************************************/

const char * save_return_last_path(void)
{
  return last_path;
}

/*************************************************/
/* save_save_source()                            */
/*                                               */
/* Saves the document source for a given browser */
/* to the given path, setting the filetype as    */
/* HTML or text as appropriate.                  */
/*                                               */
/* Parameters: Pointer to a null-terminated      */
/*             pathname to save to;              */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             owning the source to save.        */
/*************************************************/

_kernel_oserror * save_save_source(char * path, browser_data * b)
{
  _kernel_oserror * e;

  /* Sanity checks */

  if (!b || !b->source || !path || !*path)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_save_source: %p, %p (source %p)",
              path,
              b,
              b ? b->source : NULL);

      return &erb;

    #else

      return NULL;

    #endif
  }

  save_record_path(path);

  /* Save the file - lock flex heap to ensure save transfer */

  flex_set_budge(0);

  e = _swix(OS_File,
            _INR(0,2) | _INR(4,5),

            10, /* Save block of memory as a typed file */
            path,
            b->page_is_text ? FileType_TEXT : FileType_HTML,
            b->source,
            ((char *) b->source) + flex_size((flex_ptr) &b->source));

  /* Unlock flex */

  flex_set_budge(1);

  return e;
}

/*************************************************/
/* save_transfer_source()                        */
/*                                               */
/* Save a browser's page source as an HTML file, */
/* through a RAM transfer buffer with            */
/* Wimp_TransferBlock.                           */
/*                                               */
/* Intended to be called as a response to a      */
/* Message_RAMFetch from another task.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the document source;  */
/*                                               */
/*             Pointer to an int, in which the   */
/*             amount of data transferred so     */
/*             far should be stored on entry;    */
/*                                               */
/*             Pointer to the WimpMessage struct */
/*             corresponding to the              */
/*             Message_RAMFetch that led to this */
/*             function being called.            */
/*                                               */
/* Returns:    The contents of the int holding   */
/*             the amount of data transferred    */
/*             prior to the function call are    */
/*             updated with the new amount       */
/*             transferred. Callers should use   */
/*             this in any future calls, and it  */
/*             *must* be checked for a value of  */
/*             -1, which indicates the transfer  */
/*             is complete (so any tidying up    */
/*             should be done if there is an     */
/*             error returned, or if the int is  */
/*             filled in with a value of -1).    */
/*                                               */
/* Assumes:    The various pointers may be NULL, */
/*             though if they are the function   */
/*             does nothing (it just exits).     */
/*************************************************/

_kernel_oserror * save_transfer_source(browser_data * b, int * transferred, WimpMessage * m)
{
  _kernel_oserror * e     = NULL;
  int               size  = save_source_size(b);
  int               left;
  int               write;

  /* Sanity check */

  if (!b || !b->source || !transferred || !m)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_transfer_source: %p (source %p), %p, %p",
              b,
              b ? b->source : NULL,
              transferred,
              m);

      return &erb;

    #else

      return NULL;

    #endif
  }

  left = size - *transferred;

  /* If we have data to transfer, do so */

  if (left >= 0)
  {
    /* Use either the buffer size, or the bytes left, whichever */
    /* is the smallest.                                         */

    write = left > m->data.ram_fetch.buffer_size ? m->data.ram_fetch.buffer_size : left;

    if (write)
    {
      /* Transfer the data - must lock the flex heap for the */
      /* duration of this...                                 */

      flex_set_budge(0);

      e = wimp_transfer_block(task_handle,
                              (char *) b->source + (*transferred),
                              m->hdr.sender,
                              m->data.ram_fetch.buffer,
                              write);

      /* Unlock flex and report any errors */

      flex_set_budge(1);

      if (e) return e;
    }

    /* If we have any data left to send, reply with a Message_RAMTransmit */

    e = protocols_atats_send_ram_transmit(m, write, write < m->data.ram_fetch.buffer_size);

    /* Increment the transferred counter */

    *transferred += write;
    left         -= write;
  }

  /* Finished */

  return e;
}

/*************************************************/
/* save_source_size()                            */
/*                                               */
/* Returns the size that a given HTML source     */
/* document would take on disc.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             owning the source to save.        */
/*                                               */
/* Returns:    Size the file will be.            */
/*************************************************/

int save_source_size(browser_data * b)
{
  /* Sanity check */

  if (!b || !b->source)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_source_size: %p (source %p)",
              b,
              b ? b->source : NULL);

      show_error_ret(&erb);

    #endif

    return 0;
  }

  /* For now this is trivially simple, but in future it */
  /* could take account of, for example, inserting a    */
  /* <BASE> tag into the document.                      */

  return flex_size((flex_ptr) &b->source);
}

/*************************************************/
/* save_save_object()                            */
/*                                               */
/* Called when a fetch is to be spooled to disc. */
/* Handles saving as much data as is already     */
/* fetched, and setting the relevant parts of    */
/* the given browser_data structure up with the  */
/* output file details.                          */
/*                                               */
/* Parameters: Pointer to a null-terminated      */
/*             pathname to save to;              */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the object to save.   */
/*************************************************/

_kernel_oserror * save_save_object(char * path, browser_data * b)
{
  /* Sanity check */

  if (!b || !path || !*path)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_save_object: %p, %p",
              path,
              b);

      return &erb;

    #else

      return NULL;

    #endif
  }

  save_record_path(path);

  /* If using a small fetch window, set the title to the save pathname */

  if (b->small_fetch)
  {
    char title[Limits_Title];

    StrNCpy0(title, path);

    /* Don't treat any errors here as fatal */

    show_error_ret(window_set_title(0, b->self_id, title));
  }

  /* Open the file */

  b->save_file = fopen(path, "wb");

  if (!b->save_file)
  {
    fetch_stop(b, 0);
    RetLastE;
  }
  else
  {
    int bytes;

    /* Set the filetype to DEADDEAD, to represent an incomplete */
    /* file (particularly good on later Filers, which display   */
    /* a special sprite for this).                              */

    _swix(OS_File,
          _INR(0,2),

          2, /* Set load address */
          path,
          0xdeaddead);

    _swix(OS_File,
          _INR(0,1) | _IN(3),

          3, /* Set exec address */
          path,
          0xdeaddead);

    if (b->source)
    {
      /* Any data in the source store represents already */
      /* fetched bits of the file. Must lock flex down   */
      /* over the save to make sure the heap doesn't     */
      /* shift over the call to fwrite.                  */

      flex_set_budge(0);

      bytes = fwrite(b->source,
                     1,
                     flex_size((flex_ptr) &b->source),
                     b->save_file);

      flex_set_budge(1);

      /* If we didn't transfer as much as we expected, complain */

      if (bytes != flex_size((flex_ptr) &b->source))
      {
        /* Report any errors */

        fetch_stop(b, 0);
        RetLastE;
      }
      else
      {
        /* Otherwise, get rid of the data in the source store */
        /* as it's been written to the file.                  */

        flex_free((flex_ptr) &b->source);
        b->source = NULL;
      }
    }
  }

  return NULL;
}

/*************************************************/
/* save_object_size()                            */
/*                                               */
/* Returns the estimated size that a given       */
/* object will be after being spooled through    */
/* the fetcher.                                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the object to save.   */
/*                                               */
/* Returns:    *Estimated* size of the object.   */
/*************************************************/

int save_object_size(browser_data * b)
{
  /* For now, we just don't know this information... */

  return 4096;
}

/*************************************************/
/* save_save_uri()                               */
/*                                               */
/* Save the contents of a link as a URI file.    */
/*                                               */
/* Parameters: Pointer to a null-terminated      */
/*             pathname to save to;              */
/*                                               */
/*             Pointer to the URI to save;       */
/*                                               */
/*             Pointer to the URL title, or NULL */
/*             / null string for no title;       */
/*                                               */
/*             0 to save as a URI file, else     */
/*             save as a non-terminated string   */
/*             with type FileType_URL (ANT       */
/*             suite URL file).                  */
/*************************************************/

_kernel_oserror * save_save_uri(char * path, char * url, char * title, int write_url)
{
  _kernel_oserror * e = NULL;
  FILE            * file;

  /* Sanity check */

  if (title && !*title) title = NULL;

  if (!path || !*path || !url || !*url)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_save_uri: %p, %p, %d",
              path,
              url,
              write_url);

      return &erb;

    #else

      return NULL;

    #endif
  }

  save_record_path(path);

  /* Try and open the file */

  file = fopen(path, "wb");
  if (!file) RetLastE;

  /* Write the contents */

  if (!write_url)
  {
    /* URI file */

    if (
         fprintf(file,
                 "URI\t100\n\t# %s v",
                 lookup_token("_TaskName",1,0)) < 0
       )
       erb = *_kernel_last_oserror(), e = &erb;

    else if (
              fprintf(file,
                      "%s\n\n\t%s",
                      lookup_token("Version:(Unknown!)",0,0),
                      url) < 0
            )
            erb = *_kernel_last_oserror(), e = &erb;

    else if (
              fprintf(file,
                      "\n\t%s",
                      title ? title : "*") < 0
            )
            erb = *_kernel_last_oserror(), e = &erb;
  }
  else
  {
    /* URL file */

    if (fprintf(file, url) < 0) erb = *_kernel_last_oserror(), e = &erb;
  }

  /* Close the file and return any error that may have */
  /* happened during the file writing stage            */

  fclose(file);

  if (e) return &erb;

  /* Exit via. setting the filetype */

  return _swix(OS_File,
               _INR(0,2),

               18, /* Set type of named object */
               path,
               write_url ? FileType_URL : FileType_URI);
}

/*************************************************/
/* save_transfer_uri()                           */
/*                                               */
/* Save the contents of a link as a URI file,    */
/* through a RAM transfer buffer with            */
/* Wimp_TransferBlock.                           */
/*                                               */
/* Intended to be called as a response to a      */
/* Message_RAMFetch from another task.           */
/*                                               */
/* Parameters: Pointer to the URL to save;       */
/*                                               */
/*             Pointer to the URL title, or NULL */
/*             / null string for no title;       */
/*                                               */
/*             0 to save as a URI file, else     */
/*             save as a non-terminated string   */
/*             with type FileType_URL (ANT       */
/*             suite URL file);                  */
/*                                               */
/*             Pointer to an int, in which the   */
/*             amount of data transferred so     */
/*             far should be stored on entry;    */
/*                                               */
/*             Pointer to the WimpMessage struct */
/*             corresponding to the              */
/*             Message_RAMFetch that led to this */
/*             function being called.            */
/*                                               */
/* Returns:    The contents of the int holding   */
/*             the amount of data transferred    */
/*             prior to the function call are    */
/*             updated with the new amount       */
/*             transferred. Callers should use   */
/*             this in any future calls, and it  */
/*             *must* be checked for a value of  */
/*             -1, which indicates the transfer  */
/*             is complete (so any tidying up    */
/*             should be done if there is an     */
/*             error returned, or if the int is  */
/*             filled in with a value of -1).    */
/*                                               */
/* Assumes:    The various pointers may be NULL, */
/*             though if they are the function   */
/*             does nothing (it just exits).     */
/*************************************************/

_kernel_oserror * save_transfer_uri(char * url, char * title, int write_url, int * transferred, WimpMessage * m)
{
  _kernel_oserror * e        = NULL;
  char            * uri_file = NULL;
  int               size     = save_uri_size(url, title, write_url);
  int               left;
  int               write;

  /* Sanity check */

  if (title && !*title) title = NULL;

  if (!url || !*url || !transferred || !m)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_transfer_uri: %p, %d, %p, %p",
              url,
              write_url,
              transferred,
              m);

      return &erb;

    #else

      return NULL;

    #endif
  }

  left = size - *transferred;

  /* Each time the function is called, rebuild the URI file in a */
  /* temporary buffer.                                           */

  if (size > 0 && left > 0)
  {
    uri_file = malloc(size + 1); /* + 1 to account for terminators */

    if (!uri_file)
    {
      e = make_no_memory_error(10);
      goto save_transfer_uri_exit;
    }

    /* Build the URI or URL file */

    if (!write_url)
    {
      int written;

      /* URI file */

      written = sprintf(uri_file,
                        "URI\t100\n\t# %s v",
                        lookup_token("_TaskName",1,0));

      written += sprintf(uri_file + written,
                         "%s\n\n\t%s",
                         lookup_token("Version:(Unknown!)",0,0),
                         url);

      written += sprintf(uri_file + written,
                         "\n\t%s",
                         title ? title : "*");
    }
    else
    {
      /* URL file */

      strcpy(uri_file, url);
    }
  }

  /* If we have data to transfer, do so */

  if (left >= 0)
  {
    /* Use either the buffer size, or the bytes left, whichever */
    /* is the smallest.                                         */

    write = left > m->data.ram_fetch.buffer_size ? m->data.ram_fetch.buffer_size : left;

    if (write)
    {
      /* Transfer the data */

      e = wimp_transfer_block(task_handle,
                              uri_file + (*transferred),
                              m->hdr.sender,
                              m->data.ram_fetch.buffer,
                              write);

      if (e) goto save_transfer_uri_exit;
    }

    /* If we have any data left to send, reply with a Message_RAMTransmit */

    e = protocols_atats_send_ram_transmit(m, write, write < m->data.ram_fetch.buffer_size);

    /* Increment the transferred counter */

    *transferred += write;
    left         -= write;
  }

  /* Finished */

save_transfer_uri_exit:

  /* Free the URI file buffer, if we claimed one */

  if (uri_file) free (uri_file);

  return e;
}

/*************************************************/
/* save_uri_size()                               */
/*                                               */
/* Returns the size, in bytes, that a URI or URL */
/* file will be.                                 */
/*                                               */
/* Parameters: Pointer to the URL that would be  */
/*             saved;                            */
/*                                               */
/*             Pointer to the URL title, or NULL */
/*             / null string for no title;       */
/*                                               */
/*             0 to find the length of a URI     */
/*             file, 1 to find the length of a   */
/*             URL file (as in save_save_uri).   */
/*                                               */
/* Returns:    The size, in bytes, that the file */
/*             will be.                          */
/*************************************************/

int save_uri_size(char * url, char * title, int write_url)
{
  int len = 0;

  /* Sanity check */

  if (title && !*title) title = NULL;

  if (!url || !*url)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Invalid parameters to save_uri_size: %p, %d",
              url,
              write_url);

      show_error_ret(&erb);

    #endif

    return 0;
  }

  if (!write_url)
  {
    /* URI file */

    len =  strlen("URI\t100\n\t# ");
    len += strlen(lookup_token("_TaskName",1,0));

    len += strlen(" v");
    len += strlen(lookup_token("Version:(Unknown!)",0,0));

    len += strlen("\n\n\t");
    len += strlen(url);

    len += strlen("\n\t");
    len += strlen(title ? title : "*");
  }
  else
  {
    /* URL file */

    len = strlen(url); /* (No terminator needed in URL files, so no '+ 1') */
  }

  return len;
}

/*************************************************/
/* save_build_messages_path()                    */
/*                                               */
/* Builds a pathname in a malloc block through   */
/* which the Choices or Controls file may be     */
/* accessed. The caller is responsible for       */
/* freeing the block.                            */
/*                                               */
/* Parameters: 0 for the Choices file (to load), */
/*             1 for the Controls file (to       */
/*             load), 2 for the Choices file (to */
/*             save), 3 for the Controls file    */
/*             (to save).                        */
/*                                               */
/* Returns:    Pointer to a null-terminated      */
/*             pathname, in a malloc block, or   */
/*             NULL if there was an error.       */
/*************************************************/

char * save_build_messages_path(int which)
{
  int    len, exists;
  char * path;
  char * sysvar;
  char * defpath;

  len =  strlen(lookup_token("_TaskName", 1, 0));
  len += sizeof("$ControlsFile"); /* Longer than "$ChoicesFile"! */

  sysvar = malloc(len);

  if (!sysvar) return NULL;
  else strcpy(sysvar, tokens);

  switch (which)
  {
    case 0:
    {
      strcat(sysvar, "$ChoicesFile");
      defpath = ".Choices";
    }
    break;

    case 1:
    {
      strcat(sysvar, "$ControlsFile");
      defpath = ".Controls";
    }
    break;

    case 2:
    {
      strcat(sysvar, "$ChoicesSave");
      defpath = ".Choices";
    }
    break;

    case 3:
    {
      strcat(sysvar, "$ControlsSave");
      defpath = ".Controls";
    }
    break;

    default:
    {
      #ifdef TRACE

        erb.errnum = Utils_Error_Custom_Fatal;

        sprintf(erb.errmess,
                "In open_messages_file, passed unrecognised parameter value '%d'",
                which);

        /* OK, so you'll get the above error and then callers will */
        /* probably think this call ran out of memory due to the   */
        /* NULL return (assuming the below call doesn't cause an   */
        /* immediate exit - it depends on what stage of            */
        /* initialisation the browser is at). But at least you get */
        /* to see what is happening.                               */

        show_error_ret(&erb);
        free(sysvar);

        return NULL;

      #else

        free(sysvar);

        return NULL;

      #endif
    }
  }

  /* First, find the system variable length. Must use _kernel_swi */
  /* here for various reasons.                                    */

  {
    _kernel_swi_regs r;

    r.r[0] = (int) sysvar;
    r.r[1] = (int) NULL;
    r.r[2] = -1;
    r.r[3] = 0;
    r.r[4] = 0;

    /* Equivalent to getenv(), but the RISC OS implementation evaluates */
    /* the system variable as an expression which we don't want (at     */
    /* least, not under RISC OS); hence the direct use of the SWI.      */

    _kernel_swi(OS_ReadVarVal, &r, &r);

    len = -r.r[2]; /* This includes a terminator */
  }

  if (!len)
  {
    /* Variable doesn't exist */

    len    = strlen(task_dir) + strlen(defpath) + 1;
    exists = 0;
  }
  else exists = 1;

  /* Allocate space */

  path = calloc(len + 1, 1);

  if (!path)
  {
    free(sysvar);
    return NULL;
  }

  /* Read the variable or copy the data in */

  if (exists) _swix(OS_ReadVarVal,
                    _INR(0,4),

                    sysvar, /* Variable name                      */
                    path,   /* Buffer                             */
                    len,    /* Size of buffer                     */
                    0,      /* Name pointer (0 for 1st call)      */
                    4);     /* Variable type (4 = literal string) */
  else
  {
    strcpy(path, task_dir);
    strcat(path, defpath);
  }

  free(sysvar);

  return path;
}

/*************************************************/
/* save_save_string()                            */
/*                                               */
/* Output a string                               */
/*                                               */
/* Parameters: Pointer to a FILE struct holding  */
/*             info on the file to write to;     */
/*                                               */
/*             String number to write.           */
/*                                               */
/* Returns:    Number of characters written, or  */
/*             EOF if there was an error.        */
/*************************************************/

static int save_save_string(FILE * file, char * string)
{
  int length;
  if ((length = strlen(string)) == 0) return 0;

  if (fprintf(file, "%s", string) < length) return EOF;

  return length;
}

/*************************************************/
/* save_save_colour()                            */
/*                                               */
/* Output a colour number in the format seen in  */
/* the Choices file, to a given file.            */
/*                                               */
/* Parameters: Pointer to a FILE struct holding  */
/*             info on the file to write to;     */
/*                                               */
/*             Colour number to write.           */
/*                                               */
/* Returns:    Number of characters written, or  */
/*             EOF if there was an error.        */
/*************************************************/

static int save_save_colour(FILE * file, int colour)
{
  if (fprintf(file, "0x%08x", colour) < 10) return EOF;

  return 10;
}

/*************************************************/
/* save_save_number()                            */
/*                                               */
/* Output a number as ASCII, to a given file.    */
/*                                               */
/* Parameters: Pointer to a FILE struct holding  */
/*             info on the file to write to;     */
/*                                               */
/*             Number to write.                  */
/*                                               */
/* Returns:    Number of characters written, or  */
/*             EOF if there was an error.        */
/*************************************************/

static int save_save_number(FILE * file, int number)
{
  char buffer[64];
  int  len;

  len = sprintf(buffer, "%d", number);

  if (fprintf(file, "%s", buffer) < len) return EOF;

  return len;
}

/*************************************************/
/* save_save_yes_no()                            */
/*                                               */
/* Output either 'yes' or 'no' to a given file.  */
/*                                               */
/* Parameters: Pointer to a FILE struct holding  */
/*             info on the file to write to;     */
/*                                               */
/*             0 to write 'no', else 'yes'.      */
/*                                               */
/* Returns:    Number of characters written, or  */
/*             EOF if there was an error.        */
/*************************************************/

static int save_save_yes_no(FILE * file, int yes)
{
  if (!yes)
  {
    if (fprintf(file, "no") < 2) return EOF;
  }
  else
  {
    if (fprintf(file, "yes") < 3) return EOF;
  }

  return !yes ? 2 : 3;
}

/*************************************************/
/* save_save_font()                              */
/*                                               */
/* Saves a typeface definition in the form       */
/* required by the browser font manager.         */
/*                                               */
/* Parameters: Pointer to a FILE struct holding  */
/*             info on the file to write to;     */
/*                                               */
/*             The name of the typeface to save. */
/*                                               */
/* Returns:    Number of characters written, or  */
/*             EOF if there was an error.        */
/*                                               */
/* Assumes:    No sanity checking is given on    */
/*             any of the supplied parameters... */
/*************************************************/

static int save_save_font(FILE * file, char * typefacename)
{
  fm_typeface * tfptr;

  tfptr = fm_find_typeface(typefacename);

  if (!tfptr) return EOF;

  return fprintf(file,
                 "%s=%s:%s:%s:%s;%s",
                 tfptr->name,
                 tfptr->fontnames[0],
                 tfptr->fontnames[1],
                 tfptr->fontnames[2],
                 tfptr->fontnames[3],
                 tfptr->alternative);
}

/*************************************************/
/* save_save_general()                           */
/*                                               */
/* Save a general item to the given file. This   */
/* is for items with a discrete set of values    */
/* that map to a discrete set of strings.        */
/*                                               */
/* Parameters: Pointer to a FILE struct holding  */
/*             info on the file to write to;     */
/*                                               */
/*             The number of discrete values     */
/*             that the item may take;           */
/*                                               */
/*             Pointer to an array of the above  */
/*             size holding the values;          */
/*                                               */
/*             Pointer to an array of (char *)'s */
/*             pointing to the strings that map  */
/*             to the values in the int array;   */
/*                                               */
/*             Value to match out of those given */
/*             in the int array;                 */
/*                                               */
/*             Default item (from 1 to the value */
/*             given in the second parameter) to */
/*             use if the match value is not     */
/*             found in the int array.           */
/*                                               */
/* Returns:    Number of characters written, or  */
/*             EOF if there was an error.        */
/*                                               */
/* Assumes:    No sanity checking is given on    */
/*             any of the supplied parameters... */
/*************************************************/

static int save_save_general(FILE * file, int how_many, const int numbers[], const char * strings[], int match, int def)
{
  int          i, found = 0;
  const char * string   = NULL;
  int          sl;

  /* Try to find the match value in the array of ints */

  for (i = 0; i < how_many && !found; i++)
  {
    if (numbers[i] == match) string = strings[i], found = 1;
  }

  /* If not found, raise an error in TRACE builds, and pick */
  /* the given default string                               */

  if (!found)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Match value '%d' not found in save_save_general (",
              match);

      for (
            i = 0;
            i < how_many && strlen(erb.errmess) + strlen(strings[i]) + 9 < sizeof(erb.errmess);
            i++
          )
      {
        strcat(erb.errmess, strings[i]);
        if (i != how_many - 1) strcat(erb.errmess, ", ");

        if (i == how_many - 2) strcat(erb.errmess, "or ");
      }

      strcat(erb.errmess, ").");

      show_error_ret(&erb);

    #endif

    if (def > how_many) def = how_many;

    string = strings[def - 1];
  }

  /* Output the item to the file */

  if (!string) return EOF;

  sl = strlen(string);

  if (fprintf(file, "%s", string) < sl) return EOF;

  return sl;
}

/*************************************************/
/* save_save_choices()                           */
/*                                               */
/* Saves out the Choices file to either the      */
/* given path or to the path constructed in      */
/* save_build_messages_page. Uses the global     */
/* Choices block as the source of what should be */
/* written.                                      */
/*                                               */
/* Parameters: Pointer to a null-terminated path */
/*             to write the file to, or NULL for */
/*             default (see description above).  */
/*************************************************/

_kernel_oserror * save_save_choices(char * path)
{
  _kernel_oserror * e                 = NULL;

  FILE            * file              = NULL;
  char            * out               = NULL;
  char            * canonicalised_out = NULL;

  char            * p                 = NULL; /* Moves through the file loaded into a malloc block. */
  char            * op                = NULL; /* Remembers the start address of the block.          */
  char            * en;                       /* Remembers the end address of the block.            */

  int               size;
  int               type;

  #ifdef TRACE
    if (tl & (1u<<26)) Printf("save_save_choices: Called\n");
  #endif

  /* Find out where to get the file from */

  if (path && *path) out = path;
  else               out = save_build_messages_path(2);

  if (!out) return make_no_memory_error(14);

  /* Now ensure the path is there */

  (utils_canonicalise_path(out, &canonicalised_out));

  /* Can get rid of 'out' if we allocated it locally */

  if (!path || !*path)
  {
    free(out);
    out = NULL;
  }

  if (!canonicalised_out) return make_no_memory_error(14);

  #ifdef TRACE
    if (tl & (1u<<26)) Printf("save_save_choices: Working with file '%s'\n", canonicalised_out);
  #endif

  /* Ensure this path is available */

  e = utils_build_tree(canonicalised_out);
  if (e) goto save_save_choices_read_error;

  /* Find out how big the file is - for this of course, use */
  /* the *load* path, not the canonicalised save path       */
  {
    char * load_path = save_build_messages_path(0);

    if (!load_path)
    {
      free(canonicalised_out);

      return make_no_memory_error(14);
    }

    e = _swix(OS_File,
              _INR(0,1) | _OUT(0) | _OUT(4),

              17,
              load_path,

              &type,
              &size);

    /* Throw the local path away - want to minimise how many */
    /* malloc blocks we're juggling for fear of opening up a */
    /* memory leak in here                                   */

    free(load_path);

    if (e || type != 1) goto save_save_choices_read_error;
  }

  /* Allocate memory for the file */

  p = malloc(size + 1);

  if (!p)
  {
    free(canonicalised_out);

    return make_no_memory_error(14);
  }

  en = p + size;
  op = p;

  /* Read the file into memory - again, use the load path */
  /* not the save path. Rebuild this path to avoid having */
  /* any more malloc blocks flying about (see comments    */
  /* earlier) - it doesn't take long to do this, and the  */
  /* function isn't speed critical anyway.                */

  {
    char * load_path = save_build_messages_path(0);

    if (!load_path)
    {
      free(canonicalised_out);
      free(op);

      return make_no_memory_error(14);
    }

    e = _swix(OS_File,
              _INR(0,3),

              16,
              load_path,
              p,
              0);

    free(load_path);

    if (e) goto save_save_choices_read_error;
  }

  /* Force a null terminator at the end, so strchr() calls */
  /* (or equivalent code fragments) don't run off the end  */
  /* of the block.                                         */

  p[size] = 0;

  /* Open it for writing */

  file = fopen(canonicalised_out, "wb");

  if (!file) goto save_save_choices_write_error;

  /* Use the original file in memory as a reference for */
  /* writing the new one.                               */

  do
  {
    /* Comments or white space */

    if (*p == '#' || *p < ' ')
    {
      /* Output until an end of line marker */

      while (*p >= ' ')
      {
        if (fputc(*p++, file) == EOF) goto save_save_choices_write_error;
      }

      /* Output any control characters - we thus preserve   */
      /* CR, LF, CR+LF or whatever was in the original file */

      while (*p < ' ' && p < en)
      {
        if (fputc(*p++, file) == EOF) goto save_save_choices_write_error;
      }
    }

    /* If not comments or white space, must have a token */

    else
    {
      char * token_end;
      int    len;

      token_end = p;

      /* Search for a ':', but don't go off the end of a line */

      while (*token_end > 31 && *token_end != ':') token_end++;

      len = token_end - p; /* Doesn't include ':' in the count */

      if (*token_end == ':' && len)
      {
        int i, result = 0;

        for (i = 0; i <= len; i++) /* '<=' to include the ':' */
        {
          if (fputc(p[i], file) == EOF) goto save_save_choices_write_error;
        }

        /* Now it just gets tedious...! */

        if      (!strncmp(p, "HomePage",         len)) result = save_save_string(file, choices.home_page);

        else if (!strncmp(p, "BackColour",       len)) result = save_save_colour(file, choices.background_colour);
        else if (!strncmp(p, "TextColour",       len)) result = save_save_colour(file, choices.text_colour);
        else if (!strncmp(p, "LinkColour",       len)) result = save_save_colour(file, choices.link_colour);
        else if (!strncmp(p, "UsedColour",       len)) result = save_save_colour(file, choices.used_colour);
        else if (!strncmp(p, "FollColour",       len)) result = save_save_colour(file, choices.followed_colour);
        else if (!strncmp(p, "SeleColour",       len)) result = save_save_colour(file, choices.selected_colour);

        else if (!strncmp(p, "SupportTables",    len)) result = save_save_yes_no(file, choices.support_tables);
        else if (!strncmp(p, "TableOuter",       len))
        {
          static const char * s[] = {
                                      "2d",
                                      "auto",
                                      "3d",
                                      "never"
                                    };

          static const int    v[] = {
                                      Choices_TableOuter_Always2D,
                                      Choices_TableOuter_Auto,
                                      Choices_TableOuter_Always3D,
                                      Choices_TableOuter_Never
                                    };

          result = save_save_general(file, 4, v, s, choices.table_outer, 3);
        }
        else if (!strncmp(p, "TableInner",       len))
        {
          static const char * s[] = {
                                      "2d",
                                      "auto",
                                      "3d",
                                      "never"
                                    };

          static const int    v[] = {
                                      Choices_TableInner_Always2D,
                                      Choices_TableInner_Auto,
                                      Choices_TableInner_Always3D,
                                      Choices_TableInner_Never
                                    };

          result = save_save_general(file, 4, v, s, choices.table_inner, 3);
        }

        else if (!strncmp(p, "FontSize",         len)) result = save_save_number(file, choices.font_size);
        else if (!strncmp(p, "TTAspect",         len)) result = save_save_number(file, choices.tt_aspect);
        else if (!strncmp(p, "SystemFont",       len)) result = save_save_yes_no(file, choices.system_font);
        else if (!strncmp(p, "Typeface1",        len)) result = save_save_font(file, "fixed");
        else if (!strncmp(p, "Typeface2",        len)) result = save_save_font(file, "sans");
        else if (!strncmp(p, "Typeface3",        len)) result = save_save_font(file, "serif");
        else if (!strncmp(p, "Encoding",         len)) result = save_save_number(file, choices.encoding);

        else if (!strncmp(p, "UnderlineLinks",   len)) result = save_save_yes_no(file, choices.underline_links);
        else if (!strncmp(p, "UseSourceCols",    len)) result = save_save_yes_no(file, choices.use_source_cols);
        else if (!strncmp(p, "ShowForeground",   len)) result = save_save_yes_no(file, choices.show_foreground);
        else if (!strncmp(p, "ShowBackground",   len)) result = save_save_yes_no(file, choices.show_background);

        else if (!strncmp(p, "LeftMargin",       len)) result = save_save_number(file, choices.left_margin);
        else if (!strncmp(p, "RightMargin",      len)) result = save_save_number(file, choices.right_margin);
        else if (!strncmp(p, "QuoteMargin",      len)) result = save_save_number(file, choices.quote_margin);
        else if (!strncmp(p, "Leading",          len)) result = save_save_number(file, choices.leading);
        else if (!strncmp(p, "LeftIndent",       len)) result = save_save_number(file, choices.left_indent);

        else if (!strncmp(p, "MaxImages",        len)) result = save_save_number(file, choices.maximages);
        else if (!strncmp(p, "ClientPull",       len)) result = save_save_yes_no(file, choices.client_pull);
        else if (!strncmp(p, "SupportFrames",    len)) result = save_save_yes_no(file, choices.support_frames);
        else if (!strncmp(p, "SupportObject",    len)) result = save_save_yes_no(file, choices.support_object);
        else if (!strncmp(p, "PlugInControl",    len))
        {
          static const char * s[] = {
                                      "never",
                                      "viewed",
                                      "asap"
                                    };

          static const int    v[] = {
                                      Choices_PlugIns_Never,
                                      Choices_PlugIns_Viewed,
                                      Choices_PlugIns_ASAP
                                    };

          result = save_save_general(file, 3, v, s, choices.plugin_control, 3);
        }
        else if (!strncmp(p, "SeeFetches",       len)) result = save_save_yes_no(file, choices.see_fetches);

        else if (!strncmp(p, "SaveHotlist",      len))
        {
          static const char * s[] = {
                                      "never",
                                      "once",
                                      "always"
                                    };

          static const int    v[] = {
                                      Choices_SaveHotlist_Never,
                                      Choices_SaveHotlist_Once,
                                      Choices_SaveHotlist_Always
                                    };

          result = save_save_general(file, 3, v, s, choices.save_hotlist, 3);
        }
        else if (!strncmp(p, "AddHotlist", len))
        {
          static const char * s[] = {
                                      "top",
                                      "bottom"
                                    };

          static const int    v[] = {
                                      Choices_AddHotlist_Top,
                                      Choices_AddHotlist_Bottom
                                    };

          result = save_save_general(file, 2, v, s, choices.add_hotlist, 2);
        }
        else if (!strncmp(p, "HotlistType", len))
        {
          static const char * s[] = {
                                      "urls",
                                      "descriptions"
                                    };

          static const int    v[] = {
                                      Choices_HotlistType_URLs,
                                      Choices_HotlistType_Descriptions
                                    };

          result = save_save_general(file, 2, v, s, choices.hotlist_show, 2);
        }
        else if (!strncmp(p, "AutoOpenDelay",    len)) result = save_save_number(file, choices.auto_open_delay);
        else if (!strncmp(p, "AutoScrollDelay",  len)) result = save_save_number(file, choices.auto_scroll_delay);
        else if (!strncmp(p, "AutoScrollMargin", len)) result = save_save_number(file, choices.auto_scroll_margin);

        else if (!strncmp(p, "MaxSize",          len)) result = save_save_number(file, choices.max_size         / 1024); /* (Convert bytes to K)       */
        else if (!strncmp(p, "ImageMaxSize",     len)) result = save_save_number(file, choices.image_max_size   / 1024);
        else if (!strncmp(p, "ExpiryAge",        len)) result = save_save_number(file, choices.expiry_age);
        else if (!strncmp(p, "ImageExpiryAge",   len)) result = save_save_number(file, choices.image_expiry_age);
        else if (!strncmp(p, "ShowURLs",         len)) result = save_save_yes_no(file, choices.show_urls);
        else if (!strncmp(p, "SaveHistory",      len))
        {
          static const char * s[] = {
                                      "never",
                                      "once",
                                      "always"
                                    };

          static const int    v[] = {
                                      Choices_SaveHistory_Never,
                                      Choices_SaveHistory_Once,
                                      Choices_SaveHistory_Always
                                    };

          result = save_save_general(file, 3, v, s, choices.save_history, 3);
        }
        else if (!strncmp(p, "SaveImageHistory", len))
        {
          static const char * s[] = {
                                      "never",
                                      "once",
                                      "always"
                                    };

          static const int    v[] = {
                                      Choices_SaveImageHistory_Never,
                                      Choices_SaveImageHistory_Once,
                                      Choices_SaveImageHistory_Always
                                    };

          result = save_save_general(file, 3, v, s, choices.save_image_history, 3);
        }

        else if (!strncmp(p, "URLbar",           len)) result = save_save_yes_no(file, choices.url_bar);
        else if (!strncmp(p, "ButtonBar",        len)) result = save_save_yes_no(file, choices.button_bar);
        else if (!strncmp(p, "StatusBar",        len)) result = save_save_yes_no(file, choices.status_bar);
        else if (!strncmp(p, "MoveGadgets",      len))
        {
          static const char * s[] = {
                                      "never",
                                      "atend",
                                      "during"
                                    };

          static const int    v[] = {
                                      Choices_MoveGadgets_Never,
                                      Choices_MoveGadgets_AtEnd,
                                      Choices_MoveGadgets_During
                                    };

          result = save_save_general(file, 3, v, s, choices.move_gadgets, 3);
        }

        else if (!strncmp(p, "Width",            len)) result = save_save_number(file, choices.width);
        else if (!strncmp(p, "Height",           len)) result = save_save_number(file, choices.height);
        else if (!strncmp(p, "OverrideX",        len)) result = save_save_number(file, choices.override_x);
        else if (!strncmp(p, "OverrideY",        len)) result = save_save_number(file, choices.override_y);
        else if (!strncmp(p, "SolidResize",      len))
        {
          static const char * s[] = {
                                      "no",
                                      "yes",
                                      "always"
                                    };

          static const int    v[] = {
                                      Choices_SolidResize_No,
                                      Choices_SolidResize_Yes,
                                      Choices_SolidResize_Always
                                    };

          result = save_save_general(file, 3, v, s, choices.solid_resize, 3);
        }
        else if (!strncmp(p, "FullScreen",       len)) result = save_save_yes_no(file, choices.full_screen);
        else if (!strncmp(p, "HScroll",          len))
        {
          static const char * s[] = {
                                      "no",
                                      "yes",
                                      "auto"
                                    };

          static const int    v[] = {
                                      Choices_HScroll_No,
                                      Choices_HScroll_Yes,
                                      Choices_HScroll_Auto
                                    };

          result = save_save_general(file, 3, v, s, choices.h_scroll, 3);
        }
        else if (!strncmp(p, "VScroll",          len))
        {
          static const char * s[] = {
                                      "no",
                                      "yes",
                                      "auto"
                                    };

          static const int    v[] = {
                                      Choices_VScroll_No,
                                      Choices_VScroll_Yes,
                                      Choices_VScroll_Auto
                                    };

          result = save_save_general(file, 3, v, s, choices.v_scroll, 3);
        }

        else if (!strncmp(p, "RefoWait",         len)) result = save_save_yes_no(file, choices.refo_wait);
        else if (!strncmp(p, "RefoHang",         len)) result = save_save_yes_no(file, choices.refo_hang);
        else if (!strncmp(p, "RefoTime",         len)) result = save_save_number(file, choices.refo_time);

        else if (!strncmp(p, "FixedPtr",         len)) result = save_save_yes_no(file, choices.fixed_pointer);
        else if (!strncmp(p, "HighlightLks",     len)) result = save_save_yes_no(file, choices.highlight_links);
        else if (!strncmp(p, "KeyboardCtl",      len)) result = save_save_yes_no(file, choices.keyboard_ctrl);

        else if (!strncmp(p, "Clone",            len)) result = save_save_yes_no(file, choices.clone);
        else if (!strncmp(p, "UseProxy",         len)) result = save_save_yes_no(file, choices.use_proxy);
        else if (!strncmp(p, "ProxyAddress",     len)) result = save_save_string(file, choices.proxy_address);
        else if (!strncmp(p, "StartProxy",       len)) result = save_save_yes_no(file, choices.start_proxy);

        #ifndef SINGLE_USER

          else if (!strncmp(p, "PostIn",         len)) result = save_save_string(file, choices.post_in);
          else if (!strncmp(p, "PostOut",        len)) result = save_save_string(file, choices.post_out);

        #endif

        else
        {
          /* Token we don't understand - output the rest of the line */

          #ifdef TRACE

            if (tl & (1u<<26))
            {
              Printf("save_save_choices: Unrecognised token '");

              for (i = 0; i <= len; i++)
              {
                Printf("%c", p[i]);
              }

              Printf("'\n");
            }

          #endif

          p += len + 1;
          while (*p >= ' ' && result != EOF) result = fputc(*p++, file);
        }

        if (result == EOF) goto save_save_choices_write_error;

        /* Skip to the end of the line */

        while (*p >= ' ') p++;

        /* Output any control characters - we thus preserve   */
        /* CR, LF, CR+LF or whatever was in the original file */

        while (*p < ' ' && p < en)
        {
          if (fputc(*p++, file) == EOF) goto save_save_choices_write_error;
        }
      }
      else
      {
        /* Unrecognised line contents; spit out the whole line */

        #ifdef TRACE

          if (tl & (1u<<26))
          {
            int i = 0;

            Printf("save_save_choices: Unknown line fragment '");

            while (p[i] >= ' ')
            {
              Printf("%c", p[i++]);
            }

            Printf("'\n");
          }

        #endif

        while (*p >= ' ')
        {
          if (fputc(*p++, file) == EOF) goto save_save_choices_write_error;
        }
      }
    }
  }
  while (p < en);

  /* Close the output file */

  fclose(file);

  /* Set the filetype - not essential, just nice to have, so */
  /* let it fail silently.                                   */

  _swix(OS_File,
        _INR(0,2),

        18, /* Set type of named object */
        canonicalised_out,
        FileType_TEXT);

  /* Free the canonicalised path */

  free(canonicalised_out);

  /* Free the old Choices file */

  free(op);

  /* Finished */

  #ifdef TRACE
    if (tl & (1u<<26)) Printf("save_save_choices: Successful\n");
  #endif

  return NULL;

  /* Error condition exits */

save_save_choices_read_error:

  if (!path || !*path) free(out);

  free(op);
  free(canonicalised_out);

  erb.errnum = Utils_Error_Custom_Message;

  StrNCpy0(erb.errmess,
           lookup_token("COChoices:Can't open the Choices file - the preferences cannot be saved.",
                        0,
                        0));

  #ifdef TRACE
    if (tl & (1u<<26)) Printf("save_save_choices: Can't open Choices file, exitting\n");
  #endif

  return &erb;

save_save_choices_write_error:

  erb = *_kernel_last_oserror();

  if (file) fclose(file);

  /* We really want to avoid leaving a broken Choices file, so */
  /* have a last ditch go at dumping the original file down.   */

  _swix(OS_File,
        _INR(0,2) | _INR(4,5),

        10,
        out,
        FileType_TEXT,
        op,
        en);

  /* Free any temporary blocks */

  if (!path || !*path) free(out);

  free(op);
  free(canonicalised_out);

  #ifdef TRACE
    if (tl & (1u<<26)) Printf("save_save_choices: Exitting with error\n");
  #endif

  return &erb;
}