// Copyright 2001, FreeHEP.
package org.freehep.graphicsio.swf;

import java.awt.geom.Rectangle2D;
import java.io.IOException;

/**
 * DefineFont2 TAG.
 *
 * @author Mark Donszelmann
 * @author Charles Loomis
 * @author Steve Hannah
 * @version $Id: DefineFont2.java,v 1.4 2003/04/25 23:12:51 duns Exp $
 */
public class DefineFont2
    extends DefinitionTag implements SWFConstants {

    private int character;
    private boolean shiftJIS = false;
    private boolean ansi = false;
    private boolean wideOffsets = false;
    private boolean wideCodes = false;
    private boolean italic = false;
    private boolean bold = false;
    private int languageCode;
    private String name;
    private long[] offsets;
    private long codeOffset;
    private SWFShape[] shapes;
    private int[] codes;
    private int ascent, descent, leading;
    private short[] advances;
    private Rectangle2D[] bounds;
    private KerningRecord[] kerning = new KerningRecord[0];

    public DefineFont2() {
        super(48, 3);
    }

    public DefineFont2 (int character,
                        boolean italic, boolean bold, String name,
                        SWFShape[] shapes, int[] codes) {
        this(character, false, false, italic, bold, true, true, LANGUAGE_LATIN,
             name, shapes, codes, 0, 0, 0, null, null, null);
    }

    public DefineFont2 (int character,
                        boolean shiftJIS, boolean ansi,
                        boolean italic, boolean bold,
                        boolean wideOffsets, boolean wideCodes,
                        int languageCode,
                        String name,
                        SWFShape[] shapes, int[] codes,
                        int ascent, int descent, int leading, short[] advances,
                        Rectangle2D[] bounds, KerningRecord[] kerning) {
        this();
        this.character = character;
        this.shiftJIS = shiftJIS;
        this.ansi = ansi;
        this.italic = italic;
        this.bold = bold;
        this.wideOffsets = wideOffsets;
        this.wideCodes = wideCodes;
        this.languageCode = languageCode;
        this.name = name;
        this.shapes = shapes;
        this.codes = codes;
        this.ascent = ascent;
        this.descent = descent;
        this.leading = leading;
        this.advances = advances;
        this.bounds = bounds;
        this.kerning = kerning;
    }

    public SWFTag read(int tagID, SWFInputStream swf, int len)
        throws IOException {

        DefineFont2 tag = new DefineFont2();
        tag.character = swf.readUnsignedShort();
        swf.getDictionary().put(tag.character, tag);

        boolean hasLayout = swf.readBitFlag();
        tag.shiftJIS = swf.readBitFlag();
        boolean reserved = swf.readBitFlag();
        tag.ansi = swf.readBitFlag();
        tag.wideOffsets = swf.readBitFlag();
        tag.wideCodes = swf.readBitFlag();
        tag.italic = swf.readBitFlag();
        tag.bold = swf.readBitFlag();
        tag.languageCode = swf.readLanguageCode();

        int nameLength = swf.readUnsignedByte();
        if (swf.getVersion() >= 6) {
            tag.name = swf.readUTF();
        } else {
            tag.name = new String(swf.readByte(nameLength));
        }

        int glyphCount = swf.readUnsignedShort();

        tag.offsets = new long[glyphCount];
        for (int i=0; i<glyphCount; i++) {
            tag.offsets[i] = (tag.wideOffsets) ? swf.readUnsignedInt()
                                               : (long)swf.readUnsignedShort();
        }

        tag.codeOffset = (tag.wideOffsets) ? swf.readUnsignedInt() : (long)swf.readUnsignedShort();

        tag.shapes = new SWFShape[glyphCount];
        for (int i=0; i<glyphCount; i++) {
            tag.shapes[i] = new SWFShape(swf);
        }

        tag.codes = (tag.wideCodes) ? swf.readUnsignedShort(glyphCount)
                                    : swf.readUnsignedByte(glyphCount);

        if (hasLayout) {
            tag.ascent = swf.readShort();
            tag.descent = swf.readShort();
            tag.leading = swf.readShort();
            tag.advances = swf.readShort(glyphCount);
            tag.bounds = new Rectangle2D[glyphCount];
            for (int i=0; i<glyphCount; i++) {
                tag.bounds[i] = swf.readRect();
            }

            int kerningCount = swf.readUnsignedShort();
            tag.kerning = new KerningRecord[kerningCount];
            for (int i=0; i<kerningCount; i++) {
                tag.kerning[i] = new KerningRecord(swf, tag.wideCodes);
            }
        }
        return tag;
    }

    public void write(int tagID, SWFOutputStream swf) throws IOException {
        boolean hasLayout =  (ascent != 0) || (descent != 0) || (leading != 0)
                          || (advances != null) || (bounds != null) || (kerning != null);

        swf.writeUnsignedShort(character);
        swf.writeBitFlag(hasLayout);
        swf.writeBitFlag(shiftJIS);
        swf.writeUBits(0, 1);        // reserved
        swf.writeBitFlag(ansi);
        swf.writeBitFlag(wideOffsets);
        swf.writeBitFlag(wideCodes);
        swf.writeBitFlag(italic);
        swf.writeBitFlag(bold);
        if (swf.getVersion() >= 6) {
            swf.writeLanguageCode(languageCode);
        } else {
            swf.writeUnsignedByte(0);
        }

        swf.writeUnsignedByte(name.length());
        if (swf.getVersion() >= 6) {
            swf.writeUTF(name);
        } else {
            swf.writeByte(name.getBytes());
        }

        // Shapes
        swf.pushBuffer();
        int[] offsets = new int[shapes.length];
        int inc = wideOffsets ? 4 : 2;
        inc = (offsets.length + 1) * inc;
        for (int i=0; i<shapes.length; i++) {
            offsets[i] = swf.getBufferLength() + inc;
            shapes[i].write(swf);
            swf.byteAlign();
        }
        codeOffset = swf.getBufferLength() + inc;
        swf.popBuffer();
        swf.writeUnsignedShort(offsets.length);

        // Offset Table
        for (int i=0; i<offsets.length; i++) {
            if (wideOffsets) {
                swf.writeUnsignedInt(offsets[i]);
            } else {
                swf.writeUnsignedShort((int)offsets[i]);
            }
        }
        if (wideOffsets) {
            swf.writeUnsignedInt(codeOffset);
        } else {
            swf.writeUnsignedShort((int)codeOffset);
        }
        swf.append();

        if (wideCodes) {
            for (int i=0; i< offsets.length; i++) {
                swf.writeUnsignedShort(codes[i]);
            }
        } else {
            for (int i=0; i< offsets.length; i++) {
                swf.writeUnsignedByte(codes[i]);
            }
        }
        if (hasLayout) {
            swf.writeShort(ascent);
            swf.writeShort(descent);
            swf.writeShort(leading);
            for (int i=0; i<offsets.length; i++){
                swf.writeShort(advances[i]);
            }
            for (int i=0; i<bounds.length; i++) {
                swf.writeRect(bounds[i]);
            }
            swf.writeUnsignedShort(kerning.length);
            for (int i=0; i<kerning.length; i++) {
                kerning[i].write(swf, wideCodes);
            }
        }
    }

    public long getId() {
        return character;
    }

    public boolean getShiftJIS() {
        return shiftJIS;
    }

    public boolean isAnsi() {
        return ansi;
    }

    public boolean isItalic() {
        return italic;
    }

    public boolean isBold() {
        return bold;
    }

    public boolean hasWideOffsets() {
        return wideOffsets;
    }

    public boolean hasWideCodes() {
        return wideCodes;
    }

    public String getName() {
        return name;
    }

    public SWFShape[] getShapes() {
        return shapes;
    }

    public int[] getCodes() {
        return codes;
    }

    public int getAscent() {
        return ascent;
    }

    public int getDescent() {
        return descent;
    }

    public int getLeading() {
        return leading;
    }

    public short[] getAdvances() {
        return advances;
    }

    public Rectangle2D[] getBounds() {
        return bounds;
    }

    public KerningRecord[] getKerning() {
        return kerning;
    }


    public void setId(int id){
        this.character = id;
    }

    public void setShiftJIS(boolean shiftJIS){
        this.shiftJIS = shiftJIS;
    }

    public void setAnsi(boolean ansi){
        this.ansi = ansi;
    }

    public void setItalic(boolean italic){
        this.italic = italic;
    }

    public void setBold(boolean bold){
        this.bold = bold;
    }

    public void setWideOffsets(boolean wideOffsets){
        this.wideOffsets = wideOffsets;
    }

    public void setWideCodes(boolean wideCodes){
        this.wideCodes = wideCodes;
    }

    public void setName(String name){
        this.name = name;
    }

    public void setCodes(int[] codes){
        this.codes = codes;
    }

    public void setShapes(SWFShape[] shapes){
        this.shapes = shapes;
    }

    public void setAscent(int ascent){
        this.ascent = ascent;
    }

    public void setDescent(int descent){
        this.descent = descent;
    }

    public void setLeading(int leading){
        this.leading = leading;
    }

    public void setAdvances(short[] advances){
        this.advances = advances;
    }

    public void setAdvances(int[] advances){
        short[] s = new short[advances.length];
        for (int i=0; i<s.length; i++) {
            s[i] = (short)advances[i];
        }
        setAdvances(s);
    }

    public void setBounds(Rectangle2D[] bounds){
        this.bounds = bounds;
    }

    public void setKerning(KerningRecord[] kerning){
        this.kerning = kerning;
    }

    public String toString() {
        // FIXME: add other stuff...
        StringBuffer s = new StringBuffer();
        s.append(super.toString()+"\n");
        s.append("  character:  "+character+"\n");
        s.append("  name: "+name+"\n");
        if (shiftJIS)       s.append("  shiftJIS\n");
        if (ansi)           s.append("  ansi\n");
        if (italic)         s.append("  italic\n");
        if (bold)           s.append("  bold\n");
        if (wideOffsets)    s.append("  wideOffsets\n");
        if (wideCodes)      s.append("  wideCodes\n");
        if (languageCode>0) s.append("  languageCode: "+languageCode+"\n");
        if (ascent != 0)    s.append("  ascent: "+ascent+"\n");
        if (descent != 0)   s.append("  descent: "+descent+"\n");
        if (leading != 0)   s.append("  leading: "+leading+"\n");
        if (advances != null) {
            for (int i=0; i<advances.length; i++) {
                            s.append("  advances["+i+"]: "+advances[i]+"\n");
            }
        }
        if (bounds != null) {
            for (int i=0; i<bounds.length; i++) {
                            s.append("  bounds["+i+"]: "+bounds[i]+"\n");
            }
        }
        s.append("  glyphCount: "+shapes.length+"\n");
        for (int i=0; i<shapes.length; i++) {
            s.append("  >>> Shape "+i+"\n");
            s.append(shapes[i]+"\n");
        }

        return s.toString();
    }


    public static class KerningRecord {

        private int code1, code2;
        private int adjustment;

        public KerningRecord(int code1, int code2, int adjustment) {
            this.code1 = code1;
            this.code2 = code2;
            this.adjustment = adjustment;
        }

        public KerningRecord(SWFInputStream input, boolean wideCodes) throws IOException {

            code1 = (wideCodes) ? input.readUnsignedShort() : input.readUnsignedByte();
            code2 = (wideCodes) ? input.readUnsignedShort() : input.readUnsignedByte();
            adjustment = input.readShort();
        }

        public void write(SWFOutputStream swf, boolean wideCodes) throws IOException {

            if (wideCodes) swf.writeUnsignedShort(code1); else swf.writeUnsignedByte(code1);
            if (wideCodes) swf.writeUnsignedShort(code2); else swf.writeUnsignedByte(code2);
            swf.writeShort(adjustment);
        }

        public String toString() {
            return "Kerning["+code1+", "+code2+", "+adjustment+"]";
        }
    }

}
