/*******************************************************************
 *
 *  ttmmap.c                                                     2.0
 *
 *    Memory-Mapped file component ( replaces ttfile.c ).
 *
 *  Copyright 1996, 1997 by
 *  David Turner, Robert Wilhelm, and Werner Lemberg
 *
 *  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 2.0 and 1.3 :
 *
 *  - adopted new design/separation introduced in ttfile.c 2.0
 *
 ******************************************************************/

#include "ttconfig.h"

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

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <sys/mman.h>
#ifndef MAP_FILE
  #define MAP_FILE  0x00
#endif

/*
 * The prototype for munmap() is not provided on SunOS.  This needs to
 * have a check added later to see if the GNU C library is being used.
 * If so, then this prototype is not needed.
 */
#if defined(__sun__) && !defined(SVR4) && !defined(__SVR4)
  extern int  munmap( caddr_t  addr, int  len );
#endif

#include <sys/stat.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif


#include "freetype.h"
#include "tttypes.h"
#include "tterror.h"
#include "ttengine.h"
#include "ttmutex.h"
#include "ttmemory.h"
#include "ttfile.h"     /* our prototypes */

  /* This definition is mandatory for each file component !! */
  const TFileFrame  TT_Null_FileFrame = { NULL, 0, 0 };
  
  /* It has proven useful to do some bounds checks during   */
  /* development phase. Define DEBUG_FILE when compiling    */
  /* this component to enable them                          */

  #ifdef DEBUG_FILE
    #define CHECK_FRAME(frame, n)                               \
              do {                                              \
                if (frame.cursor+n > frame.address+frame.size)  \
                  Panic("Frame boundary error!\n");             \
              } while (0)
  #else
    #define CHECK_FRAME(frame, n)  \
              do{}while(0)
  #endif

  struct _TFileMap
  {
    char*   base;       /* base address of mapped file       */
    int     refcount;   /* reference count for mmaped region */
    long    size;       /* stream size in file               */
    long    offset;     /* offset in file                    */
  };

  typedef struct _TFileMap  TFileMap;

  #define MAP_Address(map)  (unsigned char*)( (map)->base + (map)->offset )

  /* The stream record structure */
  typedef struct _TStream_Rec
  {
    TFileMap*  map;     /* mapped file description */
    Long       pos ;    /* cursor in mapped file   */
  } TStream_Rec;

  typedef TStream_Rec*  PStream_Rec;

  #define STREAM2REC(x)  ((TStream_Rec*)HANDLE_Val(x))


#ifndef TT_CONFIG_REENTRANT
  
  /*******************************************************************/
  /*******************************************************************/
  /*******************************************************************/
  /****                                                           ****/
  /****  N O N   R E E N T R A N T   I M P L E M E N T A T I O N  ****/
  /****                                                           ****/
  /*******************************************************************/
  /*******************************************************************/
  /*******************************************************************/

  /* The TFile_Component structure holds all the data that was */
  /* previously declared static or global in this component    */
  /*                                                           */
  /* It is accessible through the 'engine.file_component'      */
  /* variable in re-entrant builds, or directly through the    */
  /* static 'files' variable in other builds                   */
  /*                                                           */
  struct _TFile_Component
  {
    TMutex          lock;        /* used by the thread-safe build only */
    PStream_Rec     stream;      /* current stream  */
    TFileFrame      frame;       /* current frame   */
  };

  typedef struct _TFile_Component  TFile_Component;

  #define CUR_Stream   files.stream            /* thread-safe macros */
  #define CUR_Frame    files.frame

  #define STREAM_VARS  /* void */
  #define STREAM_VAR   /* void */
  
  static TFile_Component  files;
  /* the 'files' variable is only defined in non-reentrant builds */

  /* The macro CUR_Stream denotes the current input stream              */
  /* Note that for the re-entrant version, the 'stream' name has been   */
  /* chosen according to the macro STREAM_ARGS                          */

  /* The macro CUR_Frame denotes the current file frame               */
  /* Note that for the re-entrant version, the 'frame' name has been  */
  /* chosen according to the macro FRAME_ARGS                         */

  /* The macro STREAM_VAR is used when calling public functions */
  /* that need an 'optional' stream argument                    */

/*******************************************************************
 *
 *  Function    :  TTFile_Init
 *
 *  Description :  Init the File component
 *
 ******************************************************************/

 TT_Error TTFile_Init( PEngine_Instance  engine )
 {
   MUTEX_Create( files.lock );
   files.stream = NULL;
   ZERO_Frame( files.frame );
   return TT_Err_Ok;
 }

/*******************************************************************
 *
 *  Function    :  TTFile_Done
 *
 *  Description :  Finalize the File component
 *
 ******************************************************************/

 TT_Error  TTFile_Done( PEngine_Instance  engine )
 {
   MUTEX_Destroy( files.lock );
   return TT_Err_Ok;
 }

/*******************************************************************
 *
 *  Function    : TT_Use_Stream
 *
 *  Description : copy or duplicate a given stream
 *
 *  Input  :  org_stream   original stream
 *            stream       target stream ( copy or duplicate )
 *
 *  Output :  Error code
 *
 ******************************************************************/

  TT_Error  TT_Use_Stream( TT_Stream   org_stream,
                           TT_Stream*  stream )
  {
    MUTEX_Lock( files.lock );
    *stream = org_stream;
    files.stream = STREAM2REC(org_stream);  /* set current stream */
    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    : TT_Done_Stream
 *
 *  Description : copy or duplicate a given stream
 *
 *  Input  :  org_stream   original stream
 *            stream       target stream ( copy or duplicate )
 *
 *  Output :  Error code
 *
 ******************************************************************/

 TT_Error  TT_Done_Stream( TT_Stream*  stream )
 {
   HANDLE_Set( *stream, NULL );
   MUTEX_Release( files.lock );

   return TT_Err_Ok;
 }

#else /* REENTRANT */

  /*******************************************************************/
  /*******************************************************************/
  /*******************************************************************/
  /********                                                   ********/
  /********  R E E N T R A N T   I M P L E M E N T A T I O N  ********/
  /********                                                   ********/
  /*******************************************************************/
  /*******************************************************************/
  /*******************************************************************/

  #define CUR_Stream   STREAM2REC(stream)      /* re-entrant macros */
  #define CUR_Frame    (*frame)

  #define STREAM_VARS  stream,
  #define STREAM_VAR   stream


/*******************************************************************
 *
 *  Function    :  TTFile_Init
 *
 *  Description :  Init the File component
 *
 ******************************************************************/

 TT_Error TTFile_Init( PEngine_Instance  engine )
 {
   engine.file_component = NULL;
   return TT_Err_Ok;
 }

/*******************************************************************
 *
 *  Function    :  TTFile_Done
 *
 *  Description :  Finalize the File component
 *
 ******************************************************************/

 TT_Error  TTFile_Done( PEngine_Instance  engine )
 {
   return TT_Err_Ok;
 }

/*******************************************************************
 *
 *  Function    : TT_Use_Stream
 *
 *  Description : copy or duplicate a given stream
 *
 *  Input  :  org_stream   original stream
 *            stream       target stream ( copy or duplicate )
 *
 *  Output :  Error code. The output stream is set to NULL in
 *            case of Failure
 *
 ******************************************************************/

  TT_Error  TT_Use_Stream( TT_Stream   input_stream,
                           TT_Stream*  copy )
  {
    TT_Error     error;
    PStream_Rec  stream_rec;
    PStream_Rec  copy_rec;

    stream_rec = STREAM2REC(input_stream);

    if ( ALLOC( copy_rec, sizeof(TStream_Rec) ) )
      goto Fail;

    HANDLE_Set( *copy, copy_rec );

    copy_rec->map->refcount++;
    copy_rec->pos = 0;

    return TT_Err_Ok;
  Fail:
    HANDLE_Set( *copy, NULL );
    return error;
  }

/*******************************************************************
 *
 *  Function    : TT_Done_Stream
 *
 *  Description : copy or duplicate a given stream
 *
 *  Input  :  org_stream   original stream
 *            stream       target stream ( copy or duplicate )
 *
 *  Output :  error code
 *
 ******************************************************************/

 TT_Error  TT_Done_Stream( TT_Stream*  stream )
 {
   return TT_Close_Stream(stream);
 }

#endif /* REENTRANT */

  /*******************************************************************/
  /*******************************************************************/
  /*******************************************************************/
  /***********                                             ***********/
  /***********  C O M M O N   I M P L E M E N T A T I O N  ***********/
  /***********                                             ***********/
  /*******************************************************************/
  /*******************************************************************/
  /*******************************************************************/

/*******************************************************************
 *
 *  Function    :  AllocateMap
 *
 *  Description :  Allocate a new map from the table       
 *
 *  Output :  pointer to new stream rec. NULL in case of failure
 *
 ******************************************************************/

  static 
  TFileMap* Allocate_Map()
  {
    TFileMap* result;

    if ( MEM_Alloc( result, sizeof(TFileMap) ))
      return NULL;

    result->refcount = 1;
    return result;
  }

/*******************************************************************
 *
 *  Function    :  ReleaseMap
 *
 *  Description :  Release a used map to the table if reference i
 *                 counter reaches zero
 *
 *  Input  :  map
 *
 *  Output :  None.
 *
 *  Note : Called by TT_Close_File
 *
 ******************************************************************/

  static 
  void Release_Map ( TFileMap* map )
  {
    map->refcount--;
    if ( map->refcount <= 0 )
    {
      munmap ( (char*)map->base, map->size );
      FREE( map );
    }
  }


/*******************************************************************
 *
 *  Function    :  TT_Open_Stream
 *
 *  Description :  opens the font file and saves the total file size.
 *
 *  Input  :  error          address of stream's error variable
 *                           ( re-entrant build only ).
 *
 *            filepathname   pathname of the file to open
 *            stream         address of target TT_Stream structure
 *
 *
 *  Output :  SUCCESS on sucess, FAILURE on error.
 *            The target stream is set to -1 in case of failure.
 *
 *
 ******************************************************************/

  TT_Error  TT_Open_Stream( const char*  filepathname, 
                            TT_Stream*   stream )
  {
    TT_Error     error;
    int          file;
    PStream_Rec  stream_rec;
    TFileMap*    map;
    struct stat  stat_buf;

    if ( ALLOC( *stream, sizeof(TStream_Rec) ) )
      return error;

    map = Allocate_Map();
    if ( !map )
    {
      error = TT_Err_Out_Of_Memory;
      goto Memory_Fail;
    }

    stream_rec = STREAM2REC(*stream);

    file = open( filepathname, O_RDONLY );
    if ( file < 0 )
      goto File_Fail;

    if ( fstat( file, &stat_buf ) < 0 )
      goto Map_Fail;

    map->offset = 0;
    map->size   = stat_buf.st_size + map->offset;
    map->base   = mmap( NULL,
                        map->size,
                        PROT_READ,
                        MAP_FILE | MAP_PRIVATE,
                        file,
                        0 );

    if ( (long)map->base == -1 )
      goto Map_Fail;

    close( file );

    stream_rec->map = map;
    stream_rec->pos = 0;

    #ifndef TT_CONFIG_REENTRANT
    CUR_Stream = stream_rec;
    #endif

    return TT_Err_Ok;

  Map_Fail :
    close(file);

  File_Fail :
    error = TT_Err_Could_Not_Open_File;
    FREE( map );

  Memory_Fail :
    FREE( *stream );
    return error;
  }

/*******************************************************************
 *
 *  Function    : TT_Close_Stream
 *
 *  Description : Closes a stream.
 *
 *  Input  :  
 *
 *  Output :  SUCCESS (always)
 *
 ******************************************************************/

  TT_Error  TT_Close_Stream( TT_Stream*  stream )
  {
    PStream_Rec  rec = STREAM2REC(*stream);

    Release_Map( rec->map );
    FREE( rec );

    HANDLE_Set( *stream, NULL );
    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    : TT_Flush_Stream
 *
 *  Description : Flushes a stream, i.e. closes its file handle
 *
 *  Input  :  stream         address of target TT_Stream structure
 *
 *  Output :  Error code
 *
 *  NOTE : Never flush the current opened stream. This means that
 *         you should _never_ call this function between a
 *         TT_Use_Stream and a TT_Done_Stream !!
 *
 ******************************************************************/
 
  TT_Error  TT_Flush_Stream( TT_Stream*  stream )
  {
    /* XXX - DUMMY IMPLEMENTATION */
    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    :  TT_Stream_Size
 *
 *  Description :  Returns the length of a given stream, even if it  
 *                 is flushed.
 *
 *  Input  :  stream     the stream
 *
 *  Output :  lenght of stream in bytes.            
 *
 ******************************************************************/

  Long  TT_Stream_Size( TT_Stream  stream )
  {
    PStream_Rec  rec = STREAM2REC( stream );
    if (rec)
      return rec->map->size;
    else
      return 0;  /* invalid stream - return 0 */
  }

/*******************************************************************
 *
 *  Function    : TT_Seek_File
 *
 *  Description : Seeks the file cursor to a different position.
 *
 *  Input  :  position     new position in file
 *
 *  Output :  SUCCESS on success. FAILURE if out of range.
 *
 ******************************************************************/

  TT_Error  TT_Seek_File( STREAM_ARGS long position )
  {
    if ( position > CUR_Stream->map->size )
      return TT_Err_Invalid_File_Offset;

    CUR_Stream->pos = position;

    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    : TT_Skip_File
 *
 *  Description : Skips forward the file cursor.
 *
 *  Input  :  distance    number of bytes to skip
 *
 *  Output :  see TT_Seek_File
 *
 ******************************************************************/

  TT_Error  TT_Skip_File( STREAM_ARGS long distance )
  {
    return TT_Seek_File( STREAM_VARS CUR_Stream->pos + distance );
  }

/*******************************************************************
 *
 *  Function    : TT_Read_File
 *
 *  Description : Reads a chunk of the file and copies it to memory.
 *
 *  Input  :  buffer    target buffer
 *            count     length in bytes to read
 *
 *  Output :  SUCCESS on success. FAILURE if out of range.
 *
 ******************************************************************/

  TT_Error  TT_Read_File( STREAM_ARGS void* buffer, long count )
  {
    if ( CUR_Stream->pos + count > CUR_Stream->map->size )
      return TT_Err_Invalid_File_Read;

    MEM_Copy( buffer, MAP_Address(CUR_Stream->map) + CUR_Stream->pos, count );
    CUR_Stream->pos += count;

    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    : TT_Read_At_File
 *
 *  Description : Reads file at a specified position.
 *
 *  Input  :  position  position to seek to before read
 *            buffer    target buffer
 *            count     number of bytes to read
 *
 *  Output :  SUCCESS on success. FAILURE if error.
 *
 ******************************************************************/

  TT_Error  TT_Read_At_File( STREAM_ARGS long   position, 
                                         void*  buffer, 
                                         long   count )
  {
    TT_Error  error;
    if ( (error = TT_Seek_File( STREAM_VARS position ))      || 
         (error = TT_Read_File( STREAM_VARS buffer, count )) )
      return error;

    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    :  TT_File_Pos
 *
 *  Description :  Returns current file seek pointer.
 *
 *  Input  :  none
 *
 *  Output :  current file position
 *
 ******************************************************************/

  Long  TT_File_Pos( STREAM_ARG )
  {
    return CUR_Stream->pos;
  }

/*******************************************************************
 *
 *  Function    :  TT_Access_Frame
 *
 *  Description :  Notifies the component that we're going to read
 *                 'size' bytes from the current file position.
 *                 This function should load/cache/map these bytes
 *                 so that they will be addressed by the GET_xxx
 *                 functions easily.
 *
 *  Input  :  size   number of bytes to access.
 *
 *  Output :  Error code
 *
 *  Notes:    The function fails if the byte range is not within the
 *            the file, or if there is not enough memory to cache
 *            the bytes properly (which usually means that aSize is
 *            too big in both cases).
 *
 ******************************************************************/

  TT_Error  TT_Access_Frame( STREAM_ARGS FRAME_ARGS int size )
  {
    if ( CUR_Frame.address != NULL )
      return TT_Err_Nested_Frame_Access;

    if ( CUR_Stream->pos + size > CUR_Stream->map->size )
      return TT_Err_Invalid_Frame_Access;

    CUR_Frame.size    = size;
    CUR_Frame.address = MAP_Address( CUR_Stream->map ) + CUR_Stream->pos;
    CUR_Frame.cursor  = CUR_Frame.address;

    CUR_Stream->pos += size;

    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    :  TT_Check_And_Access_Frame
 *
 *  Description :  Notifies the component that we're going to read
 *                 aSize bytes from the current file position.
 *                 This function should load/cache/map these bytes
 *                 so that they will be addressed by the GET_xxx
 *                 functions easily.
 *
 *  Input  :  size   number of bytes to access.
 *
 *  Output :  Error code
 *
 *  Notes:    The function truncates aSize if the byte range is not 
 *            within the file.
 *
 *            It will fail if there is not enough memory to cache
 *            the bytes properly (which usually means that aSize is
 *            too big).
 *
 *            It will fail if you make two consecutive calls
 *            to TT_Access_Frame(), without a TT_Forget_Frame() between
 *            them.
 *
 *            The only difference with TT_Access_Frame() is that we
 *            check that the frame is within the current file.  We
 *            otherwise truncate it. The 'overflow' is set to zero
 *
 ******************************************************************/

  TT_Error  TT_Check_And_Access_Frame( STREAM_ARGS FRAME_ARGS int size )
  {
    TT_Error  error;
    Long      readBytes;

    if ( CUR_Frame.address != NULL )
      return TT_Err_Nested_Frame_Access;

    readBytes = CUR_Stream->map->size - CUR_Stream->pos;
    if (size > readBytes)
    {
      /* There is overflow, we allocate a new block then */
      if ( ALLOC( CUR_Frame.address, size ) )
        return error;

      CUR_Frame.size = size;

      /* copy the valid part */
      MEM_Copy( CUR_Frame.address, 
                MAP_Address(CUR_Stream->map) + CUR_Stream->pos,
                readBytes );
    }
    else
    {
      CUR_Frame.size    = size;
      CUR_Frame.address = MAP_Address( CUR_Stream->map ) + CUR_Stream->pos;
    }

    CUR_Frame.cursor = CUR_Frame.address;
    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    :  TT_Forget_Frame
 *
 *  Description :  Releases a cached frame after reading.
 *
 *  Input  :  None
 *
 *  Output :  SUCCESS on success. FAILURE on error.
 *
 ******************************************************************/

  TT_Error  TT_Forget_Frame( FRAME_ARG )
  {
    if ( CUR_Frame.address == NULL )
      return TT_Err_Nested_Frame_Access;

    /* If we were using a duplicate in case of overflow, free it now */
    if ( CUR_Frame.address < (unsigned char *) CUR_Stream->map->base ||
         CUR_Frame.address >= (unsigned char *) CUR_Stream->map->base +
         CUR_Stream->map->size )
      FREE( CUR_Frame.address );

    ZERO_Frame( files.frame );
    return TT_Err_Ok;
  }

/*******************************************************************
 *
 *  Function    :  GET_Byte
 *
 *  Description :  Extracts a byte from the current file frame.
 *
 *  Input  :  None or current frame
 *
 *  Output :  Extracted Byte
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call.)
 *
 ******************************************************************/

  unsigned char TT_Get_Byte( FRAME_ARG )
  {
    CHECK_FRAME(CUR_Frame, 1);

    return (unsigned char)(*CUR_Frame.cursor++);
  }


/*******************************************************************
 *
 *  Function    :  GET_Char
 *
 *  Description :  Extracts a signed byte from the current file frame.
 *
 *  Input  :  None or current frame
 *
 *  Output :  Extracted char
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call.)
 *
 ******************************************************************/

  char TT_Get_Char( FRAME_ARG )
  {
    CHECK_FRAME(CUR_Frame, 1);

    return (char)(*CUR_Frame.cursor++);
  }

/*******************************************************************
 *
 *  Function    :  GET_Short
 *
 *  Description :  Extracts a short from the current file frame.
 *
 *  Input  :  None or current frame
 *
 *  Output :  Extracted short
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call.)
 *
 ******************************************************************/

  short  TT_Get_Short( FRAME_ARG )
  {
    short  getshort;

    CHECK_FRAME(CUR_Frame, 2);

    getshort = ((short)CUR_Frame.cursor[0] << 8) |
                (short)CUR_Frame.cursor[1];

    CUR_Frame.cursor += 2;

    return getshort;
  }


/*******************************************************************
 *
 *  Function    :  GET_UShort
 *
 *  Description :  Extracts an unsigned short from the frame.
 *
 *  Input  :  None or current frame
 *
 *  Output :  Extracted ushort
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call.)
 *
 ******************************************************************/

  unsigned short  TT_Get_UShort( FRAME_ARG )
  {
    unsigned short  getshort;

    CHECK_FRAME(CUR_Frame, 2);

    getshort = ((unsigned short)CUR_Frame.cursor[0] << 8) |
                (unsigned short)CUR_Frame.cursor[1];

    CUR_Frame.cursor += 2;

    return getshort;
  }


/*******************************************************************
 *
 *  Function    :  GET_Long
 *
 *  Description :  Extracts a long from the frame.
 *
 *  Input  :  None or current frame
 *
 *  Output :  Extracted long
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call.)
 *
 ******************************************************************/

  long TT_Get_Long( FRAME_ARG )
  {
    long  getlong;

    CHECK_FRAME(CUR_Frame, 4);

    getlong = ((long)CUR_Frame.cursor[0] << 24) |
              ((long)CUR_Frame.cursor[1] << 16) |
              ((long)CUR_Frame.cursor[2] << 8 ) |
               (long)CUR_Frame.cursor[3];

    CUR_Frame.cursor += 4;

    return getlong;
  }


/*******************************************************************
 *
 *  Function    :  GET_ULong
 *
 *  Description :  Extracts an unsigned long from the frame.
 *
 *  Input  :  None
 *
 *  Output :  Extracted ulong
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call.)
 *
 ******************************************************************/

  unsigned long TT_Get_ULong( FRAME_ARG )
  {
    unsigned long  getlong;

    CHECK_FRAME(CUR_Frame, 4);

    getlong = ( ((unsigned long)CUR_Frame.cursor[0] << 24) |
                ((unsigned long)CUR_Frame.cursor[1] << 16) |
                ((unsigned long)CUR_Frame.cursor[2] << 8 ) |
                 (unsigned long)CUR_Frame.cursor[3] );

    CUR_Frame.cursor += 4;

    return getlong;
  }


/* END */
