/*
 * Copyright (c) 2005 David Flanagan
 * 
 * You may use, modify and distribute this code for any purpose
 * provided that this notice is retained.  
 * This code has NO WARRANTY.
 */
import java.applet.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import java.util.List;
import java.net.URL;
import javax.swing.*;

// 
// This applet implements a slightly modified version of the Canvas API.
// Its methods are not intended to be invoked directly, but through the
// JavaScript functions in canvas.xbl.
//
// The functions all draw into an offscreen image. None of them call repaint().
// JavaScript functions must do this, and canvas.xbl schedules a repaint
// after doing any drawing.
//
public class Canvas extends Applet {
    BufferedImage image;
    Graphics2D g;
    // XXX These need to be generalized to use Paint instead of Color
    // I can't map two of them to a single setPaint() method, so I've got
    // to set the paint before each drawing operation.  And save and 
    // restore these values along with the Graphics2D
    Color strokeStyle;
    Color fillStyle;
    GeneralPath path = new GeneralPath();

    List<Graphics2D> contexts = new ArrayList<Graphics2D>();
    List<Color> strokeStyles = new ArrayList<Color>();
    List<Color> fillStyles = new ArrayList<Color>();

    public void init() {
	int w = getWidth();
	int h = getHeight();
	image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
	g = image.createGraphics();
	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
			   RenderingHints.VALUE_ANTIALIAS_ON);
    }

    public void paint(Graphics g) { g.drawImage(image, 0, 0, this); }

    public void save() {
	contexts.add((Graphics2D)g.create());
	strokeStyles.add(strokeStyle);
	fillStyles.add(fillStyle);
    }
    
    public void restore() {
	strokeStyle = strokeStyles.remove(strokeStyles.size()-1);
	fillStyle = fillStyles.remove(fillStyles.size()-1);
	g = contexts.remove(contexts.size()-1);
    }

    public void rotate(float angle) { g.rotate(angle); }
    public void scale(float x, float y) { g.scale(x,y); }
    public void translate(float x, float y) { g.translate(x,y); }

    public float getGlobalAlpha() { return 1.0f; }
    public void setGlobalAlpha(float alpha) { }

    public String getGlobalCompositeOperation() { return "over"; }
    public void setGlobalCompositeOperation(String op) {}

    // Just partial implementations
    public String getStrokeStyle() {
	return "#" + Integer.toHexString(strokeStyle.getRGB());
    }
    public String getFillStyle() {
	return "#" + Integer.toHexString(fillStyle.getRGB());
    }
    public void setStrokeStyle(String color) {
	// strokeStyle = Color.decode(color.substring(1));
	strokeStyle = new Color(Integer.parseInt(color.substring(1), 16));
    }
    public void setFillStyle(String color) {
	// fillStyle = Color.decode(color.substring(1));
	fillStyle = new Color(Integer.parseInt(color.substring(1), 16));
    }

    public float getLineWidth() {
	return ((BasicStroke)g.getStroke()).getLineWidth();
    }
    public void setLineWidth(float w) {
	BasicStroke old = (BasicStroke)g.getStroke();
	g.setStroke(new BasicStroke(w, 
				    old.getEndCap(),
				    old.getLineJoin(),
				    old.getMiterLimit()));
    }

    public float getMiterLimit() {
	return ((BasicStroke)g.getStroke()).getMiterLimit();
    }
    public void setMiterLimit(float ml) {
	BasicStroke old = (BasicStroke)g.getStroke();
	g.setStroke(new BasicStroke(old.getLineWidth(), 
				    old.getEndCap(),
				    old.getLineJoin(),
				    ml));
    }

    public String getLineCap() {
	switch(((BasicStroke)g.getStroke()).getEndCap()) {
	case BasicStroke.CAP_BUTT: return "butt";
	case BasicStroke.CAP_ROUND: return "round";
	case BasicStroke.CAP_SQUARE: return "square";
	default: throw new AssertionError();
	}
	
    }

    public void setLineCap(String cap) {
	int c;
	if (cap.equals("butt")) c = BasicStroke.CAP_BUTT;
	else if (cap.equals("round")) c = BasicStroke.CAP_ROUND;
	else if (cap.equals("square")) c = BasicStroke.CAP_SQUARE;
	else return;

	BasicStroke old = (BasicStroke)g.getStroke();
	g.setStroke(new BasicStroke(old.getLineWidth(), 
				    c,
				    old.getLineJoin(),
				    old.getMiterLimit()));
    }

    public String getLineJoin() {
	switch(((BasicStroke)g.getStroke()).getLineJoin()) {
	case BasicStroke.JOIN_MITER: return "miter";
	case BasicStroke.JOIN_ROUND: return "round";
	case BasicStroke.JOIN_BEVEL: return "bevel";
	default: throw new AssertionError();
	}
    }
    public void setLineJoin(String join) {
	int j;
	if (join.equals("miter")) j = BasicStroke.JOIN_MITER;
	else if (join.equals("round")) j = BasicStroke.JOIN_ROUND;
	else if (join.equals("bevel")) j = BasicStroke.JOIN_BEVEL;
	else return;

	BasicStroke old = (BasicStroke)g.getStroke();
	g.setStroke(new BasicStroke(old.getLineWidth(), 
				    old.getEndCap(),
				    j,
				    old.getMiterLimit()));

    }

    public void clearRect(float x, float y, float w, float h) {
	g.clearRect((int)x,(int)y,(int)w,(int)h);
    }

    public void fillRect(float x, float y, float w, float h) {
	g.setPaint(fillStyle);
	g.fill(new Rectangle2D.Float(x,y,w,h));
    }

    public void strokeRect(float x, float y, float w, float h) {
	g.setPaint(strokeStyle);
	g.draw(new Rectangle2D.Float(x,y,w,h));
    }


    public void beginPath() {
	path.reset();
	path.moveTo(0,0);
    }
    public void closePath() { path.closePath(); }
    public void moveTo(float x, float y) { path.moveTo(x,y); }
    public void lineTo(float x, float y) { path.lineTo(x,y); }

    public void quadraticCurveTo(float cx, float cy, float x, float y) {
	path.quadTo(cx, cy, x, y);
    }
    
    public void bezierCurveTo(float cx1, float cy1, float cx2, float cy2,
			      float x, float y)
    {
	path.curveTo(cx1, cy1, cx2, cy2, x, y);
    }

    public void rect(float x, float y, float w, float h) {
	path.append(new Rectangle2D.Float(x,y,w,h), false);
	path.moveTo(0,0);
    }

    public void arcTo(float x1, float y1, float x2, float y2, float radius) {
    }

    public void arc(float x, float y, float radius,
		    float startAngle, float endAngle,
		    boolean anticlockwise)
    {
    }

    public void fill() {
	g.setPaint(fillStyle);
	g.fill(path);
    }

    public void stroke() {
	g.setPaint(strokeStyle);
	g.draw(path);
    }

    public void clip() {
	g.setClip(path);
    }
    

    Map<String,Image> imagemap = new HashMap<String,Image>();

    Image getImage(String url) throws java.net.MalformedURLException {
	Image i = imagemap.get(url);
	if (i != null) return i;

	ImageIcon icon = new ImageIcon(new URL(url));
	i = icon.getImage();
	imagemap.put(url, i);
	return i;
    }

    public void drawImage(String url, float x, float y, float w, float h)
	throws java.net.MalformedURLException
    {
	Image i = getImage(url);
	//g.drawImage(i, (int)x, (int)y, (int)w, (int)h, Color.BLACK, this);


	AffineTransform t = AffineTransform.getTranslateInstance(x,y);

	int iw = i.getWidth(this);
	int ih = i.getHeight(this);
	float sx = w/iw;
	float sy = h/ih;
	t.scale(sx, sy);

	g.drawImage(i, t, this);
    }

    public void drawImage(String url, float x, float y)
	throws java.net.MalformedURLException
    {
	Image i = getImage(url);
	g.drawImage(i, 
		    AffineTransform.getTranslateInstance(x,y),
		    this);
    }


}


