// Copyright 2001-2003 FreeHEP
package org.freehep.graphicsio;

import java.awt.geom.Point2D;
import java.awt.geom.Line2D;
import java.io.IOException;

/**
 * Implements the Cubic Bezier Curve PathConstructor functionality in terms of
 * Quadratic Bezier Curves
 *
 * Uses an algorithm puplished as ActionScript (SWF) by:
 *
 * Robert Penner
 * www.robertpenner.com
 *
 *==========================
 * Cubic Bezier Drawing v0.9
 *==========================
 * recursive quadratic approximation with adjustable tolerance
 *
 * May 20, 2002
 *
 * @author Mark Donszelmann
 * @version $Id: CubicToQuadPathConstructor.java,v 1.4 2003/06/09 21:52:57 duns Exp $
 */
public abstract class CubicToQuadPathConstructor extends AbstractPathConstructor {

    private double resolutionSq;

    protected CubicToQuadPathConstructor(double resolution) {
        super();
        resolutionSq = resolution*resolution;
    }

    public void move(double x, double y) throws IOException {
        currentX = x;
        currentY = y;
    }

    public void line(double x, double y) throws IOException {
        currentX = x;
        currentY = y;
    }

    public void cubic(double x1, double y1, double x2, double y2, double x3, double y3) throws IOException {
        quadratify(new Point2D.Double(currentX, currentY),
                   new Point2D.Double(x1, y1),
                   new Point2D.Double(x2, y2),
                   new Point2D.Double(x3, y3));

        currentX = x3;
        currentY = y3;
    }

    public void closePath() throws IOException {
        currentX = 0;
        currentY = 0;
    }

    public static Point2D intersect(Point2D p1, Point2D p2, Point2D p3, Point2D p4) {

    	double dx1 = p2.getX() - p1.getX();
    	double dx2 = p3.getX() - p4.getX();
    	if ((dx1 == 0) && (dx2 == 0)) return null;

    	double m1 = (p2.getY() - p1.getY()) / dx1;
    	double m2 = (p3.getY() - p4.getY()) / dx2;

    	if (dx1 == 0) {
    		// infinity
    		return new Point2D.Double(p1.getX(),
    		                          m2 * (p1.getX() - p4.getX()) + p4.getY());
    	} else if (dx2 == 0) {
    		// infinity
    		return new Point2D.Double(p4.getX(),
    		                          m1 * (p4.getX() - p1.getX()) + p1.getY());
    	}
    	double x = (-m2 * p4.getX() + p4.getY() + m1 * p1.getX() - p1.getY()) / (m1 - m2);
    	double y = m1 * (x - p1.getX()) + p1.getY();
    	return new Point2D.Double(x, y);
    }

    public static Point2D midPoint(Point2D a, Point2D b) {
    	return new Point2D.Double(
    	   (a.getX() + b.getX())/2.0,
    	   (a.getY() + b.getY())/2.0
    	);
    }

    public void quadratify(Point2D a, Point2D b, Point2D c, Point2D d) throws IOException {
    	// find intersection between bezier arms
    	Point2D s = intersect(a, b, c, d);
    	// find distance between the midpoints
    	double dx = (a.getX() + d.getX() + s.getX() * 4 - (b.getX() + c.getX()) * 3) * .125;
    	double dy = (a.getY() + d.getY() + s.getY() * 4 - (b.getY() + c.getY()) * 3) * .125;
    	// split curve if the quadratic isn't close enough
    	if (dx*dx + dy*dy > resolutionSq) {
        	Point2D p01 = midPoint(a, b);
        	Point2D p12 = midPoint(b, c);
        	Point2D p23 = midPoint(c, d);
        	Point2D p02 = midPoint(p01, p12);
        	Point2D p13 = midPoint(p12, p23);
        	Point2D p03 = midPoint(p02, p13);
    		// recursive call to subdivide curve
    		quadratify(a,    p01, p02, p03);
    		quadratify(p03,  p13, p23, d);
    	} else {
    		// end recursion by drawing quadratic bezier
    		quad(s.getX(), s.getY(), d.getX(), d.getY());
    	}
    }

    static class Test extends CubicToQuadPathConstructor {
        public Test(double resolution) {
            super(resolution);
        }

        public void quad(double x1, double y1, double x2, double y2) {
            System.out.println("Quad: ("+currentX+", "+currentY+") ("+x1+", "+y1+") ("+x2+", "+y2+")");
            currentX = x2;
            currentY = y2;
        }

    }

    public static void main(String[] args) throws Exception {
        PathConstructor pc = new Test(0.5);
        // A, B, C, D
        pc.move(20, 20);
        pc.cubic(20, 40, 40, 60, 60, 60);

        // A, B, D, C
        pc.move(20, 20);
        pc.cubic(20, 40, 60, 60, 40, 60);

        // Intersecting Curve
        pc.move(183, 149);
        pc.cubic(189, 291, 256, 347, 295, 244);
        pc.cubic(334, 141, 286, 216, 214, 228);
        pc.cubic(142, 240, 142, 256, 176, 284);
    }
}
