/**************************************************************************\
 *
 *  This file is part of the Coin 3D visualization library.
 *  Copyright (C) 1998-2006 by Systems in Motion.  All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  ("GPL") version 2 as published by the Free Software Foundation.
 *  See the file LICENSE.GPL at the root directory of this source
 *  distribution for additional information about the GNU GPL.
 *
 *  For using Coin with software that can not be combined with the GNU
 *  GPL, and for taking advantage of the additional benefits of our
 *  support services, please contact Systems in Motion about acquiring
 *  a Coin Professional Edition License.
 *
 *  See http://www.coin3d.org/ for more information.
 *
 *  Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY.
 *  http://www.sim.no/  sales@sim.no  coin-support@coin3d.org
 *
\**************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#include <Inventor/C/base/dict.h>
#include <Inventor/C/base/string.h>
#include <Inventor/C/threads/threadsutilp.h>
#include <Inventor/C/tidbitsp.h>

#include "fontlib_wrapper.h"
#include "glyph2d.h"
#include "glyph.h"

static SbBool glyph2d_specmatch(const cc_font_specification * spec1, const cc_font_specification * spec2);

struct cc_glyph2d {
  struct cc_glyph c; /* "c" for "common" glyph data (2d & 3d). */

  float angle; /* FIXME: is this one really in use? 20060109 mortene. */
  unsigned short width; /* FIXME: is this one really in use? 20060109 mortene. */

  unsigned short height;
  unsigned short bitmapwidth;
  unsigned short bitmapheight;
  short bitmapoffsetx;
  short bitmapoffsety;
  unsigned char * bitmap;
  SbBool mono;
};

static cc_dict * glyph2d_fonthash = NULL;
static SbBool glyph2d_initialized = FALSE;

/*
  Mutex lock for the static ang global font hash
*/
static void * glyph2d_fonthash_lock = NULL;

/* Set '#if 1' to enable debug output to stderr for tracking mutex locking. */
#if 0
#define GLYPH2D_MUTEX_LOCK(m) \
  do { \
    (void)fprintf(stderr, "glyph2d mutex lock in %s\n", __func__); \
    CC_MUTEX_LOCK(m); \
  } while (0)
#define GLYPH2D_MUTEX_UNLOCK(m) \
  do { \
    (void)fprintf(stderr, "glyph2d mutex unlock in %s\n", __func__); \
    CC_MUTEX_UNLOCK(m); \
  } while (0)
#else
#define GLYPH2D_MUTEX_LOCK(m) CC_MUTEX_LOCK(m)
#define GLYPH2D_MUTEX_UNLOCK(m) CC_MUTEX_UNLOCK(m)
#endif

static void
cc_glyph2d_cleanup(void)
{
  CC_MUTEX_DESTRUCT(glyph2d_fonthash_lock);
  cc_dict_destruct(glyph2d_fonthash);
  glyph2d_fonthash = NULL;
  glyph2d_initialized = FALSE;
}

static void
cc_glyph2d_initialize()
{
  CC_MUTEX_CONSTRUCT(glyph2d_fonthash_lock);
  GLYPH2D_MUTEX_LOCK(glyph2d_fonthash_lock);
  
  if (glyph2d_initialized) {
    GLYPH2D_MUTEX_UNLOCK(glyph2d_fonthash_lock);
    return;
  }
  glyph2d_initialized = TRUE;
  
  glyph2d_fonthash = cc_dict_construct(15, 0.75);

  /* +1, so it happens before the underlying font abstraction layer
     cleans itself up: */
  coin_atexit((coin_atexit_f*) cc_glyph2d_cleanup, CC_ATEXIT_FONT_SUBSYSTEM + 1);
  
  GLYPH2D_MUTEX_UNLOCK(glyph2d_fonthash_lock);
}

cc_glyph2d * 
cc_glyph2d_ref(uint32_t character, const cc_font_specification * spec, float angle)
{
  void * val;
  cc_glyph2d * glyph;
  int fontidx;
  int glyphidx;
  int i;
  struct cc_font_bitmap * bm;
  cc_font_specification * newspec;
  int namelen = 0;
  cc_string * fonttoload;
  cc_list * glyphlist;


  /* Beacuse this function is the entry point for glyph2d, the mutex
     is initialized here. */
  if (glyph2d_fonthash_lock == NULL) 
    cc_glyph2d_initialize();
  
  assert(spec);

  GLYPH2D_MUTEX_LOCK(glyph2d_fonthash_lock);

  /* Has the glyph been created before? */
  if (cc_dict_get(glyph2d_fonthash, (uintptr_t)character, &val)) {
    glyphlist = (cc_list *) val;
    for (i = 0; i < cc_list_get_length(glyphlist); ++i) {
      glyph = (cc_glyph2d *) cc_list_get(glyphlist, i);
      if (glyph2d_specmatch(spec, glyph->c.fontspec)) {
        GLYPH2D_MUTEX_UNLOCK(glyph2d_fonthash_lock);
        glyph->c.refcount++;
        return glyph;
      }
    }    
  } 
  else {
    /* No glyphlist for this character is found. Create one and
       add it to the hashtable. */
    glyphlist = cc_list_construct();
    cc_dict_put(glyph2d_fonthash, (uintptr_t)character, glyphlist);
  }

  assert(glyphlist);

  /* build a new glyph struct with bitmap */    
  glyph = (cc_glyph2d *) malloc(sizeof(cc_glyph2d));
  glyph->c.character = character;
  
  newspec = (cc_font_specification *) malloc(sizeof(cc_font_specification)); 
  assert(newspec);
  cc_fontspec_copy(spec, newspec);

  glyph->c.fontspec = newspec;

  /* FIXME: fonttoload variable should be allocated on the
     stack. 20030921 mortene. */
  fonttoload = cc_string_construct_new();
  cc_string_set_text(fonttoload, cc_string_get_text(&spec->name));
  if (cc_string_length(&spec->style) > 0) {
    cc_string_append_text(fonttoload, " ");
    cc_string_append_string(fonttoload, &spec->style);
  }
  
  /* FIXME: get rid of angle -- not used. 20050516 mortene. */
  fontidx =
    cc_flw_get_font_id(cc_string_get_text(fonttoload),
                       (unsigned int)(newspec->size), angle,
                       /* FIXME: passing in -1 for complexity below is
                          necessary because the Win32 API code in
                          win32.c cc_flww32_get_font() detects this
                          and uses this as a hack to *not* change the
                          font size. (Changing the font size is
                          necessary for *3D* fonts because otherwise
                          the resolution will be too low for
                          vectorization.)

                          What an ugly mess. Should be cleaned up
                          properly -- the worst offender against any
                          good design taste is the code in
                          win32.c. 20050706 mortene.*/
                       -1.0f);

  cc_string_destruct(fonttoload);
  assert(fontidx >= 0);

  cc_flw_ref_font(fontidx);

  /* Should _always_ be able to get hold of a glyph -- if no glyph is
     available for a specific character, a default empty rectangle
     should be used.  -mortene. */
  glyphidx = cc_flw_get_glyph(fontidx, character);
  assert(glyphidx >= 0);

  glyph->c.glyphidx = glyphidx;
  glyph->c.fontidx = fontidx;
  glyph->angle = angle;

  bm = cc_flw_get_bitmap(fontidx, glyphidx);
  assert(bm);
  glyph->width = bm->width;
  glyph->height = bm->rows;
  glyph->bitmapwidth = bm->mono ? bm->pitch * 8 : bm->pitch;
  glyph->bitmapheight = bm->rows;
  glyph->bitmapoffsetx = bm->bearingX;
  glyph->bitmapoffsety = bm->bearingY;
  glyph->bitmap = bm->buffer;
  glyph->mono = bm->mono;
  glyph->c.refcount = 1;
  
  /* Store newly created glyph in the list for this character */
  cc_list_append(glyphlist, glyph);
  
  GLYPH2D_MUTEX_UNLOCK(glyph2d_fonthash_lock);
  return glyph;
}

void
cc_glyph2d_unref(cc_glyph2d * glyph)
{
  cc_glyph_unref(glyph2d_fonthash, &(glyph->c), NULL);
}

static SbBool 
glyph2d_specmatch(const cc_font_specification * spec1, 
                  const cc_font_specification * spec2)
{
  assert(spec1);
  assert(spec2);

  if ((!cc_string_compare(&spec1->name, &spec2->name)) &&
      (!cc_string_compare(&spec1->style, &spec2->style)) &&
      (spec1->size == spec2->size)) {
    /* No need to compare complexity for 2D fonts */
    return TRUE;
  }
  else return FALSE;
  
}

void 
cc_glyph2d_getadvance(const cc_glyph2d * g, int * x, int * y)
{
  cc_flw_get_bitmap_advance(g->c.fontidx, g->c.glyphidx, x, y);
}

void 
cc_glyph2d_getkerning(const cc_glyph2d * left, const cc_glyph2d * right, int * x, int * y)
{
  cc_flw_get_bitmap_kerning(right->c.fontidx, left->c.glyphidx, right->c.glyphidx, x, y);
}

unsigned int 
cc_glyph2d_getwidth(const cc_glyph2d * g)
{
  return (int) g->width;
}

const unsigned char * 
cc_glyph2d_getbitmap(const cc_glyph2d * g, int * size, int * offset)
{
  size[0] = g->bitmapwidth;
  size[1] = g->bitmapheight;
  offset[0] = g->bitmapoffsetx;
  offset[1] = g->bitmapoffsety;

  return g->bitmap;
}

SbBool 
cc_glyph2d_getmono(const cc_glyph2d * g)
{
  return g->mono;
}
