
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

import byucc.jhdl.base.Browser;
import byucc.jhdl.base.BV;
import byucc.jhdl.base.HWSystem;
import byucc.jhdl.base.SimulatorCallback;
import byucc.jhdl.base.Wire;
import byucc.jhdl.base.WireValueException;
import byucc.jhdl.util.cli.CLInterpreter;
import byucc.jhdl.util.cli.CLICommand;
import byucc.jhdl.util.cli.CLIException;
import byucc.jhdl.util.cli.CLIInvalidUsageException;

/** This is a special class used to demonstrate the addition of new
 * viewer elements in JabLite.  It displays a custom JComponent that
 * visually shows the angles from which cosine values are computed.
 * @author Anthony L. Slade */
class CustomCosineView extends JDialog
  implements SimulatorCallback, Browser, CLICommand {

  static final String COMMAND_COSINE_VIEW = "cosview";

  public CustomCosineView( CLInterpreter interpreter,
			   HWSystem hws ) {
    setTitle("Custom Cosine View");
    this.interp = interpreter;
    this.hws = hws;
    hws.addSimulatorCallback(this);
    interpreter.registerCommand(COMMAND_COSINE_VIEW,this);
    circleView = new SemiCircleView();
    JScrollPane scroll = new JScrollPane(circleView);

    Container contentPane = getContentPane();
    contentPane.setLayout(new BorderLayout());

    JPanel topPanel = new JPanel();
    topPanel.setLayout(new BorderLayout());
    JButton cycleButton = new JButton("Cycle");
    cycleButton.addActionListener( new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  interp.parseCommand("cycle");
	}
      });
    topPanel.add(cycleButton,BorderLayout.WEST);
    JButton resetButton = new JButton("Reset");
    resetButton.addActionListener( new ActionListener() {
	public void actionPerformed(ActionEvent e) {
	  interp.parseCommand("reset");
	}
      });
    topPanel.add(resetButton,BorderLayout.EAST);
    cycleLabel = new JLabel("Cycle: ");
    topPanel.add(cycleLabel,BorderLayout.SOUTH);

    contentPane.add(topPanel,BorderLayout.NORTH);
    contentPane.add(scroll,BorderLayout.CENTER);
  }

  public void dispose() {
    hws.removeSimulatorCallback(this);
    super.dispose();
  }

  /*-------------------------
    SIMULATORCALLBACK SECTION
    -------------------------*/
  public void simulatorRefresh(int cycle, int phase) {
    cycleLabel.setText("Cycle: "+cycle);
    circleView.revalidate();
    circleView.repaint();
  }
  public void simulatorUpdate(int cycle, int phase) {
  }
  public void simulatorReset() {
    simulatorRefresh(0,0);
  }


  /*-------------------
    CLI COMMAND SECTION
    -------------------*/
  public Object execute( CLInterpreter interp, String argv[] )
    throws CLIException {
    if ( !argv[0].equals(COMMAND_COSINE_VIEW) )
      return null;
    if ( 1 == argv.length ) {
      interp.println("Watching the cosines on the following wires:");
      for ( Iterator itr = circleView.iterator(); itr.hasNext(); ) {
	Wire wire = (Wire)itr.next();
	interp.println("  * "+wire);
      }
      pack();
      show();
      return null;
    }
    if ( 2 < argv.length ) throw new CLIInvalidUsageException();
    Wire wire;
    try {
      wire = (Wire)hws.findNamed(argv[1]);
    } catch ( ClassCastException cce ) {
      throw new CLIException(argv[1]+" is not a wire.");
    }
    if ( null == wire )
      throw new CLIException("Cannot find wire named "+argv[1]);
    if ( 32 != wire.getWidth() )
      throw new CLIException("Sorry, can only view cosine information of 32-bit wires");
    circleView.addWire(wire);
    interp.println("CosineView added the wire "+wire);
    repaint();

    return null;
  }
  public String getHelpText(String cmdName) {
    return "Allows you to watch a wire's progress around a unit circle.";
  }
  public String getHelpType(String cmdName) {
    return "Cosine";
  }
  public String getUsageText(String cmdName) {
    return "[wire_name]";
  }

  /*------------
    MISC METHODS
    ------------*/
  static String getStringValue(Wire wire, Browser browser) {
    BV value;
    try {
      value = wire.getBV(browser);
    } catch (WireValueException wve) {
      return "?UNKNOWN VALUE?";
    }
    return Cosine.formatIntBV(value);
  }
  static double getDoubleValue(Wire wire, Browser browser) {
    BV value;
    try {
      value = wire.getBV(browser);
    } catch (WireValueException wve) {
      return 0;
    }
    return Cosine.getDoubleValue(value);
  }

  private HWSystem hws;
  private SemiCircleView circleView;
  private CLInterpreter interp;
  private JLabel cycleLabel;
} // end class CustomCosineView


/** Class that draws a semicircle to demonstrate the angles that
 * create given cosine values
 * @author Anthony L. Slade */
class SemiCircleView extends JComponent implements Browser {

  static final int MIN_DIMENSION = 350;
  static final Color[] COLORS = {
    Color.blue,
    Color.darkGray,
    Color.green.darker().darker(),
    Color.magenta,
    Color.orange,
    Color.pink,
    Color.red,
    Color.cyan,
    Color.yellow,
    Color.gray,
  };

  SemiCircleView() {
    wires = new ArrayList();
    revalidate();
    font = new Font( "Helvetica", Font.PLAIN, 15 );
    setBackground(Color.white);
  }

  Iterator iterator() {
    return wires.iterator();
  }

  void addWire(Wire wire) {
    if ( wires.contains( wire ) )
      return;
    wires.add(wire);
    revalidate();
    repaint();
  }

  public void revalidate() {
    int height = MIN_DIMENSION + 2*textSpace*wires.size();
    int width = MIN_DIMENSION;
    Dimension size = new Dimension(width,height);
    setMinimumSize(size);
    setPreferredSize(size);
    setSize(size);
    super.revalidate();
  }

  public void paintComponent(Graphics g) {
    g.clearRect(0,0,
		getWidth(),
		getHeight());
    int diameter = MIN_DIMENSION-10;
    g.drawArc(5,5,
	      diameter,diameter,
	      0,180);
    
    g.setFont( font );
    int circleCenter = 5+diameter/2;
    int circleRadius = diameter/2;

    // Get font sizes
    FontMetrics FM = g.getFontMetrics();
    int textHeight = FM.getHeight();
    int textLeft = 5;
    int textPad = 3;
    textSpace = textHeight + textPad;
    int textBase = circleCenter+textSpace;

    for ( int wi = 0; wi < wires.size(); ++wi ) {
      g.setColor(COLORS[wi%COLORS.length]);
      Wire wire = (Wire)wires.get(wi);
      String wireName = wire.toString();
      String wireValue = CustomCosineView.getStringValue(wire,this);
      g.drawString( wireName, textLeft, textBase );
      textBase += textSpace;
      g.drawString( wireValue, textLeft+10, textBase );
      textBase += textSpace;

      double cosine = CustomCosineView.getDoubleValue(wire,this);
      double theta = Math.acos(cosine);
      double sine = Math.sin(theta);
      g.drawLine(circleCenter,circleCenter,
		 circleCenter+(int)(cosine*circleRadius),
		 circleCenter-(int)(sine*circleRadius));
		 
		 
    }
  }
  private ArrayList wires;
  private Font font;
  private int textSpace;
} // end class SemiCircleView
