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

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

import woolpack.bool.BoolUtils;
import woolpack.fn.Fn;
import woolpack.fn.FnUtils;
import woolpack.id.IdContext;
import woolpack.xml.NodeContext;
import woolpack.xml.NodeFindUtils;
import woolpack.xml.XmlUtils;

public class IFrameInserter<C extends NodeContext & IdContext, E extends Exception> implements Fn<C, Void, E> {
	
	private static final Fn<Node, NodeList, RuntimeException> IFRAME = NodeFindUtils.list(
	FnUtils.join(XmlUtils.GET_NODE_NAME, BoolUtils.checkEquals("IFRAME")), false);

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

	private static final Fn<Node, Node, RuntimeException> HEAD = NodeFindUtils.one(
	FnUtils.join(XmlUtils.GET_NODE_NAME, BoolUtils.checkEquals("HEAD")));
	
	private String frameId;
	private String targetName;
	private Fn<? super C, Void, ? extends E> nodeMaker;
	private Fn<String, String, ? extends E> idConverter;

	public IFrameInserter(
			final String frameId,
			final String targetName,
			final Fn<? super C, Void, ? extends E> nodeMaker,
			final Fn<String, String, ? extends E> idConverter) {
		this.frameId = frameId;
		this.targetName = targetName;
		this.nodeMaker = nodeMaker;
		this.idConverter = idConverter;
	}
	
	private Node getNode(final String id, final C c) throws E {
		final String baseId = c.getId();
		final Node baseNode = c.getNode();
		try {
			c.setId(id);
			nodeMaker.exec(c);
			return  c.getNode();
		} finally {
			c.setId(baseId);
			c.setNode(baseNode);
		}
	}
	
	private static void importNode(final Node node0, final Node node1) {
		node0.getParentNode().insertBefore(
				XmlUtils.getDocumentNode(node0).importNode(node1, true),
				node0);
	}

	public Void exec(final C c) throws E {
		final Node node0 = getNode(frameId, c);
		final NodeList node0IframeList = IFRAME.exec(node0);
		for(int i = 0; i < node0IframeList.getLength(); i++) {
			final Element node0Iframe = (Element) node0IframeList.item(i);
			final boolean targetFlag = targetName.equals(node0Iframe.getAttribute("name"));
			final Node node1 = getNode(
					targetFlag ? c.getId() : idConverter.exec(node0Iframe.getAttribute("src")),		
					c);
			{
				final Node node1Body = BODY.exec(node1);
				Node tmp1 = node1Body.getFirstChild();
				while (tmp1 != null) {
					importNode(node0Iframe, tmp1);
					tmp1 = tmp1.getNextSibling();
				}
				XmlUtils.removeThis(node0Iframe);
			}
			if(targetFlag) {
				final Node node0Head = HEAD.exec(node0);
				importNode(node0Head, HEAD.exec(node1));
				XmlUtils.removeThis(node0Head);
			}
		}
		c.setNode(node0);
		return null;
	}

	public String getFrameId() {
		return frameId;
	}
	public void setFrameId(final String frameId) {
		this.frameId = frameId;
	}
	public Fn<String, String, ? extends E> getIdConverter() {
		return idConverter;
	}
	public void setIdConverter(final Fn<String, String, ? extends E> idConverter) {
		this.idConverter = idConverter;
	}
	public Fn<? super C, Void, ? extends E> getNodeMaker() {
		return nodeMaker;
	}
	public void setNodeMaker(final Fn<? super C, Void, ? extends E> nodeMaker) {
		this.nodeMaker = nodeMaker;
	}
	public String getTargetName() {
		return targetName;
	}
	public void setTargetName(final String targetName) {
		this.targetName = targetName;
	}
}
