/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.display;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.event.MouseInputAdapter;

/**
 * InteractivePanel is a drawing panel that invokes the handleMouseAction method in
 * Interactive objects.
 * @author Wolfgang Christian
 * @author Francisco Esquembre
 * @version 1.0
 */
public class InteractivePanel extends DrawingPanel implements InteractiveMouseHandler {
  public static final int MOUSE_PRESSED = 1;
  public static final int MOUSE_RELEASED = 2;
  public static final int MOUSE_DRAGGED = 3;
  public static final int MOUSE_CLICKED = 4;
  public static final int MOUSE_ENTERED = 5;
  public static final int MOUSE_EXITED = 6;
  public static final int MOUSE_MOVED = 7;
  protected boolean containsInteractive = false;
  protected int mouseAction = 0;
  protected MouseEvent mouseEvent = null;
  protected InteractiveMouseHandler interactive = null;
  private Interactive iaDraggable = null; // interactive object that is being dragged
  private Selectable iaSelectable = null; // interactive object that has been selected

  /**
   * Constructs an InteractivePanel with the given handler.
   * @param in InteractiveMouseHandler
   */
  public InteractivePanel(InteractiveMouseHandler in) {
    this();
    interactive = in;
  }

  /**
   * Constructs an InteractivePanel with an internal handler.
   */
  public InteractivePanel() {
    // remove the drawing panel mouse controller
    removeMouseListener(mouseController);
    removeMouseMotionListener(mouseController);
    // create and add a new mouse controller for interactive drawing
    mouseController = new IADMouseController();
    addMouseListener(mouseController);
    addMouseMotionListener(mouseController);
    interactive = this; // this panel is the default handler
  }

  /**
   * Adds a drawable object to the drawable list.
   * @param drawable
   */
  public void addDrawable(Drawable drawable) {
    super.addDrawable(drawable);
    if(drawable instanceof Interactive) {
      containsInteractive = true;
    }
  }

  /**
   * Removes all drawable objects from the drawable list.
   */
  public void clear() {
    super.clear();
    containsInteractive = false;
  }

  /**
   * Sets the x axis scale based on the max and min values of all measurable objects.
   * Autoscale flag is not respected.
   */
  protected void scaleX(ArrayList<Drawable> tempList) {
    double tempmin = xminPreferred;
    double tempmax = xmaxPreferred;
    super.scaleX(tempList);
    // increase but do not decrease if dragging and autoscaling
    if(autoscaleX&&(mouseAction==MOUSE_DRAGGED)) {
      if(xminPreferred>tempmin) {
        xminPreferred = tempmin;
      }
      if(xmaxPreferred<tempmax) {
        xmaxPreferred = tempmax;
      }
    }
  }

  /**
   * Sets the y axis scale based on the max and min values of all measurable objects.
   * Autoscale flag is not respected.
   */
  protected void scaleY(ArrayList<Drawable> tempList) {
    double tempmin = yminPreferred;
    double tempmax = ymaxPreferred;
    super.scaleY(tempList);
    // increase but do not decrease if dragging and autoscaling
    if(autoscaleY&&(mouseAction==MOUSE_DRAGGED)) {
      if(yminPreferred>tempmin) {
        yminPreferred = tempmin;
      }
      if(ymaxPreferred<tempmax) {
        ymaxPreferred = tempmax;
      }
    }
  }

  /**
   * Sets the interactive mouse handler.
   *
   * The interactive mouse handler is notified whenever a mouse action occurs.
   *
   * @param handler the mouse handler
   */
  public void setInteractiveMouseHandler(InteractiveMouseHandler handler) {
    interactive = handler;
  }

  /**
   * Handles mouse actions by dragging the current interactive drawable object.
   *
   * @param panel
   * @param evt
   */
  public void handleMouseAction(InteractivePanel panel, MouseEvent evt) {
    switch(panel.getMouseAction()) {
       case InteractivePanel.MOUSE_CLICKED :
         Interactive clickedIA = getInteractive();
         if((panel.getMouseClickCount()<2)||(clickedIA==null)||!(clickedIA instanceof Selectable)) {
           return;
         }
         if((iaSelectable!=null)&&(iaSelectable!=clickedIA)) {
           iaSelectable.setSelected(false);
         }
         iaSelectable = ((Selectable) clickedIA);
         iaSelectable.toggleSelected();
         invalidateImage();       //validImage = false;
         if(!getIgnoreRepaint()) {
           panel.repaint();
         }
         break;
       case InteractivePanel.MOUSE_DRAGGED :
         if(iaDraggable==null) {
           return;                // nothing to drag
         }
         double x = panel.getMouseX();
         double y = panel.getMouseY();
         if(!autoscaleX&&(evt.getX()<1+leftGutter)) {
           x = panel.pixToX(1+leftGutter);
         }
         if(!autoscaleX&&(evt.getX()>panel.getWidth()-1-rightGutter)) {
           x = panel.pixToX(panel.getWidth()-1-rightGutter);
         }
         if(!autoscaleY&&(evt.getY()<1+topGutter)) {
           y = panel.pixToY(1+topGutter);
         }
         if(!autoscaleY&&(evt.getY()>panel.getHeight()-1-bottomGutter)) {
           y = panel.pixToY(panel.getHeight()-1-bottomGutter);
         }
         iaDraggable.setXY(x, y); // drag the interactive object
         invalidateImage();       //validImage = false;
         if(!getIgnoreRepaint()) {
           panel.repaint();       // repaint to keep the screen up to date
         }
         break;
       case InteractivePanel.MOUSE_RELEASED :
         if((autoscaleX||autoscaleY)&&!getIgnoreRepaint()) {
           panel.repaint();       // repaint to keep the screen up to date
         }
         break;
    }
  }

  /**
   * Get the Interactive object that is currently being dragged.
   * @return Interactive
   */
  public Interactive getCurrentDraggable() {
    return iaDraggable;
  }

  /**
   * Gets the interactive object that was accessed by the last mouse event.
   * @return Interactive
   */
  public Interactive getInteractive() {
    if(!containsInteractive) {
      return null; // don't check unless we have a least one Interactive
    }
    if(iaDraggable!=null) {
      return iaDraggable;
    }
    if((iaSelectable!=null)&&iaSelectable.isSelected()) {
      // check only selected object
      Interactive iad = ((Interactive) iaSelectable).findInteractive(this, mouseEvent.getX(), mouseEvent.getY());
      return iad;
    }
    Object[] array = null;
    synchronized(drawableList) {
      array = drawableList.toArray();
    }
    for(int i = array.length-1; i>=0; i--) {
      Object obj = array[i];
      if(obj instanceof Interactive) {
        Interactive iad = ((Interactive) obj).findInteractive(this, mouseEvent.getX(), mouseEvent.getY());
        if(iad!=null) {
          return iad;
        }
      }
    }
    return null;
  }

  /**
   * Shows the coordinates in the text box in the lower left hand corner.
   *
   * @param show
   */
  public void setShowCoordinates(boolean show) {
    showCoordinates = show;
  }

  /**
   * Gets the mouse button of the last mouse event.
   * @return int
   */
  public int getMouseButton() {
    switch(mouseEvent.getModifiers()) {
       case java.awt.event.InputEvent.BUTTON1_MASK :
         return 1;
       case java.awt.event.InputEvent.BUTTON2_MASK :
         return 2;
       case java.awt.event.InputEvent.BUTTON3_MASK :
         return 3;
       default :
         return 0;
    }
  }

  /**
   * Gets the click count of the last mouse event.
   *
   * @return int
   */
  public int getMouseClickCount() {
    return mouseEvent.getClickCount();
  }

  /**
   * Gets the last mouse action.
   * @return int
   */
  public int getMouseAction() {
    return mouseAction;
  }

  /**
   * Gets the x pixel coordinate of the last mouse event.
   * @return pixel
   */
  public int getMouseIntX() {
    return mouseEvent.getX();
  }

  /**
   * Gets the y pixel coordinate of the last mouse event.
   * @return pixel
   */
  public int getMouseIntY() {
    return mouseEvent.getY();
  }

  /**
   * Gets the x world coordinate of the last mouse event.
   * @return coordiante
   */
  public double getMouseX() {
    return pixToX(mouseEvent.getX());
  }

  /**
   * Gets the y world coordinate of the last moust event
   * @return coordinate
   */
  public double getMouseY() {
    return pixToY(mouseEvent.getY());
  }

  /**
   * Saves the last mouse event.
   * @param type
   * @param evt
   */
  public void saveMouseEvent(int type, java.awt.event.MouseEvent evt) {
    mouseAction = type;
    mouseEvent = evt;
  }

  /**
   * The inner class that will handle all mouse related events.
   */
  protected class IADMouseController extends MouseInputAdapter {
    /**
     * Handle the mouse pressed event.
     * @param e
     */
    public void mousePressed(MouseEvent e) {
      mouseEvent = e;
      mouseAction = MOUSE_PRESSED;
      if(interactive!=null) { // is there an object available to hande the mouse event
        interactive.handleMouseAction(InteractivePanel.this, e);
        iaDraggable = null;   // force the panel to search all drawables
        iaDraggable = getInteractive();
        if(iaDraggable!=null) {
          if(iaDraggable instanceof Selectable) {
            setMouseCursor(((Selectable) iaDraggable).getPreferredCursor());
          } else {
            setMouseCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
          }
        }
      }
      if(showCoordinates) {
        String s = coordinateStrBuilder.getCoordinateString(InteractivePanel.this, e);
        blMessageBox.setText(s);
      }
    }

    /**
     * Handles the mouse released event.
     * @param e
     */
    public void mouseReleased(MouseEvent e) {
      mouseEvent = e;
      mouseAction = MOUSE_RELEASED;
      if(interactive!=null) {
        interactive.handleMouseAction(InteractivePanel.this, e);
      }
      iaDraggable = null;
      if(showCoordinates) {
        blMessageBox.setText(null);
      }
      setMouseCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
    }

    /**
     * Handles the mouse entered event.
     * @param e
     */
    public void mouseEntered(MouseEvent e) {
      if(showCoordinates) {
        setMouseCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
      }
      mouseEvent = e;
      mouseAction = MOUSE_ENTERED;
      if(interactive!=null) {
        interactive.handleMouseAction(InteractivePanel.this, e);
      }
    }

    /**
     * Handles the mouse exited event.
     * @param e
     */
    public void mouseExited(MouseEvent e) {
      setMouseCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      mouseEvent = e;
      mouseAction = MOUSE_EXITED;
      if(interactive!=null) {
        interactive.handleMouseAction(InteractivePanel.this, e);
      }
    }

    /**
     * Handles the mouse clicked event.
     * @param e
     */
    public void mouseClicked(MouseEvent e) {
      mouseEvent = e;
      mouseAction = MOUSE_CLICKED;
      if(interactive==null) {
        return;
      }
      interactive.handleMouseAction(InteractivePanel.this, e);
    }

    /**
     * Handles the mouse dragged event.
     * @param e
     */
    public void mouseDragged(MouseEvent e) {
      mouseEvent = e;
      mouseAction = MOUSE_DRAGGED;
      if(interactive!=null) {
        interactive.handleMouseAction(InteractivePanel.this, e);
      }
      if(showCoordinates) {
        String s = coordinateStrBuilder.getCoordinateString(InteractivePanel.this, e);
        blMessageBox.setText(s);
      }
    }

    /**
     * Handles the mouse moved event.
     * @param e
     */
    public void mouseMoved(MouseEvent e) {
      mouseEvent = e;
      mouseAction = MOUSE_MOVED;
      iaDraggable = null;
      if(interactive!=null) { // check to see if there is an interactive object
        interactive.handleMouseAction(InteractivePanel.this, e);
        Interactive iad = getInteractive();
        if(iad==null) {
          setMouseCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        } else {
          if(iad instanceof Selectable) {
            setMouseCursor(((Selectable) iad).getPreferredCursor());
          } else {
            setMouseCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
          }
        }
      }
    }

  }

}

/*
 * Open Source Physics software is free software; you can redistribute
 * it and/or modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.

 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be released
 * under the GNU GPL license.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
 * or view the license online at http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (c) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
