/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2023  Gaspar Sinai <gaspar@yudit.org>  
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2,
 *  dated June 1991. See file COPYYING for details.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "swindow/SFontBDF.h"
#include "stoolkit/SCluster.h"
#include "stoolkit/SBinVector.h"

#include <stdlib.h>
#include <string.h>


SFontBDF::SFontBDF (const SFile& _file) : file(_file)
{
  image = file.getFileImage();
  array = image.array();
  /* position of bimaps */
  memset (position, 0, 0x10000 * sizeof (unsigned int));
  memset (widths, 0, 0x10000 * sizeof (char));
  /* these  values are read from bdf font */
  iscale = 0;
  iascent = 0;
  idescent = 0;
  size = (image.size() >= 0) ? (unsigned int) image.size() : 0;
  bool gotiscale = false;
  bool gotiascent = false;
  bool gotidescent = false;
  bool gotname = false;
  unsigned int i=0;
  while (i<size)
  {
    unsigned int index = nextLine(i);

    if (i+10<size && strncmp (&array[i], "STARTCHAR", 9)==0) break;
    if (i+5<index && strncmp (&array[i], "FONT ", 5)==0)
    {
      name = SString (&array[i+5], (unsigned int)((index-i)-5));
      name.lower();
      gotname = true;
      i = index +1;
      continue;
    }

    SString str = SString (&array[i], index-i);
    str.append ((char)0);
    i = index +1;

    if (!gotiscale && sscanf (str.array(), "PIXEL_SIZE %d", &iscale)==1)
    {
       gotiscale = true; continue;
    }
    if (!gotiascent && sscanf (str.array(), "FONT_ASCENT %d", &iascent)==1)
    {
       gotiascent = true; continue;
    }
    if (!gotidescent && sscanf (str.array(), "FONT_DESCENT %d", &idescent)==1)
    {
       gotidescent = true; continue;
    }
  }
  if (file.size()<0)
  {
    fprintf (stderr, "File not found: %*.*s\n", SSARGS(file.getName()));
  }
  else if (!gotiscale || iscale==0 || !gotiascent || !gotidescent || !gotname) 
  {
    fprintf (stderr, "Can not scan BDF font: %*.*s (scale=%d iscale=%d ascent=%d descent=%d name=%d)\n", SSARGS(file.getName()), gotiscale, iscale, gotiascent, gotidescent, gotname);
    //fprintf (stderr, "Can not scan BDF font: %*.*s\n", SSARGS(file.getName()));
    iscale = 0;
    iascent = 0;
    idescent = 0;
  }
  else if (!name.match("*-iso10646-1"))
  {
    fprintf (stderr, "BDF is not iso10646-1: : %*.*s.\n", SSARGS(name));
    iscale = 0;
    iascent = 0;
    idescent = 0;
  }
}
SFontBDF::~SFontBDF ()
{
} 

unsigned int 
SFontBDF::nextLine (unsigned int from)
{
  unsigned int i = from;
  while (i < size && array[i] != '\n') i++;
  return i;
}

bool
SFontBDF::draw (double scale, SCanvas* canvas, const SColor& fg, 
  const SS_Matrix2D& matrix, SS_UCS4 g, bool mirrored)
{
  /* not a valid font */
  if (iscale == 0 || (int)scale != iscale) return false;
  if (g>0x10000) return false;
  double w;
  bool re =  width (scale, g, &w);
  if (!re) return re;
  int wd = (unsigned int) w;
  /* scan */
  unsigned int pt = (unsigned int) position[g];
  char ch[9];
  int x = (int)matrix.t0;
  int y = (int)matrix.t1;

  SBinVector<int> ix;
  SBinVector<int> iy;
  
  for (int i=0; i<iascent+idescent; i++)
  {
    while (array[pt] == '\n' || array[pt] == ' ' || array[pt] == '\t') pt++; 
    if (strncmp (&array[pt], "ENDCHAR", 7)==0) break;

    SS_UCS4 mask = 0;
    SS_UCS4 bits = 0;
    char* next = ch;
    if (wd <= 8)
    {
      ch[0] = array[pt++]; ch[1] = array[pt++];
      ch[2] = 0;
      bits = (SS_UCS4) strtoul (ch, &next, 16);
      mask = 0x80;
    }
    else if (wd <= 16)
    {
      ch[0] = array[pt++]; ch[1] = array[pt++];
      ch[2] = array[pt++]; ch[3] = array[pt++];
      ch[4] = 0;
      bits = (SS_UCS4) strtoul (ch, &next, 16);
      mask = 0x8000;
    }
    else if (wd <= 24)
    {
      ch[0] = array[pt++]; ch[1] = array[pt++];
      ch[2] = array[pt++]; ch[3] = array[pt++];
      ch[4] = array[pt++]; ch[5] = array[pt++];
      ch[6] = 0;
      bits = (SS_UCS4) strtoul (ch, &next, 16);
      mask = 0x800000;
    }
    else if (wd <= 32)
    {
      ch[0] = array[pt++]; ch[1] = array[pt++];
      ch[2] = array[pt++]; ch[3] = array[pt++];
      ch[4] = array[pt++]; ch[5] = array[pt++];
      ch[6] = array[pt++]; ch[7] = array[pt++];
      ch[8] = 0;
      bits = (SS_UCS4) strtoul (ch, &next, 16);
      mask = 0x80000000;
    }
    for (int j=0; j<wd; j++)
    {
      if (bits&mask)
      {
        if (mirrored)
        {
           ix.append (x+(wd-j-1));
           
        } else {
           ix.append (x+j);
        }
        iy.append (y+i-iascent);
      }
      mask = mask >> 1;
    }
  }
  if (ix.size())
  {
    canvas->bitpoints (fg, ix.array(), iy.array(), ix.size());
  }
  return true;
}

bool
SFontBDF::width (double scale, SS_UCS4 g, double *width_)
{
  /* not a valid font */
  if (width_) *width_ = 0.0;
  if (iscale == 0 || (int)scale != iscale) return false;

  if (find (g))
  {
     /* Sorry, indic won't work with unifont. */
     if (getUnicodeScript (g) <= 0 
         && g != 0x200D && g != 0x200C) /* ZWJ ZWNJ */
     {
       if (width_) *width_ = (double) widths[g];
       return true;
     }
  }
  return false;
}

double
SFontBDF::width (double scale)
{
  if (iscale == 0 || (int)scale != iscale) return 1.0;
  return (double) iascent; /* hack. */
}

double
SFontBDF::ascent (double scale)
{
  if (iscale == 0 || (int)scale != iscale) return 1.0;
  return (double) iascent;
}
double
SFontBDF::descent (double scale)
{
  if (iscale == 0 || (int)scale != iscale) return 0.0;
  return (double) idescent;
}

double
SFontBDF::gap (double scale)
{
  if (iscale == 0 || (int)scale != iscale) return 0.0;
  return 0.0;
}

bool
SFontBDF::find (SS_UCS4 g)
{
  if (g < 0x20) return false;
  if (g > 0xffff) return false;
  if (array == 0) return false;
  if (widths[g] == 1) return false;
  if (widths[g] != 0) return true;

  /* load it ... */
  int bottom = 0;
  int top = (int)image.size();
  /* do a binary search */
  SS_UCS4 gout;
  int owidth = 0;
  while (top >= bottom)
  {
    int mid = (top + bottom) / 2;
    int index = nextIndex (mid, &gout, &owidth); 
    
    if (gout < 0x10000 && index >=0)
    {
      if (widths[gout] == 0)
      {
         position[gout] = index;
         widths[gout] = (char)owidth;
      }
    }
    if (index < 0 || gout > g)
    {
      top = mid - 1;
    }
    else if (gout < g)
    {
      bottom = mid + 1;
    }
    else
    {
      return true;
    }
  }
  widths[g] = 1;
  return false;
}

/**
 * Find next XXXX: combination.
 * @return position to first X and return the value of X in g.
 * return negative if not found any, and set g to 0x10000.
 * Sample:
   STARTCHAR exclam
   ENCODING 33
   SWIDTH 540 0
   DWIDTH 9 0
   BBX 9 18 0 -4
   BITMAP
   0000
   0000
   0000
   0800
   0800
   0800
   0800
   0800
   0800
   0800
   0000
   0000
   0800
   0000
   0000
   0000
   0000
   ENDCHAR
 */
int
SFontBDF::nextIndex (int pos, SS_UCS4* g, int *owidth)
{
  *g = 0x10000;
  *owidth = 1;
  unsigned int i=(unsigned int) pos;

  bool started = false;
  int encoding = -1;
  while (i<size)
  {
    unsigned int index = nextLine(i);
    if (i!=0 && array[i-1] != '\n')
    {
      i = index +1;
      continue;
    }
    
    if (!started)
    {
      started = (i+9 < size && strncmp (&array[i], "STARTCHAR", 9)==0);
      i = index +1;
      continue;
    }
    if (i+6 < size && strncmp (&array[i], "BITMAP", 6) ==0)
    {
      if (encoding < 0 || *owidth == 1) return -1;
      *g = encoding;
      return index+1;
    }

    SString str = SString(&array[i], index-i);
    str.append ((char)0);
    i = index +1;

    if (sscanf (str.array(), "ENCODING %d", &encoding)==1) continue;
    if (sscanf (str.array(), "DWIDTH %d", owidth)==1)
    {
      continue;
    }
  }
  return -1;
}
