/**
 * File:    PropertyTabbedPropertySheetPage.java
 * Created: 28.07.2008
 *
 *
/*******************************************************************************
 * Copyright (c) 2008-2010 Open Wide SA <www.openwide.fr>
 *
 * 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:
 *    Marc Dutoo, Open Wide, Lyon, France
 *      - creation and implementation
 *******************************************************************************/

package org.eclipse.jwt.we.conf.we.editors.properties.tabbedPage;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.ListenerList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jwt.we.conf.model.AspectInstance;
import org.eclipse.jwt.we.conf.model.aspects.AspectManager;
import org.eclipse.jwt.we.editors.WEEditor;
import org.eclipse.jwt.we.editors.properties.tabbedPage.TabbedModelPropertySheetPage;
import org.eclipse.jwt.we.model.view.Reference;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.internal.views.properties.tabbed.view.TabbedPropertyComposite;
import org.eclipse.ui.internal.views.properties.tabbed.view.TabbedPropertyRegistry;
import org.eclipse.ui.internal.views.properties.tabbed.view.TabbedPropertyViewer;
import org.eclipse.ui.views.properties.IPropertySourceProvider;
import org.eclipse.ui.views.properties.tabbed.ISection;
import org.eclipse.ui.views.properties.tabbed.ITabDescriptor;
import org.eclipse.ui.views.properties.tabbed.TabContents;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;

/**
 * This is a standard page for a dynamic Property-enabled multitab property
 * sheet.
 * 
 * It is built using the code of the usual TabbedPropertyPage and enriches its
 * behaviour for dynamic Properties. In order to do that in a fine enough manner
 * (notably UI resizing), it access several hidden vars and methods of this
 * superclass.
 * 
 * @version $Id: PropertyTabbedPropertySheetPage.java,v 1.7 2009/11/24 16:44:47
 *          flautenba Exp $
 * @author Marc Dutoo, Open Wide SA, France <www.openwide.fr>
 */
@SuppressWarnings("restriction")
public class PropertyTabbedPropertySheetPage extends TabbedModelPropertySheetPage
{

	/** Used to compute dynamic Property tab height */
	private static final int DYNPROP_SECTION_HEIGHT = 115;

	/** Used to test whether a tab is the dynamic Property tab */
	private static final String DYNPROP_TAB_DESCRIPTOR_ID = "org.eclipse.jwt.we.propertytabs.aspects.Property"; //$NON-NLS-1$

	/**
	 * The main editor
	 */
	private TabContents propertyTab;

	/**
	 * Enriches the superclass' SelectionChangedListener with dynamic Property
	 * specific behaviour, i.e. recreating and setting input of the dynamic
	 * Property tab.
	 * 
	 * Specifically handles the display of dynamic Properties when selecting the
	 * dynamic Property tab.
	 * 
	 * Added in createControl().
	 * 
	 * @author mdutoo
	 * 
	 */
	private class MySelectionChangedListener implements ISelectionChangedListener
	{

		private ISelectionChangedListener superSelectionChangedListener;


		public MySelectionChangedListener(
				ISelectionChangedListener superSelectionChangedListener)
		{
			this.superSelectionChangedListener = superSelectionChangedListener;
		}


		public void selectionChanged(SelectionChangedEvent event)
		{
			IStructuredSelection selection = (IStructuredSelection) event.getSelection();
			ITabDescriptor descriptor = (ITabDescriptor) selection.getFirstElement();

			if (isDynamicPropertyTab(descriptor))
			{
				// dynamic Property specific behaviour :

				TabbedPropertyViewer myTabbedPropertyViewer = getTabbedPropertyViewer();

				// getting aspects
				EObject modelElement = jwtSpecificGetAspectsFromSelection(myTabbedPropertyViewer
						.getInput());
				List<AspectInstance> aspects = AspectManager.INSTANCE
						.getAspectInstances(modelElement); // NB. never null

				// custom recreating
				Composite tabComposite = reCreateAndInitDynamicPropertyTab(aspects.size());

				// superclass' behaviour
				this.superSelectionChangedListener.selectionChanged(event);

				// layouting ; required else won't refresh on click on an
				// element
				Composite tabCompositeParent = tabComposite.getParent();
				tabCompositeParent.pack(true);
				tabComposite.setVisible(true);

				// custom set input (tab sections)
				setTabInput(getTabbedPropertyViewer().getWorkbenchPart(), aspects);

			}
			else
			{
				// as usual
				this.superSelectionChangedListener.selectionChanged(event);
			}
		}

	}


	/**
	 * Creates a new TabbedModelPropertySheetPage.
	 * 
	 * @param adapterFactory
	 *            The factory that provides an {@link IPropertySourceProvider}.
	 */
	public PropertyTabbedPropertySheetPage(WEEditor editor)
	{
		super(editor);
	}


	/**
	 * Adds to its superclass' : wraps the superclass' SelectionChangedListener
	 * by our custom one.
	 */
	@Override
	public void createControl(Composite parent)
	{
		super.createControl(parent);

		ListenerList selectionChangedListeners = null;
		try
		{
			Field field = Viewer.class.getDeclaredField("selectionChangedListeners"); //$NON-NLS-1$
			field.setAccessible(true);
			selectionChangedListeners = (ListenerList) field
					.get(getTabbedPropertyViewer());

			Object[] listeners = selectionChangedListeners.getListeners();
			ISelectionChangedListener superListener = (ISelectionChangedListener) listeners[0];
			listeners[0] = new MySelectionChangedListener(superListener);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}


	/**
	 * Overriden to patch npex when deleting an element because called on
	 * refresh by WE
	 */
	@Override
	public void refresh()
	{
		boolean doRefresh = true;
		try
		{
			Field field = TabbedPropertySheetPage.class.getDeclaredField("currentTab"); //$NON-NLS-1$
			field.setAccessible(true);
			TabContents myCurrentTab = (TabContents) field.get(this);
			if (myCurrentTab == null)
			{
				doRefresh = false;
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		if (doRefresh)
		{
			super.refresh();
		} // else would be npex on refresh !
	}


	/**
	 * Multisection hack : remembering the single dynamic Property tab of this
	 * page if any
	 */
	protected TabContents createTab(ITabDescriptor tabDescriptor)
	{
		TabContents myTab = tabDescriptor.createTab();// ((org.eclipse.ui.views.properties.tabbed.ISectionDescriptor)
		// tabDescriptor.getSectionDescriptors().get(0)).getSectionClass();
		if (isDynamicPropertyTab(tabDescriptor))
		{
			this.propertyTab = myTab;
		}
		return myTab;
	}


	/**
	 * Returns whether the given tab is a dynamic Property tab, based on its id.
	 * 
	 * @param tabDescriptor
	 * @return
	 */
	private boolean isDynamicPropertyTab(ITabDescriptor tabDescriptor)
	{
		if (tabDescriptor == null)
			return false;

		return DYNPROP_TAB_DESCRIPTOR_ID.equals(tabDescriptor.getId());
	}


	/**
	 * Helper method for creating dynamic Property tab composites.
	 * 
	 * @return the property tab composite.
	 */
	private Composite createMyTabComposite()
	{
		TabbedPropertyComposite myTabbedPropertyComposite = (TabbedPropertyComposite) this
				.getControl();
		Composite result = this.getWidgetFactory().createComposite(
				myTabbedPropertyComposite.getTabComposite(), SWT.NO_FOCUS);
		result.setVisible(false);
		result.setLayout(new FillLayout());
		FormData data = new FormData();
		// boolean myHasTitleBar = registry.getLabelProvider() != null;
		boolean myHasTitleBar = true;
		if (myHasTitleBar)
		{
			data.top = new FormAttachment(myTabbedPropertyComposite.getTitle(), 0);
		}
		else
		{
			data.top = new FormAttachment(0, 0);
		}
		data.bottom = new FormAttachment(100, 0);
		data.left = new FormAttachment(0, 0);
		data.right = new FormAttachment(100, 0);
		result.setLayoutData(data);
		return result;
	}


	/**
	 * Enriches the superclass' selectionChanged() with dynamic Property
	 * specific behaviour, i.e. recreating and setting input of the dynamic
	 * Property tab inbetween setting UI input and setting selection /
	 * refreshing.
	 * 
	 * Specifically handles the display of dynamic Properties when selecting
	 * another element.
	 */
	@Override
	public void selectionChanged(IWorkbenchPart part, ISelection selection)
	{
		if (selection.equals(this.getCurrentSelection()))
		{
			// nothing to do
			return;
		}

		// 1. first part of the "usual" selectionChanged() : set selection/input
		// of model element in UI

		this.setCurrentSelection(selection);
		ISelection myCurrentSelection = getCurrentSelection();

		// see if the selection provides a new contributor
		// myValidateRegistry(selection);
		try
		{
			Method m = this.getClass().getSuperclass().getSuperclass()
					.getDeclaredMethod("validateRegistry", ISelection.class);
			m.setAccessible(true);
			m.invoke(this, selection);
		}
		catch (Throwable t)
		{
			t.printStackTrace(); // TODO log
		}
		ITabDescriptor[] descriptors = this.getRegistry().getTabDescriptors(part,
				myCurrentSelection);
		// If there are no descriptors for the given input we do not need to
		// touch the tab objects. We might reuse them for the next valid
		// input.
		if (descriptors.length > 0)
		{
			updateTabs(descriptors);
		}
		// update tabs list
		TabbedPropertyViewer myTabbedPropertyViewer = getTabbedPropertyViewer();
		myTabbedPropertyViewer.setInput(part, myCurrentSelection);
		// int lastTabSelectionIndex = getLastTabSelection(part, selection);
		int lastTabSelectionIndex = 0;
		try
		{
			Method m = this
					.getClass()
					.getSuperclass()
					.getSuperclass()
					.getDeclaredMethod("getLastTabSelection", IWorkbenchPart.class,
							ISelection.class);
			m.setAccessible(true);
			lastTabSelectionIndex = ((Integer) m.invoke(this, part, myCurrentSelection))
					.intValue();
		}
		catch (Throwable t)
		{
			t.printStackTrace(); // TODO log
		}
		Object selectedTab = myTabbedPropertyViewer.getElementAt(lastTabSelectionIndex);

		// 2. enriching it for dynamic Properties : creating their sections if
		// any

		if (this.isDynamicPropertyTab((ITabDescriptor) selectedTab)
				&& this.propertyTab != null)
		{
			// getting aspects
			EObject modelElement = jwtSpecificGetAspectsFromSelection(myCurrentSelection);
			List<AspectInstance> aspects = AspectManager.INSTANCE
					.getAspectInstances(modelElement); // NB. never null

			// recreate tab
			Composite tabComposite = reCreateAndInitDynamicPropertyTab(aspects.size());

			// layouting ; required else won't refresh on click on an element
			Composite tabCompositeParent = tabComposite.getParent();
			tabCompositeParent.pack(true);
			tabComposite.setVisible(true);

			// set input
			setTabInput(part, aspects);
		}

		// 1. second part of the "usual" selectionChanged() : set selection of
		// tab in UI and refresh

		setSelectionQueueLocked(true);
		try
		{
			if (selectedTab == null)
			{
				myTabbedPropertyViewer.setSelection(null);
			}
			else
			{
				myTabbedPropertyViewer.setSelection(new StructuredSelection(selectedTab));
			}
		}
		finally
		{
			setSelectionQueueLocked(false);
		}
		// refreshTitleBar();
		try
		{
			Method m = this.getClass().getSuperclass().getSuperclass()
					.getDeclaredMethod("refreshTitleBar");
			m.setAccessible(true);
			m.invoke(this);
		}
		catch (Throwable t)
		{
			t.printStackTrace(); // TODO log
		}
	}


	/**
	 * 
	 * @param sectionNb
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Composite reCreateAndInitDynamicPropertyTab(int sectionNb)
	{
		// delete tab
		Composite tabComposite = (Composite) getTabToComposite().get(this.propertyTab);
		if (tabComposite != null)
		{
			tabComposite.dispose();
		}

		// init enough sections
		List<ISection> sections = new ArrayList<ISection>(sectionNb);
		for (int i = 0; i < sectionNb; i++)
		{
			PropertyPropertySection propSect = new PropertyPropertySection();
			sections.add(propSect);
		}
		this.propertyTab.setSections(sections.toArray(new ISection[sections.size()]));

		tabComposite = createMyTabComposite();
		this.propertyTab.createControls(tabComposite, this);
		getTabToComposite().put(this.propertyTab, tabComposite);
		return tabComposite;
	}


	/**
	 * Sets aspects as inputs of this tab's sections
	 * 
	 * @param part
	 * @param aspects
	 */
	private void setTabInput(IWorkbenchPart part, List<AspectInstance> aspects)
	{
		for (int i = 0; i < propertyTab.getSections().length; i++)
		{
			ISection section = propertyTab.getSections()[i];
			if (i < aspects.size())
			{
				AspectInstance aspect = aspects.get(i);
				section.setInput(part, new StructuredSelection(aspect));
			}
		}
	}


	/**
	 * Returns the given selection or element as an EObject
	 * 
	 * @param selectionOrElement
	 * @return
	 */
	private EObject getSelectedEObject(Object selectionOrElement)
	{
		if (selectionOrElement instanceof IStructuredSelection)
		{
			selectionOrElement = ((IStructuredSelection) selectionOrElement)
					.getFirstElement();
		}
		if (selectionOrElement instanceof EObject)
		{
			return (EObject) selectionOrElement;
		}
		return null;
	}


	/**
	 * Show aspects connected to refelem instead of ref.
	 * 
	 * @param object
	 * @return
	 */
	private EObject jwtSpecificGetAspectsFromSelection(Object selectionOrElement)
	{
		EObject selectedElement = getSelectedEObject(selectionOrElement);
		if (selectedElement != null)
		{
			if (selectedElement instanceof Reference)
			{
				return ((Reference) selectedElement).getReference();
			}
		}
		return selectedElement;
	}


	/**
	 * Enriches the "usual" resizeScrolledComposite() for dynamic Properties :
	 * computes their height based on how many of them there are
	 */
	public void resizeScrolledComposite()
	{
		if (getCurrentTab() != this.propertyTab)
		{
			// non property case
			super.resizeScrolledComposite();
			return;
		}

		// property specific case :

		Point currentTabSize = new Point(0, 0);
		if (this.propertyTab != null)
		{
			// HACK for computing min size, because computed preferred size
			// is wrong when selecting another model element

			Composite sizeReference = (Composite) getTabToComposite().get(
					this.propertyTab);
			if (sizeReference != null)
			{
				currentTabSize = sizeReference.computeSize(SWT.DEFAULT, SWT.DEFAULT);
				// computing their height according to how many of them there
				// are
				currentTabSize = new Point(currentTabSize.x,
						this.propertyTab.getSections().length * DYNPROP_SECTION_HEIGHT);
			}
		}
		TabbedPropertyComposite tabbedPropertyComposite = (TabbedPropertyComposite) this
				.getControl();

		tabbedPropertyComposite.getScrolledComposite().setMinSize(currentTabSize);

		ScrollBar verticalScrollBar = tabbedPropertyComposite.getScrolledComposite()
				.getVerticalBar();
		if (verticalScrollBar != null)
		{
			Rectangle clientArea = tabbedPropertyComposite.getScrolledComposite()
					.getClientArea();
			int increment = clientArea.height - 5;
			verticalScrollBar.setPageIncrement(increment);
		}

		ScrollBar horizontalScrollBar = tabbedPropertyComposite.getScrolledComposite()
				.getHorizontalBar();
		if (horizontalScrollBar != null)
		{
			Rectangle clientArea = tabbedPropertyComposite.getScrolledComposite()
					.getClientArea();
			int increment = clientArea.width - 5;
			horizontalScrollBar.setPageIncrement(increment);
		}
	}


	// ///////////////////////////////////////////////////////////////////////
	// Helpers providing access to hidden superclass vars
	// (in order to be able to smartly override / enrich some superclass code)

	/**
	 * Helper providing access to a hidden superclass var
	 */
	@SuppressWarnings("unchecked")
	private Map getTabToComposite()
	{
		Map myTabToComposite = null;
		try
		{
			Field field = TabbedPropertySheetPage.class
					.getDeclaredField("tabToComposite"); //$NON-NLS-1$
			field.setAccessible(true);
			myTabToComposite = (Map) field.get(this);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return myTabToComposite;
	}


	/**
	 * Helper providing access to a hidden superclass var
	 */
	@SuppressWarnings(
	{ "unchecked", "unused" })
	private Map getDescriptorToTab()
	{
		Map descriptorToTab = null;
		try
		{
			Field field = TabbedPropertySheetPage.class
					.getDeclaredField("descriptorToTab"); //$NON-NLS-1$
			field.setAccessible(true);
			descriptorToTab = (Map) field.get(this);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return descriptorToTab;
	}


	/**
	 * Helper providing access to a hidden superclass var
	 */
	private ISelection getCurrentSelection()
	{
		ISelection myCurrentSelection = null;
		try
		{
			Field field = TabbedPropertySheetPage.class
					.getDeclaredField("currentSelection"); //$NON-NLS-1$
			field.setAccessible(true);
			myCurrentSelection = (ISelection) field.get(this);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return myCurrentSelection;
	}


	/**
	 * Helper providing access to a hidden superclass var
	 */
	private void setCurrentSelection(ISelection currentSelection)
	{
		try
		{
			Field field = TabbedPropertySheetPage.class
					.getDeclaredField("currentSelection"); //$NON-NLS-1$
			field.setAccessible(true);
			field.set(this, currentSelection);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}


	/**
	 * Helper providing access to a hidden superclass var
	 */
	private TabbedPropertyViewer getTabbedPropertyViewer()
	{
		TabbedPropertyViewer myTabbedPropertyViewer = null;
		try
		{
			Field field = TabbedPropertySheetPage.class
					.getDeclaredField("tabbedPropertyViewer"); //$NON-NLS-1$
			field.setAccessible(true);
			myTabbedPropertyViewer = (TabbedPropertyViewer) field.get(this);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return myTabbedPropertyViewer;
	}


	/**
	 * Helper providing access to a hidden superclass var
	 */
	private TabbedPropertyRegistry getRegistry()
	{
		TabbedPropertyRegistry myRegistry = null;
		try
		{
			Field field = TabbedPropertySheetPage.class.getDeclaredField("registry"); //$NON-NLS-1$
			field.setAccessible(true);
			myRegistry = (TabbedPropertyRegistry) field.get(this);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return myRegistry;
	}


	/**
	 * Helper providing access to a hidden superclass var
	 */
	@SuppressWarnings(
	{ "unused" })
	private boolean getSelectionQueueLocked()
	{
		boolean mySelectionQueueLocked = false;
		try
		{
			Field field = TabbedPropertySheetPage.class
					.getDeclaredField("selectionQueueLocked"); //$NON-NLS-1$
			field.setAccessible(true);
			mySelectionQueueLocked = ((Boolean) field.get(this)).booleanValue();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return mySelectionQueueLocked;
	}


	/**
	 * Helper providing access to a hidden superclass var
	 */
	private void setSelectionQueueLocked(boolean selectionQueueLocked)
	{
		try
		{
			Field field = TabbedPropertySheetPage.class
					.getDeclaredField("selectionQueueLocked"); //$NON-NLS-1$
			field.setAccessible(true);
			field.set(this, selectionQueueLocked);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

}