package lcm.util;

import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;

public class ParameterGUI
{
    HashMap<String, PValue> parammap = new HashMap<String, PValue>();
    JPanel  panel = new JPanel(new GridBagLayout());
    int     row = 0;
    GridBagConstraints gA, gB, gC, gD, gBC, gCD, gBCD, gABCD;
    ArrayList<ParameterListener> listeners = new ArrayList<ParameterListener>();
    boolean showvars;

    /** This makes a text field yellow until they've pressed return;
	it's a reminder that you need to hit return for the changes to
	your text field to apply. 
    **/
    static void setupJTextField(JTextField jtf)
    {
	JTFListener l = new JTFListener(jtf);
	jtf.addActionListener(l);
	jtf.addCaretListener(l);
	jtf.addKeyListener(l);
    }

    static class JTFListener extends KeyAdapter implements ActionListener, CaretListener 
    {
	JTextField jtf;

	JTFListener(JTextField jtf)
	{
	    this.jtf=jtf;
	}

	public void keyPressed(KeyEvent e)
	{
	    jtf.setBackground(Color.yellow);
	}

	public void actionPerformed(ActionEvent e)
	{
	    jtf.setBackground(Color.white);
	}

	public void caretUpdate(CaretEvent e)
	{
	}
    }

    /** Parent interface of all value elements **/
    abstract class PValue
    {
	String name;  // name of element, e.g., "logthresh"
	String desc;  // description, e.g., "Logarithmic threshold"

	PValue(String name, String desc)
	{
	    this.name = name;
	    this.desc = desc;
	}

	abstract void setEnabled(boolean enabled);
    }

    /** parse the integer in string s, or if it fails, return 'default' **/
    static final int parseInteger(String s, int val)
    {
	try {
	    return Integer.parseInt(s);
	} catch (Exception ex) {
	    return val;
	}
    }

    /** parse the integer in string s, or if it fails, return 'default' **/
    static final double parseDouble(String s, double val)
    {
	try {
	    return Double.parseDouble(s);
	} catch (Exception ex) {
	    return val;
	}
    }
    
    class ActionNotifier implements ActionListener
    {
	String name;

	public ActionNotifier(String name)
	{
	    this.name = name;
	}

	public void actionPerformed(ActionEvent e)
	{
	    notifyListeners(name);
	}
    }

    class BooleanValue extends PValue
    {
	JCheckBox  jcb;
	boolean value;

	BooleanValue(String name, String desc, boolean value)
	{
	    super(name, desc);

	    this.value = value;
	}

	JCheckBox getCheckBox()
	{
	    if (jcb == null)
		{
		    jcb = new JCheckBox(desc, value);
		    jcb.addActionListener(new ActionListener() {
			    public void actionPerformed(ActionEvent e) {
				setBooleanValue(jcb.isSelected());
			    }});

		}
	    return jcb;
	}

	boolean getBooleanValue()
	{
	    return value;
	}

	void setBooleanValue(boolean v)
	{
	    if (v==value)
		return;

	    value = v;
	    notifyListeners(name);
	}

	void setEnabled(boolean v)
	{
	    jcb.setEnabled(v);
	}
    }

    class IntegerValue extends PValue
    {
	JSlider    slider;
	JTextField textField;
	JLabel     label;
	int        min, max;
	int        value;

	IntegerValue(String name, String desc, int min, int max, int value)
	{
	    super(name, desc);

	    this.min = min;
	    this.max = max;
	    this.value = value;
	}

	JSlider getSlider()
	{
	    if (slider == null)
		{
		    slider = new JSlider(min, max, value);
		    slider.addChangeListener(new ChangeListener() {
			    public void stateChanged(ChangeEvent e) {
				setIntegerValue(slider.getValue());
			    }});
		}
	    return slider;
	}

	JLabel getLabel()
	{
	    if (label == null)
		label = new JLabel(""+value);
	    return label;
	}

	JTextField getTextField()
	{
	    if (textField == null) {
		textField = new JTextField(""+value);
		setupJTextField(textField);
		textField.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    setIntegerValue(parseInteger(textField.getText(), value));
			}});
		    
	    }
	    return textField;
	}

	int getIntegerValue()
	{
	    return value;
	}

	void setIntegerValue(int v)
	{
	    if (v<min)
		v=min;
	    if (v>max)
		v=max;
	    if (v == value)
		return;

	    value = v;
	    if (slider != null)
		slider.setValue(v);
	    if (textField != null)
		textField.setText(""+v);
	    if (label != null)
		label.setText(""+v);

	    notifyListeners(name);
	}

	void setMinMax(int min, int max)
	{
	    this.min = min;
	    this.max = max;
	    if (slider != null)
		{
		    slider.setMinimum(min);
		    slider.setMaximum(max);
		}
	}

	void setEnabled(boolean v)
	{
	    if (slider!=null)
		slider.setEnabled(v);
	    if (textField!=null)
		textField.setEnabled(v);
	}
    }

    class DoubleValue extends PValue
    {
	JSlider    slider;
	JTextField textField;
	JLabel     label;
	double     min, max;
	double     value;
	int        ivalue; // scaled values used for slider
	static final int SLIDER_CLICKS = 100000;
	double     stepsize;
	String     svalue; // string representation of value.

	DoubleValue(String name, String desc, double min, double max, double value)
	{
	    super(name, desc);

	    this.min = min;
	    this.max = max;
	    this.value = value;

	    updateSliderValue();
	}

	void updateSliderValue()
	{
	    ivalue = (int) (SLIDER_CLICKS*(value-min)/(max-min));
	    if (slider != null)
		slider.setValue(ivalue);
	    stepsize = (max-min)/SLIDER_CLICKS;
	}

	JSlider getSlider()
	{
	    if (slider == null)
		{
		    slider = new JSlider(0, SLIDER_CLICKS, ivalue);
		    slider.addChangeListener(new ChangeListener() {
			    public void stateChanged(ChangeEvent e) {
				setDoubleValue(((double)slider.getValue()+.5)/SLIDER_CLICKS*(max-min)+min);
			    }});
		}
	    return slider;
	}

	JLabel getLabel()
	{
	    if (label == null)
		label = new JLabel(""+value);
	    return label;
	}

	JTextField getTextField()
	{
	    if (textField == null) {
		textField = new JTextField(""+value);
		setupJTextField(textField);
		textField.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    setDoubleValue(parseDouble(textField.getText(), value));
			}});
	    }

	    return textField;
	}

	double getDoubleValue()
	{
	    return value;
	}

	void setDoubleValue(double v)
	{
	    if (v<min)
		v=min;
	    if (v>max)
		v=max;
	    if (v == value)
		return;

	    value = v;
	    updateSliderValue();

	    {
		int digits = (int) (-Math.log(stepsize)/Math.log(10))+1;
		if (digits<0)
		    digits=0;
		
		svalue=String.format("%."+digits+"f",value);
	    }

	    if (textField != null)
		{
		    textField.setText(svalue);
		}
	    if (label != null)
		{
		    label.setText(svalue);
		}

	    notifyListeners(name);
	}

	void setMinMax(double min, double max)
	{
	    this.min = min;
	    this.max = max;
	    updateSliderValue();
	}

	void setEnabled(boolean v)
	{
	    if (slider!=null)
		slider.setEnabled(v);
	    if (textField!=null)
		textField.setEnabled(v);
	}
    }

    class StringValue extends PValue
    {
	JTextField textField;
	JComboBox  comboBox;

	String     value;
	String     values[];
	int        idx; // if values is set, idx is such that value=values[idx].

	public StringValue(String name, String desc, String values[], String value)
	{
	    super(name, desc);
	    this.value = value;
	    this.values = values;
	    updateIndex();
	}

 	JTextField getTextField()
	{
	    if (textField == null) {
		textField = new JTextField(value);
		setupJTextField(textField);
		textField.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    String v = textField.getText();
			    setStringValue(v);
			}});
	    }
	    return textField;
	}

	JComboBox getComboBox()
	{
	    assert(values != null);

	    if (comboBox == null) {
		comboBox = new JComboBox(values);
		comboBox.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    setStringValue(values[comboBox.getSelectedIndex()]);
			}});
		comboBox.setSelectedIndex(idx);
	    }

	    return comboBox;
	}

	String getStringValue()
	{
	    return value;
	}

	void updateIndex()
	{
	    if (values!=null)
		{
		    idx = -1;

		    for (int i = 0; i < values.length; i++)
			{
			    if (value.equals(values[i]))
				idx = i;
			}

		    if (idx == -1)
			System.out.println("Warning: illegal string value specified: "+value);
		}
	}

	void setStringValue(String v)
	{
	    if (v.equals(value))
		return;

	    value = v;
	    updateIndex();

	    if (textField != null)
		textField.setText(v);
	    if (comboBox != null)
		comboBox.setSelectedIndex(idx);

	    notifyListeners(name);
	}

 	void setEnabled(boolean v)
	{
	    if (textField!=null)
		textField.setEnabled(v);
	}
  }

    /////////////////////////////////////////////////////////////////////////////
    public ParameterGUI()
    {
	this(true);
    }

    // Our grid:
    //
    //    0                1                   2       3
    //  AAAAA  BBBBBBBBBBBBBBBBBBBBBBBBBBBB  CCCCCC  DDDDDD
    //    gA               gB                  gC      gD
    // 
    //  gCD spans columns C and D.
    //  gBCD spans columsn B, C, and D. (etc)

    public ParameterGUI(boolean showvars)
    {
	this.showvars = showvars;

	gA = new GridBagConstraints();
	gA.gridx = 0;
	gA.weightx = 0.05;
	gA.fill=GridBagConstraints.HORIZONTAL;

	gB = new GridBagConstraints();
	gB.gridx = 1;
	gB.weightx = 1;
	gB.fill=GridBagConstraints.HORIZONTAL;

	gC = new GridBagConstraints();
	gC.gridx = 2;
	gC.weightx = .1;
	gC.fill=GridBagConstraints.HORIZONTAL;

	gD = new GridBagConstraints();
	gD.gridx = 3;
	gD.weightx = .05;
	//	gD.fill=GridBagConstraints.HORIZONTAL;
	gD.anchor = GridBagConstraints.CENTER;

	gBC = new GridBagConstraints();
	gBC.gridx = 1;
	gBC.gridwidth = 2;
	gBC.weightx = gB.weightx + gC.weightx;
	gBC.fill=GridBagConstraints.HORIZONTAL;

	gCD = new GridBagConstraints();
	gCD.gridx = 2;
	gCD.gridwidth = 2;
	gCD.weightx = gC.weightx + gD.weightx;
	gCD.fill=GridBagConstraints.HORIZONTAL;
	gCD.anchor = GridBagConstraints.EAST;

	gBCD = new GridBagConstraints();
	gBCD.gridx = 1;
	gBCD.gridwidth = 3;
	gBCD.weightx = gB.weightx + gC.weightx + gD.weightx;
	gBCD.fill=GridBagConstraints.HORIZONTAL;
	gBCD.anchor = GridBagConstraints.EAST;

	gABCD = new GridBagConstraints();
	gABCD.gridx = 0;
	gABCD.gridwidth = 4;
	gABCD.weightx = gA.weightx + gB.weightx + gC.weightx + gD.weightx;
	gABCD.fill=GridBagConstraints.HORIZONTAL;
    }

    protected void notifyListeners(String name)
    {
	for (ParameterListener pl : listeners)
	    pl.parameterChanged(name);
    }

    public void addListener(ParameterListener l)
    {
	listeners.add(l);
    }
    
    public void removeListeners()
    {
	listeners.clear();
    }

    public void addInt(String name, String desc, int value)
    {
	addInt(name, desc, -Integer.MAX_VALUE, Integer.MAX_VALUE, value);
    }

    public void addInt(String name, String desc, int min, int max, int value)
    {
	IntegerValue val = new IntegerValue(name, desc, -Integer.MAX_VALUE, Integer.MAX_VALUE, value);
	parammap.put(name, val);

	gA.gridy = row;
	gBCD.gridy = row;
	panel.add(new JLabel(desc), gA);
	panel.add(val.getTextField(), gBCD);
	row++;
    }

    public void addIntSlider(String name, String desc, int min, int max, int value)
    {
	IntegerValue val = new IntegerValue(name, desc, min, max, value);
	parammap.put(name, val);

	gA.gridy = row;
	gBC.gridy = row;
	gD.gridy = row;

	panel.add(new JLabel(desc), gA);
	panel.add(val.getSlider(), gBC);
	panel.add(val.getLabel(), gD);
	row++;
    }

    public void addDouble(String name, String desc, double value)
    {
	addDouble(name, desc, -Double.MAX_VALUE, Double.MAX_VALUE, value);
     }

    public void addDouble(String name, String desc, double min, double max, double value)
    {
	DoubleValue val = new DoubleValue(name, desc, min, max, value);
	parammap.put(name, val);

 	gA.gridy = row;
	gBCD.gridy = row;
	panel.add(new JLabel(desc), gA);
	panel.add(val.getTextField(), gBCD);
	row++;
    }
    
    public void addDoubleSlider(String name, String desc, double min, double max, double value)
    {
	DoubleValue val = new DoubleValue(name, desc, min, max, value);
	parammap.put(name, val);

 	gA.gridy = row;
	gBC.gridy = row;
	gD.gridy = row;

	panel.add(new JLabel(desc), gA);
	panel.add(val.getSlider(), gBC);
	panel.add(val.getLabel(), gD);
	row++;
    }

    public void addString(String name, String desc, String value)
    {
	StringValue val = new StringValue(name, desc, null, value);
	parammap.put(name, val);

	gA.gridy = row;
	gBCD.gridy = row;

	panel.add(new JLabel(desc), gA);
	panel.add(val.getTextField(), gBCD);
	row++;
    }

    public void addChoice(String name, String desc, String values[], int value)
    {
	StringValue val = new StringValue(name, desc, values, values[value]);
	parammap.put(name, val);

	gA.gridy = row;
	gBCD.gridy = row;

	panel.add(new JLabel(desc), gA);
	panel.add(val.getComboBox(), gBCD);
	row++;
    }

    public void addBoolean(String name, String desc, boolean value)
    {
	BooleanValue val = new BooleanValue(name, desc, value);
	parammap.put(name, val);

	gA.gridy = row;
	gBCD.gridy = row;
	panel.add(new JLabel(desc), gA);
	panel.add(val.getCheckBox(), gBCD);
	row++;
    }

    public void addButton(String name, String desc)
    {
	addButtons(name, desc);
    }
    
    public void addButtons(Object... args)
    {
	JPanel p = new JPanel();
	p.setLayout(new GridLayout(1, args.length/2));
	
	for (int i = 0; i < args.length/2; i++)
	    {
		String name = (String) args[i*2];
		String desc = (String) args[i*2+1];
		
		JButton button = new JButton(desc);
		button.addActionListener(new ActionNotifier(name));
		
		p.add(button);
	    }
	
	gABCD.gridy = row;
	panel.add(p, gABCD);
	row++;
    }

    public void addCheckBoxes(Object... args)
    {
	JPanel p = new JPanel();
	p.setLayout(new GridLayout(1, args.length/3));

	for (int i = 0; i < args.length/3; i++)
	    {
		String name = (String) args[i*3+0];
		String desc = (String) args[i*3+1];
		boolean value = (Boolean) args[i*3+2];

		BooleanValue bv = new BooleanValue(name, desc, value);
		parammap.put(name, bv);

		p.add(bv.getCheckBox());
	    }

	gABCD.gridy = row;
	panel.add(p, gABCD);
	row++;
    }

    public double gd(String name)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof DoubleValue);

	return ((DoubleValue) p).getDoubleValue();
    }

    public void sd(String name, double v)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof DoubleValue);

	((DoubleValue) p).setDoubleValue(v);
    }

    public int gi(String name)
    {
	PValue p = parammap.get(name);
	assert(p!=null);

	if (p instanceof StringValue)
	    return ((StringValue) p).idx;

	assert(p instanceof IntegerValue);

	return ((IntegerValue) p).getIntegerValue();
    }

    public void si(String name, int v)
    {
	PValue p = parammap.get(name);
	assert(p!=null);

	if (p instanceof StringValue)
	    {
		StringValue sv = (StringValue) p;
		assert (v < sv.values.length);
		sv.setStringValue(sv.values[v]);
	    }
	else if (p instanceof IntegerValue)
	    {
		((IntegerValue) p).setIntegerValue(v);
	    }
	else
	    {
		assert(false);
	    }

    }

    public void setMinMax(String name, int min, int max)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof IntegerValue);

	((IntegerValue) p).setMinMax(min, max);
    }

    public void setMinMax(String name, double min, double max)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof DoubleValue);

	((DoubleValue) p).setMinMax(min, max);
    }

    public String gs(String name)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof StringValue);

	return ((StringValue) p).getStringValue();
    }

    public void ss(String name, String v)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof StringValue);

	((StringValue) p).setStringValue(v);
    }

    public boolean gb(String name)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof BooleanValue);

	return ((BooleanValue) p).getBooleanValue();
    }

    public void sb(String name, boolean v)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	assert(p instanceof BooleanValue);

	((BooleanValue) p).setBooleanValue(v);
    }

    public void setEnabled(String name, boolean e)
    {
	PValue p = parammap.get(name);
	assert(p!=null);
	p.setEnabled(e);
    }

    public Container getPanel()
    {
	return panel;
    }

    public static void main(String[] args)
    {
	JFrame f = new JFrame("ParameterGUI Test");
	f.setLayout(new BorderLayout());
	f.setSize(400,400);
	
	ParameterGUI pg = new ParameterGUI();

	pg.addDouble("Double Value", "A double value", 3.1415926);
	pg.addInt("IntValue1", "An integer value", 45);
	pg.addIntSlider("IntValue2", "An integer value", 100, 1000, 999);
	pg.addDoubleSlider("DoubleSlider", "A sliding double", -1, 1, 0.5);
	pg.addDoubleSlider("DoubleSlider2", "Another sliding double",2500000, 3000000, 2500000);
	
	pg.addString("StringValue1", "A string value", "Hi");
	pg.addString("StringValue2", "A string value", "world");

	pg.addChoice("Combo", "A combo box", new String[] {"Choice 1", "Choice 2", "Choice 3"}, 1);
	
	pg.addBoolean("BoolVal1","A boolean value", true);
	pg.addBoolean("BoolVal2","A boolean value", false);

	pg.addButton("Button", "button one");
	pg.addButtons("Buttons", "button one",
		      "button2", "button two",
		      "button3", "button three");

	pg.addCheckBoxes("name1", "Checkbox 1", true,
			 "name2", "Checkbox 2", false);

	pg.addListener(new ParameterListener() {
		public void parameterChanged(String name)
		{
		    System.out.println("Changed "+name);
		}
	    });
	f.add(pg.getPanel(), BorderLayout.CENTER);
	f.setVisible(true);
    }
}
