/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: PromptAt.java
 * Display a prompt dialog over a specific piece of circuitry.
 * Written by Steven M. Rubin, Sun Microsystems.
 *
 * Copyright (c) 2005 Sun Microsystems and Static Free Software
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Electric(tm); see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, Mass 02111-1307, USA.
 */
package com.sun.electric.tool.user.dialogs;

import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.variable.EditWindow_;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/**
 * This class places an inquiry dialog at specific places on the display.
 */
public class PromptAt extends EDialog
{
	private boolean yesNo;
	private String value;
	private boolean goodClicked;
	private String customButtonClicked;
	private boolean closed;
	private Field [] fieldList;
	private Field [][] fieldArray;

	/**
	 * Class to define a single entry in the custom prompt dialog.
	 */
	public static class Field
	{
		private String label;
		private Object initial;
		private Object finalValue;
		private int type;
		private JTextField textField;
		private JComboBox combo;
		private JButton but;
		private JPanel patch;
		private JLabel labelObj;

		private static final int FIELD_MESSAGE   = 1;
		private static final int FIELD_BOOL      = 2;
		private static final int FIELD_STRING    = 3;
		private static final int FIELD_SELECT    = 4;
		private static final int FIELD_COLOR     = 5;
		private static final int FIELD_BUTTON    = 6;

		/**
		 * Constructor for a field in a prompt dialog that displays a message.
		 * @param label the question to ask.
		 */
		public Field(String label)
		{
			this.label = label;
			this.initial = null;
			this.finalValue = null;
			this.type = FIELD_MESSAGE;
		}

		/**
		 * Constructor for a field in a prompt dialog that chooses between Yes and No.
		 * @param label the question to ask.
		 * @param initial the default response.
		 */
		public Field(String label, boolean initial)
		{
			this.label = label;
			this.initial = Boolean.valueOf(initial);
			this.finalValue = this.initial;
			this.type = FIELD_BOOL;
		}

		/**
		 * Constructor for a field in a prompt dialog that edits a string.
		 * @param label the label of the string.
		 * @param initial the initial string value.
		 */
		public Field(String label, String initial)
		{
			this.label = label;
			this.initial = initial;
			this.finalValue = this.initial;
			this.type = FIELD_STRING;
		}

		/**
		 * Constructor for a field in a prompt dialog that selects among different choices.
		 * @param label the label of the choice.
		 * @param choices the array of choices.
		 * @param initial the default choice.
		 */
		public Field(String label, String [] choices, String initial)
		{
			this.label = label;
			this.initial = choices;
			this.finalValue = initial;
			this.type = FIELD_SELECT;
		}

		/**
		 * Constructor for a field in a prompt dialog that edits a color value.
		 * @param label the label of the color.
		 * @param initial the initial Color value.
		 */
		public Field(String label, Color initial)
		{
			this.label = label;
			this.initial = initial;
			this.finalValue = initial;
			this.type = FIELD_COLOR;
		}

		/**
		 * Constructor for a field in a prompt dialog that places a button.
		 * @param id the returned value of the dialog if the button is pressed.
		 * @param but the button.
		 */
		public Field(String id, JButton but)
		{
			this.label = null;
			this.initial = but;
			this.finalValue = id;
			this.type = FIELD_BUTTON;
		}

		/**
		 * Method to return the final value for a field, after the dialog has completed.
		 * @return the final value (dependent on the type of field).
		 */
		public Object getFinal() { return finalValue; }
	}

	/**
	 * Method to invoke a "yes/no" dialog centered at a point in the circuit.
	 * @param wnd the window displaying the circuit.
	 * @param ni the NodeInst about which to display the dialog.
	 * @param title the dialog title.
	 * @param label the message inside of the dialog, before the text area.
	 * @param initial the default button (true for yes, false for no).
	 * @return the returned value.
	 */
	public static boolean showPromptAt(EditWindow_ wnd, NodeInst ni, String title, String label, boolean initial)
	{
		Field [] fields = new Field[1];
		fields[0] = new PromptAt.Field(label);

		PromptAt dialog = new PromptAt(true);
		dialog.initComponents(wnd, ni, title, fields, null);
		dialog.setVisible(true);
		if (dialog.closed) return initial;
		return dialog.goodClicked;
	}

	/**
	 * Method to invoke a popup dialog centered at a point in the circuit.
	 * @param wnd the window displaying the circuit.
	 * @param ni the NodeInst about which to display the dialog.
	 * @param title the dialog title.
	 * @param label the message inside of the dialog, before the choices.
	 * @param initial the default choice.
	 * @param choices an array of strings to present as choices.
	 * @return the returned choice (null if cancelled).
	 */
	public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, String label, String initial, String [] choices)
	{
		Field [] fields = new Field[1];
		fields[0] = new PromptAt.Field(label, choices, initial);

		PromptAt dialog = new PromptAt(false);
		dialog.initComponents(wnd, ni, title, fields, null);
		dialog.setVisible(true);
		if (!dialog.goodClicked) return null;
		return (String)fields[0].finalValue;
	}

	/**
	 * Method to invoke an input dialog centered at a point in the circuit.
	 * @param wnd the window displaying the circuit.
	 * @param ni the NodeInst about which to display the dialog.
	 * @param title the dialog title.
	 * @param label the message inside of the dialog, before the text area.
	 * @param initial the initial value of the text area.
	 * @return the returned value (null if cancelled).
	 */
	public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, String label, String initial)
	{
		Field [] fields = new Field[1];
		fields[0] = new PromptAt.Field(label, initial);
		
		PromptAt dialog = new PromptAt(false);
		dialog.initComponents(wnd, ni, title, fields, null);
		dialog.setVisible(true);
		if (!dialog.goodClicked) return null;
		return (String)fields[0].finalValue;
	}

	/**
	 * Method to invoke a custom dialog centered at a point in the circuit.
	 * @param wnd the window displaying the circuit.
	 * @param ni the NodeInst about which to display the dialog.
	 * @param title the dialog title.
	 * @param fields an array of Field objects that describe each field in the dialog.
	 * @return null if cancelled, non-null if OK (the results are stored in the Field objects).
	 */
	public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, Field [] fields)
	{
		PromptAt dialog = new PromptAt(false);
		dialog.initComponents(wnd, ni, title, fields, null);
		dialog.setVisible(true);
		if (!dialog.goodClicked) return null;
		return dialog.value;
	}

	/**
	 * Method to invoke a custom dialog centered at a point in the circuit.
	 * @param wnd the window displaying the circuit.
	 * @param ni the NodeInst about which to display the dialog.
	 * @param title the dialog title.
	 * @param fields an array of Field objects that describe each field in the dialog.
	 * @return null if cancelled, non-null if OK (the results are stored in the Field objects).
	 */
	public static String showPromptAt(EditWindow_ wnd, NodeInst ni, String title, Field [][] fields)
	{
		PromptAt dialog = new PromptAt(false);
		dialog.initComponents(wnd, ni, title, null, fields);
		dialog.setVisible(true);
		if (!dialog.goodClicked) return null;
		return dialog.value;
	}

	/** Creates new form PromptAt */
	public PromptAt(boolean yesNo)
	{
		super(null, true);
		this.yesNo = yesNo;
		this.closed = false;
	}

	protected void escapePressed() { closed = true;   exit(false); }

	/**
	 * Call this method when the user closes the dialog.
	 * @param goodButton true if it is an "OK" completion, false if a "Cancel" completion.
	 */
	private void exit(boolean goodButton)
	{
		goodClicked = goodButton;
		if (goodClicked)
		{
			if (!yesNo)
			{
				if (fieldList != null)
				{
					for(int i=0; i<fieldList.length; i++)
					{
						finishField(fieldList[i]);
					}
				} else if (fieldArray != null)
				{
					for(int i=0; i<fieldArray.length; i++)
					{
						Field [] row = fieldArray[i];
						for(int j=0; j<row.length; j++)
							finishField(row[j]);
					}
				}
				value = "";
				if (customButtonClicked != null) value = customButtonClicked;
			}
		}
		dispose();
	}

	/**
	 * Method called to complete a field when the dialog closes.
	 * @param field the field to process.
	 */
	private void finishField(Field field)
	{
		if (field == null) return;
		switch (field.type)
		{
			case Field.FIELD_STRING:
				field.finalValue = field.textField.getText();
				break;
			case Field.FIELD_SELECT:
				field.finalValue = field.combo.getSelectedItem();
				break;
			case Field.FIELD_COLOR:
				Color newColor = parseColor(field);
				if (newColor == null) break;
				field.finalValue = newColor;
				break;
		}
	}

	/**
	 * Method called to initialize the prompt dialog.
	 * @param wnd the EditWindow_ in which to show the dialog.
	 * @param ni the NodeInst (in the EditWindow_) over which to show the dialog.
	 * @param title the title of the dialog.
	 * @param fieldList if not null, a 1-dimensional array of fields to place in the dialog.
	 * @param fieldArray if not null, a 2-dimensional grid of fields to place in the dialog.
	 */
	private void initComponents(EditWindow_ wnd, NodeInst ni, String title, Field [] fieldList, Field [][] fieldArray)
	{
		getContentPane().setLayout(new GridBagLayout());

		setTitle(title);
		setName("");
		addWindowListener(new WindowAdapter()
		{
			public void windowClosing(WindowEvent evt) { closed = true;   exit(false); }
		});

		int buttonRow = 1;
		goodClicked = false;
		JComponent centerIt = null;
		this.fieldList = fieldList;
		this.fieldArray = fieldArray;
		if (fieldList != null)
		{
			for(int i=0; i<fieldList.length; i++)
			{
				centerIt = initializeField(fieldList[i], i, 0, centerIt);
			}
			buttonRow = fieldList.length + 1;
		} else if (fieldArray != null)
		{
			for(int i=0; i<fieldArray.length; i++)
			{
				Field [] row = fieldArray[i];
				for(int j=0; j<row.length; j++)
					centerIt = initializeField(row[j], i, j, centerIt);
			}
			buttonRow = fieldArray.length + 1;
		}
		if (centerIt == null)
			centerIt = fieldList[0].labelObj;

		String badButton = (yesNo ? "No" : "Cancel");
		JButton cancel = new JButton(badButton);
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.gridx = 0;
		gbc.gridy = buttonRow;
		gbc.insets = new java.awt.Insets(4, 4, 4, 4);
		gbc.weightx = 0.5;
		getContentPane().add(cancel, gbc);
		cancel.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent evt) { exit(false); }
		});

		String goodButton = (yesNo ? "Yes" : "OK");
		JButton ok = new JButton(goodButton);
		getRootPane().setDefaultButton(ok);
		gbc = new java.awt.GridBagConstraints();
		gbc.gridx = 1;
		gbc.gridy = buttonRow;
		gbc.gridwidth = 3;
		gbc.insets = new java.awt.Insets(4, 4, 4, 4);
		gbc.weightx = 0.5;
		getContentPane().add(ok, gbc);
		ok.addActionListener(new java.awt.event.ActionListener()
		{
			public void actionPerformed(java.awt.event.ActionEvent evt) { exit(true); }
		});

		pack();

		// now make the dialog appear over a node
		Point ew = wnd.getScreenLocationOfCorner();
		Point locInWnd = wnd.databaseToScreen(ni.getAnchorCenterX(), ni.getAnchorCenterY());
		Point textfield = centerIt.getLocation();
		Dimension textSize = centerIt.getSize();
		setLocation(locInWnd.x+ew.x-(textfield.x+textSize.width/2), locInWnd.y+ew.y-(textfield.y+textSize.height/2+20));
	}

	/**
	 * Method to initialize a field in the dialog.
	 * @param field the Field to initialize.
	 * @param i the Y index (0-based) of the field in the dialog.
	 * @param j the X index (0-based) of the field in the dialog.
	 * @param centerIt the component in the dialog that will be centered over the desired piece of circuitry.
	 * @return the new component in the dialog that will be centered over the desired piece of circuitry.
	 */
	private JComponent initializeField(Field field, int i, int j, JComponent centerIt)
	{
		if (field == null) return centerIt;
		if (field.label != null)
		{
			field.labelObj = new JLabel(field.label);
			GridBagConstraints gbc = new java.awt.GridBagConstraints();
			gbc.gridx = j*4;
			gbc.gridy = i;
			gbc.insets = new java.awt.Insets(4, 4, 4, 4);
			gbc.anchor = java.awt.GridBagConstraints.WEST;
			getContentPane().add(field.labelObj, gbc);
		}
		switch (field.type)
		{
			case Field.FIELD_STRING:
				field.textField = new JTextField((String)field.initial);
				GridBagConstraints gbc = new java.awt.GridBagConstraints();
				gbc.gridx = j*4+1;
				gbc.gridy = i;
				gbc.gridwidth = 3;
				gbc.weightx = 1.0;
				gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
				gbc.insets = new java.awt.Insets(4, 4, 4, 4);
				getContentPane().add(field.textField, gbc);
				if (centerIt == null)
				{
					field.textField.selectAll();
					centerIt = field.textField;
				}
				break;
			case Field.FIELD_SELECT:
				field.combo = new JComboBox();
				String [] poss = (String [])field.initial;
				for(int k=0; k<poss.length; k++)
					field.combo.addItem(poss[k]);
				field.combo.setSelectedItem(field.finalValue);
				gbc = new java.awt.GridBagConstraints();
				gbc.gridx = j*4+1;
				gbc.gridy = i;
				gbc.gridwidth = 3;
				gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
				gbc.insets = new java.awt.Insets(4, 4, 4, 4);
				getContentPane().add(field.combo, gbc);
				if (centerIt == null) centerIt = field.combo;
				break;
			case Field.FIELD_BUTTON:
				JButton but = (JButton)field.initial;
				but.addActionListener(new CustomButtonActionListener(this, field));
				gbc = new java.awt.GridBagConstraints();
				gbc.gridx = j*4+1;
				gbc.gridy = i;
				gbc.gridwidth = 3;
				gbc.fill = java.awt.GridBagConstraints.HORIZONTAL;
				gbc.insets = new java.awt.Insets(4, 4, 4, 4);
				getContentPane().add(but, gbc);
				if (centerIt == null) centerIt = but;
				break;			
			case Field.FIELD_COLOR:
				Color col = (Color)field.initial;
				field.patch = new JPanel();
				Dimension size = new Dimension(25, 25);
				field.patch.setSize(size);
				field.patch.setPreferredSize(size);
				field.patch.setBackground(col);
				gbc = new java.awt.GridBagConstraints();
				gbc.gridx = j*4+1;
				gbc.gridy = i;
				gbc.insets = new java.awt.Insets(4, 4, 4, 4);
				getContentPane().add(field.patch, gbc);
		
				field.textField = new JTextField();
				field.textField.setColumns(8);
				field.textField.setText(col.getRed() + "," + col.getGreen() + "," + col.getBlue());
				gbc = new java.awt.GridBagConstraints();
				gbc.gridx = j*4+2;
				gbc.gridy = i;
				gbc.insets = new java.awt.Insets(4, 4, 4, 4);
				getContentPane().add(field.textField, gbc);
				field.textField.getDocument().addDocumentListener(new ColorDocumentListener(field));
		
				field.but = new JButton("Set");
				gbc = new java.awt.GridBagConstraints();
				gbc.gridx = j*4+3;
				gbc.gridy = i;
				gbc.insets = new java.awt.Insets(4, 4, 4, 4);
				getContentPane().add(field.but, gbc);
				field.but.addActionListener(new MixColorActionListener(this, field));
				if (centerIt == null)
				{
					field.textField.selectAll();
					centerIt = field.but;
				}
				break;
		}
		return centerIt;
	}

	/**
	 * Method to extract a Color value from a Field.
	 * @param field the Field to examine (must hold a Color selection).
	 * @return the Color in that Field.
	 */
	private static Color parseColor(Field field)
	{
		String newColor = field.textField.getText();
		String [] rgb = newColor.split(",");
		if (rgb.length < 3) return null;
		int r = TextUtils.atoi(rgb[0]);
		if (r < 0) r = 0;   if (r > 255) r = 255;
		int g = TextUtils.atoi(rgb[1]);
		if (g < 0) g = 0;   if (g > 255) g = 255;
		int b = TextUtils.atoi(rgb[2]);
		if (b < 0) b = 0;   if (b > 255) b = 255;
		return new Color(r, g, b);
	}

	/**
	 * Class to handle clicks on the "set" color button.
	 */
	private static class MixColorActionListener implements ActionListener
	{
		private PromptAt top;
		private Field field;

		private MixColorActionListener(PromptAt top, Field field)
		{
			this.top = top;
			this.field = field;
		}

		public void actionPerformed(ActionEvent evt)
		{
			Color origColor = parseColor(field);
			Color newColor = JColorChooser.showDialog(top, "Edit color", origColor);
			if (newColor == null) return;
			field.textField.setText(newColor.getRed() + "," + newColor.getGreen() + "," + newColor.getBlue());
			field.patch.setBackground(newColor);
		}
	}

	/**
	 * Class to handle clicks on user's custom buttons.
	 */
	private static class CustomButtonActionListener implements ActionListener
	{
		private PromptAt top;
		private Field field;

		private CustomButtonActionListener(PromptAt top, Field field)
		{
			this.top = top;
			this.field = field;
		}

		public void actionPerformed(ActionEvent evt)
		{
			top.customButtonClicked = (String)field.finalValue;
			top.exit(true);
		}
	}

	/**
	 * Class to handle changes to the text description of a color value.
	 */
	private static class ColorDocumentListener implements DocumentListener
	{
		private Field which;

		private ColorDocumentListener(Field which)
		{
			this.which = which;
		}

		public void changedUpdate(DocumentEvent e) { updatePatchColor(); }
		public void insertUpdate(DocumentEvent e) { updatePatchColor(); }
		public void removeUpdate(DocumentEvent e) { updatePatchColor(); }

		private void updatePatchColor()
		{
			Color newColor = parseColor(which);
			which.patch.setBackground(newColor);
		}
	}
}
