// Copyright 2004-2006, FreeHEP
package org.freehep.graphicsio.latex;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.TexturePaint;
import java.awt.geom.AffineTransform;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;

import org.freehep.graphicsio.AbstractVectorGraphicsIO;
import org.freehep.util.ScientificFormat;

/**
 * @author Andre Bach
 * @author Mark Donszelmann
 * @version $Id: freehep-graphicsio-latex/src/main/java/org/freehep/graphicsio/latex/LatexGraphics2D.java cba39eb5843a 2006/03/20 18:04:28 duns $
 */
public class LatexGraphics2D extends AbstractVectorGraphicsIO {
    /*
     * ================================================================================
     * Table of Contents: ------------------ 1. Constructors & Factory Methods
     * 2. Document Settings 3. Header, Trailer, Multipage & Comments 3.1 Header &
     * Trailer 3.2 MultipageDocument methods 4. Create & Dispose 5. Drawing
     * Methods 5.1. shapes (draw/fill) 5.1.1. lines, rectangles, round
     * rectangles 5.1.2. polylines, polygons 5.1.3. ovals, arcs 5.1.4. shapes
     * 5.2. Images 5.3. Strings 6. Transformations 7. Clipping 8. Graphics State /
     * Settings 8.1. stroke/linewidth 8.2. paint/color 8.3. font 8.4. rendering
     * hints 9. Auxiliary 10. Private/Utility Methos
     * ================================================================================
     */

    private PrintStream ps;

    private AffineTransform pageTrans = new AffineTransform();

    private double componentHeight;

    private static double pageHeight;

    private static double pageWidth;

    private int numberClips = 0;

    private float width = 1.f;

    private float[] dash;

    private Color savedColor;

    private float savedWidth;

    private float[] savedDash;

    /*
     * ================================================================================
     * 1. Constructors & Factory Methods
     * ================================================================================
     */
    public LatexGraphics2D(File file, Dimension size)
            throws FileNotFoundException {
        this(new FileOutputStream(file), size, true);
    }

    public LatexGraphics2D(File file, Component component)
            throws FileNotFoundException {
        this(new FileOutputStream(file), component, true);
    }

    public LatexGraphics2D(OutputStream os, Component component,
            boolean doRestoreOnDispose) {
        super(component, doRestoreOnDispose);
        init(os);
        componentHeight = component.getSize().getHeight();
        pageWidth = component.getSize().getWidth();
    }

    public LatexGraphics2D(OutputStream os, Dimension size,
            boolean doRestoreOnDispose) {
        super(size, doRestoreOnDispose);
        init(os);
    }

    private void init(OutputStream os) {
        ps = new PrintStream(os);
    }

    protected LatexGraphics2D(LatexGraphics2D graphics,
            boolean doRestoreOnDispose) {
        super(graphics, doRestoreOnDispose);
        ps = graphics.ps;
    }

    /*
     * ================================================================================ |
     * 2. Document Settings
     * ================================================================================
     */

    /*
     * ================================================================================ |
     * 3. Header, Trailer, Multipage & Comments
     * ================================================================================
     */
    /* 3.1 Header & Trailer */
    public void writeHeader() throws IOException {
        ps.println("\\documentclass{article}");
        ps.println("\\usepackage[noxcolor]{pstricks}");
        ps.println("\\usepackage{pst-grad}");
        ps.println("\\pagestyle{empty}");
        ps.println("\\begin{document}");
        ps.println("\\begin{pspicture}(5,20)(15,15)");
        ps.println("\\psset{unit=1pt}"); // One thing that should be changed
                                            // for scaling.

        ps.println("% componentHeight = " + componentHeight);
        ps.println("% pageWidth = " + pageWidth);
        ps.println("% pageHeight = " + pageHeight);
        pageHeight = componentHeight;
        ps.println("% pageHeight = " + pageHeight);
        pageTrans.scale(1, -1);
        pageTrans.translate(0, -pageHeight);
        transform(pageTrans);
    }

    public void writeBackground() throws IOException {
        // writeWarning(getClass()+": writeBackground() not implemented.");
        // Write out the background to the output stream.
        ps.println("% writeBackground() called");
    }

    public void writeTrailer() throws IOException {
        ps.println("\\end{pspicture}");
        ps.println("\\end{document}");
    }

    public void closeStream() throws IOException {
        ps.close();
    }

    /* 3.2 MultipageDocument methods */

    /*
     * ================================================================================
     * 4. Create & Dispose
     * ================================================================================
     */

    public Graphics create() {
        ps.println("% create() called");
        LatexGraphics2D g = new LatexGraphics2D(this, true);
        try {
            g.writeGraphicsSave(getColor(), width, dash);
        } catch (IOException e) {
	    handleException(e);
	}
        return g;
    }

    public Graphics create(double x, double y, double width, double height) {
        ps.println("% create(x,y,width,height) called");
        ps.println("% x      = " + x);
        ps.println("% y      = " + y);
        ps.println("% width  = " + width);
        ps.println("% height = " + height);
        LatexGraphics2D g = new LatexGraphics2D(this, true);
        try {
            g.writeGraphicsSave(getColor(), width, dash);
        } catch (IOException e) {
	    handleException(e);
	}
        g.clipRect(x, y, width, height);
        return g;
    }

    protected void writeGraphicsSave(Color c, double w, float[] d)
            throws IOException {
        savedColor = c;
        savedWidth = (float) w;
        savedDash = d;
    }

    protected void writeGraphicsSave() throws IOException {
    }

    protected void writeGraphicsRestore() throws IOException {
        while (numberClips > 0) {
            ps.println("\\endpsclip");
            numberClips--;
        }
        // If the current color was written inside a clip statement, it will not
        // be
        // remembered outside of it, so the restore must always be written.
        if (/* savedColor != getColor() && */savedColor != null)
            writePaint(savedColor);
        /* if (savedWidth != width) */writeWidth(savedWidth);
        /* if (savedDash != dash) */writeDash(savedDash, 0.0f);
    }

    /*
     * ================================================================================ |
     * 5. Drawing Methods
     * ================================================================================
     */
    /* 5.1.4. shapes */

    public void draw(Shape shape) {
        LatexPathConstructor pc = new LatexPathConstructor(ps);
        try {
            if (getStroke() instanceof BasicStroke) {
                ps.println("\\pscustom[fillstyle=none]{");
                pc.addPath(shape, getTransform());
                ps.println("}");
            } else {
                ps.println("\\pscustom[linestyle=none]{");
                pc.addPath(getStroke().createStrokedShape(shape), getTransform());
                ps.println("}");
            }
        } catch (IOException e) {
            handleException(e);
        }
    }

    public void fill(Shape shape) {
        LatexPathConstructor pc = new LatexPathConstructor(ps);
        try {
            ps.println("\\pscustom[linestyle=none]{");
            pc.addPath(shape, getTransform());
            ps.println("}");
        } catch (IOException e) {
            handleException(e);
        }
    }

    public void fillAndDraw(Shape shape, Color fillColor) {
        LatexPathConstructor pc = new LatexPathConstructor(ps);
        try {
            ps.println("\\newrgbcolor{colorfill}{"
                    + fixedPrecision(fillColor.getRed() / 255.) + " "
                    + fixedPrecision(fillColor.getGreen() / 255.) + " "
                    + fixedPrecision(fillColor.getBlue() / 255.) + "}");
            ps
                    .println("\\pscustom[fillstyle=solid,fillcolor=colorfill,linecolor=current]{");
            pc.addPath(shape, getTransform());
            ps.println("}");
        } catch (IOException e) {
            handleException(e);
        }
    }

    /* 5.2. Images */
    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
        // Mostly unimplemented.
        ps.println("% copyArea(x,y,width,height,dx,dy) called");
    }

    protected void writeImage(RenderedImage image, AffineTransform xform,
            Color bkg) throws IOException {
        // writeWarning(getClass()+": writeImage(RenderedImage, AffineTransform,
        // Color) not implemented.");
        // Write out the image.
        ps.println("% writeImage(image,xform,bkg) called");
    }

    /* 5.3. Strings */
    protected void writeString(String str, double x, double y)
            throws IOException {
        ps.println("% string componentHeight = " + componentHeight);
        ps.println("% string pageHeight = " + pageHeight);
        y = -y + pageHeight;

        ps.print("\\put(" + fixedPrecision(x) + "," + fixedPrecision(y) + "){");
        ps.print("\\fontsize{" + getFont().getSize() + "}{"
                + fixedPrecision(1.2 * getFont().getSize()) + "}");
        ps.print("\\textcolor{current}{");

        if (getFont().getFamily().equals("Serif"))
            ps.print("\\rmfamily ");
        if (getFont().getFamily().equals("SansSerif"))
            ps.print("\\sffamily ");
        if (getFont().getFamily().equals("Monospaced"))
            ps.print("\\ttfamily ");

        switch (getFont().getStyle()) {
        case 0:
            ps.print("\\upshape ");
            break;
        case 1:
            ps.print("\\bfseries ");
            break;
        case 2:
            ps.print("\\itshape ");
            break;
        case 3:
            ps.print("\\bfseries \\itshape ");
            break;
        default:
        }

        ps.println(fixString(str) + "}}");
    }

    // Certain chars can only be printed in the LaTeX math environment, or
    // are commands and must be preceded by a backslash.
    protected String fixString(String str) {
        StringBuffer buffer = new StringBuffer(str);
        for (int i = 0; i < buffer.length(); i++) {
            switch (buffer.charAt(i)) {
            default:
                break;
            case '{':
            case '}':
            case '#':
            case '%':
            case '&':
            case '$':
            case '_':
                buffer.insert(i, '\\');
                i++;
                break;
            case '<':
            case '>':
                buffer.insert(i, '$');
                buffer.insert((i + 2), '$');
                i += 2;
                break;
            case '\\':
                buffer.delete(i, (i + 1));
                buffer.insert(i, "$\\backslash$");
                i += 11;
                break;
            }
        }
        return buffer.toString();
    }

    /*
     * ================================================================================ |
     * 6. Transformations
     * ================================================================================
     */

    protected void writeTransform(AffineTransform t) throws IOException {
        // Ignored, transforms are pre-calculated before written to LaTeX.
    }

    protected void writeSetTransform(AffineTransform t) throws IOException {
        // Ignored, transforms are pre-calculated before written to LaTeX.
    }

    /*
     * ================================================================================ |
     * 7. Clipping
     * ================================================================================
     */
        protected void writeClip(Shape s) throws IOException {
        if (s == null || !isProperty(CLIP)) {
	    return;
	}

	ps.println("\\psclip{");
        LatexPathConstructor pc = new LatexPathConstructor(ps);
        try {
            ps.println("\\pscustom[linestyle=none,fillstyle=none]{");
            pc.addPath(s, getTransform());
            ps.println("}");
        } catch (IOException e) {
            handleException(e);
        }
        ps.println("}");
        numberClips++;
    }

    protected void writeSetClip(Shape s) throws IOException {
	// clear existing clips
	while (numberClips > 0) {
	    ps.println("\\endpsclip");
	    numberClips--;
	}

	writeClip(s);
    }

    /*
     * ================================================================================ |
     * 8. Graphics State
     * ================================================================================
     */
    /* 8.1. stroke/linewidth */
    protected void writeWidth(float width) throws IOException {
        ps.println("\\psset{linewidth=" + width + "}");
        this.width = width;
    }

    protected void writeCap(int cap) throws IOException {
        // Not supported in LaTeX PSTricks when using lineto.
        ps.println("% writeCap(int cap) called, not supported");
        /*
         * switch (cap) { case 0: ps.println("\\psset{arrows=-}"); break; case
         * 1: ps.println("\\psset{arrows=c-c}"); break; case 2:
         * ps.println("\\psset{arrows=C-C}"); break; default: }
         */
    }

    protected void writeJoin(int join) throws IOException {
        // Not supported in LaTeX PSTricks.
        ps.println("% writeJoin(int join) called, not supported");
    }

    protected void writeMiterLimit(float limit) throws IOException {
        // Not supported in LaTeX PSTricks.
        ps.println("% writeMiterLimit(float limit) called, not supported");
    }

    protected void writeDash(float[] dash, float phase) throws IOException {
        this.dash = dash;
        if (dash == null || dash.length == 0) {
            ps.println("\\psset{linestyle=solid}");
            return;
        }

        // Dotted lines must be dealt with separately.
        if (dash[0] == 0.0) {
            ps.println("\\psset{linestyle=dotted,dotsep="
                    + fixedPrecision(dash[1]) + "}");
            return;
        }

        // In more complex patterns, dots will appear as very thin dashes.
        ps.print("\\psset{linestyle=dashed,dash=");
        for (int i = 0; i < dash.length - 1; i++) {
            ps.print(fixedPrecision(dash[i]) + " ");
        }
        ps.print(fixedPrecision(dash[dash.length - 1]));
        ps.println("}");
    }

    /* 8.2. paint/color */
    public void setPaintMode() {
        // Mostly unimplemented.
        ps.println("% setPaintMode() called");
    }

    public void setXORMode(Color c1) {
        // Mostly unimplemented.
        ps.println("% setXORMode(Color c1) called");
    }

    protected void writePaint(Color p) throws IOException {
        ps.println("\\newrgbcolor{current}{"
                + fixedPrecision(p.getRed() / 255.) + " "
                + fixedPrecision(p.getGreen() / 255.) + " "
                + fixedPrecision(p.getBlue() / 255.) + "}");
        ps
                .println("\\psset{linecolor=current,fillcolor=current,fillstyle=solid}");
    }

    protected void writePaint(GradientPaint p) throws IOException {
        double gradTheta = Math.atan((p.getPoint1().getY() - p.getPoint2()
                .getY())
                / (p.getPoint1().getX() - p.getPoint2().getX()));
        ps.println("\\newrgbcolor{gradbegin}{"
                + fixedPrecision(p.getColor1().getRed() / 255.) + " "
                + fixedPrecision(p.getColor1().getGreen() / 255.) + " "
                + fixedPrecision(p.getColor1().getBlue() / 255.) + "}");
        ps.println("\\newrgbcolor{gradend}{"
                + fixedPrecision(p.getColor2().getRed() / 255.) + " "
                + fixedPrecision(p.getColor2().getGreen() / 255.) + " "
                + fixedPrecision(p.getColor2().getBlue() / 255.) + "}");
        ps.println("\\psset{gradmidpoint=1,gradangle="
                + Math.toDegrees(gradTheta) + ",fillstyle=gradient}");
    }

    protected void writePaint(TexturePaint p) throws IOException {
        // Not supported in LaTeX PSTricks.
        ps
                .println("% writePaint(TexturePaint p) called, not supported, crosshatch substituted");
        ps.println("\\psset{fillstyle=crosshatch}");
    }

    protected void writePaint(Paint p) throws IOException {
        writeWarning(getClass() + ": writePaint(Paint) not implemented for "
                + p.getClass());
        // Write out the paint.
        ps.println("% writePaint(Paint p) called");
    }

    /* 8.3. font */
    protected void writeFont(Font font) throws IOException {
	// written when needed
    }

    /* 8.4. rendering hints */

    /*
     * ================================================================================ |
     * 9. Auxiliary
     * ================================================================================
     */
    public GraphicsConfiguration getDeviceConfiguration() {
        // Mostly unimplemented
        return null;
    }

    public boolean hit(Rectangle rect, Shape s, boolean onStroke) {
        // Mostly unimplemented
        return false;
    }

    public void writeComment(String comment) throws IOException {
        ps.println("% " + comment);
    }

    public void writeWarning(String msg) {
        System.out.println(msg);
    }

    public String toString() {
        return "LatexGraphics";
    }

    private static ScientificFormat scientific = new ScientificFormat(5, 100,
            false);

    public static String fixedPrecision(double d) {
        return scientific.format(d);
    }
}