/*******************************************************************
 *
 *  ttmemory.c                                               1.2
 *
 *    Memory management component (body).
 *
 *  Copyright 1996, 1997 by
 *  David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 *  Portions Copyright 1998 by Michal Necasek
 *
 *  This file is part of the FreeType project, and may only be used
 *  modified and distributed under the terms of the FreeType project
 *  license, LICENSE.TXT.  By continuing to use, modify, or distribute
 *  this file you indicate that you have read the license and
 *  understand and accept it fully.
 *
 *
 *  Changes between 1.1 and 1.2:
 *
 *  - the font pool is gone.
 *
 *  - introduced the FREE macro and the Free function for
 *    future use in destructors.
 *
 *  - Init_FontPool() is now a macro to allow the compilation of
 *    'legacy' applications (all four test programs have been updated).
 *
 *  Note:  This code underwent major changes for use in the OS/2
 *         Font Driver (FreeType/2).
 *
 ******************************************************************/

#include "ttdebug.h"
#include "ttmemory.h"
#include "ttengine.h"

#define INCL_DEV
#include <os2.h>
#include <pmddi.h>

#include <stdlib.h>

#undef  DEBUG_MEM

/* -------------------- debugging defs ----------------------- */
/* DEBUG_MEM creates a file and logs all actions to it         */

#ifdef DEBUG_MEM
  static HFILE LogHandle = NULLHANDLE;
  static ULONG Written   = 0;
  static char  log[2048] = "";
  static char  buf[2048] = "";


char*  itoa10( int i, char* buffer ) {
    char*  ptr  = buffer;
    char*  rptr = buffer;
    char   digit;

    if (i == 0) {
      buffer[0] = '0';
      buffer[1] =  0;
      return buffer;
    }

    if (i < 0) {
      *ptr = '-';
       ptr++; rptr++;
       i   = -i;
    }

    while (i != 0) {
      *ptr = (char) (i % 10 + '0');
       ptr++;
       i  /= 10;
    }

    *ptr = 0;  ptr--;

    while (ptr > rptr) {
      digit = *ptr;
      *ptr  = *rptr;
      *rptr = digit;
       ptr--;
       rptr++;
    }

    return buffer;
}

static const char*  hexstr = "0123456789abcdef";

char* itohex2( int i, char* buffer )
 {
  buffer[0] = hexstr[ (i >> 12) & 0xF ];
  buffer[1] = hexstr[ (i >> 8 ) & 0xF ];
  buffer[2] = hexstr[ (i >> 4 ) & 0xF ];
  buffer[3] = hexstr[ (i      ) & 0xF ];
  buffer[4] = '\0';
  return buffer;
}

char* itohex4( long i, char* buffer )
{
  itohex2( (i >> 16) & 0xFFFF, buffer );
  /* We separate the high and low part with a dot to make it */
  /* more readable                                           */
  buffer[4] = '.';
  itohex2( i & 0xFFFF, buffer+5 );
  return buffer;
}

  #define  COPY(s)     strcpy(log, s)
  #define  CAT(s)      strcat(log, s)
  #define  CATI(v)     strcat(log, itoa10( (int)v, buf ))
  #define  CATH(v)     strcat(log, itohex4( (long)v, buf ))
  #define  CATW(v)     strcat(log, itohex2( (short)v, buf ))
  #define  WRITE       DosWrite(LogHandle, log, strlen(log), &Written)
  #define  ERRRET(e)   { COPY("Error at ");  \
                          CATI(__LINE__);    \
                          CAT("\r\n");       \
                          WRITE;             \
                          return(e);         \
                       }

#else

  #define  COPY(s)
  #define  CAT(s)
  #define  CATI(v)
  #define  CATH(v)
  #define  CATW(v)
  #define  WRITE
  #define  ERRRET(e)  return(e);

#endif /* DEBUG_MEM */


#undef  TRACK_MEM
/* TRACK_MEM allows online tracking of memory usage online (via shared */
/* memory). It is used in conjunction with the FTMEM utility.          */

#ifdef TRACK_MEM
   /* name of shared memory used for memory usage reporting  */
   #define MEM_NAME  "\\sharemem\\freetype"

   /* structure containing memory usage information */
   typedef struct _INFOSTRUCT {
      ULONG  signature;      /* signature (0x46524545, 'FREE') */
      ULONG  used;           /* bytes actually used */
      ULONG  maxused;        /* maximum amount ever used */
      ULONG  num_err;        /* number of (de)allocation errors */
   } INFOSTRUCT, *PINFOSTRUCT;

   /* structure (in named shared memory) pointing to the above struct */
   typedef struct _INFOPTR {
      PINFOSTRUCT  address;        /* pointer to actual memory info  */
   } INFOPTR, *PINFOPTR;

   PINFOSTRUCT  meminfo;      /* struct in shared memory holding usage info */
   PINFOPTR     memptr;
#endif

/* -----------------------------------------------------------------

   A brief explanation of the memory allocator :

   - We use an hybrid scheme, separating small blocks ( < 8 Kb )
     from large ones.  The allocator uses one or more heaps of 128 Kb
     each to store the small blocks, while large ones are simply
     allocated directly through SSAllocMem (their size is thus
     padded up to be a multiple of 4 Kb = 1 page).

   - We store the block's size in front of it. The size implies the nature
     of the block, and selects a de-allocation scheme..

   - A note on the memory debugging schemes: logging and online tracking
     are independent of each other and none, either or both may be used.

   ----------------------------------------------------------------- */

  #define PAGE_SIZE          4096
  #define BLOCK_THRESHOLD    (2*PAGE_SIZE)
  #define HEAP_SIZE          (32*4096)      /* Each heap is 128 Kb  */
  #define MAX_HEAPS          128            /* Max 16 Meg of heaps  */

  struct _THeap
  {
    void*  base;  /* heap base                */
    long   free;  /* memory available in heap */
  };

  typedef struct _THeap  THeap;

  static  THeap  heaps[MAX_HEAPS];
  static  int    num_heaps;

  /****************************************************************/
  /*                                                              */
  /* Allocate a block of memory                                   */
  /*                                                              */
  static
  void*  ft2_malloc( long size )
  {
    long*  head;
    void*  base;
    int    h, rc;

    /* add header size */
    size += sizeof(long);

    if (size >= BLOCK_THRESHOLD)
    {
      /* Allocate a large block */
      if (( rc = SSAllocMem( (PVOID)&head, size, 0 )))
      {
        COPY( "ft2_malloc: block SSAllocMem failed with rc = " );
        CATH( rc );
        CAT ( "\r\n" );
        WRITE;
        return NULL;
      }
    }
    else
    {
      /* Allocate a small block in one of the heaps */
      for (h = 0; h < num_heaps; h++)
        if (heaps[h].free >= size)
          if (!DosSubAllocMem( heaps[h].base, (PVOID)&head, size ))
            break;

      if (h >= num_heaps)
      {
        /* There wasn't enough room, allocate a new heap */
        if ( num_heaps >= MAX_HEAPS )
        {
          COPY( "ft2_malloc : Too many heaps defined\r\n" );
          WRITE;
          return NULL;
        }

        if (( rc = SSAllocMem( &heaps[h].base, HEAP_SIZE, 0 )))
        {
          COPY( "ft2_malloc: heap SSAllocMem failed with rc = " );
          CATH( rc );
          CAT ( "\r\n" );
          WRITE;
          return NULL;
        }

        /* create a heap in the memory */
        if ((rc = DosSubSetMem(heaps[h].base, DOSSUB_INIT, HEAP_SIZE)))
        {
          COPY( "ft2_malloc: heap DosSubSetMem failed with rc = " );
          CATH( rc );
          CAT ( "\r\n" );
          WRITE;
          return NULL;
        }

        heaps[h].free = HEAP_SIZE;
        num_heaps++;

        COPY("ft2_malloc: new heap created (num_heaps = " );
        CATI( num_heaps );
        CAT (")\r\n");
        WRITE;

        if ((rc = DosSubAllocMem( heaps[h].base, (PVOID)&head, size )))
        {
          COPY( "ft2_malloc: DosSubAllocMem failed with rc = " );
          CATI( rc );
          CAT ( "\r\n" );
          WRITE;
          return NULL;
        }
      }

      heaps[h].free -= size;
    }
    *head = size;
    base  = (void*)(head + 1);
    #ifdef TRACK_MEM
       meminfo->used += size;
       if (meminfo->used > meminfo->maxused)
          meminfo->maxused = meminfo->used;
    #endif
    return base;
  }

  /****************************************************************/
  /*                                                              */
  /* Release a block of memory                                    */
  /*                                                              */
  int  ft2_free( void* block )
  {
    long*  head;
    long   size, offset;
    int    rc, h;

    if (!block)
      return -1;

    head = ((long*)block) - 1;
    size = *head;

    if (size <= 0)
    {
      COPY( "ft2_free: negative size !!\r\n" );
      WRITE;
      return -1;
    }

    if (size >= BLOCK_THRESHOLD)
    {
      /* the block is a large one */
      rc = SSFreeMem( (PVOID)head );
      if (rc)
      {
        COPY( "ft2_free: block SSFreeMem failed with rc = " );
        CATI( rc );
        CAT ( "\r\n" );
        WRITE;
      }
      #ifdef TRACK_MEM
         meminfo->used -= size;
      #endif
      return rc;
    }

    /* is the block in one of the heaps ? - do a linear search */
    for ( h = 0; h < num_heaps; h++ )
    {
      offset = (long)head - (long)heaps[h].base;
      if (offset >= 0 && offset+size <= HEAP_SIZE)
        break;
    }

    if ( h >= num_heaps )
    {
      COPY( "ft2_free: small block in unknown heap !!\r\n" );
      WRITE;
      return -1;
    }

    /* then release it */
    rc = DosSubFreeMem( heaps[h].base, head, size );
    if (rc)
    {
      COPY( "ft2_free: heap DosSubFreeMem failed with rc = " );
      CATI( rc );
      CAT ( "\r\n" );
      WRITE;
    }
    else {
      heaps[h].free += size;
      #ifdef TRACK_MEM
         meminfo->used -= size;
      #endif
    }
    return rc;
  }

/*******************************************************************
 *
 *  Function    :  TT_Alloc
 *
 *  Description :  Allocates memory from the heap buffer.
 *
 *  Input  :  Size      size of the memory to be allocated
 *            P         pointer to a buffer pointer
 *
 *  Output :  Error code.
 *
 *  NOTE:  The newly allocated block should _always_ be zeroed
 *         on return.  Many parts of the engine rely on this to
 *         work properly.
 *
 ******************************************************************/

  TT_Error  TT_Alloc( long  Size, void**  P )
  {
    if ( Size )
    {
      *P = ft2_malloc( Size );
      if (!*P) {
        #ifdef TRACK_MEM
           meminfo->num_err++;
        #endif
        return TT_Err_Out_Of_Memory;
      }

      MEM_Set( *P, 0, Size);
    }
    else
      *P = NULL;

    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  TT_Free
 *
 *  Description :  Releases a previously allocated block of memory.
 *
 *  Input  :  P    pointer to memory block
 *
 *  Output :  Always SUCCESS.
 *
 *  Note : The pointer must _always_ be set to NULL by this function.
 *
 ******************************************************************/

  TT_Error  TT_Free( void**  P )
  {
    if ( !P || !*P )
      return TT_Err_Ok;

    if (ft2_free( *P )) {
        #ifdef TRACK_MEM
           meminfo->num_err++;
        #endif
    }
    *P = NULL;
    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  TTMemory_Init
 *
 *  Description :  Initializes the memory.
 *
 *  Output :  Always SUCCESS.
 *
 ******************************************************************/

  TT_Error  TTMemory_Init()
  {
    int  rc;

    #ifdef DEBUG_MEM
       ULONG  Action;

       DosOpen("C:\\FTMEM.LOG", &LogHandle, &Action, 0, FILE_NORMAL,
               OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_REPLACE_IF_EXISTS,
               OPEN_FLAGS_NO_CACHE | OPEN_FLAGS_WRITE_THROUGH |
               OPEN_FLAGS_SEQUENTIAL | OPEN_SHARE_DENYWRITE | OPEN_ACCESS_WRITEONLY,
               NULL);

       COPY("FTMEM Init.\r\n");
       WRITE;

       /* Check that "heaps" starts at a dword address */
       if ( (long)&heaps & 3 )
       {
         COPY("ERROR : heaps = " );
         CATI( (long)&heaps );
         CAT ( "\r\n" );
         WRITE;
       }

    #endif /* DEBUG */

    #ifdef TRACK_MEM
    /* allocate named shared memory and global shared memory */

       SSAllocMem(&meminfo, 4096, 0);
       DosAllocSharedMem((PVOID*)&memptr, MEM_NAME, 4096, fALLOC);
       memptr->address = meminfo;
       meminfo->signature = 0x46524545;   /* 'FREE' */
       meminfo->maxused = 0;
       meminfo->used = 0;
    #endif /* TRACK */

    num_heaps = 0;
    return TT_Err_Ok;
  }


/*******************************************************************
 *
 *  Function    :  TTMemory_Done
 *
 *  Description :  Finalizes memory usage.
 *
 *  Output :  Always SUCCESS.
 *
 ******************************************************************/

  TT_Error  TTMemory_Done()
  {
    /* Never called by the font driver (beats me why). We do not
       release the heaps */

    #ifdef TRACK_MEM
       DosFreeMem(memptr);  /* free shared memory */
       SSFreeMem(meminfo);
    #endif
    return TT_Err_Ok;
  }


/* END */
