// Charles A. Loomis, Jr., and University of California, Santa Cruz,
// Copyright (c) 2000
package org.freehep.swing.graphics;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.util.Arrays;

import org.freehep.swing.images.FreeHepImage;

/**
 * Selects a screen region shaped like a parallelogram. 
 *
 * @author Charles Loomis
 * @author Mark Donszelmann
 * @version $Id: ParallelogramSelectionPanel.java,v 1.1.1.1 2004/05/31 05:08:49 kuromaru Exp $ */
public class ParallelogramSelectionPanel 
    extends AbstractRegionSelectionPanel {

    /**
     * The initial starting width (in pixels) of the first and last
     * sides. */
    final private static int STARTING_WIDTH = 25;

    /**
     * Creates a ParallelogramSelectionPanel. */
    public ParallelogramSelectionPanel() {
        super();
    }

    /**
     * The number of control points is 6 for the parallelogram.  The
     * four corners and two at the centerpoints of the first and last
     * sides. 
     *
     * @return 6 the number of control points */
    public int getNumberOfControlPoints() {
        return 6;
    }

    public Cursor getControlPointCursor(int index) {
        int k;
        switch(index) {
            case 0:
            case 3:
            case 5:
                k = 4;
                break;
            case 1:
            case 2:
            case 4:
                k = 5;
                break;    
            default: 
                return FreeHepImage.getCursor("ParallelogramCursor");
        }
        return compassCursor("Rotation", xCtrlPts[index] - xCtrlPts[k], 
                                         yCtrlPts[index] - yCtrlPts[k], 8, true);
    }
    
    /**
     * Initialize the control points for this selection given the
     * initial starting point (x,y).
     *
     * @param x the initial x-coordinate
     * @param y the initial y-coordinate */
    public void initializeControlPoints(int x, int y) {

        // Set the fifth control point to be the active one and
        // initialize all of the coordinates. 
        activeCtrlPt = 5;
        Arrays.fill(yCtrlPts,y);
        xCtrlPts[0] = x-STARTING_WIDTH;
        xCtrlPts[1] = x-STARTING_WIDTH;
        xCtrlPts[2] = x+STARTING_WIDTH;
        xCtrlPts[3] = x+STARTING_WIDTH;
        xCtrlPts[4] = x;
        xCtrlPts[5] = x;
    }

    /**
     * Move the active control point to the point (x,y).  
     *
     * @param x x-coordinate of the new point 
     * @param y y-coordinate of the new point */    
    public void updateActiveControlPoint(int x, int y) {

        // Bring the location within bounds.
        x = forceXCoordinateWithinBounds(x);
        y = forceYCoordinateWithinBounds(y);

        // Change what is done depending on which control point is
        // active. 
        int width;
        int dx;
        int dy;
        switch (activeCtrlPt) {

        case 4:

            // Determine the delta-x and delta-y.
            dx = xCtrlPts[0] - xCtrlPts[4];
            dy = yCtrlPts[0] - yCtrlPts[4];
            
            // Update the active control point.
            xCtrlPts[activeCtrlPt] = x;
            yCtrlPts[activeCtrlPt] = y;
            
            // Update the control points on either side.
            xCtrlPts[0] = x+dx;
            yCtrlPts[0] = y+dy;
            xCtrlPts[3] = x-dx;
            yCtrlPts[3] = y-dy;

            break;
        
        case 5:
            
            // Determine the delta-x and delta-y.
            dx = xCtrlPts[1] - xCtrlPts[5];
            dy = yCtrlPts[1] - yCtrlPts[5];
            
            // Update the active control point.
            xCtrlPts[activeCtrlPt] = x;
            yCtrlPts[activeCtrlPt] = y;
            
            // Update the control points on either side.
            xCtrlPts[1] = x+dx;
            yCtrlPts[1] = y+dy;
            xCtrlPts[2] = x-dx;
            yCtrlPts[2] = y-dy;

            break;
         
        case 0:
        case 3:

            // Update the active control point.
            xCtrlPts[activeCtrlPt] = x;
            yCtrlPts[activeCtrlPt] = y;
            
            // Determine the delta-x and delta-y.
            dx = xCtrlPts[activeCtrlPt] - xCtrlPts[4];
            dy = yCtrlPts[activeCtrlPt] - yCtrlPts[4];

            if (activeCtrlPt==3) {
                dx = -dx;
                dy = -dy;
            }
            
            xCtrlPts[1] = xCtrlPts[5]+dx;
            yCtrlPts[1] = yCtrlPts[5]+dy;
            xCtrlPts[2] = xCtrlPts[5]-dx;
            yCtrlPts[2] = yCtrlPts[5]-dy;

            xCtrlPts[0] = xCtrlPts[4]+dx;
            yCtrlPts[0] = yCtrlPts[4]+dy;
            xCtrlPts[3] = xCtrlPts[4]-dx;
            yCtrlPts[3] = yCtrlPts[4]-dy;

            break;

        case 1:
        case 2:

            // Update the active control point.
            xCtrlPts[activeCtrlPt] = x;
            yCtrlPts[activeCtrlPt] = y;
            
            // Determine the delta-x and delta-y.
            dx = xCtrlPts[activeCtrlPt] - xCtrlPts[5];
            dy = yCtrlPts[activeCtrlPt] - yCtrlPts[5];
            
            if (activeCtrlPt==2) {
                dx = -dx;
                dy = -dy;
            }
            
            xCtrlPts[1] = xCtrlPts[5]+dx;
            yCtrlPts[1] = yCtrlPts[5]+dy;
            xCtrlPts[2] = xCtrlPts[5]-dx;
            yCtrlPts[2] = yCtrlPts[5]-dy;

            xCtrlPts[0] = xCtrlPts[4]+dx;
            yCtrlPts[0] = yCtrlPts[4]+dy;
            xCtrlPts[3] = xCtrlPts[4]-dx;
            yCtrlPts[3] = yCtrlPts[4]-dy;

            break;

        default:
            break;
        }
        
        repaintPanel();
    }

    /**
     * Repaint this component. 
     *
     * @param g Graphics context in which to draw */    
    public void paintComponent(Graphics g) {
        
        // Allow parent to draw any custom painting.
        super.paintComponent(g);
        
        // If the selection region is visible, paint it.
        if (visible) {

            // Make a 2D graphics context.
            Graphics2D g2d = (Graphics2D) g;
            
            // Draw a rectangle on top the the image.
            g2d.setStroke(thickStroke);
            g.setColor(Color.black);
            g.drawPolygon(xCtrlPts, yCtrlPts, 4);

            if (visibleGuides) {
                g.drawLine(xCtrlPts[4], yCtrlPts[4], xCtrlPts[5], yCtrlPts[5]);
            }

            g2d.setStroke(thinStroke);
            g.setColor(Color.white);
            g.drawPolygon(xCtrlPts, yCtrlPts, 4);

            if (visibleGuides) {
                g.drawLine(xCtrlPts[4], yCtrlPts[4], xCtrlPts[5], yCtrlPts[5]);
            }

            if (activeCtrlPt >= 0) {
                // Draw the active control point.
                g.setColor(Color.black);
                g.fillRect(xCtrlPts[activeCtrlPt]-ctrlPtSize-1, 
                           yCtrlPts[activeCtrlPt]-ctrlPtSize-1,
                           2*ctrlPtSize+3, 2*ctrlPtSize+3);
                g.setColor(Color.white);
                g.fillRect(xCtrlPts[activeCtrlPt]-ctrlPtSize, 
                           yCtrlPts[activeCtrlPt]-ctrlPtSize,
                           2*ctrlPtSize+1, 2*ctrlPtSize+1);
            }
        }
    }    

    /**
     * Make the affine transform which corresponds to this
     * paralleogram-shaped selection.
     *
     * @return AffineTransform which describes the selected region */
    public AffineTransform makeAffineTransform() {

        // Find first the upper, left-hand point.
        int first = 0;
        int savedValue = xCtrlPts[0]*xCtrlPts[0]+yCtrlPts[0]*yCtrlPts[0];
        for (int i=1; i<4; i++) {
            int value = xCtrlPts[i]*xCtrlPts[i]+yCtrlPts[i]*yCtrlPts[i];
            if (value<savedValue) {
                savedValue = value;
                first = i;
            }
        }

        // Calculate the opposite index.
        int third = (first+2)%4;

        // Now use the cross-product to determine which of the
        // remaining points is the one which keep the path going
        // clockwise. 
        int second = (first+1)%4;
        int dx0 = xCtrlPts[third]-xCtrlPts[first];
        int dy0 = yCtrlPts[third]-yCtrlPts[first];
        int dx1 = xCtrlPts[second]-xCtrlPts[first];
        int dy1 = yCtrlPts[second]-yCtrlPts[first];
        if (dx0*dy1-dy0*dx1>0) second = (first+3)%4;

        // Now call the utility function of the parent.
        return makeTransform((double) xCtrlPts[first], 
                             (double) yCtrlPts[first],
                             (double) xCtrlPts[second], 
                             (double) yCtrlPts[second],
                             (double) xCtrlPts[third], 
                             (double) yCtrlPts[third]);
    }

    /**
     * Returns a boolean indicating whether or not the selected region
     * is valid.  It is valid only if the region has a non-zero area. 
     * 
     * @return flag indicating whether the region is valid */
    public boolean isValidSelection() {
        return (makeAffineTransform()!=null);
    } 

}
