import simulator.*;
import utils.*;
import scripting.*;
import testprocs.*;

import java.util.*;
import java.awt.*; 
import java.awt.event.*; 
import java.awt.geom.*;
import java.io.*;
import javax.swing.*; 
import javax.swing.event.*;
import javax.swing.text.*; 


public class SimApp extends JApplet {

  /**
     main function: just create the app.
     The first argument is the script for the factory to use
   */
  public static void main (String argv[]) {
    File f = new File(argv[0]);
    JFrame frame = new JFrame("Amorphous Experiment");
    frame.setSize(500, 500);
    SimApp app = new SimApp(f);
    frame.getContentPane().add(app, BorderLayout.CENTER);
    frame.show();
    frame.addWindowListener(new Closer());
  }

  public SimApp(File f) {
    // create simulation
    CommunicationsModel comm = new PerfectCommunicationsModel();
    //ProcessorFactory pf = new DataflowProcFactory();
    ProcessorFactory pf = new ScriptProcFactory(f);
    //Simulator.jiggledGrid(0.0);
    modules.hierarchy.Level.w_m=1.5;
    //Simulator sim = new Simulator(comm,pf,50000,0.008);
    //Simulator sim = new Simulator(comm,pf,30000,0.01);
    //Simulator sim = new Simulator(comm,pf,10000,0.02);
    //Simulator sim = new Simulator(comm,pf,5000,0.03);
    Simulator sim = new Simulator(comm,pf,2000,0.04);
    //Simulator sim = new Simulator(comm,pf,1000,0.04); // low connectivity
    //Simulator sim = new Simulator(comm,pf,1000,0.05);  // normal connectivity
    //Simulator sim = new Simulator(comm,pf,1000,0.07); // med-high connect
    //Simulator sim = new Simulator(comm,pf,1000,0.08); // high connectivity
    //Simulator sim = new Simulator(comm,pf,500,0.065); // low connectivity
    //Simulator sim = new Simulator(comm,pf,500,0.08);
    //Simulator sim = new Simulator(comm,pf,200,0.12);
    //Simulator sim = new Simulator(comm,pf,100,0.15);
    //Simulator sim = new Simulator(comm,pf,20,0.35);
    //Simulator sim = new Simulator(comm,pf,10,0.50);

    // create display
    Box rootBox = Box.createVerticalBox();
    getContentPane().add("Center",rootBox);
    SimViewer viewer = new SimViewer();
    //viewer.setShowLinks(false);
    rootBox.add(viewer);

    // add the mouse information system...
    InfoMouse im = new InfoMouse(sim,viewer);
    Component gp = this.getGlassPane();
    gp.addMouseListener(im);
    gp.addMouseMotionListener(im);
    gp.setVisible(true);

    ButtonWindow bw = new ButtonWindow(sim,viewer);

    // finally, hook up the display to the processors
    ProcessorDisplayManager pdm = new ProcessorDisplayManager(viewer,sim);
    pdm.setColorModel(new OutputColorModel());
    //pdm.setColorModel(new HierarchyColorModel());

    // and... run!
    Thread simthread = new Thread(sim);
    sim.mythread = simthread; // kludge
    simthread.start();
  }
}

class Closer extends WindowAdapter {
  public void windowClosing(WindowEvent e) {System.exit(0);}     
}

class InfoMouse extends MouseInputAdapter {
  Simulator sim;
  SimViewer view;

  InfoMouse(Simulator s,SimViewer v) {
    sim = s;
    view = v;
  }

  static String mode = "msg-mouse";
  static String message = "";

  public void mouseClicked(MouseEvent e) {
    int modifiers = e.getModifiers();
    if((modifiers&InputEvent.SHIFT_MASK)==0) {
      Processor p = getClosestProc(e.getX(),e.getY());
      if(p!=null) { 
        System.out.println(p);
      }
    } else {
      if(mode.equals("msg-mouse")) {
        messageproc(e,false);
      } else if(mode.equals("death-mouse")) {
        suspendzone(e);
      } else if(mode.equals("life-mouse")) {
        revivezone(e);
      } else if(mode.equals("reset-mouse")) {
        resetzone(e);
      } else {
        System.out.println("Unknown mouse command! "+mode);
      }
    }
  }
  public void mouseDragged(MouseEvent e) {
    int modifiers = e.getModifiers();
    if((modifiers&InputEvent.SHIFT_MASK)==0)
      return;
    
    if(mode.equals("msg-mouse")) {
      messageproc(e,true);
    } else if(mode.equals("death-mouse")) {
      suspendzone(e);
    } else if(mode.equals("life-mouse")) {
      revivezone(e);
    } else if(mode.equals("reset-mouse")) {
      resetzone(e);
    } else {
      System.out.println("Unknown mouse command! "+mode);
    }
  }
    //System.out.println("Handling a click.");

  Processor lastproc=null;
  private void messageproc(MouseEvent e, boolean drag) {
    Processor p = getClosestProc(e.getX(),e.getY());
    if(p!=null && (p!=lastproc || !drag)) { 
      lastproc=p;
      System.out.println("Sent message "+message+" to "+
                         p.getUID()+" on port UI");
      Symbol ui = Symbol.GetSymbol("UI");
      Symbol msg = Symbol.GetSymbol(message);
      sim.userSendMessage(p,new Pair(ui,msg));
    }
  }
  private void suspendzone(MouseEvent e) {
    Vector v = getRegionProcs(e.getX(),e.getY());
    Iterator i = v.iterator();
    while(i.hasNext()) { sim.suspendProcessor((Processor)i.next()); }
  }
  private void revivezone(MouseEvent e) {
    Vector v = getRegionProcs(e.getX(),e.getY());
    Iterator i = v.iterator();
    while(i.hasNext()) { sim.reviveProcessor((Processor)i.next()); }
  }
  private void resetzone(MouseEvent e) {
    Vector v = getRegionProcs(e.getX(),e.getY());
    Iterator i = v.iterator();
    while(i.hasNext()) { sim.resetProcessor((Processor)i.next()); }
  }

  private Vector getRegionProcs(double ex, double ey)
  { return (Vector)getRelevantProcs(ex,ey).car(); }
  private Processor getClosestProc(double ex, double ey)
  { return (Processor)getRelevantProcs(ex,ey).cdr(); }

  private Pair getRelevantProcs(double ex, double ey) {
    double halffly = view.getFlySize()/2.0;
    double x = ((double)ex-halffly)/((double)view.getCanvasWidth());
    double y = ((double)ey-halffly)/((double)view.getCanvasHeight());
    // acceptable radius is 10 pixels:
    double xThreshold = 10.0/view.getCanvasWidth();
    double yThreshold = 10.0/view.getCanvasHeight();

    EmbeddedNetwork net = sim.getNet();
    //Processor p = net.nearestNode(x,y);

    Vector inThreshold = new Vector();
    Processor best = null;
    double bestval = Double.NaN;
    Enumeration i = net.getNodes();
    while(i.hasMoreElements()) {
      Processor pd = (Processor)i.nextElement();
      Point2D loc = net.getLocation(pd);
      if(Math.abs(loc.getX() - x) > xThreshold ||
         Math.abs(loc.getY() - y) > yThreshold)
        continue;
      inThreshold.add(pd);
      double score = Math.abs(loc.getX() - x)+Math.abs(loc.getY() - y);
      if(Double.isNaN(bestval) || score < bestval) {
        best = pd; bestval = score;
      }
    }
    return new Pair(inThreshold,best);
  }
}

class ButtonWindow extends JFrame {
  Simulator sim;
  SimViewer view;

  ButtonWindow(Simulator s,SimViewer v) {
    super("Controls");

    sim = s; view = v;
    setSize(200, 360);
    addWindowListener(new Closer());
    makeButtons();
    show();
  }

  void makeButtons() {
    Box rootBox = Box.createVerticalBox();
    getContentPane().add("Center",rootBox);
    
    // add buttons & displays
    JLabel tLbl = new JLabel("Time: ");
    rootBox.add(tLbl);

    // make the button listener
    ButtonsListener buttonListener = new ButtonsListener(sim,view,tLbl);
    sim.addObserver(buttonListener);

    JToggleButton bp = new JToggleButton("Pause");
    bp.addChangeListener(buttonListener);
    bp.setActionCommand("pause");
    bp.setSelected(true);
    rootBox.add(bp);

    JLabel mLbl = new JLabel("Mouse Function: ");
    rootBox.add(tLbl);
    // make radio-button family for mouse
    Box blay = Box.createVerticalBox();
    JRadioButton bmm = new JRadioButton("Send Message");
    bmm.addActionListener(buttonListener);
    bmm.setActionCommand("msg-mouse");
    bmm.setSelected(true);
    blay.add(bmm);
    JTextArea text = new JTextArea(1,20);
    text.addCaretListener(buttonListener);
    //text.setActionCommand("message-text");
    //Dimension size = textField.getPreferredSize();
    //size.setSize(size.getWidth(),bmm.getPreferredSize().getHeight());
    text.setMaximumSize(bmm.getPreferredSize());
    text.setMinimumSize(bmm.getPreferredSize());
    text.setPreferredSize(bmm.getPreferredSize());
    text.setAlignmentX(Component.LEFT_ALIGNMENT);
    blay.add(text);
    rootBox.add(blay);
     JRadioButton bm2 = new JRadioButton("Kill Processor");
    bm2.addActionListener(buttonListener);
    bm2.setActionCommand("death-mouse");
    rootBox.add(bm2);
    JRadioButton bm3 = new JRadioButton("Revive Processor");
    bm3.addActionListener(buttonListener);
    bm3.setActionCommand("life-mouse");
    rootBox.add(bm3);
    JRadioButton bm4 = new JRadioButton("Reset Processor");
    bm4.addActionListener(buttonListener);
    bm4.setActionCommand("reset-mouse");
    rootBox.add(bm4);
    //Group the radio buttons.
    ButtonGroup mgroup = new ButtonGroup();
    mgroup.add(bmm); mgroup.add(bm2); 
    mgroup.add(bm3); mgroup.add(bm4);
  }
}

class ButtonsListener implements ActionListener, ChangeListener, CaretListener,
                                        Observer {
  Simulator sim;
  SimViewer view;
  JLabel time;

  public ButtonsListener(Simulator sim, SimViewer view, JLabel time) {
    this.sim = sim; this.view = view; this.time = time;
  }

  /**
     Responds to button commands.
  */
  public void actionPerformed(ActionEvent e) {
    String s = e.getActionCommand();
    if(s.equals("msg-mouse")) {
      InfoMouse.mode = s;
    } else if(s.equals("death-mouse")) {
      InfoMouse.mode = s;
    } else if(s.equals("life-mouse")) {
      InfoMouse.mode = s;
    } else if(s.equals("reset-mouse")) {
      InfoMouse.mode = s;
    }
  }

  /**
     Responds to the mouse pressing on "Run"
  */
  public void stateChanged(ChangeEvent e) {
    AbstractButton button = (AbstractButton)e.getSource();
    String s = button.getActionCommand();
    if(s.equals("pause")) {
      sim.setPaused(button.getModel().isSelected());
    }
  }

  public void caretUpdate(CaretEvent e) {
    JTextComponent text = (JTextComponent)e.getSource();
    InfoMouse.message = text.getText();
  }

  // sets the labels correctly
  public void update(Observable o, Object arg) {
    time.setText("Time: "+sim.getTime());
  }
}
