/*
   This file contains simple code to manage access to fonts, insuring that
   library routines access/load fonts only once
 */

#include <../src/sys/classes/draw/impls/x/ximpl.h>

static PetscErrorCode PetscDrawXiInitFonts(PetscDraw_X *);
static PetscErrorCode PetscDrawXiLoadFont(PetscDraw_X *, PetscDrawXiFont *);
static PetscErrorCode PetscDrawXiMatchFontSize(PetscDrawXiFont *, int, int);

/*
    PetscDrawXiFontFixed - Return a pointer to the selected font.

    Warning: Loads a new font for each window. This should be
   ok because there will never be many windows and the graphics
   are not intended to be high performance.
*/
PetscErrorCode PetscDrawXiFontFixed(PetscDraw_X *XBWin, int w, int h, PetscDrawXiFont **outfont)
{
  static PetscDrawXiFont *curfont = NULL, *font;

  PetscFunctionBegin;
  if (!curfont) PetscCall(PetscDrawXiInitFonts(XBWin));
  PetscCall(PetscNew(&font));
  PetscCall(PetscDrawXiMatchFontSize(font, w, h));
  PetscCall(PetscDrawXiLoadFont(XBWin, font));

  curfont  = font;
  *outfont = curfont;
  PetscFunctionReturn(PETSC_SUCCESS);
}

/* this is set by XListFonts at startup */
#define NFONTS 20
static struct {
  int w, h, descent;
} nfonts[NFONTS];
static int act_nfonts = 0;

/*
  These routines determine the font to be used based on the requested size,
  and load it if necessary
*/

static PetscErrorCode PetscDrawXiLoadFont(PetscDraw_X *XBWin, PetscDrawXiFont *font)
{
  char         font_name[100];
  XFontStruct *FontInfo;
  XGCValues    values;

  PetscFunctionBegin;
  PetscCall(PetscSNPrintf(font_name, PETSC_STATIC_ARRAY_LENGTH(font_name), "%dx%d", font->font_w, font->font_h));
  font->fnt = XLoadFont(XBWin->disp, font_name);

  /* The font->descent may not have been set correctly; get it now that
      the font has been loaded */
  FontInfo           = XQueryFont(XBWin->disp, font->fnt);
  font->font_descent = FontInfo->descent;
  font->font_w       = FontInfo->max_bounds.rbearing - FontInfo->min_bounds.lbearing;
  font->font_h       = FontInfo->max_bounds.ascent + FontInfo->max_bounds.descent;

  XFreeFontInfo(NULL, FontInfo, 1);

  /* Set the current font in the CG */
  values.font = font->fnt;
  XChangeGC(XBWin->disp, XBWin->gc.set, GCFont, &values);
  PetscFunctionReturn(PETSC_SUCCESS);
}

/* Code to find fonts and their characteristics */
static PetscErrorCode PetscDrawXiInitFonts(PetscDraw_X *XBWin)
{
  char       **names;
  int          cnt, i, j;
  XFontStruct *info;

  PetscFunctionBegin;
  /* This just gets the most basic fixed-width fonts */
  names = XListFontsWithInfo(XBWin->disp, "?x??", NFONTS, &cnt, &info);
  j     = 0;
  for (i = 0; i < cnt; i++) {
    names[i][1]       = '\0';
    nfonts[j].w       = info[i].max_bounds.width;
    nfonts[j].h       = info[i].ascent + info[i].descent;
    nfonts[j].descent = info[i].descent;
    if (nfonts[j].w <= 0 || nfonts[j].h <= 0) continue;
    j++;
    if (j >= NFONTS) break;
  }
  act_nfonts = j;
  if (cnt > 0) XFreeFontInfo(names, info, cnt);

  /* If the above fails,try this: */
  if (!act_nfonts) {
    /* This just gets the most basic fixed-width fonts */
    names = XListFontsWithInfo(XBWin->disp, "?x", NFONTS, &cnt, &info);
    j     = 0;
    for (i = 0; i < cnt; i++) {
      size_t len;

      PetscCall(PetscStrlen(names[i], &len));
      if (len != 2) continue;
      names[i][1] = '\0';
      nfonts[j].w = info[i].max_bounds.width;
      /* nfonts[j].w         = info[i].max_bounds.lbearing + info[i].max_bounds.rbearing; */
      nfonts[j].h       = info[i].ascent + info[i].descent;
      nfonts[j].descent = info[i].descent;
      if (nfonts[j].w <= 0 || nfonts[j].h <= 0) continue;
      j++;
      if (j >= NFONTS) break;
    }
    act_nfonts = j;
    XFreeFontInfo(names, info, cnt);
  }
  PetscFunctionReturn(PETSC_SUCCESS);
}

static PetscErrorCode PetscDrawXiMatchFontSize(PetscDrawXiFont *font, int w, int h)
{
  int i, max, imax, tmp;

  PetscFunctionBegin;
  for (i = 0; i < act_nfonts; i++) {
    if (nfonts[i].w == w && nfonts[i].h == h) {
      font->font_w       = w;
      font->font_h       = h;
      font->font_descent = nfonts[i].descent;
      PetscFunctionReturn(PETSC_SUCCESS);
    }
  }

  /* determine closest fit,per max. norm */
  imax = 0;
  max  = PetscMax(PetscAbsInt(nfonts[0].w - w), PetscAbsInt(nfonts[0].h - h));
  for (i = 1; i < act_nfonts; i++) {
    tmp = PetscMax(PetscAbsInt(nfonts[i].w - w), PetscAbsInt(nfonts[i].h - h));
    if (tmp < max) {
      max  = tmp;
      imax = i;
    }
  }

  /* should use font with closest match */
  font->font_w       = nfonts[imax].w;
  font->font_h       = nfonts[imax].h;
  font->font_descent = nfonts[imax].descent;
  PetscFunctionReturn(PETSC_SUCCESS);
}
