/** 
 *  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/SUniFont.h"
#include "stoolkit/SCluster.h"
#include "stoolkit/SBinVector.h"
#include "stoolkit/STypes.h" 

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

/**
 * We have 16 widths
 */
#define SD_W08  0
#define SD_W16  1

/* pos 1 is impossible */
#define SD_PW_ERR 1

SUniFont::SUniFont (const SFile& _file) : file(_file)
{
  image = file.getFileImage();
  array = image.array();
}
SUniFont::~SUniFont ()
{
} 

bool
SUniFont::draw (double scale, SCanvas* canvas, const SColor& fg, 
  const SS_Matrix2D& matrix, SS_UCS4 g, bool mirrored)
{
  int scriptCode = getLigatureScriptCode (g); 
  /* only support SD_AS_SHAPES if scriptCode */
  if (scriptCode != SD_AS_SHAPES && scriptCode >= 0) return false;
  //if ((int)scale != 16) return false;
  double w;

  unsigned int posw;
  bool re =  width (16, g, &w, &posw);
  if (!re) return re;

  unsigned int pt = SD_GET_POS (posw);
  int wd = (unsigned int) w;

  /* scan */
  char ch[5];
  int x = (int)matrix.t0;
  int y = (int)matrix.t1;
  SBinVector<int> ix;
  SBinVector<int> iy;
  for (int i=0; i<16; i++)
  {
    SS_UCS4 mask = 0;
    SS_UCS4 bits = 0;
    char* next = ch;
    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;
    }
    if (wd == 8)
    {
      ch[0] = array[pt++]; ch[1] = array[pt++];
      ch[2] = 0;
      bits = (SS_UCS4) strtoul (ch, &next, 16);
      mask = 0x80;
    }
    for (int j=0; j<wd; j++)
    {
      if (bits&mask)
      {
        if (mirrored)
        {
           ix.append (x+(wd-j-1));
           
        } else {
           ix.append (x+j);
        }
        /* ascent 2 */
        iy.append (y+i-14);
      }
      mask = mask >> 1;
    }
  }
  if (ix.size())
  {
    canvas->bitpoints (fg, ix.array(), iy.array(), ix.size());
  }
  return true;
}

bool
SUniFont::width (double scale, SS_UCS4 g, double *width_)
{
  unsigned int pwd;
  return width (scale, g, width_, &pwd);
}

bool
SUniFont::width (double scale, SS_UCS4 g, double *width_, unsigned int* pwd)
{
  if (width_) *width_ = 0.0;
  int scriptCode = getLigatureScriptCode (g); 
  /* only support SD_AS_SHAPES if scriptCode */
  if (scriptCode != SD_AS_SHAPES && scriptCode >= 0) return false;
  if ((int)scale != 16) return false;
  if ((*pwd = find (g)) != 0)
  {
     /* Sorry, indic won't work with unifont. */
     if (getUnicodeScript (g) <= 0 && g != 0x200D && g != 0x200C) /* ZWJ ZWNJ */
     {
       if (width_)
       {
         unsigned int wi = SD_GET_WIDTH (*pwd);
         *width_ = 8.0;
         if (wi == SD_W08) *width_ = 8.0; 
         if (wi == SD_W16) *width_ = 16.0; 
       }
       return true;
     }
  }
  return false;
}

double
SUniFont::width (double scale)
{
  if ((int)scale != 16) return 1.0;
  return 8.0;
}

double
SUniFont::ascent (double scale)
{
  if ((int)scale != 16) return 1.0;
  return 14.0;
}
double
SUniFont::descent (double scale)
{
  if ((int)scale != 16) return 0.0;
  return 2.0;
}

double
SUniFont::gap (double scale)
{
  if ((int)scale != 16) return 0.0;
  return 0.0;
}

static const char* shapeVariants[7] = {
       "n", // ISOLATED
       "l", // INITIAL
       "m", // MEDIAL
       "r", // FINAL
       "fj", // FINAL-FJ
       "fn", // ISOLATED-FN
       "fx", // ISOLATED-FX
};

/**
 * Find this glyph in the font. It also should put this 
 * in the cache.
 * @return 0 on fail widthposition on success.
 */
unsigned int
SUniFont::find (SS_UCS4 g)
{
  if (array == 0) return 0; // No font file found.
  if (g < 0x20) return 0;

  char arr[32];

  /* colon is part of it */
  if (g <= 0xffff)
  {
    snprintf (arr, 32, "%04X:", g);
  }
  else if (g <= 0xfffff)
  {
    snprintf (arr, 32, "%05X:", g);
  }
  else if (g <= 0xffffff)
  {
    snprintf (arr, 32, "%06X:", g);
  }
  else if (g <= 0xfffffff)
  {
    snprintf (arr, 32, "%07X:", g);
  }
  else if (g <= 0x7fffffff)
  {
    snprintf (arr, 32, "%08X:", g);
  }
  else if (getLigatureScriptCode (g)==SD_AS_SHAPES)
  {
    unsigned int fcode = (g & 0xf000) >> 12;
    unsigned int gcode = g&0x0fff;

    if (fcode ==0 || fcode > 7) return 0;
    fcode--;
    switch (gcode)
    {
    case 1:  /* 0xA000X001: */
      snprintf (arr, 32, "072A+0308-%s:", shapeVariants[fcode]);
      break;
    default:
      snprintf (arr, 32, "%04X-%s:", gcode, shapeVariants[fcode]);
    break;
    }
  }
  else
  {
    return 0;
  }
  SString key (arr);
  unsigned int pw = posWidth.get (key);
  if (pw == SD_PW_ERR) return 0;
  if (pw!= 0) return pw;

  /* load it ... */
  int size = (int)image.size();
  int bottom = 0;
  int top = size;
  /* do a binary search */
  SString gout;
  while (top >= bottom)
  {
    int mid = (top + bottom) / 2;
    int index = nextIndex (mid, size, &gout); 
    
      /* build the cache */
    if (index >=0 && posWidth.get(gout) == 0)
    {
      /* 
       * fill in width. 1. means non-existent. 
       * 100 is here so that we don't consume much time with corrupted files.
       */
      for (int j=index; j<size && j<=index+100; j++)
      {
        if (array[j] <= 0x20) /* usually \n */
        {
          int w = (j-index)/4;
          if (w <= 8)
          {
            posWidth.put (gout, SD_GET_POSWIDTH (index, SD_W08));
          }
          else if (w <= 16)
          {

             posWidth.put (gout, SD_GET_POSWIDTH (index, SD_W16));
          }
          else
          {
             posWidth.put (gout, SD_PW_ERR);
          }
          break;
        }
      }
    }
    if (index < 0 || gout > key)
    {
      top = mid - 1;
    }
    else if (gout < key)
    {
      bottom = mid + 1;
    }
    else
    {
      unsigned int pwr = posWidth.get (key);
      if (pwr == SD_PW_ERR) return 0;
      if (pwr != 0) return pwr;
    }
  }
  posWidth.put (key, SD_PW_ERR);
  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 
 * unifont.hex:
 * 001F:0000000000000000000024E0250024C0242019C0000000000000000000000000
 * 0021:00000000101010101010100010100000
 * syriacforms.hex:
 * 0710-fn:00180404040810203E10100000000000
 * arabforms.hex:
 * 0678-l:0304030400080404F800000000000000
 * <-*g->.^ position returned
 */
int
SUniFont::nextIndex (int pos, int size, SString* g)
{
  g->clear();
  for (int i=pos; i<size && i<=pos+100; i++)
  {
    if (i==0 || array[i-1] == '\n')
    {
      for (int j=i+1; j<i+100 && j+32 < size; j++)
      {
        if (array[j]==':')
        {
          /* : is inside */
          *g = SString (&array[i], (unsigned int) (j-i+1));
          return (j + 1);
        }
      }
      return -1;
    }
  }
  return -1;
}
