/** 
 *  Yudit Unicode Editor Source File
 *
 *  GNU Copyright (C) 1997-2006  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/SFontImpl.h"
#include "swindow/SFontTTF.h"
#include "swindow/SFontNative.h"
#include "swindow/SFontBDF.h"
#include "swindow/SUniFont.h"
#include "swindow/SAwt.h"
#include "swindow/SImage.h"

#include "stoolkit/SIO.h"
#include "stoolkit/SBinHashtable.h"
#include "stoolkit/SHashtable.h"
#include "stoolkit/SExcept.h"
#include "stoolkit/SUtil.h"
#include "stoolkit/SCluster.h"

static SStringVector fontSearchPath(
   "/,fonts,../fonts,/etc/fonts,/usr/share/yudit/fonts");


/* it makes a big difference in speed */

/**
 * @author: Gaspar Sinai <gaspar@yudit.org>
 * @version: 2000-04-23
 * This is the abstract widget toolkit font package
 */


class SFontDelegate
{
public:
  enum SE_Type { SE_NONE, SE_TTF, SE_NATIVE, SE_UNIFONT, SE_BDF };
  SFontDelegate (const SFile& file, SE_Type type, 
      const SString& encoding=SString(""));
  ~SFontDelegate();
  unsigned int count;
  SE_Type type;
  union {
   SFontTTF* ttf;
   SFontNative* native;
   SUniFont* unifont;
   SFontBDF* bdf;
  } u;
};

/**
 * Create a brand new delegate
 */
SFontDelegate::SFontDelegate (const SFile& file, 
  SE_Type _type, const SString& enc)
{
  count = 1;
  type = _type;
  u.ttf = 0;
  switch (type)
  {
  case SE_TTF:
    u.ttf = new SFontTTF (file, enc);
    if (!u.ttf->isOK())
    {
      delete u.ttf;
      u.ttf = 0;
      type = SE_NONE;
    }
    break;
  case SE_NATIVE:
    u.native = SAwt::getFont(enc);
    break;
  case SE_UNIFONT:
    u.unifont = new SUniFont(file);
    break;
  case SE_BDF:
    u.bdf = new SFontBDF(file);
    break;
  case SE_NONE:
     break;
  }
}

SFontDelegate::~SFontDelegate()
{
  switch (type)
  {
  case SE_TTF:
     if (u.ttf!=0) delete u.ttf;
     break;
  case SE_NATIVE:
     if (u.native!=0) delete u.native;
     break;
  case SE_UNIFONT:
     if (u.unifont!=0) delete u.unifont;
     break;
  case SE_BDF:
     if (u.bdf!=0) delete u.bdf;
     break;
  case SE_NONE:
     break;
  }
}

typedef SBinHashtable<SFontDelegate*> SFontCache;
static SFontCache fontCache;

/**
 * Create a new font face.
 * @param _name is the logical x11 font descriptor that matches
 *        this font.  "-%s-%s-%s-%s-*-%s-%g-*-*-*-%s-%s-%s-%s"
 * foundry-family-weight-slant-*-style-pixel-*-*-*-spacing-avgWidth-registry-encoding
 * @param encoding is not zero size if there is an external encoder.
 */
SFontImpl::SFontImpl (const SString& _name, const SString& _encoding) : 
    xlfd (_name), name(_name), encoding (_encoding)
{
  /**
   * Try to find TTF first
   */
  rlFont = false;
  lrFont = false;
  SString decorated (name);
  decorated.append ("~");
  decorated.append (encoding);
  //fprintf (stderr, "XLFD %*.*s\n", SSARGS(xlfd));
  SFontDelegate* d = fontCache.get(decorated);
  delegate = 0;
  if (d!=0)
  {
    //fprintf (stderr, "resusing %*.*s %u\n", SSARGS(name), d->count);
    delegate = d;
    d->count++;
    if (d->type == SFontDelegate::SE_NATIVE)
    {
      createSaneXLFD();
    }
  }
  else
  {
    //n.append (".ttf");
    SFile f(name, fontSearchPath);
    if (f.size() > 0 && name.match ("*.hex"))
    {
      d = new SFontDelegate (f, SFontDelegate::SE_UNIFONT);
      CHECK_NEW (d);
      delegate = d;
      fontCache.put (decorated, d);
    }
    else if (f.size() > 0 && name.match ("*.bdf"))
    {
      d = new SFontDelegate (f, SFontDelegate::SE_BDF);
      CHECK_NEW (d);
      delegate = d;
      fontCache.put (decorated, d);
    }
    else if (f.size() > 0)
    {
      if (encoding.size()==0 || encoding == name || encoding == SString("unicode"))
      {
         d = new SFontDelegate (f, SFontDelegate::SE_TTF);
      }
      else
      {
         /* xlfd contains encoding */
         d = new SFontDelegate (f, SFontDelegate::SE_TTF, encoding);
      }
      CHECK_NEW (d);
      delegate = d;
      // It is obvious that we don't use fontCache at this point.
      // FIXME: this was commented out till yudit-2.3.beta-14 !!! Why?
      fontCache.put (decorated, d);
    }
    else
    {
      createSaneXLFD();
//fprintf (stderr, "name=%*.*s xlfd=%*.*s\n", SSARGS(name), SSARGS(xlfd));
      if (encoding.size()==0 || encoding == name)
      {
        d = new SFontDelegate (xlfd, SFontDelegate::SE_NATIVE);
      }
      else
      {
        d = new SFontDelegate (xlfd, SFontDelegate::SE_NATIVE, encoding);
      }
      CHECK_NEW (d);
      delegate = d;
      fontCache.put (decorated, d);
    }
  }
}
void
SFontImpl::createSaneXLFD ()
{
  SStringVector l;
  xlfd.replace("--", "-*-");
  l.split (xlfd, "-");
  if (l.size() < 14)
  {
    //fprintf (stderr, "SFontImpl: Need better XLFD %*.*s. Adding some -*-\n", SSARGS(xlfd));
    /* find a good insertion point - FIXME this is not very good here */
    unsigned int ipoint = 0;
    if (l.size() > 2)
    {
      ipoint = l.size() -1;
      while (ipoint > 0 && l[ipoint-1] != "*") ipoint--;
    }
    else if (xlfd[0] == '-')
    {
      while (ipoint < l.size()  && l[ipoint] != "*") ipoint++;
      if (ipoint >= l.size())
      {
        ipoint = 0;
      }
    }
    while (l.size() < 14) l.insert(ipoint, "*");
    xlfd = "-";
    xlfd.append(l.join ("-"));
  }
}

SFontImpl::SFontImpl (const SFontImpl& ff)
{
  name = ff.name;
  xlfd = ff.xlfd;
  matrix = ff.matrix;
  encoding = ff.encoding;
  SFontDelegate* d = (SFontDelegate*) ff.delegate;
  if (d)
  {
    d->count++;
  }
  lrFont = ff.lrFont;
  rlFont = ff.rlFont;
  delegate = d;
}

void
SFontImpl::setAttributes (const SProperties& properties)
{
  if (properties.get("LR"))
  {
    lrFont = properties["LR"]==SString("true");
  }
  if (properties.get("RL"))
  {
    rlFont = properties["RL"]==SString("true");
  }
}

SFontImpl
SFontImpl::operator= (const SFontImpl& ff)
{
  if (&ff == this) return *this;
  SFontDelegate* d;
  if (ff.delegate == delegate)
  {
    xlfd = ff.xlfd;
    matrix = ff.matrix;
    name = ff.name;
    encoding = ff.encoding;
    lrFont = ff.lrFont;
    rlFont = ff.rlFont;
    return *this;
  }

  /* Get this till name exists */
  SString decorated (name);
  decorated.append ("~");
  decorated.append (encoding);

  xlfd = ff.xlfd;
  matrix = ff.matrix;
  name = ff.name;
  encoding = ff.encoding;
  lrFont = ff.lrFont;
  rlFont = ff.rlFont;

  if (delegate)
  {
    d = (SFontDelegate*) delegate;
    d->count--;
    if (d->count==0)
    {
      if (fontCache.get (decorated))
      {
         fontCache.remove (decorated);
         delete d;
      }
      else
      {
         fprintf (stderr, "SFontImpl.cpp: FontCache does not have: %*.*s\n", 
            SSARGS(decorated));
      }
    }
  }
  d = (SFontDelegate*) ff.delegate;
  if (d)
  {
    d->count++;
  }
  delegate = d;
  return *this;
}

bool
SFontImpl::isTTF () const
{
  if (delegate)
  {
    return (((SFontDelegate*) delegate)->type==SFontDelegate::SE_TTF);
  }
  return false;
}

/**
 * \brief Try to make a fuzzy guess if we need to align the diacritics to
 *    the left or to the right.
 * left aligned marks will be rendered this way:
 *    x----basewith----x
 *         x-markwidth-x 
 * right aligned marks will be rendered this way:
 *    x----basewith----x
 *    x-markwidth-x 
 */
bool
SFontImpl::isLeftAligned (SS_UCS4 c) const
{
  if (delegate && ((SFontDelegate*) delegate)->type==SFontDelegate::SE_TTF)
  {
    return (((SFontDelegate*)delegate)->u.ttf->isLeftAligned (c));
  }
  /* 
   * X11 font non-spacing marks  will be right aligned. 
   * Except for RL scripts .
   */
  /* 
   * FIXME: 
   * In reality should check if it is a non-spacing mark.
   */
  if (c>= 0x500 && c < 0x900)
  {
     return true;
  }
  return false;
}


SFontImpl::~SFontImpl ()
{
  if (delegate)
  {
    SFontDelegate* d = (SFontDelegate*) delegate;
    d->count--;
    if (isTTF())
    if (d->count==0)
    {
      /* Get this till name exists */
      SString decorated (name);
      decorated.append ("~");
      decorated.append (encoding);
      if (fontCache.get (decorated))
      {
         fontCache.remove (decorated);
         delete d;
      }
      else
      {
         fprintf (stderr, "SFontImpl.cpp: FontCache does not have: %*.*s\n", 
            SSARGS(decorated));
      }
    }
  }
}

/**
 * set the search path for the font
 * This path will be used to locate font files on the disk
 * Currently it is used for True Type fonts.
 * @param l is the list of directories.
 */
void
SFontImpl::setPath(const SStringVector& l)
{
//fprintf (stderr, "set path ...\n");
  fontSearchPath = l;
}

/**
 * search files for property in order and set the path to the 
 * property. Always add YUDIT_DATA/fonts
 */
void
SFontImpl::guessPath (const SStringVector& files, const SString& property)
{
  
  SStringVector outDataPath;
  for (unsigned int i=0; i<files.size(); i++)
  {
    SProperties p;
    loadProperties (files[i], &p);
    if (p.get (property))
    {
      SStringVector v(p[property], ",:;");
      for (unsigned int j=0; j<v.size(); j++)
      {
         outDataPath.append (v[j]);
      }
    }
  }
  SString c1 = getHome();
  c1.append ("/.yudit/fonts");
  SString c2 = getPrefix();
  c2.append ("/fonts");
  if (outDataPath.size()!=0)
  { 
    outDataPath.append (c1); 
    outDataPath.append (c2); 
    fontSearchPath = outDataPath;
  }
  else
  {
    outDataPath.append (c1); 
    outDataPath.append (c2); 
    outDataPath.append (fontSearchPath);
    fontSearchPath = outDataPath;
  }
//fprintf (stderr, "fontpath is %*.*s\n", SSARGS(fontSearchPath.join(",")));
}
/**
 * get the path using the "yudit.default.path"
 */
void
SFontImpl::guessPath()
{
  SString c1 = getHome();
  c1.append ("/.yudit/yudit.properties");
  SString c2 = getPrefix();
  c2.append ("/config/yudit.properties");
  SStringVector v;
  v.append (c1);
  v.append (c2);
  guessPath (v, "yudit.fontpath");
}

/**
 * Scale the font to the given pointsize.
 * @param x is the x axis pointsize
 * @param y is the y axis pointsize
 */
void
SFontImpl::scale (double x, double y)
{
  SS_Matrix2D m;
  matrix = m;
  if (!delegate) return;
  SFontDelegate* d = (SFontDelegate*) delegate;;
  SStringVector l;
  char tmp[64];
  switch (d->type)
  {
  case SFontDelegate::SE_TTF:
    if (d->u.ttf->isOK())
    {
        double scale = d->u.ttf->scale ();
        m.scale (x * scale, y * scale);
        matrix = m;
    }
    break;
  case SFontDelegate::SE_NATIVE:
    /*
    * Modify the currentXLFD and load the font.
    * it will be our pixel size. nice eh? 
    * A reminder on xlfd:
    * -foundry-family-weight-slant-*-style-pixel-*-*-*-spacing-avgWidth-registry-encoding
    */
    if (d->u.native==0) break;
    l.split (xlfd, "-");
    sprintf (tmp, "%u", (unsigned int) (y + 0.5));
    if (l.size() < 14)
    {
      fprintf (stderr, "BAD XLFD %*.*s\n", SSARGS(xlfd));
      break;
    }
    l.replace (6, tmp);
    xlfd = "-";
    xlfd.append(l.join ("-"));
    break;
  case SFontDelegate::SE_UNIFONT:
    if (d->u.unifont==0) break;
    m.scale (x, y);
    matrix = m;
    break;
  case SFontDelegate::SE_BDF:
    if (d->u.bdf==0) break;
    m.scale (x, y);
    matrix = m;
    break;
  case SFontDelegate::SE_NONE:
    break;
  }
  
}

/**
 * Try to draw one single glyph.
 * @param canvas is the canvas to draw to 
 * @param m is the conversion matrix
 * @param uch is the array containing ucs4 
 * @prama len is the length of the array
 * @return true is drawn.
 */
bool
SFontImpl::draw (SCanvas* canvas, const SPen& pen, const SS_Matrix2D& m, 
  SS_UCS4 uch, bool isLRContext)
{
  if (!delegate) return 0;

  SFontDelegate* d = (SFontDelegate*) delegate;;
  SS_Matrix2D sd = m * matrix;
  switch (d->type)
  {
  case SFontDelegate::SE_TTF:
    if (d->u.ttf->isOK())
    {
        if (!d->u.ttf->width (m, uch, 0)) return 0;
        long larr[8];
        larr[0] = 'T' ; larr[1] = 'T'; larr[2] = 'F';
        larr[3] = (long)d->u.ttf; larr[4] = (long)uch;
        SString key((char*) larr, 5 * sizeof (long));

        /* FIXME: you should in fact use an instance counter. */
        key.append ((long)(sd.x0*16000.0)); /* scale-x */
        key.append ((long)(sd.y1*16000.0)); /* scale-y */
        if (!isLRContext) key.append ("R");
        double offsetX = 0.0;
        double offsetY = 0.0;
        /* for better positioning of diacritical marks */
        d->u.ttf->getBaseOffsets (sd, uch, &offsetX, &offsetY);
        // No need to be part of the key 
        //key.append ((long)(offsetX*16000.0)); 
        //key.append ((long)(offsetY*16000.0)); 
        sd.t0  += offsetX;
        sd.t1  += offsetY;
        /* reverse coordinates Y, negative m.t1 */
        if (!canvas->newpath (m.t0+offsetX, m.t1+offsetY, key))
        {
          /* not cached yet */
          d->u.ttf->draw (canvas, sd, uch, isLRContext);
        }
        canvas->fill (pen);
        return true;
    }
    break;
  case SFontDelegate::SE_NATIVE:
    if (d->u.native==0) break;
    return (d->u.native->draw (xlfd, canvas, pen, sd, uch));
  case SFontDelegate::SE_UNIFONT:
    if (d->u.unifont)
    {
        if (!d->u.unifont->width (matrix.y1, uch, 0)) return 0;
        d->u.unifont->draw (matrix.y1, canvas, pen.getForeground(), sd, uch,
           (m.x0 < 0));
        return true;
    }
    break;
  case SFontDelegate::SE_BDF:
    if (d->u.bdf)
    {
        if (!d->u.bdf->width (matrix.y1, uch, 0)) return 0;
        d->u.bdf->draw (matrix.y1, canvas, pen.getForeground(), sd, uch, 
          (m.x0 < 0));
        return true;
    }
    break;
  case SFontDelegate::SE_NONE:
    break;
  }
  return false;
}

/**
 * @param m is the conversion matrix
 * @param uch is the array containing ucs4 
 * @prama len is the length of the array
 * @return ture if it has width.
 *  width_ will be set t width if exists.
 */
 
bool
SFontImpl::width (SS_UCS4 uch, double* width_)
{
  if (!delegate) return false;
  SFontDelegate* d = (SFontDelegate*) delegate;;
  switch (d->type)
  {
  case SFontDelegate::SE_TTF:
    if (d->u.ttf->isOK())
    {
        return (d->u.ttf->width (matrix, uch, width_));
    }
    break;
  case SFontDelegate::SE_NATIVE:
    if (d->u.native==0) break;
    return (d->u.native->width (xlfd, uch, width_));
  case SFontDelegate::SE_UNIFONT:
    if (d->u.unifont)
    {
        return (d->u.unifont->width (matrix.y1, uch, width_));
    }
    break;
  case SFontDelegate::SE_BDF:
    if (d->u.bdf)
    {
        return (d->u.bdf->width (matrix.y1, uch, width_));
    }
    break;
  case SFontDelegate::SE_NONE:
     break;
  }
  return false;
}

/**
 * set the base character for better glyph positioning
 * @param base is the base character relative to which 
 * we will position all of out composing marks.
 */
void
SFontImpl::setBase(SS_UCS4 base)
{
  SFontTTF::setBase (base);
}

/**
 * return the overall width
 */
double
SFontImpl::width () const
{
  if (!delegate) return 0.0;

  SFontDelegate* d = (SFontDelegate*) delegate;;
  switch (d->type)
  {
  case SFontDelegate::SE_TTF:
    if (d->u.ttf->isOK())
    {
        return (d->u.ttf->width (matrix));
    }
    break;
  case SFontDelegate::SE_NATIVE:
    if (d->u.native==0) break;
    return d->u.native->width (xlfd);
  case SFontDelegate::SE_UNIFONT:
    if (d->u.unifont)
    {
        return (d->u.unifont->width (matrix.y1));
    }
    break;
  case SFontDelegate::SE_BDF:
    if (d->u.bdf)
    {
        return (d->u.bdf->width (matrix.y1));
    }
    break;
  case SFontDelegate::SE_NONE:
     break;
  }
  return 0.0;
}

/**
 * return the overall ascent
 */
double
SFontImpl::ascent () const
{
  if (!delegate) return 0.0;

  SFontDelegate* d = (SFontDelegate*) delegate;;
  switch (d->type)
  {
  case SFontDelegate::SE_TTF:
    if (d->u.ttf->isOK())
    {
        return (d->u.ttf->ascent (matrix));
    }
    break;
  case SFontDelegate::SE_NATIVE:
    if (d->u.native==0) break;
    return d->u.native->ascent (xlfd);
  case SFontDelegate::SE_UNIFONT:
    if (d->u.unifont)
    {
        return (d->u.unifont->ascent (matrix.y1));
    }
    break;
  case SFontDelegate::SE_BDF:
    if (d->u.bdf)
    {
        return (d->u.bdf->ascent (matrix.y1));
    }
    break;
  case SFontDelegate::SE_NONE:
     break;
  }
  return 0.0;
}


/**
 * return the overall descent
 */
double
SFontImpl::descent () const
{
  if (!delegate) return 0.0;

  SFontDelegate* d = (SFontDelegate*) delegate;;
  switch (d->type)
  {
  case SFontDelegate::SE_TTF:
    if (d->u.ttf->isOK())
    {
        return (d->u.ttf->descent (matrix));
    }
    break;
  case SFontDelegate::SE_NATIVE:
    if (d->u.native==0) break;
    return d->u.native->descent (xlfd);
  case SFontDelegate::SE_UNIFONT:
    if (d->u.unifont)
    {
        return (d->u.unifont->descent (matrix.y1));
    }
    break;
  case SFontDelegate::SE_BDF:
    if (d->u.bdf)
    {
        return (d->u.bdf->descent (matrix.y1));
    }
    break;
  case SFontDelegate::SE_NONE:
     break;
  }
  return 0.0;
}

/**
 * return the overall gap
 */
double
SFontImpl::gap () const
{
  if (!delegate) return 0.0;

  SFontDelegate* d = (SFontDelegate*) delegate;;
  switch (d->type)
  {
  case SFontDelegate::SE_TTF:
    if (d->u.ttf->isOK())
    {
        return (d->u.ttf->gap (matrix));
    }
    break;
  case SFontDelegate::SE_NATIVE:
    if (d->u.native==0) break;
    return d->u.native->gap (xlfd);
  case SFontDelegate::SE_UNIFONT:
    if (d->u.unifont)
    {
        return (d->u.unifont->gap (matrix.y1));
    }
    break;
  case SFontDelegate::SE_BDF:
    if (d->u.bdf)
    {
        return (d->u.bdf->gap (matrix.y1));
    }
    break;
  case SFontDelegate::SE_NONE:
     break;
  }
  return 0.0;
}

SObject*
SFontImpl::clone () const
{
  SFontImpl *ret =  new SFontImpl(*this);
  CHECK_NEW (ret);
  return ret;
}

bool
SFontImpl::needSoftMirror (SS_UCS4 uch, bool isLRContext) const
{
  // Sort out strong font types.
  if (isLR() && !isLRContext) return true;
  if (isRL() && isLRContext) return true;
  if (isLR() || isRL()) return false;

  if (getPUARovasType (uch) > 0) return !isLRContext;
  if (getRovasType (uch) > 0) return isLRContext;
  // rest is LR, need mirroring in RL context.
  return !isLRContext;
}
