/***************************************************************************/
/*                                                                         */
/*  ftcmanag.c                                                             */
/*                                                                         */
/*    FreeType Cache Manager (body).                                       */
/*                                                                         */
/*  Copyright 2000-2001, 2002 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 FT_CACHE_MANAGER_H
#include FT_CACHE_INTERNAL_LRU_H
#include FT_INTERNAL_OBJECTS_H
#include FT_INTERNAL_DEBUG_H
#include FT_SIZES_H

#include "ftcerror.h"


#undef  FT_COMPONENT
#define FT_COMPONENT  trace_cache

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


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                    FACE LRU IMPLEMENTATION                    *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  typedef struct FTC_FaceNodeRec_*  FTC_FaceNode;
  typedef struct FTC_SizeNodeRec_*  FTC_SizeNode;


  typedef struct  FTC_FaceNodeRec_
  {
    FT_LruNodeRec  lru;
    FT_Face        face;

  } FTC_FaceNodeRec;


  typedef struct  FTC_SizeNodeRec_
  {
    FT_LruNodeRec  lru;
    FT_Size        size;

  } FTC_SizeNodeRec;


  FT_CALLBACK_DEF( FT_Error )
  ftc_face_node_init( FTC_FaceNode  node,
                      FTC_FaceID    face_id,
                      FTC_Manager   manager )
  {
    FT_Error  error;


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


  /* helper function for ftc_face_node_done() */
  FT_CALLBACK_DEF( FT_Bool )
  ftc_size_node_select( FTC_SizeNode  node,
                        FT_Face       face )
  {
    return FT_BOOL( node->size->face == face );
  }


  FT_CALLBACK_DEF( void )
  ftc_face_node_done( FTC_FaceNode  node,
                      FTC_Manager   manager )
  {
    FT_Face  face    = node->face;


    /* we must begin by removing all sizes for the target face */
    /* from the manager's list                                 */
    FT_LruList_Remove_Selection( manager->sizes_list,
                                 (FT_LruNode_SelectFunc)ftc_size_node_select,
                                 face );

    /* all right, we can discard the face now */
    FT_Done_Face( face );
    node->face = NULL;
  }


  FT_CALLBACK_TABLE_DEF
  const FT_LruList_ClassRec  ftc_face_list_class =
  {
    sizeof ( FT_LruListRec ),
    (FT_LruList_InitFunc)0,
    (FT_LruList_DoneFunc)0,

    sizeof ( FTC_FaceNodeRec ),
    (FT_LruNode_InitFunc)   ftc_face_node_init,
    (FT_LruNode_DoneFunc)   ftc_face_node_done,
    (FT_LruNode_FlushFunc)  0,  /* no flushing needed                      */
    (FT_LruNode_CompareFunc)0,  /* direct comparison of FTC_FaceID handles */
  };


  /* documentation is in ftcache.h */

  FT_EXPORT_DEF( FT_Error )
  FTC_Manager_Lookup_Face( FTC_Manager  manager,
                           FTC_FaceID   face_id,
                           FT_Face     *aface )
  {
    FT_Error      error;
    FTC_FaceNode  node;


    if ( aface == NULL )
      return FTC_Err_Bad_Argument;

    *aface = NULL;

    if ( !manager )
      return FTC_Err_Invalid_Cache_Handle;

    error = FT_LruList_Lookup( manager->faces_list,
                               (FT_LruKey)face_id,
                               (FT_LruNode*)(void*)&node );
    if ( !error )
      *aface = node->face;

    return error;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                      SIZES LRU IMPLEMENTATION                 *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  typedef struct  FTC_SizeQueryRec_
  {
    FT_Face  face;
    FT_UInt  width;
    FT_UInt  height;

  } FTC_SizeQueryRec, *FTC_SizeQuery;


  FT_CALLBACK_DEF( FT_Error )
  ftc_size_node_init( FTC_SizeNode   node,
                      FTC_SizeQuery  query )
  {
    FT_Face   face = query->face;
    FT_Size   size;
    FT_Error  error;


    node->size = NULL;
    error = FT_New_Size( face, &size );
    if ( !error )
    {
      FT_Activate_Size( size );
      error = FT_Set_Pixel_Sizes( query->face,
                                  query->width,
                                  query->height );
      if ( error )
        FT_Done_Size( size );
      else
        node->size = size;
    }
    return error;
  }


  FT_CALLBACK_DEF( void )
  ftc_size_node_done( FTC_SizeNode  node )
  {
    if ( node->size )
    {
      FT_Done_Size( node->size );
      node->size = NULL;
    }
  }


  FT_CALLBACK_DEF( FT_Error )
  ftc_size_node_flush( FTC_SizeNode   node,
                       FTC_SizeQuery  query )
  {
    FT_Size   size = node->size;
    FT_Error  error;


    if ( size->face == query->face )
    {
      FT_Activate_Size( size );
      error = FT_Set_Pixel_Sizes( query->face, query->width, query->height );
      if ( error )
      {
        FT_Done_Size( size );
        node->size = NULL;
      }
    }
    else
    {
      FT_Done_Size( size );
      node->size = NULL;

      error = ftc_size_node_init( node, query );
    }
    return error;
  }


  FT_CALLBACK_DEF( FT_Bool )
  ftc_size_node_compare( FTC_SizeNode   node,
                         FTC_SizeQuery  query )
  {
    FT_Size  size = node->size;


    return FT_BOOL( size->face                    == query->face   &&
                    (FT_UInt)size->metrics.x_ppem == query->width  &&
                    (FT_UInt)size->metrics.y_ppem == query->height );
  }


  FT_CALLBACK_TABLE_DEF
  const FT_LruList_ClassRec  ftc_size_list_class =
  {
    sizeof ( FT_LruListRec ),
    (FT_LruList_InitFunc)0,
    (FT_LruList_DoneFunc)0,

    sizeof ( FTC_SizeNodeRec ),
    (FT_LruNode_InitFunc)   ftc_size_node_init,
    (FT_LruNode_DoneFunc)   ftc_size_node_done,
    (FT_LruNode_FlushFunc)  ftc_size_node_flush,
    (FT_LruNode_CompareFunc)ftc_size_node_compare
  };


  /* documentation is in ftcache.h */

  FT_EXPORT_DEF( FT_Error )
  FTC_Manager_Lookup_Size( FTC_Manager  manager,
                           FTC_Font     font,
                           FT_Face     *aface,
                           FT_Size     *asize )
  {
    FT_Error  error;


    /* check for valid `manager' delayed to FTC_Manager_Lookup_Face() */
    if ( aface )
      *aface = 0;

    if ( asize )
      *asize = 0;

    error = FTC_Manager_Lookup_Face( manager, font->face_id, aface );
    if ( !error )
    {
      FTC_SizeQueryRec  query;
      FTC_SizeNode      node;


      query.face   = *aface;
      query.width  = font->pix_width;
      query.height = font->pix_height;

      error = FT_LruList_Lookup( manager->sizes_list,
                                 (FT_LruKey)&query,
                                 (FT_LruNode*)(void*)&node );
      if ( !error )
      {
        /* select the size as the current one for this face */
        FT_Activate_Size( node->size );

        if ( asize )
          *asize = node->size;
      }
    }

    return error;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                    SET TABLE MANAGEMENT                       *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/

  static void
  ftc_family_table_init( FTC_FamilyTable  table )
  {
    table->count   = 0;
    table->size    = 0;
    table->entries = NULL;
    table->free    = FTC_FAMILY_ENTRY_NONE;
  }


  static void
  ftc_family_table_done( FTC_FamilyTable  table,
                         FT_Memory        memory )
  {
    FT_FREE( table->entries );
    table->free  = 0;
    table->count = 0;
    table->size  = 0;
  }


  FT_EXPORT_DEF( FT_Error )
  ftc_family_table_alloc( FTC_FamilyTable   table,
                          FT_Memory         memory,
                          FTC_FamilyEntry  *aentry )
  {
    FTC_FamilyEntry  entry;
    FT_Error         error = 0;


    /* re-allocate table size when needed */
    if ( table->free == FTC_FAMILY_ENTRY_NONE && table->count >= table->size )
    {
      FT_UInt  old_size = table->size;
      FT_UInt  new_size, idx;


      if ( old_size == 0 )
        new_size = 8;
      else
      {
        new_size = old_size * 2;

        /* check for (unlikely) overflow */
        if ( new_size < old_size )
          new_size = 65534;
      }

      if ( FT_RENEW_ARRAY( table->entries, old_size, new_size ) )
        return error;

      table->size = new_size;

      entry       = table->entries + old_size;
      table->free = old_size;

      for ( idx = old_size; idx + 1 < new_size; idx++, entry++ )
      {
        entry->link  = idx + 1;
        entry->index = idx;
      }

      entry->link  = FTC_FAMILY_ENTRY_NONE;
      entry->index = idx;
    }

    if ( table->free != FTC_FAMILY_ENTRY_NONE )
    {
      entry       = table->entries + table->free;
      table->free = entry->link;
    }
    else if ( table->count < table->size )
    {
      entry = table->entries + table->count++;
    }
    else
    {
      FT_ERROR(( "ftc_family_table_alloc: internal bug!" ));
      return FTC_Err_Invalid_Argument;
    }

    entry->link = FTC_FAMILY_ENTRY_NONE;
    table->count++;

    *aentry = entry;
    return error;
  }


  FT_EXPORT_DEF( void )
  ftc_family_table_free( FTC_FamilyTable  table,
                         FT_UInt          idx )
  {
    /* simply add it to the linked list of free entries */
    if ( idx < table->count )
    {
      FTC_FamilyEntry  entry = table->entries + idx;


      if ( entry->link != FTC_FAMILY_ENTRY_NONE )
        FT_ERROR(( "ftc_family_table_free: internal bug!\n" ));
      else
      {
        entry->link = table->free;
        table->free = entry->index;
        table->count--;
      }
    }
  }


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

    error = FT_LruList_New( &ftc_face_list_class,
                            max_faces,
                            manager,
                            memory,
                            &manager->faces_list );
    if ( error )
      goto Exit;

    error = FT_LruList_New( &ftc_size_list_class,
                            max_sizes,
                            manager,
                            memory,
                            &manager->sizes_list );
    if ( error )
      goto Exit;

    manager->library      = library;
    manager->max_weight   = max_bytes;
    manager->cur_weight   = 0;

    manager->request_face = requester;
    manager->request_data = req_data;

    ftc_family_table_init( &manager->families );

    *amanager = manager;

  Exit:
    if ( error && manager )
    {
      FT_LruList_Destroy( manager->faces_list );
      FT_LruList_Destroy( manager->sizes_list );
      FT_FREE( manager );
    }

    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->library->memory;

    /* now discard all caches */
    for (idx = 0; idx < FTC_MAX_CACHES; idx++ )
    {
      FTC_Cache  cache = manager->caches[idx];


      if ( cache )
      {
        cache->clazz->cache_done( cache );
        FT_FREE( cache );
        manager->caches[idx] = 0;
      }
    }

    /* discard families table */
    ftc_family_table_done( &manager->families, memory );

    /* discard faces and sizes */
    FT_LruList_Destroy( manager->faces_list );
    manager->faces_list = 0;

    FT_LruList_Destroy( manager->sizes_list );
    manager->sizes_list = 0;

    FT_FREE( manager );
  }


  /* documentation is in ftcache.h */

  FT_EXPORT_DEF( void )
  FTC_Manager_Reset( FTC_Manager  manager )
  {
    if ( manager )
    {
      FT_LruList_Reset( manager->sizes_list );
      FT_LruList_Reset( manager->faces_list );
    }
    /* XXX: FIXME: flush the caches? */
  }


#ifdef FT_DEBUG_ERROR

  FT_EXPORT_DEF( 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_FamilyEntry  entry = manager->families.entries + node->fam_index;
        FTC_Cache     cache;

        if ( (FT_UInt)node->fam_index >= manager->families.count ||
             entry->link              != FTC_FAMILY_ENTRY_NONE  )
          FT_ERROR(( "FTC_Manager_Check: invalid node (family index = %ld\n",
                     node->fam_index ));
        else
        {
          cache   = entry->cache;
          weight += cache->clazz->node_weight( node, cache );
        }

        node = node->mru_next;

      } while ( node != first );

      if ( weight != manager->cur_weight )
        FT_ERROR(( "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 = node->mru_next;

      } while ( node != first );

      if ( count != manager->num_nodes )
        FT_ERROR((
          "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_EXPORT_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_ERROR(( "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 = first->mru_prev;
    do
    {
      FTC_Node  prev = node->mru_prev;


      prev = ( node == first ) ? NULL : node->mru_prev;

      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_EXPORT_DEF( FT_Error )
  FTC_Manager_Register_Cache( FTC_Manager      manager,
                              FTC_Cache_Class  clazz,
                              FTC_Cache       *acache )
  {
    FT_Error   error = FTC_Err_Invalid_Argument;
    FTC_Cache  cache = NULL;


    if ( manager && clazz && acache )
    {
      FT_Memory  memory = manager->library->memory;
      FT_UInt    idx  = 0;


      /* check for an empty cache slot in the manager's table */
      for ( idx = 0; idx < FTC_MAX_CACHES; idx++ )
      {
        if ( manager->caches[idx] == 0 )
          break;
      }

      /* return an error if there are too many registered caches */
      if ( idx >= FTC_MAX_CACHES )
      {
        error = FTC_Err_Too_Many_Caches;
        FT_ERROR(( "FTC_Manager_Register_Cache:" ));
        FT_ERROR(( " too many registered caches\n" ));
        goto Exit;
      }

      if ( !FT_ALLOC( cache, clazz->cache_size ) )
      {
        cache->manager = manager;
        cache->memory  = memory;
        cache->clazz   = clazz;

        /* THIS IS VERY IMPORTANT!  IT WILL WRETCH THE MANAGER */
        /* IF IT IS NOT SET CORRECTLY                          */
        cache->cache_index = idx;

        if ( clazz->cache_init )
        {
          error = clazz->cache_init( cache );
          if ( error )
          {
            if ( clazz->cache_done )
              clazz->cache_done( cache );

            FT_FREE( cache );
            goto Exit;
          }
        }

        manager->caches[idx] = cache;
      }
    }

  Exit:
    *acache = cache;
    return error;
  }


  /* documentation is in ftcmanag.h */

  FT_EXPORT_DEF( void )
  FTC_Node_Unref( FTC_Node     node,
                  FTC_Manager  manager )
  {
    if ( node && (FT_UInt)node->fam_index < manager->families.count &&
         manager->families.entries[node->fam_index].cache )
    {
      node->ref_count--;
    }
  }


/* END */
