/*
 * 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 org.w3c.dom.Element;
import org.w3c.dom.Node;

import woolpack.el.GettingEL;
import woolpack.fn.Fn;
import woolpack.utils.Utils;
import woolpack.xml.NodeContext;

/**
 * 値を再生成する{@link Fn}です。
 * 入力部品(file, image)の場合はなにもしません。
 * ノードの種類がラジオボタン/チェックボックス/セレクトで selectFlag の場合、
 * selected 属性の有無または checked 属性の有無を変更します。
 * ノードの種類がラジオボタン/チェックボックス/セレクトで selectFlag でないか、
 * 入力部品(text, password, hidden, submit, reset, button)の場合、
 * value 属性値を変更します。
 * ノードの種類が入力部品以外であるかテキストエリアの場合、子ノードを値のテキストで置き換えます。
 * 
 * @author nakamura
 * 
 */
public class ValueUpdater implements Fn<NodeContext, Void> {
	private static final String VALUE = "value";
	private static final String SELECTED = "selected";
	private static final String CHECKED = "checked";
	private static final String TRUE = "true";

	private GettingEL valueEL;
	private GettingEL mapEL;
	private boolean selectFlag;
	private final SelectedValueUpdater defaultReturnable;

	/**
	 * @param valueEL 値の取得先への参照。
	 * @param mapEL 値とラベルの{@link java.util.Map}への参照。
	 * @param selectFlag selected または checked 属性の有無を変更するなら true。value の属性値を変更するなら false。
	 */
	public ValueUpdater(
			final GettingEL valueEL,
			final GettingEL mapEL,
			final boolean selectFlag) {
		this.valueEL = valueEL;
		this.mapEL = mapEL;
		this.selectFlag = selectFlag;
		
		defaultReturnable = new SelectedValueUpdater(valueEL, mapEL);
	}

	/**
	 * selected または checked 属性の有無を変更するモードです。
	 * @param valueEL 値の取得先。
	 * @param mapEL 値とラベルの{@link java.util.Map}への参照。
	 */
	public ValueUpdater(
			final GettingEL valueEL,
			final GettingEL mapEL) {
		this(valueEL, mapEL, true);
	}

	/**
	 * selected または checked 属性の有無を変更するモードです。
	 * @param valueEL
	 *            値の取得先への参照。コンテキスト役に対して副作用が発生すべきではない。
	 */
	public ValueUpdater(
			final GettingEL valueEL) {
		this(valueEL, null, true);
	}
	
	private void executeAttrValue(final NodeContext context, final Element e) {
		final Object value = valueEL.getValue(context);
		if (value != null) {
			e.setAttribute(VALUE, value.toString());
		}
	}
	
	private void executeOption(final NodeContext context, final Element e) {
		if (selectFlag) {
			if (contains(context, e)) {
				e.setAttribute(SELECTED, TRUE);
			} else {
				e.removeAttribute(SELECTED);
			}
		} else {
			executeAttrValue(context, e);
		}
	}
	
	private boolean contains(final NodeContext context, final Element e) {
		final String value = e.getAttribute(VALUE);
		final Collection collection = Utils.toCollection(valueEL.getValue(context));
		for (final Object o : collection) {
			if (o.toString().equals(value)) {
				return true;
			}
		}
		return false;
		//return collection.contains(value);
	}

	public Void exec(final NodeContext context) {
		final Element e = (Element) context.getNode();
		final String elementName = e.getNodeName();
		if ("OPTION".equals(elementName)) {
			executeOption(context, e);
		} else if ("SELECT".equals(elementName)) {
			for (Node n = e.getFirstChild(); n != null; n = n.getNextSibling()) {
				if (n.getNodeType() == Node.ELEMENT_NODE && "OPTION".equals(n.getNodeName())) {
					executeOption(context, (Element) n);
				}
			}
		} else if ("INPUT".equals(elementName)) {
			final String inputType = e.getAttribute("type");
			if ("file".equals(inputType) || "image".equals(inputType)) {
				// nothing to do.
			} else if (selectFlag) {
				if ("radio".equals(inputType) || "checkbox".equals(inputType)) {
					if (contains(context, e)) {
						e.setAttribute(CHECKED, TRUE);
					} else {
						if (e.hasAttribute(CHECKED)) {
							e.removeAttribute(CHECKED);
						}
					}
				} else {
					executeAttrValue(context, e);
				}
			} else {
				executeAttrValue(context, e);
			}
		} else if ("BUTTON".equals(elementName)) {
			executeAttrValue(context, e);
		} else {
			defaultReturnable.exec(context);
		}
		return null;
	}

	public GettingEL getMapEL() {
		return mapEL;
	}
	public void setMapEL(final GettingEL mapEL) {
		this.mapEL = mapEL;
		defaultReturnable.setValueEL(mapEL);
	}
	public boolean isSelectFlag() {
		return selectFlag;
	}
	public void setSelectFlag(final boolean selectFlag) {
		this.selectFlag = selectFlag;
	}
	public GettingEL getValueEL() {
		return valueEL;
	}
	public void setValueEL(final GettingEL valueEL) {
		this.valueEL = valueEL;
		defaultReturnable.setValueEL(valueEL);
	}
}
