/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package woolpack.html;

import java.util.Collection;
import java.util.Map;
import java.util.regex.Pattern;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import woolpack.bool.BoolUtils;
import woolpack.convert.ConvertUtils;
import woolpack.el.EL;
import woolpack.el.GettingEL;
import woolpack.fn.Fn;
import woolpack.fn.FnUtils;
import woolpack.xml.NodeContext;
import woolpack.xml.NodeFindUtils;
import woolpack.xml.XmlUtils;

/**
 * DOMで表現されたHTMLを操作するユーティリティです。
 * 型推論で表記を簡略するためのスタティックメソッドと変数を含みます。
 * 
 * @author nakamura
 * 
 */
public final class HtmlUtils {

	/**
	 * テキストノードの空白文字を圧縮する関数です。
	 */
	public static final Fn<NodeContext, Void, RuntimeException> COMPRESS_SPACE = new SpaceCompressor<RuntimeException>();
	
	/**
	 * テーブルの全ての列の上下同一値のセルを結合する関数です。
	 */
	public static final Fn<NodeContext, Void, RuntimeException> MERGE_CELL_ALL = new MergeCellAll<RuntimeException>();
	
	/**
	 * エレメント名を大文字に、属性名を小文字に変換する関数です。
	 */
	public static final Fn<NodeContext, Void, RuntimeException> NORMALIZE_CASE = new CaseNormalizer<RuntimeException>();
	
	/**
	 * SCRIPT ノード以外のコメントノードを削除する関数です。
	 */
	public static final Fn<NodeContext, Void, RuntimeException> REMOVE_COMMENT = new CommentRemover<RuntimeException>();
	
	/**
	 * 指定された DOM ノードを削除し、さらにその後ろにあるテキストノードを全て削除する関数です。
	 */
	public static final Fn<NodeContext, Void, RuntimeException> REMOVE_THIS_AND_TEXTS = new ThisAndTextsRemover<RuntimeException>();

	static final Fn<Node, NodeList, RuntimeException> TR_LIST = NodeFindUtils.list(
	FnUtils.join(XmlUtils.GET_NODE_NAME, BoolUtils.checkEquals("TR")), false);

	static final Fn<Node, Node, RuntimeException> TR_ONE = NodeFindUtils.one(
	FnUtils.join(XmlUtils.GET_NODE_NAME, BoolUtils.checkEquals("TR")));

	static final Fn<Node, NodeList, RuntimeException> TD_LIST = NodeFindUtils.list(
			FnUtils.join(XmlUtils.GET_NODE_NAME, BoolUtils.checkEquals("TD")), false);

	private HtmlUtils() {
	}

	/**
	 * 指定された DOM ノードを削除し、さらにその後ろにあるテキストノードを全て削除します。
	 * ラジオボタン・チェックボックスを削除するために使用します。
	 * 
	 * @param node
	 */
	public static void removeThisAndText(final Node node) {
		Node nextNode = node.getNextSibling();
		while (nextNode != null && nextNode.getNodeType() == Node.TEXT_NODE) {
			XmlUtils.removeThis(nextNode);
			nextNode = node.getNextSibling();
		}
		XmlUtils.removeThis(node);
	}
	
	/**
	 * DOM エレメントの属性値をプロパティ名としてコンポーネントから値を取得し、
	 * DOM ノードに自動設定する関数を生成します。
	 * @param attrNames 属性名の一覧。
	 * @param componentEL コンポーネントへの参照。
	 * @param configEL 設定値への参照。
	 * @param atomCollection 値の個数に関して原子的であるクラスの一覧。
	 * @param errorEL 値取得に失敗した場合の値の取得先。
	 * @return 関数。
	 * @see AutoUpdater
	 */
	public static Fn<NodeContext, Void, RuntimeException> updateAuto(
			final Iterable<String> attrNames,
			final GettingEL componentEL,
			final GettingEL configEL,
			final Collection<Class<?>> atomCollection,
			final GettingEL errorEL) {
		return new AutoUpdater<RuntimeException>(attrNames, componentEL, configEL, atomCollection, errorEL);
	}
	
	/**
	 * DOM エレメントの属性値をプロパティ名としてコンポーネントから値を取得し、
	 * DOM ノードに自動設定する関数を生成します。
	 * 値取得に失敗した場合は何もしません。
	 * @param attrNames 属性名の一覧。
	 * @param componentEL コンポーネントへの参照。
	 * @param configEL 設定値への参照。
	 * @return 関数。
	 * @see AutoUpdater
	 */
	public static Fn<NodeContext, Void, RuntimeException> updateAuto(
			final Iterable<String> attrNames,
			final GettingEL componentEL,
			final GettingEL configEL) {
		return new AutoUpdater<RuntimeException>(attrNames, componentEL, configEL);
	}
	
	/**
	 * 各属性値の出現回数(1回か2回以上)により処理を分岐する関数を生成します。
	 * @param <C>
	 * @param <E>
	 * @param el プロパティの出現回数を保持する位置。
	 * @param attrNames 属性名の一覧。
	 * @param firstFn 最初の検索結果に対する委譲先。
	 * @param pluralFn 2番目以降の検索結果に対する委譲先。
	 * @return 関数。
	 */
	public static <C extends NodeContext, E extends Exception> Fn<C, Void, E> branchPropertyCount(
			final EL el,
			final Iterable<String> attrNames,
			final Fn<? super C, Void, ? extends E> firstFn,
			final Fn<? super C, Void, ? extends E> pluralFn) {
		return new BranchPropertyCounter<C, E>(el, attrNames, firstFn, pluralFn);
	}
	
	/**
	 * テーブルの行毎の属性値を循環的に設定する関数を生成します。
	 * @param attrName 属性名。
	 * @param attrValueArray 属性値の一覧。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> convertRowAttr(
			final String attrName,
			final String[] attrValueArray) {
		return new RowAttrConverter<RuntimeException>(attrName, attrValueArray);
	}
	
	/**
	 * 子ノードに HTML の隠し項目(hidden パラメータ)を追加する関数を生成します。
	 * @param mapEL hidden にする情報が格納された{@link Map}への参照。
	 * @param excludeProperties hidden として追加しないキーの一覧。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> hiddenAllToChild(
			final GettingEL mapEL,
			final Collection<String> excludeProperties) {
		return new HiddenAppender<RuntimeException>(mapEL, excludeProperties);
	}
	
	/**
	 * テーブルに行番号列を追加する関数を生成します。
	 * @param headValue 最初の行の値。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> insertRowIndex(final String headValue) {
		return new RowIndexInserter<RuntimeException>(headValue);
	}
	
	/**
	 * {@link Map}に格納された値とラベルの対応表を使用して
	 * HTML のラジオボタンまたはチェックボックスを再生成する関数を生成します。
	 * @param mapEL 値とラベルの{@link Map}への参照。キー・値とも{@link Object#toString()}で文字列として扱います。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> makeRadio(final GettingEL mapEL) {
		return new RadioRemaker<RuntimeException>(mapEL);
	}
	
	/**
	 * {@link Map}に格納された値とラベルの対応表を使用して
	 * HTML のセレクトを再生成する関数を生成します。
	 * @param mapEL 値とラベルの{@link Map}への参照。キー・値とも{@link Object#toString()}で文字列として扱います。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> makeSelect(final GettingEL mapEL) {
		return new SelectRemaker<RuntimeException>(mapEL);
	}
	
	/**
	 * テーブルの指定した列の上下同一値のセルを結合する関数を生成します。
	 * @param colIndex 結合対象の列。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> mergeCell(final int colIndex) {
		return new MergeCell<RuntimeException>(colIndex);
	}
	
	/**
	 * 値に対応するラベルを再生成する関数を生成します。
	 * 値が複数の場合は複数のラベルを再生成します。
	 * {@link Map}が存在しない場合または{@link Map}
	 * に対応するキーが存在しない場合は値をそのまま表示します。
	 * 前の画面のラジオボタン・チェックボックス・セレクトで選択した値を確認画面で表示するために使用します。
	 * @param valueEL 値の取得先への参照。
	 * @param mapEL 値とラベルの{@link Map}への参照。 キー・値とも{@link Object#toString()}で文字列として扱います。mapEL は null を許容します。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> updateToSelectedValue(
			final GettingEL valueEL,
			final GettingEL mapEL) {
		return new SelectedValueUpdater<RuntimeException>(valueEL, mapEL);
	}
	
	/**
	 * 値を再生成する関数を生成します。
	 * 入力部品(file, image)の場合はなにもしません。
	 * ノードの種類がラジオボタン/チェックボックス/セレクトで selectFlag の場合、
	 * selected 属性の有無または checked 属性の有無を変更します。
	 * ノードの種類がラジオボタン/チェックボックス/セレクトで selectFlag でないか、
	 * 入力部品(text, password, hidden, submit, reset, button)の場合、
	 * value 属性値を変更します。
	 * ノードの種類が入力部品以外であるかテキストエリアの場合、子ノードを値のテキストで置き換えます。
	 * @param valueEL 値の取得先への参照。
	 * @param mapEL 値とラベルの{@link java.util.Map}への参照。
	 * @param selectFlag selected または checked 属性の有無を変更するなら true。value の属性値を変更するなら false。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> updateValue(
			final GettingEL valueEL,
			final GettingEL mapEL,
			final boolean selectFlag) {
		return new ValueUpdater<RuntimeException>(valueEL, mapEL, selectFlag);
	}
	
	/**
	 * 値を再生成する関数を生成します。
	 * selected または checked 属性の有無を変更するモードです。
	 * @param valueEL 値の取得先。
	 * @param mapEL 値とラベルの{@link java.util.Map}への参照。
	 * @return 関数。
	 * @see #updateValue(GettingEL, GettingEL, boolean)
	 */
	public static ValueUpdater<RuntimeException> updateValue(
			final GettingEL valueEL,
			final GettingEL mapEL) {
		return new ValueUpdater<RuntimeException>(valueEL, mapEL, true);
	}
	
	/**
	 * 値を再生成する関数を生成します。
	 * selected または checked 属性の有無を変更するモードです。
	 * @param valueEL 値の取得先への参照。
	 * @return 関数。
	 * @see #updateValue(GettingEL, GettingEL, boolean)
	 */
	public static ValueUpdater<RuntimeException> updateValue(
			final GettingEL valueEL) {
		return new ValueUpdater<RuntimeException>(valueEL, null, true);
	}

	/**
	 * 拡張子を取り除く関数を生成します。
	 * @param elementName 作用対象のエレメント名。
	 * @param attrName 作用対象の属性名。
	 * @return 関数。
	 */
	public static Fn<NodeContext, Void, RuntimeException> removeExtension(final String elementName, final String attrName) {
		return XmlUtils.findNode(
			NodeFindUtils.list(
					new Fn<Node, Boolean, RuntimeException>() {
						public Boolean exec(final Node c) {
							return c.getNodeType() == Node.ELEMENT_NODE
							&& elementName.equals(c.getNodeName())
							&& ((Element) c).hasAttribute(attrName);
						}
					}, true),
			XmlUtils.updateAttrValue(attrName,
				FnUtils.join(
						XmlUtils.getAttrValue(attrName),
						ConvertUtils.convertRegExp(Pattern.compile("^([^\\.]+)\\.[^\\.]+$"), "$1"))));
	}
}
