/* 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   : History.c                              */
/*                                                 */
/* Purpose: History functions for the browser.     */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 07-Feb-97: Created.                    */
/***************************************************/

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

#include "kernel.h"
#include "swis.h"
#include "flex.h"

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

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

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

#include "Browser.h"
#include "FetchPage.h"
#include "Memory.h"
#include "OpenURL.h"
#include "Save.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "History.h"

/* Static variables */

static void * global_history    = NULL;
static void * view_history_menu = NULL;

/* Static function prototypes */

static char * history_title(char * url);

// void browser_save_history(void)
// {
//   char command[sizeof(user.historyfile)+32];
//
//   sprintf(command,"Save %s %p +%x",user.historyfile,global_history,flex_size(&global_history));
//   os_cli(command);
// }
//
// void browser_lose_history(void)
// {
//   if(global_history)
//   {
//     flex_free(&global_history);
//     global_history=NULL;
//   }
//   if(authorise)
//   {
//     flex_free(&authorise);
//     authorise=NULL;
//   }
// }
//
// /*----------------------------------------------------------------------*/
// void browser_load_history(void)
// {
//   browser_lose_history();
//   load_file_into_flex(user.historyfile,&global_history);
// }
//
//
// static int hotlist_make_history_file(int count,browser_data * b,char *filename)
// { /* make the history export file or, if count is 1, just get its length.
//      If filename is set, write to it, otherwise use the save library. */
//   int  length,i;
//   char title[100];
//   char buffer[200];
//   char *p,*e;
//   FILE *f=NULL;
//
//   length=0;
//   if(filename)
//   {
//     f=fopen(filename,"w");
//     if(!f)
//     {
//       show_error_ret((_kernel_oserror *) _kernel_last_oserror());
//
//       return 0;
//     }
//   }
//   sprintf(title,msgs_lookup("HTITLEH:Browser history list for %s"),user.name);
//   sprintf(buffer,"<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",title);
//   hotlist_output(buffer,count,&length,f);
//   sprintf(buffer,"<h1>%s</h1>\n<p><dl>\n",title);
//   hotlist_output(buffer,count,&length,f);
//   p=(char*)global_history;
//   e=p+flex_size(&global_history)-1;
//   while(p && p<e)
//   {
//     p+=sizeof(int);
//     hotlist_output("    <dt><a href=\"",count,&length,f);
//     hotlist_output(p,count,&length,f);
//     hotlist_output("\">",count,&length,f);
//     hotlist_output(p,count,&length,f);
//     p+=strlen(p)+1;
//     while((int)p&3) p++; /* word aligned */
//     hotlist_output("</a>\n",count,&length,f);
//   }
//   hotlist_output("</dl><p>\n</body>\n</html>\n",count,&length,f);
//   if(!count) length=!length;
//   if(f) fclose(f);
//   return(length);
// }
// static int hotlist_history_saver(void *handle,char *pathname)
// {
//   pathname=pathname;
//   return(hotlist_make_history_file(0,(browser*)handle,NULL));
// }
//
// /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*/
// static int hotlist_file_history_size(browser_data * b)
// {
//   return(hotlist_make_history_file(1,b,NULL));
// }
//
// /*----------------------------------------------------------------------*/

/*************************************************/
/* history_title()                               */
/*                                               */
/* Returns a pointer to the title string for a   */
/* given URL in the global history, or NULL if   */
/* the URL can't be found / the global history   */
/* is empty. NULL is also returned if the title  */
/* string is of zero length.                     */
/*                                               */
/* This pointer is in a flex block, so beware of */
/* flex block shifts when using it.              */
/*                                               */
/* Parameters: Pointer to the URL.               */
/*                                               */
/* Returns:    Pointer to the title string, or   */
/*             NULL if the URL isn't found / the */
/*             global history is empty / the     */
/*             title string is of zero length.   */
/*************************************************/

static char * history_title(char * url)
{
  int entry;

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_title: Called with URL '%s'\n", url);
  #endif

  /* If history is empty, return NULL */

  if (!global_history)
  {
    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_title: Exitting, the history is empty\n", url);
    #endif

    return NULL;
  }

  /* Find the offset of the history entry */

  entry = history_visited(url, 0);

  /* Return NULL if the URL wasn't found */

  if (entry < 0)
  {
    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_title: Exitting, the URL wasn't found\n", url);
    #endif

    return NULL;
  }

  /* Else calculate the offset of the title string */

  entry += sizeof(int);
  entry += strlen((char *) ((int) global_history + entry)) + 1;

  /* If the string is zero length return NULL, else */
  /* return a pointer to it.                        */

  if (!strlen((char *) ((int) global_history + entry)))
  {
    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_title: Exitting, the title was a null string\n", url);
    #endif

    return NULL;
  }

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_title: Successful, pointing to '%s'\n", (char *) ((int) global_history + entry));
  #endif

  return (char *) ((int) global_history + entry);
}

/*************************************************/
/* history_visited()                             */
/*                                               */
/* Returns an offset into the global history if  */
/* a given URL has been visited before (i.e. is  */
/* in the global history), optionally            */
/* timestamping it if it's there.                */
/*                                               */
/* Parameters: Pointer to the URL;               */
/*                                               */
/*             1 to timestamp it, else 0.        */
/*                                               */
/* Returns:    -1 if the item is not in the      */
/*             global history, else an offset    */
/*             into the history of the item.     */
/*************************************************/

int history_visited(char * url, int stamp)
{
  int offset = 0;

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_visited: Called with URL '%s'\n", url);
  #endif

  if (global_history)
  {
    int e;

    /* If the history exists, set e to the offset of the end */

    e = flex_size(&global_history) - 1;

    /* Loop around inside the history list, searching for the given URL */

    while (offset < e)
    {
      /* If the URL is found, timestamp it if required and exit, */
      /* returning the offset of this entry.                     */

//Printf("comparing '%s'\n"
//       "to        '%s'\n\n",url,(char *) global_history + offset + sizeof(int));
      if (!strcmp((char *) global_history + offset + sizeof(int), url))
      {
        if (stamp) *(int *) ((char *) global_history + offset) = time(NULL);

        #ifdef TRACE
          if (tl & (1u<<16)) Printf("history_visited: Succesful, found URL\n");
        #endif

        return offset;
      }

      /* Jump past the current entry (i.e. a timestamp plus the two */
      /* strings that follow including terminators, word aligned).  */

      offset += sizeof(int);
      offset += strlen((char *) global_history + offset) + 1;
      offset += strlen((char *) global_history + offset) + 1;

      offset = (int) WordAlign(offset);
    }
  }

  /* URL not found - return -1. */

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_visited: Successful, but URL was not found\n");
  #endif

  return -1;
}

/*************************************************/
/* history_record_global()                       */
/*                                               */
/* Records a URL in the global history. The URL  */
/* must NOT be in a flex block, as this routine  */
/* may allocate flex store which could lead to   */
/* other blocks shifting, invalidating the URL   */
/* pointer passed in.                            */
/*                                               */
/* Parameters: Pointer to a URL, which is not in */
/*             a flex block.                     */
/*************************************************/

_kernel_oserror * history_record_global(char * url)
{
  /* Proceed if the URL isn't already in the history */

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_record_global: Called with URL '%s'\n", url);
  #endif

  if (history_visited(url, 1) < 0)
  {
    int len, oldsize, ok;

//    toolbars_hide_internal(url);

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_record_global: Proceeding\n", url);
    #endif

    /* Set 'len' to the size of this entry; the */
    /* URL plus terminator, null title string,  */
    /* and the timestamp, word aligned.         */

    len = strlen(url) + 1 + 1 + sizeof(int);
    len = (int) WordAlign(len);

    if (global_history)
    {
      /* If the history already exists, may need to ensure */
      /* it hasn't got too long                            */

      while (
              flex_size(&global_history) > 4 &&
              (flex_size(&global_history) + len) > choices.maxghistory * 1024
            )
      {
        /* As long as the history is oversized, delete the oldest entry */

        int    size, oldest = *(int *) global_history;
        char * e;
        char * f = NULL;
        char * p;

        /* Lock flex - we want to store pointers for a moment and */
        /* can't allow it to move (it shouldn't anyway, but this  */
        /* makes absolutely sure!).                               */

        flex_set_budge(0);

        /* Point to the first byte in 'p', the last byte in 'e', and */
        /* start with 'f' pointing to the first byte too.            */

        p = (char *) global_history;
        e = p + flex_size(&global_history) - 1;
        f = p;

        /* Search for the oldest entry */

        while (p < e)
        {
          if (*(int *) p < oldest)
          {
            oldest = *(int *) p;
            f = p;
          }

          p += sizeof(int);
          p += strlen(p) + 1; /* Get past URL   */
          p += strlen(p) + 1; /* Get past title */

          p = WordAlign(p);
        }

        /* Get the size of this oldest entry */

        size = strlen(f + sizeof(int)) + 1; /* Length of URL plus terminator */

        size = sizeof(int) + size + strlen(f + sizeof(int) + size) + 1;

        size = (int) WordAlign(size);

        /* Copy entries above the entry down over it */
        /* and shrink the flex block as appropriate  */

        memmove(f, f + size, (e - f + 1) - size); /* (e - f + 1 as e points to the last used byte, not to the first free one) */

        /* Allow flex to move again */

        flex_set_budge(1);

        #ifdef TRACE
          flexcount -= size;
          if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
        #endif

        flex_extend(&global_history, flex_size(&global_history) - size);
      }

      /* Allocate space for the entry */

      oldsize = flex_size(&global_history);

      ok = flex_extend(&global_history, oldsize + len);

      #ifdef TRACE
        if (ok)
        {
          flexcount += len;
          if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
        }
      #endif
    }

    /* If the history doesn't exist, create it */

    else
    {
      oldsize = 0;
      ok = flex_alloc(&global_history, len);

      #ifdef TRACE
        if (ok)
        {
          flexcount += len;
          if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
        }
      #endif
    }

    if (!ok)
    {
      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               lookup_token("NoMemGHi:There is not enough free memory to add the page to the global history.",
                            0,
                            0));

      #ifdef TRACE
        if (tl & (1u<<16)) Printf("history_record_global: Exitting with error\n", url);
      #endif

      return &erb;
    }

    /* Add the entry */

    *(int *) ((char *) global_history + oldsize) = time(NULL);                       /* Timestamp         */
    strcpy(((char *) global_history + oldsize) + sizeof(int), url);                  /* URL string        */
    strcpy(((char *) global_history + oldsize) + sizeof(int) + strlen(url) + 1, ""); /* Null title string */

    /* If required, save the history */

    if (choices.save_history == 2) return history_save(lookup_choice("HistoryPath:Browse:User.History",0,0));
  }

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_record_global: Successful\n", url);
  #endif

  return NULL;
}

/*************************************************/
/* history_record_local()                        */
/*                                               */
/* Records a URL in the local (view) history.    */
/* The URL must NOT be in a flex block, as this  */
/* routine may allocate flex store which could   */
/* lead to other blocks shifting, invalidating   */
/* the URL Pointer passed in.                    */
/*                                               */
/* If the URL pointer is NULL, then the current  */
/* URL as returned by browser_current_url() in   */
/* Windows.c will be added.                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the history;          */
/*                                               */
/*             Pointer to the URL, which must    */
/*             not be in a flex block.           */
/*************************************************/

_kernel_oserror * history_record_local(browser_data * b, char * url)
{
  _kernel_oserror * e;

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_record_local: Called with %p\n", (void *) b);
  #endif

  if (!url) url = browser_current_url(b);

  /* If we're in the history, truncate it to avoid circular references. */
  /* Don't bother freeing memory here, it'll be set to the required     */
  /* amount next time an addition is made anyway.                       */

  if (b->hpos)
  {
    b->hnum = b->hpos;
    b->hpos = 0;
  }

  /* Otherwise, provided we've a URL, proceed. */

  else if (url && *url)
  {
    int    bytes, count, needed, len;
    char * p;
    char * last = NULL;

    bytes = 0;

    p     = b->histdata;
    count = b->hnum;

    /* Point to the last entry */

    while (count--)
    {
      /* Get past the URL */

      len = strlen(p) + 1;

      last   = p;
      bytes += len;
      p     += len;
    }

    /* If the given URL matches the last one on the history list, */
    /* there's nothing to add.                                    */

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_record_local: Exitting with no action\n");
    #endif

    if (last && !strcmp(last, url)) return NULL;

    /* Needed = space required to store URL plus terminator. */

    needed = strlen(url) + 1;

    /* Allocate the memory and store the URL */

    e = memory_set_chunk_size(b, NULL, CK_HIST, bytes + needed);

    if (e)
    {
      #ifdef TRACE
        if (tl & (1u<<16)) Printf("history_record_local: Exitting (error from memory_set_chunk_size)\n", (void *) b);
      #endif

      return e;
    }

    strcpy(b->histdata + bytes, url);
    toolbars_hide_internal(b->histdata + bytes);
    b->hnum ++;

    bytes += strlen(b->histdata +  bytes) + 1;

    while(b->hnum > choices.maxvhistory)
    {
      /* If the history is oversized, remove old entries. */
      /* 'count' = length of the bottom (oldest) entry.   */

      count  = strlen(b->histdata) + 1;

      /* Copy the newer entries down over the old and  */
      /* decrement the entries counter. Again, no need */
      /* to free memory as the next history addition   */
      /* will allocate the required amount anyway.     */

      memmove(b->histdata, b->histdata + count, bytes - count);

      b->hnum--;
    }
  }

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_record_local: Successful\n", (void *) b);
  #endif

  return NULL;
}

/*************************************************/
/* history_pull_local_last()                     */
/*                                               */
/* Copies the last visited URL, according to the */
/* local history, to the given buffer.           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the history;          */
/*                                               */
/*             Pointer to the buffer (not in a   */
/*             flex block!);                     */
/*                                               */
/*             Size of the buffer.               */
/*                                               */
/* Assumes:    That neither pointer is NULL.     */
/*************************************************/

void history_pull_local_last(browser_data * b, char * last, int size)
{
  char * p;
  int    count;

// This function doesn't work yet... =8*O
#ifdef TRACE
  Printf("** history_pull_local_last: Not run\n");
#endif
return;

  count = b->hnum;

  if (count <= 0)
  {
    *last = 0;
    return;
  }

  /* Get a pointer to the URL */

  p = b->histdata;
  while(--count > 0) p += strlen(p) + 1;

  strncpy(last, p, size - 1);
  last[size - 1] = 0;

  return;
}

/*************************************************/
/* history_add_title()                           */
/*                                               */
/* Adds a title for the given URL in the global  */
/* history. The title must NOT be stored in a    */
/* flex block as this routine may allocate flex  */
/* store which could shift the block with the    */
/* title in, invalidating the pointer to it.     */
/* However, this occurs after the URL is dealt   */
/* with, so the URL can be in a flex block.      */
/*                                               */
/* If the URL already has a title string, the    */
/* existing title is kept. If the URL can't be   */
/* found, the function will fail silently.       */
/*                                               */
/* This may make the history exceed its maximum  */
/* size. The history will not be truncated to    */
/* within that value until a new URL is added.   */
/*                                               */
/* Parameters: Pointer to the title string which */
/*             is not in a flex block;           */
/*                                               */
/*             Pointer to the URL with which it  */
/*             is to be associated.              */
/*                                               */
/* Assumes:    That the title and URL pointers   */
/*             are not NULL.                     */
/*************************************************/

_kernel_oserror * history_add_title(char * title, char * url)
{
  int offset, entrysize, pretitle, needed;

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_add_title: Called with title '%s'\n"
                           "                             and URL '%s'\n",title,url);
  #endif

  /* Exit with an error if the history is empty */

  if (!global_history)
  {
    erb.errnum = Utils_Error_Custom_Message;

    StrNCpy0(erb.errmess,
             lookup_token("EmptyHistE:The history list is empty.",
                          0,
                          0));

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_add_title: Exitting with error (history is empty)\n");
    #endif

    return &erb;
  }

  /* Exit if the URL isn't there. */

  offset = history_visited(url, 0);

  if (offset < 0)
  {
    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_add_title: Exitting (couldn't find URL)\n");
    #endif

    return NULL;
  }

  /* Otherwise, is there already a title string? */

  offset    += sizeof(int);                                                /* Get past timestamp   */
  entrysize  = strlen((char *) global_history + offset) + 1 + sizeof(int);
  offset    += strlen((char *) global_history + offset) + 1;               /* Get past URL         */

  /* Exit if there was already a title present */

  if (strlen((char *) global_history + offset))
  {
    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_add_title: Exitting (URL already had a title)\n");
    #endif

    return NULL;
  }

  /* Otherwise, extend the flex block. For 'needed', we already have */
  /* the terminator of the (currently null) title string in the      */
  /* history, so don't need to add one to the string length here.    */

  pretitle   = (int) WordAlign(entrysize + 1); /* Word aligned size of the history entry including null title string */
  entrysize += strlen(title) + 1;
  entrysize  = (int) WordAlign(entrysize); /* Word aligned size of the history entry including new title string */

  needed = entrysize - pretitle;

  if (!flex_midextend(&global_history, offset, needed))
  {
    erb.errnum = Utils_Error_Custom_Normal;

    StrNCpy0(erb.errmess,
             lookup_token("NoMemGHi:There is not enough free memory to add the page to the global history.",
                          0,
                          0));

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_add_title: Exitting with error\n", url);
    #endif

    return &erb;
  }

  #ifdef TRACE
    flexcount += needed;
    if (tl & (1u<<14)) Printf("**   flexcount: %d\n",flexcount);
  #endif

  /* Copy in the URL */

  strcpy((char *) global_history + offset, title);

  /* If required, save the history */

  if (choices.save_history == 2) return history_save(lookup_choice("HistoryPath:Browse:User.History",0,0));

  /* Success. */

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

  return NULL;
}

/*************************************************/
/* history_fetch_forwards()                      */
/*                                               */
/* When called, will fetch the next page in the  */
/* history list (assuming we've moved into it    */
/* with history_fetch_backwards()).              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the history;          */
/*                                               */
/*             1 to open the URL in a new window */
/*             or 0 to open it in the window to  */
/*             which the browser_data struct is  */
/*             relevant.                         */
/*************************************************/

_kernel_oserror * history_fetch_forwards(browser_data * b, int new_view)
{
  /* Can only go forward if we're somewhere within */
  /* the local history list...                     */

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_fetch_forwards: Called with %p\n", (void *) b);
  #endif

  if (b->hpos > 0 && b->hpos < b->hnum)
  {
    int    count;
    char * p;

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_fetch_forwards: Proceeding\n");
    #endif

    /* Set 'count' to the next entry in the list */

    count = ++b->hpos;

    /* Only set up this window to look as though it's gone forwards */
    /* if the URL isn't to be opened in a new window.               */

    if (!new_view)
    {
      /* If the position now lies outside of the history, */
      /* we've gone forward past its end; set hpos to 0   */
      /* to show that we're not in the history and remove */
      /* the last item, as that's what will be fetched    */
      /* now and 'back' shouldn't then go back to it      */
      /* again.                                           */

      if (b->hpos >= b->hnum)
      {
        b->hpos = 0;
        if (b->hnum > 0) b->hnum--;
      }
    }

    /* Get a pointer to the URL */

    p = b->histdata;
    while (--count) p += strlen(p) + 1;

    /* Get the URL in either the same window or a new window. */

    if (!new_view)
    {
      #ifdef TRACE
        if (tl & (1u<<16)) Printf("history_fetch_forwards: Exitting through fetchpage_new\n");
      #endif

      /* Fetch the URL, flagging not to record this URL in the history */

      return (fetchpage_new(b, p, 0, 1));
    }
    else
    {
      #ifdef TRACE
        if (tl & (1u<<16)) Printf("history_fetch_forwards: Exitting through windows_create_browser\n");
      #endif

      return windows_create_browser(p, NULL, NULL, NULL, 0);
    }
  }

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_fetch_forwards: Exitting with no action\n");
  #endif

  return NULL;
}

/*************************************************/
/* history_fetch_backwards()                     */
/*                                               */
/* When called, will fetch the previous page in  */
/* the history list.                             */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the history;          */
/*                                               */
/*             1 to open the URL in a new window */
/*             or 0 to open it in the window to  */
/*             which the browser_data struct is  */
/*             relevant.                         */
/*************************************************/

_kernel_oserror * history_fetch_backwards(browser_data * b, int new_view)
{
  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_fetch_backwards: Called with %p\n", (void *) b);
  #endif

  /* Only proceed if we're not right at the start of the history */
  /* (hpos > 1) or we're not in the history at all (hpos = 0).   */

  if ((b->hpos > 1 || b->hpos == 0) && b->hnum)
  {
    int    count;
    char * p;

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_fetch_backwards: Proceeding\n");
    #endif

    /* Need to be careful manipulating the history, as if the */
    /* URL is to be opened in a new view then the position of */
    /* this window in its history is unchanged.               */

    if (b->hpos == 0)
    {
      _kernel_oserror * e = NULL;

      /* If not already in the history list... */

      if (!new_view)
      {
        /* Ensure the current URL is put at the top of the history, */
        /* unless it is a temporary file.                           */

        if (b->displayed != Display_Scrap_File)
        {
          e = history_record_local(b, NULL);

          if (e)
          {
            #ifdef TRACE
              if (tl & (1u<<16)) Printf("history_fetch_backwards: Exitting with error\n");
            #endif

            return e;
          }
        }
      }

      /* Step backwards. */

      count = b->hnum - 1;
      if (count < 1) count = 1;

      if (!new_view) b->hpos = count;
    }

    /* If already in the history list, just step back again. */

    else
    {
      count = b->hpos - 1;

      if (!new_view) b->hpos--;
    }

    /* Get a pointer to the URL */

    p = b->histdata;
    while(--count > 0) p += strlen(p) + 1;

    /* Get the URL in either the same window or a new window. */

    if (!new_view)
    {
      #ifdef TRACE
        if (tl & (1u<<16)) Printf("history_fetch_backwards: Exitting through fetchpage_new\n");
      #endif

      /* Fetch the URL, flagging not to record this URL in the history */

      return (fetchpage_new(b, p, 0, 1));
    }
    else
    {
      #ifdef TRACE
        if (tl & (1u<<16)) Printf("history_fetch_backwards: Exitting through windows_create_browser\n");
      #endif

      return windows_create_browser(p, NULL, NULL, NULL, 0);
    }
  }

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_fetch_backwards: Exitting with no action\n");
  #endif

  return NULL;
}

/*************************************************/
/* history_menu_popup()                          */
/*                                               */
/* Handles clicks on a history menu popup item.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the history to show,  */
/*             or NULL if this is for a global   */
/*             history from an OpenURL dialogue  */
/*             box;                              */
/*                                               */
/*             Object ID of the item holding the */
/*             menu gadget;                      */
/*                                               */
/*             Component ID of the item that was */
/*             clicked on;                       */
/*                                               */
/*             1 to show the global history even */
/*             if the first parameter is not     */
/*             NULL, else 0;                     */
/*                                               */
/*             1 to show the URLs, 0 to show     */
/*             page titles where available.      */
/*************************************************/

_kernel_oserror * history_menu_popup(browser_data * b, ObjectId object, ComponentId component, int global, int show_urls)
{
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   state;
  BBox                      menu;

  /* If there's already a menu open, close it */
  /* (so the action is to toggle the menu).   */

  if ((menusrc == Menu_LocalHist || menusrc == Menu_GlobalHist) && menuhdl == b)
  {
    menusrc = Menu_None;
    menuhdl = NULL;

    return wimp_create_menu((void *) -1, 0, 0);
  }

  /* Get the Wimp handle for the tool bar and get the window state */

  e = window_get_wimp_handle(0, object, &state.window_handle);
  if (e) return e;

  e = wimp_get_window_state(&state);
  if (e) return e;

  /* Get the bounding box of the popup icon that was used */

  e = gadget_get_bbox(0, object, component, &menu);
  if (e) return e;

  /* Convert that to screen coords ready for opening the menu */
  /* next to it.                                              */

  coords_box_toscreen(&menu, (WimpRedrawWindowBlock *) &state);

  if (component == DisplayMenu || component == OpenHistory)
  {
    /* Build and show menu to right of menu icon for DisplayMenu object */

    e = (history_build_menu(b,
                            menu.xmax - 2,
                            menu.ymax,
                            global,
                            show_urls,
                            0));
    if (e) return e;
  }
  else
  {
    /* Otherwise, show it to the left of the icon */

    e = history_build_menu(b,
                           menu.xmin - 4,
                           menu.ymin + 4,
                           global,
                           show_urls,
                           1);
    if (e) return e;
  }

  return NULL;
}

/*************************************************/
/* history_build_menu()                          */
/*                                               */
/* Builds a history menu for the given browser,  */
/* showing it at the specified coordinates.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the menu, or NULL to  */
/*             build from the global history;    */
/*                                               */
/*             x coordinate to show at;          */
/*                                               */
/*             y coordinate to show at;          */
/*                                               */
/*             1 to show the global history even */
/*             if the first parameter is not     */
/*             NULL;                             */
/*                                               */
/*             Non-0 to only show URLs, 0 to     */
/*             allow titles in the menu;         */
/*                                               */
/*             Non-0 to subtract the width of    */
/*             the menu from the given show x    */
/*             coordinate (e.g. to show to the   */
/*             left of a given position) else 0. */
/*************************************************/

_kernel_oserror * history_build_menu(browser_data * b, int x, int y, int global, int show_urls, int subtract)
{
  _kernel_oserror * e;
  wimp_menuhdr    * mhp;
  wimp_menuitem   * mip;
  int               size, i, offset, width, awidth, tempwidth, len, entry;
  char            * menudata;
  char            * title;
  int               tlen;
  int               entries;

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_build_menu: Called with %p\n", (void *) b);
  #endif

  /* If there are no history items, flag an error. */

  if (b && !b->hnum) global = 1;

  if (global && !global_history)
  {
    erb.errnum = Utils_Error_Custom_Message;

    StrNCpy0(erb.errmess,
             lookup_token("EmptyHistE:The history list is empty.",
                          0,
                          0));

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_build_menu: Exitting (history is empty)\n");
    #endif

    return &erb;
  }

  /* Work out the data size required for the menu structure. */
  /* Can't just point the menu to the history list as it's a */
  /* flex block, which may shift whilst the menu is open.    */

  size   = sizeof(wimp_menuhdr);
  offset = 0;
  width  = 8;
  awidth = 0;

  /* Loop round finding the string length of the longest entry */
  /* in 'width' and the OS unit width of it in 'awidth'.       */

  #ifdef TRACE
    if (tl & (1u<<16))
    {
      Printf("\nhistory_build_menu: Widthing menu\n"
               "=================================\n\n");
    }
  #endif


  if (b && !global)
  {
    entries = b->hnum;

    for (i = 0; i < entries; i++)
    {
      len   = strlen(b->histdata + offset) + 1;
      entry = history_visited(b->histdata + offset, 0);
      title = history_title(b->histdata + offset);

      #ifdef TRACE
        if (tl & (1u<<16))
        {
          Printf("Local  - URL  : '%s'\n",   b->histdata + offset);
          Printf("Local  - Title: '%s'\n\n", title);
        }
      #endif

      /* If the URL isn't in the history or it has no title, */
      /* or if the global choices dictate it, use the URL.   */

      if (entry < 0 || !title || show_urls)
      {
        size += len + sizeof(wimp_menuitem);
        if ((len - 1) > width) width = len - 1;

        /* Find the width of the entry in OS units. */

        RetError(utils_text_width(b->histdata + offset, &tempwidth, width));
      }
      else
      {
        tlen = strlen(title);
        size += tlen + 1 + sizeof(wimp_menuitem);
        if (tlen > width) width = tlen;

        RetError(utils_text_width(title, &tempwidth, width));
      }

      /* If the string is wider than previously recorded, store the new width */

      if (tempwidth > awidth) awidth = tempwidth;

      /* Move past the current URL in the local history */

      offset += len;
    }
  }
  else
  {
    int hsize = flex_size(&global_history) - 1;

    /* The code is analogous to the local history stuff */
    /* above, so see that for comments.                 */

    entries = 0;

    while (offset < hsize)
    {
      entries ++;
      offset  += sizeof(int); /* Skip timestamp */

      len  = strlen((char *) global_history + offset)       + 1;
      tlen = strlen((char *) global_history + offset + len) + 1;

      #ifdef TRACE
        if (tl & (1u<<16))
        {
          Printf("Global - URL  : '%s'\n",   (char *) global_history + offset);
          Printf("Global - Title: '%s'\n\n", (char *) global_history + offset + len);
        }
      #endif

      if (tlen < 2 || show_urls)
      {
        size += len + sizeof(wimp_menuitem);
        if ((len - 1) > width) width = len - 1;

        RetError(utils_text_width((char *) global_history + offset, &tempwidth, width));
      }
      else
      {
        size += tlen + sizeof(wimp_menuitem);
        if ((tlen - 1) > width) width = tlen - 1;

        RetError(utils_text_width((char *) global_history + offset + len, &tempwidth, width));
      }

      if (tempwidth > awidth) awidth = tempwidth;

      offset += len + tlen;
      offset = (int) WordAlign(offset);
    }
  }

  size += 4;

  /* Deallocate any existing menu data and allocate the new required size. */

  #ifdef TRACE
    if (tl & (1u<<16))
    {
      if (view_history_menu) Printf("history_build_menu: Freeing existing store\n");
      else                   Printf("history_build_menu: There is no existing store\n");
    }
  #endif

  if (view_history_menu) free(view_history_menu);

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_build_menu: Attempting to malloc %d bytes\n",size);
  #endif

  view_history_menu = malloc(size);

  if (!view_history_menu)
  {
    erb.errnum = Utils_Error_Custom_Normal;

    StrNCpy0(erb.errmess,
             lookup_token("NoMemLHi:There is not enough free memory to open the history menu.",
                          0,
                          0));

    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_build_menu: Exitting (couldn't claim %d bytes for menu)\n", size);
    #endif

    return &erb;
  }

  /* Ensure the contents are zeroed. */

  memset(view_history_menu, 0, size);

  /* Point mhp to the start of the menu header, and mip  */
  /* to the first menu item (straight after the header). */

  mhp = (wimp_menuhdr  *) view_history_menu;
  mip = (wimp_menuitem *) (((int) mhp) + sizeof(wimp_menuhdr));

  /* Fill in the header. */

  strncpy(mhp->title, lookup_token("HistMenT:History",0,0), 12);

  mhp->tit_fcol  = 7;
  mhp->tit_bcol  = 2;
  mhp->work_fcol = 7;
  mhp->work_bcol = 0;
  mhp->width     = awidth;
  mhp->height    = 44;
  mhp->gap       = 0;

  /* Pointer arithmetic - mip + entries adds entries lots */
  /* of sizeof(mip) to menudata, since the cast to char * */
  /* is the last thing that happens. So menudata points   */
  /* past all the menu structure stuff to the data area.  */

  menudata = (char *) (mip + entries);
  offset   = 0;

  /* Fill in each menu item. */

  #ifdef TRACE
    if (tl & (1u<<16))
    {
      Printf("\nhistory_build_menu: Building menu\n"
               "=================================\n\n");
    }
  #endif

  for (i = 0; i < entries; i++)
  {
    if (b && !global)
    {
      len   = strlen(b->histdata + offset) + 1;
      entry = history_visited(b->histdata + offset, 0);
      title = history_title(b->histdata + offset);

      #ifdef TRACE
        if (tl & (1u<<16))
        {
          Printf("Local  - URL  : '%s'\n",   b->histdata + offset);
          Printf("Local  - Title: '%s'\n\n", title);
        }
      #endif
    }
    else
    {
      offset += sizeof(int);

      entry = -1;
      len   = strlen((char *) global_history + offset) + 1;
      title = (char *) global_history + offset + len;

      #ifdef TRACE
        if (tl & (1u<<16))
        {
          Printf("Global - URL  : '%s'\n",   (char *) global_history + offset);
          Printf("Global - Title: '%s'\n\n", title);
        }
      #endif
    }

    mip->flags     = (i == entries - 1 ? wimp_MLAST : 0);
    mip->submenu   = (wimp_menuptr) -1;
    mip->iconflags = wimp_ITEXT    |
                     wimp_IFILLED  |
                     wimp_INDIRECT |
                     (7<<24);

    mip->data.indirecttext.validstring = NULL;
    mip->data.indirecttext.bufflen     = 0;
    mip->data.indirecttext.buffer      = menudata;

    /* If the URL isn't in the global history, or it doesn't */
    /* have a title string associated with it, or if the     */
    /* global choices dicate it, use the URL in the menu.    */

    if (
         show_urls ||
         !title    ||
         (
           title &&
           strlen(title) < 1
         )
       )
    {
      /* Copy the history item into the data area */

      if (b && !global) strcpy(menudata, b->histdata + offset);
      else              strcpy(menudata, (char *) global_history + offset);

      /* Advance the data pointer, possibly removing any */
      /* CGI information if HIDE_CGI is defined inside   */
      /* the compiler.                                   */

      #ifdef HIDE_CGI

        toolbars_hide_cgi(menudata);
        menudata += strlen(menudata) + 1;

      #else

        /* If not hiding all CGI information, still don't want to  */
        /* put all the CGI stuff in the menu or it can get far too */
        /* wide. So leave an indicator to show there was CGI info. */

        toolbars_hide_cgi(menudata);

        if      (len > strlen(menudata) + 7) strcat(menudata, " (+CGI)");
        else if (len > strlen(menudata) + 4) strcat(menudata, "?...");

        menudata += strlen(menudata) + 1;

      #endif
    }
    else
    {
      /* Copy the title string in and advance the data pointer */

      strcpy(menudata, title);
      menudata += strlen(title) + 1;
    }

    /* Next item... */

    offset += len;

    if (global)
    {
      offset += strlen(title) + 1;
      offset = (int) WordAlign(offset);
    }

    mip ++;
  }

  #ifdef TRACE
    if (menudata > ((char *) view_history_menu) + size)
    {
      erb.errnum = 0;
      sprintf(erb.errmess,"Fatal error inside history_build_menu: Overran menu buffer! Allocated %d bytes, then used %d.",size,(int) menudata - (int) view_history_menu);
      show_error(&erb);
    }
  #endif

  /* Finally, open the menu */

  #ifdef TRACE
    if (tl & (1u<<16)) Printf("history_build_menu: Exitting through wimp_create_menu\n");
  #endif

  menuhdl = (void *) b;

  if (global) menusrc = Menu_GlobalHist;
  else        menusrc = Menu_LocalHist;

  return wimp_create_menu(view_history_menu, x - (subtract ? (mhp->width + 64) : 0), y);
}

/*************************************************/
/* history_menu_selection()                      */
/*                                               */
/* Jumps to a URL according to the item selected */
/* in a history menu.                            */
/*                                               */
/* If Adjust is used the menu is not reopened as */
/* the fetch will occur in a new window (as with */
/* following page links) - it doesn't make sense */
/* to reopen the menu in this case.              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the history, or NULL  */
/*             for the global history - in this  */
/*             case it is assumed that the menu  */
/*             opened from an Open URL dialogue  */
/*             and appropriate alternative       */
/*             functions will be called;         */
/*                                               */
/*             Pointer to a WimpPollBlock struct */
/*             from which the menu item that was */
/*             selected may be determined.       */
/*************************************************/

_kernel_oserror * history_menu_selection(browser_data * b, WimpPollBlock * block)
{
  int item, adj;
  int global;
  int offset;

  /* What type of menu was it? */

  if (menusrc == Menu_GlobalHist || !b) global = 1;
  else                                  global = 0;

  /* What item was clicked on? */

  item = block->menu_selection[0];

  /* If the menu selection appears to be out of range, */
  /* exit quietly.                                     */

  if (!global && (item < 0 || (item + 1) > b->hnum))
  {
    #ifdef TRACE
      if (tl & (1u<<16)) Printf("history_menu_selection: Warning, menu selection out of range\n");
    #endif

    return NULL;
  }

  /* Otherwise find out the button that was used. */

  adj = fixed.ignoreadjust ? 0 : adjust();

  /* If a new window isn't going to be opened, */
  /* need to remember that we've just dived    */
  /* into the History list so that forwards /  */
  /* backwards work correctly.                 */

  if (!adj && b)
  {
    /* If jumping from the top of the history, ensure that the */
    /* page we're currently on is added to it first.           */

    if (!b->hpos)
    {
      _kernel_oserror * e;

      /* Ensure the current URL is put at the top of the history */

      e = history_record_local(b, NULL);

      if (e)
      {
        #ifdef TRACE
          if (tl & (1u<<16)) Printf("history_fetch_backwards: Exitting with error\n", (void *) b);
        #endif

        return e;
      }
    }

    /* Set the position in the history */

    b->hpos = item + 1;

    /* Is this the end of the history? */

    if (b->hpos == b->hnum) b->hpos = 0;
  }

  /* Point to the required entry in the history. */

  if (!global)
  {
    offset = 0;

    while (item--)
    {
      /* Get past each URL... */

      offset += strlen(b->histdata + offset) + 1;
    }
  }
  else
  {
    offset = sizeof(int);

    while (item--)
    {
      /* Get past each URL, title, and datestamp for next entry */

      offset += strlen((char *) global_history + offset) + 1;
      offset += strlen((char *) global_history + offset) + 1;
      offset += sizeof(int);

      offset = (int) WordAlign(offset);
    }
  }

  /* Flag that there is no known menu source anymore */
  /* and fetch the new URL.                          */

  menusrc = Menu_None;

  /* If b is set, this is from a browser toolbar */

  if (b)
  {
    /* Somewhat non-standard behaviour to have an adjust-click  */
    /* open a new window instead of leaving the menu up, but    */
    /* this is more consistent with the rest of the browser UI. */

    if (!adj) return fetchpage_new(b,
                                   (global ? (char *) global_history : b->histdata) + offset,
                                   0,
                                   1);

    else return windows_create_browser((global ? (char *) global_history : b->histdata) + offset,
                                       NULL,
                                       NULL,
                                       NULL,
                                       0);
  }

  /* Otherwise, if b is NULL, it's from an Open URL dialogue */

  else
  {
    return openurl_fill_in_url((char *) global_history + offset);
  }

  return NULL;
}

/*************************************************/
/* history_load()                                */
/*                                               */
/* Loads the global history from the given path. */
/*                                               */
/* Parameters: Pointer to the full pathname for  */
/*             the history file.                 */
/*************************************************/

_kernel_oserror * history_load(char * pathname)
{
  _kernel_oserror * e;
  int               type, length;

  /* Find out the file length */

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

            17, /* Read catalogue info */
            pathname,

            &type,
            &length);

  if (e) return e;

  if (!type) return NULL; /* Not found - fail quietly, not finding it is fine */

  /* Free any existing store and allocate space for the file */

  if (global_history) flex_free((flex_ptr) &global_history);

  if (length)
  {
    if (!flex_alloc((flex_ptr) &global_history, length))
    {
      /* Complain if the allocation fails */

      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               lookup_token("NoMemHiF:There is not enough free memory to load the global history.",
               0,
               0));

      return &erb;
    }

    /* Load the file */

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

                 16, /* Load named file */
                 pathname,
                 global_history,
                 0);
  }

  return NULL;
}

/*************************************************/
/* history_load()                                */
/*                                               */
/* Saves the global history to the given path.   */
/*                                               */
/* If there is no global history allocated or it */
/* is empty, a zero byte length file is written. */
/*                                               */
/* Parameters: Pointer to the full pathname for  */
/*             the history file.                 */
/*************************************************/

_kernel_oserror * history_save(char * pathname)
{
  int    length;
  FILE * file;

  /* Determine the file length */

  if (!global_history) length = 0;
  else                 length = flex_size((flex_ptr) &global_history);

  /* Create the file */

  file = fopen(pathname, "wb");
  if (!file) return _kernel_last_oserror();

  /* Write the data */

  if (fwrite(global_history, 1, length, file) < length) return _kernel_last_oserror();

  /* Close the file and exit */

  fclose(file);

  return NULL;
}