/***************************************************************************/
/*                                                                         */
/*  ftcglyph.c                                                             */
/*                                                                         */
/*    FreeType Glyph Image (FT_Glyph) cache (body).                        */
/*                                                                         */
/*  Copyright 2000 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 <freetype/cache/ftcglyph.h>
#include <freetype/fterrors.h>
#include <freetype/ftlist.h>
#include <freetype/internal/ftobjs.h>
#include <freetype/internal/ftdebug.h>


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                      GLYPH NODES                              *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  /* create a new glyph node, setting its cache index and ref count */
  FT_EXPORT_DEF( void )  FTC_GlyphNode_Init( FTC_GlyphNode  node,
                                             FTC_GlyphSet   gset,
                                             FT_UInt        gindex )
  {
    FTC_Glyph_Cache      cache = gset->cache;
    FTC_CacheNode_Data*  data  = FTC_CACHENODE_TO_DATA_P( &node->root );


    data->cache_index = (FT_UShort)cache->root.cache_index;
    data->ref_count   = (FT_Short) 0;
    node->gset_index  = (FT_UShort)gset->gset_index;
    node->glyph_index = (FT_UShort)gindex;
  }


  /* Important: This function is called from the cache manager to */
  /* destroy a given cache node during `cache compression'.  The  */
  /* second argument is always `cache.cache_data'.  Thus be       */
  /* certain that the function FTC_Glyph_Cache_New() does indeed  */
  /* set its `cache_data' field correctly, otherwise bad things   */
  /* will happen!                                                 */

  FT_EXPORT_DEF( void )  FTC_GlyphNode_Destroy( FTC_GlyphNode    node,
                                                FTC_Glyph_Cache  cache )
  {
    FT_LruNode    gset_lru = cache->gsets_lru->nodes + node->gset_index;
    FTC_GlyphSet  gset     = (FTC_GlyphSet)gset_lru->root.data;
    FT_UInt       hash     = node->glyph_index % gset->hash_size;


    /* remove the node from its gset's bucket list */
    {
      FTC_GlyphNode*  pnode = gset->buckets + hash;
      FTC_GlyphNode   cur;


      for (;;)
      {
        cur = *pnode;
        if ( !cur )
        {
          /* this should never happen */
          FT_ERROR(( "FTC_GlyphNode_Destroy:"
                     " trying to delete an unlisted node!" ));
          return;
        }

        if ( cur == node )
        {
          *pnode = cur->gset_next;
          break;
        }
        pnode = &cur->gset_next;
      }
    }

    /* destroy the node */
    gset->clazz->destroy_node( node, gset );
  }


  /* Important: This function is called from the cache manager to */
  /* size a given cache node during `cache compression'.  The     */
  /* second argument is always `cache.user_data'.  Thus be        */
  /* certain that the function FTC_Glyph_Cache_New() does indeed  */
  /* set its `user_data' field correctly, otherwise bad things    */
  /* will happen!                                                 */

  FT_EXPORT_DEF( FT_ULong )  FTC_GlyphNode_Size( FTC_GlyphNode    node,
                                                 FTC_Glyph_Cache  cache )
  {
    FT_LruNode    gset_lru = cache->gsets_lru->nodes + node->gset_index;
    FTC_GlyphSet  gset     = (FTC_GlyphSet)gset_lru->root.data;


    return gset->clazz->size_node( node, gset );
  }


  FT_CALLBACK_TABLE_DEF
  const FTC_CacheNode_Class  ftc_glyph_cache_node_class =
  {
    (FTC_CacheNode_SizeFunc)   FTC_GlyphNode_Size,
    (FTC_CacheNode_DestroyFunc)FTC_GlyphNode_Destroy
  };


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                      GLYPH SETS                               *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  FT_EXPORT_DEF( FT_Error )  FTC_GlyphSet_New( FTC_Glyph_Cache  cache,
                                               FT_Pointer       type,
                                               FTC_GlyphSet    *aset )
  {
    FT_Error                error;
    FT_Memory               memory  = cache->root.memory;
    FTC_Manager             manager = cache->root.manager;
    FTC_GlyphSet            gset    = 0;

    FTC_Glyph_Cache_Class*  gcache_class;
    FTC_GlyphSet_Class*     clazz;


    gcache_class = (FTC_Glyph_Cache_Class*)cache->root.clazz;
    clazz        = gcache_class->gset_class;

    *aset = 0;

    if ( ALLOC( gset, clazz->gset_byte_size ) )
      goto Exit;

    gset->cache     = cache;
    gset->manager   = manager;
    gset->memory    = memory;
    gset->hash_size = FTC_GSET_HASH_SIZE_DEFAULT;
    gset->clazz     = clazz;

    /* allocate buckets table */
    if ( ALLOC_ARRAY( gset->buckets, gset->hash_size, FTC_GlyphNode ) )
      goto Exit;

    /* initialize gset by type if needed */
    if ( clazz->init )
    {
      error = clazz->init( gset, type );
      if ( error )
        goto Exit;
    }

    *aset = gset;

  Exit:
    if ( error && gset )
    {
      FREE( gset->buckets );
      FREE( gset );
    }

    return error;
  }


  FT_EXPORT_DEF( void )  FTC_GlyphSet_Destroy( FTC_GlyphSet  gset )
  {
    FTC_Glyph_Cache      cache        = gset->cache;
    FTC_Manager          manager      = cache->root.manager;
    FT_List              glyphs_lru   = &manager->global_lru;
    FTC_GlyphNode*       bucket       = gset->buckets;
    FTC_GlyphNode*       bucket_limit = bucket + gset->hash_size;
    FT_Memory            memory       = cache->root.memory;

    FTC_GlyphSet_Class*  clazz        = gset->clazz;


    /* for each bucket, free the list of glyph nodes */
    for ( ; bucket < bucket_limit; bucket++ )
    {
      FTC_GlyphNode   node = bucket[0];
      FTC_GlyphNode   next = 0;
      FT_ListNode     lrunode;


      for ( ; node; node = next )
      {
        next    = node->gset_next;
        lrunode = FTC_GLYPHNODE_TO_LRUNODE( node );

        manager->num_bytes -= clazz->size_node( node, gset );
        manager->num_nodes--;

        FT_List_Remove( glyphs_lru, lrunode );

        clazz->destroy_node( node, gset );
      }

      bucket[0] = 0;
    }

    if ( clazz->done )
      clazz->done( gset );

    FREE( gset->buckets );
    FREE( gset );
  }


  FT_EXPORT_DEF( FT_Error )  FTC_GlyphSet_Lookup_Node(
                               FTC_GlyphSet    gset,
                               FT_UInt         glyph_index,
                               FTC_GlyphNode  *anode )
  {
    FTC_Glyph_Cache      cache      = gset->cache;
    FTC_Manager          manager    = cache->root.manager;
    FT_UInt              hash_index = glyph_index % gset->hash_size;
    FTC_GlyphNode*       bucket     = gset->buckets + hash_index;
    FTC_GlyphNode*       pnode      = bucket;
    FTC_GlyphNode        node;
    FT_Error             error;

    FTC_GlyphSet_Class*  clazz      = gset->clazz;


    *anode = 0;

    for ( ;; )
    {
      node = *pnode;
      if ( !node )
        break;

      if ( (FT_UInt)node->glyph_index == glyph_index )
      {
        /* we found it! -- move glyph to start of the lists */
        *pnode          = node->gset_next;
        node->gset_next = bucket[0];
        bucket[0]       = node;

        FT_List_Up( &manager->global_lru, FTC_GLYPHNODE_TO_LRUNODE( node ) );
        *anode = node;
        return 0;
      }
      /* go to next node in bucket */
      pnode = &node->gset_next;
    }

    /* we didn't found the glyph image, we will now create a new one */
    error = clazz->new_node( gset, glyph_index, &node );
    if ( error )
      goto Exit;

    /* insert the node at the start of our bucket list */
    node->gset_next = bucket[0];
    bucket[0]       = node;

    /* insert the node at the start the global LRU glyph list */
    FT_List_Insert( &manager->global_lru, FTC_GLYPHNODE_TO_LRUNODE( node ) );

    manager->num_bytes += clazz->size_node( node, gset );
    manager->num_nodes++;

    if ( manager->num_bytes > manager->max_bytes )
    {
      FTC_GlyphNode_Ref   ( node );
      FTC_Manager_Compress( manager );
      FTC_GlyphNode_Unref ( node );
    }

    *anode = node;

  Exit:
    return error;
  }


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                   GLYPH SETS LRU CALLBACKS                    *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


#define FTC_GSET_LRU_GET_CACHE( lru )   \
          ( (FTC_Glyph_Cache)(lru)->user_data )

#define FTC_GSET_LRU_GET_MANAGER( lru ) \
          FTC_GSET_LRU_GET_CACHE( lru )->manager

#define FTC_LRUNODE_GSET( node )        \
          ( (FTC_GlyphSet)(node)->root.data )


  FT_CALLBACK_DEF
  FT_Error  ftc_glyph_set_lru_init( FT_Lru      lru,
                                    FT_LruNode  node )
  {
    FTC_Glyph_Cache  cache = FTC_GSET_LRU_GET_CACHE( lru );
    FT_Error         error;
    FTC_GlyphSet     gset;


    error = FTC_GlyphSet_New( cache, (FT_Pointer)node->key, &gset );
    if ( !error )
    {
      /* good, now set the gset index within the gset object */
      gset->gset_index = node - lru->nodes;
      node->root.data  = gset;
    }

    return error;
  }


  FT_CALLBACK_DEF
  void  ftc_glyph_set_lru_done( FT_Lru      lru,
                                FT_LruNode  node )
  {
    FTC_GlyphSet  gset = FTC_LRUNODE_GSET( node );

    FT_UNUSED( lru );


    FTC_GlyphSet_Destroy( gset );
  }


  FT_CALLBACK_DEF
  FT_Bool  ftc_glyph_set_lru_compare( FT_LruNode  node,
                                      FT_LruKey   key )
  {
    FTC_GlyphSet  gset = FTC_LRUNODE_GSET( node );


    return gset->clazz->compare( gset, (FT_Pointer)key );
  }


  FT_CALLBACK_TABLE_DEF
  const FT_Lru_Class  ftc_glyph_set_lru_class =
  {
    sizeof( FT_LruRec ),
    ftc_glyph_set_lru_init,
    ftc_glyph_set_lru_done,
    0,  /* no flush */
    ftc_glyph_set_lru_compare
  };


  /*************************************************************************/
  /*************************************************************************/
  /*****                                                               *****/
  /*****                      GLYPH CACHE OBJECTS                      *****/
  /*****                                                               *****/
  /*************************************************************************/
  /*************************************************************************/


  FT_EXPORT_DEF( FT_Error )  FTC_Glyph_Cache_Init( FTC_Glyph_Cache  cache )
  {
    FT_Memory  memory = cache->root.memory;
    FT_Error   error;

    FTC_Glyph_Cache_Class*  gcache_clazz;


    /* set up root node_class to be used by manager */
    cache->root.node_clazz =
      (FTC_CacheNode_Class*)&ftc_glyph_cache_node_class;

    /* setup the `compare' shortcut */
    gcache_clazz   = (FTC_Glyph_Cache_Class*)cache->root.clazz;
    cache->compare = gcache_clazz->gset_class->compare;

    /* The following is extremely important for ftc_destroy_glyph_image() */
    /* to work properly, as the second parameter that is sent to it       */
    /* through the cache manager is `cache_data' and must be set to       */
    /* `cache' here.                                                      */
    /*                                                                    */
    cache->root.cache_data = cache;

    error = FT_Lru_New( &ftc_glyph_set_lru_class,
                        FTC_MAX_GLYPH_SETS,
                        cache,
                        memory,
                        1, /* pre_alloc == TRUE */
                        &cache->gsets_lru );
    return error;
  }


  FT_EXPORT_DEF( void )  FTC_Glyph_Cache_Done( FTC_Glyph_Cache  cache )
  {
    /* discard glyph sets */
    FT_Lru_Done( cache->gsets_lru );
  }


  FT_EXPORT_DEF( FT_Error )  FTC_Glyph_Cache_Lookup( FTC_Glyph_Cache  cache,
                                                     FT_Pointer       type,
                                                     FT_UInt          gindex,
                                                     FTC_GlyphNode   *anode )
  {
    FT_Error       error;
    FTC_GlyphSet   gset;
    FTC_GlyphNode  node;
    FTC_Manager    manager;


    /* check for valid `desc' delayed to FT_Lru_Lookup() */

    if ( !cache || !anode )
      return FT_Err_Invalid_Argument;

    *anode = 0;
    gset   = cache->last_gset;

    if ( !gset || !cache->compare( gset, type ) )
    {
      error = FT_Lru_Lookup( cache->gsets_lru,
                             (FT_LruKey)type,
                             (FT_Pointer*)&gset );
      cache->last_gset = gset;
      if ( error )
        goto Exit;
    }

    error = FTC_GlyphSet_Lookup_Node( gset, gindex, &node );
    if ( error )
      goto Exit;

    /* now compress the manager's cache pool if needed */
    manager = cache->root.manager;
    if ( manager->num_bytes > manager->max_bytes )
    {
      FTC_GlyphNode_Ref   ( node );
      FTC_Manager_Compress( manager );
      FTC_GlyphNode_Unref ( node );
    }

    *anode = node;

  Exit:
    return error;
  }


/* END */
