/*******************************************************************************
 * Copyright (c) 2014 UT-Battelle, LLC.
 * 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:
 *   Initial API and implementation and/or initial documentation - Jay Jay Billings,
 *   Jordan H. Deyton, Dasha Gorin, Alexander J. McCaskey, Taylor Patterson,
 *   Claire Saunders, Matthew Wang, Anna Wojtowicz
 *******************************************************************************/
package org.eclipse.ice.client.widgets;

import java.util.ArrayList;

import org.eclipse.eavp.viz.modeling.FaceController;
import org.eclipse.eavp.viz.modeling.base.BasicController;
import org.eclipse.eavp.viz.modeling.base.IController;
import org.eclipse.eavp.viz.modeling.properties.MeshCategory;
import org.eclipse.eavp.viz.modeling.properties.MeshProperty;
import org.eclipse.eavp.viz.service.mesh.properties.MeshSelection;
import org.eclipse.ice.datastructures.ICEObject.IUpdateable;
import org.eclipse.ice.datastructures.ICEObject.IUpdateableListener;
import org.eclipse.ice.datastructures.form.Form;
import org.eclipse.ice.datastructures.form.MeshComponent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.part.WorkbenchPart;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class extends ViewPart to create a tree of elements (polygons, edges,
 * vertices) in the MeshComponent.
 * 
 * @author Taylor Patterson
 */
public class MeshElementTreeView extends ViewPart
		implements IUpdateableListener, IPartListener2, ISelectionListener,
		ITabbedPropertySheetPageContributor {

	/**
	 * Logger for handling event messages and other information.
	 */
	private static final Logger logger = LoggerFactory
			.getLogger(MeshElementTreeView.class);

	/**
	 * Eclipse view ID
	 */
	public static final String ID = "org.eclipse.ice.client.widgets.MeshElementTreeView";

	/**
	 * The MeshComponent managed by this view.
	 */
	private MeshComponent meshComponent;

	/**
	 * The TreeViewer for mesh elements.
	 */
	private TreeViewer elementTreeViewer;

	/**
	 * The ID of the most recently active item.
	 */
	private int lastFormItemID;

	/**
	 * The constructor.
	 */
	public MeshElementTreeView() {

		// Call the super constructor
		super();

		return;
	}

	/**
	 * This operation sets the MeshComponent that should be used by the
	 * MeshElementTreeView. It also registers the MeshElementTreeView with the
	 * MeshComponent so that it can be notified of state changes through the
	 * IUpdateableListener interface.
	 * 
	 * @param component
	 *            The MeshComponent
	 */
	public void setMeshComponent(MeshComponent component) {

		// Make sure the MeshComponent exists
		if (component != null) {
			// Set the component reference
			meshComponent = component;

			// Register this view with the Component to receive updates
			component.register(this);

			// Update the view
			update(component);
		}

		return;

	}

	/**
	 * This operation retrieves the MeshComponent that has been rendered by this
	 * view or null if the component does not exist.
	 * 
	 * @return The MeshComponent or null if the component was not previously
	 *         set.
	 */
	public MeshComponent getMeshComponent() {

		return meshComponent;

	}

	/**
	 * This operation overrides the ViewPart.createPartControl method to create
	 * and draw the TreeViewer before registering it as a selection provider and
	 * part listener.
	 * 
	 * @param parent
	 *            The Composite containing this view
	 * 
	 * @see WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	public void createPartControl(Composite parent) {

		// Initialize the TreeViewer
		elementTreeViewer = new TreeViewer(parent);

		// Create content and label providers
		initializeTreeViewer(elementTreeViewer);

		// Register this view's TreeViewer as a SelectionProvider
		getSite().setSelectionProvider(elementTreeViewer);

		// Register this view as a part listener
		getSite().getWorkbenchWindow().getPartService().addPartListener(this);

		// Register this view as a SelectionListener to the ICEMeshPage
		getSite().getWorkbenchWindow().getSelectionService()
				.addSelectionListener(ICEMeshPage.ID, this);

		return;
	}

	/**
	 * This operation creates content and label providers for a TreeViewer.
	 * 
	 * @param inputTreeViewer
	 *            The TreeViewer to have the providers added to.
	 */
	private void initializeTreeViewer(TreeViewer inputTreeViewer) {

		// Set the tree's content provider
		inputTreeViewer.setContentProvider(new ITreeContentProvider() {

			@Override
			public void dispose() {
			}

			@Override
			public void inputChanged(Viewer viewer, Object oldInput,
					Object newInput) {
			}

			/**
			 * This function retrieves the elements of the tree.
			 * 
			 * @param inputElement
			 *            The structure containing all of the elements
			 * 
			 * @see ITreeContentProvider#getElements(Object)
			 */
			@Override
			public Object[] getElements(Object inputElement) {

				// Local Declaration
				ArrayList<BasicController> allElements = (ArrayList<BasicController>) inputElement;
				ArrayList<MeshSelection> contents = new ArrayList<MeshSelection>();

				// Wrap the Polygons into PropertySources and add them to
				// the array
				for (BasicController i : allElements) {
					contents.add(new MeshSelection(meshComponent.getMesh(), i));
				}

				return contents.toArray();
			}

			/**
			 * This function retrieves the children of a given tree element.
			 * 
			 * @param parentElement
			 *            The tree element for which children are being
			 *            requested
			 * 
			 * @see ITreeContentProvider#getChildren(Object)
			 */
			@Override
			public Object[] getChildren(Object parentElement) {

				// If the element is a PropertySource
				if (parentElement instanceof MeshSelection) {

					MeshSelection selection = (MeshSelection) parentElement;

					// Load edges and vertices as children of polygons
					ArrayList<MeshSelection> children = new ArrayList<MeshSelection>();

					// An array of every unique vertex from the selection
					ArrayList<IController> vertices = new ArrayList<IController>();

					if (selection.selectedMeshPart instanceof FaceController) {
						FaceController polygon = (FaceController) selection.selectedMeshPart;
						// Add new MeshSelections for the edges.
						for (IController e : polygon
								.getEntitiesFromCategory(MeshCategory.EDGES)) {
							children.add(new MeshSelection(
									meshComponent.getMesh(), e));

							// Add each of the edge's vertices to the list if
							// they are nto already present
							for (IController v : e.getEntitiesFromCategory(
									MeshCategory.VERTICES)) {
								if (!vertices.contains(v)) {
									vertices.add(v);
								}
							}
						}

						// Add new MeshSelections for the vertices.
						for (IController v : vertices) {
							children.add(new MeshSelection(
									meshComponent.getMesh(), v));
						}
					}

					return children.toArray();
				}

				return null;
			}

			@Override
			public Object getParent(Object element) {
				return null;
			}

			/**
			 * This function checks whether or not the tree element has any
			 * children. This should only return true for Polygons.
			 * 
			 * @param element
			 *            The tree element to investigate
			 * 
			 * @return True if this is of type Polygon. False otherwise.
			 * 
			 * @see ITreeContentProvider#hasChildren(Object)
			 */
			@Override
			public boolean hasChildren(Object element) {

				// Only selected Polygons will have children.
				return (element instanceof MeshSelection
						&& ((MeshSelection) element).selectedMeshPart instanceof FaceController);
			}
		});

		// Add a label provider to properly label the mesh elements
		inputTreeViewer.setLabelProvider(new LabelProvider() {

			/**
			 * The String to contain the text of the label.
			 */
			String label = "";

			/**
			 * A function to create text labels for elements in the
			 * MeshElementTreeView.
			 * 
			 * @param element
			 *            The tree element to be labeled
			 * 
			 * @return The String to serve as the tree element label
			 * 
			 * @see LabelProvider#getText(Object)
			 */
			@Override
			public String getText(Object element) {

				// Only set a label if it is a PropertySource. Otherwise we
				// shouldn't need it.... I think.
				if (element instanceof MeshSelection) {

					// Get the wrapped IMeshPart.
					IController meshPart = ((MeshSelection) element).selectedMeshPart;

					// Cast the IMeshPart to an ICEObject and set the label text
					// from its name and ID.
					label = meshPart.getProperty(MeshProperty.NAME) + " "
							+ meshPart.getProperty(MeshProperty.ID);

					return label;
				}
				return element.toString();
			}
		});

		return;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see WorkbenchPart#setFocus()
	 */
	@Override
	public void setFocus() {
		return;
	}

	/**
	 * Update elementTreeViewer when new elements are added to the mesh.
	 * 
	 * @param component
	 * 
	 * @see IUpdateableListener#update(IUpdateable)
	 */
	@Override
	public void update(IUpdateable component) {

		// If the update came from the mesh component, redraw the mesh
		if (component == meshComponent) {

			logger.info("ICEMeshPage Message: " + "Mesh changed!");

			// Sync with the display
			PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
				@Override
				public void run() { // Just do a blanket update - no need to
									// check
									// the component
					if (elementTreeViewer != null) {
						logger.info("MeshElementTreeView Message: "
								+ "Updating element view.");

						// Set the tree content
						if (meshComponent != null) {
							elementTreeViewer
									.setInput(meshComponent.getPolygons());
						}

						// Refresh the view
						elementTreeViewer.refresh();
						elementTreeViewer.getTree().redraw();
					}
				}
			});
		}

		return;
	}

	/**
	 * This function is called whenever a Workbench part is closed. Here, we are
	 * only interested if the part is an ICEFormEditor. Get the Form from the
	 * editor. Clear the elements from the tree view.
	 * 
	 * @param partRef
	 *            The workbench part calling this function.
	 * 
	 * @see IPartListener2#partClosed(IWorkbenchPartReference)
	 */
	@Override
	public void partClosed(IWorkbenchPartReference partRef) {

		logger.info("MeshElementTreeView Message: Called partClosed("
				+ partRef.getId() + ")");

		if (partRef.getId().equals(ICEFormEditor.ID)) {
			// Get the closed editor
			ICEFormEditor activeEditor = (ICEFormEditor) partRef.getPart(false);
			// Get the form from the editor
			Form activeForm = ((ICEFormInput) activeEditor.getEditorInput())
					.getForm();
			// If this is the most recently active form, clear the TreeViewer.
			if (activeForm.getItemID() == lastFormItemID) {
				elementTreeViewer.setInput(null);
				elementTreeViewer.refresh();
				elementTreeViewer.getTree().redraw();
			}
		}

		return;
	}

	/**
	 * This function is called whenever a Workbench part gains focus. Here, we
	 * are only interested if the part is an ICEFormEditor. A call to this
	 * function will occur prior to a part being closed, so just keep track of
	 * that form's id.
	 * 
	 * @param partRef
	 *            The workbench part calling this function.
	 * 
	 * @see IPartListener2#partActivated(IWorkbenchPartReference)
	 */
	@Override
	public void partActivated(IWorkbenchPartReference partRef) {

		logger.info("MeshElementTreeView Message: Called partActivated("
				+ partRef.getId() + ")");

		if (partRef.getId().equals(ICEFormEditor.ID)) {
			// Get the activated editor
			ICEFormEditor activeEditor = (ICEFormEditor) partRef.getPart(false);
			// Pull the form from the editor
			Form activeForm = ((ICEFormInput) activeEditor.getEditorInput())
					.getForm();
			// Record the ID of this form
			lastFormItemID = activeForm.getItemID();
		}

		return;
	}

	/**
	 * This function is called whenever a Workbench part gains focus. Here, we
	 * are only interested if the part is an ICEFormEditor. A call to this
	 * function will occur prior to a part being closed, so just keep track of
	 * that form's id.
	 * 
	 * @param partRef
	 *            The workbench part calling this function.
	 * 
	 * @see IPartListener2#partHidden(IWorkbenchPartReference)
	 */
	@Override
	public void partHidden(IWorkbenchPartReference partRef) {

		logger.info("MeshElementTreeView Message: Called partHidden("
				+ partRef.getId() + ")");

		if (partRef.getId().equals(ICEFormEditor.ID)) {
			// Get the hidden editor
			ICEFormEditor activeEditor = (ICEFormEditor) partRef.getPart(false);
			// Get the form from the editor
			Form activeForm = ((ICEFormInput) activeEditor.getEditorInput())
					.getForm();
			// Record the ID of this form
			lastFormItemID = activeForm.getItemID();
		}

		return;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see IPartListener2#partBroughtToTop(IWorkbenchPartReference)
	 */
	@Override
	public void partBroughtToTop(IWorkbenchPartReference partRef) {
		// Do nothing
		return;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see IPartListener2#partDeactivated(IWorkbenchPartReference)
	 */
	@Override
	public void partDeactivated(IWorkbenchPartReference partRef) {
		// Do nothing
		return;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see IPartListener2#partOpened(IWorkbenchPartReference)
	 */
	@Override
	public void partOpened(IWorkbenchPartReference partRef) {
		// Do nothing
		return;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see IPartListener2#partVisible(IWorkbenchPartReference)
	 */
	@Override
	public void partVisible(IWorkbenchPartReference partRef) {
		// Do nothing
		return;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see IPartListener2#partInputChanged(IWorkbenchPartReference)
	 */
	@Override
	public void partInputChanged(IWorkbenchPartReference partRef) {
		// Do nothing
		return;
	}

	/**
	 * This operation overrides the default/abstract implementation of
	 * ISelectionListener.selectionChanged to capture selections made in the
	 * ICEMeshPage and highlight the corresponding element in the
	 * MeshElementTreeView.
	 * 
	 * @param part
	 *            The IWorkbenchPart that called this function.
	 * @param selection
	 *            The ISelection chosen in the part parameter.
	 */
	@Override
	public void selectionChanged(IWorkbenchPart part, ISelection selection) {

		// Get the selection made in the ICEMeshPage.

		return;
	}

	public void revealSelectedEdge(int id) {
		elementTreeViewer.expandAll();

	}

	public void revealSelectedVertex(int id) {
		elementTreeViewer.expandAll();
	}

	// ---- These methods are required for tabbed properties. ---- //
	@Override
	public String getContributorId() {
		return getSite().getId();
	}

	@Override
	public Object getAdapter(Class adapter) {
		if (adapter == IPropertySheetPage.class) {
			return new TabbedPropertySheetPage(this);
		}
		return super.getAdapter(adapter);
	}
	// ----------------------------------------------------------- //

}