/*
 * 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.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

import dvi.DviException;
import dvi.DviFontSpec;
import dvi.DviObject;
import dvi.DviResolution;
import dvi.DviUnit;
import dvi.api.DviContextSupport;
import dvi.api.DviFont;
import dvi.api.Glyph;
import dvi.api.SimpleMetrics;
import dvi.io.DviInputStreamReader;
import dvi.util.DviUtils;

public class PkFont
extends DviObject
implements DviFont, SimpleMetrics
{
  private static final long serialVersionUID = 1531160169076910099L;

  private final File file;

  private int idByte;
  private byte[] comment;

  private int designSize;
  private int checkSum;
  private int hppp;
  private int vppp;
  private long bodyOffset;
  private int numChars;

  private HashMap<Integer, Glyph> glyphs
      = new HashMap<Integer, Glyph>();

  public PkFont(DviContextSupport dcs, File file) 
  throws DviException
  {
    super(dcs);
    this.file = file;
    try {
      FileInputStream fis = null;
      try {
        fis = new FileInputStream(file);
        parseInputStream(fis);
      } finally {
        DviUtils.silentClose(fis);
      }
    } catch (DviException ex) {
      throw ex;
    } catch (IOException ex) {
      throw new DviException(ex);
    }
  }

  public PkFont(DviContextSupport dcs, InputStream is) 
  throws DviException
  {
    super(dcs);
    try {
      parseInputStream(is);
    } catch (DviException ex) {
      throw ex;
    } catch (IOException ex) {
      throw new DviException(ex);
    }
    this.file = null;
  }

  public boolean hasChar(int code)
  {
    return glyphs.containsKey(code);
  }

  public Glyph getGlyph(LogicalFont lf, int code)
  {
    return glyphs.get(code);
  }

  public int getWidth(int code)
  {
    PkGlyph g = (PkGlyph) glyphs.get(code);
    if (g == null) return 0;
    return g.width();
  }

  public int getHeight(int code)
  {
    PkGlyph g = (PkGlyph) glyphs.get(code);
    if (g == null) return 0;
    return g.height();
  }

  public int getTfmWidth(int code)
  {
    PkGlyph g = (PkGlyph) glyphs.get(code);
    if (g == null) return 0;
    return g.getTfmWidth();
  }
  
  public int getIdByte() {
	  return idByte;
  }

  public byte[] getComment() {
	  return comment.clone();
  }

  public int getDesignSize() {
	  return designSize;
  }

  public int getCheckSum() {
	  return checkSum;
  }

  // TODO: rename this method
  public int getHppp() {
	  return hppp;
  }

  // TODO: rename this method
  public int getVppp() {
	  return vppp;
  }

  public long getBodyOffset() {
	  return bodyOffset;
  }

  public int getNumChars() {
	  return numChars;
  }

  public void parseInputStream(InputStream is)
  throws IOException, DviException
  {
    DviInputStreamReader in = new DviInputStreamReader(
      new BufferedInputStream(is, 8192)
    );

    if (in.readU1() != PkConstants.PK_PRE)
      throw new DviException
        ("PK file doesn't start with PRE.");

    idByte     = in.readU1();
    if (idByte != PkConstants.PK_ID_BYTE)
      throw new DviException
        ("Unrecognized PK file: wrong idByte: " + idByte);
    int commentLen = in.readU1();
    comment = new byte[commentLen];
    in.readFully(comment);
    designSize = in.readS4();
    checkSum   = in.readS4();
    hppp       = in.readS4();
    vppp       = in.readS4();
    bodyOffset = in.getOffset();

    numChars = 0;

    boolean stop = false;
    while (!stop) {
      byte [] buf;
      int c = in.readU1();
      switch (c) {
        case PkConstants.PK_XXX4:
        case PkConstants.PK_XXX3:
        case PkConstants.PK_XXX2:
        case PkConstants.PK_XXX1: {
          int len;
          len = in.readU(c - PkConstants.PK_XXX1 + 1);
          buf = new byte[len];
          in.readFully(buf);
          // System.out.println("xxx: \"" + new String(buf) + "\"");
          break;
        }
        case PkConstants.PK_YYY: {
//          int yyy = in.readS4();
        	in.readS4();
          // System.out.println("yyy: " + yyy);
          break;
        }
        case PkConstants.PK_NOP:
          break;
        case PkConstants.PK_POST:
          stop = true;
          break;
        case PkConstants.PK_PRE:
          throw new DviException
            ("broken PK file: PRE found in the body of PK file: "
              + file.getPath());
        default:
        {
//          long pos = in.getOffset() - 1;
          PkGlyph g = (PkGlyph) PkGlyph.readFromInput(c, in);
          glyphs.put(g.code(), g);
        }
      }
    }
  }
  
  public static String getDviResourceName(LogicalFont lf)
  {
    DviFontSpec fs = lf.fontSpec();
    DviResolution res = lf.resolution();
    DviUnit dviUnit = lf.dviUnit();
    int useDpi = (int) (
      (double) res.dpi() * fs.spaceSize() * dviUnit.magnification()
        / fs.designSize() / 1000
    );
    if (useDpi <= 0) return null;
    String pk = fs.name() + "." + useDpi + "pk";
    return pk;
  }
}
