package ash.gui.core;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.EventObject;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Color;
import java.awt.Toolkit;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.TextEvent;
import java.awt.event.TextListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.JPanel;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.AbstractButton;
import javax.swing.JLabel;
import javax.swing.JComponent;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JRadioButton;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.JPasswordField;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.ButtonGroup;
import javax.swing.GroupLayout.Group;
import javax.swing.BorderFactory; 
import javax.swing.border.Border; 
import static javax.swing.border.EtchedBorder.RAISED;
import static javax.swing.border.TitledBorder.*;

/**
 * AshPanel は プリミティブコンポーネントとコンテナーコンポーネントで
 * 構成されるパネルを生成するための各種メソッドを提供する。
 * <p>
 * 対応しているコンポーネントの種別と関連メソッドは下記の通り：
 * <pre>
 *	プリミティブ・コンポーネント：
 *		label, textarea,
 *		button, check, radio, combobox, listbox, field, password
 *		groupButtons
 *  デコレータ・コンポーネント
 * 		scroll, border, splitH, splitV
 *  ボックスレイアウト・コンポーネント
 *		boxH/boxV{glue, strut, rigid}
 *  各種レイアウトマネージャをもつパネル
 *		flowL, flowC, flowR, grid,
 *		group{line, sg, gap},
 *		panel
 * </pre>
 */
@SuppressWarnings("serial")
public class AshPanel extends JPanel {
	public AshPanel() {}
	public AshPanel(int hgap, int vgap) {
		this(FlowLayout.CENTER, hgap, vgap);
	}
	public AshPanel(int align, int hgap, int vgap) {
		setLayout(new FlowLayout(align, hgap, vgap));
	}
	protected void add(Component... components) {
		for(Component c : components) add(c);
	}

	private ActionListener listener = GUIAdapter.getListener();
	protected void setActionListener(ActionListener listener) {
		this.listener = listener;
	}

	/**
	 * ★Mediator パターン：
	 * AshPanel は、複数個のコンポーネントの調停役(Mediator)として、
	 * コンポーネントの状態が変更されるときに、関係コンポーネント間の調整を行う。
	 * Mediator 機能を使用するには、AshPanel の拡張クラスで mediate() メソッドを
	 * オーバライドし、調停の内容を実装する。
	 * <p>
	 * 調停が必要なコンポーネント(Colleague)は、AshPanel の内部クラスとして
	 * 実装されており、コンポーネントの状態が変更されると AshPanel#mediate(this)
	 * を呼び出す。Colleague となりうるのは、以下のコンポーネント
	 * button, check, radio, combobox, listbox, field, password
	 * <p>
	 * mediate() の実装では、compo が AshField か AshPassword の場合は docEvent
	 * を、それ以外の場合は docEvent を参照することができる。
	 * @param compo 調停の依頼を発行したコンポーネント
	 */
	protected void mediate(Component compo) {}

	protected EventObject eventObject;
	protected DocumentEvent docEvent;

	private boolean mediationStarted = false;

	/**
	 * 調停の開始と終了
	 */
	public void startMediation() {
		mediationStarted = true;
		mediate(null);
	}
	public void stopMediation() {
		mediationStarted = false;
	}

	/**
	 * setColleagues()によって明示的に調停関連者が定義された場合は、
	 * その関係者の調停依頼だけを mediate() で処理する。
	 */
	private Set<Component> colleagues;

	protected void setColleagues(Component... components) {
		if(colleagues == null)
			colleagues = new HashSet<Component>();
		for(Component comp : components)
			colleagues.add(comp);
	}
	protected void resetColleagues() { colleagues = null; }

	private void _mediate(Component compo, EventObject e) {
		eventObject = e;
		_mediate(compo);
	}
	private void _mediate(Component compo, DocumentEvent e) {
		docEvent = e;
		_mediate(compo);
	}
	/**
	 * 調停は、以下の条件が満たされたときに実行される。
	 * mediationStarted && (colleagues == null || colleagues.contains(compo)
	 */
	private void _mediate(Component compo) {
		if(mediationStarted && (colleagues==null || colleagues.contains(compo)))
		   mediate(compo);
	}

	/**
	 * Colleague になるためのプリミティブコンポーネントのクラス定義：
	 * AshButton, AshCheckBox, AshRadio, AshComboBox, AshList,
	 * AshField, AshPassword
	 */
	protected class AshButton extends JButton implements ActionListener {
		public AshButton() { addActionListener(this); }
		@Override public void actionPerformed(ActionEvent e) {_mediate(this,e);}
	}
	protected class AshCheckBox extends JCheckBox implements ItemListener {
		public AshCheckBox() { addItemListener(this); }
		public void itemStateChanged(ItemEvent e) { _mediate(this,e); }
	}
	protected class AshRadio extends JRadioButton implements ItemListener {
		public AshRadio() { addItemListener(this); }
		public void itemStateChanged(ItemEvent e) { _mediate(this,e); }
	}
	@SuppressWarnings("unchecked")					// CA,111003
	protected class AshComboBox extends JComboBox implements ItemListener {
		AshComboBox() { addItemListener(this); }
		AshComboBox(Object[] listData) {
			this();
			for(Object item : listData) addItem(item);
		}
		public void itemStateChanged(ItemEvent e) { _mediate(this,e); }
	}
	@SuppressWarnings("unchecked")					// CA,111003
	protected class AshList extends JList implements ListSelectionListener {
		AshList() { addListSelectionListener(this); }
		AshList(Object[] listData) {
			this();
			setListData(listData);
		}
		public void valueChanged(ListSelectionEvent e) { _mediate(this,e); }
	}
	protected class AshField extends JTextField implements DocumentListener {
		public AshField() { getDocument().addDocumentListener(this); }
		public void setEnabled(boolean enabled) {
			super.setEnabled(enabled);
			setBackground(enabled ? Color.white : Color.lightGray);
		}
		// DocumentListener の実装
		public void insertUpdate(DocumentEvent e) { _mediate(this,e); }
		public void removeUpdate(DocumentEvent e) { _mediate(this,e); }
		public void changedUpdate(DocumentEvent e) {
			System.err.println("==> AshField#changedUpdate()");
		}
		public int getInt() { return Integer.parseInt(getText()); }
	}
	protected class AshPassword extends JPasswordField
		implements DocumentListener {
		public AshPassword() {
			//addTextListener(this);
			getDocument().addDocumentListener(this);
		}
		public void setEnabled(boolean enabled) {
			super.setEnabled(enabled);
			setBackground(enabled ? Color.white : Color.lightGray);
		}
		// DocumentListener の実装
		public void insertUpdate(DocumentEvent e) { _mediate(this,e); }
		public void removeUpdate(DocumentEvent e) { _mediate(this,e); }
		public void changedUpdate(DocumentEvent e) { _mediate(this,e); }
	}

	/**
	 * プリミティブコンポーネント：
	 * 下記基本コンポーネントの生成メソッドを提供する。
	 * label, button, check, radio, combobox, listbox, field, password, textarea
	 */
	static protected Component convert(Object obj) {
		if(obj instanceof String) return label((String)obj);
		return (Component)obj;
	}
	static protected JLabel label(String text) {
		AshFont af = new AshFont(text);
		JLabel label = new JLabel(af.getText());
		af.setFont(label);
		return label;
	}

	protected AshButton button() {
		AshButton b = new AshButton();
		b.addActionListener(listener);
		return b;
	}
	protected AshButton button(Icon icon) {
		AshButton b = button();
		b.setIcon(icon);
		return b;
	}
	protected AshButton button(String text) {
		AshFont af = new AshFont(text);
		text = af.getText();
		AshButton b = button();
		b.setText(text);
		setMnemonic(b, text);
		af.setFont(b);
		return b;
	}
	protected AshButton button(String text, String action) {
		AshButton b = button(text);
		b.setActionCommand(action);
		return b;
	}
	protected AshButton button(String text, boolean enable) {
		AshButton b = button(text);
		b.setEnabled(enable);
		return b;
	}
	protected AshButton button(String text, String action, boolean enable) {
		AshButton b = button(text, action);
		b.setEnabled(enable);
		return b;
	}

	protected AshCheckBox check() {
		return new AshCheckBox();
	}
	protected AshCheckBox check(String text) {
		AshCheckBox cb = check();
		cb.setText(text);
		setMnemonic(cb, text);
		return cb;
	}
	protected AshCheckBox check(String text, boolean selected) {
		AshCheckBox cb = check(text);
		cb.setSelected(selected);
		return cb;
	}
	protected AshCheckBox check(String text, String action, boolean selected) {
		AshCheckBox cb = check(text, selected);
		cb.setActionCommand(action);
		cb.addActionListener(listener);
		return cb;
	}
	protected AshRadio radio() {
		return new AshRadio();
	}
	protected AshRadio radio(boolean selected) {
		AshRadio rb = radio();
		rb.setSelected(selected);
		return rb;
	}
	protected AshRadio radio(String text) {
		AshRadio rb = radio();
		rb.setText(text);
		setMnemonic(rb, text);
		return rb;
	}
	protected AshRadio radio(String text, boolean selected) {
		AshRadio rb = radio(text);
		rb.setSelected(selected);
		return rb;
	}
	protected AshRadio radio(String text, String action, boolean selected) {
		AshRadio rb = radio(text, selected);
		rb.setActionCommand(action);
		rb.addActionListener(listener);
		return rb;
	}
	// 任意個のラジオボタン(AshRadio)をグループ化する。
	static protected void groupButtons(AbstractButton... buttons) {
		ButtonGroup group = new ButtonGroup();
		for(AbstractButton b : buttons) group.add(b);
	}

	protected AshComboBox combobox() { return new AshComboBox(); }
	protected AshComboBox combobox(Object[] items) {
		return new AshComboBox(items);
	}
	protected AshList listbox() { return new AshList(); }
	protected AshList listbox(Object[] items) {
		return new AshList(items);
	}

	protected AshFileChooser fchooser(String text, int columns) {
		return chooser(text, columns, 'f');
	}
	protected AshFileChooser dchooser(String text, int columns) {
		return chooser(text, columns, 'd');
	}
	private AshFileChooser chooser(String text, int columns, char mode) {
		AshFont af = new AshFont(text);
		AshFileChooser chooser = new AshFileChooser(af.getText(), columns, mode);
		af.setFont(chooser.getField());
		return chooser;
	}

	protected AshField field() { return new AshField(); }
	protected AshField field(String text) {
		AshFont af = new AshFont(text);
		AshField field = field();
		field.setText(af.getText());
		af.setFont(field);
		return field;
	}
	protected AshField field(int columns) {
		AshField field = field();
		field.setColumns(columns);
		return field;
	}
	protected AshField field(String text, int columns) {
		AshField field = field(text);
		field.setColumns(columns);
		return field;
	}

	protected AshPassword password() { return new AshPassword(); }
	protected AshPassword password(String text) {
		AshFont af = new AshFont(text);
		AshPassword password = password();
		password.setText(af.getText());
		af.setFont(password);
		return password;
	}
	protected AshPassword password(int columns) {
		AshPassword password = password();
		password.setColumns(columns);
		return password;
	}
	protected AshPassword password(String text, int columns) {
		AshPassword password = password(text);
		password.setColumns(columns);
		return password;
	}

	protected JTextArea textarea(int rows, int columns) {
		return textarea(rows, columns, AshFont.getFont());
	}
	protected JTextArea textarea(int rows, int columns, String text) {
		AshFont af = new AshFont(text);
		return textarea(rows, columns, af.getTextFont());
	}
	protected JTextArea textarea(int rows, int columns, Font font) {
		JTextArea ta = new JTextArea(rows, columns);
		ta.setFont(font);
		ta.setTabSize(4);
		ta.setLineWrap(true);
		Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
		d.height -= 64;
		d.width -= 64;
		ta.setMaximumSize(d);
		return ta;
	}

	/**
	 * ニーモニック定義の処理：
	 * 構文：	<テキスト>(<ニーモニック文字>)
	 * 定義例：	button("実行(R)")
	 */
	static private final String MNEMONIC = "\\(([A-Z])\\)$";
	static private Pattern mnemonicPat = Pattern.compile(MNEMONIC);
	static private int getMnemonic(String text) {
		Matcher m = mnemonicPat.matcher(text);
		return m.find() ? m.group(1).charAt(0) : 0;
	}
	static public void setMnemonic(AbstractButton b, String text) {
		int mnemonicChar = getMnemonic(text);
		if(mnemonicChar != 0) b.setMnemonic(mnemonicChar);
	}

	/**
	 * コンテナーコンポーネント：
	 * 下記のレイアウト機能をもつパネル生成メソッドを提供する。
	 * flowL, flowC, flowR, grid, boxH, boxV, group(line, sg)
	 * 各生成メソッドは、コンポーネント要素とレイアウト指示用マーカから、
	 * コンポーネントをレイアウトしたパネルを生成する。
	 * コンポーネント要素の中に文字列がある場合は、ラベルに変換する。
	 */
	/** フローレイアウト */
	protected JPanel flowL(Object... components) {
		return flowLG(hgap, components);
	}
	protected JPanel flowLG(int hgap, Object... components) {	// CA,111003
		JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, hgap, vgap));
		return addComponents(p, components);
	}
	protected JPanel flowC(Object... components) {
		return flowCG(hgap, components);
	}
	protected JPanel flowCG(int hgap, Object... components) {	// CA,111003
		JPanel p = new JPanel(new FlowLayout(FlowLayout.CENTER, hgap, vgap));
		return addComponents(p, components);
	}
	protected JPanel flowR(Object... components) {
		return flowRG(hgap, components);
	}
	protected JPanel flowRG(int hgap, Object... components) {	// CA,111003
		JPanel p = new JPanel(new FlowLayout(FlowLayout.RIGHT, hgap, vgap));
		return addComponents(p, components);
	}
	private int hgap = 2;
	private int vgap = 2;
	protected void setHgap(int hgap) { this.hgap = hgap; }
	protected void setVgap(int vgap) { this.vgap = vgap; }

	/** グリッドレイアウト */
	static public JPanel grid(int rows, int columns, Object... components) {
		JPanel p = new JPanel();
		p.setLayout(new GridLayout(rows, columns));
		return addComponents(p, components);
	}
	/** パネルにコンポーネント要素を登録する */
	static private JPanel addComponents(JPanel p, Object... components) {
		for(Object c : components) p.add(convert(c));
		return p;
	}

	/** ボックスレイアウト(横方向) */
	static protected Box boxH(Object... objects) {
		return boxHA(Component.TOP_ALIGNMENT, objects);
	}
	static protected Box boxHA(float alignment, Object... objects) { // CA,111003
		Box box = Box.createHorizontalBox();
		for(Object obj : objects) {
			if(obj instanceof JComponent) {
				JComponent c = (JComponent)obj;
				c.setAlignmentY(alignment);
				box.add(c);
			} else if(obj instanceof String) {
				JComponent c = label((String)obj);
				c.setAlignmentY(alignment);
				box.add(c);
			} else if(obj instanceof Glue) {
				box.add(Box.createHorizontalGlue());
			} else if(obj instanceof Strut) {
				box.add(Box.createHorizontalStrut(((Strut)obj).size));
			} else if(obj instanceof Rigid) {
				box.add(Box.createRigidArea((Rigid)obj));
			} else {
				throw new RuntimeException("ERROR: boxH: " + obj);
			}
		}
		return box;
	}
	/** ボックスレイアウト(縦方向) */
	static protected Box boxV(Object... objects) {
		return boxVA(Component.LEFT_ALIGNMENT, objects);
	}
	static protected Box boxVA(float alignment, Object... objects) { // CA,111003
		Box box = Box.createVerticalBox();
		for(Object obj : objects) {
			if(obj instanceof JComponent) {
				JComponent c = (JComponent)obj;
				c.setAlignmentX(alignment);
				box.add(c);
			} else if(obj instanceof String) {
				JComponent c = label((String)obj);
				c.setAlignmentX(alignment);
				box.add(c);
			} else if(obj instanceof Glue) {
				box.add(Box.createVerticalGlue());
			} else if(obj instanceof Strut) {
				box.add(Box.createVerticalStrut(((Strut)obj).size));
			} else if(obj instanceof Rigid) {
				box.add(Box.createRigidArea((Rigid)obj));
			} else {
				throw new RuntimeException("ERROR: boxV: " + obj);
			}
		}
		return box;
	}
	static public Glue glue() { return new Glue(); }
	static public Strut strut(int size) { return new Strut(size); }
	static public Rigid rigid(int width, int height) {
		return new Rigid(width, height);
	}
	static public class Glue {}
	static public class Strut {
		int size;
		public Strut(int size) { this.size = size; }
	}
	static public class Rigid extends Dimension {
		public Rigid(int width, int height) { super(width, height); }
	}

	/** グループレイアウト */
	static protected AshGroupPanel group(int hPad, int vPad, Object... objects) {
		return new AshGroupPanel(hPad, vPad).form(objects);	// CA,111003
	}
	static protected List<Object> line(Object... objects) {
		return sg(objects);
	}
	static protected List<Object> sg(Object... objects) {
		List<Object> sg = new ArrayList<Object>();
		for(Object obj : objects) {
			if(obj instanceof String) obj = label((String)obj);
			sg.add(obj);
		}
		return sg;
	}
	/**
	 * 指定サイズの Gap を生成する。
	 */
	static protected Gap gap(int size) {
		return new Gap(size, size, size);
	}
	/**
	 * 最小、推奨、最大のそれぞれの値をもつ Gap を生成する。
	 */
	static protected Gap gap(int min, int pref, int max) {
		return new Gap(min, pref, max);
	}
	/**
	 *　Gap クラス。最小、推奨、最大のそれぞれの値をもつ。
	 */
	static public class Gap {
		int min, pref, max;
		public Gap(int min, int pref, int max) {
			this.min = min; this.pref = pref; this.max = max;
		}
		public void addTo(Group g) {
			g.addGap(min, pref, max);
		}
		@Override public String toString() {
			if(min == max) return "Gap[" + min + "]";
			return "Gap[" + min + ", " + pref + ", " + max + "]";
		}
	}

	/** ボーダレイアウト・パネル */
	static public AshBorderPanel panel(Component compo) { // 固定レイアウト
		return panel().setNorth(panel().setWest(compo));
	}
	static public AshBorderPanel centerEast(Object center, Object east) {
		return panel().setCenter(center).setEast(east);
	}
	static public AshBorderPanel westCenter(Object west, Object center) {
		return panel().setWest(west).setCenter(center);
	}
	static public AshBorderPanel northCenter(Object north, Object center) {
		return panel().setNorth(north).setCenter(center);
	}
	static public AshBorderPanel centerSouth(Object center, Object south) {
		return panel().setCenter(center).setSouth(south);
	}
	static private AshBorderPanel panel() { return new AshBorderPanel(); }

	static public AshBorderPanel pane(Object... objects) {
		int n = objects.length;
		if(n == 0) return panel();
		if(n == 1) return pane().setCenter(objects[0]);
		if(n == 2) return northCenter(objects[0], objects[1]);
		return northCenter(objects[0], pane(shift(objects)));
	}
	static private Object[] shift(Object[] objects) {
		return Arrays.copyOfRange(objects, 1, objects.length);
	}

	/**
	 * スクロールバーで囲む
	 */
	static public JScrollPane scroll(Component compo) {
		JScrollPane sp = new JScrollPane();
		sp.setViewportView(compo);
		sp.setEnabled(true);
		sp.setHorizontalScrollBarPolicy(JScrollPane.
										HORIZONTAL_SCROLLBAR_AS_NEEDED);
		return sp;
	}

	/**
	 * ２つのコンポーネントをJSplitPaneでつなげる。
	 */
	static public JComponent splitH(boolean oneTouch, int resizeWeigth,
									   JComponent left, JComponent right) {
		JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
											  left, right);
		splitPane.setOneTouchExpandable(oneTouch);
		splitPane.setResizeWeight(resizeWeigth/100.0);
		return splitPane;
	}
	static public JComponent splitV(boolean oneTouch, int resizeWeigth,
									   JComponent top, JComponent bottom) {
		JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
											  top, bottom);
		splitPane.setOneTouchExpandable(oneTouch);
		splitPane.setResizeWeight(resizeWeigth/100.0);
		return splitPane;
	}

	/**
	 * 境界線で囲む
	 */
	static public JComponent border(int top, int left, int bottom, int right,
									   JComponent compo) {
		compo.setBorder(BorderFactory.createEmptyBorder(top,left,bottom,right));
		return compo;
	}
	static public JComponent border(JComponent compo) {
		compo.setBorder(BorderFactory.createEtchedBorder(RAISED));
		return compo;
	}
	static public JComponent border(JComponent compo, Color color) {
		compo.setBorder(BorderFactory.createLineBorder(color));
		return compo;
	}
	static public JComponent border(String title, JComponent compo) {
		AshFont af = new AshFont(title);
		Border border = BorderFactory.createEtchedBorder(RAISED);
		compo.setBorder(BorderFactory.createTitledBorder(border, af.getText(),
														 DEFAULT_JUSTIFICATION,
														 DEFAULT_POSITION,
														 af.getTextFont()
														 ));
		return compo;
	}
	static protected JComponent border(String title,
									   JComponent compo, Color color) {
		AshFont af = new AshFont(title);
		Border border = BorderFactory.createLineBorder(color);
		compo.setBorder(BorderFactory.createTitledBorder(border, af.getText(),
														 DEFAULT_JUSTIFICATION,
														 DEFAULT_POSITION,
														 af.getTextFont()
														 ));
		return compo;
	}
}
