/*******************************************************************************
 * Copyright (c) 2001, 2010 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.ui.internal.actions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDataType;
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.CMNode;
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.provisional.document.IDOMModel;
import org.eclipse.wst.xml.ui.internal.XMLUIMessages;
import org.w3c.dom.Attr;
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;
import org.w3c.dom.ProcessingInstruction;

public abstract class BaseNodeActionManager {


	/**
	 * MyMenuManager
	 */
	public static class MyMenuManager extends MenuManager {
		protected String title;

		public MyMenuManager(String s) {
			super(s);
			title = s;
		}

		public boolean isEnabled() {
			return !isEmpty();
		}

		public String toString() {
			return title;
		}
	}

	public static DocumentType getDoctype(Node node) {
		Document document = (node.getNodeType() == Node.DOCUMENT_NODE) ? (Document) node : node.getOwnerDocument();
		return document.getDoctype();
	}

	protected MenuBuilder menuBuilder = new MenuBuilder();
	protected IStructuredModel fModel;
	protected ModelQuery modelQuery;

	protected BaseNodeActionManager(IStructuredModel model, ModelQuery modelQuery) {
		this.fModel = model;
		this.modelQuery = modelQuery;
	}


	protected void addActionHelper(IMenuManager menu, List modelQueryActionList) {
		List actionList = new Vector();
		for (Iterator i = modelQueryActionList.iterator(); i.hasNext();) {
			ModelQueryAction action = (ModelQueryAction) i.next();
			if (action.getCMNode() != null) {
				int cmNodeType = action.getCMNode().getNodeType();
				if (action.getKind() == ModelQueryAction.INSERT) {
					switch (cmNodeType) {
						case CMNode.ATTRIBUTE_DECLARATION : {
							actionList.add(createAddAttributeAction((Element) action.getParent(), (CMAttributeDeclaration) action.getCMNode()));
							break;
						}
						case CMNode.ELEMENT_DECLARATION : {
							actionList.add(createAddElementAction(action.getParent(), (CMElementDeclaration) action.getCMNode(), action.getStartIndex()));
							break;
						}
					}
				}
				else if (action.getKind() == ModelQueryAction.REPLACE) {
					if ((action.getParent() != null) && (action.getCMNode() != null)) {
						actionList.add(createReplaceAction(action.getParent(), action.getCMNode(), action.getStartIndex(), action.getEndIndex()));
					}
				}
			}
		}
		menuBuilder.populateMenu(menu, actionList, false);
	}

	protected void contributeAction(IMenuManager menu, Action action) {
		if (action != null) {
			menu.add(action);
		}
	}


	public void contributeActions(IMenuManager menu, List selection) {
		int editMode = modelQuery.getEditMode();
		int ic = ModelQuery.INCLUDE_CHILD_NODES;
		int vc = (editMode == ModelQuery.EDIT_MODE_CONSTRAINED_STRICT) ? ModelQuery.VALIDITY_STRICT : ModelQuery.VALIDITY_NONE;

		List implicitlySelectedNodeList = null;

		if (selection.size() > 0) {
			implicitlySelectedNodeList = getSelectedNodes(selection, true);

			// contribute delete actions
			contributeDeleteActions(menu, implicitlySelectedNodeList, ic, vc);
		}

		if (selection.size() == 1) {
			Node node = (Node) selection.get(0);

			// contribute edit actions
			contributeEditActions(menu, node);

			// contribute add child actions
			contributeAddChildActions(menu, node, ic, vc);

			// contribute add before actions
			contributeAddSiblingActions(menu, node, ic, vc);
		}

		if (selection.size() > 0) {
			// contribute replace actions
			contributeReplaceActions(menu, implicitlySelectedNodeList, ic, vc);
		}

		if (selection.size() == 0) {
			Document document = ((IDOMModel) fModel).getDocument();
			contributeAddDocumentChildActions(menu, document, ic, vc);
			contributeEditGrammarInformationActions(menu, document);
		}
	}

	protected boolean canContributeChildActions(Node node){
		return true;
	}

	protected void contributeAddChildActions(IMenuManager menu, Node node, int ic, int vc) {
		int nodeType = node.getNodeType();

		if (nodeType == Node.ELEMENT_NODE && canContributeChildActions(node)) {
			// 'Add Child...' and 'Add Attribute...' actions
			//
			Element element = (Element) node;

			IMenuManager addAttributeMenu = new MyMenuManager(XMLUIMessages._UI_MENU_ADD_ATTRIBUTE);
			IMenuManager addChildMenu = new MyMenuManager(XMLUIMessages._UI_MENU_ADD_CHILD);
			menu.add(addAttributeMenu);
			menu.add(addChildMenu);
		
			CMElementDeclaration ed = modelQuery.getCMElementDeclaration(element);
			if (ed != null) {
				// add insert attribute actions
				//
				List modelQueryActionList = new ArrayList();
				modelQuery.getInsertActions(element, ed, -1, ModelQuery.INCLUDE_ATTRIBUTES, vc, modelQueryActionList);
				addActionHelper(addAttributeMenu, modelQueryActionList);
				// add insert child node actions
				//
				modelQueryActionList = new ArrayList();
				modelQuery.getInsertActions(element, ed, -1, ic, vc, modelQueryActionList);
				addActionHelper(addChildMenu, modelQueryActionList);
			}

			// add PI and COMMENT
			contributePIAndCommentActions(addChildMenu, element, ed, -1);

			// add PCDATA, CDATA_SECTION
			contributeTextNodeActions(addChildMenu, element, ed, -1);

			// add NEW ELEMENT
			contributeUnconstrainedAddElementAction(addChildMenu, element, ed, -1);

			// add ATTRIBUTE
			contributeUnconstrainedAttributeActions(addAttributeMenu, element, ed);
		}
	}


	protected void contributeAddDocumentChildActions(IMenuManager menu, Document document, int ic, int vc) {
		IMenuManager addChildMenu = new MyMenuManager(XMLUIMessages._UI_MENU_ADD_CHILD);
		menu.add(addChildMenu);

		// add PI and COMMENT
		contributePIAndCommentActions(addChildMenu, document, -1);

		// add NEW ELEMENT
		contributeUnconstrainedAddElementAction(addChildMenu, document, -1);
	}


	protected void contributeAddSiblingActions(IMenuManager menu, Node node, int ic, int vc) {
		IMenuManager addBeforeMenu = new MyMenuManager(XMLUIMessages._UI_MENU_ADD_BEFORE);
		IMenuManager addAfterMenu = new MyMenuManager(XMLUIMessages._UI_MENU_ADD_AFTER);
		menu.add(addBeforeMenu);
		menu.add(addAfterMenu);

		Node parentNode = node.getParentNode();
		if (parentNode != null) {
			int index = getIndex(parentNode, node);
			if (parentNode.getNodeType() == Node.ELEMENT_NODE) {
				Element parentElement = (Element) parentNode;
				CMElementDeclaration parentED = modelQuery.getCMElementDeclaration(parentElement);
				if (parentED != null) {
					// 'Add Before...' and 'Add After...' actions
					//
					List modelQueryActionList = new ArrayList();
					modelQuery.getInsertActions(parentElement, parentED, index, ic, vc, modelQueryActionList);
					addActionHelper(addBeforeMenu, modelQueryActionList);

					modelQueryActionList = new ArrayList();
					modelQuery.getInsertActions(parentElement, parentED, index + 1, ic, vc, modelQueryActionList);
					addActionHelper(addAfterMenu, modelQueryActionList);
				}

				// add COMMENT and PI before and after
				contributePIAndCommentActions(addBeforeMenu, parentElement, parentED, index);
				contributePIAndCommentActions(addAfterMenu, parentElement, parentED, index + 1);

				// add PCDATA, CDATA_SECTION before and after
				contributeTextNodeActions(addBeforeMenu, parentElement, parentED, index);
				contributeTextNodeActions(addAfterMenu, parentElement, parentED, index + 1);

				// add NEW ELEMENT before and after
				contributeUnconstrainedAddElementAction(addBeforeMenu, parentElement, parentED, index);
				contributeUnconstrainedAddElementAction(addAfterMenu, parentElement, parentED, index + 1);
			}
			else if (parentNode.getNodeType() == Node.DOCUMENT_NODE) {
				Document document = (Document) parentNode;
				CMDocument cmDocument = modelQuery.getCorrespondingCMDocument(parentNode);
				if (cmDocument != null) {
					// add possible root element insertions
					//        
					List modelQueryActionList = new ArrayList();
					modelQuery.getInsertActions(document, cmDocument, index, ic, vc, modelQueryActionList);
					addActionHelper(addAfterMenu, modelQueryActionList);

					modelQueryActionList = new ArrayList();
					modelQuery.getInsertActions(document, cmDocument, index + 1, ic, vc, modelQueryActionList);
					addActionHelper(addAfterMenu, modelQueryActionList);
				}

				// add COMMENT and PI before and after
				contributePIAndCommentActions(addBeforeMenu, document, index);
				contributePIAndCommentActions(addAfterMenu, document, index + 1);

				// add ELEMENT before and after
				contributeUnconstrainedAddElementAction(addBeforeMenu, document, index);
				contributeUnconstrainedAddElementAction(addAfterMenu, document, index + 1);
			}
		}
	}

	protected void contributeDeleteActions(IMenuManager menu, List list, int ic, int vc) {
		boolean canRemove = modelQuery.canRemove(list, vc);


		// a delete action with an empty list will produce a disabled menu
		// item
		//
		List resultList = canRemove ? list : Collections.EMPTY_LIST;
		contributeAction(menu, createDeleteAction(resultList));
	}


	protected void contributeEditActions(IMenuManager menu, Node node) {
		contributeEditGrammarInformationActions(menu, node);

		if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) {
			contributeAction(menu, createEditProcessingInstructionAction((ProcessingInstruction) node));
		}
		else if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
			contributeAction(menu, createEditAttributeAction((Attr) node, null));
		}
	}


	protected void contributeEditGrammarInformationActions(IMenuManager menu, Node node) {
		Document document = node.getNodeType() == Node.DOCUMENT_NODE ? (Document) node : node.getOwnerDocument();

		DocumentType doctype = getDoctype(node);
		if (doctype == null) {
			contributeAction(menu, createAddDoctypeAction(document, -1));
		}

		if (node.getNodeType() == Node.DOCUMENT_TYPE_NODE) {
			contributeAction(menu, createEditDoctypeAction((DocumentType) node));
		}

		if ((doctype == null) && (getRootElement(document) != null)) {
			contributeAction(menu, createEditSchemaInfoAction(getRootElement(document)));
		}
	}

	protected void contributePIAndCommentActions(IMenuManager menu, Document document, int index) {
		// test to make sure that the index isn't before the XML declaration
		// 
		contributeAction(menu, createAddCommentAction(document, index));
		contributeAction(menu, createAddProcessingInstructionAction(document, index));
	}


	protected void contributePIAndCommentActions(IMenuManager menu, Element parentElement, CMElementDeclaration parentEd, int index) {
		if ((parentEd == null) || isCommentAllowed(parentEd)) {
			contributeAction(menu, createAddCommentAction(parentElement, index));
			contributeAction(menu, createAddProcessingInstructionAction(parentElement, index));
		}
	}


	protected void contributeReplaceActions(IMenuManager menu, List selectedNodeList, int ic, int vc) {
		// 'Replace With...' actions
		//                                                                                                                   
		IMenuManager replaceWithMenu = new MyMenuManager(XMLUIMessages._UI_MENU_REPLACE_WITH);
		menu.add(replaceWithMenu);

		if ((modelQuery.getEditMode() == ModelQuery.EDIT_MODE_CONSTRAINED_STRICT) && (selectedNodeList.size() > 0)) {
			Node node = (Node) selectedNodeList.get(0);
			Node parentNode = node.getParentNode();
			if ((parentNode != null) && (parentNode.getNodeType() == Node.ELEMENT_NODE)) {
				Element parentElement = (Element) parentNode;
				CMElementDeclaration parentED = modelQuery.getCMElementDeclaration(parentElement);
				if (parentED != null) {
					List replaceActionList = new Vector();
					modelQuery.getReplaceActions(parentElement, parentED, selectedNodeList, ic, vc, replaceActionList);
					addActionHelper(replaceWithMenu, replaceActionList);
				}
			}
		}
	}

	protected void contributeTextNodeActions(IMenuManager menu, Element parentElement, CMElementDeclaration parentEd, int index) {
		if ((parentEd == null) || isTextAllowed(parentEd)) {
			CMDataType dataType = parentEd != null ? parentEd.getDataType() : null;
			contributeAction(menu, createAddPCDataAction(parentElement, dataType, index));
			contributeAction(menu, createAddCDataSectionAction(parentElement, index));
		}
	}


	protected void contributeUnconstrainedAddElementAction(IMenuManager menu, Document document, int index) {
		if (isUnconstrainedActionAllowed()) {
			if (getRootElement(document) == null) {
				int xmlDeclarationIndex = -1;
				int doctypeIndex = -1;
				NodeList nodeList = document.getChildNodes();
				int nodeListLength = nodeList.getLength();
				for (int i = 0; i < nodeListLength; i++) {
					Node node = nodeList.item(i);
					int nodeType = node.getNodeType();
					if (nodeType == Node.DOCUMENT_TYPE_NODE) {
						doctypeIndex = i;
						break;
					}
					else if (nodeType == Node.PROCESSING_INSTRUCTION_NODE) {
						ProcessingInstruction pi = (ProcessingInstruction) node;
						if (pi.getTarget().equalsIgnoreCase("xml") && (xmlDeclarationIndex == -1)) { //$NON-NLS-1$
							xmlDeclarationIndex = i;
						}
					}
				}

				if (((xmlDeclarationIndex == -1) || (index > xmlDeclarationIndex)) && ((doctypeIndex == -1) || (index > doctypeIndex))) {
					contributeAction(menu, createAddElementAction(document, null, index));
				}
			}
		}
	}


	protected void contributeUnconstrainedAddElementAction(IMenuManager menu, Element parentElement, CMElementDeclaration parentEd, int index) {
		if (isUnconstrainedActionAllowed()) {
			if ((parentEd == null) || (parentEd.getProperty("isInferred") == Boolean.TRUE) || ((modelQuery.getEditMode() != ModelQuery.EDIT_MODE_CONSTRAINED_STRICT) && isElementAllowed(parentEd))) { //$NON-NLS-1$
				contributeAction(menu, createAddElementAction(parentElement, null, index));
			}
		}
	}


	protected void contributeUnconstrainedAttributeActions(IMenuManager menu, Element parentElement, CMElementDeclaration parentEd) {
		if (isUnconstrainedActionAllowed()) {
			if ((parentEd == null) || (parentEd.getProperty("isInferred") == Boolean.TRUE) || (modelQuery.getEditMode() != ModelQuery.EDIT_MODE_CONSTRAINED_STRICT)) { //$NON-NLS-1$
				contributeAction(menu, createAddAttributeAction(parentElement, null));
			}
		}
	}

	abstract protected Action createAddAttributeAction(Element parent, CMAttributeDeclaration ad);

	abstract protected Action createAddCDataSectionAction(Node parent, int index);

	abstract protected Action createAddCommentAction(Node parent, int index);

	abstract protected Action createAddDoctypeAction(Document parent, int index);

	abstract protected Action createAddElementAction(Node parent, CMElementDeclaration ed, int index);

	abstract protected Action createAddPCDataAction(Node parent, CMDataType dataType, int index);

	abstract protected Action createAddProcessingInstructionAction(Node parent, int index);

	abstract protected Action createAddSchemaInfoAction(Element element);

	abstract protected Action createDeleteAction(List selection);

	abstract protected Action createEditAttributeAction(Attr attribute, CMAttributeDeclaration ad);

	abstract protected Action createEditDoctypeAction(DocumentType doctype);

	abstract protected Action createEditProcessingInstructionAction(ProcessingInstruction pi);

	abstract protected Action createEditSchemaInfoAction(Element element);

	abstract protected Action createRenameAction(Node node);

	abstract protected Action createReplaceAction(Node parent, CMNode cmnode, int startIndex, int endIndex);


	public 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;
	}


	public Node getRefChildNodeAtIndex(Node parent, int index) {
		NodeList nodeList = parent.getChildNodes();
		Node refChild = ((index >= 0) && (index < nodeList.getLength())) ? nodeList.item(index) : null;
		return refChild;
	}


	protected Element getRootElement(Document document) {
		Element result = null;
		NodeList nodeList = document.getChildNodes();
		int nodeListLength = nodeList.getLength();
		for (int i = 0; i < nodeListLength; i++) {
			Node node = nodeList.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				result = (Element) node;
				break;
			}
		}
		return result;
	}


	protected List getSelectedNodes(List list, boolean includeTextNodes) {
		List result = new ArrayList(0);
		for (Iterator i = list.iterator(); i.hasNext();) {
			Object object = i.next();
			if (object instanceof Node) {
				Node node = (Node) object;
				if (node.getNodeType() == Node.TEXT_NODE) {
					if (includeTextNodes) {
						result.add(object);
					}
				}
				else {
					result.add(node);
				}
			}
		}
		return result;
	}


	protected boolean isCommentAllowed(CMElementDeclaration parentEd) {
		int contentType = parentEd.getContentType();
		return (contentType == CMElementDeclaration.ELEMENT) || (contentType == CMElementDeclaration.MIXED) || (contentType == CMElementDeclaration.PCDATA) || (contentType == CMElementDeclaration.ANY);
	}


	protected boolean isElementAllowed(CMElementDeclaration parentEd) {
		int contentType = parentEd.getContentType();
		return (contentType == CMElementDeclaration.ELEMENT) || (contentType == CMElementDeclaration.MIXED) || (contentType == CMElementDeclaration.ANY);
	}


	protected boolean isTextAllowed(CMElementDeclaration parentEd) {
		int contentType = parentEd.getContentType();
		return (contentType == CMElementDeclaration.MIXED) || (contentType == CMElementDeclaration.PCDATA) || (contentType == CMElementDeclaration.ANY);
	}


	protected boolean isUnconstrainedActionAllowed() {
		return true;
	}


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