/*******************************************************************************
 * Copyright (c) 2005, 2018 IBM Corporation and others.
 *
 * 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
 *     Alexander Kurtakov <akurtako@redhat.com> - bug 415649
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 487943
 *     Martin Karpisek <martin.karpisek@gmail.com> - Bug 351356
 *******************************************************************************/
package org.eclipse.pde.internal.ui.editor.schema;

import java.util.Arrays;
import java.util.Iterator;

import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.IModelChangedEvent;
import org.eclipse.pde.internal.core.ICoreConstants;
import org.eclipse.pde.internal.core.ischema.ISchema;
import org.eclipse.pde.internal.core.ischema.ISchemaAttribute;
import org.eclipse.pde.internal.core.ischema.ISchemaComplexType;
import org.eclipse.pde.internal.core.ischema.ISchemaCompositor;
import org.eclipse.pde.internal.core.ischema.ISchemaElement;
import org.eclipse.pde.internal.core.ischema.ISchemaInclude;
import org.eclipse.pde.internal.core.ischema.ISchemaObject;
import org.eclipse.pde.internal.core.ischema.ISchemaObjectReference;
import org.eclipse.pde.internal.core.ischema.ISchemaRootElement;
import org.eclipse.pde.internal.core.ischema.ISchemaSimpleType;
import org.eclipse.pde.internal.core.ischema.ISchemaType;
import org.eclipse.pde.internal.core.schema.Schema;
import org.eclipse.pde.internal.core.schema.SchemaAttribute;
import org.eclipse.pde.internal.core.schema.SchemaCompositor;
import org.eclipse.pde.internal.core.schema.SchemaElement;
import org.eclipse.pde.internal.core.schema.SchemaElementReference;
import org.eclipse.pde.internal.core.schema.SchemaRootElement;
import org.eclipse.pde.internal.ui.PDEPlugin;
import org.eclipse.pde.internal.ui.PDEUIMessages;
import org.eclipse.pde.internal.ui.editor.ModelDataTransfer;
import org.eclipse.pde.internal.ui.editor.PDEFormPage;
import org.eclipse.pde.internal.ui.editor.TreeSection;
import org.eclipse.pde.internal.ui.editor.actions.CollapseAction;
import org.eclipse.pde.internal.ui.parts.TreePart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;

public class ElementSection extends TreeSection {
	private TreeViewer fTreeViewer;
	private Schema fSchema;
	private final NewElementAction fNewElementAction = new NewElementAction();
	private final NewAttributeAction fNewAttributeAction = new NewAttributeAction();
	private Clipboard fClipboard;
	private SchemaRearranger fRearranger;
	private CollapseAction fCollapseAction;

	static class ContentProvider implements ITreeContentProvider {
		@Override
		public Object[] getElements(Object object) {
			if (object instanceof Schema schema) {
				return schema.getElements();
			}
			return new Object[0];
		}

		@Override
		public Object[] getChildren(Object parent) {
			Object[] children = new Object[0];
			if (parent instanceof ISchemaElement) {
				Object[] types = new Object[0];
				Object[] attributes = ((ISchemaElement) parent).getAttributes();
				ISchemaType type = ((ISchemaElement) parent).getType();
				if (type instanceof ISchemaComplexType) {
					Object compositor = ((ISchemaComplexType) type).getCompositor();
					if (compositor != null)
						types = new Object[] {compositor};
				}
				children = new Object[types.length + attributes.length];
				System.arraycopy(types, 0, children, 0, types.length);
				System.arraycopy(attributes, 0, children, types.length, attributes.length);
			} else if (parent instanceof ISchemaCompositor) {
				children = ((ISchemaCompositor) parent).getChildren();
			}
			return children;
		}

		@Override
		public Object getParent(Object child) {
			if (child instanceof ISchemaObject)
				return ((ISchemaObject) child).getParent();
			return null;
		}

		@Override
		public boolean hasChildren(Object parent) {
			if (parent instanceof ISchemaAttribute || parent instanceof ISchemaObjectReference)
				return false;
			return getChildren(parent).length > 0;
		}
	}

	public ElementSection(PDEFormPage page, Composite parent) {
		super(page, parent, Section.DESCRIPTION, new String[] {PDEUIMessages.SchemaEditor_ElementSection_newElement, PDEUIMessages.SchemaEditor_ElementSection_newAttribute, PDEUIMessages.SchemaEditor_ElementSection_newChoice, PDEUIMessages.SchemaEditor_ElementSection_newSequence, PDEUIMessages.SchemaEditor_ElementSection_remove});
		getSection().setText(PDEUIMessages.SchemaEditor_ElementSection_title);
		getSection().setDescription(PDEUIMessages.SchemaEditor_ElementSection_desc);
	}

	@Override
	public void createClient(Section section, FormToolkit toolkit) {
		Composite container = createClientContainer(section, 2, toolkit);
		createTree(container, toolkit);
		toolkit.paintBordersFor(container);
		section.setClient(container);
		initialize();
		createSectionToolbar(section, toolkit);
	}

	private void createSectionToolbar(Section section, FormToolkit toolkit) {

		ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
		ToolBar toolbar = toolBarManager.createControl(section);
		final Cursor handCursor = Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND);
		toolbar.setCursor(handCursor);
		// Add collapse action to the tool bar
		fCollapseAction = new CollapseAction(fTreeViewer, PDEUIMessages.ExtensionsPage_collapseAll);
		toolBarManager.add(fCollapseAction);

		toolBarManager.update(true);

		section.setTextClient(toolbar);
	}

	private void createTree(Composite container, FormToolkit toolkit) {
		TreePart treePart = getTreePart();
		createViewerPartControl(container, SWT.MULTI, 2, toolkit);
		fTreeViewer = treePart.getTreeViewer();
		fTreeViewer.setContentProvider(new ContentProvider());
		fTreeViewer.setLabelProvider(PDEPlugin.getDefault().getLabelProvider());
		PDEPlugin.getDefault().getLabelProvider().connect(this);
		initDragAndDrop();
	}

	protected void initDragAndDrop() {
		fClipboard = new Clipboard(fTreeViewer.getControl().getDisplay());
		int ops = DND.DROP_COPY | DND.DROP_MOVE | DND.DROP_LINK;
		Transfer[] transfers = new Transfer[] {ModelDataTransfer.getInstance(), TextTransfer.getInstance()};
		ElementSectionDragAdapter dragAdapter = new ElementSectionDragAdapter(fTreeViewer);
		fTreeViewer.addDragSupport(ops, transfers, dragAdapter);
		fTreeViewer.addDropSupport(ops | DND.DROP_DEFAULT, transfers, new ElementSectionDropAdapter(dragAdapter, this));
	}

	protected TreeViewer getTreeViewer() {
		return fTreeViewer;
	}

	@Override
	public void refresh() {
		fTreeViewer.refresh();
		super.refresh();

		if (fTreeViewer.getSelection().isEmpty() && fSchema.getElementCount() > 0) {
			fTreeViewer.setSelection(new StructuredSelection(fSchema.getElements()[0]));
		}
	}

	@Override
	protected void buttonSelected(int index) {
		switch (index) {
			case 0 :
				handleNewElement();
				break;
			case 1 :
				handleNewAttribute();
				break;
			case 2 :
				addCompositor(ISchemaCompositor.CHOICE);
				break;
			case 3 :
				addCompositor(ISchemaCompositor.SEQUENCE);
				break;
			case 4 :
			final IStructuredSelection selection = fTreeViewer.getStructuredSelection();
				handleDelete(selection);
				break;
		}
	}

	private void addCompositor(int kind) {
		Object selection = fTreeViewer.getStructuredSelection().getFirstElement();
		ISchemaElement sourceElement = null;
		Object current = selection;
		while (current instanceof ISchemaCompositor)
			current = ((ISchemaCompositor) current).getParent();
		if (current instanceof ISchemaElement)
			sourceElement = (ISchemaElement) current;
		if (sourceElement != null)
			new NewCompositorAction(sourceElement, selection, kind).run();
	}

	@Override
	public void dispose() {
		if (fClipboard != null) {
			fClipboard.dispose();
			fClipboard = null;
		}
		PDEPlugin.getDefault().getLabelProvider().disconnect(this);
		super.dispose();
	}

	@Override
	public boolean doGlobalAction(String actionId) {
		boolean cut = actionId.equals(ActionFactory.CUT.getId());
		if (cut || actionId.equals(ActionFactory.DELETE.getId())) {
			// Get the current selection
			IStructuredSelection sel = fTreeViewer.getStructuredSelection();
			// Get the first selected object
			Object selectedObject = sel.getFirstElement();
			// Ensure we have a selection
			if (selectedObject == null) {
				return true;
			}
			handleDelete(sel);
			// if cutting delete here and let the editor transfer
			// the selection to the clipboard
			return !cut;
		}
		if (actionId.equals(ActionFactory.PASTE.getId())) {
			doPaste();
			return true;
		}
		return super.doGlobalAction(actionId);
	}

	@Override
	public boolean setFormInput(Object object) {
		if (object instanceof ISchemaElement || object instanceof ISchemaAttribute || object instanceof ISchemaCompositor) {
			fTreeViewer.setSelection(new StructuredSelection(object), true);

			ISelection selection = fTreeViewer.getSelection();
			if (selection != null && !selection.isEmpty())
				return true;
			if (object instanceof ISchemaElement) {
				ISchemaElement found = fSchema.findElement(((ISchemaElement) object).getName());
				if (found != null)
					fTreeViewer.setSelection(new StructuredSelection(found), true);
				return found != null;
			}
		}
		return false;
	}

	@Override
	protected void fillContextMenu(IMenuManager manager) {
		final IStructuredSelection selection = fTreeViewer.getStructuredSelection();
		final Object object = selection.getFirstElement();

		MenuManager submenu = new MenuManager(PDEUIMessages.Menus_new_label);
		if (object == null) {
			fNewElementAction.setSchema(fSchema);
			fNewElementAction.setEnabled(fSchema.isEditable());
			submenu.add(fNewElementAction);
		}
		if (object != null) {
			ISchemaElement element = null;
			if (object instanceof SchemaElement)
				element = (SchemaElement) object;
			else if (object instanceof SchemaAttribute)
				element = (SchemaElement) ((SchemaAttribute) object).getParent();

			if (element != null && !(element instanceof ISchemaRootElement) && !(element instanceof ISchemaObjectReference)) {
				fNewAttributeAction.setElement((SchemaElement) element);
				fNewAttributeAction.setEnabled(fSchema.isEditable());
				submenu.add(fNewAttributeAction);
			}
		}
		if (object instanceof SchemaElement || object instanceof SchemaCompositor) {
			ISchemaElement sourceElement = null;
			ISchemaObject schemaObject = (ISchemaObject) object;
			while (schemaObject != null) {
				if (schemaObject instanceof ISchemaElement) {
					sourceElement = (ISchemaElement) schemaObject;
					break;
				}
				schemaObject = schemaObject.getParent();
			}
			if (sourceElement != null) {
				if (object instanceof SchemaCompositor || sourceElement.getType() instanceof ISchemaSimpleType || ((ISchemaComplexType) sourceElement.getType()).getCompositor() == null) {
					if (submenu.getItems().length > 0)
						submenu.add(new Separator());
					submenu.add(new NewCompositorAction(sourceElement, object, ISchemaCompositor.CHOICE));
					submenu.add(new NewCompositorAction(sourceElement, object, ISchemaCompositor.SEQUENCE));
				}
				if (object instanceof SchemaCompositor) {
					boolean seperatorAdded = false;
					ISchemaElement[] elements = sourceElement.getSchema().getResolvedElements();
					Arrays.sort(elements);
					for (int i = 0; i < elements.length; i++) {
						if (!(elements[i] instanceof SchemaRootElement)) {
							if (!seperatorAdded) {
								submenu.add(new Separator());
								seperatorAdded = true;
							}
							submenu.add(new NewReferenceAction(sourceElement, object, elements[i]));
						}
					}
				}
			}
		}
		manager.add(submenu);
		if (object != null) {
			if (!(object instanceof ISchemaRootElement)) {
				if (manager.getItems().length > 0)
					manager.add(new Separator());
				if (!(object instanceof ISchemaAttribute && ((ISchemaAttribute) object).getParent() instanceof ISchemaRootElement)) {
					Action deleteAction = new Action() {
						@Override
						public void run() {
							handleDelete(selection);
						}
					};
					deleteAction.setText(PDEUIMessages.Actions_delete_label);
					deleteAction.setEnabled(fSchema.isEditable());
					manager.add(deleteAction);
				}
			}
		}
		getPage().getPDEEditor().getContributor().contextMenuAboutToShow(manager);
		manager.add(new Separator());
	}

	private void handleDelete(IStructuredSelection selection) {
		IStructuredSelection nextSelection = null;
		Object selectionSource = null;
		for (Iterator<?> iter = selection.iterator(); iter.hasNext();) {
			Object thisObject = iter.next();
			// Do the delete and generate a new selection in one of the following cases:
			//   1. No selection has been generated
			//   2. This object is higher up in the hierarchy than the previous
			//      object used to generate the selection
			//   3. The object selected for deletion is currently set as the next selection
			IStructuredSelection result = handleDelete(thisObject, nextSelection == null || schemaObjectHigherThan(thisObject, selectionSource) || nextSelection.getFirstElement().equals(thisObject));
			if (result != null) {
				nextSelection = result;
				selectionSource = thisObject;
			}
		}
		if (nextSelection != null)
			getTreeViewer().setSelection(nextSelection);
	}

	private IStructuredSelection handleDelete(Object object, boolean generateSelection) {
		IStructuredSelection newSelection = null;
		if (!isEditable()) {
			Display.getCurrent().beep();
		} else if (object instanceof ISchemaRootElement) {
			// Semantic rule: The root "extension" element of a schema
			// cannot be removed

			// Produce audible beep
			Display.getCurrent().beep();
		} else if (object instanceof SchemaElementReference) {
			newSelection = handleReferenceDelete((SchemaElementReference) object, generateSelection);
		} else if (object instanceof ISchemaElement) {
			newSelection = handleElementDelete((ISchemaElement) object, generateSelection);
		} else if (object instanceof ISchemaAttribute att) {
			if (!(att.getParent() instanceof ISchemaRootElement)) {
				newSelection = handleAttributeDelete(att, generateSelection);
			} else {
				// Semantic rule: Attributes of the root "extension" element
				// of a schema cannot be removed

				// Produce audible beep
				Display.getCurrent().beep();
			}
		} else if (object instanceof ISchemaCompositor) {
			newSelection = handleCompositorDelete((ISchemaCompositor) object, generateSelection);
		}
		return newSelection;
	}

	private IStructuredSelection handleReferenceDelete(SchemaElementReference ref, boolean generateSelection) {
		IStructuredSelection newSelection = null;
		if (generateSelection) {
			SchemaCompositor parent = (SchemaCompositor) ref.getParent();
			ISchemaObject[] children = parent.getChildren();
			int index = getNewSelectionIndex(getArrayIndex(children, ref), children.length);
			if (index == -1)
				newSelection = new StructuredSelection(parent);
			else
				newSelection = new StructuredSelection(children[index]);
		}
		fRearranger.deleteReference(ref);
		return newSelection;
	}

	private IStructuredSelection handleElementDelete(ISchemaElement element, boolean generateSelection) {
		IStructuredSelection newSelection = null;
		if (generateSelection) {
			ISchema parent = element.getSchema();
			ISchemaElement[] children = parent.getElements();
			int index = getNewSelectionIndex(getArrayIndex(children, element), children.length);
			if (index != -1)
				newSelection = new StructuredSelection(children[index]);
		}
		fRearranger.deleteElement(element);
		return newSelection;
	}

	private IStructuredSelection handleAttributeDelete(ISchemaAttribute att, boolean generateSelection) {
		IStructuredSelection newSelection = null;
		if (generateSelection) {
			ISchemaElement parent = (ISchemaElement) att.getParent();
			ISchemaAttribute[] children = parent.getAttributes();
			int index = getNewSelectionIndex(getArrayIndex(children, att), children.length);
			if (index == -1) {
				ISchemaType type = parent.getType();
				if (type instanceof ISchemaComplexType) {
					ISchemaCompositor comp = ((ISchemaComplexType) type).getCompositor();
					if (comp != null)
						newSelection = new StructuredSelection(comp);
					else
						newSelection = new StructuredSelection(parent);
				}
			} else
				newSelection = new StructuredSelection(children[index]);
		}
		fRearranger.deleteAttribute(att);
		return newSelection;
	}

	private IStructuredSelection handleCompositorDelete(ISchemaCompositor comp, boolean generateSelection) {
		IStructuredSelection newSelection = null;
		if (generateSelection) {
			ISchemaObject parent = comp.getParent();
			if (parent instanceof ISchemaElement element) {
				ISchemaAttribute[] attributes = element.getAttributes();
				if (attributes.length > 0)
					newSelection = new StructuredSelection(attributes[0]);
				else
					newSelection = new StructuredSelection(element);
			} else {
				ISchemaCompositor parentComp = (ISchemaCompositor) parent;
				ISchemaObject[] children = parentComp.getChildren();
				int index = getNewSelectionIndex(getArrayIndex(children, comp), children.length);
				if (index == -1)
					newSelection = new StructuredSelection(parent);
				else
					newSelection = new StructuredSelection(children[index]);
			}
		}
		fRearranger.deleteCompositor(comp);
		return newSelection;
	}

	// returns true if object a is a SchemaObject higher in the hierarchy than object b
	// returns false if b is higher or they are equal
	private boolean schemaObjectHigherThan(Object a, Object b) {
		if (!(b instanceof ISchemaObject))
			return true;
		if (!(a instanceof ISchemaObject))
			return false;
		return (computeNestLevel((ISchemaObject) a) < computeNestLevel((ISchemaObject) b));
	}

	// determines how deeply nested an ISchemaObject is
	// returns 0 if this is an element, 1 if it's a direct child, etc.
	private int computeNestLevel(ISchemaObject o) {
		int result = 0;
		while ((o instanceof SchemaElementReference) || !(o instanceof ISchemaElement)) {
			o = o.getParent();
			result++;
		}
		return result;
	}

	private void handleNewAttribute() {
		Object object = fTreeViewer.getStructuredSelection().getFirstElement();
		if (object != null) {
			SchemaElement element = null;
			if (object instanceof SchemaElement)
				element = (SchemaElement) object;
			else if (object instanceof SchemaAttribute)
				element = (SchemaElement) ((SchemaAttribute) object).getParent();

			if (element != null && !(element instanceof ISchemaRootElement)) {
				fNewAttributeAction.setElement(element);
				fNewAttributeAction.run();
			}
		}
	}

	private void handleNewElement() {
		fNewElementAction.setSchema(fSchema);
		fNewElementAction.run();
	}

	public void initialize() {
		this.fSchema = (Schema) getPage().getModel();
		fRearranger = new SchemaRearranger(fSchema);
		fTreeViewer.setInput(fSchema);
		boolean isEditable = fSchema.isEditable();
		getTreePart().setButtonEnabled(0, isEditable);
		getTreePart().setButtonEnabled(1, false);
		getTreePart().setButtonEnabled(2, isEditable);
		getTreePart().setButtonEnabled(3, isEditable);
		getTreePart().setButtonEnabled(4, isEditable);
	}

	public void handleModelChanged(IModelChangedEvent e) {
		if (e.getChangedProperty() != null && e.getChangedProperty().equals(ISchemaObject.P_DESCRIPTION))
			return;
		if (e.getChangeType() == IModelChangedEvent.WORLD_CHANGED) {
			handleModelEventWorldChanged(e);
			return;
		}
		Object[] objects = e.getChangedObjects();
		for (Object obj : objects) {
			if (obj instanceof SchemaElementReference) {
				fTreeViewer.refresh(((SchemaElementReference) obj).getCompositor());
				if (e.getChangeType() == IModelChangedEvent.INSERT)
					fTreeViewer.setSelection(new StructuredSelection(obj), true);
			} else if (obj instanceof ISchemaElement || obj instanceof ISchemaAttribute) {
				if (e.getChangeType() == IModelChangedEvent.CHANGE) {
					String changeProp = e.getChangedProperty();
					if (changeProp != null && (changeProp.equals(ISchemaObject.P_NAME) || changeProp.equals(SchemaAttribute.P_KIND)))
						fTreeViewer.update(obj, null);
					Object typeCheck = e.getNewValue();
					if (typeCheck instanceof ISchemaComplexType && changeProp.equals(SchemaElement.P_TYPE) && obj instanceof ISchemaElement) {
						fTreeViewer.refresh(typeCheck);
						fTreeViewer.setSelection(new StructuredSelection(typeCheck), true);
					} else {
						fTreeViewer.refresh(obj);
					}
				} else if (e.getChangeType() == IModelChangedEvent.INSERT) {
					ISchemaObject sobj = (ISchemaObject) obj;
					ISchemaObject parent = sobj.getParent();
					fTreeViewer.refresh(parent);
					fTreeViewer.setSelection(new StructuredSelection(obj), true);
				} else if (e.getChangeType() == IModelChangedEvent.REMOVE) {
					fTreeViewer.remove(obj);
					// the new selection is handled by the handleDelete method for cuts and deletes,
					// for moves it is handled by the subsequent insert
				}
			} else if (obj instanceof ISchemaCompositor || obj instanceof ISchemaObjectReference) {
				final ISchemaObject sobj = (ISchemaObject) obj;
				ISchemaObject parent = sobj.getParent();
				if (e.getChangeType() == IModelChangedEvent.CHANGE) {
					fTreeViewer.refresh(sobj);
				} else if (e.getChangeType() == IModelChangedEvent.INSERT) {
					fTreeViewer.add(parent, sobj);
					fTreeViewer.getTree().getDisplay().asyncExec(() -> fTreeViewer.setSelection(new StructuredSelection(sobj), true));
				} else if (e.getChangeType() == IModelChangedEvent.REMOVE) {
					fTreeViewer.remove(sobj);
					// the new selection is handled by the handleDelete method for cuts and deletes,
					// for moves it is handled by the subsequent insert
				}
			} else if (obj instanceof ISchemaComplexType) {
				// first compositor added/removed
				ISchemaCompositor comp = ((ISchemaComplexType) obj).getCompositor();
				fTreeViewer.refresh(comp);
				if (comp != null)
					fTreeViewer.refresh(comp.getParent());

				if (e.getChangeType() == IModelChangedEvent.INSERT || e.getChangeType() == IModelChangedEvent.CHANGE) {
					ISchemaComplexType type = (ISchemaComplexType) obj;
					final ISchemaCompositor compositor = type.getCompositor();
					if (compositor != null) {
						fTreeViewer.getTree().getDisplay().asyncExec(() -> fTreeViewer.setSelection(new StructuredSelection(compositor), true));
					}
				}
			} else if (obj instanceof ISchema) {
				if (!ISchemaObject.P_NAME.equals(e.getChangedProperty()))
					fTreeViewer.refresh();
			}
		}
	}

	private void handleModelEventWorldChanged(IModelChangedEvent event) {
		// Note:  Cannot use event.  There are no changed objects within it
		// This method acts like a refresh
		initialize();
		// TODO: MP: REVERT: LOW: Update initialize with this once Bug #171897 is fixed
		ISchemaElement root = fSchema.getSchema().findElement(ICoreConstants.EXTENSION_NAME);
		// Ensure the root element is present
		if (root == null) {
			return;
		}
		// Select the root extension element
		fTreeViewer.setSelection(new StructuredSelection(root), true);
		// Collapse tree to the first level
		fTreeViewer.expandToLevel(1);
	}

	@Override
	protected void selectionChanged(IStructuredSelection selection) {
//		getPage().getManagedForm().fireSelectionChanged(this, selection);
		getPage().getPDEEditor().setSelection(selection);
		updateButtons();
	}

	@Override
	public void setFocus() {
		if (fTreeViewer != null) {
			fTreeViewer.getTree().setFocus();
			getPage().getPDEEditor().setSelection(fTreeViewer.getSelection());
		}
	}

	private void updateButtons() {
		if (!fSchema.isEditable())
			return;
		IStructuredSelection selection = fTreeViewer.getStructuredSelection();
		ISchemaObject sobject = (ISchemaObject) selection.getFirstElement();

		boolean canAddAttribute = false;
		if (sobject instanceof ISchemaElement) {
			if (!(sobject instanceof ISchemaRootElement) && !(sobject instanceof ISchemaObjectReference))
				canAddAttribute = true;
		} else if (sobject instanceof ISchemaAttribute) {
			ISchemaElement element = (ISchemaElement) (sobject.getParent());
			if (!(element instanceof ISchemaRootElement))
				canAddAttribute = true;
		}
		getTreePart().setButtonEnabled(1, canAddAttribute);

		boolean canAddCompositor = false;
		if (sobject instanceof ISchemaCompositor || (sobject instanceof ISchemaElement && !(sobject instanceof SchemaElementReference) && (((ISchemaElement) sobject).getType() instanceof ISchemaSimpleType || ((ISchemaComplexType) ((ISchemaElement) sobject).getType()).getCompositor() == null)))
			canAddCompositor = true;
		getTreePart().setButtonEnabled(2, canAddCompositor);
		getTreePart().setButtonEnabled(3, canAddCompositor);

		boolean canRemove = false;
		for (Iterator<?> iter = selection.iterator(); iter.hasNext();) {
			sobject = (ISchemaObject) iter.next();
			if (sobject != null && !(sobject instanceof ISchemaRootElement) && !(sobject instanceof ISchemaAttribute && sobject.getParent() instanceof ISchemaRootElement)) {
				canRemove = true;
				break;
			}
		}
		getTreePart().setButtonEnabled(4, canRemove);
	}

	private ISchemaObject getSibling(Object target, Object object) {
		if (target instanceof ISchemaElement && object instanceof ISchemaElement)
			return (ISchemaElement) target;
		if (target instanceof ISchemaAttribute && object instanceof ISchemaAttribute)
			return (ISchemaAttribute) target;
		if (target instanceof SchemaElementReference && object instanceof ISchemaElement)
			return (SchemaElementReference) target;
		return null;
	}

	private ISchemaObject getRealTarget(Object target, Object object) {
		if (object instanceof ISchemaElement || object instanceof ISchemaObjectReference) {
			if (target instanceof SchemaElementReference)
				return ((SchemaElementReference) target).getCompositor();
			if (target instanceof ISchemaCompositor)
				return (ISchemaCompositor) target;
			if (object instanceof ISchemaElement)
				return fSchema;
		}
		if (object instanceof ISchemaAttribute) {
			if (target instanceof ISchemaAttribute) {
				// add it to the parent of the selected attribute
				return ((ISchemaAttribute) target).getParent();
			}
			if (target instanceof ISchemaElement)
				return (ISchemaElement) target;
		}
		if (object instanceof ISchemaCompositor) {
			if (target instanceof SchemaElementReference)
				return ((SchemaElementReference) target).getCompositor();
			if (target instanceof ISchemaElement)
				return (ISchemaElement) target;
			if (target instanceof ISchemaCompositor)
				return (ISchemaCompositor) target;
		}
		return null;
	}

	@Override
	protected boolean canPaste(Object target, Object[] objects) {
		for (Object object : objects) {
			Object obj = object;
			if (obj instanceof ISchemaAttribute && target instanceof ISchemaAttribute) {
				continue;
			} else if (obj instanceof ISchemaObjectReference && target instanceof ISchemaCompositor) {
				continue;
			} else if (target instanceof ISchemaElement && !(target instanceof ISchemaObjectReference) && !(obj instanceof ISchemaRootElement)) {
				continue;
			}
			return false;
		}
		return true;
	}

	@Override
	protected void handleDoubleClick(IStructuredSelection selection) {
		super.handleDoubleClick(selection);
		Object object = selection.getFirstElement();
		if (object instanceof SchemaElementReference) {
			ISchemaElement element = ((SchemaElementReference) object).getReferencedElement();
			if (element == null) {
				String name = ((SchemaElementReference) object).getName();
				MessageDialog.openWarning(getPage().getSite().getShell(), PDEUIMessages.ElementSection_missingRefElement, NLS.bind(PDEUIMessages.SchemaIncludesSection_missingWarningMessage, name));
				return;
			}
			ISchema schema = element.getSchema();
			if (schema.equals(fSchema))
				fireSelection(new StructuredSelection(element));
			else {
				ISchemaInclude[] includes = fSchema.getIncludes();
				for (ISchemaInclude include : includes) {
					ISchema includedSchema = include.getIncludedSchema();
					if (includedSchema != null && includedSchema.equals(schema)) {
						String location = include.getLocation();
						SchemaEditor.openToElement(IPath.fromOSString(location), element);
						break;
					}
				}
			}
		}
	}

	protected void fireSelection(ISelection selection) {
		if (selection == null)
			selection = fTreeViewer.getSelection();
		fTreeViewer.setSelection(selection);
	}

	@Override
	protected void doPaste(Object target, Object[] objects) {
		handleOp(target, objects, DND.DROP_COPY);
	}

	public void handleOp(Object currentTarget, Object[] objects, int currentOperation) {
		for (int i = 0; i < objects.length; i++) {
			if (!(objects[i] instanceof ISchemaObject object))
				continue;
			ISchemaObject realTarget = getRealTarget(currentTarget, object);
			ISchemaObject sibling = getSibling(currentTarget, object);
			if (realTarget == null)
				continue;
			switch (currentOperation) {
				case DND.DROP_COPY -> doPaste(realTarget, sibling, object);
				case DND.DROP_MOVE -> doMove(realTarget, sibling, object);
				case DND.DROP_LINK -> doLink(realTarget, sibling, object);
			}
		}
	}

	private void doLink(ISchemaObject realTarget, ISchemaObject sibling, ISchemaObject object) {
		if (realTarget instanceof ISchemaCompositor && object instanceof ISchemaElement) {
			fRearranger.linkReference((ISchemaCompositor) realTarget, (ISchemaElement) object, sibling);
		}
	}

	private void doMove(ISchemaObject realTarget, ISchemaObject sibling, ISchemaObject object) {
		if (object instanceof ISchemaCompositor) {
			fRearranger.moveCompositor(realTarget, (ISchemaCompositor) object);
		} else if (object instanceof SchemaElementReference) {
			fRearranger.moveReference((SchemaElementReference) object, (ISchemaCompositor) realTarget, sibling);
		} else if (object instanceof ISchemaElement) {
			fRearranger.moveElement(realTarget, (ISchemaElement) object, sibling);
		} else if (object instanceof ISchemaAttribute) {
			fRearranger.moveAttribute((ISchemaElement) realTarget, (ISchemaAttribute) object, sibling != null ? (ISchemaAttribute) sibling : null);
		}
	}

	private void doPaste(ISchemaObject realTarget, ISchemaObject sibling, ISchemaObject object) {
		if (object instanceof ISchemaCompositor) {
			fRearranger.pasteCompositor(realTarget, (ISchemaCompositor) object, sibling);
		} else if (object instanceof SchemaElementReference) {
			fRearranger.pasteReference(realTarget, (SchemaElementReference) object, sibling);
		} else if (object instanceof ISchemaElement) {
			fRearranger.pasteElement((ISchemaElement) object, sibling);
		} else if (object instanceof ISchemaAttribute) {
			fRearranger.pasteAttribute((ISchemaElement) realTarget, (ISchemaAttribute) object, sibling);
		}
	}
}
