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

import java.io.IOException;
import java.util.Vector;

import org.freehep.util.io.BitOutputStream;

/**
 * Shape TAG.
 * 
 * @author Mark Donszelmann
 * @author Charles Loomis
 * @version $Id: freehep-graphicsio-swf/src/main/java/org/freehep/graphicsio/swf/SWFShape.java 3e48ba4ef214 2006/11/27 22:51:07 duns $
 */
public class SWFShape {

    private int numFillBits;

    private int numLineBits;

    protected FillStyleArray fillStyles;

    protected LineStyleArray lineStyles;

    private Vector<Record> records;

    public SWFShape(Vector<Record> records) {
        this.records = records;
    }

    // for font shapes
    public SWFShape(SWFInputStream input) throws IOException {
        this(input, null, null, false, false, false);
    }

    public SWFShape(SWFInputStream input, FillStyleArray fillStyles,
            LineStyleArray lineStyles, boolean isMorphStyle, boolean hasAlpha, boolean hasStyles)
            throws IOException {

        this.fillStyles = fillStyles;
        this.lineStyles = lineStyles;

        input.byteAlign();
        numFillBits = (int) input.readUBits(4);
        numLineBits = (int) input.readUBits(4);

        records = new Vector<Record>();

        boolean endOfShape = false;
        do {
            boolean edge = input.readBitFlag();
            if (edge) {
                // Edge Record
                records.add(new EdgeRecord(input));
            } else {
                // Shape Record
                int state = (int) input.readUBits(5);
                if (state == 0) {
                    endOfShape = true;
                } else {
                    records.add(new ShapeRecord(this, input, numFillBits,
                            numLineBits, isMorphStyle, hasAlpha, hasStyles, state));
                }
            }
        } while (!endOfShape);
    }

    // for font shapes
    public void write(SWFOutputStream swf) throws IOException {
        write(swf, false, false, false);	// FIXME, false for hasStyles is a guess.
    }

    public void write(SWFOutputStream swf, boolean isMorphStyle, boolean hasAlpha, boolean hasStyles) throws IOException {
        swf.byteAlign();
        int numFillBits = 0;
        int numLineBits = 0;

        for (int i = 0; i < records.size(); i++) {
            Record r = records.get(i);
            if (r instanceof ShapeRecord) {
                ShapeRecord s = (ShapeRecord) r;
                numFillBits = Math.max(numFillBits, s.getNumFillBits());
                numLineBits = Math.max(numLineBits, s.getNumLineBits());
            }
        }
        swf.writeUBits(numFillBits, 4);
        swf.writeUBits(numLineBits, 4);

        for (int i = 0; i < records.size(); i++) {
            Record r = records.get(i);
            r.write(swf, numFillBits, numLineBits, isMorphStyle, hasAlpha, hasStyles);
        }
        swf.writeUBits(0, 6); // End of Shape
        swf.byteAlign(); // make sure any offset calculations will be correct
    }

    void setNumFillBits(int numFillBits) {
        this.numFillBits = numFillBits;
    }

    void setNumLineBits(int numLineBits) {
        this.numLineBits = numLineBits;
    }

    void setFillStyles(FillStyleArray fillStyles) {
        this.fillStyles = fillStyles;
    }

    void setLineStyles(LineStyleArray lineStyles) {
        this.lineStyles = lineStyles;
    }

    public String toString() {
        StringBuffer s = new StringBuffer();
        for (int i = 0; i < records.size(); i++) {
            s.append("    ");
            s.append(i);
            s.append(":");
            s.append(records.get(i));
            s.append("\n");
        }
        s.append("    End of shape\n");
        return s.toString();
    }

    /**
     * Abstract superclass for Records.
     */
    public static abstract class Record {
        public abstract void write(SWFOutputStream swf, int numFillBits,
                int numLineBits, boolean isMorphStyle, boolean hasAlpha, boolean hasStyles) throws IOException;
    }

    /**
     * Shape Record, specifying style, move, ...
     */
    public static class ShapeRecord extends Record {

        private static final int MOVE = 0x01;

        private static final int FILLSTYLE0 = 0x02;

        private static final int FILLSTYLE1 = 0x04;

        private static final int LINESTYLE = 0x08;

        private static final int NEWSTYLES = 0x10;

        private boolean move;

        private int moveX = 0;

        private int moveY = 0;

        private int fillStyle0 = -1;

        private int fillStyle1 = -1;

        private int lineStyle = -1;

        private FillStyleArray newFillStyles;

        private LineStyleArray newLineStyles;

        public ShapeRecord(boolean move, int moveX, int moveY, int fillStyle0,
                int fillStyle1, int lineStyle) {
            this.move = move;
            this.moveX = moveX;
            this.moveY = moveY;
            this.fillStyle0 = fillStyle0;
            this.fillStyle1 = fillStyle1;
            this.lineStyle = lineStyle;
        }

        ShapeRecord(SWFShape shape, SWFInputStream input, int numFillBits,
                int numLineBits, boolean isMorphStyle, boolean hasAlpha, boolean hasStyles,
                int state) throws IOException {

            if ((state & MOVE) > 0) {
                move = true;
                int numMoveBits = (int) input.readUBits(5);
                moveX = (int) input.readSBits(numMoveBits);
                moveY = (int) input.readSBits(numMoveBits);
            }

            if ((state & FILLSTYLE0) > 0)
                fillStyle0 = (int) input.readUBits(numFillBits);
            if ((state & FILLSTYLE1) > 0)
                fillStyle1 = (int) input.readUBits(numFillBits);
            if ((state & LINESTYLE) > 0)
                lineStyle = (int) input.readUBits(numLineBits);

            if ((state & NEWSTYLES) > 0) {
                newFillStyles = new FillStyleArray(input, isMorphStyle,
                        hasAlpha);
                shape.setFillStyles(newFillStyles);
                newLineStyles = new LineStyleArray(input, isMorphStyle,
                        hasAlpha, hasStyles);
                shape.setLineStyles(newLineStyles);

                numFillBits = (int) input.readUBits(4);
                shape.setNumFillBits(numFillBits);
                numLineBits = (int) input.readUBits(4);
                shape.setNumLineBits(numLineBits);
            }
        }

        public void write(SWFOutputStream swf, int numFillBits,
                int numLineBits, boolean isMorphStyle, boolean hasAlpha, boolean hasStyles) throws IOException {

            int state = 0x00;
            if (newFillStyles != null)
                state |= NEWSTYLES;
            if (lineStyle != -1)
                state |= LINESTYLE;
            if (fillStyle1 != -1)
                state |= FILLSTYLE1;
            if (fillStyle0 != -1)
                state |= FILLSTYLE0;
            if (move)
                state |= MOVE;
            swf.writeUBits(state, 6);

            if (move) {
                int numMoveBits = Math.max(BitOutputStream.minBits(moveX, true), BitOutputStream
                        .minBits(moveY, true));
                swf.writeUBits(numMoveBits, 5);
                swf.writeSBits(moveX, numMoveBits);
                swf.writeSBits(moveY, numMoveBits);
            }

            if (fillStyle0 != -1)
                swf.writeUBits(fillStyle0, numFillBits);
            if (fillStyle1 != -1)
                swf.writeUBits(fillStyle1, numFillBits);
            if (lineStyle != -1)
                swf.writeUBits(lineStyle, numLineBits);

            if (newFillStyles != null) {
                newFillStyles.write(swf, isMorphStyle, hasAlpha);
                newLineStyles.write(swf, isMorphStyle, hasAlpha, hasStyles);
                swf.writeUBits(numFillBits, 4);
                swf.writeUBits(numLineBits, 4);
            }
        }

        public int getNumFillBits() {
            int numFillBits = 0;
            if (fillStyle0 != -1)
                numFillBits = Math.max(numFillBits, BitOutputStream
                        .minBits(fillStyle0));
            if (fillStyle1 != -1)
                numFillBits = Math.max(numFillBits, BitOutputStream
                        .minBits(fillStyle1));
            return numFillBits;
        }

        public int getNumLineBits() {
            return (lineStyle != -1) ? BitOutputStream.minBits(lineStyle) : 0;
        }

        public String toString() {
            StringBuffer s = new StringBuffer();
            if (move)
                s.append("moveto(" + moveX + ", " + moveY + "); ");
            if (fillStyle0 != -1)
                s.append("fillStyle0=" + fillStyle0 + "; ");
            if (fillStyle1 != -1)
                s.append("fillStyle1=" + fillStyle1 + "; ");
            if (lineStyle != -1)
                s.append("lineStyle=" + lineStyle + "; ");
            if ((newFillStyles != null) || (newLineStyles != null))
                s.append("More Styles;\n");
            if (newFillStyles != null)
                s.append(newFillStyles.toString());
            if (newLineStyles != null)
                s.append(newLineStyles.toString());
            return s.toString();
        }
    }

    /**
     * Edge Record, specifying lines, curves, ...
     */
    public static class EdgeRecord extends Record {

        boolean curve;

        int controlDeltaX, controlDeltaY, anchorDeltaX, anchorDeltaY;

        int deltaX, deltaY;

        public EdgeRecord(int controlDeltaX, int controlDeltaY,
                int anchorDeltaX, int anchorDeltaY) {
            curve = true;
            this.controlDeltaX = controlDeltaX;
            this.controlDeltaY = controlDeltaY;
            this.anchorDeltaX = anchorDeltaX;
            this.anchorDeltaY = anchorDeltaY;
        }

        public EdgeRecord(int deltaX, int deltaY) {
            curve = false;
            this.deltaX = deltaX;
            this.deltaY = deltaY;
        }

        EdgeRecord(SWFInputStream input) throws IOException {
            curve = !input.readBitFlag();
            int numBits = (int) input.readUBits(4) + 2;
            if (curve) {
                controlDeltaX = (int) input.readSBits(numBits);
                controlDeltaY = (int) input.readSBits(numBits);
                anchorDeltaX = (int) input.readSBits(numBits);
                anchorDeltaY = (int) input.readSBits(numBits);
            } else {
                if (input.readBitFlag()) {
                    // general line
                    deltaX = (int) input.readSBits(numBits);
                    deltaY = (int) input.readSBits(numBits);
                } else {
                    if (input.readBitFlag()) {
                        // vertical
                        deltaX = 0;
                        deltaY = (int) input.readSBits(numBits);
                    } else {
                        // horizontal
                        deltaX = (int) input.readSBits(numBits);
                        deltaY = 0;
                    }
                }
            }
        }

        public void write(SWFOutputStream swf, int numFillBits,
                int numLineBits, boolean isMorphStyle, boolean hasAlpha, boolean hasStyles) throws IOException {

            swf.writeBitFlag(true);
            swf.writeBitFlag(!curve);

            int numBits = 0;
            if (curve) {
                numBits = Math.max(numBits, BitOutputStream.minBits(controlDeltaX, true));
                numBits = Math.max(numBits, BitOutputStream.minBits(controlDeltaY, true));
                numBits = Math.max(numBits, BitOutputStream.minBits(anchorDeltaX, true));
                numBits = Math.max(numBits, BitOutputStream.minBits(anchorDeltaY, true));
            } else {
                if ((deltaX != 0) && (deltaY != 0)) {
                    numBits = Math.max(numBits, BitOutputStream.minBits(deltaX, true));
                    numBits = Math.max(numBits, BitOutputStream.minBits(deltaY, true));
                } else {
                    if (deltaX == 0) {
                        // vertical
                        numBits = Math.max(numBits, BitOutputStream.minBits(deltaY, true));
                    } else {
                        // horizontal
                        numBits = Math.max(numBits, BitOutputStream.minBits(deltaX, true));
                    }
                }
            }
            swf.writeUBits(numBits - 2, 4);

            if (curve) {
                swf.writeSBits(controlDeltaX, numBits);
                swf.writeSBits(controlDeltaY, numBits);
                swf.writeSBits(anchorDeltaX, numBits);
                swf.writeSBits(anchorDeltaY, numBits);
            } else {
                if ((deltaX != 0) && (deltaY != 0)) {
                    // general line
                    swf.writeBitFlag(true);
                    swf.writeSBits(deltaX, numBits);
                    swf.writeSBits(deltaY, numBits);
                } else {
                    swf.writeBitFlag(false);
                    if (deltaX == 0) {
                        // vertical
                        swf.writeBitFlag(true);
                        swf.writeSBits(deltaY, numBits);
                    } else {
                        // horizontal
                        swf.writeBitFlag(false);
                        swf.writeSBits(deltaX, numBits);
                    }
                }
            }
        }

        public String toString() {
            if (curve) {
                return "rcurveto(" + controlDeltaX + ", " + controlDeltaY
                        + ", " + anchorDeltaX + ", " + anchorDeltaY + ")";
            } else {
                return "rlineto(" + deltaX + ", " + deltaY + ")";
            }
        }
    }
}
