/*******************************************************************************
 * Copyright (c) 2002 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     Exadel, Inc.
 *     Red Hat, Inc. 
 *******************************************************************************/
package org.jboss.tools.vpe.editor.menu.xpl;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMGroup;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNode;
import org.eclipse.wst.xml.core.internal.contentmodel.CMNodeList;
import org.eclipse.wst.xml.core.internal.contentmodel.internal.util.CMValidator;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery;
import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQueryAction;
import org.eclipse.wst.xml.core.internal.contentmodel.modelqueryimpl.ModelQueryImpl;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ActionHelper {
	public static final String copyright = "(c) Copyright IBM Corporation 2002."; //$NON-NLS-1$
	protected ModelQueryImpl modelQuery;

	protected static class Action implements ModelQueryAction {
		public int kind;
		public int startIndex;
		public int endIndex;
		public Node parent;
		public CMNode cmNode;
		public Object userData;

		public Action(int kind, Node parent, CMNode cmNode) {
			this.kind = kind;
			this.parent = parent;
			this.cmNode = cmNode;
		}

		public Action(int kind, Node parent, CMNode cmNode, int startIndex,
				int endIndex) {
			this.kind = kind;
			this.parent = parent;
			this.cmNode = cmNode;
			this.startIndex = startIndex;
			this.endIndex = endIndex;
		}

		public int getKind() {
			return kind;
		}

		public int getStartIndex() {
			return startIndex;
		}

		public int getEndIndex() {
			return endIndex;
		}

		public Node getParent() {
			return parent;
		}

		public CMNode getCMNode() {
			return cmNode;
		}

		public Object getUserData() {
			return userData;
		}

		public void setUserData(Object object) {
			userData = object;
		}

		public void performAction() {
		}
  
	}

	public ActionHelper(ModelQueryImpl modelQuery) {
		this.modelQuery = modelQuery;
	}

	public void getAllActions(Element parent, CMElementDeclaration ed, int validityChecking, List actionList) {
	}

	// insert actions
	public void getInsertActions(Element parent, CMElementDeclaration ed, int index, int includeOptions, int validityChecking, List actionList) {
    if ((includeOptions & ModelQuery.INCLUDE_ATTRIBUTES) != 0) {
			getInsertAttributeActions(parent, ed, validityChecking, actionList);
		}
		includeOptions &= ~ModelQuery.INCLUDE_ATTRIBUTES;
		if ((includeOptions & ModelQuery.INCLUDE_CHILD_NODES) != 0) {
			if (index != -1) {
				getInsertChildNodeActionsAtIndex(parent, ed, index,
						includeOptions, validityChecking, actionList);
			} else {
				getInsertChildNodeActions(parent, ed, includeOptions,
						validityChecking, actionList);
			}
		}
	}

	protected void getInsertAttributeActions(Element parent,
			CMElementDeclaration ed, int validityChecking, List actionList) {
		// get actions for each insertable attribute
		List availableAttributeList = modelQuery.getAvailableContent(parent,
				ed, ModelQuery.INCLUDE_ATTRIBUTES);

		for (Iterator i = availableAttributeList.iterator(); i.hasNext();) {
			CMAttributeDeclaration ad = (CMAttributeDeclaration) i.next();
			if (modelQuery.canInsert(parent, ed, ad, 0, validityChecking)) {
				Action action = new Action(Action.INSERT, parent, ad);
				actionList.add(action);
			}
		}
	}

	protected void getInsertChildNodeActionsAtIndex(Element parent,
			CMElementDeclaration ed, int index, int includeOptions,
			int validityChecking, List actionList) {
		// get actions for each insertable attribute
		List availableChildNodeList = modelQuery.getAvailableContent(parent,
				ed, includeOptions);

		boolean isSimpleChoice = isSimpleChoiceGroupContentModel(ed);

		for (Iterator i = availableChildNodeList.iterator(); i.hasNext();) {
			CMNode cmNode = (CMNode) i.next();
			if (isSimpleChoice) {
				Action action = new Action(Action.INSERT, parent, cmNode,
						index, index);
				actionList.add(action);
			}
		}
	}

	protected boolean isSimpleChoiceGroupContentModel(CMElementDeclaration ed) {       
	    boolean result = false;
		CMNode cmNode = ed.getContent();
		if (cmNode != null && cmNode.getNodeType() == CMNode.GROUP) {
			CMGroup cmGroup = (CMGroup) cmNode;
			if (cmGroup.getOperator() == CMGroup.CHOICE
					&& cmGroup.getMaxOccur() == -1) {
				result = true;
				CMNodeList list = cmGroup.getChildNodes();
				for (int i = list.getLength() - 1; i >= 0; i--) {
					if (list.item(i).getNodeType() != CMNode.ELEMENT_DECLARATION) {
						result = false;
						break;
					}
				}
			}
		}
		return result;
	}

	protected void getInsertChildNodeActions(Element parent,
			CMElementDeclaration ed, int includeOptions, int validityChecking,
			List actionList) {
		int size = parent.getChildNodes().getLength();
		List availableChildNodeList = modelQuery.getAvailableContent(parent,
				ed, includeOptions);

		boolean isSimpleChoice = isSimpleChoiceGroupContentModel(ed);

		for (Iterator iterator = availableChildNodeList.iterator(); iterator
				.hasNext();) {
			CMNode cmNode = (CMNode) iterator.next();
			for (int i = size; i >= 0; i--) {
				if (isSimpleChoice) {
					Action action = new Action(Action.INSERT, parent, cmNode,
							i, i);
					actionList.add(action);
					break;
				}
			}
		}
	}

	public void getInsertActions(Document parent, CMDocument cmDocument,
			int index, int includeOptions, int validityChecking, List actionList) {
		// get the root element and doctype index (if any)
		int doctypeIndex = -1;
		DocumentType doctype = null;
		Element rootElement = null;
		NodeList nodeList = parent.getChildNodes();
		int nodeListLength = nodeList.getLength();
		for (int i = 0; i < nodeListLength; i++) {
			Node childNode = nodeList.item(i);
			if (childNode.getNodeType() == Node.ELEMENT_NODE) {
				rootElement = (Element) childNode;
				break;
			} else if (childNode.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
				doctype = (DocumentType) childNode;
				doctypeIndex = i;
			}
		}

		// make sure that root elements are only added after the doctype (if
		// any)
		if (rootElement == null && index > doctypeIndex) {
			CMNamedNodeMap map = cmDocument.getElements();
			int mapLength = map.getLength();
			for (int i = 0; i < mapLength; i++) {
				CMNode cmNode = map.item(i);

				boolean canAdd = true;
				if (validityChecking == ModelQuery.VALIDITY_STRICT) {
					canAdd = doctype == null
							|| doctype.getName().equals(cmNode.getNodeName());
				}

				if (canAdd) {
					Action action = new Action(Action.INSERT, parent, cmNode,
							index, index);
					actionList.add(action);
				}
			}
		}
	}

	public void getInsertChildNodeActionTable(Element parent, CMElementDeclaration ed, int validityChecking, Hashtable actionTable) {
	}

	public void getReplaceActions(Element parent, CMElementDeclaration ed,
			int includeOptions, int validityChecking, List actionList) {
		CMValidator.MatchModelNode matchModelNode = modelQuery.getValidator()
				.getMatchModel(ed, parent);
		if (matchModelNode != null) {
			MatchModelVisitor visitor = new MatchModelVisitor(parent,
					actionList);
			visitor.visitMatchModelNode(matchModelNode);
		}
	}

	public void getReplaceActions(Element parent, CMElementDeclaration ed,
			List selectedChildren, int includeOptions, int validityChecking,
			List actionList) {
		int[] range = getRange(parent, selectedChildren);
		if (range != null) {
			if (isContiguous(parent, range, selectedChildren)) {
				List tempList = new Vector();
				getReplaceActions(parent, ed, includeOptions, validityChecking,
						tempList);
				if ((includeOptions & ModelQuery.INCLUDE_ENCLOSING_REPLACE_ACTIONS) != 0) {
					removeActionsNotContainingRange(tempList, range[0],
							range[1]);
				} else {
					removeActionsNotMatchingRange(tempList, range[0], range[1]);
				}
				actionList.addAll(tempList);
			}
		}

		if (selectedChildren.size() == 1) {
			Node node = (Node) selectedChildren.get(0);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				Element childElement = (Element) node;
				CMNode childEd = modelQuery
						.getCMElementDeclaration(childElement);
				if (childEd != null) {

					CMNode childOrigin = (CMNode) modelQuery
							.getOrigin(childElement);

					CMNodeList cmNodeList = childOrigin != null ? (CMNodeList) childOrigin
							.getProperty("SubstitutionGroup") //$NON-NLS-1$
							: (CMNodeList) childEd
									.getProperty("SubstitutionGroup"); //$NON-NLS-1$

					if (cmNodeList != null && cmNodeList.getLength() > 1) {
						int replaceIndex = getIndex(parent, childElement);
						String childEdName = childEd.getNodeName();
						for (int i = 0; i < cmNodeList.getLength(); i++) {
							CMNode substitution = cmNodeList.item(i);
							if (!substitution.getNodeName().equals(childEdName)
									&& !Boolean.TRUE.equals(substitution
											.getProperty("Abstract"))) { //$NON-NLS-1$
								Action action = new Action(Action.REPLACE,
										parent, cmNodeList.item(i),
										replaceIndex, replaceIndex);
								actionList.add(action);
							}
						}
					}
				}
			}
		}
	}     

	// returns true if the selected nodes are contiguous
	protected boolean isContiguous(Element parent, int[] range,
			List selectedNodeList) {
		boolean result = true;
		NodeList nodeList = parent.getChildNodes();
		for (int i = range[0]; i < range[1]; i++) {
			Node node = nodeList.item(i);
			if (!isWhitespaceNode(node) && !selectedNodeList.contains(node)) {
				result = false;
				break;
			}
		}
		return result;
	}
 
	protected int[] getRange(Element parent, List list) {
		int[] result = null;
		int first = -1;
		int last = -1;

		NodeList nodeList = parent.getChildNodes();
		int nodeListLength = nodeList.getLength();
		for (int i = 0; i < nodeListLength; i++) {
			Node node = nodeList.item(i);
			if (list.contains(node)) {
				first = (first == -1) ? i : Math.min(first, i);
				last = Math.max(last, i);
			}
		}

		if (first != -1 && last != -1) {
			result = new int[2];
			result[0] = first;
			result[1] = last;
		}
		return result;
	} 

	protected boolean isWhitespaceNode(Node node) {
		return node.getNodeType() == Node.TEXT_NODE
				&& node.getNodeValue().trim().length() == 0;
	} 

	protected int getIndex(Node parentNode, Node child) {
		NodeList nodeList = parentNode.getChildNodes();
		int index = -1;
		int size = nodeList.getLength();
		for (int i = 0; i < size; i++) {
			if (nodeList.item(i) == child) {
				index = i;
				break;
			}
		}
		return index;
	}                    

	protected boolean isActionContainingRange(ModelQueryAction action,
			int startIndex, int endIndex) {
		int actionStartIndex = action.getStartIndex();
		int actionEndIndex = action.getEndIndex();

		return (actionStartIndex <= startIndex && actionEndIndex >= endIndex);
	} 

	protected boolean isActionMatchingRange(ModelQueryAction action,
			int startIndex, int endIndex) {
		int actionStartIndex = action.getStartIndex();
		int actionEndIndex = action.getEndIndex();
		return (actionStartIndex == startIndex && actionEndIndex == endIndex);
	} 

	protected void removeActionsNotContainingRange(List actionList,
			int startIndex, int endIndex) {
		for (int i = actionList.size() - 1; i >= 0; i--) {
			ModelQueryAction action = (ModelQueryAction) actionList.get(i);
			if (!isActionContainingRange(action, startIndex, endIndex)) {
				actionList.remove(i);
			}
		}
	}

	protected void removeActionsNotMatchingRange(List actionList,
			int startIndex, int endIndex) {
		for (int i = actionList.size() - 1; i >= 0; i--) {
			ModelQueryAction action = (ModelQueryAction) actionList.get(i);
			if (!isActionMatchingRange(action, startIndex, endIndex)) {
				actionList.remove(i);
			}
		}
	}

	public static class MatchModelVisitor {
		int indent;
		int elementIndex;
		Node parent;
		List actionList;

		public MatchModelVisitor(Node parent, List actionList) {
			this.parent = parent;
			this.actionList = actionList;
		}

		public int indexOfNextElement(int start) {
			NodeList nodeList = parent.getChildNodes();
			int length = nodeList.getLength();
			int result = length;
			for (int i = start; i < length; i++) {
				Node node = nodeList.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					result = i;
					break;
				}
			}
			return result;
		}

		public void visitMatchModelNode(
				CMValidator.MatchModelNode matchModelNode) {
			int startIndex = indexOfNextElement(elementIndex);

			String cmNodeName = matchModelNode.cmNode != null ? matchModelNode.cmNode
					.getNodeName()
					: "null"; //$NON-NLS-1$

			indent += 2;
			for (Iterator iterator = matchModelNode.children.iterator(); iterator
					.hasNext();) {
				CMValidator.MatchModelNode child = (CMValidator.MatchModelNode) iterator
						.next();
				visitMatchModelNode(child);
			}
			indent -= 2;

			if (matchModelNode.cmNode != null) {
				int nodeType = matchModelNode.cmNode.getNodeType();
				if (nodeType == CMNode.GROUP) {
					CMGroup group = (CMGroup) matchModelNode.cmNode;
					if (group.getOperator() == CMGroup.CHOICE) {
						addReplaceActions(matchModelNode, group, startIndex,
								elementIndex - 1);
					}
				} else if (nodeType == CMNode.ELEMENT_DECLARATION) {
					elementIndex = startIndex + 1;
				}
			}
		}

		public void addReplaceActions(
				CMValidator.MatchModelNode matchModelNode, CMGroup group,
				int startIndex, int endIndex) {
			CMNode excludeCMNode = null;
			if (matchModelNode.children.size() > 0) {
				CMValidator.MatchModelNode child = (CMValidator.MatchModelNode) matchModelNode.children
						.get(0);
				excludeCMNode = child.cmNode;
			}

			CMNodeList nodeList = group.getChildNodes();
			int size = nodeList.getLength();
			for (int i = 0; i < size; i++) {
				CMNode alternative = nodeList.item(i);
				if (alternative != excludeCMNode) {
					Action action = new Action(Action.REPLACE, parent,
							alternative, startIndex, endIndex);
					actionList.add(action);
				}
			}
		}
	}

}

