/// Evolver Copyright 2000, Morgan McGuire
/// matrix@alum.mit.edu   http://graphics3d.com/matrix
import javax.swing.*;
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.io.*;
import java.awt.event.*;
import java.awt.*;
import java.io.IOException;
import java.awt.Insets;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;

/**
 * A small display of a behavior used in the Evolver panel.
 */
public class BehaviorDisplay extends JToggleButton 
  implements DragSourceListener, DragGestureListener, DropTargetListener
{
  /**
   * Behavior being referenced.  Use setBehavior to change this.
   */
  Behavior behavior = null;

  DragSource dragSource = null;
  DropTarget dropTarget = null;

  /**
   * Behavior viewer or other GUI this sits inside of. 
   */
  BehaviorDisplayListener listener = null;

  private Thread animation;

  /**
   * Environment values are seen in.
   */
  private ELEnvironment environment = new ELEnvironment();
  
  // Used for inter-thread communication by stopAnimation();
  boolean killThread = false;

  /**
   * Whether a new expression should be animated or not.
   */
  private boolean animate = false;

  private double displayScale = 1.0;

  public BehaviorDisplay(Behavior b)
  {
    this(b, 1.0);
  }

  public BehaviorDisplay(Behavior b, double displayScale)
  {
    super();

    this.displayScale = displayScale;
    
    setBehavior(b);
    setMargin(new Insets(3,3,3,3));
    setBackground(new java.awt.Color(255,255,255));

    // Add an event handler so that when the mouse passes over, the
    // expression is displayed in the big viewer.
    addMouseListener(new MouseAdapter() {
        public void mouseEntered(MouseEvent e) {
          if (Evolver.instance == null) {
	      return;
          }
          String s = behavior.expression.toString();
          s = s + "\n" + behavior.expression.numNestedSubexpressions() + " terms";
          if (behavior.expression.hasExternalInfluences()) {
            s = s + " (animated)";
          }
          
          Evolver.instance.textDisplay.setText(s);
        }
      });


    dropTarget = new DropTarget(this, this);

    dragSource = new DragSource();
    dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this);
  }

  /**
   * Starts animation of this behavior.  Animation will continue if
   * the behavior is changed.  
   */
  public synchronized void startAnimation() 
  {
    animate = true;
    // Start a thread if there isn't one running already and this needs to be animated.
    if (behavior.expression.hasExternalInfluences() && ((animation == null) || !animation.isAlive())) {
      killThread = false;
      animation = new Thread() {
          public void run() {
            // Trigger a redraw of the display
            BehaviorDisplay bd = BehaviorDisplay.this;
            while(!bd.killThread) {
              // Clock the environment
              bd.environment.clock(System.currentTimeMillis() / 1000.0, bd.behavior.currentValue);

              bd.behavior.dirty = true;
              bd.behavior.evaluate(bd.environment);
              // Clean the matrix up on our thread so that cleaning is
              // not out of sync.
              bd.behavior.currentValue.forceClean();
              bd.repaint();
              Thread.yield();
            }
          }
        };
      
      animation.setPriority(Thread.MIN_PRIORITY);
      
      animation.start();
    }
  }

  public synchronized void stopAnimation()
  {
    animate = false;
    killAnimation();
  }

  synchronized private void killAnimation()
  {
    if (animation == null) {
      return;
    }

    if (animation.isAlive()) {
      killThread = true;
      try {
        animation.join();
        animation = null;
      } catch (InterruptedException e) {
        System.err.println("Unexpected " + e);
      }
    }
  }

  /**
   * a drag gesture has been initiated
   * 
   */
  // inherited from DragGestureListener
  public void dragGestureRecognized(DragGestureEvent event) {
    String s = behavior.expression.unparse();
    StringSelection text = new StringSelection(s);
    dragSource.startDrag(event, DragSource.DefaultMoveDrop, text, this);
  }

  public void setBehavior(Behavior behavior)
    {
	setBehavior(behavior, displayScale);
    }

  public void setBehavior(Behavior behavior, double displayScale)
  {
    if ((behavior != this.behavior) || (displayScale != this.displayScale)) {
      this.behavior = behavior;
      this.displayScale = displayScale;

      if (displayScale == 1.0) {
        setIcon(behavior.currentValue);
      } else {
        setIcon(new ScaleIcon(displayScale, behavior.currentValue));
      }

      validate();
      repaint();
      if (listener != null) {
        listener.behaviorDisplayChange(this);
      }

      changed();
    }
  }

  /**
   * this message goes to DragSourceListener, informing it that the dragging 
   * has ended
   */
  // inherited from DragSourceListener
  public void dragDropEnd(DragSourceDropEvent event) {   
    // Toggle buttons end up pressed when dragged; disarm & unpress
    getModel().setArmed(false);
    getModel().setPressed(false);

    if (event.getDropSuccess()){
      //System.out.println("Successful drag-drop");
    }
  }

  /**
   * this message goes to DragSourceListener, informing it that the dragging 
   * has entered the DropSite
   */
  // inherited from DragSourceListener
  public void dragEnter(DragSourceDragEvent event) {
    //  System.out.println("Source: dragEnter");
  }
  
  /**
   * is invoked if the use modifies the current drop gesture
   */
  // inherited from DragSourceListener
  public void dropActionChanged(DragSourceDragEvent event) {
    System.out.println("dropActionChanged"); 
  }

  /**
   * this message goes to DragSourceListener, informing it that the dragging 
   * has exited the DropSite
   */
  // inherited from DragSourceListener
  public void dragExit(DragSourceEvent event) {
    // System.out.println("Source: dragExit"); 
  }

  /**
   * this message goes to DragSourceListener, informing it that the dragging is currently 
   * ocurring over the DropSite
   */
  // inherited from DragSourceListener
  public void dragOver(DragSourceDragEvent event) {
    // System.out.println("Source: dragOver");
  }


  /**
   * is invoked when you are dragging over the DropSite
   */
  // inherited from DropTargetListener
  public void dragEnter(DropTargetDragEvent event) {
    // System.out.println("Target: dragEnter");
    event.acceptDrag(DnDConstants.ACTION_MOVE);
  }

  /**
   * is invoked when you are exit the DropSite without dropping
   */
  // inherited from DropTargetListener
  public void dragExit(DropTargetEvent event) {
    // System.out.println("Target: dragExit");    
  }

  /**
   * is invoked when a drag operation is going on
   * 
   */
  // inherited from DropTargetListener
  public void dragOver (DropTargetDragEvent event) {
    //      System.out.println("Target: dragOver");
  }

  /**
   * a drop has occurred
   */
  // inherited from DropTargetListener
  public void drop(DropTargetDropEvent event) {
    try {
      Transferable transferable = event.getTransferable();
                   
      // we accept only Strings      
      if (transferable.isDataFlavorSupported (DataFlavor.stringFlavor)){
        
        String s = (String)transferable.getTransferData(DataFlavor.stringFlavor);
        
        // Create an expression
        ELExpression expr = Evolver.instance.geneticParameters.parseELExpression(s);
        if (expr == null) {
          System.err.println("Can't parse expression '" + s + "'");
          event.rejectDrop();
          return;
        }

        event.acceptDrop(DnDConstants.ACTION_MOVE);
        setExpression(expr);

        event.getDropTargetContext().dropComplete(true);
      } else {
        event.rejectDrop();
      }

    } catch (IOException exception) {
      exception.printStackTrace();
      System.err.println("Exception" + exception.getMessage());
      event.rejectDrop();

    } catch (UnsupportedFlavorException ufException ) {
      ufException.printStackTrace();
      System.err.println("Exception" + ufException.getMessage());
      event.rejectDrop();
    }
  
  }

  public void setExpression(ELExpression expr) 
  {
      killAnimation();

      environment.setPreviousFrame(null);
      behavior.setExpression(expr, environment);
      environment.setPreviousFrame(behavior.currentValue);
      if (listener != null) {
	  listener.behaviorDisplayChange(this);
      }
      repaint();
      
      changed();
      if (animate) {
	  startAnimation();
      }
  }

  private void changed()
  {
    if (animate) {

      if (! behavior.expression.hasExternalInfluences()) {
        killAnimation();
      } else {
        startAnimation();
      }
    }
  }

  public void setBehaviorDisplayListener(BehaviorDisplayListener l)
  {
    listener = l;
  }

  /**
   * Invoked if the use modifies the current drop gesture.
   */
  public void dropActionChanged(DropTargetDragEvent event) {
  }

}

