/*******************************************************************************
 * Copyright (c) 2010, 2013 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
 *******************************************************************************/
package org.eclipse.wst.xml.ui.internal.views.contentmodel;

import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.List;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.plugin.AbstractUIPlugin;
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.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.util.CMVisitor;
import org.eclipse.wst.xml.ui.internal.XMLUIMessages;
import org.eclipse.wst.xml.ui.internal.XMLUIPlugin;
import org.eclipse.wst.xml.ui.internal.editor.XMLEditorPluginImages;

public class CMListWorkbenchAdapter extends CMVisitor implements IWorkbenchAdapter {

	private CMElementDeclaration fDeclaration;
	private String text;
	private ImageDescriptor image;
	private GroupStack fGroupStack;
	private CMDescriber fRoot;

	private static final Object[] EMPTY = new Object[0];

	public CMListWorkbenchAdapter(CMElementDeclaration decl) {
		if (decl != null) {
			text = getFormattedLabel(decl.getNodeName(), decl.getMinOccur(), decl.getMaxOccur(), decl.getDataType());
			fDeclaration = decl;
			image = AbstractUIPlugin.imageDescriptorFromPlugin(XMLUIPlugin.ID, XMLEditorPluginImages.IMG_OBJ_ELEMENT);

			fGroupStack = new GroupStack();
			fRoot = new CMDescriber(decl);
		}
		else {
			text = XMLUIMessages.ContentModel_ElementNotDescribed;
			image = AbstractUIPlugin.imageDescriptorFromPlugin(XMLUIPlugin.ID, XMLEditorPluginImages.IMG_OBJ_WARNING_OBJ);
		}
	}

	public Object[] getChildren(Object o) {
		/* TODO Use the CMElementDeclaration and ModelQuery to get the available children of the root element */
		return fRoot != null ? fRoot.getChildren(o) : EMPTY;
	}

	public ImageDescriptor getImageDescriptor(Object object) {
		return image;
	}

	public String getLabel(Object o) {
		return text;
	}

	public Object getParent(Object o) {
		return fDeclaration;
	}

	/**
	 * Formats the label for an element declaration. The format will be: <b>name</b> [<i>min</i>..<i>max</i>], where <i>max</i> when unbounded is represented by <b>*</b>
	 * @param name The name of the element declaration
	 * @param min The minimum number of occurrences of the element
	 * @param max The maximum number of occurrences of the element
	 * @return The formatted label String
	 */
	static String getFormattedLabel(String name, int min, int max, CMDataType dataType) {
		StringBuffer buffer = new StringBuffer(name);
		buffer.append('[');
		buffer.append(min);
		buffer.append(".."); //$NON-NLS-1$
		buffer.append((max >= 0 ? Integer.toString(max) : "*")); //$NON-NLS-1$
		buffer.append(']');
		if (dataType != null) {
			buffer.append(" {"); //$NON-NLS-1$
			buffer.append(dataType.getDataTypeName());
			buffer.append('}');
		}
		return buffer.toString();
	}

	/**
	 * Workbench adapter that describes its CMNode. It calculates its children lazily using the CMVisitor
	 * to identify immediate children (i.e., the content of child nodes is not visited).
	 * 
	 */
	private class CMDescriber extends CMVisitor implements IWorkbenchAdapter {

		List fChildren = null;
		ImageDescriptor fImage;
		String label;
		CMNode root;

		public CMDescriber(CMNode node) {
			root = node;
		}

		public Object[] getChildren(Object o) {
			if (fChildren == null) {
				fChildren = new ArrayList();
				if (root.getNodeType() == CMNode.ELEMENT_DECLARATION) {
					CMElementDeclaration ed = (CMElementDeclaration) root;
					CMNamedNodeMap nodeMap = ed.getAttributes();
					int size = nodeMap.getLength();
					for (int i = 0; i < size; i++)	{
						visitCMNode(nodeMap.item(i));
					}
					visitCMNode(ed.getContent());
				}
			}
			return fChildren.toArray();
		}

		public ImageDescriptor getImageDescriptor(Object object) {
			return fImage;
		}

		public void setImage(String path) {
			fImage = AbstractUIPlugin.imageDescriptorFromPlugin(XMLUIPlugin.ID, path);
		}
		public String getLabel(Object o) {
			return label;
		}

		public Object getParent(Object o) {
			return root;
		}

		public void visitCMGroup(CMGroup e) { 
			fGroupStack.push(e.getMaxOccur());
			super.visitCMGroup(e);
			fGroupStack.pop();
		}

		public void visitCMAttributeDeclaration(CMAttributeDeclaration ad) {
			CMDescriber describer = new CMDescriber(ad);
			describer.label = ad.getNodeName();
			describer.setImage(XMLEditorPluginImages.IMG_OBJ_ATTRIBUTE);
			fChildren.add(describer);
		}

		public void visitCMElementDeclaration(CMElementDeclaration ed) {
			CMDescriber describer = new CMDescriber(ed);

			// If the parent group stack containing this element declaration is unbounded, the element within is unbounded as well
			describer.label = getFormattedLabel(ed.getNodeName(), ed.getMinOccur(), (!fGroupStack.isEmpty() && fGroupStack.peek() < 0) ? -1 : ed.getMaxOccur(), ed.getDataType());
			describer.setImage(XMLEditorPluginImages.IMG_OBJ_ELEMENT);
			fChildren.add(describer);
		}
	}

	/**
	 * A stack of integers used to determine if an element declaration is unbounded
	 *
	 */
	private static class GroupStack {
		int[] stack;
		int size;

		public GroupStack() {
			stack = new int[5];
		}

		public void push(int i) {
			if (size >= stack.length) {
				int[] tmp = stack;
				stack = new int[stack.length * 2];
				System.arraycopy(tmp, 0, stack, 0, tmp.length);
			}
			stack[size++] = i;
		}

		public int pop() {
			if (isEmpty())
				throw new EmptyStackException();
			return stack[--size];
		}

		public boolean isEmpty() {
			return size == 0;
		}

		public int peek() {
			if (isEmpty())
				throw new EmptyStackException();
			return stack[size - 1];
		}
	}
}
