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

/*                                                                         */

/*  ftcmanag.c                                                             */

/*                                                                         */

/*    FreeType Cache Manager (body).                                       */

/*                                                                         */

/*  Copyright 2000-2001, 2002, 2003, 2004, 2005, 2006, 2008, 2009, 2010 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.                                        */

/*                                                                         */

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





#include <ft2build.h>

#include FT_CACHE_H

#include "ftcmanag.h"

#include FT_INTERNAL_OBJECTS_H

#include FT_INTERNAL_DEBUG_H

#include FT_SIZES_H



#include "ftccback.h"

#include "ftcerror.h"



#ifdef FT_CONFIG_OPTION_PIC

#error "cache system does not support PIC yet"

#endif 





#undef  FT_COMPONENT

#define FT_COMPONENT  trace_cache



#define FTC_LRU_GET_MANAGER( lru )  ( (FTC_Manager)(lru)->user_data )





  static FT_Error

  ftc_scaler_lookup_size( FTC_Manager  manager,

                          FTC_Scaler   scaler,

                          FT_Size     *asize )

  {

    FT_Face   face;

    FT_Size   size = NULL;

    FT_Error  error;





    error = FTC_Manager_LookupFace( manager, scaler->face_id, &face );

    if ( error )

      goto Exit;



    error = FT_New_Size( face, &size );

    if ( error )

      goto Exit;



    FT_Activate_Size( size );



    if ( scaler->pixel )

      error = FT_Set_Pixel_Sizes( face, scaler->width, scaler->height );

    else

      error = FT_Set_Char_Size( face, scaler->width, scaler->height,

                                scaler->x_res, scaler->y_res );

    if ( error )

    {

      FT_Done_Size( size );

      size = NULL;

    }



  Exit:

    *asize = size;

    return error;

  }





  typedef struct  FTC_SizeNodeRec_

  {

    FTC_MruNodeRec  node;

    FT_Size         size;

    FTC_ScalerRec   scaler;



  } FTC_SizeNodeRec, *FTC_SizeNode;



#define FTC_SIZE_NODE( x ) ( (FTC_SizeNode)( x ) )





  FT_CALLBACK_DEF( void )

  ftc_size_node_done( FTC_MruNode  ftcnode,

                      FT_Pointer   data )

  {

    FTC_SizeNode  node = (FTC_SizeNode)ftcnode;

    FT_Size       size = node->size;

    FT_UNUSED( data );





    if ( size )

      FT_Done_Size( size );

  }





  FT_CALLBACK_DEF( FT_Bool )

  ftc_size_node_compare( FTC_MruNode  ftcnode,

                         FT_Pointer   ftcscaler )

  {

    FTC_SizeNode  node    = (FTC_SizeNode)ftcnode;

    FTC_Scaler    scaler  = (FTC_Scaler)ftcscaler;

    FTC_Scaler    scaler0 = &node->scaler;





    if ( FTC_SCALER_COMPARE( scaler0, scaler ) )

    {

      FT_Activate_Size( node->size );

      return 1;

    }

    return 0;

  }





  FT_CALLBACK_DEF( FT_Error )

  ftc_size_node_init( FTC_MruNode  ftcnode,

                      FT_Pointer   ftcscaler,

                      FT_Pointer   ftcmanager )

  {

    FTC_SizeNode  node    = (FTC_SizeNode)ftcnode;

    FTC_Scaler    scaler  = (FTC_Scaler)ftcscaler;

    FTC_Manager   manager = (FTC_Manager)ftcmanager;





    node->scaler = scaler[0];



    return ftc_scaler_lookup_size( manager, scaler, &node->size );

  }





  FT_CALLBACK_DEF( FT_Error )

  ftc_size_node_reset( FTC_MruNode  ftcnode,

                       FT_Pointer   ftcscaler,

                       FT_Pointer   ftcmanager )

  {

    FTC_SizeNode  node    = (FTC_SizeNode)ftcnode;

    FTC_Scaler    scaler  = (FTC_Scaler)ftcscaler;

    FTC_Manager   manager = (FTC_Manager)ftcmanager;





    FT_Done_Size( node->size );



    node->scaler = scaler[0];



    return ftc_scaler_lookup_size( manager, scaler, &node->size );

  }





  FT_CALLBACK_TABLE_DEF

  const FTC_MruListClassRec  ftc_size_list_class =

  {

    sizeof ( FTC_SizeNodeRec ),

    ftc_size_node_compare,

    ftc_size_node_init,

    ftc_size_node_reset,

    ftc_size_node_done

  };





  /* helper function used by ftc_face_node_done */

  static FT_Bool

  ftc_size_node_compare_faceid( FTC_MruNode  ftcnode,

                                FT_Pointer   ftcface_id )

  {

    FTC_SizeNode  node    = (FTC_SizeNode)ftcnode;

    FTC_FaceID    face_id = (FTC_FaceID)ftcface_id;





    return FT_BOOL( node->scaler.face_id == face_id );

  }





  /* documentation is in ftcache.h */



  FT_EXPORT_DEF( FT_Error )

  FTC_Manager_LookupSize( FTC_Manager  manager,

                          FTC_Scaler   scaler,

                          FT_Size     *asize )

  {

    FT_Error     error;

    FTC_MruNode  mrunode;





    if ( asize == NULL )

      return FTC_Err_Invalid_Argument;



    *asize = NULL;



    if ( !manager )

      return FTC_Err_Invalid_Cache_Handle;



#ifdef FTC_INLINE



    FTC_MRULIST_LOOKUP_CMP( &manager->sizes, scaler, ftc_size_node_compare,

                            mrunode, error );



#else

    error = FTC_MruList_Lookup( &manager->sizes, scaler, &mrunode );

#endif



    if ( !error )

      *asize = FTC_SIZE_NODE( mrunode )->size;



    return error;

  }





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

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

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

  /*****                    FACE MRU IMPLEMENTATION                    *****/

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

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

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



  typedef struct  FTC_FaceNodeRec_

  {

    FTC_MruNodeRec  node;

    FTC_FaceID      face_id;

    FT_Face         face;



  } FTC_FaceNodeRec, *FTC_FaceNode;



#define FTC_FACE_NODE( x ) ( ( FTC_FaceNode )( x ) )





  FT_CALLBACK_DEF( FT_Error )

  ftc_face_node_init( FTC_MruNode  ftcnode,

                      FT_Pointer   ftcface_id,

                      FT_Pointer   ftcmanager )

  {

    FTC_FaceNode  node    = (FTC_FaceNode)ftcnode;

    FTC_FaceID    face_id = (FTC_FaceID)ftcface_id;

    FTC_Manager   manager = (FTC_Manager)ftcmanager;

    FT_Error      error;





    node->face_id = face_id;



    error = manager->request_face( face_id,

                                   manager->library,

                                   manager->request_data,

                                   &node->face );

    if ( !error )

    {

      /* destroy initial size object; it will be re-created later */

      if ( node->face->size )

        FT_Done_Size( node->face->size );

    }



    return error;

  }





  FT_CALLBACK_DEF( void )

  ftc_face_node_done( FTC_MruNode  ftcnode,

                      FT_Pointer   ftcmanager )

  {

    FTC_FaceNode  node    = (FTC_FaceNode)ftcnode;

    FTC_Manager   manager = (FTC_Manager)ftcmanager;





    /* we must begin by removing all scalers for the target face */

    /* from the manager's list                                   */

    FTC_MruList_RemoveSelection( &manager->sizes,

                                 ftc_size_node_compare_faceid,

                                 node->face_id );



    /* all right, we can discard the face now */

    FT_Done_Face( node->face );

    node->face    = NULL;

    node->face_id = NULL;

  }





  FT_CALLBACK_DEF( FT_Bool )

  ftc_face_node_compare( FTC_MruNode  ftcnode,

                         FT_Pointer   ftcface_id )

  {

    FTC_FaceNode  node    = (FTC_FaceNode)ftcnode;

    FTC_FaceID    face_id = (FTC_FaceID)ftcface_id;





    return FT_BOOL( node->face_id == face_id );

  }





  FT_CALLBACK_TABLE_DEF

  const FTC_MruListClassRec  ftc_face_list_class =

  {

    sizeof ( FTC_FaceNodeRec),



    ftc_face_node_compare,

    ftc_face_node_init,

    0,                          /* FTC_MruNode_ResetFunc */

    ftc_face_node_done

  };





  /* documentation is in ftcache.h */



  FT_EXPORT_DEF( FT_Error )

  FTC_Manager_LookupFace( FTC_Manager  manager,

                          FTC_FaceID   face_id,

                          FT_Face     *aface )

  {

    FT_Error     error;

    FTC_MruNode  mrunode;





    if ( aface == NULL )

      return FTC_Err_Invalid_Argument;



    *aface = NULL;



    if ( !manager )

      return FTC_Err_Invalid_Cache_Handle;



    /* we break encapsulation for the sake of speed */

#ifdef FTC_INLINE



    FTC_MRULIST_LOOKUP_CMP( &manager->faces, face_id, ftc_face_node_compare,

                            mrunode, error );



#else

    error = FTC_MruList_Lookup( &manager->faces, face_id, &mrunode );

#endif



    if ( !error )

      *aface = FTC_FACE_NODE( mrunode )->face;



    return error;

  }





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

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

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

  /*****                    CACHE MANAGER ROUTINES                     *****/

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

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

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





  /* documentation is in ftcache.h */



  FT_EXPORT_DEF( FT_Error )

  FTC_Manager_New( FT_Library          library,

                   FT_UInt             max_faces,

                   FT_UInt             max_sizes,

                   FT_ULong            max_bytes,

                   FTC_Face_Requester  requester,

                   FT_Pointer          req_data,

                   FTC_Manager        *amanager )

  {

    FT_Error     error;

    FT_Memory    memory;

    FTC_Manager  manager = 0;





    if ( !library )

      return FTC_Err_Invalid_Library_Handle;



    memory = library->memory;



    if ( FT_NEW( manager ) )

      goto Exit;



    if ( max_faces == 0 )

      max_faces = FTC_MAX_FACES_DEFAULT;



    if ( max_sizes == 0 )

      max_sizes = FTC_MAX_SIZES_DEFAULT;



    if ( max_bytes == 0 )

      max_bytes = FTC_MAX_BYTES_DEFAULT;



    manager->library      = library;

    manager->memory       = memory;

    manager->max_weight   = max_bytes;



    manager->request_face = requester;

    manager->request_data = req_data;



    FTC_MruList_Init( &manager->faces,

                      &ftc_face_list_class,

                      max_faces,

                      manager,

                      memory );



    FTC_MruList_Init( &manager->sizes,

                      &ftc_size_list_class,

                      max_sizes,

                      manager,

                      memory );



    *amanager = manager;



  Exit:

    return error;

  }





  /* documentation is in ftcache.h */



  FT_EXPORT_DEF( void )

  FTC_Manager_Done( FTC_Manager  manager )

  {

    FT_Memory  memory;

    FT_UInt    idx;





    if ( !manager || !manager->library )

      return;



    memory = manager->memory;



    /* now discard all caches */

    for (idx = manager->num_caches; idx-- > 0; )

    {

      FTC_Cache  cache = manager->caches[idx];





      if ( cache )

      {

        cache->clazz.cache_done( cache );

        FT_FREE( cache );

        manager->caches[idx] = NULL;

      }

    }

    manager->num_caches = 0;



    /* discard faces and sizes */

    FTC_MruList_Done( &manager->sizes );

    FTC_MruList_Done( &manager->faces );



    manager->library = NULL;

    manager->memory  = NULL;



    FT_FREE( manager );

  }





  /* documentation is in ftcache.h */



  FT_EXPORT_DEF( void )

  FTC_Manager_Reset( FTC_Manager  manager )

  {

    if ( manager )

    {

      FTC_MruList_Reset( &manager->sizes );

      FTC_MruList_Reset( &manager->faces );

    }

    /* XXX: FIXME: flush the caches? */

  }





#ifdef FT_DEBUG_ERROR



  static void

  FTC_Manager_Check( FTC_Manager  manager )

  {

    FTC_Node  node, first;





    first = manager->nodes_list;



    /* check node weights */

    if ( first )

    {

      FT_ULong  weight = 0;





      node = first;



      do

      {

        FTC_Cache  cache = manager->caches[node->cache_index];





        if ( (FT_UInt)node->cache_index >= manager->num_caches )

          FT_TRACE0(( "FTC_Manager_Check: invalid node (cache index = %ld\n",

                      node->cache_index ));

        else

          weight += cache->clazz.node_weight( node, cache );



        node = FTC_NODE__NEXT( node );



      } while ( node != first );



      if ( weight != manager->cur_weight )

        FT_TRACE0(( "FTC_Manager_Check: invalid weight %ld instead of %ld\n",

                    manager->cur_weight, weight ));

    }



    /* check circular list */

    if ( first )

    {

      FT_UFast  count = 0;





      node = first;

      do

      {

        count++;

        node = FTC_NODE__NEXT( node );



      } while ( node != first );



      if ( count != manager->num_nodes )

        FT_TRACE0(( "FTC_Manager_Check:"

                    " invalid cache node count %d instead of %d\n",

                    manager->num_nodes, count ));

    }

  }



#endif /* FT_DEBUG_ERROR */





  /* `Compress' the manager's data, i.e., get rid of old cache nodes */

  /* that are not referenced anymore in order to limit the total     */

  /* memory used by the cache.                                       */



  /* documentation is in ftcmanag.h */



  FT_LOCAL_DEF( void )

  FTC_Manager_Compress( FTC_Manager  manager )

  {

    FTC_Node   node, first;





    if ( !manager )

      return;



    first = manager->nodes_list;



#ifdef FT_DEBUG_ERROR

    FTC_Manager_Check( manager );



    FT_TRACE0(( "compressing, weight = %ld, max = %ld, nodes = %d\n",

                manager->cur_weight, manager->max_weight,

                manager->num_nodes ));

#endif



    if ( manager->cur_weight < manager->max_weight || first == NULL )

      return;



    /* go to last node -- it's a circular list */

    node = FTC_NODE__PREV( first );

    do

    {

      FTC_Node  prev;





      prev = ( node == first ) ? NULL : FTC_NODE__PREV( node );



      if ( node->ref_count <= 0 )

        ftc_node_destroy( node, manager );



      node = prev;



    } while ( node && manager->cur_weight > manager->max_weight );

  }





  /* documentation is in ftcmanag.h */



  FT_LOCAL_DEF( FT_Error )

  FTC_Manager_RegisterCache( FTC_Manager      manager,

                             FTC_CacheClass   clazz,

                             FTC_Cache       *acache )

  {

    FT_Error   error = FTC_Err_Invalid_Argument;

    FTC_Cache  cache = NULL;





    if ( manager && clazz && acache )

    {

      FT_Memory  memory = manager->memory;





      if ( manager->num_caches >= FTC_MAX_CACHES )

      {

        error = FTC_Err_Too_Many_Caches;

        FT_ERROR(( "FTC_Manager_RegisterCache:"

                   " too many registered caches\n" ));

        goto Exit;

      }



      if ( !FT_ALLOC( cache, clazz->cache_size ) )

      {

        cache->manager   = manager;

        cache->memory    = memory;

        cache->clazz     = clazz[0];

        cache->org_class = clazz;



        /* THIS IS VERY IMPORTANT!  IT WILL WRETCH THE MANAGER */

        /* IF IT IS NOT SET CORRECTLY                          */

        cache->index = manager->num_caches;



        error = clazz->cache_init( cache );

        if ( error )

        {

          clazz->cache_done( cache );

          FT_FREE( cache );

          goto Exit;

        }



        manager->caches[manager->num_caches++] = cache;

      }

    }



  Exit:

    if ( acache )

      *acache = cache;

    return error;

  }





  FT_LOCAL_DEF( FT_UInt )

  FTC_Manager_FlushN( FTC_Manager  manager,

                      FT_UInt      count )

  {

    FTC_Node  first = manager->nodes_list;

    FTC_Node  node;

    FT_UInt   result;





    /* try to remove `count' nodes from the list */

    if ( first == NULL )  /* empty list! */

      return 0;



    /* go to last node - it's a circular list */

    node = FTC_NODE__PREV(first);

    for ( result = 0; result < count; )

    {

      FTC_Node  prev = FTC_NODE__PREV( node );





      /* don't touch locked nodes */

      if ( node->ref_count <= 0 )

      {

        ftc_node_destroy( node, manager );

        result++;

      }



      if ( node == first )

        break;



      node = prev;

    }

    return  result;

  }





  /* documentation is in ftcache.h */



  FT_EXPORT_DEF( void )

  FTC_Manager_RemoveFaceID( FTC_Manager  manager,

                            FTC_FaceID   face_id )

  {

    FT_UInt  nn;



    /* this will remove all FTC_SizeNode that correspond to

     * the face_id as well

     */

    FTC_MruList_RemoveSelection( &manager->faces,

                                 ftc_face_node_compare,

                                 face_id );



    for ( nn = 0; nn < manager->num_caches; nn++ )

      FTC_Cache_RemoveFaceID( manager->caches[nn], face_id );

  }





  /* documentation is in ftcache.h */



  FT_EXPORT_DEF( void )

  FTC_Node_Unref( FTC_Node     node,

                  FTC_Manager  manager )

  {

    if ( node && (FT_UInt)node->cache_index < manager->num_caches )

      node->ref_count--;

  }





#ifdef FT_CONFIG_OPTION_OLD_INTERNALS



  FT_EXPORT_DEF( FT_Error )

  FTC_Manager_Lookup_Face( FTC_Manager  manager,

                           FTC_FaceID   face_id,

                           FT_Face     *aface )

  {

    return FTC_Manager_LookupFace( manager, face_id, aface );

  }





  FT_EXPORT( FT_Error )

  FTC_Manager_Lookup_Size( FTC_Manager  manager,

                           FTC_Font     font,

                           FT_Face     *aface,

                           FT_Size     *asize )

  {

    FTC_ScalerRec  scaler;

    FT_Error       error;

    FT_Size        size;

    FT_Face        face;





    scaler.face_id = font->face_id;

    scaler.width   = font->pix_width;

    scaler.height  = font->pix_height;

    scaler.pixel   = TRUE;

    scaler.x_res   = 0;

    scaler.y_res   = 0;



    error = FTC_Manager_LookupSize( manager, &scaler, &size );

    if ( error )

    {

      face = NULL;

      size = NULL;

    }

    else

      face = size->face;



    if ( aface )

      *aface = face;



    if ( asize )

      *asize = size;



    return error;

  }



#endif /* FT_CONFIG_OPTION_OLD_INTERNALS */





/* END */

