/* 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   : Memory.c                               */
/*                                                 */
/* Purpose: Granularity-controlled memory handling */
/*          functions for the browser, designed as */
/*          transparent replacements for malloc,   */
/*          calloc, realloc and free (amongst      */
/*          other memory handlers).                */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 29-Nov-96: Created.                    */
/***************************************************/

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

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

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

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

#include "Memory.h"

/* Local definitions */

#define MallocGranularity    256
#define FlexGranularityShift 12

#define Flex_256_Point       8192
#define Flex_4K_Point        16384

/*************************************************/
/* memory_malloc()                               */
/*                                               */
/* Works as malloc, but only allocates data in   */
/* chunks of MallocGranularity (see Memory.h)    */
/* bytes. It always adds 4 bytes to the amount   */
/* requested after rounding to the block size,   */
/* and stores the actual exact requested size    */
/* in these bytes after mallocing the block. The */
/* pointer that is returned is 4 bytes on from   */
/* the actual size store so that the caller      */
/* can treat the routines exactly like malloc.   */
/* This does mean that a mixture of these        */
/* routines and malloc etc. must *NEVER BE USED* */
/* on the same blocks of data!                   */
/*                                               */
/* Parameters: As for malloc.                    */
/*                                               */
/* Returns:    As for malloc, in effect.         */
/*************************************************/

void * memory_malloc(size_t size)
{
  size_t s;
  void * r;

  /* Get the rounded up size of the block to allocate, plus */
  /* the four bytes for the requested size store.           */

  s = (((size / MallocGranularity) + 1) * MallocGranularity) + 4;

  /* Allocate the block */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("memory_malloc: %d bytes asked for, giving %d\n",size,s);
  #endif

  r = malloc(s);

  #ifdef TRACE
    if (tl & (1u<<12))
    {
      if (r) Printf("memory_malloc: Success, block is %p\n",r);
      else Printf("memory_malloc: Failure\n");
    }
  #endif

  /* Assuming the allocation was successful, return the  */
  /* pointer to the block plus 4 to skip the size store, */
  /* after storing the exact requested size in those     */
  /* four bytes. Else return NULL.                       */

  if (r)
  {
    #ifdef TRACE
      malloccount += s;
      if (tl & (1u<<13)) Printf("** malloccount (memory_malloc): \0211%d\0217\n",malloccount);
    #endif

    *((int *) r) = size;
    return ((void *) (((int) r) + 4));
  }
  else return NULL;
}

/*************************************************/
/* memory_calloc()                               */
/*                                               */
/* Works as calloc, but with the added design    */
/* rationale of memory_malloc.                   */
/*                                               */
/* Parameters: As for calloc.                    */
/*                                               */
/* Returns:    As for calloc, in effect.         */
/*************************************************/

void * memory_calloc(size_t n, size_t size)
{
  size_t s;
  void * r;

  /* On entry, 'n' holds a number of objects, and 'size' */
  /* holds the size of each object. Set 'size' to now    */
  /* hold the total amount required for all the objects. */

  size = n * size;

  /* Get the rounded up size of the block to allocate, plus */
  /* the four bytes for the requested size store.           */

  s = (((size / MallocGranularity) + 1) * MallocGranularity) + 4;

  /* Allocate the block */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("memory_calloc: %d bytes asked for, giving %d\n",size,s);
  #endif

  r = malloc(s);

  #ifdef TRACE
    if (tl & (1u<<12))
    {
      if (r) Printf("memory_calloc: Success, block is %p\n",r);
      else Printf("memory_calloc: Failure\n");
    }
  #endif

  /* Assuming the allocation was successful, return the  */
  /* pointer to the block plus 4 to skip the size store, */
  /* after storing the exact requested size in those     */
  /* four bytes and zeroing the rest of the block.  Else */
  /* return NULL.                                        */

  if (r)
  {
    #ifdef TRACE
      malloccount += s;
      if (tl & (1u<<13)) Printf("** malloccount (memory_calloc): \0211%d\0217\n",malloccount);
    #endif

    *((int *) r) = size;
    memset((void *) (((int) r) + 4), 0, s - 4);
    return ((void *) (((int) r) + 4));
  }
  else return NULL;
}

/*************************************************/
/* memory_realloc()                              */
/*                                               */
/* Works as realloc, but with the added design   */
/* rationale of memory_malloc. In this case,     */
/* should the rounding up of the requested sizes */
/* mean that the requested size this time fits   */
/* within the actual amount allocated, no        */
/* reallocation will be done, thus speeding      */
/* things up a bit. It also helps cut down on    */
/* fragmentation. This applies both for growing  */
/* and shrinking the size of a block.            */
/*                                               */
/* Parameters: As for realloc.                   */
/*                                               */
/* Returns:    As for realloc, in effect.        */
/*************************************************/

void * memory_realloc(void * ptr, size_t size)
{
  size_t s, c;
  void * r;

  /* Work out what the current size must be, based on the   */
  /* first four bytes of the block; these contain the exact */
  /* size last passed to memory_alloc or memory_calloc.     */

  r = (void *) (((int) ptr) - 4); /* r holds the real block pointer now */
  s = *((int *) r);               /* s now holds the last requested size */

  c = (((s / MallocGranularity) + 1) * MallocGranularity) + 4;

  /* Get the rounded up new size plus 4 bytes of size store */

  s = (((size / MallocGranularity) + 1) * MallocGranularity) + 4;

  /* If these two sizes are different, we need to do a realloc. */
  /* If the realloc fails we return NULL anyway, so there's no  */
  /* problem with doing 'pointer = realloc (same pointer, ...)' */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("memory_realloc: Block is %p.\n"
                          "                Will proceed if old size %d <> new size %d\n",r,c,s);
  #endif

  if (s != c)
  {
    #ifdef TRACE
      if (tl & (1u<<12)) Printf("memory_realloc: %d bytes asked for, giving %d\n",size,s);
    #endif

    r = realloc(r,s);

    #ifdef TRACE
      if (tl & (1u<<12))
      {
        if (r) Printf("memory_realloc: Success\n");
        else Printf("memory_realloc: Failure\n");
      }
    #endif

    /* If the realloc succeeded write the new exact requested size */
    /* into the first word and return the pointer plus 4, as in    */
    /* memory_malloc; else return NULL.                            */

    if (r)
    {
      #ifdef TRACE
        malloccount += (s - c);
        if (tl & (1u<<13)) Printf("** malloccount (memory_realloc): \0211%d\0217\n",malloccount);
      #endif

      * ((int *) r) = size;
      return ((void *) (((int) r) + 4));
    }
    else return NULL;
  }

  /* If the two sizes were the same, no realloc is needed so */
  /* just give the original pointer back again.              */

  else return ptr;
}

/*************************************************/
/* memory_alloc_and_set()                        */
/*                                               */
/* mallocs an area of memory and sets it to      */
/* contain a specific character using memset.    */
/* Does *NOT* given an error if the malloc fails */
/* - this is left to the caller to handle.       */
/*                                               */
/* Parameters: The amount of memory to allocate, */
/*             in bytes;                         */
/*                                               */
/*             The value to fill the block with  */
/*             (from 0 to 255).                  */
/*                                               */
/* Returns:    Pointer to the claimed memory, or */
/*             NULL if the claim failed          */
/*************************************************/

void * memory_alloc_and_set(size_t s, int f)
{
  void * p;

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("memory_alloc_and_set: malloc %d, initialise to %d\n",s,f);
  #endif

  p = malloc(s);
  if (p) memset(p,f,s);

  #ifdef TRACE
    if (tl & (1u<<12))
    {
      if (p) Printf("memory_alloc_and_set: Success, block is %p\n",p);
      else   Printf("memory_alloc_and_set: Failure\n");
    }
  #endif

  return p;
}

/*************************************************/
/* memory_free()                                 */
/*                                               */
/* Works as free() but subtracts four from the   */
/* passed pointer first, assuming that the block */
/* was originally allocated with memory_alloc.   */
/* The contents of the pointer *ARE NOT ZEROED*  */
/* (unlike with, say, flex_free).                */
/*                                               */
/* Parameters: As for free.                      */
/*************************************************/

void memory_free(void * ptr)
{
  #ifdef TRACE
    if (tl & (1u<<12)) Printf("memory_free: Freeing block %p\n",(void *) (((int) ptr) - 4));
    malloccount -= memory_granular_size(ptr);
    if (tl & (1u<<13)) Printf("** malloccount (memory_free): \0212%d\0217\n",malloccount);
  #endif

  free ((void *) (((int) ptr) - 4));
}

/*************************************************/
/* memory_size()                                 */
/*                                               */
/* Returns the size of a memory_malloced block   */
/* of memory. There's no alloc-style equivalent. */
/* The amount is the originally requested size,  */
/* and not the actual 'granular' size.           */
/*                                               */
/* Parameters: The pointer to the block, as      */
/*             returned by memory_malloc and     */
/*             memory_realloc (for example).     */
/*                                               */
/* Returns:    The size in bytes as an int. The  */
/*             function has no way of knowing if */
/*             the pointer was sensible, but     */
/*             will return zero if it's null.    */
/*************************************************/

int memory_size(void * ptr)
{
  void * r;

  r = (void *) (((int) ptr) - 4); /* r holds the real block pointer now */

  return *((int *) r); /* Return the size */
}

/*************************************************/
/* memory_granular_size()                        */
/*                                               */
/* As memory_size, but returns the granular size */
/* (i.e. the real amount allocated).             */
/*                                               */
/* Parameters: As memory_size.                   */
/*                                               */
/* Returns:    As memory_size, but the real size */
/*             rather than the requested size.   */
/*************************************************/

int memory_granular_size(void * ptr)
{
  void * r;
  int    s;

  r = (void *) (((int) ptr) - 4); /* r holds the real block pointer now */
  s = *((int *) r);               /* s now holds the last requested size */

  /* Get the rounded up new size plus 4 bytes of size store */

  return (((s / MallocGranularity) + 1) * MallocGranularity) + 4; /* Return the granular size */
}

/*************************************************/
/* memory_set_chunk_size()                       */
/*                                               */
/* Sets the size of a particular block of memory */
/* identified by int chunk, where chunk is:      */
/*                                               */
/* 1:  CK_FURL (fetching URL)                    */
/* 2:  CK_DURL (display URL)                     */
/* 3:  CK_CHIL (frames/children array)           */
/* 4:  CK_NAME (window name for targetted links) */
/* 5:  CK_LINE (line structures array)           */
/* 6:  CK_LDAT (chunk structures array)          */
/* 7:  CK_FWID (frame widths array)              */
/* 8:  CK_FHEI (frame heights array)             */
/* 9:  CK_LINV (lines, variable granularity)     */
/* 10: CK_LDAV (chunks, variable granularity)    */
/* 11: CK_STAT (status_content array)            */
/* 12: CK_OBJB (OBJECT, APPLET, EMBED structs)   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             for which the memory is to be     */
/*             allocated;                        */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             in which some allocations will be */
/*             made (e.g. CK_LINE, CK_LINV);     */
/*                                               */
/*             The chunk identifier (see above); */
/*                                               */
/*             The amount of memory to allocate, */
/*             in bytes.                         */
/*                                               */
/* Assumes:    The reformat_cell pointer may be  */
/*             NULL, in which case the 'cell'    */
/*             field of the browser_data struct  */
/*             will be used.                     */
/*************************************************/

_kernel_oserror * memory_set_chunk_size(browser_data * b, reformat_cell * cell, int chunk, int size)
{
  int    oldsize = 0;
  int    success = 1;
  void * pointer = NULL;

  if (!cell) cell = b->cell;

  #ifdef TRACE
    if (tl & (1u<<7)) Printf("memory_set_chunk_size: Data * %p, cell * %p, chunk %d, size %d\n",b,cell,chunk,size);
  #endif

  /* Go through all the possible chunks, dealing with each case individually. */
  /* Though some of the code will be duplicated, this individual handling     */
  /* allows greater flexibility in future - various blocks could have the way */
  /* they are dealt with changed radically without too much trouble at this   */
  /* end of things.                                                           */

  switch (chunk)
  {
    case CK_OBJB:
    {
      /* Set oldsize to the block size of the requested bit of memory,    */
      /* giving an immediate exit error if the chunk ID isn't recognised. */
      /* Note that oldsize will be zero if the block hasn't been          */
      /* allocated already.                                               */

      if (b->odata) oldsize = flex_size((flex_ptr) &b->odata);
      else if (size == 0) return NULL;

      /* If the requested size is different from the current size... */

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

          flex_free((flex_ptr) &b->odata); /* If size = 0, free the block */
        }
        else
        {
          /* If the block exists, resize it. Otherwise allocate it anew. */

          if (oldsize) success = flex_extend((flex_ptr) &b->odata, size);
          else success = flex_alloc((flex_ptr) &(b->odata), size);

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

      break;
    }

    case CK_FURL:
    {
      if (!size)
      {
        /* If setting the size to zero, want to free the block */

        if (b->urlfdata)
        {
          memory_free((void *) b->urlfdata);
          b->urlfdata = NULL;
        }
      }
      else
      {
        /* If there is a non-null pointer, reallocate the block. */
        /* Store the new pointer in 'pointer', knowing this may  */
        /* be NULL if the realloc failed. Otherwise, malloc a    */
        /* new block again storing the pointer in 'pointer'.     */

        if (b->urlfdata) pointer = memory_realloc((void *) b->urlfdata, size);
        else pointer = memory_malloc(size);

        /* If 'pointer' isn't null, set b->urlfdata to point to      */
        /* the new block. If the pointer *is* null, then b->urlfdata */
        /* is left alone. This is what we want - if it pointed to    */
        /* an old block that was realloced, we still want that       */
        /* pointer as the old size block is still malloced; else, it */
        /* will have the default value of NULL.                      */

        if (pointer) b->urlfdata = (char *) pointer;

        /* Failure is flagged through success = 0 (it's default value is 1). */

        else success = 0;
      }

      break;
    }

    /* The rest of the code is basically the same as the stuff above at present. */

    case CK_DURL:
    {
      if (!size)
      {
        if (b->urlddata)
        {
          memory_free((void *) b->urlddata);
          b->urlddata = NULL;
        }
      }
      else
      {
        if (b->urlddata) pointer = memory_realloc((void *) b->urlddata, size);
        else pointer = memory_malloc(size);

        if (pointer) b->urlddata = (char *) pointer;
        else success = 0;
      }

      break;
    }

    case CK_NAME:
    {
      if (!size)
      {
        if (b->window_name)
        {
          free((void *) b->window_name);
          b->window_name = NULL;
        }
      }
      else
      {
        if (b->window_name) pointer = realloc((void *) b->window_name, size);
        else pointer = malloc(size);

        if (pointer) b->window_name = (char *) pointer;
        else success = 0;
      }

      break;
    }

    /* Small arrays - not allocated in any granular fashion, and other */
    /* generally necessary flags/counters allow the array size to be   */
    /* calculated easily. Hence use malloc etc. directly.              */

    case CK_CHIL:
    {
      if (!size)
      {
        if (b->children)
        {
          free((void *) b->children);
          b->children = NULL;
        }
      }
      else
      {
        if (b->children) pointer = realloc((void *) b->children, size);
        else pointer = malloc(size);

        if (pointer) b->children = (browser_data **) pointer;
        else success = 0;
      }

      break;
    }

    case CK_FWID:
    {
      if (!size)
      {
        if (b->frame_widths)
        {
          free((void *) b->frame_widths);
          b->frame_widths = NULL;
        }
      }
      else
      {
        if (b->frame_widths) pointer = realloc((void *) b->frame_widths, size);
        else pointer = malloc(size);

        if (pointer) b->frame_widths = (int *) pointer;
        else success = 0;
      }

      break;
    }

    case CK_FHEI:
    {
      if (!size)
      {
        if (b->frame_heights)
        {
          free((void *) b->frame_heights);
          b->frame_heights = NULL;
        }
      }
      else
      {
        if (b->frame_heights) pointer = realloc((void *) b->frame_heights, size);
        else pointer = malloc(size);

        if (pointer) b->frame_heights = (int *) pointer;
        else success = 0;
      }

      break;
    }

    /* Small block, likely to change size fairly often - so use */
    /* memory_malloc etc. for the granularity of allocation.    */

    case CK_STAT:
    {
      if (!size)
      {
        if (b->status_contents)
        {
          memory_free((void *) b->status_contents);
          b->status_contents = NULL;
        }
      }
      else
      {
        if (b->status_contents) pointer = memory_realloc((void *) b->status_contents, size);
        else pointer = memory_malloc(size);

        if (pointer) b->status_contents = (browser_data **) pointer;
        else success = 0;
      }

      break;
    }

    /* The line and line data code only allocates in blocks of 4096 bytes - */
    /* the shifting by FlexGranularityShift (see Memory.h) bits handles     */
    /* effectively fast division / multiplication by this size to see if    */
    /* the requested allocation exceeds the current block size.             */

    case CK_LINE:
    {
      if (cell->ldata) oldsize = flex_size((flex_ptr) &cell->ldata);
      else if (size == 0) return NULL;

      if (((oldsize >> FlexGranularityShift) != ((size >> FlexGranularityShift) + 1)) || !size)
      {
        if (!size)
        {
          #ifdef TRACE
            flexcount -= oldsize;
            if (tl & (1u<<14)) Printf("**   line flexcount: %d\n",flexcount);
          #endif

          flex_free((flex_ptr) &cell->ldata);
        }
        else
        {
          if (oldsize) success = flex_extend((flex_ptr) &cell->ldata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift);
          else success = flex_alloc((flex_ptr) &cell->ldata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift);

          #ifdef TRACE
            if (success)
            {
              flexcount += (flex_size((flex_ptr) &cell->ldata) - oldsize);
              if (tl & (1u<<14)) Printf("**   line flexcount: %d\n",flexcount);
            }
          #endif
        }
      }

      break;
    }

    case CK_LDAT:
    {
      if (cell->cdata) oldsize = flex_size((flex_ptr) &cell->cdata);
      else if (size == 0) return NULL;

      if (((oldsize >> FlexGranularityShift) != ((size >> FlexGranularityShift) + 1)) || !size)
      {
        if (!size)
        {
          #ifdef TRACE
            flexcount -= oldsize;
            if (tl & (1u<<14)) Printf("**   ldat flexcount: %d\n",flexcount);
          #endif

          flex_free((flex_ptr) &cell->cdata);
        }
        else
        {
          if (oldsize) success = flex_extend((flex_ptr) &cell->cdata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift);
          else success = flex_alloc((flex_ptr) &cell->cdata, ((size >> FlexGranularityShift) + 1) << FlexGranularityShift);

          #ifdef TRACE
            if (success)
            {
              flexcount += (flex_size((flex_ptr) &cell->cdata) - oldsize);
              if (tl & (1u<<14)) Printf("**   ldat flexcount: %d\n",flexcount);
            }
          #endif
        }
      }

      break;
    }

    /* Variable granularity versions of the above static granularity routines */

    case CK_LINV:
    {
      int gshift = 0;

      if (cell->ldata) oldsize = flex_size((flex_ptr) &cell->ldata);
      else if (size == 0) return NULL;

      if (oldsize > Flex_256_Point)  gshift = 8;  /* 256 byte granularity */
      if (oldsize > Flex_4K_Point) gshift = 12; /* 4K granularity */

      if (((oldsize >> gshift) != ((size >> gshift) + 1)) || !size)
      {
        if (!size)
        {
          #ifdef TRACE
            flexcount -= oldsize;
            if (tl & (1u<<14)) Printf("**   line flexcount: %d\n",flexcount);
          #endif

          flex_free((flex_ptr) &cell->ldata);
        }
        else
        {
          if (oldsize) success = flex_extend((flex_ptr) &cell->ldata, ((size >> gshift) + 1) << gshift);
          else success = flex_alloc((flex_ptr) &cell->ldata, ((size >> gshift) + 1) << gshift);

          #ifdef TRACE
            if (success)
            {
              flexcount += (flex_size((flex_ptr) &cell->ldata) - oldsize);
              if (tl & (1u<<14)) Printf("**   line flexcount: %d\n",flexcount);
            }
          #endif
        }
      }

      break;
    }

    case CK_LDAV:
    {
      int gshift = 0;

      if (cell->cdata) oldsize = flex_size((flex_ptr) &cell->cdata);
      else if (size == 0) return NULL;

      if (oldsize > Flex_256_Point)  gshift = 8;  /* 256 byte granularity */
      if (oldsize > Flex_4K_Point) gshift = 12; /* 4K granularity */

      if (((oldsize >> gshift) != ((size >> gshift) + 1)) || !size)
      {
        if (!size)
        {
          #ifdef TRACE
            flexcount -= oldsize;
            if (tl & (1u<<14)) Printf("**   ldat flexcount: %d\n",flexcount);
          #endif

          flex_free((flex_ptr) &cell->cdata);
        }
        else
        {
          if (oldsize) success = flex_extend((flex_ptr) &cell->cdata, ((size >> gshift) + 1) << gshift);
          else success = flex_alloc((flex_ptr) &cell->cdata, ((size >> gshift) + 1) << gshift);

          #ifdef TRACE
            if (success)
            {
              flexcount += (flex_size((flex_ptr) &cell->cdata) - oldsize);
              if (tl & (1u<<14)) Printf("**   ldat flexcount: %d\n",flexcount);
            }
          #endif
        }
      }

      break;
    }

    /* If we reach this stage, something is very wrong - the chunk ID */
    /* hasn't been recognised. This is a fairly good reason to panic  */
    /* and Get Out Now.                                               */

    default:
    {
      erb.errnum = 0;
      StrNCpy0(erb.errmess,
               lookup_token("STCUnkwn:Serious internal error - Unknown chunk ID in memory_set_chunk_size; must exit immediately.",
                            0,
                            0));
      show_error(&erb);

      break;
    }
  }

  /* If success is 1 at this stage, exit with no error. This will */
  /* be the case if no (re)allocation was needed as the old and   */
  /* requested sizes were the same, as success was initialised to */
  /* a value of 1.                                                */

  #ifdef TRACE
    if (tl & (1u<<7))
    {
      if (success) Printf("memory_set_chunk_size: Successful\n");
      else Printf("memory_set_chunk_size: Exitting with an error\n");
    }
  #endif

  if (success) return NULL;

  /* The allocation failed, so must have run out of memory or something... */

  return make_no_cont_memory_error(10);
}