package ash.gui.core;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.awt.Component;
import javax.swing.SwingConstants;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.SequentialGroup;
import javax.swing.GroupLayout.ParallelGroup;
import javax.swing.GroupLayout.Group;
import javax.swing.GroupLayout.Alignment;
import static javax.swing.GroupLayout.Alignment.BASELINE;
import static javax.swing.GroupLayout.Alignment.LEADING;
import static javax.swing.GroupLayout.Alignment.CENTER;
import static javax.swing.GroupLayout.Alignment.TRAILING;
import ash.util.Log;

/**
 * AshGroupPanel は、GroupLayout でレイアウトされるパネルを簡便な表記で
 * 定義できるようにしたパネル生成用のクラスであり、
 * 個々のパネルごとに拡張して使用することを想定している。
 * <p>拡張の方法は、コンストラクタの中、または construct() メソッドを
 * オーバライドして、その中でレイアウトを定義する。
 * レイアウト定義では、生成したComponentを横方向と縦方向にグループ化するために、
 * 可変個引数を持つメソッド setH/V(), paraB/L/C/T(), seq() を使用する。
 * ギャップの生成にはgap()メソッドを使用する。
 * <p>setH/V()メソッドは、コンポーネント要素を水平方向と水力方向の両方で
 * グループ化の指定を行わなければならず、使用方法が複雑である。
 * この問題を解決するために form()メソッドを用意した。
 * GUI画面の簡易な生成のためには form()メソッドを利用すると良い。
 * <p>form()メソッドは、二次元の表に揃えてレイアウトする場合に利用できる。
 * form() は可変個の行を引数にとり、各行は 可変個の列要素を引数にとる
 * line()メソッドで定義する。列要素は Component、文字列、sg()メソッドで
 * 定義する。sg(seauential group)は複数の要素を一つの列要素にまとめる際に
 * 使用する。
 * @see javax.swing.GroupLayout
 */
@SuppressWarnings("serial")
public class AshGroupPanel extends AshPanel {
	protected GroupLayout layout;
	public AshGroupPanel() {
		Log.debug(1, "new AshGroupPanel()");
		layout = new GroupLayout(this);
		setLayout(layout);
		layout.setAutoCreateGaps(true);
		layout.setAutoCreateContainerGaps(true);
		construct();
	}
	public AshGroupPanel(int hPadding, int vPadding) {	// CA,111003
		this();
		setPadding(hPadding, vPadding);
	}
	protected void construct() {};

	private int hPadding = -1;	// デフォルトの水平方向パディング
	private int vPadding = -1;	// デフォルトの垂直方向パディング
	private Gap hGap;			// デフォルトの水平方向ギャップ
	private Gap vGap;			// デフォルトの垂直方向ギャップ
	/**
	 * 水平方向と垂直方向のパディングを設定する。
	 * @param hPadding 水平方向パディング
	 * @param vPadding 垂直方向パディング
	 */
	public void setPadding(int hPadding, int vPadding) {
		this.hPadding = hPadding;
		this.vPadding = vPadding;
	}
	/**
	 * 水平方向のギャップを設定する。
	 * @param hGap 水平方向ギャップ
	 */
	public void setHGap(int hGap) { this.hGap = gap(hGap); } // CA,090824
	/**
	 * 垂直方向ギャップを設定する。
	 * @param vGap 垂直方向ギャップ
	 */
	public void setVGap(int vGap) { this.vGap = gap(vGap); } // CA,090824

	private Alignment hAlignment = LEADING;
	private Alignment vAlignment = CENTER;
	/**
	 * 水平方向のアライメントを設定する。
	 * @param hAlignment 水平方向アライメント
	 */
	public void setHAlignment(Alignment hAlignment) {	// CA,090824
		this.hAlignment = hAlignment;
	}
	/**
	 * 垂直方向アライメントを設定する。
	 * @param vAlignment 垂直方向アライメント
	 */
	public void setVAlignment(Alignment vAlignment) {	// CA,090824
		this.vAlignment = vAlignment;
	}

	/**
	 * 行のリストとして定義されたフォームをレイアウトする。
	 * <pre>
	 * form := (line | gap)+
	 * line := (string | component | sg | null)+
	 * sg   := (string | component | gap)+			// sg = sequential group
	 * </pre>
	 * @param lines 文字列または JComponent の並び
	 */
	@SuppressWarnings("unchecked")
	public AshGroupPanel form(Object... lines) {	// CA,111003
		List<Object> vg = new ArrayList<Object>();
		List<Object> hg = new ArrayList<Object>();
		for(Object obj : lines) {					// line(...)
			if(obj instanceof Gap) {
				vg.add(para(vAlignment, obj));
			} else if(obj instanceof List) {
				formLine(vg, hg, (List)obj);
			} else {
				throw new RuntimeException("ERROR: form: " + obj);
			}
		}
		for(int i = 0; i < hg.size(); i++) {
			Object obj = hg.get(i);
			if(obj instanceof List)					// CA,090824
				hg.set(i, para(hAlignment, ((List)obj).toArray()));
		}
		setV(vg.toArray());
		setH(hg.toArray());
		return this;								// CA,111003
	}
	@SuppressWarnings("unchecked")
	private void formLine(List<Object> vg, List<Object> hg, List columns) {
		List<Object> line = new ArrayList<Object>();
		int columnNo = 0;
		for(Object column : columns) {				// 一行の各要素
			if(column == null) {
			} else if(column instanceof Gap) {
				hg.add(para(hAlignment, column));
			} else if(column instanceof Component) { // label, button, ...
				line.add(column);
				addPara(hg, columnNo, column);
			} else if(column instanceof List) {		// sg(gap, label, ...)
				List seqg = (List)column;
				for(Object c : seqg) {
					if(c instanceof Component) line.add(c);
				}
				addPara(hg, columnNo, seq(seqg.toArray()));
			} else {
				throw new RuntimeException("ERROR: form line: " + column);
			}
			columnNo++;
		}
		vg.add(para(vAlignment, line.toArray()));
	}
	@SuppressWarnings("unchecked")
	private void addPara(List hg, int columnNo, Object column) {
		for(int i = hg.size(); i <= columnNo; i++)
			hg.add(new ArrayList());
		Object obj = hg.get(columnNo);
		if(obj instanceof List) {					// CA,090824
			List para = (List)obj;
			para.add(column);		// unchecked
		}
	}
	/**
	 * コンポーネントのグループを水平方向に配置するように設定する。
	 */
	protected void setH(Object... objects) {
		layout.setHorizontalGroup(seq(hPadding, objects));
	}
	/**
	 * コンポーネントのグループを垂直方向に配置するように設定する。
	 */
	protected void setV(Object... objects) {
		layout.setVerticalGroup(seq(vPadding, objects));
	}

	/**
	 * ParallelGroup 生成メソッド：paraB/L/C/T(compo/group/gap,...)。
	 * <p>任意個の Component, Group, または Gap をもつ ParallelGroup を
	 * 生成する。整列規則として、BASELINE, LEADING, CENTER, TAILING が
	 * あり、それぞれ１文字の頭文字で区別する。
	 */
	protected ParallelGroup paraB(Object... objects) {
		return para(BASELINE, objects);
	}
	protected ParallelGroup paraL(Object... objects) {
		return para(LEADING, objects);
	}
	protected ParallelGroup paraC(Object... objects) {
		return para(CENTER, objects);
	}
	protected ParallelGroup paraT(Object... objects) {
		return para(TRAILING, objects);
	}
	private ParallelGroup para(Alignment alignment, Object... objects) {
		ParallelGroup pg = layout.createParallelGroup(alignment);
		assert Log.debug(1, "==> AshGroupPanel#para(): " + alignment);
		for(Object obj : objects) {
			assert Log.debug(1, getObjectText(obj));
			if(obj instanceof Component) {
				pg.addComponent((Component)obj);
			} else if(obj instanceof String) {
				pg.addComponent(label((String)obj));
			} else if(obj instanceof Gap) {
				((Gap)obj).addTo(pg);
			} else if(obj instanceof Group) {
				pg.addGroup((Group)obj);
			} else {
				throw new RuntimeException("ERROR: para: " + obj);
			}
		}
		assert Log.debug(1, "<== AshGroupPanel#para(): " + alignment);
		return pg;
	}
	
	/**
	 * SequentialGroup 生成メソッド。
	 * <p> 任意個の Component, Group, または Gap をもつ SequentialGroup を
	 * 生成する。
	 * @param objects 任意個の要素の並び
	 */
	protected SequentialGroup seq(Object... objects) {
		return seq(-1, objects);
	}
	/**
	 * 第１引数が int型で０以上の場合は、その値で padding する。
	 * @param padding ０以上の場合パディング値として使う。
	 * 負の値の場合はデフォルトのパディングとなる。
	 */
	protected SequentialGroup seq(int padding, Object... objects) {
		Gap pad = gap(padding);
		SequentialGroup sg = layout.createSequentialGroup();
		assert Log.debug(1, "==> AshGroupPanel#seq()");
		if(padding >= 0) pad.addTo(sg);
		for(Object obj : objects) {
			assert Log.debug(1, getObjectText(obj));
			if(obj instanceof Component) {
				sg.addComponent((Component)obj);
			} else if(obj instanceof String) {
				sg.addComponent(label((String)obj));
			} else if(obj instanceof Gap) {
				((Gap)obj).addTo(sg);
			} else if(obj instanceof Group) {
				sg.addGroup((Group)obj);
			} else {
				throw new RuntimeException("ERROR: seq: " + obj);
			}
		}
		if(padding >= 0) pad.addTo(sg);
		assert Log.debug(1, "<== AshGroupPanel#seq()");
		return sg;
	}
	static final String COMP_FORMAT = "^([^\\[]+)\\[.*text=([^,\\]]*)";
	// <????>[...text=<????>, ...]
	static private Pattern compPat = Pattern.compile(COMP_FORMAT);
	/**
	 * GUIコンポーネントのテキスト情報を取得する。
	 * デバッグ用メソッド(debug 1 で有効になる)
	 */
	protected static String getObjectText(Object obj) {
		String s = obj.toString();
		Matcher m = compPat.matcher(s);
		if(!m.find()) return s;
		return m.group(1) + " [" + m.group(2) + "]";
	}

	/**
	 * Component を置換する。
	 */
	protected void replace(Component compo, Component newCompo) {
		layout.replace(compo, newCompo);
	}

	/**
	 * 任意個の Component を同一サイズにする
	 */
	protected void linkSize(Component... components) {
		layout.linkSize(components);
	}
	/**
	 * 任意個の Component を同一の幅にする
	 */
	protected void linkSizeH(Component... components) {
		layout.linkSize(SwingConstants.HORIZONTAL, components);
	}
	/**
	 * 任意個の Component を同一の高さにする
	 */
	protected void linkSizeV(Component... components) {
		layout.linkSize(SwingConstants.VERTICAL, components);
	}
}
