/*
 * 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 jp.sourceforge.dvibrowser.dvicore.font;

import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

import jp.sourceforge.dvibrowser.dvicore.DviConstants;
import jp.sourceforge.dvibrowser.dvicore.DviException;
import jp.sourceforge.dvibrowser.dvicore.DviFontSpec;
import jp.sourceforge.dvibrowser.dvicore.DviFontTable;
import jp.sourceforge.dvibrowser.dvicore.DviResolution;
import jp.sourceforge.dvibrowser.dvicore.api.BinaryDevice;
import jp.sourceforge.dvibrowser.dvicore.api.DevicePainter;
import jp.sourceforge.dvibrowser.dvicore.api.DviContextSupport;
import jp.sourceforge.dvibrowser.dvicore.api.DviExecutor;
import jp.sourceforge.dvibrowser.dvicore.api.Geometer;
import jp.sourceforge.dvibrowser.dvicore.cmd.DviCommand;
import jp.sourceforge.dvibrowser.dvicore.io.ByteArrayDviData;
import jp.sourceforge.dvibrowser.dvicore.io.DviInputStreamReader;
import jp.sourceforge.dvibrowser.dvicore.render.VirtualFontGeometer;
import jp.sourceforge.dvibrowser.dvicore.util.DviUtils;


public class VirtualFont
extends AbstractDynamicPkFont
{
  private static final long serialVersionUID = -4723597208297552498L;

  private final HashMap<Integer, GlyphInfo> glyphInfos
    = new HashMap<Integer, GlyphInfo>();
  
  private final String name;

  public VirtualFont(DviContextSupport dcs, File file)
  throws DviException
  {
    super(dcs);
    try {
      FileInputStream fis = null;
      try {
        fis = new FileInputStream(file);
        parseInputStream(fis);
      } finally {
        DviUtils.silentClose(fis);
      }
    } catch (IOException ex) {
      throw new DviException(ex);
    }
    name = getClass().getName()
      + "--" + file
      + "--" + file.lastModified()
      + "--[" + new String(comment) + "]"
      ;
  }

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


  private       int idByte;
  private   byte [] comment;
  private       int checkSum;
  private       int designSize;
  private DviFontTable fontTable;
  private       int defaultFontNumber = DviConstants.UNDEFINED_FONT_NUMBER;

  public boolean hasChar(int code)
  throws DviException
  {
    return glyphInfos.containsKey(code);
  }

  public String getName()
  {
    return name;
  }

  public int getIdByte() {
	  return idByte;
  }

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

  public int getCheckSum() {
	  return checkSum;
  }

  public int getDesignSize() {
	  return designSize;
  }

  public int getDefaultFontNumber() {
	  return defaultFontNumber;
  }
  
  private void parseInputStream(InputStream is)
  throws IOException, DviException
  {
    DviInputStreamReader in
      = new DviInputStreamReader(
        new BufferedInputStream(is, 1024)
      );
    try {
      if (DviCommand.DVI_PRE != in.readU1())
        throw new DviException
          ("VF data doesn't start with PRE");

      idByte = in.readU1();
      if (idByte != DviConstants.VF_ID_BYTE)
        throw new DviException
          ("unknown id byte: " + idByte);

      int commentSize = in.readU1();
      comment = new byte [commentSize];
      in.readFully(comment);

      checkSum   = in.readS4();
      designSize = in.readS4();

      int c;

      fontTable = new DviFontTable();

      boolean stop = false;
      do {
        c = in.readU1();

        switch (c) {
          case DviCommand.DVI_FNT_DEF4:
          case DviCommand.DVI_FNT_DEF3:
          case DviCommand.DVI_FNT_DEF2:
          case DviCommand.DVI_FNT_DEF1: {
            int t = c - DviCommand.DVI_FNT_DEF1 + 1;
            int fn, cs, ss, ds, al, nl;
            byte [] fontName;

            fn = in.readS(t);
            cs = in.readS4();
            ss = in.readS4();
            ds = in.readS4();
            al = in.readU1();
            nl = in.readU1();
  
            if (defaultFontNumber == DviConstants.UNDEFINED_FONT_NUMBER)
              defaultFontNumber = fn;
  
            fontName = new byte[al+nl];
            in.readFully(fontName);

            fontTable.put(
              fn,
              DviFontSpec.getInstance(
                cs, ss, ds, al, nl, fontName
              )
            );
            break;
          }
          default:
            stop = true;
        }
      } while (!stop);

      while (true) {
//        int cmdLength = 1;
    
        GlyphInfo gi = new GlyphInfo();
        int macroLength;
//        int packetLength;
    
        if (c < 242) {  // short chararacter
          gi.type = DviConstants.VF_SHORT_CHAR;
          macroLength = c;
//          packetLength = c + 5;
          gi.code = in.readU1();
          gi.tfmw = in.readS3();
          gi.macro = new byte [macroLength];
          in.readFully(gi.macro);
        } else if (c == 242) { // long character
          gi.type = DviConstants.VF_LONG_CHAR;
          macroLength = in.readS4();
//          packetLength = macroLength + 13;
          gi.code = in.readS4();
          gi.tfmw = in.readS4();
          gi.macro = new byte [macroLength];
          in.readFully(gi.macro);
        } else if (c == DviCommand.DVI_POST) {
          break;
        } else {
          throw new DviException
            ("VF data doesn't terminate with POST");
        }
        glyphInfos.put(gi.code, gi);

        c = in.readU1();
      }
    } catch (EOFException ex) {
      throw new DviException
        ("VF file ended while reading glyphs.", ex);
    } catch (IOException ex) {
      throw new DviException(ex);
    }
  }

  private static class GlyphInfo
  implements java.io.Serializable
  {
	private static final long serialVersionUID = -3968612702338551000L;
	private int type;
    private int code;
    private int tfmw;
    private byte [] macro;

    public String toString() {
      return getClass().getName()
        + "[type=" + type
        + " code=" + code
        + " tfwm=" + tfmw
        + " macroLength=" + macro.length
        + "]";
    }
  }

  protected PkGlyph generatePkGlyph(LogicalFont lf, int code)
  throws DviException
  {
    GlyphInfo gi = glyphInfos.get(code);
    if (gi == null) return null;

    DviResolution res = lf.resolution();

    RunLengthEncodedGlyph rlg = new RunLengthEncodedGlyph();
    BinaryDevice dev = rlg.getBinaryDevice(res);
    // This is a trick to use VirtualFontGeometer.
    dev.translate(-res.dpi(), -res.dpi());

    DviExecutor exe = getDviContext().newDviExecutor();
    DevicePainter dp = getDviContext().newDevicePainter();
    dp.setOutput(dev);
    Geometer geometer = new VirtualFontGeometer(this, lf.fontSpec());
    geometer.setPainter(dp);
    DviFontTable ft =
      fontTable.transformForVirtualFont(
        lf.fontSpec(), designSize
      );
  
    {
      final byte [] buf = new byte[gi.macro.length + 1 + 4];
      final int fn = defaultFontNumber;
      buf[0] = (byte) DviCommand.DVI_FONT4;
      buf[1] = (byte)((fn >>> 24) & 0xff);
      buf[2] = (byte)((fn >>> 16) & 0xff);
      buf[3] = (byte)((fn >>>  8) & 0xff);
      buf[4] = (byte)((fn >>>  0) & 0xff);
      System.arraycopy(gi.macro, 0, buf, 5, gi.macro.length);
  
      exe.execute(
        new ByteArrayDviData(
          buf, lf.dviUnit(), ft
        ),
         geometer
      );
    }

    return rlg.toPkGlyph();
  }
  
  public static String getDviResourceName(LogicalFont logicalFont)
  {
    return logicalFont.fontSpec().name() + ".vf";
  }
}
