package nomadgui.control;

import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.event.*;
import nomadgui.tools.*;
import nomadgui.nddsJava.*;
import nomadgui.Drive.*;
import NomadGUI;

/** An extension of JPanel that provides for manual control of the robot.
  * The panel has an image of the robot with an arrow and buttons that can be
  * used to specify direction.  There are also two text boxes for entering
  * specific steering radii and driving speeds.
  */

public class ManualPanel extends JPanel {
  private Image nomad;
  private MediaTracker mt;

  private JTextField speedField, radiusField;
  private JLabel speedLabel, radiusLabel;
  private float speed, radius;
  private JSlider speedSlider;
  private JButton goButton, stopButton;
  private JButton L20, L12, L8, L4, L2,
                  R20, R12, R8, R4, R2, Str; // turning buttons
  private Insets buttonMargin;  // the margins for the turning buttons

  /** Default Constructor.  Creates a panel with an image of the robot
    * and two text boxes to specify speed and turning radius.
    */
  
  public ManualPanel() {
    buttonMargin = new Insets(0,0,0,0);
    
    mt = new MediaTracker(this);
    nomad = Toolkit.getDefaultToolkit().getImage("manual.gif");
    mt.addImage(nomad, 3);
    try { mt.waitForID(3); } catch(InterruptedException ie) {}
    
    GridBagLayout gridbag = new GridBagLayout();
    GridBagConstraints c = new GridBagConstraints();

    c.anchor = GridBagConstraints.NORTH;
    
    setLayout(gridbag);

    c.gridx = 0;
    add(Box.createHorizontalStrut(30), c);
    
    c.gridx = 1; c.gridy = 3;
    L4 = createButton("4");
    L4.setHorizontalAlignment(JButton.RIGHT);
    L4.setPreferredSize(new Dimension(30, 30));
    add(L4, c);

    c.gridy = 4;
    L2 = createButton("2");
    L2.setPreferredSize(new Dimension(30, 30));
    add(L2, c);

    c.gridx = 2; c.gridy = 2;
    L8 = createButton("8");
    L8.setPreferredSize(new Dimension(30, 30));
    add(L8, c);

    c.gridx = 3; c.gridy = 1;
    L12 = createButton("12");
    L12.setVerticalAlignment(JButton.BOTTOM);
    L12.setPreferredSize(new Dimension(30, 30));
    add(L12, c);

    c.gridx = 4; c.gridy = 1;
    L20 = createButton("20");
    L20.setVerticalAlignment(JButton.TOP);
    L20.setPreferredSize(new Dimension(30, 30));
    add(L20, c);

    c.gridx = 5; c.gridy = 0;
    c.gridwidth = 1; c.gridheight = 1;
    Str = createButton("straight");
    Str.setVerticalAlignment(JButton.BOTTOM);
    Str.setPreferredSize(new Dimension(70, 30));
    add(Str, c);

    c.gridx = 6; c.gridy = 1;
    R20 = createButton("20");
    R20.setVerticalAlignment(JButton.TOP);
    R20.setPreferredSize(new Dimension(30, 30));
    add(R20, c);

    c.gridx = 7; c.gridy = 1;
    R12 = createButton("12");
    R12.setVerticalAlignment(JButton.BOTTOM);
    R12.setPreferredSize(new Dimension(30, 30));
    add(R12, c);

    c.gridx = 8; c.gridy = 2;
    R8 = createButton("8");
    R8.setPreferredSize(new Dimension(30, 30));
    add(R8, c);

    c.gridx = 9; c.gridy = 3;
    R4 = createButton("4");
    R4.setHorizontalAlignment(JButton.LEFT);
    R4.setPreferredSize(new Dimension(30, 30));
    add(R4, c);

    c.gridy = 4;
    R2 = createButton("2");
    R2.setPreferredSize(new Dimension(30, 30));
    add(R2, c);

    c.gridx = 10; c.gridheight = 6;
    add(Box.createHorizontalStrut(20), c);

    c.gridwidth = 11; c.gridheight = 1;
    c.gridx = 0; c.gridy = 5;
    speedSlider = new JSlider(-50, 50, 0);
    speedSlider.setMajorTickSpacing(10);
    speedSlider.setMinorTickSpacing(1);
    speedSlider.setPaintTicks(true);
    speedSlider.setPaintLabels(true);
    speedSlider.setSnapToTicks(true);
    speedSlider.setBackground(Color.white);
    speedSlider.setPreferredSize(new Dimension(350,50));
    add(speedSlider, c);

    c.gridwidth = 1; c.gridheight = 1;
    c.gridx = 11; c.gridy = 0;
    speedLabel = new JLabel("Speed (cm/s)");
    speedLabel.setVerticalAlignment(JLabel.BOTTOM);
    speedLabel.setPreferredSize(new Dimension(120, 30));
    add(speedLabel, c);

    c.gridy = 1;
    speedField = new JTextField("0");
    speedField.setPreferredSize(new Dimension(120, 30));
    add(speedField, c);

    c.gridy = 2;
    radiusLabel = new JLabel("Radius (m)");
    radiusLabel.setVerticalAlignment(JLabel.BOTTOM);
    radiusLabel.setPreferredSize(new Dimension(120, 30));
    add(radiusLabel, c);

    c.gridy = 3;
    radiusField = new JTextField("0");
    radiusField.setPreferredSize(new Dimension(120, 30));
    add(radiusField, c);

    c.gridy = 4;
    add(Box.createVerticalStrut(20), c);

    c.gridy = 5;
    goButton = new JButton("GO");
    goButton.setBackground(Color.green);
    goButton.setForeground(Color.black);
    goButton.setPreferredSize(new Dimension(100, 30));
    add(goButton, c);

    c.gridy = 6;
    stopButton = new JButton("STOP");
    stopButton.setBackground(Color.red);
    stopButton.setForeground(Color.white);
    stopButton.setPreferredSize(new Dimension(100, 30));
    add(stopButton, c);

    setActions();
  }

  /** Draws the manual panel.  This should not be called directly, but
    * should be called via repaint().
    * @param g The graphics context for this panel.
    */
  
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D) g;
    
    setBackground(Color.white);
    setOpaque(true);
    g2.drawImage(nomad, 125, 100, 200, 100, this);

    AffineTransform at = g2.getTransform();  // get default transform
    
    Point2D.Float base = new Point2D.Float(225f, 190f);

    float angle = 0;

    // weird number stuff to make the arrow line up right (mostly...)
    if (Math.abs(radius)<=12) angle = (float)Math.toRadians(radius*4.5f);
    else if (radius>0) angle = (float)Math.toRadians(radius*2f+28f);
    else angle = (float)Math.toRadians(radius*2f-28f);
    if (angle>=0) angle = (float)Math.PI/2 - angle;
    else angle = -(float)Math.PI/2 - angle;
    if (Math.abs(radius)>35) angle = 0;
	
    g2.transform(AffineTransform.getRotateInstance(angle, base.x, base.y));
    g2.setColor(Color.blue);
    g2.fill(new Rectangle2D.Float(base.x-5, base.y-90, 10, 90));
    GeneralPath arrow = new GeneralPath();   // Set up path
    arrow.moveTo(base.x - 10, base.y - 90);
    arrow.lineTo(base.x, base.y - 110);
    arrow.lineTo(base.x + 10, base.y - 90);
    arrow.lineTo(base.x - 10, base.y - 90);
    arrow.closePath();
    g2.fill(arrow);

    g2.setTransform(at);
  }

  /** Sets the speed to the specified number.  This does not actually command
    * the robot to change speeds, it simply changes the variable and text
    * field associated with the speed.
    * @param s The number to which the speed is changed.
    */
  
  public void setSpeed(float s) {
    speed = s;
    runnableText r = new runnableText(Float.toString(s), speedField);
    SwingUtilities.invokeLater(r);
  }
  
  /** Sets the radius to the specified number.  This does not actually command
    * the robot to change radii, it simply changes the variable and text
    * field associated with the radius.
    * @param r The number to which the radius is changed.
    */
  
  public void setRadius(float r) {
    radius = r;
    runnableText rt = new runnableText(Float.toString(r), radiusField);
    SwingUtilities.invokeLater(rt);
    repaint();   // move arrow
  }
  
  /** Creates a JButton with certain settings.  The Button is created
    * with no border or internal color (BorderPainted and ContentAreaFilled
    * are both false) and the margins are set to 0.  The text of the button
    * is set to the String specified.
    * @param m The string to be used as the button's label.
    * @return The JButton with all formatting applied.
    */
  
  private JButton createButton(String m) {
    JButton temp = new JButton(m);
    temp.setBorderPainted(false);
    temp.setMargin(buttonMargin);
    temp.setContentAreaFilled(false);
    return temp;
  }

  /** Sets the actions for each button and the slider. */
  
  private void setActions() {
    goButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	boolean go = true;
	try {
	  speed = Float.parseFloat(speedField.getText());
	  radius = Float.parseFloat(radiusField.getText());
	} catch(NumberFormatException nfe) {
	  go = false;
	  JOptionPane.showMessageDialog(ManualPanel.this,
 				      "Speed and Radius must be real numbers.",
 				      "Incorrect Number Format",
 				      JOptionPane.INFORMATION_MESSAGE);
	}
	if (go) {  // as long as numbers are acceptable, do this stuff
	  System.out.println("Speed and Radius are set:");
	  System.out.println("  Speed = "+speed);
	  System.out.println("  Radius = "+radius);
	  nddsCmd drive = new nddsCmd();
	  drive.connect(NomadGUI.getIP(), NomadGUI.getPort());
	  move cmd = new move(radius, 0, 0, 0, speed, 0);
	  System.out.println(cmd.translateSpeed);
	  nddsMsg driveMsg = new nddsMsg(cmd, nddsMsg.RT_MOVE_MSG_NAME,
					 nddsMsg.PUBLISH_SUBSCRIBE,
					 null, null);
	  drive.sendCmd(driveMsg);
	  drive.disconnect();
	}
      }
    });

    stopButton.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	setSpeed(0f);
	setRadius(0f);
	
	System.out.println("Speed and Radius are set:");
	System.out.println("  Speed = "+speed);
	System.out.println("  Radius = "+radius);
	Runnable r = new Runnable() {
	    public void run() {
	      speedSlider.setValue((int)speed);
	    }
	  };
	SwingUtilities.invokeLater(r);
	nddsCmd drive = new nddsCmd();
	drive.connect(NomadGUI.getIP(), NomadGUI.getPort());
	move cmd = new move(radius, 0, 0, 0, speed, 0);
	System.out.println(cmd.translateSpeed);
	nddsMsg driveMsg = new nddsMsg(cmd, nddsMsg.RT_MOVE_MSG_NAME,
				       nddsMsg.PUBLISH_SUBSCRIBE,
				       null, null);
	drive.sendCmd(driveMsg);
	drive.disconnect();
      }
    });

    radiusField.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	float temp = radius;
	boolean go = true;
	try {
	  temp = Float.parseFloat(radiusField.getText());
	} catch(NumberFormatException nfe) {
	  go = false;
	  JOptionPane.showMessageDialog(ManualPanel.this,
 				      "Radius must be a real number.",
 				      "Incorrect Number Format",
 				      JOptionPane.INFORMATION_MESSAGE);
	}
	if (go) {  // as long as numbers are acceptable, do this stuff
	  radius = temp;
	  repaint();
	}
      }
    });

    speedField.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
	float temp = speed;
	boolean go = true;
	try {
	  temp = Float.parseFloat(speedField.getText());
	} catch(NumberFormatException nfe) {
	  go = false;
	  JOptionPane.showMessageDialog(ManualPanel.this,
 				      "Speed must be a real number.",
 				      "Incorrect Number Format",
 				      JOptionPane.INFORMATION_MESSAGE);
	}
	if (go) {  // as long as numbers are acceptable, do this stuff
	  speed = temp;
	  Runnable r = new Runnable() {
	    public void run() {
	      speedSlider.setValue((int)speed);
	    }
	  };
	  SwingUtilities.invokeLater(r);
	}
      }
    });

    L20.addActionListener(new numberAction(-20, this));
    L12.addActionListener(new numberAction(-12, this));
    L8.addActionListener(new numberAction(-8, this));
    L4.addActionListener(new numberAction(-4, this));
    L2.addActionListener(new numberAction(-2, this));
    R20.addActionListener(new numberAction(20, this));
    R12.addActionListener(new numberAction(12, this));
    R8.addActionListener(new numberAction(8, this));
    R4.addActionListener(new numberAction(4, this));
    R2.addActionListener(new numberAction(2, this));
    Str.addActionListener(new numberAction(50, this));

    speedSlider.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
	JSlider s = (JSlider)e.getSource();
	ManualPanel.this.setSpeed(s.getValue());
      }
    });
  }
}

/** An extension of ActionListener specifically for the number buttons
  * on the ManualPanel. */

class numberAction implements ActionListener {
  float rad;
  ManualPanel mp;

  /** Default Constructor, should not be called. */
  
  private numberAction() {}

  /** Sets the numberAction's response to the specified number.
    * @param f The number that is set by this numberAction.
    */

  public numberAction(float f, ManualPanel m) {
    super();
    rad = f;
    mp = m;
  }

  /** The action performed by the button.  This sets the text field in the
    * ManualPanel to the specified number.
    * @param e The ActionEvent associated with this action.
    */
  
  public void actionPerformed(ActionEvent e) {
    mp.setRadius(rad);
  }
}
