/*******************************************************************************
 * Copyright (c) 2007 Intel Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Dennis Ushakov, Intel - Initial API and Implementation
 *    Oleg Danilov, Intel
 *
 *******************************************************************************/

package org.eclipse.bpel.model.util;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

import org.eclipse.bpel.model.ExtensionActivity;
import org.eclipse.wst.wsdl.WSDLElement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

public class ElementPlacer {
	private static HashMap<String, List<String>> mapper = new HashMap<String, List<String>>();
	private static final String ACTIVITY = "activity";

	static {
		// TODO: (DU) add other activities
		// Process
		String processElements[] = { BPELConstants.ND_EXTENSIONS,
				BPELConstants.ND_IMPORT, BPELConstants.ND_PARTNER_LINKS,
				BPELConstants.ND_MESSAGE_EXCHANGES, BPELConstants.ND_VARIABLES,
				BPELConstants.ND_CORRELATION_SETS,
				BPELConstants.ND_FAULT_HANDLERS,
				BPELConstants.ND_EVENT_HANDLERS, ACTIVITY };
		mapper.put(BPELConstants.ND_PROCESS, Arrays.asList(processElements));
		// FaultHandlers
		String faultHandlersElements[] = { BPELConstants.ND_CATCH,
				BPELConstants.ND_CATCH_ALL };
		mapper.put(BPELConstants.ND_FAULT_HANDLERS, Arrays
				.asList(faultHandlersElements));
		// EventHandlers
		String eventHandlersElements[] = { BPELConstants.ND_ON_EVENT,
				BPELConstants.ND_ON_ALARM };
		mapper.put(BPELConstants.ND_EVENT_HANDLERS, Arrays
				.asList(eventHandlersElements));
		// Invoke
		String invokeElements[] = { BPELConstants.ND_CORRELATIONS,
				BPELConstants.ND_CATCH, BPELConstants.ND_CATCH_ALL,
				BPELConstants.ND_COMPENSATION_HANDLER,
				BPELConstants.ND_TO_PARTS, BPELConstants.ND_FROM_PARTS };
		mapper.put(BPELConstants.ND_INVOKE, Arrays.asList(invokeElements));
		// While
		String whileElements[] = { BPELConstants.ND_CONDITION, ACTIVITY };
		mapper.put(BPELConstants.ND_WHILE, Arrays.asList(whileElements));
		// ForEach
		String forEachElements[] = { BPELConstants.ND_START_COUNTER_VALUE,
				BPELConstants.ND_FINAL_COUNTER_VALUE,
				BPELConstants.ND_COMPLETION_CONDITION, ACTIVITY };
		mapper.put(BPELConstants.ND_FOR_EACH, Arrays.asList(forEachElements));
		// RepeatUntil
		String repeatElements[] = { ACTIVITY, BPELConstants.ND_CONDITION };
		mapper
				.put(BPELConstants.ND_REPEAT_UNTIL, Arrays
						.asList(repeatElements));
		// If
		String ifElements[] = { BPELConstants.ND_CONDITION, ACTIVITY,
				BPELConstants.ND_ELSEIF, BPELConstants.ND_ELSE };
		mapper.put(BPELConstants.ND_IF, Arrays.asList(ifElements));
		// ElseIf
		String elseIfElements[] = { BPELConstants.ND_CONDITION, ACTIVITY };
		mapper.put(BPELConstants.ND_ELSEIF, Arrays.asList(elseIfElements));
	}

	public static void placeChild(WSDLElement parent, Node child) {
		Element parentElement = parent.getElement();
		
		if (parent instanceof ExtensionActivity){
			parentElement = ReconciliationHelper.getExtensionActivityChildElement(parentElement);
		}
		
		List<String> nodeTypeList = mapper.get(parentElement.getLocalName());
		if (nodeTypeList != null) {
			String nodeName = child.getLocalName();
			String nodeType = findType(nodeName, nodeTypeList);
			if (nodeType != null) {
				Node beforeElement = parentElement.getFirstChild();
				while (beforeElement != null
						&& (!isPreviousType(nodeType, findType(beforeElement
								.getLocalName(), nodeTypeList), nodeTypeList) || beforeElement
								.getNodeType() != Node.ELEMENT_NODE)) {
					beforeElement = beforeElement.getNextSibling();
				}
				while (beforeElement != null
						&& (isType(beforeElement.getLocalName(), nodeType) || beforeElement
								.getNodeType() != Node.ELEMENT_NODE)) {
					beforeElement = beforeElement.getNextSibling();
				}
				ElementPlacer.niceInsertBefore(parent, child, beforeElement);
				return;
			}
		}
		ElementPlacer.niceAppend(parent, child);
	}

	private static String findType(String nodeName, List<String> nodeTypeList) {
		for (String nodeType : nodeTypeList) {
			if (isType(nodeName, nodeType)) {
				return nodeType;
			}
		}
		return null;
	}

	private static boolean isPreviousType(String typeName1, String typeName2,
			List<String> nodeTypeList) {
		int type1Index = nodeTypeList.indexOf(typeName1);
		int type2Index = nodeTypeList.indexOf(typeName2);
		return type1Index < type2Index || (type2Index < 0 && type1Index >= 0);
	}

	private static boolean isType(String nodeName, String typeName) {
		return ACTIVITY.equals(typeName) ? isActivity(nodeName) : typeName
				.equals(nodeName);
	}

	private static boolean isActivity(String nodeName) {
		return BPELConstants.ND_ASSIGN.equals(nodeName)
				|| BPELConstants.ND_COMPENSATE.equals(nodeName)
				|| BPELConstants.ND_COMPENSATE_SCOPE.equals(nodeName)
				|| BPELConstants.ND_EMPTY.equals(nodeName)
				|| BPELConstants.ND_EXIT.equals(nodeName)
				|| BPELConstants.ND_EXTENSION_ACTIVITY.equals(nodeName)
				|| BPELConstants.ND_FLOW.equals(nodeName)
				|| BPELConstants.ND_FOR_EACH.equals(nodeName)
				|| BPELConstants.ND_IF.equals(nodeName)
				|| BPELConstants.ND_INVOKE.equals(nodeName)
				|| BPELConstants.ND_PICK.equals(nodeName)
				|| BPELConstants.ND_RECEIVE.equals(nodeName)
				|| BPELConstants.ND_REPEAT_UNTIL.equals(nodeName)
				|| BPELConstants.ND_REPLY.equals(nodeName)
				|| BPELConstants.ND_RETHROW.equals(nodeName)
				|| BPELConstants.ND_SCOPE.equals(nodeName)
				|| BPELConstants.ND_SEQUENCE.equals(nodeName)
				|| BPELConstants.ND_THROW.equals(nodeName)
				|| BPELConstants.ND_VALIDATE.equals(nodeName)
				|| BPELConstants.ND_WAIT.equals(nodeName)
				|| BPELConstants.ND_WHILE.equals(nodeName)
				|| BPELConstants.ND_OPAQUEACTIVITY.equals(nodeName);
	}

	public static void niceInsertBefore(WSDLElement parent, Node newChild,
			Node referenceChild) {
		boolean was = ReconciliationHelper.isUpdatingDom(parent);
		Element parentElement = parent.getElement();
		
		if (parent instanceof ExtensionActivity){
			parentElement = ReconciliationHelper.getExtensionActivityChildElement(parentElement);
		}
		
		ReconciliationHelper.setUpdatingDom(parent, true);
		Node child = (referenceChild == null) ? parentElement.getLastChild()
				: referenceChild.getPreviousSibling();

		// DO: parentElement has no children ( <tag /> )
		if (child == null) {
			StringBuffer indent = new StringBuffer();
			for (Node ancestor = parentElement.getParentNode(); ancestor != null
					&& ancestor.getNodeType() != Node.DOCUMENT_NODE; ancestor = ancestor
					.getParentNode()) {
				indent.append("    ");
			}
			Text text = parentElement.getOwnerDocument().createTextNode(
					"\n" + indent + "    ");
			parentElement.insertBefore(text, referenceChild);
			text = parentElement.getOwnerDocument().createTextNode(
					"\n" + indent);
			referenceChild = parentElement.insertBefore(text, referenceChild);
			parentElement.insertBefore(newChild, referenceChild);
			return;
		}

		while (child != null) {
			short nodeType = child.getNodeType();
			if (nodeType == Node.ELEMENT_NODE)
				break;
			if (nodeType != Node.TEXT_NODE) {
				child = child.getPreviousSibling();
				continue;
			}

			Text text = (Text) child;
			String data = text.getData();
			int index = data.lastIndexOf('\n');
			if (index == -1) {
				child = child.getPreviousSibling();
				continue;
			}

			StringBuffer indent = new StringBuffer();
			for (Node ancestor = parentElement.getParentNode(); ancestor != null
					&& ancestor.getNodeType() != Node.DOCUMENT_NODE; ancestor = ancestor
					.getParentNode()) {
				indent.append("    ");
			}
			if (index + 1 < data.length() && data.charAt(index + 1) == '\r') {
				index++;
			}
			text.replaceData(index + 1, data.length() - index - 1, indent
					+ "    ");

			// DO:
			// Format children of the newChild.
			Node innerChild = newChild.getFirstChild();
			if (innerChild != null) {
				// add "\n" + indent before every child
				while (innerChild != null) {
					if (innerChild.getNodeType() == Node.TEXT_NODE) {
						// remove an old indentation
						newChild.removeChild(innerChild);
					} else {
						newChild.insertBefore(newChild.getOwnerDocument()
								.createTextNode("\n" + indent + "        "),
								innerChild);
					}
					innerChild = innerChild.getNextSibling();
				}
				// add "\n" after the last child
				newChild.appendChild(newChild.getOwnerDocument()
						.createTextNode("\n" + indent + "    "));
			}

			if (referenceChild != null) {
				indent.append("    ");
			}
			text = parentElement.getOwnerDocument().createTextNode(
					"\n" + indent);
			parentElement.insertBefore(text, referenceChild);
			referenceChild = text;
			break;
		}

		parentElement.insertBefore(newChild, referenceChild);
		ReconciliationHelper.setUpdatingDom(parent, was);
	}

	public static void niceAppend(WSDLElement parent, Node child) {
		niceInsertBefore(parent, child, null);
	}

	public static void niceRemoveChild(WSDLElement parent, Node child) {
		boolean was = ReconciliationHelper.isUpdatingDom(parent);
		ReconciliationHelper.setUpdatingDom(parent, true);

		
		Element parseElement = parent.getElement();

		if (parent instanceof ExtensionActivity) {
			parseElement = ReconciliationHelper.getExtensionActivityChildElement(parseElement);
		}
		
		boolean done = false;

		Node previous = child.getPreviousSibling();
		if (previous != null && previous.getNodeType() == Node.TEXT_NODE) {
			Text text = (Text) previous;
			String data = text.getData();
			int index = data.lastIndexOf('\n');
			if (index != -1) {
				if (index - 1 > 0 && data.charAt(index - 1) == '\r') {
					text.deleteData(index - 1, data.length() - index + 1);
				} else {
					text.deleteData(index, data.length() - index);
				}
				done = true;
			}
		}

		if (!done) {
			for (Node next = child.getNextSibling(); next != null; next = next
					.getNextSibling()) {
				if (next.getNodeType() == Node.TEXT_NODE) {
					Text text = (Text) next;
					String data = text.getData();
					int index = data.indexOf('\n');
					if (index != -1) {
						if (index + 1 < data.length()
								&& data.charAt(index + 1) == '\r') {
							text.deleteData(0, index + 2);
						} else {
							text.deleteData(0, index + 1);
						}
						break;
					}
					continue;
				}

				if (next.getNodeType() == Node.ELEMENT_NODE) {
					break;
				}
			}
		}

		parseElement.removeChild(child);
		ReconciliationHelper.setUpdatingDom(parent, was);
	}
}
