// Copyright 2000-2003, FreeHEP
package org.freehep.graphics2d;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.awt.image.renderable.*;
import java.util.*;
import java.text.*;

import org.freehep.util.UserProperties;

/**
 * This class implements all conversions from integer to double as well as a few
 * other convenience functions. It also handles the different
 * drawSymbol and fillSymbol methods and print colors.
 *
 * @author Simon Fischer
 * @author Mark Donszelmann
 * @version $Id: AbstractVectorGraphics.java,v 1.1.1.1 2004/05/31 05:08:53 kuromaru Exp $
 */
public abstract class AbstractVectorGraphics
    extends VectorGraphics {

    private UserProperties properties;
    private String creator;
    private boolean isDeviceIndependent;
    private SymbolShape cachedShape;
    private int colorMode;

    private Color backgroundColor;
    private Color currentColor;
    private Paint currentPaint;
    private Font currentFont;

    public AbstractVectorGraphics() {
        properties = new UserProperties();
        creator = "FreeHEP Graphics2D Driver";
        isDeviceIndependent = false;
        cachedShape = new SymbolShape();
        colorMode = PrintColor.COLOR;

        // all of these have to be set in the subclasses
        currentFont = null;
        backgroundColor = null;
        currentColor = null;
        currentPaint = null;
    }

    protected AbstractVectorGraphics(AbstractVectorGraphics graphics) {
        super();
        properties = graphics.properties;
        creator = graphics.creator;
        isDeviceIndependent = graphics.isDeviceIndependent;
        cachedShape = graphics.cachedShape;

        backgroundColor = graphics.backgroundColor;
        currentColor = graphics.currentColor;
        currentPaint = graphics.currentPaint;
        colorMode = graphics.colorMode;
        currentFont = graphics.currentFont;
    }

    public void setProperties(Properties newProperties) {
        if (newProperties == null) return;
        properties.setProperties(newProperties);
    }

    protected void initProperties(Properties defaults) {
        properties = new UserProperties();
        properties.setProperties(defaults);
    }

    protected Properties getProperties() {
        return properties;
    }

    public String getProperty(String key) {
        return properties.getProperty(key);
    }

    public Color getPropertyColor(String key) {
        return properties.getPropertyColor(key);
    }

    public Rectangle getPropertyRectangle(String key) {
        return properties.getPropertyRectangle(key);
    }

    public Insets getPropertyInsets(String key) {
        return properties.getPropertyInsets(key);
    }

    public Dimension getPropertyDimension(String key) {
        return properties.getPropertyDimension(key);
    }

    public int getPropertyInt(String key) {
        return properties.getPropertyInt(key);
    }

    public double getPropertyDouble(String key) {
        return properties.getPropertyDouble(key);
    }

    public boolean isProperty(String key) {
        return properties.isProperty(key);
    }

    public String getCreator() {
        return creator;
    }

    public void setCreator(String creator) {
        if (creator != null) {
            this.creator = creator;
        }
    }

    public boolean isDeviceIndependent() {
        return isDeviceIndependent;
    }

    public void setDeviceIndependent(boolean isDeviceIndependent) {
        this.isDeviceIndependent = isDeviceIndependent;
    }


    /**
     * Gets the current font.
     *
     * @return current font
     */
    public Font getFont() {
        return currentFont;
    }

    /**
     * Sets the current font.
     *
     * @param font to be set
     */
    public void setFont(Font font) {
        // FIXME: maybe add delayed setting
        currentFont = font;
    }

    public void drawSymbol(int x, int y, int size, int symbol) {
        drawSymbol((double) x, (double) y, (double) size, symbol);
    }

    public void fillSymbol(int x, int y, int size, int symbol) {
        fillSymbol((double) x, (double) y, (double) size, symbol);
    }

    public void drawSymbol(double x, double y, double size, int symbol) {
        if (size <= 0) return;
        drawSymbol(this, x, y, size, symbol);
    }

    protected void drawSymbol(VectorGraphics g, double x, double y, double size, int symbol) {
        switch (symbol) {
            case SYMBOL_VLINE:
            case SYMBOL_STAR:
            case SYMBOL_HLINE:
            case SYMBOL_PLUS:
            case SYMBOL_CROSS:
            case SYMBOL_BOX:
            case SYMBOL_UP_TRIANGLE:
            case SYMBOL_DN_TRIANGLE:
            case SYMBOL_DIAMOND:
                cachedShape.create(symbol, x, y, size);
                g.draw(cachedShape);
                break;

            case SYMBOL_CIRCLE: {
                double diameter = Math.max(1, size);
                diameter += (diameter%2);
                g.drawOval(x-diameter/2,y-diameter/2,diameter,diameter);
                break;
            }
        }
    }

    public void fillSymbol(double x, double y, double size, int symbol) {
        if (size <= 0) return;
        fillSymbol(this, x, y, size, symbol);
    }

    protected void fillSymbol(VectorGraphics g, double x, double y, double size, int symbol) {
        switch (symbol) {
            case SYMBOL_VLINE:
            case SYMBOL_STAR:
            case SYMBOL_HLINE:
            case SYMBOL_PLUS:
            case SYMBOL_CROSS:
                cachedShape.create(symbol, x, y, size);
                g.draw(cachedShape);
                break;

            case SYMBOL_BOX:
            case SYMBOL_UP_TRIANGLE:
            case SYMBOL_DN_TRIANGLE:
            case SYMBOL_DIAMOND:
                cachedShape.create(symbol, x, y, size);
                g.fill(cachedShape);
                break;

            case SYMBOL_CIRCLE: {
                double diameter = Math.max(1, size);
                diameter += (diameter%2);
                g.fillOval(x-diameter/2,y-diameter/2,diameter,diameter);
                break;
            }
        }
    }

    // ---------------------------------------------------------
    // -------------------- WRAPPER METHODS --------------------
    // --------------------  int -> double  --------------------
    // ---------------------------------------------------------

    public void clearRect(int x, int y, int width, int height) {
        clearRect((double) x, (double) y, (double) width, (double) height);
    }

    public void drawLine(int x1, int y1, int x2, int y2) {
        drawLine((double)x1, (double)y1, (double)x2, (double)y2);
    }

    public void drawRect(int x, int y, int width, int height) {
        drawRect((double)x, (double)y, (double)width, (double)height);
    }

    public void fillRect(int x, int y, int width, int height) {
        fillRect((double)x, (double)y, (double)width, (double)height);
    }

    public void drawArc(int x, int y,
                        int width, int height,
                        int startAngle, int arcAngle) {
        drawArc((double)x, (double)y, (double)width, (double)height,
                (double) startAngle, (double) arcAngle);
    }

    public void fillArc(int x, int y, int width, int height,
                        int startAngle, int arcAngle) {
        fillArc((double) x, (double) y, (double) width, (double) height,
                (double) startAngle, (double) arcAngle);
    }

    public void drawOval(int x, int y, int width, int height) {
        drawOval((double)x, (double)y, (double)width, (double)height);
    }

    public void fillOval(int x, int y, int width, int height) {
        fillOval((double)x, (double)y, (double)width, (double)height);
    }

    public void drawRoundRect(int x, int y, int width, int height,
                              int arcWidth, int arcHeight) {
        drawRoundRect(x, y, (double)width, (double)height,
                            (double)arcWidth,   (double)arcHeight);
    }

    public void fillRoundRect(int x, int y, int width, int height,
                              int arcWidth, int arcHeight) {
        fillRoundRect((double)x, (double)y, (double)width, (double)height,
                      (double)arcWidth, (double)arcHeight);
    }

    public void translate(int x, int y) {
        translate((double) x, (double) y);
    }


    /*--------------------------------------------------------------------------------
     | 8.1. stroke/linewidth
     *--------------------------------------------------------------------------------*/
    public void setLineWidth(int width) {
        setLineWidth((double) width);
    }

    public void setLineWidth(double width) {
        Stroke stroke = getStroke();
        if (stroke instanceof BasicStroke) {
            BasicStroke cs = (BasicStroke)stroke;
            if (cs.getLineWidth() != width) {
                stroke = new BasicStroke((float)width,
                                         cs.getEndCap(),
                                         cs.getLineJoin(),
                                         cs.getMiterLimit(),
                                         cs.getDashArray(),
                                         cs.getDashPhase());
                setStroke(stroke);
            }
        } else {
            stroke = new BasicStroke((float)width);
            setStroke(stroke);
        }
    }

    public void drawString(String str, int x, int y) {
        drawString(str, (double)x, (double)y);
    }

    public void drawString(String s, float x, float y) {
        drawString(s, (double)x, (double)y);
    }

    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
        drawString(iterator, (float)x, (float)y);
    }

    // ------------------ other wrapper methods ----------------

    public void drawString(String str,
                           double x, double y,
                           int horizontal, int vertical) {
        drawString(str, x, y, horizontal, vertical, false, null, 0, false, null);
    }

    public void drawString(TagString str, double x, double y) {
        drawString(str, x, y, TEXT_LEFT, TEXT_BASELINE);
    }

    public void drawString(TagString str,
                           double x, double y,
                           int horizontal, int vertical) {
        drawString(str, x, y, horizontal, vertical, false, null, 0, false, null);
    }


    /* 8.2. paint/color */
    public int getColorMode() {
        return colorMode;
    }

    public void setColorMode(int colorMode) {
        this.colorMode = colorMode;
    }

    /**
     * Gets the background color.
     *
     * @return background color
     */
    public Color getBackground() {
        return backgroundColor;
    }

    /**
     * Sets the background color.
     *
     * @param color background color to be set
     */
    public void setBackground(Color color) {
        backgroundColor = color;
    }

    /**
     * Sets the current color and the current paint. Calls writePaint(Color).
     *
     * @param color to be set
     */
    public void setColor(Color color) {
        currentColor = color;
        currentPaint = color;
    }

    /**
     * Gets the current color.
     *
     * @return the current color
     */
    public Color getColor() {
        return currentColor;
    }

    /**
     * Sets the current paint.
     *
     * @return current paint
     */
    public void setPaint(Paint paint) {
        if (!(paint instanceof Color)) currentColor = null;
        currentPaint = paint;
    }


    /**
     * Gets the current paint.
     *
     * @return current paint
     */
    public Paint getPaint() {
        return currentPaint;
    }

    /**
     * Returns a printColor created from the original printColor,
     * based on the ColorMode. If you run getPrintColor on it again
     * you still get the same color, so that it does not matter
     * if you convert it more than once.
     */
    protected Color getPrintColor(Color color) {
        // shortcut if mode is COLOR for speed
        if (colorMode == PrintColor.COLOR) return color;

        // otherwise...
        PrintColor printColor = PrintColor.createPrintColor(color);
        return printColor.getColor(colorMode);
    }

    public void rotate(double theta, double x, double y) {
        translate(x,y);
        rotate(theta);
        translate(-x,-y);
    }

    public void drawArc(double x, double y, double width, double height,
            double startAngle, double arcAngle) {
        draw(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.OPEN));
    }

    public void drawLine(double x1, double y1, double x2, double y2) {
        draw(new Line2D.Double(x1, y1, x2, y2));
    }

    public void drawOval(double x, double y, double width, double height) {
        draw(new Ellipse2D.Double(x, y, width, height));
    }

    public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints) {
        draw(createShape(xPoints, yPoints, nPoints, false));
    }

    public void drawPolyline(double[] xPoints, double[] yPoints, int nPoints) {
        draw(createShape(xPoints, yPoints, nPoints, false));
    }

    public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints) {
        draw(createShape(xPoints, yPoints, nPoints, true));
    }

    public void drawPolygon(double[] xPoints, double[] yPoints, int nPoints) {
        draw(createShape(xPoints, yPoints, nPoints, true));
    }

    public void drawRect(double x, double y, double width, double height) {
        draw(new Rectangle2D.Double(x, y, width, height));
    }

    public void drawRoundRect(double x, double y, double width, double height,
                  double arcWidth, double arcHeight) {
        draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
    }

    public void fillArc(double x, double y, double width, double height,
            double startAngle, double arcAngle) {
        fill(new Arc2D.Double(x, y, width, height, startAngle, arcAngle, Arc2D.PIE));
    }

    public void fillOval(double x, double y, double width, double height) {
        fill(new Ellipse2D.Double(x, y, width, height));
    }

    public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints) {
        fill(new Polygon(xPoints, yPoints, nPoints));
    }

    public void fillPolygon(double[] xPoints, double[] yPoints, int nPoints) {
        fill(createShape(xPoints, yPoints, nPoints, true));
    }

    public void fillRect(double x, double y, double width, double height) {
        fill(new Rectangle2D.Double(x, y, width, height));
    }

    public void fillRoundRect(double x, double y, double width, double height,
                  double arcWidth, double arcHeight) {
        fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
    }

    /**
     * Creates a polyline/polygon shape from a set of points.
     * Needs to be defined in subclass because its implementations
     * could be device specific
     *
     * @param xPoints, yPoints, nPoints coordinates of the polyline.
     * @param close is shape closed
     */
    protected abstract GeneralPath createShape(double[] xPoints, double[] yPoints, int nPoints,
                    boolean close);

    /**
     * Creates a polyline/polygon shape from a set of points.
     *
     * @param xPoints, yPoints, nPoints coordinates of the polyline.
     * @param close is shape closed
     */
    protected GeneralPath createShape(int[] xPoints, int[] yPoints, int nPoints,
                    boolean close) {
        GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
        if (nPoints > 0) {
            path.moveTo(xPoints[0], yPoints[0]);
            int lastX = xPoints[0];
            int lastY = yPoints[0];
            if (close && (Math.abs(xPoints[nPoints-1] - lastX) < 1)
                      && (Math.abs(yPoints[nPoints-1] - lastY) < 1)) {
                nPoints--;
            }
            for (int i = 1; i < nPoints; i++) {
                if ((Math.abs(xPoints[i] - lastX) > 1)
                ||  (Math.abs(yPoints[i] - lastY) > 1)) {
                    path.lineTo(xPoints[i], yPoints[i]);
                    lastX = xPoints[i];
                    lastY = yPoints[i];
                }
            }
            if (close) path.closePath();
        }
        return path;
    }
}
