/**
 * File:    Views.java
 * Created: 22.08.2006
 *
 *
/*******************************************************************************
 * Copyright (c) 2005-2011
 * University of Augsburg, Germany <www.ds-lab.org>
 *
 * 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:
 *    Wolf Fischer, Programming distributed Systems Lab, University of Augsburg 
 *    	- initial API and implementation
 *    Florian Lautenbacher, Programming distributed Systems Lab, University of Augsburg 
 *    	- support for external views
 *    Christian Saad, Programming distributed Systems Lab, University of Augsburg 
 *    	- support for dropdown
 *    Marc Dutoo, Open Wide, Lyon, France
 *		- behaviour on model objects that are not know of the view file : display
 * by default rather than hide it (allows the user to see its own model elements
 * without changing the standard view file)
 *    Marc Blachon, Bull SAS, Grenoble, France
 *      - FigureFactory no more got from parsing attribute from the view extension point.
 *******************************************************************************/

package org.eclipse.jwt.we.misc.views;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.jface.action.ToolBarContributionItem;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jwt.we.DefaultFactoryRegistry;
import org.eclipse.jwt.we.IFactoryRegistry;
import org.eclipse.jwt.we.Plugin;
import org.eclipse.jwt.we.PluginProperties;
import org.eclipse.jwt.we.editors.WEEditor;
import org.eclipse.jwt.we.editors.actions.managed.views.ViewsAction;
import org.eclipse.jwt.we.editors.preferences.PreferenceReader;
import org.eclipse.jwt.we.misc.extensions.ExtensionsHelper;
import org.eclipse.jwt.we.misc.logging.Logger;
import org.eclipse.jwt.we.misc.util.internal.Serialization;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.SubActionBars2;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import org.osgi.framework.Bundle;

/**
 * This class represents the view management. This is done by adding items that
 * are not allowed to be seen by special users.
 * 
 * @version $Id: Views.java,v 1.34 2010-05-10 09:31:27 chsaad Exp $
 * @author Wolf Fischer, Programming distributed Systems Lab, University of
 *         Augsburg, Germany, www.ds-lab.org Adapted for external views by
 *         Florian Lautenbacher, Programming distributed Systems Lab, University
 *         of Augsburg
 */
public class Views
{

	/**
	 * The Logger.
	 */
	private Logger logger = Logger.getLogger(Views.class);

	/**
	 * Singleton access.
	 */
	private static Views myinstance;

	/**
	 * Path to the directory where the views should be
	 */
	private static final String viewdirectory = "views/"; //$NON-NLS-1$

	/**
	 * The views that are available.
	 */
	private ArrayList<ViewDescriptor> availableViews = new ArrayList<ViewDescriptor>();

	/**
	 * Points to the currently selected view.
	 */
	private ViewDescriptor selectedView;

	/**
	 * The name of the default view.
	 */
	private String defaultViewName = "Technical"; //$NON-NLS-1$


	/**
	 * Standard constructor. Initializes the selected views and sets the
	 * properties of every view that are not allowed to be shown!
	 */
	private Views()
	{
		// collect all available internal and external views
		collectViews();

		// set the default view (if it is not present in the available views)
		if (availableViews.size() > 0)
		{
			boolean found = false;
			for (ViewDescriptor vD : availableViews)
			{
				if (vD.getInternalName().equals(defaultViewName))
				{
					found = true;
					break;
				}
			}
			if (!found)
			{
				defaultViewName = availableViews.get(0).getInternalName();
			}
		}

		// set the currently selected view
		Object obj = PreferenceReader.viewSelected.get();
		obj = obj == null ? defaultViewName : obj;

		// try to read the selected view
		ViewDescriptor defaultViewDescriptor = null;
		boolean found = false;
		for (ViewDescriptor vD : availableViews)
		{
			if (vD.getInternalName().equals(obj.toString()))
			{
				found = true;
				selectedView = vD;
				break;
			}

			if (vD.getInternalName().equals(defaultViewName))
			{
				defaultViewDescriptor = vD;
			}
		}

		// if selected view was not found, load default
		if (!found)
		{
			selectedView = defaultViewDescriptor;
		}
	}


	/**
	 * Singleton accessor
	 * 
	 * @return The only instance of this class.
	 */
	public static Views getInstance()
	{
		if (myinstance == null)
			myinstance = new Views();
		return myinstance;
	}


	// ///////////////////////////////////////////////////////////////////////////
	// Read the views
	//

	/**
	 * Collects all views registered through the extension point.
	 */
	private void collectViews()
	{
		String extension_point_id = PluginProperties.extension_point_view;

		logger.debug("get views from extension point " + extension_point_id); //$NON-NLS-1$

		try
		{
			// first get all extending classes and their directories get the
			// registry and
			// the extension point
			IConfigurationElement[] confElements = ExtensionsHelper
					.findConfigurationElements(extension_point_id);

			// exit if there are no extensions registered
			if (confElements == null)
			{
				logger.info("no external views found"); //$NON-NLS-1$
				return;
			}

			// read external views from extension point
			for (IConfigurationElement point : confElements)
			{
				Bundle bundle = Platform.getBundle(point.getContributor().getName());

				// read the attributes from the extension point
				String viewID = point.getAttribute("id");//$NON-NLS-1$

				// read the attributes from the extension point
				String viewFile = point.getAttribute("viewFile");//$NON-NLS-1$

				logger.info("JWT Extension - found VIEW <" + viewID + "> at " + extension_point_id + ": " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						+ viewFile);

				IFactoryRegistry factoryRegistry = null;
				try
				{
					factoryRegistry = (IFactoryRegistry) point
							.createExecutableExtension("factoryRegistry");//$NON-NLS-1$
				}
				catch (Exception e)
				{
					factoryRegistry = new DefaultFactoryRegistry();
				}

				// read the attributes from the extension point
				String viewName = point.getAttribute("displayName");//$NON-NLS-1$
				viewName = viewName == null ? "" : viewName;

				ImageDescriptor viewImage = null;
				try
				{
					String imageString = point.getAttribute("displayIcon");//$NON-NLS-1$
					URL imagePath = bundle.getEntry(imageString);

					if (imagePath == null && imageString.length() > 3
							&& imageString.substring(0, 4).equals("src/")) //$NON-NLS-1$
					{
						imageString = imageString.replaceFirst("src/", ""); //$NON-NLS-1$ //$NON-NLS-2$
						imagePath = bundle.getEntry(imageString);
					}

					if (imagePath != null)
					{
						viewImage = ImageDescriptor.createFromURL(imagePath);
					}
				}
				catch (Exception e)
				{
				}

				URL viewPath = bundle.getEntry(viewFile);

				// if the view couldn't be loaded, remove "src/" from path if
				// present and try again
				if (viewPath == null && viewFile.length() > 3
						&& viewFile.substring(0, 4).equals("src/")) //$NON-NLS-1$
				{
					viewFile = viewFile.replaceFirst("src/", ""); //$NON-NLS-1$ //$NON-NLS-2$
					viewPath = bundle.getEntry(viewFile);
				}

				// load the view
				if (viewPath != null)
				{
					// resolve the file path
					viewPath = FileLocator.resolve(viewPath);

					// load the view and set the properties in the descriptor
					InputStream stream = viewPath.openStream();
					ViewDescriptor viewDesc = loadViewFromFile(stream);
					stream.close();

					viewDesc.setFactoryRegistry(factoryRegistry);

					viewDesc.setInternalID(viewID);

					viewDesc.setOfficialName(viewName);

					viewDesc.setImageDescriptor(viewImage);

					viewDesc.setViewAction(new ViewsAction(viewDesc.getInternalName(),
							viewDesc.getOfficialName(), viewImage));
				}
				else
				{
					logger.warning("view file " + viewFile + " could not be found"); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		}
		catch (Exception e)
		{
			logger.warning("error loading external views"); //$NON-NLS-1$
			e.printStackTrace();
		}
	}


	/**
	 * Loads a given view file.
	 * 
	 * @param f
	 *            The view file
	 * 
	 * @return The descriptor of the view.
	 */
	public ViewDescriptor loadViewFromFile(Object file)
	{
		try
		{
			SAXBuilder sax = new SAXBuilder();

			Document doc;
			if (file instanceof File)
			{
				doc = sax.build((File) file);
			}
			else if (file instanceof InputStream)
			{
				doc = sax.build((InputStream) file);
			}
			else
			{
				return null;
			}

			Element rootnode = doc.getRootElement();

			// build view descriptor
			ViewDescriptor viewDesc = new ViewDescriptor();
			viewDesc.setInternalID(rootnode.getChild("viewname").getText()); //$NON-NLS-1$
			viewDesc.setViewData((Hashtable<String, ViewItemWrapper>) Serialization
					.createObject(rootnode.getChild("viewdata"))); //$NON-NLS-1$

			// put view in available views, if not already loaded
			boolean viewExists = false;
			for (ViewDescriptor vD : availableViews)
			{
				if (vD.getInternalName().equals(viewDesc.getInternalName()))
				{
					viewExists = true;
				}
			}

			if (!viewExists)
			{
				availableViews.add(viewDesc);
			}

			// set current view
			selectedView = viewDesc;

			return viewDesc;
		}
		catch (Exception ex)
		{
			logger.debug(ex);

			return null;
		}
	}


	// ///////////////////////////////////////////////////////////////////////////
	// View manipulation
	//

	/**
	 * Adds a viewitemwrapper to the currently selected view.
	 * 
	 * @param newchild
	 * @param parent
	 */
	public void addViewItemWrapper(ViewItemWrapper newchild, ViewItemWrapper parent)
	{
		if (parent != null)
		{
			parent.addChild(newchild);
		}
		selectedView.getViewData().put(newchild.toString(), newchild);
	}


	/**
	 * Sets the display value of a viewitemwrapper.
	 * 
	 * @param item
	 * @param value
	 */
	public void setDisplay(ViewItemWrapper item, boolean value)
	{
		Object obj = selectedView.getViewData().get(item.toString());
		if (obj == null)
			return;
		((ViewItemWrapper) obj).setDisplay(value);
	}


	/**
	 * Resets the current view.
	 * 
	 */
	public void reset()
	{
		if (selectedView == null)
		{
			ViewDescriptor viewDesc = new ViewDescriptor();
			viewDesc.setInternalID("NewView"); //$NON-NLS-1$
			viewDesc.setViewData(new Hashtable<String, ViewItemWrapper>());
			selectedView = viewDesc;
		}

		selectedView.getViewData().clear();
	}


	/**
	 * Saves a view to a file.
	 * 
	 * @param f
	 */
	public void saveCurrentView(File f)
	{
		Document doc = new Document(new Element("WorkFlowEditorView")); //$NON-NLS-1$
		Element name = new Element("viewname"); //$NON-NLS-1$
		name.addContent(selectedView.getInternalName());
		doc.getRootElement().addContent(name);
		try
		{
			Serialization.writeObjecttoXML(selectedView.getViewData(),
					doc.getRootElement(), "viewdata"); //$NON-NLS-1$
			XMLOutputter xmlout = new XMLOutputter();
			xmlout.output(doc, new FileOutputStream(f));
		}
		catch (Exception ex)
		{
			logger.debug(ex);
		}
	}


	// ///////////////////////////////////////////////////////////////////////////
	// Getter
	//

	/**
	 * Returns the active view.
	 * 
	 * @return The active view.
	 */
	public ViewDescriptor getSelectedView()
	{
		return selectedView;
	}


	/**
	 * Returns all internal and external viewdescriptors.
	 * 
	 * @return All viewdescriptors.
	 */
	public ArrayList<ViewDescriptor> getAvailableViews()
	{
		return availableViews;
	}


	// ///////////////////////////////////////////////////////////////////////////
	// Using Views
	//

	/**
	 * Returns a value, if a given object should be displayed.
	 * 
	 * @param obj
	 *            The object to be checked
	 * @return True, if the propertie can be displayed. Else false
	 */
	public boolean displayObject(EObject obj)
	{
		if (selectedView.getViewData() == null)
		{
			return false;
		}
		String name = ""; //$NON-NLS-1$
		if (obj instanceof EClass || obj instanceof EAttribute
				|| obj instanceof EReference)
		{
			name = ViewItemWrapper.makeName(obj, false);
		}
		else
		{
			name = ViewItemWrapper.makeName(obj.eClass(), false);
		}
		Object vi_obj = selectedView.getViewData().get(name);
		if (vi_obj != null)
		{
			ViewItemWrapper vi = (ViewItemWrapper) vi_obj;
			return vi.getDisplay();
		}

		// MDU allow unknown extensions : by default display it
		return true;
	}


	/**
	 * Changes the view and forces an update of the display.
	 * 
	 * @param viewName
	 *            The name of the view
	 */
	public void changeView(String viewName)
	{
		// try to read the selected view
		for (ViewDescriptor vD : availableViews)
			if (vD.getInternalName().equals(viewName))
				selectedView = vD;

		// the view is updated in preferencechangelistener
		PreferenceReader.viewSelected.set(viewName);
	}


	/**
	 * Refreshes the checked/enabled state of all views and the toolbar entries
	 * and sets the external figure factory. This is called every time a new
	 * editor page is selected and every time the view has changed.
	 * 
	 * @param weeditor
	 *            The editor
	 */
	public void refreshViewsState(final WEEditor weeditor)
	{
		PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable()
		{

			public void run()
			{
				// refresh the checked state of all views
				refreshCheckedState();

				// refresh the toolbar dropdown item
				refreshToolbarCommand(weeditor);
			}
		});
		// set the figure factory
		// FigureFactory.getInstance().setExternalFigureFactory(
		// selectedView.getFigureFactory());
	}


	/**
	 * Refreshes the checked state of all view actions.
	 */
	private void refreshCheckedState()
	{
		// get all views actions
		List<ViewsAction> viewsActions = new ArrayList<ViewsAction>();

		for (ViewDescriptor viewDesc : Views.getInstance().getAvailableViews())
		{
			ViewsAction viewAction = viewDesc.getViewAction();
			if (viewAction != null)
			{
				viewsActions.add(viewAction);
			}
		}

		// refresh
		for (ViewsAction act : viewsActions)
		{
			act.setChecked(false);
			if (act.getId().equals(selectedView.getInternalName()))
			{
				act.setChecked(true);
			}

			// refresh the checked state
			act.setEnabled(act.isEnabled());
		}
	}


	private void refreshToolbarCommand(final WEEditor weEditor)
	{
		// only if weeditor is present
		if (weEditor == null)
		{
			return;
		}

		final int dropdown_width = 120;

		// get the editoractionbars
		SubActionBars2 actionBars = null;
		if (weEditor.getSite() instanceof IViewSite)
		{
			actionBars = (SubActionBars2) ((IViewSite) weEditor.getSite())
					.getActionBars();
		}
		else if (weEditor.getSite() instanceof IEditorSite)
		{
			actionBars = (SubActionBars2) ((IEditorSite) weEditor.getSite())
					.getActionBars();
		}

		// get the toolbar view group
		ToolBarContributionItem viewGroup = (ToolBarContributionItem) actionBars
				.getCoolBarManager().find("org.eclipse.jwt.we.viewBar"); //$NON-NLS-1$

		// synchronize text and image of the dropdown toolitem with current view
		if (viewGroup == null || (ToolBarManager) viewGroup.getToolBarManager() == null)
		{
			return;
		}
		final ToolBar tb = ((ToolBarManager) viewGroup.getToolBarManager()).getControl();

		if (tb.getItemCount() > 1)
		{
			// set the image
			if (selectedView.getImageDescriptor() != null)
			{
				tb.getItem(1).setImage(
						Plugin.getDefault().getFactoryRegistry().getImageFactory()
								.getImage(selectedView.getImageDescriptor()));
			}
			else
			{
				tb.getItem(1).setImage(null);
			}

			try
			{
				// the official or internal name of the view
				String viewName = selectedView.getOfficialName() != null ? selectedView
						.getOfficialName() : selectedView.getInternalName();

				// shorten the displayed name until the dropdown is small enough
				boolean tooLong = false;
				int overflowProtection = 40;
				do
				{
					// set the text
					tb.getItem(1).setText(viewName + (tooLong ? "..." : "")); //$NON-NLS-1$ //$NON-NLS-2$
					// if the text was manipulated once, this will always be
					// true
					tooLong = tb.getItem(1).getWidth() > dropdown_width ? true : tooLong;
					// if the text was too long, take away the last character
					viewName = tooLong ? viewName.substring(0, viewName.length() - 1)
							: viewName;
				}
				while (tb.getItem(1).getWidth() > dropdown_width
						&& overflowProtection-- > 0);

				// make the displayed name longer if it is too small and was not
				// shortened
				overflowProtection = 100;
				while (tb.getItem(1).getWidth() < dropdown_width && !tooLong
						&& overflowProtection-- > 0)
				{
					viewName += " "; //$NON-NLS-1$
					tb.getItem(1).setText(viewName);
				}
			}
			catch (Exception e)
			{
				logger.warning("error updating the toolbar view dropdown");
			}

			// update the toolbar
			tb.update();
			viewGroup.getToolBarManager().update(true);
		}
		else
		{
			// workaround: during startup, the toolbar may not be fully
			// available at
			// this point. therefore, we add a listener which repeats this task
			// once the
			// toolbar is painted and removes itself.
			tb.addPaintListener(new PaintListener()
			{

				public void paintControl(PaintEvent e)
				{
					tb.removePaintListener(this);

					refreshViewsState(weEditor);
				}
			});
		}
	}

}