import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.io.File;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;

import org.apache.batik.parser.AWTTransformProducer;
import org.apache.batik.parser.TransformListParser;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.events.MouseEvent;
import org.w3c.dom.svg.SVGDocument;
import org.w3c.dom.svg.SVGLocatable;
import org.w3c.dom.svg.SVGMatrix;
import org.w3c.dom.svg.SVGPoint;

/**
 * shows the move issue. The application opens a frame containing rect.svg (which has to be in the execution folder)
 * rect.svg contains 3 rects. You can click on the blue one, hold and move the mouse. I want the 3 rectangles to move together,
 * so that their relative position does not change.
 * 
 * In <code>convertCoord</code> I have implemented the 2 solutions of the thread. I can see that my solution does not work (as Thomas said)
 * but the code of Jonathan does not work neither (just switch the comments in the method for the 2 implementations). 
 * Furthermore, when you move the mouse, the rects does not move smoothly and does not really follows the cursor.
 * 
 * @author hodac
 * 
 */
public class MoveIssue {

	/**
	 * wraps the element into a class to provide some move methods.
	 * @author hodac
	 *
	 */
	private class MyRect {

		private final Element element;
		/**
		 * holds the initial transformation when the user clicks left button
		 */
		private AffineTransform initialAffineTransform;

		public MyRect(Element element) {
			this.element = element;
		}

		/**
		 * called when the user clicks left button on the rect 
		 */
		public void beginMove() {
			initialAffineTransform = getAffineTransform(element);
		}

		/**
		 * actually moves the element within the canvas to follow the mouse pointer.
		 * @param x
		 * @param y
		 */
		public void move(double x, double y) {
			AffineTransform at = translate(initialAffineTransform, x, y);
			element.setAttribute("transform", getTransform(at));
		}

	}
	
	public static void main(String[] args) {
		// Create a new JFrame.
		JFrame f = new JFrame("Batik move problem");
		MoveIssue app = new MoveIssue(f);

		// Add components to the frame.
		f.getContentPane().add(app.createCanvas());

		// Display the frame.
		f.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		f.setSize(400, 400);
		f.setVisible(true);
	}

	// The frame.
	protected JFrame frame;

	// The SVG canvas.
	protected JSVGCanvas svgCanvas;

	protected MyRect greenRect;

	protected MyRect redRect;

	protected MyRect blueRect;

	protected SVGPoint mouseDownPoint;

	public MoveIssue(JFrame f) {
		frame = f;
	}

	/**
	 * just creates the canvas and add a renderer listener.
	 * 
	 * @return
	 */
	public JComponent createCanvas() {
		svgCanvas = new JSVGCanvas();
		svgCanvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
		svgCanvas.setURI(new File("rect.svg").toURI().toString());

		final JPanel panel = new JPanel(new BorderLayout());
		panel.add("Center", svgCanvas);

		svgCanvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
			public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
				// we get the 3 rectangles
				greenRect = new MyRect(svgCanvas.getSVGDocument().getElementById("greenRect"));
				redRect = new MyRect(svgCanvas.getSVGDocument().getElementById("redRect"));
				blueRect = new MyRect(svgCanvas.getSVGDocument().getElementById("blueRect"));
				// we add some listeners to the blue rect
				addMouseMoveListener(blueRect);
				addMouseDownListener(blueRect);
			}
		});

		return panel;
	}

	/**
	 * initializes the context of the move. puts the current mouse pointer position into <code>mouseDownPoint</code>
	 * @param rect
	 */
	private void addMouseDownListener(final MyRect rect) {
		((EventTarget) rect.element).addEventListener("mousedown", new org.w3c.dom.events.EventListener() {

			public void handleEvent(Event evt) {
				MouseEvent e = (MouseEvent) evt;
				if (e.getButton() == 0) {
					try {
						mouseDownPoint = convertCoord(e.getClientX(), e.getClientY(), ((SVGLocatable) rect.element));
						blueRect.beginMove();
						greenRect.beginMove();
						redRect.beginMove();
					} catch (NoninvertibleTransformException e1) {
						e1.printStackTrace();
					}
				}
			}

		}, false);
	}
	
	/**
	 * actually performs the move: gets the current position of the mouse and compares it to the <code>mouseDownPoint</code>
	 * to deduce the translation.
	 * @param rect
	 */
	private void addMouseMoveListener(final MyRect rect) {
		((EventTarget) rect.element).addEventListener("mousemove", new org.w3c.dom.events.EventListener() {

			public void handleEvent(Event evt) {
				MouseEvent e = (MouseEvent) evt;

				SVGPoint mouseCurrentPoint;
				try {
					if (e.getButton() == 0) {
						mouseCurrentPoint = convertCoord(e.getClientX(), e.getClientY(), ((SVGLocatable) rect.element));
						double x = mouseCurrentPoint.getX() - mouseDownPoint.getX();
						double y = mouseCurrentPoint.getY() - mouseDownPoint.getY();
						blueRect.move(x, y);
						greenRect.move(x, y);
						redRect.move(x, y);
					}

				} catch (NoninvertibleTransformException e1) {
					e1.printStackTrace();
				}
			}

		}, false);
	}


	/**
	 * converts the screen coords to the element or canvas coords
	 * @param screenX
	 * @param screenY
	 * @param elt
	 * @return
	 * @throws NoninvertibleTransformException
	 */
	public SVGPoint convertCoord(int screenX, int screenY, SVGLocatable elt) throws NoninvertibleTransformException {
//		SVGDocument doc = svgCanvas.getSVGDocument();
//		Point2D ptAwt = new Point(screenX, screenY);
//		AffineTransform tr = svgCanvas.getViewBoxTransform();
//		Point2D pt;
//		pt = tr.inverseTransform(ptAwt, null);
//		SVGPoint svgPoint = ((SVGDocument) doc).getRootElement().createSVGPoint();
//		svgPoint.setX((float) pt.getX());
//		svgPoint.setY((float) pt.getY());
//		return svgPoint;
		 SVGDocument doc = svgCanvas.getSVGDocument();
		 SVGPoint svgPoint = doc.getRootElement().createSVGPoint();
		 svgPoint.setX(screenX);
		 svgPoint.setY(-screenY);
		 SVGMatrix m = elt.getScreenCTM();
		 m = m.inverse();
		 svgPoint = svgPoint.matrixTransform(m);
		 return svgPoint;
	}
	
	
	//////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////from TransformUtil code (see batik wiki)/////////////////////////////
	//////////////////////////////////////////////////////////////////////////////////////////////
    private static final String TRANSFORM_LITERAL = "transform";

    public static final AffineTransform getAffineTransform(Element element) {
        TransformListParser p = new TransformListParser();
        AWTTransformProducer tp = new AWTTransformProducer();
        p.setTransformListHandler(tp);
        p.parse(element.getAttributeNS(null, TRANSFORM_LITERAL));
        return tp.getAffineTransform();
    }
    
    public static AffineTransform translate(AffineTransform at, double x, double y) {
    	AffineTransform translate = AffineTransform.getTranslateInstance(x, y);
    	translate.concatenate(at);
    	return translate;
    }

    public static final String getTransform(AffineTransform at) {
        double[] matrix = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
        at.getMatrix(matrix);
        StringBuilder sb = new StringBuilder();
        sb.append("matrix(");
        sb.append(matrix[0]);
        sb.append(" ");
        sb.append(matrix[1]);
        sb.append(" ");
        sb.append(matrix[2]);
        sb.append(" ");
        sb.append(matrix[3]);
        sb.append(" ");
        sb.append(matrix[4]);
        sb.append(" ");
        sb.append(matrix[5]);
        sb.append(")");
        return sb.toString();
    }

}
