/*
 * DataPointSelectorMouseModule.java
 *
 * Created on November 3, 2005, 2:53 PM
 *
 *
 */
package org.das2.event;

import java.util.logging.Level;
import org.das2.dataset.DataSetConsumer;
import org.das2.datum.Datum;
import org.das2.datum.DatumRange;
import org.das2.datum.DatumRangeUtil;
import org.das2.graph.DasAxis;
import org.das2.graph.DasCanvasComponent;
import org.das2.graph.DasPlot;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.HashMap;

/**
 * General purpose mouse module for getting data point selections.  The client
 * provides the DragRenderer, generally a vertical line, horizontal line or a
 * crosshair.
 *
 * Three properties control when BoxSelectionEvents are to be fired:
 * <ul>
 * <li> dragEvents,     as the mouse is dragged,
 * <li> keyEvents,      when a key is pressed.  (The key is the "keyChar" plane of the event)
 * <li> releaseEvents,  when the mouse is released.  (false by default)
 * </ul>
 * This is intended to be used as a base class for other slicers which need a
 * range to be selected in X, Y, or both.
 * @see BoxRenderer
 * @author Jeremy
 */
public class BoxSelectorMouseModule extends MouseModule {

    DasAxis xaxis, yaxis;
    DataSetConsumer dataSetConsumer;
    javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();
    
    /**
     * lastMouseEvent is used for ...  I don't know because it was never documented, but I can see that getBoxSelectionEvent
     * must be called for this to be set.
     */
    MouseDragEvent lastMouseEvent;
    
    /** when true, box selections are remembered, and tweaks to corners are allowed. */
    boolean tweakable = false;
    BoxSelectionEvent lastSelectionEvent = null;

    /**
     * create a BoxSelectorMouseModule
     * @param parent the plot component
     * @param label the label for this mouseModule.
     * @return new  BoxSelectorMouseModule
     */
    public static BoxSelectorMouseModule create( DasPlot parent, String label ) {
        return new BoxSelectorMouseModule( parent, parent.getXAxis(), parent.getYAxis(), null, new BoxRenderer(parent,true), label );
    }
    
    /**
     * create a new BoxSelectorMouseModule
     * @param parent the parent component
     * @param xAxis
     * @param yAxis
     * @param consumer used by some subclasses, such as CutoffMouseModule.
     * @param dragRenderer the drag renderer to use, typically a BoxRenderer but needn't be.
     * @param label the label for this mouseModule.
     */
    public BoxSelectorMouseModule(DasCanvasComponent parent, DasAxis xAxis, DasAxis yAxis,
            DataSetConsumer consumer,
            DragRenderer dragRenderer, String label) {
        super(parent, dragRenderer, label);
        this.xaxis = xAxis;
        this.yaxis = yAxis;
        this.dataSetConsumer = consumer;
    }

    /**
     * allow the last selection to be tweaked.  It's the client's responsibility
     * to draw the current selection.
     * @param b
     */
    public void setTweakable(boolean b) {
        this.tweakable = b;
    }

    private Datum[] checkTweak(Point p) {
        double nx = DatumRangeUtil.normalize(lastSelectionEvent.getXRange(), xaxis.invTransform(p.getX()));
        double ny = DatumRangeUtil.normalize(lastSelectionEvent.getYRange(), yaxis.invTransform(p.getY()));

        logger.log(Level.FINE, "{0} {1}", new Object[]{nx, ny});
        Datum otherx = null;
        Datum othery = null;

        if (nx >= 0.0 && nx < 0.1) {
            otherx = lastSelectionEvent.getXRange().max();
        } else if (nx > 0.9 && nx < 1.0) {
            otherx = lastSelectionEvent.getXRange().min();
        }

        if (ny >= 0.0 && ny < 0.1) {
            othery = lastSelectionEvent.getYRange().max();
        } else if (ny > 0.9 && ny < 1.0) {
            othery = lastSelectionEvent.getYRange().min();
        }

        Datum[] otherCorner = new Datum[2];

        otherCorner[0] = otherx;
        otherCorner[1] = othery;

        return otherCorner;

    }

    @Override
    public void mousePressed(MouseEvent e) {
        
        if (tweakable && lastSelectionEvent != null) {
            Point p = new Point(e.getPoint());
            p.translate(e.getComponent().getX(), e.getComponent().getY());
            
            Datum[] otherCorner= checkTweak( p );
            if (otherCorner[0] != null && otherCorner[1] != null) { // we're tweaking
                double p1x = xaxis.transform(otherCorner[0]);
                double p2x = yaxis.transform(otherCorner[1]);
                Point p1 = new Point((int) p1x, (int) p2x);
                ((BoxRenderer) dragRenderer).setDragStart(p1);
            } else {
                ((BoxRenderer) dragRenderer).setDragStart(null);
            }
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        Cursor c= null;
        if (tweakable && lastSelectionEvent != null) {
            Point p = new Point(e.getPoint());
            p.translate(e.getComponent().getX(), e.getComponent().getY());
            
            Datum[] otherCorner= checkTweak( p );
            if (otherCorner[0] != null && otherCorner[1] != null) { // we're tweaking
                c= new Cursor( Cursor.MOVE_CURSOR );
            }
        }        
        parent.getCanvas().setCursor(c);
    }

    private BoxSelectionEvent getBoxSelectionEvent(MouseDragEvent mde) {

        MouseBoxEvent e = (MouseBoxEvent) mde;
        if ( e==null ) {
            throw new NullPointerException("MouseDragEvent is null, the DragRenderer should have created it.");
        }

        DatumRange xrange = null;
        DatumRange yrange = null;

        Datum x=null, y=null;
        Datum sx= null, sy=null;
        
        if (xaxis != null) {
            Datum min = xaxis.invTransform(e.getXMinimum());
            Datum max = xaxis.invTransform(e.getXMaximum());
            if (min.gt(max)) {
                Datum t = min;
                min = max;
                max = t;
            }
            xrange = new DatumRange(min, max);
            x= xaxis.invTransform(e.getPoint().x);
            sx= xaxis.invTransform(e.getPressPoint().x);
        }

        if (yaxis != null) {
            Datum min = yaxis.invTransform(e.getYMinimum());
            Datum max = yaxis.invTransform(e.getYMaximum());
            if (min.gt(max)) {
                Datum t = min;
                min = max;
                max = t;
            }
            yrange = new DatumRange(min, max);
            y= yaxis.invTransform( e.getPoint().y );
            sy= yaxis.invTransform( e.getPressPoint().y );
        }

        BoxSelectionEvent evt = new BoxSelectionEvent(this, xrange, yrange);
        evt.setStart( sx,sy );
        evt.setFinish( x,y );
        
        
        this.lastSelectionEvent = evt;
        return evt;
    }

    @Override
    public void mouseRangeSelected(MouseDragEvent e) {
        lastMouseEvent = e;
        if (keyEvents) {
            parent.requestFocus();
        }
        if (dragEvents) {
            fireBoxSelectionListenerBoxSelected(getBoxSelectionEvent(e));
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {

        if (lastMouseEvent != null) {
            BoxSelectionEvent dpse = getBoxSelectionEvent(lastMouseEvent);
            HashMap planes = new HashMap();
            planes.put("keyChar", String.valueOf(e.getKeyChar()));
            BoxSelectionEvent dpse2 = new BoxSelectionEvent(this, dpse.getXRange(), dpse.getYRange(), planes);
            dpse2.setStart( dpse.getStartX(), dpse.getStartY() );
            dpse2.setFinish( dpse.getFinishX(), dpse.getFinishY() );
            fireBoxSelectionListenerBoxSelected(dpse2);
        }
    }

    /** Registers BoxSelectionListener to receive events.
     * @param listener The listener to register.
     */
    public void addBoxSelectionListener(org.das2.event.BoxSelectionListener listener) {
        listenerList.add(org.das2.event.BoxSelectionListener.class, listener);
    }

    /** Removes BoxSelectionListener from the list of listeners.
     * @param listener The listener to remove.
     */
    public void removeBoxSelectionListener(org.das2.event.BoxSelectionListener listener) {
        listenerList.remove(org.das2.event.BoxSelectionListener.class, listener);
    }

    /** Notifies all registered listeners about the event.
     *
     * @param event The event to be fired
     */
    protected void fireBoxSelectionListenerBoxSelected(BoxSelectionEvent event) {
        Object[] listeners;
        listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == org.das2.event.BoxSelectionListener.class) {
                ((org.das2.event.BoxSelectionListener) listeners[i + 1]).boxSelected(event);
            }
        }
    }
    
    /**
     * Holds value of property dragEvents.
     */
    private boolean dragEvents = false;

    /**
     * Getter for property dragEvents.
     * @return Value of property dragEvents.
     */
    public boolean isDragEvents() {
        return this.dragEvents;
    }

    /**
     * Setter for property dragEvents.
     * @param dragEvents New value of property dragEvents.
     */
    public void setDragEvents(boolean dragEvents) {
        this.dragEvents = dragEvents;
    }
    
    /**
     * Holds value of property keyEvents.
     */
    private boolean keyEvents = false;

    /**
     * Getter for property keyEvents.
     * @return Value of property keyEvents.
     */
    public boolean isKeyEvents() {
        return this.keyEvents;
    }

    /**
     * Setter for property keyEvents.
     * @param keyEvents New value of property keyEvents.
     */
    public void setKeyEvents(boolean keyEvents) {
        this.keyEvents = keyEvents;
    }

    @Override
    public void mouseReleased(java.awt.event.MouseEvent e) {
        super.mouseReleased(e);
        if (releaseEvents) {
            if ( lastMouseEvent!=null ) {
                fireBoxSelectionListenerBoxSelected(getBoxSelectionEvent(lastMouseEvent));
            } else {
                logger.fine("no mouse event to fire");
            }
        }
    }
    /**
     * Holds value of property releaseEvents.
     */
    private boolean releaseEvents = true;

    /**
     * Getter for property releaseEvents.
     * @return Value of property releaseEvents.
     */
    public boolean isReleaseEvents() {
        return this.releaseEvents;
    }

    /**
     * Setter for property releaseEvents.
     * @param releaseEvents New value of property releaseEvents.
     */
    public void setReleaseEvents(boolean releaseEvents) {
        this.releaseEvents = releaseEvents;
    }
}
