/*
 * Copyright (c) 2009, Takeyuki Nagao
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the
 * following conditions are met:
 * 
 *  * Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer.
 *  * Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *    
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */

package dvi.font;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;

import dvi.DviException;
import dvi.DviFontSpec;
import dvi.DviResolution;
import dvi.DviUnit;
import dvi.api.CharacterCodeMapper;
import dvi.api.DviContextSupport;

public class AWTDynamicPkFont
extends AbstractDynamicPkFont
{
  private static final long serialVersionUID = 6218737476095318238L;
  private final Font font;
  private CharacterCodeMapper mapper;
  private boolean renderBoundigBoxes = false;

  public AWTDynamicPkFont(DviContextSupport dcs, Font font)
  throws DviException
  {
    this(dcs, font, null);
  }
  public AWTDynamicPkFont(DviContextSupport dcs, Font font, CharacterCodeMapper mapper)
  throws DviException
  {
    super(dcs);
    if (font == null)
      throw new NullPointerException
        ("font is null");
    this.font = font;
    this.setCharacterCodeMapper(mapper);
  }
  
  public boolean hasChar(int code) { return font.canDisplay(code); }
  
  protected String mapToUnicode(LogicalFont lf, int code) throws DviException
  {
    if (getCharacterCodeMapper() != null) {
      return getCharacterCodeMapper().mapCharacterCodeToUnicode(lf, code);
    } else {
      return getDviContext().getCharacterCodeMapper(lf)
        .mapCharacterCodeToUnicode(lf, code);
    }
  }

  protected PkGlyph generatePkGlyph(LogicalFont lf, int code)
  throws DviException
  {
    String unicode = mapToUnicode(lf, code);
    
//    System.out.println("str=(" + unicode + ") code=0x" + Integer.toHexString(code) + " hex=" + DviUtils.hexDump(unicode));
    
    Graphics2D g;
    BufferedImage img;

    // We instantiate an image to get Graphics2D.
    img = new BufferedImage(
      1, 1,
      BufferedImage.TYPE_BYTE_GRAY
    );

    DviUnit dviUnit = lf.dviUnit();
    DviFontSpec fs = lf.fontSpec();
    DviResolution res = lf.resolution();
    float fontSize = (float) dviUnit.mapToPixelDouble(fs.spaceSize(), res.dpi());
    Font derivedFont = font.deriveFont(fontSize);
    
//    System.out.println(Integer.toHexString(code) + "(" + unicode + "): " + derivedFont);
    
    g = img.createGraphics();
    FontMetrics fm = g.getFontMetrics(derivedFont);
    int descent = fm.getMaxDescent();
    int ascent = fm.getMaxAscent();
    int leading = fm.getLeading();
    int maxAdvance = fm.getMaxAdvance();
    int maxAscent = fm.getMaxAscent();
    int maxDescent = fm.getMaxDescent();
    
    Rectangle2D charBounds = fm.getMaxCharBounds(g);
    Rectangle2D bounds = fm.getStringBounds(unicode, g);
    Rectangle2D boundsByChars = fm.getStringBounds(unicode.toCharArray(), 0, 1, g);
    int x = (int) Math.floor(bounds.getMinX());
    int y = (int) Math.floor(bounds.getMinY());
    int width = (int) (bounds.getWidth() + 0.5);
    int height = (int) (bounds.getHeight() + 0.5);
    //height = maxAscent + maxDescent;
    height = (int)(charBounds.getHeight() + 0.5);
    
    int padding = 600;
    
    // padding = Math.max(maxAscent, maxDescent);
    padding = (int) (charBounds.getHeight() + 0.5);
    
    y = - maxAscent;
    
    x += -padding - maxAdvance;
    y += -padding;
    width += padding * 2 + maxAdvance * 2;
    height += padding * 2;
    
    int bw = width + 1;
    int bh = height + 1;
    
//    System.out.println("codePoint=" + String.format("x0%06x", code));
//    System.out.println("bounds=" + bounds);
//    System.out.println("charBounds=" + charBounds);
//    System.out.println("padding=" + padding);
//    System.out.println("boundsByChars=" + boundsByChars);
//    System.out.println("maxAdvance=" + maxAdvance);
//    System.out.println("ascent=" + ascent + " max=" + fm.getMaxAscent());
//    System.out.println("descent=" + descent + " max=" + fm.getMaxDescent());
//    System.out.println("leading=" + leading);
//    System.out.println("width=" + width);
//    System.out.println("height=" + height);
//    System.out.println("(x, y)=(" + x + "," + y + ")");
    g.dispose();
    g = null;
    
    img = new BufferedImage(
      bw, bh,
      BufferedImage.TYPE_BYTE_GRAY
    );
    g = img.createGraphics();
    g.setFont(derivedFont);
    g.drawString(unicode, -x, -y);
    if (renderBoundigBoxes()) {
      g.drawRect(0, 0, width - 1, height - 1);
      g.drawRect(0, 0, width - 1 , -y - 1);
      g.drawRect(0, 0, -x - 1, height -1);
    }
    g.dispose();

    Raster raster = img.getRaster();
    DataBufferByte data = (DataBufferByte) raster.getDataBuffer();
    RunLengthEncodedGlyph rlg = RunLengthEncodedGlyph.readByteGray(
      data.getData(),
      bw, bh,
      -x, -y
    );
    PkGlyph glyph = rlg.toPkGlyph();
//    System.out.println(code);
//    BinaryDevice out = new dvi.render.DumpBinaryDevice(System.out);
//    glyph.rasterizeTo(out);
    return glyph;
  }
  public void setRenderBoundigBoxes(boolean renderBoundigBoxes) {
    this.renderBoundigBoxes = renderBoundigBoxes;
  }
  public boolean renderBoundigBoxes() {
    return renderBoundigBoxes;
  }
  public CharacterCodeMapper getCharacterCodeMapper() {
    return mapper;
  }
  public void setCharacterCodeMapper(CharacterCodeMapper mapper) {
    this.mapper = mapper;
  }
}
