/* 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 "TBEvents.h"
#include "Utils.h"

#include "Browser.h"
#include "FetchPage.h"
#include "Memory.h"
#include "Toolbars.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 = (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) - size);

        #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 */
  }

  #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.       */
/*                                               */
/* 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);

  /* 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));
    }
    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);
    }
  }

  #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;

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

      if (!new_view)
      {
        /* 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");
          #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));
    }
    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);
    }
  }

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

  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;             */
/*                                               */
/*             x coordinate to show at;          */
/*                                               */
/*             y coordinate to show at;          */
/*                                               */
/*             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 show_urls, int subtract)
{
  wimp_menuhdr  * mhp;
  wimp_menuitem * mip;
  int             size, i, offset, width, awidth, tempwidth, len, entry;
  int             cspacing = 0, cwidth = 0;
  char          * menudata;
  char          * title;

  #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->hnum)
  {
    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;

  if (!nested_wimp)
  {
    /* Find out the spacing (start of one char to start of next) */
    /* and width of the text the Wimp is using, assuming that if */
    /* there is no nested wimp, Wimp_TextOp is unavailable.      */

    int vars[3] = {BBC_GCharSizeX, BBC_GCharSpaceX, -1};

    _swix(OS_ReadVduVariables, _INR(0, 1), &vars, &vars);

    cwidth   = vars[0];
    cspacing = vars[1];
  }

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

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

    /* 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. If nested_wimp */
      /* is set assume Wimp_TextOp is available, else assume it  */
      /* is not available.                                       */

      if (nested_wimp) _swix(Wimp_TextOp,
                             _INR(0,2) | _OUT(0),

                             1,
                             b->histdata + offset,
                             width,

                             &tempwidth);

      /* Spacing is width of char plus the gap between it and the */
      /* next one. String length is in 'width', so want to do     */
      /* (width - 1) lots of spacing plus the last char's width.  */

      else tempwidth = (width - 1) * cspacing + cwidth;
    }
    else
    {
      int tlen;

      tlen = strlen(title);
      size += tlen + 1 + sizeof(wimp_menuitem);
      if (tlen > width) width = tlen;

      /* See above code for comments */

      if (nested_wimp) _swix(Wimp_TextOp,
                             _INR(0,2) | _OUT(0),

                             1,
                             title,
                             width,

                             &tempwidth);

      else tempwidth = (width - 1) * cspacing + cwidth;
    }

    /* 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;
  }

  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("HistMemT: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 + b->hnum adds b->hnum 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 + b->hnum);
  offset   = 0;

  /* Fill in each menu item. */

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

    mip->flags     = (i == b->hnum - 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 (entry < 0 || !title || show_urls)
    {
      /* Copy the history item into the data area */

      strcpy(menudata, b->histdata + 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;
    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;
  menusrc = Menu_History;

  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;          */
/*             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;
  char * p;

  item = block->menu_selection[0];

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

  if (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)
  {
    /* 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. */

  p = b->histdata;

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

    p += strlen(p) + 1;
  }

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

  menusrc = Menu_None;

  /* 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, p, 0);
  else      return windows_create_browser(p, NULL, NULL, NULL);

  return NULL;
}

/*************************************************/