/* @(#)PolygonsAndCubics.java   26 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 PolygonsAndCubics
    extends DisplayPanel
{
    /**
     * The paintable sequence variable that will represent
     * the polygon or cubic, its point handles, and its
     * labels.
     * 
     * This sequence will be constructed just before it is
     * time to paint.
     * 
     * This sequence will be placed in the tile.
     */
    PaintableSequence sequence = null;
    
    
    /** The array of points in the shape. */
    float[][] points = null;
    
    /** The shape. */
    Shape shape = null;
    
    /** The tangent strategy. */
    Tangent.Strategy strategy = null;
    
    
    /** The graphics tile size. */
    int size = 400;
    
    /** The graphics tile bounds. */
    XRect bounds = new XRect(0, 0, size, size);
    
    /** The graphics tile. */
    Tile tile = new Tile();
    
    // The tile initialization.
    {
        tile.setDefaultBounds2D(bounds);
    }
    
    
    /**
     * The paintable component to hold the tile
     * and manage the mouse actions.
     */
    PaintableComponent tileComponent =
        new PaintableComponent(tile);
    
    // The tile component initialization.
    {
        tileComponent.setBorder(Borders.line(4));
    }
    
    
    /** 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 polygon lines or cubic curve. */
    BasicStroke stroke = new BasicStroke(2);
    
    
    /** The draw color. */
    Color drawColor = Colors.black;
    
    /** The fill color. */
    Color fillColor = Colors.lime;
    
    /** The point and label color. */
    Color pointColor = Colors.red;
    
    
    /** The label font. */
    Font font = null;
    
    /** The label locator strategy. */
    TextAnchor.Locator locator = TextAnchor.CENTER_ASCENTLINE;
    
    
    /** Common table gap. */
    int gap = 4;
    
    /** Maximum number of points in a shape. */
    int maxpoints = 18;
    
    /** When to repeat rows in the point count panel. */
    int repeat = 8;
    
    
    /** True if the mouse position is in vertex. */
    boolean inVertex = false;
    
    /** The vertex index for the mouse point. */
    int vertex = -1;
    
    
    /** The label for polygon. */
    String POLYGON = "Polygon";
    
    /** The label for cubic curve. */
    String CUBIC = "Cubic Curve";
    
    /** The label for the Bezier strategy. */
    String BEZIER = "Bezier Strategy";
    
    /** The label for the chord strategy. */
    String CHORD = "Chord Strategy With Factor: ";
    
    
    /** The action to paint the shape. */
    private SimpleAction paintShape =
        new SimpleAction("Paint Shape") {
            public void perform() { paintShape(); }
    };
    
    
    /** The action to update and paint the shape. */
    private SimpleAction updateShape =
        new SimpleAction("Update Shape") {
            public void perform() { updateShape(); }
    };
    
    
    /** The action to create and paint the shape. */
    private SimpleAction randomShape =
        new SimpleAction("Create Random Shape") {
            public void perform() { randomShape(); }
    };
    
    
    /** 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 radio panel to select a polygon or cubic curve. */
    RadioPanel shapePanel = shapeRadioPanel();
    
    /** The radio panel to select the number of points. */
    RadioPanel countPanel = intRadioPanel(3, maxpoints, repeat);
    
    /** The radio panel to select the closure mode. */
    StringObjectRadioPanel closurePanel = closureRadioPanel();
    
    /** The radio panel to select the winding rule. */
    StringObjectRadioPanel windingPanel = windingRadioPanel();
    
    /** The radio panel to select the tangent strategy. */
    RadioPanel tangentPanel = tangentRadioPanel();
    
    /** The text panel for the chord factor. */
    TextFieldView factorView = new TextFieldView("0.25", 100);
    
    {
        factorView.addActionListener(updateShape);
    }
    
    /** The check box to determine whether to show labels. */
    BooleanView showLabels =
        new BooleanView("Show Point Labels", paintShape, false);
     
    
    /** The stuff for the closure and winding choices. */
    Object[] hstuff1 = { closurePanel, windingPanel };
    
    /** The panel for the closure and winding choices. */
    HTable htable1 = new HTable(hstuff1, 4 * gap, 4 * gap, CENTER);
    
    
    /** The stuff for the tangent strategy and chord factor. */
    Object[] hstuff2 = { tangentPanel, factorView };
    
    /** The panel for the tangent strategy and chord factor. */
    HTable htable2 = new HTable(hstuff2, gap, gap, CENTER);
    
    
    /** The stuff for the main panel. */
    Object[] vstuff = {
        tileComponent,
        randomShape,
        shapePanel,
        countPanel,
        htable1,
        htable2,
        showLabels
    };
    
    /** The main panel. */
    VTable vtable = new VTable(vstuff, gap, gap, CENTER);
    
    
    /** The constructor. */
    public PolygonsAndCubics() {
        add(vtable);
        setLabelFont();
        randomShape();
        addMouseActions();
    }
    
    
    /**
     * The method to paint the shape.
     * 
     * This method creates a new paintable sequence with
     * all the shape information to be displayed and
     * then installs that sequence into the tile.
     */
    void paintShape() {
        sequence = new PaintableSequence();
        
        appendPoints();
        appendLabels();
        appendShape();
        
        tile.setPaintable(sequence);
    }
    
    
    /** The method to append the points to the sequence. */
    void appendPoints() {
        int n = points.length;
        
        for (int i = 0; i < n; i++) {
            PointPaintable pp =
                new PointPaintable(points[i], mark, pointColor);
            
            sequence.appendPaintable(pp);
        }
    }
    
    
    /** The method to append the labels to the sequence. */
    void appendLabels() {
        if (! showLabels())
            return;
        
        int n = points.length;
        
        for (int i = 0; i < n; i++)
            appendLabel("" + i, points[i], 10, pointColor);
    }
    
    
    /**
     * The method to append the shape to the sequence as a
     * shape paintable with fill and edge.
     * 
     */
    void appendShape() {
        ShapePaintable sp =
            new ShapePaintable
                (shape, PaintMode.FILL_DRAW,
                 fillColor, drawColor, stroke);
        
        sequence.appendPaintable(sp);
    }
    
    
    /**
     * The method to update the shape if some aspect of the
     * geometry has changed.
     * 
     * Calls paintShape.
     */
    void updateShape() {
        String type = getShapeType();
        ClosureMode mode = getClosureMode();
        WindingRule rule = getWindingRule();
        
        if (type == POLYGON) {
            shape = new PolygonShape(points, mode, rule);
        }
        else {
             String tangenttype = getTangentType();
             
             if (tangenttype == BEZIER)
                 strategy = Tangent.bezierStrategy();
             else {
                 float f = factorView.demandFloat();
                 
                 strategy = Tangent.chordStrategy(f);
             }
             
             shape = new AutomaticCurve(points, strategy, mode, rule);
        }
        
        paintShape();
    }
    
    
    /**
     * The method to create a new random shape.
     *
     * Calls updateShape.
     */
    void randomShape() {
        setRandomPoints();
        updateShape();
    }
    
    
    /**
     * The method to create the points for a shape
     * with the number of random points
     * specified by the user in the GUI.
     */
    void setRandomPoints() {
        int n = getPointCount();
        
        points = new float[n][2];
        
        for (int row = 0; row < n; row++)
            for (int col = 0; col <= 1; col++)
                points[row][col] =
                    MathUtilities.randomInt(0, size);
    }
    
    
    /** The method to set the font for the labels. */
    void setLabelFont() {
        String name = Fonts.getMonospacedFontFamilyName();
        int fontsize = 20;
        
        font = new Font(name, Font.BOLD, fontsize);
    }
    
    
    /**
     * Append the given label
     * below the given point p
     * by the given amount delta
     * using the given paint.
     */
    void appendLabel
        (String label, float[] p, float delta, Paint paint)
    {
        TextPaintable tp =
            new TextPaintable
                (label, font, paint, locator, p[0], p[1] + delta);
        
        sequence.appendPaintable(tp);
    }
    
    
    /** Add the mouse actions to the tile component. */
    void addMouseActions() {
        MouseActionAdapter adapter =
            tileComponent.getMouseActionAdapter();
        
        adapter.addMousePressedAction(mousePressed);
        adapter.addMouseDraggedAction(mouseDragged);
    }
    
    
    /** Get the user point count from the GUI. */
    int getPointCount() {
        return countPanel.getSelectedIndex() + 3;
    }
    
    
    /** Get the user shape type choice from the GUI. */
    String getShapeType() {
        return shapePanel.getSelectedLabel();
    }
    
    
    /** Get the user closure mode choice from the GUI. */
    ClosureMode getClosureMode() {
        return (ClosureMode) closurePanel.getSelectedObject();
    }
    
    
    /** Get the user winding rule choice from the GUI. */
    WindingRule getWindingRule() {
        return (WindingRule) windingPanel.getSelectedObject();
    }
    
    
    /** Get the user tangent strategy choice from the GUI. */
    String getTangentType() {
        return tangentPanel.getSelectedLabel();
    }
    
    
    /** Get the user show labels choice from the GUI. */
    boolean showLabels() {
        return showLabels.getBooleanValue();
    }
    
    
    /**
     * Create a radio panel with a sequence of integers.
     * 
     * @param min the minimum integer
     * @param max the maximum integer
     * @param repeat how many integers in one row of labels

     */
    RadioPanel intRadioPanel(int min, int max, int repeat) {
        if (min > max) {
            int t = min;
            min = max;
            max = t;
        }
         
        String[] labels = new String[max - min + 1];
        
        for (int i = min; i <= max; i++)
            labels[i - min] = i + "";
        
        TableLayout layout =
            new TableLayout(1, repeat, gap, gap, WEST, VERTICAL);
        
        return new RadioPanel(labels, randomShape, layout);
    }
    
    
    /** Create the radio panel for shape type choice. */
    RadioPanel shapeRadioPanel() {
        String[] labels = { POLYGON, CUBIC };
        
        TableLayout layout = new TableLayout(1, 2, gap, gap);
        
        return new RadioPanel(labels, updateShape, layout);
    }
    
    
    /** Create the radio panel for closure mode choice. */
    StringObjectRadioPanel closureRadioPanel() {
        Object[][] pairs = {
            { "Closed Shape", ClosureMode.CLOSED },
            { "Open Shape",   ClosureMode.OPEN   }
        };
        
        TableLayout layout = new TableLayout(2, 1, gap, gap, WEST);
        
        StringObjectRadioPanel panel
            = new StringObjectRadioPanel
                (pairs, updateShape, 0, layout);
        
        panel.setLayout(layout);
        
        return panel;
    }
    
    
    /** Create the radio panel for winding rule choice. */
    StringObjectRadioPanel windingRadioPanel() {
        Object[][] pairs = {
            { "Wind Even-Odd", WindingRule.WIND_EVEN_ODD },
            { "Wind Non-Zero", WindingRule.WIND_NON_ZERO }
        };
        
        TableLayout layout = new TableLayout(2, 1, gap, gap, WEST);
        
        StringObjectRadioPanel panel
            = new StringObjectRadioPanel
                (pairs, updateShape, 0, layout);
        
        return panel;
    }
    
    
    /** Create the radio panel for tangent strategy choice. */
    RadioPanel tangentRadioPanel() {
        String[] labels = { BEZIER, CHORD };
        
        TableLayout layout = new TableLayout(1, 2, gap, gap);
        
        return new RadioPanel(labels, updateShape, layout);
    }
    
    
    /** The mouse pressed behavior. */
    void mousePressed(MouseEvent evt) {
        vertex = -1;
        
        float x = (float) evt.getX();
        float y = (float) evt.getY();
        
        float[] mouse = { x, y };
        
        int n = points.length;
        
        for (int i = 0; i < n; i++) {
            if (Metric.MAX.isNear(points[i], mouse, epsilon)) {
                vertex = i;
                break;
            }
        }
        
        inVertex = (vertex >= 0);
    }
    
    
    /** The mouse dragged behavior. */
    void mouseDragged(MouseEvent evt) {
        if (inVertex) {
            points[vertex][0] = evt.getX();
            points[vertex][1] = evt.getY();
            
            updateShape();
        }
    }
    
    
    /** The main program to launch this panel in a frame. */
    public static void main(String[] args) {
        new PolygonsAndCubics().frame("Polygons And Cubics");
    }
}

