// 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;

/**
 * A panel which selects a parallogram-shaped region in which two
 * sides are parallel to the y-axis and the other two are skewed with
 * respect to the x-axis.
 *
 * @author Charles Loomis
 * @author Mark Donszelmann
 * @version $Id: XSkewSelectionPanel.java,v 1.8 2001/03/05 13:42:40 duns Exp $ */
public class XSkewSelectionPanel 
    extends AbstractRegionSelectionPanel {

    /**
     * The initial starting width of the skewed parallogram. */
    final private static int STARTING_WIDTH = 25;

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

    /**
     * Return the number of control points 6---the four corners and
     * the centerpoints of the two sides parallel to the y-axis.
     *
     * @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:
                k = 4;
                break;
            case 1:
            case 2:
                k = 5;
                break;
            case 4:
            case 5: return FreeHepImage.getCursor("0_MoveCursor", 16, 16);
            default: return FreeHepImage.getCursor("XSkewCursor");
        }
        return compassCursor("Resize", xCtrlPts[index] - xCtrlPts[k], 
                                       yCtrlPts[index] - yCtrlPts[k], 4, false);
    }
    
    /**
     * Initialize the control points based in the starting point
     * (x,y). 
     *
     * @param x x-coordinate of starting point
     * @param y y-coordinate of starting point */
    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(xCtrlPts,x);
        yCtrlPts[0] = y-STARTING_WIDTH;
        yCtrlPts[1] = y-STARTING_WIDTH;
        yCtrlPts[2] = y+STARTING_WIDTH;
        yCtrlPts[3] = y+STARTING_WIDTH;
        yCtrlPts[4] = y;
        yCtrlPts[5] = y;
    }

    /**
     * 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;
        switch (activeCtrlPt) {

        case 0:

            width = y-yCtrlPts[4];

            yCtrlPts[0] = yCtrlPts[4]+width;
            yCtrlPts[1] = yCtrlPts[5]+width;

            yCtrlPts[2] = yCtrlPts[5]-width;
            yCtrlPts[3] = yCtrlPts[4]-width;

            break;

        case 1:

            width = y-yCtrlPts[5];

            yCtrlPts[0] = yCtrlPts[4]+width;
            yCtrlPts[1] = yCtrlPts[5]+width;

            yCtrlPts[2] = yCtrlPts[5]-width;
            yCtrlPts[3] = yCtrlPts[4]-width;

            break;

        case 2:

            width = y-yCtrlPts[5];

            yCtrlPts[0] = yCtrlPts[4]-width;
            yCtrlPts[1] = yCtrlPts[5]-width;

            yCtrlPts[2] = yCtrlPts[5]+width;
            yCtrlPts[3] = yCtrlPts[4]+width;

            break;

        case 3:

            width = y-yCtrlPts[4];

            yCtrlPts[0] = yCtrlPts[4]-width;
            yCtrlPts[1] = yCtrlPts[5]-width;

            yCtrlPts[2] = yCtrlPts[5]+width;
            yCtrlPts[3] = yCtrlPts[4]+width;

            break;

        case 4:

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

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

            break;
         
        default:
            break;
        }
        
        repaintPanel();
    }

    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 skewed
     * region. 
     *
     * @return AffineTransform which describes the selected region */
    public AffineTransform makeAffineTransform() {

        // Find first the upper, left-hand point.   This is the one
        // with the smallest x-value and then the smallest y-value.
        int first = 0;
        int xSavedValue = xCtrlPts[first];
        int ySavedValue = yCtrlPts[first];
        for (int i=1; i<4; i++) {
            int xValue = xCtrlPts[i];
            int yValue = yCtrlPts[i];
            if (xValue<xSavedValue) {
                xSavedValue = xValue;
                ySavedValue = yValue;
                first = i;
            } else if (xValue==xSavedValue) {
                if (yValue<ySavedValue) {
                    xSavedValue = xValue;
                    ySavedValue = yValue;
                    first = i;
                }
            }
        }

        // Calculate which is the opposite corner.
        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]);
    }

    /**
     * Check that the selection is valid; regions with a zero area are
     * invalid. 
     *
     * @return flag indicating whether the region is valid */
    public boolean isValidSelection() {
        return (visible) &&
            (xCtrlPts[4]!=xCtrlPts[5]) &&
            (yCtrlPts[0]!=yCtrlPts[3]);
    } 

}
