/* @(#)CubicSubdivision.java   16 October 2006 */

/* Useful imports */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.jpf.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.quick.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.regex.*;
import java.text.ParseException;


public class CubicSubdivision
    extends DisplayPanel
{
    /**
     * The paintable sequence variable that will represent
     * the cubic subdivision structure.
     * 
     * This sequence will be constructed just before it is
     * time to paint the cubic subdivision.
     * 
     * This sequence will be placed in the tile.
     */
    PaintableSequence sequence = null;
    
    
    /** The tile size. */
    int size = 500;
    
    /** The tile bounds. */
    XRect bounds = new XRect(0, 0, size, size);
    
    
    /** The tile to display the cubic structure. */
    Tile tile = new Tile();
    
    // The tile initialization.
    {
        tile.setDefaultBounds2D(bounds);
    }
    
    
    /** The paintable component to hold the tile. */
    PaintableComponent tileComponent =
        new PaintableComponent(tile);
    
    // The tile component initialization.
    {
        tileComponent.setBackground(Colors.white);
        tileComponent.setOpaque(true);
        tileComponent.setBorder(Borders.line(2));
    }
    
    // The points
    
    XPoint2D P0 = new XPoint2D(100, 400);
    
    XPoint2D P1 = new XPoint2D( 50,  50);
    
    XPoint2D P2 = new XPoint2D(450, 200);
    
    XPoint2D P3 = new XPoint2D(350, 450);
    
    XPoint2D Q0 = new XPoint2D();
    
    XPoint2D Q1 = new XPoint2D();
    
    XPoint2D Q2 = new XPoint2D();
    
    XPoint2D R0 = new XPoint2D();
    
    XPoint2D R1 = new XPoint2D();
    
    XPoint2D S0 = new XPoint2D();
    
    
    // The paints
    
    Paint paint0 = Colors.red;
    
    Paint paint1 = Colors.mediumvioletred;
    
    Paint paint2 = Colors.blueviolet;
    
    Paint paint3 = Colors.lightskyblue;
    
    
    /** The error for mouse hits. */
    int epsilon = 5;
    
    /** The plotmark for the points. */
    PlotMark mark = new PlotMark(PlotMark.FILLED_SQUARE, epsilon);
    
    /** The stroke for the cubic and the lines. */
    BasicStroke stroke = new BasicStroke(2);
    
    /** The label font. */
    Font font = null;
    
    /** The label locator strategy. */
    TextAnchor.Locator locator = TextAnchor.CENTER_ASCENTLINE;
    
    
    /** True if mouse position in vertex. */
    boolean inVertex = false;
    
    /** The vertex for the mouse point. */
    XPoint2D vertex;
    
    
    /** The mouse pressed action. */
    private MouseAction mousePressed = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            mousePressed(evt);
        }
    };
    
    
    /** The mouse dragged action. */
    private MouseAction mouseDragged = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            mouseDragged(evt);
        }
    };
    
    
    /** The action to paint the structure. */
    private SimpleAction paintStructure =
        new SimpleAction("Paint Structure") {
            public void perform() { paintStructure(); }
    };
    
    
    /** The view to determine whether to show labels. */
    BooleanView showLabels =
        new BooleanView("Show Point Labels", paintStructure, true);
    
    
    /** The panel gap. */
    int gap = 10;
    
    /** The stuff for the structure panel. */
    Object[] structureStuff = { tileComponent, showLabels };
    
    /** The structure panel. */
    VTable structurePanel =
        new VTable(structureStuff, gap, gap, CENTER);
    
    
    /** The annotation array for P row in data panel. */
    Annotation[] PRow = new Annotation[12];
    
    /** The annotation array for Q row in data panel. */
    Annotation[] QRow = new Annotation[9];
    
    /** The annotation array for R row in data panel. */
    Annotation[] RRow = new Annotation[6];
    
    /** The annotation array for S row in data panel. */
    Annotation[] SRow = new Annotation[3];
    
    
    /** The cubic data panel. */
    TablePanel dataPanel = new TablePanel(10, 3, gap, gap, CENTER);
    
    {
        dataPanel.emptyBorder(8);
        dataPanel.lineBorder(2);
    }
    
    
    /**
     * The tabbed pane to hold the cubic structure tab
     * and the cubic data tab.
     */
    JTabbedPane tabbedPane =
        new JTabbedPane();
    
    // The tabbed pane initialization.
    {
        tabbedPane.add("Cubic Structure", structurePanel);
        tabbedPane.add("Cubic Data",      dataPanel     );
    }
    
    
    /** The constructor to complete initialization. */
    public CubicSubdivision() {
        add(tabbedPane);
        
        setLabelFont();
        initDataPanel();
        paintStructure();
        addMouseActions();
    }
    
    
    /** Set the font for the labels. */
    void setLabelFont() {
        String name = Fonts.getMonospacedFontFamilyName();
        int fontsize = 20;
        
        font = new Font(name, Font.BOLD, fontsize);
    }
    
    
    void addMouseActions() {
        MouseActionAdapter adapter =
            tileComponent.getMouseActionAdapter();
        
        adapter.addMousePressedAction(mousePressed);
        adapter.addMouseDraggedAction(mouseDragged);
    }
    
    
    /**
     * Perform the midpoint subdivision of the Bezier frame
     * defined by P0, P1, P2, P3.  Place the data into
     * Q0, Q1, Q2, R0, R1, S0.
     */
    void subdivide() {
        Q0.x = (P0.x + P1.x) / 2;
        Q0.y = (P0.y + P1.y) / 2;
        
        Q1.x = (P1.x + P2.x) / 2;
        Q1.y = (P1.y + P2.y) / 2;
        
        Q2.x = (P2.x + P3.x) / 2;
        Q2.y = (P2.y + P3.y) / 2;
        
        R0.x = (Q0.x + Q1.x) / 2;
        R0.y = (Q0.y + Q1.y) / 2;
        
        R1.x = (Q1.x + Q2.x) / 2;
        R1.y = (Q1.y + Q2.y) / 2;
        
        S0.x = (R0.x + R1.x) / 2;
        S0.y = (R0.y + R1.y) / 2;
    }
    
    
    /** Paint the cubic structure. */
    void paintStructure() {
        subdivide();
        
        sequence = new PaintableSequence();
        
        appendLabels();
        appendPoints();
        appendCubic();
        appendLines();
        
        tile.setPaintable(sequence);
        
        updateDataPanel();
    }
    
    
    /** Append the point labels to the sequence. */
    void appendLabels() {
        if (! showLabels())
            return;
        
        appendLabel("P0", P0, 10, paint0);
        appendLabel("P1", P1, 10, paint0);
        appendLabel("P2", P2, 10, paint0);
        appendLabel("P3", P3, 10, paint0);
        
        appendLabel("Q0", Q0, 10, paint1);
        appendLabel("Q1", Q1, 10, paint1);
        appendLabel("Q2", Q2, 10, paint1);
        
        appendLabel("R0", R0, 10, paint2);
        appendLabel("R1", R1, 10, paint2);
        
        appendLabel("S0", S0, 10, paint3);
    }
    
    
    /** Append the points to the sequence. */
    void appendPoints() {
        PointPaintable p0 =
            new PointPaintable(P0, mark, paint0);
        
        PointPaintable p1 =
            new PointPaintable(P1, mark, paint0);
        
        PointPaintable p2 =
            new PointPaintable(P2, mark, paint0);
        
        PointPaintable p3 =
            new PointPaintable(P3, mark, paint0);
        
        PointPaintable q0 =
            new PointPaintable(Q0, mark, paint1);
        
        PointPaintable q1 =
            new PointPaintable(Q1, mark, paint1);
        
        PointPaintable q2 =
            new PointPaintable(Q2, mark, paint1);
        
        PointPaintable r0 =
            new PointPaintable(R0, mark, paint2);
        
        PointPaintable r1 =
            new PointPaintable(R1, mark, paint2);
        
        PointPaintable s0 =
            new PointPaintable(S0, mark, paint3);
        
        sequence.appendPaintable(p0);
        sequence.appendPaintable(p1);
        sequence.appendPaintable(p2);
        sequence.appendPaintable(p3);
        sequence.appendPaintable(q0);
        sequence.appendPaintable(q1);
        sequence.appendPaintable(q2);
        sequence.appendPaintable(r0);
        sequence.appendPaintable(r1);
        sequence.appendPaintable(s0);
    }
    
    
    /** Append the cubic curve to the sequence. */
    void appendCubic() {
        float x0 = (float) P0.x;
        float y0 = (float) P0.y;
        
        float x1 = (float) P1.x;
        float y1 = (float) P1.y;
        
        float x2 = (float) P2.x;
        float y2 = (float) P2.y;
        
        float x3 = (float) P3.x;
        float y3 = (float) P3.y;
        
        GeneralPath path = new GeneralPath();
        
        path.moveTo(x0, y0);
        path.curveTo(x1, y1, x2, y2, x3, y3);
        
        ShapePaintable cubic =
            new ShapePaintable
                (path, PaintMode.DRAW, null, Colors.black, stroke);
        
        sequence.appendPaintable(cubic);
    }
    
    
    /** Append the lines to the sequence. */
    void appendLines() {
        appendLine(P0, P1, paint0);
        appendLine(P1, P2, paint0);
        appendLine(P2, P3, paint0);
        
        appendLine(Q0, Q1, paint1);
        appendLine(Q1, Q2, paint1);
        
        appendLine(R0, R1, paint2);
    }
    
    
    /** Whether to show point labels. */
    boolean showLabels() {
        return showLabels.getBooleanValue();
    }
    
    
    /**
     * Append the label
     * below the given point
     * by the given amount delta
     * using the given paint.
     */
    void appendLabel
        (String label, XPoint2D a, double delta, Paint paint)
    {
        float x = (float) a.x;
        float y = (float) (a.y + delta);
        
        TextPaintable tp =
            new TextPaintable
                (label, font, paint, locator, x, y);
        
        sequence.appendPaintable(tp);
    }
    
    
    void appendLine(XPoint2D a, XPoint2D b, Paint paint) {
        XLine2D line = new XLine2D(a.x, a.y, b.x, b.y);
        
        ShapePaintable sp =
            new ShapePaintable
                (line, PaintMode.DRAW, null, paint, stroke);
        
        sequence.appendPaintable(sp);
    }
    
    
    /** The mouse pressed behavior. */
    void mousePressed(MouseEvent evt) {
        vertex = null;
        
        int x = evt.getX();
        int y = evt.getY();
        
        if (Metric.MAX.isNear(P0.x, P0.y, x, y, epsilon))
            vertex = P0;
        else
        if (Metric.MAX.isNear(P1.x, P1.y, x, y, epsilon))
            vertex = P1;
        else
        if (Metric.MAX.isNear(P2.x, P2.y, x, y, epsilon))
            vertex = P2;
        else
        if (Metric.MAX.isNear(P3.x, P3.y, x, y, epsilon))
            vertex = P3;
        
        inVertex = (vertex != null);
    }
    
    
    /** The mouse dragged behavior. */
    void mouseDragged(MouseEvent evt) {
        if (inVertex) {
            vertex.x = evt.getX();
            vertex.y = evt.getY();
            
            paintStructure();
        }
    }
    
    
    /**
     * Initialize the labels in the data panel
     * and set minimum column widths.
     */
    void initDataPanel() {
        for (int i = 0; i < 12; i++) {
            PRow[i] = new Annotation("", font);
            
            int row = i / 3;
            int col = i % 3;
            
            dataPanel.addObject(PRow[i], row, col);
        }
        
        for (int i = 0; i < 9; i++) {
            QRow[i] = new Annotation("", font);
            
            int row = i / 3 + 4;
            int col = i % 3;
            
            dataPanel.addObject(QRow[i], row, col);
        }
        
        for (int i = 0; i < 6; i++) {
            RRow[i] = new Annotation("", font);
            
            int row = i / 3 + 7;
            int col = i % 3;
            
            dataPanel.addObject(RRow[i], row, col);
        }
        
        for (int i = 0; i < 3; i++) {
            SRow[i] = new Annotation("", font);
            
            int row = i / 3 + 9;
            int col = i % 3;
            
            dataPanel.addObject(SRow[i], row, col);
        }
        
        int width = 120;
        
        for (int i = 0; i < 3; i++)
            dataPanel.setMinimumColumnWidth(i, width);

        
        PRow[0].setText("P0");
        PRow[3].setText("P1");
        PRow[6].setText("P2");
        PRow[9].setText("P3");
        
        QRow[0].setText("Q0");
        QRow[3].setText("Q1");
        QRow[6].setText("Q2");
        
        RRow[0].setText("R0");
        RRow[3].setText("R1");
        
        SRow[0].setText("S0");
        
        dataPanel.setBackground(Colors.gold);
    }
    
    
    /** Update the coordinate data in the data panel. */
    void updateDataPanel() {
        PRow[1].setText(P0.x + "");
        PRow[2].setText(P0.y + "");
        
        PRow[4].setText(P1.x + "");
        PRow[5].setText(P1.y + "");
        
        PRow[7].setText(P2.x + "");
        PRow[8].setText(P2.y + "");
        
        PRow[10].setText(P3.x + "");
        PRow[11].setText(P3.y + "");
        
        QRow[1].setText(Q0.x + "");
        QRow[2].setText(Q0.y + "");
        
        QRow[4].setText(Q1.x + "");
        QRow[5].setText(Q1.y + "");
        
        QRow[7].setText(Q2.x + "");
        QRow[8].setText(Q2.y + "");
        
        RRow[1].setText(R0.x + "");
        RRow[2].setText(R0.y + "");
        
        RRow[4].setText(R1.x + "");
        RRow[5].setText(R1.y + "");
        
        SRow[1].setText(S0.x + "");
        SRow[2].setText(S0.y + "");
    }
    
    
    /**
     * The main program to launch the cubic subdivision demo.
     * 
     * @param args ignored
     */
    public static void main(String[] args) {
        new CubicSubdivision().frame("Cubic Subdivision");
    }
    
}

