/*
 * This program is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
 * Foundation.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 * or from the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * 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 Lesser General Public License for more details.
 *
 * Copyright (c) 2006 - 2009 Pentaho Corporation and Contributors.  All rights reserved.
 */

package org.pentaho.reporting.libraries.fonts.itext;

import java.util.Arrays;

import com.lowagie.text.pdf.BaseFont;
import org.pentaho.reporting.libraries.fonts.encoding.CodePointUtilities;
import org.pentaho.reporting.libraries.fonts.registry.BaselineInfo;
import org.pentaho.reporting.libraries.fonts.registry.FontMetrics;
import org.pentaho.reporting.libraries.fonts.LibFontsDefaults;
import org.pentaho.reporting.libraries.fonts.tools.StrictGeomUtility;

/**
 * Creation-Date: 22.07.2007, 19:04:00
 *
 * @author Thomas Morgner
 */
public class BaseFontFontMetrics implements FontMetrics
{
  private BaseFont baseFont;
  private float size;
  private long xHeight;
  private char[] cpBuffer;
  private long[] cachedWidths;
  private long ascent;
  private long descent;
  private long leading;
  private long sizeScaled;
  private long italicsAngle;
  private long maxAscent;
  private long maxDescent;
  private long maxCharAdvance;
  private boolean trueTypeFont;
  private transient BaselineInfo cachedBaselineInfo;

  public BaseFontFontMetrics(final BaseFont baseFont, final float size)
  {
    if (baseFont == null)
    {
      throw new NullPointerException("BaseFont is invalid.");
    }

    this.baseFont = baseFont;
    this.size = size;
    this.cpBuffer = new char[4];
    this.cachedWidths = new long[256 - 32];
    Arrays.fill(cachedWidths, -1);

    sizeScaled = StrictGeomUtility.toInternalValue(size);

    this.ascent = (long) baseFont.getFontDescriptor(BaseFont.AWT_ASCENT, sizeScaled);
    this.descent = (long) -baseFont.getFontDescriptor(BaseFont.AWT_DESCENT, sizeScaled);
    this.leading = (long) baseFont.getFontDescriptor(BaseFont.AWT_LEADING, sizeScaled);
    italicsAngle = StrictGeomUtility.toInternalValue(baseFont.getFontDescriptor(BaseFont.ITALICANGLE, size));
    maxAscent = (long) baseFont.getFontDescriptor(BaseFont.BBOXURY, sizeScaled);
    maxDescent = (long) -baseFont.getFontDescriptor(BaseFont.BBOXLLY, sizeScaled);
    maxCharAdvance = (long) baseFont.getFontDescriptor(BaseFont.AWT_MAXADVANCE, sizeScaled);

    final int[] charBBox = this.baseFont.getCharBBox('x');
    if (charBBox != null)
    {
      this.xHeight = (long) (charBBox[3] * size);
    }
    if (this.xHeight == 0)
    {
      this.xHeight = getAscent() / 2;
    }
    this.trueTypeFont = baseFont.getFontType() == BaseFont.FONT_TYPE_TT ||
        baseFont.getFontType() == BaseFont.FONT_TYPE_TTUNI;
  }

  public boolean isTrueTypeFont()
  {
    return trueTypeFont;
  }

  public long getAscent()
  {
    return ascent;
  }

  public long getDescent()
  {
    return descent;
  }

  public long getLeading()
  {
    return leading;
  }

  public long getXHeight()
  {
    return xHeight;
  }

  public long getOverlinePosition()
  {
    return getLeading() - Math.max (1000, sizeScaled / 20);
  }

  public long getUnderlinePosition()
  {
    return (getLeading() + getMaxAscent()) +  Math.max (1000, sizeScaled / 20);
  }

  public long getStrikeThroughPosition()
  {
    return getMaxAscent() - (long) (LibFontsDefaults.DEFAULT_STRIKETHROUGH_POSITION * getXHeight());
  }

  public long getItalicAngle()
  {
    return italicsAngle;
  }

  public long getMaxAscent()
  {
    return maxAscent;
  }

  public long getMaxDescent()
  {
    return maxDescent;
  }

  public long getMaxHeight()
  {
    return getMaxAscent() + getMaxDescent() + getLeading();
  }

  public long getMaxCharAdvance()
  {
    return maxCharAdvance;
  }

  public long getCharWidth(final int character)
  {
    if (character >= 32 && character < 256)
    {
      // can be cached ..
      final int index = character - 32;
      final long cachedWidth = cachedWidths[index];
      if (cachedWidth >= 0)
      {
        return cachedWidth;
      }

      final int retval = CodePointUtilities.toChars(character, cpBuffer, 0);

      if (retval == 1)
      {
        final char char1 = cpBuffer[0];
        if (char1 < 128 || (char1 >= 160 && char1 <= 255))
        {
          final long width = (long) (baseFont.getWidth(char1) * size);
          cachedWidths[index] = width;
          return width;
        }
      }
      else if (retval < 1)
      {
        cachedWidths[index] = 0;
        return 0;
      }

      final long width = (long) (baseFont.getWidth(new String(cpBuffer, 0, retval)) * size);
      cachedWidths[index] = width;
      return width;
    }

    final int retval = CodePointUtilities.toChars(character, cpBuffer, 0);
    if (retval == 1)
    {
      final char char1 = cpBuffer[0];
      if (char1 < 128 || (char1 >= 160 && char1 <= 255))
      {
        return (long) (baseFont.getWidth(char1) * size);
      }
    }
    else if (retval < 1)
    {
      return 0;
    }

    return (long) (baseFont.getWidth(new String(cpBuffer, 0, retval)) * size);
  }

  public long getKerning(final int previous, final int codePoint)
  {
    return (long) (size * baseFont.getKerning((char) previous, (char) codePoint));
  }

  /**
   * Is it guaranteed that the font always returns the same baseline info objct?
   *
   * @return true, if the baseline info in question is always the same, false otherwise.
   */
  public boolean isUniformFontMetrics()
  {
    return true;
  }

  public BaselineInfo getBaselines(final int c, BaselineInfo info)
  {
    if (cachedBaselineInfo != null)
    {
      if (info == null)
      {
        info = new BaselineInfo();
      }
      info.update(cachedBaselineInfo);
      return info;
    }

    if (info == null)
    {
      info = new BaselineInfo();
    }

    // If we had more data, we could surely create something better. Well, this has to be enough ..
    final long maxAscent = getMaxAscent();
    info.setBaseline(BaselineInfo.MATHEMATICAL, maxAscent - getXHeight());
    info.setBaseline(BaselineInfo.IDEOGRAPHIC, getMaxHeight());
    info.setBaseline(BaselineInfo.MIDDLE, maxAscent / 2);
    info.setBaseline(BaselineInfo.ALPHABETIC, maxAscent);
    info.setBaseline(BaselineInfo.CENTRAL, maxAscent / 2);
    info.setBaseline(BaselineInfo.HANGING, maxAscent - getXHeight());
    info.setDominantBaseline(BaselineInfo.ALPHABETIC);

    final BaselineInfo cached = new BaselineInfo();
    cached.update(info);
    cachedBaselineInfo = cached;
    return info;
  }

  public BaseFont getBaseFont()
  {
    return baseFont;
  }
}
