/* CairoGraphics2D.java
Copyright (C) 2005  Object Refinery Limited

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
USA */

package test;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.RenderingHints.Key;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.RenderableImage;
import java.text.AttributedCharacterIterator;
import java.util.HashMap;
import java.util.Map;

import org.freedesktop.cairo.Context;
import org.freedesktop.cairo.FontSlant;
import org.freedesktop.cairo.FontWeight;
import org.freedesktop.cairo.Matrix;

public class CairoGraphics2D extends Graphics2D {

    private Context context;
    
    private Paint paint;
    
    private Color backgroundColor;

    private Stroke stroke;
    
    private Font font;
    
    private Map renderingHints;
    
    private AffineTransform transform;
    
    /**
     * Creates a new instance with the given context.
     * 
     * @param context  the context.
     */
    public CairoGraphics2D(Context context) {
        this.context = context; 
        this.paint = Color.BLACK;
        this.backgroundColor = Color.BLACK;
        this.stroke = new BasicStroke(1.0f);
        this.font = new Font("Dialog", Font.PLAIN, 12);
        this.renderingHints = new HashMap();
        this.transform = new AffineTransform();
    }
    
    /**
     * Returns the paint used for drawing operations.
     * 
     * @return The paint (never <code>null</code>).
     */
    public Paint getPaint() {
        return this.paint;
    }

    /**
     * Sets the paint used for drawing operations.
     * 
     * @param paint  the paint (<code>null</code> not permitted).
     */
    public void setPaint(Paint paint) {
        if (paint == null) {
            throw new IllegalArgumentException("Null 'paint' argument.");
        }
        this.paint = paint;
        if (paint instanceof Color) {
            Color c = (Color) paint;
            setColor(c);
        }
        else {
            throw new RuntimeException("Can only handle 'Color' at present.");
        }
    }

    /**
     * Sets the color.
     * 
     * @param color  the color (<code>null</code> not permitted).
     */
    public void setColor(Color color) {
        if (color == null) { 
            throw new IllegalArgumentException("Null 'color' argument.");
        }
        this.context.setSourceRGBA(color.getRed() / 255.0, 
                color.getGreen() / 255.0, color.getBlue() / 255.0, 
                color.getAlpha() / 255.0);
    }

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

    /**
     * Sets the background color.
     * 
     * @param color  the new background color (<code>null</code> not permitted).
     */
    public void setBackground(Color color) {
        if (color == null) {
            throw new IllegalArgumentException("Null 'color' argument.");
        }
        this.backgroundColor = color;
    }

    /**
     * Returns the current stroke.
     * 
     * @return The current stroke (<code>null</code> not permitted).
     */
    public Stroke getStroke() {
        return this.stroke;
    }

    /**
     * Sets the stroke used for drawing operations.  At the current time,
     * this method expects a {@link BasicStroke} instance.
     * 
     * @param stroke  the stroke (<code>null</code> not permitted).
     */
    public void setStroke(Stroke stroke) {
        if (stroke == null) {
            throw new IllegalArgumentException("Null 'stroke' argument.");
        }
        this.stroke = stroke;
        if (stroke instanceof BasicStroke) {
            BasicStroke bs = (BasicStroke) stroke;
            context.setLineWidth(bs.getLineWidth() / 100.0);
            // TODO: set other stroke characteristics
        }
        else {
            throw new RuntimeException("Stroke type not recognised.");
        }
    }

    /**
     * Returns the current font.
     * 
     * @return The current font.
     */
    public Font getFont() {
        return this.font;
    }

    /**
     * Sets the font.
     * 
     * @param font  the font (<code>null</code> not permitted).
     */
    public void setFont(Font font) {
        if (font == null) {
            throw new IllegalArgumentException("Null 'font' argument.");
        }
        this.font = font;
        String family = font.getFamily();
        FontSlant slant = FontSlant.NORMAL;
        if (font.isItalic()) {
            slant = FontSlant.ITALIC;
        }
        FontWeight weight = FontWeight.NORMAL;
        if (font.isBold()) {
            weight = FontWeight.BOLD;
        }
        this.context.selectFontFace(family, slant, weight);
        this.context.setFontSize(font.getSize());
    }

    /**
     * Adds the specified hints to the rendering hints for this 
     * <code>Graphics2D</code>.
     * 
     * @param hints  the hints.
     */
    public void addRenderingHints(Map hints) {
        if (hints != null) {
            this.renderingHints.putAll(hints);
        }
    }

    /**
     * Returns the current transform.
     * 
     * @return The current transform.
     */
    public AffineTransform getTransform() {
        return new AffineTransform(transform);
    }

    /**
     * Sets the current transform.
     * 
     * @param t  the transform (<code>null</code> permitted, resets to the 
     *           identity transform).
     */
    public void setTransform(AffineTransform t) {
        if (t == null) t = new AffineTransform();
        this.transform = new AffineTransform(t);
        Matrix m = this.context.getMatrix();
        m.setX0(t.getTranslateX());
        m.setY0(t.getTranslateY());
        m.setXX(t.getScaleX());
        m.setXY(t.getShearX());
        m.setYX(t.getShearY());
        m.setYY(t.getScaleY());
        this.context.setMatrix(m);
    }

    public void transform(AffineTransform tx) {
        AffineTransform t = getTransform();
        tx.concatenate(t);
        setTransform(tx);
    }

    public void translate(double tx, double ty) {
        AffineTransform t = getTransform();
        t.translate(tx, ty);
        setTransform(t);
    }

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

    /**
     * Draws the outline of the given <code>shape</code> using the current
     * stroke and paint.
     * 
     * @param shape  the shape (<code>null</code> not permitted).
     */
    public void draw(Shape shape) {
        if (shape == null) {
            throw new IllegalArgumentException("Null 'shape' argument.");
        }
        PathIterator path = shape.getPathIterator(null);
        double[] c = new double[6];  
        while (!path.isDone()) {
            int segment = path.currentSegment(c);
            if (segment == PathIterator.SEG_MOVETO) {
                context.moveTo(c[0], c[1]);
            }
            else if (segment == PathIterator.SEG_LINETO) {
                context.lineTo(c[0], c[1]);
            }
            else if (segment == PathIterator.SEG_QUADTO) {
                context.curveTo(c[0], c[1], c[0], c[1], c[2], c[3]);
            }
            else if (segment == PathIterator.SEG_CUBICTO) {
                context.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
            }
            else if (segment == PathIterator.SEG_CLOSE) {
                context.closePath();
            }
            else {
                throw new RuntimeException("Unrecognised segment type: " 
                        + segment);
            }
            path.next();
        }
        context.setLineWidth(1.0);
        context.stroke();
    }

    public void fill(Shape shape) {
        if (shape == null) {
            throw new IllegalArgumentException("Null 'shape' argument.");
        }
        PathIterator path = shape.getPathIterator(null);
        double[] c = new double[6];  
        while (!path.isDone()) {
            int segment = path.currentSegment(c);
            if (segment == PathIterator.SEG_MOVETO) {
                context.moveTo(c[0], c[1]);
            }
            else if (segment == PathIterator.SEG_LINETO) {
                context.lineTo(c[0], c[1]);
            }
            else if (segment == PathIterator.SEG_QUADTO) {
                context.curveTo(c[0], c[1], c[0], c[1], c[2], c[3]);
            }
            else if (segment == PathIterator.SEG_CUBICTO) {
                context.curveTo(c[0], c[1], c[2], c[3], c[4], c[5]);
            }
            else if (segment == PathIterator.SEG_CLOSE) {
                context.closePath();
            }
            else {
                throw new RuntimeException("Unrecognised segment type: " 
                        + segment);
            }
            path.next();
        }
        context.fill();
    }

    public FontMetrics getFontMetrics(Font font) {
        return Toolkit.getDefaultToolkit().getFontMetrics(font);
    }

    public FontRenderContext getFontRenderContext() {
        return new FontRenderContext(null, true, true);
    }

    public void drawString(String text, float x, float y) {
        context.moveTo(x, y);
        context.showText(text);
    }

    public void drawString(String text, int x, int y) {
        drawString(text, (float) x, (float) y);
    }

    public void clip(Shape s) {
        System.out.println("clip(Shape s)");
    }

    public void drawGlyphVector(GlyphVector g, float x, float y) {
        System.out.println("drawGlyphVector(GlyphVector g, float x, float y)");
    }

    public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y) {
        System.out.println("drawImage(BufferedImage image, BufferedImageOp op, int x, int y)");
    }

    public boolean drawImage(Image image, AffineTransform xform, ImageObserver obs) {
        System.out.println("drawImage(Image image, AffineTransform xform, ImageObserver obs)");
        return false;
    }

    public void drawRenderableImage(RenderableImage image, AffineTransform xform) {
        System.out.println("drawRenderableImage(RenderableImage image, AffineTransform xform)");
    }

    public void drawRenderedImage(RenderedImage image, AffineTransform xform) {
        System.out.println("drawRenderedImage(RenderedImage image, AffineTransform xform)");
    }

    public void drawString(AttributedCharacterIterator iterator, float x, float y) {
        System.out.println("drawString(AttributedCharacterIterator iterator, float x, float y)");
    }

    public void drawString(AttributedCharacterIterator iterator, int x, int y) {
        System.out.println("drawString(AttributedCharacterIterator iterator, int x, int y)");
    }

    public Composite getComposite() {
        System.out.println("getComposite()");
        return null;
    }

    public GraphicsConfiguration getDeviceConfiguration() {
        System.out.println("getDeviceConfiguration()");
        return null;
    }

    public Object getRenderingHint(Key hintKey) {
        System.out.println("getRenderingHint(Key hintKey)");
        return null;
    }

    public RenderingHints getRenderingHints() {
        System.out.println("getRenderingHints()");
        return null;
    }

    public boolean hit(Rectangle rect, Shape text, boolean onStroke) {
        System.out.println("hit(Rectangle rect, Shape text, boolean onStroke)");
        return false;
    }

    public void rotate(double theta, double x, double y) {
        System.out.println("rotate(double theta, double x, double y)");
    }

    public void rotate(double theta) {
        System.out.println("rotate(double theta)");
    }

    public void scale(double scaleX, double scaleY) {
        System.out.println("scale(double scaleX, double scaleY)");
    }

    public void setComposite(Composite comp) {
        System.out.println("setComposite(Composite comp)");
    }

    public void setRenderingHint(Key hintKey, Object hintValue) {
        System.out.println("");
    }

    public void setRenderingHints(Map hints) {
        System.out.println("setRenderingHints(Map hints)");
    }

    public void shear(double shearX, double shearY) {
        System.out.println("shear(double shearX, double shearY)");
    }

    public void clearRect(int x, int y, int width, int height) {
        System.out.println("clearRect(int x, int y, int width, int height)");
    }

    public void clipRect(int x, int y, int width, int height) {
        System.out.println("clipRect(int x, int y, int width, int height)");
    }

    public void copyArea(int x, int y, int width, int height, int dx, int dy) {
        System.out.println("copyArea(int x, int y, int width, int height, int dx, int dy)");
    }

    public Graphics create() {
        System.out.println("create()");
        return null;
    }

    public void dispose() {
        System.out.println("dispose()");
    }

    public void drawArc(int x, int y, int width, int height, int arcStart, int arcAngle) {
        System.out.println("drawArc(int x, int y, int width, int height, int arcStart, int arcAngle)");
    }

    public boolean drawImage(Image image, int x, int y, Color bgcolor, ImageObserver observer) {
        System.out.println("drawImage(Image image, int x, int y, Color bgcolor, ImageObserver observer)");
        return false;
    }

    public boolean drawImage(Image image, int x, int y, ImageObserver observer) {
        System.out.println("drawImage(Image image, int x, int y, ImageObserver observer)");
        return false;
    }

    public boolean drawImage(Image image, int x, int y, int width, int height, Color bgcolor, ImageObserver observer) {
        System.out.println("drawImage(Image image, int x, int y, int width, int height, Color bgcolor, ImageObserver observer)");
        return false;
    }

    public boolean drawImage(Image image, int x, int y, int width, int height, ImageObserver observer) {
        System.out.println("drawImage(Image image, int x, int y, int width, int height, ImageObserver observer)");
        return false;
    }

    public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver observer) {
        System.out.println("drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer)");
        return false;
    }

    public boolean drawImage(Image image, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver observer) {
        System.out.println("");
        return false;
    }

    public void drawLine(int x1, int y1, int x2, int y2) {
        System.out.println("drawLine(int x1, int y1, int x2, int y2)");
    }

    public void drawOval(int x, int y, int width, int height) {
        System.out.println("drawOval(int x, int y, int width, int height)");
    }

    public void drawPolygon(int[] xPoints, int[] yPoints, int npoints) {
        System.out.println("drawPolygon(int[] xPoints, int[] yPoints, int npoints)");
    }

    public void drawPolyline(int[] xPoints, int[] yPoints, int npoints) {
        System.out.println("drawPolyline(int[] xPoints, int[] yPoints, int npoints)");
    }

    public void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
        System.out.println("fillArc(int x, int y, int width, int height, int arcStart, int arcAngle)");
    }

    public void fillArc(int x, int y, int width, int height, int arcStart, int arcAngle) {
        System.out.println("");
    }

    public void fillOval(int x, int y, int width, int height) {
        System.out.println("fillOval(int x, int y, int width, int height)");
    }

    public void fillPolygon(int[] xPoints, int[] yPoints, int npoints) {
        System.out.println("fillPolygon(int[] xPoints, int[] yPoints, int npoints)");
    }

    public void fillRect(int x, int y, int width, int height) {
        System.out.println("fillRect(int x, int y, int width, int height)");
    }

    public void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) {
        System.out.println("fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)");
    }

    public Shape getClip() {
        System.out.println("getClip()");
        return null;
    }

    public Rectangle getClipBounds() {
        System.out.println("getClipBounds()");
        return null;
    }

    public Color getColor() {
        System.out.println("getColor()");
        return null;
    }

    public void setClip(int x, int y, int width, int height) {
        System.out.println("setClip(int x, int y, int width, int height)");
    }

    public void setClip(Shape clip) {
        System.out.println("setClip(Shape clip)");        
    }

    public void setPaintMode() {
        System.out.println("setPaintMode()");
    }

    public void setXORMode(Color color) {
        System.out.println("setXORMode(Color color)");
    }

}
