/*
 * 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.Set;

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

import woolpack.el.ArrayPathEL;
import woolpack.el.EL;
import woolpack.el.ELUtils;
import woolpack.el.GettingEL;
import woolpack.el.PropertyEL;
import woolpack.el.StateEL;
import woolpack.utils.Utils;
import woolpack.xml.AbstractNodeSeeker;
import woolpack.xml.NodeContext;
import woolpack.xml.TemplateCopier;

/**
 * DOM エレメントの属性値をプロパティ名としてコンポーネントから値を取得し、
 * DOM ノードに自動設定する{@link woolpack.fn.Fn}です。
 * 業務系プログラムで作成したデータ構造を
 * HTMLのDOM表現に流し込む本機構により
 * プログラマとデザイナの結合を疎に保ちます。
 * @author nakamura
 * 
 */
public class AutoUpdater extends AbstractNodeSeeker<NodeContext> {
	private Iterable<String> attrNames;
	private GettingEL componentEL;
	private GettingEL configEL;
	private Set<Class> atomSet;
	private GettingEL errorEL;
	private final boolean selectFlag;

	AutoUpdater(
			final Iterable<String> attrNames,
			final GettingEL componentEL,
			final GettingEL configEL,
			final Set<Class> atomSet,
			final GettingEL errorEL,
			final boolean selectMode) {
		super();
		this.attrNames = attrNames;
		this.componentEL = componentEL;
		this.configEL = configEL;
		this.atomSet = atomSet;
		this.errorEL = errorEL;
		this.selectFlag = selectMode;
	}

	/**
	 * @param attrNames 属性名の一覧。
	 * @param componentEL コンポーネントへの参照。
	 * @param configEL 設定値への参照。
	 * @param atomSet 値の個数に関して原子的であるクラスの一覧。
	 * @param errorEL 値取得に失敗した場合の値の取得先。
	 */
	public AutoUpdater(
			final Iterable<String> attrNames,
			final GettingEL componentEL,
			final GettingEL configEL,
			final Set<Class> atomSet,
			final GettingEL errorEL) {
		this(attrNames, componentEL, configEL, atomSet, errorEL, true);
	}

	/**
	 * 値取得に失敗した場合は何もしません。
	 * 
	 * @param attrNames 属性名の一覧。
	 * @param componentEL コンポーネントへの参照。
	 * @param configEL 設定値への参照。
	 */
	public AutoUpdater(
			final Iterable<String> attrNames,
			final GettingEL componentEL,
			final GettingEL configEL) {
		this(
				attrNames,
				componentEL,
				configEL,
				Utils.ATOM_SET,
				ELUtils.NULL);
	}

	@Override
	public Void exec(final NodeContext c) {
		if (c.getNode().getNodeType() == Node.ELEMENT_NODE) {
			final Element e = (Element) c.getNode();
			final String attrName = getAttrName(e);
			if (attrName != null) {
				final String attrValue = e.getAttribute(attrName);
				final GettingEL valueEL = new ArrayPathEL(componentEL, new PropertyEL(attrValue));
				final Object value = getValue(c, valueEL);
				if (value != null) {
					if (isBeanCollection(value, atomSet)) {
						final EL tmpEL = new StateEL();
						new TemplateCopier<NodeContext>(
								valueEL,
								tmpEL,
								new AutoUpdater(
										attrNames,
										tmpEL,
										configEL,
										atomSet,
										errorEL,
										false)).exec(c);
					} else {
						new ValueUpdater(
								valueEL,
								new ArrayPathEL(configEL, new PropertyEL(attrValue)),
								selectFlag).exec(c);
					}
					return null;
				}
			}
		}
		return super.exec(c);
	}

	private Object getValue(final NodeContext c, final GettingEL valueEL) {
		try {
			return valueEL.getValue(c);
		} catch (final RuntimeException exception) {
			return errorEL.getValue(c);
		}
	}

	private String getAttrName(final Element e) {
		for (final String attrName : attrNames) {
			if (e.hasAttribute(attrName)) {
				return attrName;
			}
		}
		return null;
	}

	private static boolean isBeanCollection(final Object value,
			final Set<Class> atomSet) {
		if (value instanceof Collection) {
			final Collection c = (Collection) value;
			if (c.size() == 0) {
				return true;
			}
			return !atomSet.contains(c.iterator().next().getClass());
		}
		if (value.getClass().isArray()) {
			return !atomSet.contains(value.getClass().getComponentType());
		}
		return false;
	}

	public Set<Class> getAtomSet() {
		return atomSet;
	}
	public void setAtomSet(final Set<Class> atomSet) {
		this.atomSet = atomSet;
	}
	public Iterable<String> getAttrNames() {
		return attrNames;
	}
	public void setAttrNames(final Iterable<String> attrNames) {
		this.attrNames = attrNames;
	}
	public GettingEL getComponentEL() {
		return componentEL;
	}
	public void setComponentEL(final GettingEL componentEL) {
		this.componentEL = componentEL;
	}
	public GettingEL getConfigEL() {
		return configEL;
	}
	public void setConfigEL(final GettingEL configEL) {
		this.configEL = configEL;
	}
	public GettingEL getErrorEL() {
		return errorEL;
	}
	public void setErrorEL(final GettingEL errorEL) {
		this.errorEL = errorEL;
	}
}
