/*
 * 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.DviConstants;
import dvi.DviException;
import dvi.DviFontSpec;
import dvi.DviObject;
import dvi.api.DviContextSupport;
import dvi.api.DviInput;
import dvi.api.FullMetrics;
import dvi.io.DviInputStreamReader;
import dvi.util.DviUtils;

// TODO: support Tategumi

public class TexFontMetrics
extends DviObject
implements FullMetrics
{
  private static final long serialVersionUID = 962603909880917980L;

  // TODO: outsource JFM configuration
  private boolean enableJFM = true;

  private boolean isJFM; // For Japanese pTeX

private int checkSum;
  private int designSize;

  private final String name;

  private final HashMap<Integer, Record> ct2rec
    = new HashMap<Integer, Record>();
  private HashMap<Integer, Integer> code2ct;

  public TexFontMetrics(DviContextSupport dcs, File file)
  throws DviException
  {
    super(dcs);
    FileInputStream is = null;
    try {
      is = new FileInputStream(file);
      parseInputStream(is);
    } catch (IOException ex) {
      DviUtils.silentClose(is);
      throw new DviException(ex);
    }
    name = getClass().getName()
      + "--" + file.getPath()
      + "--" + file.lastModified()
      ;
  }

  public TexFontMetrics(DviContextSupport dcs, InputStream is)
  throws DviException
  {
    super(dcs);
    try {
      parseInputStream(is);
    } catch (IOException ex) {
      throw new DviException(ex);
    }
    name = getClass().getName()
      + "--" + is
      + "--" + System.currentTimeMillis()
      ;
  }

  @SuppressWarnings("unused")
  protected void parseInputStream(InputStream is)
  throws IOException, DviException
  {
    DviInputStreamReader in = new DviInputStreamReader(
      new BufferedInputStream(is, 1024)
    );

    long start = in.getOffset();

    int id; // For Japanese pTeX
    int nt; // For Japanese pTeX
    int lf;
    int lh;
    int bc;
    int ec;
    int nw;
    int nh;
    int nd;
    int ni;
    int nl;
    int nk;
    int ne;
    int np;

    int numChars;

    if (enableJFM) {
      id = in.readU2();
      nt = in.readU2();
      isJFM = (id == DviConstants.TFM_ID_TATEGUMI || id == DviConstants.TFM_ID_YOKOGUMI);
      if (isJFM) {
        lf = in.readU2();
        lh = in.readU2();
      } else {
        lf = id;
        lh = nt;
        id = 0;
        nt = 0;
      }
    } else {
      id = 0;
      nt = 0;
      lf = in.readU2();
      lh = in.readU2();
      isJFM = false;
    }

    bc = in.readU2();
    ec = in.readU2();
    nw = in.readU2();
    nh = in.readU2();
    nd = in.readU2();
    ni = in.readU2();
    nl = in.readU2();
    nk = in.readU2();
    ne = in.readU2();
    np = in.readU2();
    numChars = ec - bc + 1;

    // header
    int [] header_t = readBuffer(in, lh);
    {
      checkSum   = header_t[0];
      designSize = header_t[1];
    }

    // character type map (for Japanese JFM)
    if (isJFM) {
      code2ct = new HashMap<Integer, Integer>();
      for (int i=0; i<nt; i++) {
        final int from = in.readU2();
        final int   to = in.readU2();
        code2ct.put(from, to);
      }
    } else {
      code2ct = null;
    }

    int [] char_info_t = readBuffer(in, numChars);
    int []     width_t = readBuffer(in, nw);
    int []    height_t = readBuffer(in, nh);
    int []     depth_t = readBuffer(in, nd);
    int []    italic_t = readBuffer(in, ni);
    int []  lig_kern_t = readBuffer(in, nl);
    int []      kern_t = readBuffer(in, nk);
    int []     exten_t = readBuffer(in, ne);
    int []     param_t = readBuffer(in, np);

    long size = in.getOffset() - start;
    if (lf * 4 != size ||
        lf != (isJFM ? 7 : 6) +
                           lh +
                           nt +
                     numChars +
                           nw +
                           nh +
                           nd +
                           ni +
                           nl +
                           nk +
                           ne +
                           np)
        throw new DviException
          ("Length mismatch in TFM data");

    if (isJFM && bc != 0)
      throw new DviException
        ("bc must be 0 in JFM data.");

    for (int i=0; i<numChars; i++) {
      final int  ct = i + bc;
      final int  ci = char_info_t[i];
      final int  wi = (ci >>> 24) & 0xff;
      final int  hi = (ci >>> 20) & 0x0f;
      final int  di = (ci >>> 16) & 0x0f;
      final int  ii = (ci >>> 10) & 0x3f;
      final int tag = (ci >>> 8)  & 0x03;
      final int rem = (ci >>> 0)  & 0xff;

      final int  width = width_t [wi];
      final int height = height_t[hi];
      final int  depth = depth_t [di];
      final int italic = italic_t[ii];

      ct2rec.put(ct, new Record(width, height, depth, italic));
    }
  }

  private static int [] readBuffer(DviInput in, int length)
  throws IOException
  {
    int [] buf = new int[length];
    for (int i=0; i<length; i++)
      buf[i] = in.readU4();
    return buf;
  }


  public String getName()
  {
    return name;
  }
  
  public int getCheckSum() {
	  return checkSum;
  }

  public int getDesignSize() {
	  return designSize;
  }

  public boolean isJFM() {
	  return isJFM;
  }

  public boolean hasChar(int code)
  {
    return isJFM ? true : ct2rec.containsKey(code);
  }

  private int codeToCt(int code)
  {
    return (code2ct == null) ? code :
      code2ct.containsKey(code) ? code2ct.get(code) : 0;
  }
  private Record getRecord(int code)
  {
    return ct2rec.get(codeToCt(code));
  }

  public int getTfmWidth(int code)
  {
    Record rec = getRecord(code);
    return (rec != null) ? rec.width : 0;
  }
  public int getTfmHeight(int code)
  {
    Record rec = getRecord(code);
    return (rec != null) ? rec.height : 0;
  }
  public int getTfmDepth(int code)
  {
    Record rec = getRecord(code);
    return (rec != null) ? rec.depth : 0;
  }
  public int getTfmItalic(int code)
  {
    Record rec = getRecord(code);
    return (rec != null) ? rec.italic : 0;
  }
  
  public static String getTfmFilename(DviFontSpec spec)
  {
    String tfm = spec.name() + ".tfm";
    return tfm;
  }

  private static class Record implements java.io.Serializable {
    private static final long serialVersionUID = 1638947545775532119L;
    private final int width;
    private final int height;
    private final int depth;
    private final int italic;

    private Record(int width, int height, int depth, int italic) {
      this.width = width;
      this.height = height;
      this.depth = depth;
      this.italic = italic;
    }
  }
}
