/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 * 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:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.jsdt.internal.ui.browsing;

import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.part.IShowInTargetList;
import org.eclipse.wst.jsdt.core.IClassFile;
import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
import org.eclipse.wst.jsdt.core.IImportContainer;
import org.eclipse.wst.jsdt.core.IImportDeclaration;
import org.eclipse.wst.jsdt.core.IJavaScriptElement;
import org.eclipse.wst.jsdt.core.IMember;
import org.eclipse.wst.jsdt.core.IType;
import org.eclipse.wst.jsdt.core.JavaScriptModelException;
import org.eclipse.wst.jsdt.internal.ui.IJavaHelpContextIds;
import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
import org.eclipse.wst.jsdt.internal.ui.actions.CategoryFilterActionGroup;
import org.eclipse.wst.jsdt.internal.ui.actions.LexicalSortingAction;
import org.eclipse.wst.jsdt.internal.ui.preferences.MembersOrderPreferenceCache;
import org.eclipse.wst.jsdt.internal.ui.viewsupport.AppearanceAwareLabelProvider;
import org.eclipse.wst.jsdt.internal.ui.viewsupport.ColoredViewersManager;
import org.eclipse.wst.jsdt.internal.ui.viewsupport.JavaUILabelProvider;
import org.eclipse.wst.jsdt.internal.ui.viewsupport.ProblemTreeViewer;
import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
import org.eclipse.wst.jsdt.ui.JavaScriptUI;
import org.eclipse.wst.jsdt.ui.PreferenceConstants;
import org.eclipse.wst.jsdt.ui.actions.MemberFilterActionGroup;

public class MembersView extends JavaBrowsingPart implements IPropertyChangeListener {

	private MemberFilterActionGroup fMemberFilterActionGroup;
	/**
	 * Category filter action group.
	 * 
	 */
	private CategoryFilterActionGroup fCategoryFilterActionGroup;


	public MembersView() {
		setHasWorkingSetFilter(false);
		setHasCustomSetFilter(true);
		JavaScriptPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.internal.ui.browsing.JavaBrowsingPart#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(Class key) {
		if (key == IShowInTargetList.class) {
			return new IShowInTargetList() {
				public String[] getShowInTargetIds() {
					return new String[] { JavaScriptUI.ID_PACKAGES };
				}

			};
		}
		return super.getAdapter(key);
	}

	/**
	 * Creates and returns the label provider for this part.
	 *
	 * @return the label provider
	 * @see org.eclipse.jface.viewers.ILabelProvider
	 */
	protected JavaUILabelProvider createLabelProvider() {
		return new AppearanceAwareLabelProvider(
						AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS | JavaScriptElementLabels.F_APP_TYPE_SIGNATURE | JavaScriptElementLabels.ALL_CATEGORY,
						AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS
						);
	}

	/**
	 * Returns the context ID for the Help system
	 *
	 * @return	the string used as ID for the Help context
	 */
	protected String getHelpContextId() {
		return IJavaHelpContextIds.MEMBERS_VIEW;
	}

	protected String getLinkToEditorKey() {
		return PreferenceConstants.LINK_BROWSING_MEMBERS_TO_EDITOR;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.wst.jsdt.internal.ui.browsing.JavaBrowsingPart#createViewer(org.eclipse.swt.widgets.Composite)
	 */
	protected StructuredViewer createViewer(Composite parent) {
		ProblemTreeViewer viewer= new ProblemTreeViewer(parent, SWT.MULTI);
		ColoredViewersManager.install(viewer);
		fMemberFilterActionGroup= new MemberFilterActionGroup(viewer, JavaScriptUI.ID_MEMBERS_VIEW);
		return viewer;
	}

	protected void fillToolBar(IToolBarManager tbm) {
		tbm.add(new LexicalSortingAction(getViewer(), JavaScriptUI.ID_MEMBERS_VIEW));
		fMemberFilterActionGroup.contributeToToolBar(tbm);
		super.fillToolBar(tbm);
	}

	/*
	 * @see org.eclipse.wst.jsdt.internal.ui.browsing.JavaBrowsingPart#fillActionBars(org.eclipse.ui.IActionBars)
	 * 
	 */
	protected void fillActionBars(IActionBars actionBars) {
		super.fillActionBars(actionBars);
		fCategoryFilterActionGroup= new CategoryFilterActionGroup(getViewer(), getViewSite().getId(), getCategoryFilterActionGroupInput());
		fCategoryFilterActionGroup.contributeToViewMenu(actionBars.getMenuManager());
	}

	/*
	 * @see org.eclipse.wst.jsdt.internal.ui.browsing.JavaBrowsingPart#setInput(java.lang.Object)
	 * 
	 */
	protected void setInput(Object input) {
		super.setInput(input);
		if (fCategoryFilterActionGroup != null)
			fCategoryFilterActionGroup.setInput(getCategoryFilterActionGroupInput());
	}
	
	private IJavaScriptElement[] getCategoryFilterActionGroupInput() {
		Object input= getInput();
		if (input instanceof IJavaScriptElement)
			return new IJavaScriptElement[] { (IJavaScriptElement)input };
		return new IJavaScriptElement[0];
	}

	/**
	 * Answers if the given <code>element</code> is a valid
	 * input for this part.
	 *
	 * @param 	element	the object to test
	 * @return	<true> if the given element is a valid input
	 */
	protected boolean isValidInput(Object element) {
		if (element instanceof IType) {
			IType type= (IType)element;
			return type.isBinary() || type.getDeclaringType() == null;
		}
		return false;
	}

	/**
	 * Answers if the given <code>element</code> is a valid
	 * element for this part.
	 *
	 * @param 	element	the object to test
	 * @return	<true> if the given element is a valid element
	 */
	protected boolean isValidElement(Object element) {
		if (element instanceof IMember)
			return super.isValidElement(((IMember)element).getDeclaringType());
		else if (element instanceof IImportDeclaration)
			return isValidElement(((IJavaScriptElement)element).getParent());
		else if (element instanceof IImportContainer) {
			Object input= getViewer().getInput();
			if (input instanceof IJavaScriptElement) {
				IJavaScriptUnit cu= (IJavaScriptUnit)((IJavaScriptElement)input).getAncestor(IJavaScriptElement.JAVASCRIPT_UNIT);
				if (cu != null) {
					IJavaScriptUnit importContainerCu= (IJavaScriptUnit)((IJavaScriptElement)element).getAncestor(IJavaScriptElement.JAVASCRIPT_UNIT);
					return cu.equals(importContainerCu);
				} else {
					IClassFile cf= (IClassFile)((IJavaScriptElement)input).getAncestor(IJavaScriptElement.CLASS_FILE);
					IClassFile importContainerCf= (IClassFile)((IJavaScriptElement)element).getAncestor(IJavaScriptElement.CLASS_FILE);
					return cf != null && cf.equals(importContainerCf);
				}
			}
		}
		return false;
	}

	/**
	 * Finds the element which has to be selected in this part.
	 *
	 * @param je	the Java element which has the focus
	 * @return the element to select
	 */
	protected IJavaScriptElement findElementToSelect(IJavaScriptElement je) {
		if (je == null)
			return null;

		switch (je.getElementType()) {
			case IJavaScriptElement.TYPE:
				if (((IType)je).getDeclaringType() == null)
					return null;
				return je;
			case IJavaScriptElement.METHOD:
			case IJavaScriptElement.INITIALIZER:
			case IJavaScriptElement.FIELD:
			case IJavaScriptElement.PACKAGE_DECLARATION:
			case IJavaScriptElement.IMPORT_CONTAINER:
				return je;
			case IJavaScriptElement.IMPORT_DECLARATION:
				IJavaScriptUnit cu= (IJavaScriptUnit)je.getParent().getParent();
				try {
					if (cu.getImports()[0].equals(je)) {
						Object selectedElement= getSingleElementFromSelection(getViewer().getSelection());
						if (selectedElement instanceof IImportContainer)
							return (IImportContainer)selectedElement;
					}
				} catch (JavaScriptModelException ex) {
					// return je;
				}
				return je;
		}
		return null;
	}

	/**
	 * Finds the closest Java element which can be used as input for
	 * this part and has the given Java element as child.
	 *
	 * @param 	je 	the Java element for which to search the closest input
	 * @return	the closest Java element used as input for this part, or <code>null</code>
	 */
	protected IJavaScriptElement findInputForJavaElement(IJavaScriptElement je) {
		if (je == null || !je.exists() || (je.getJavaScriptProject() != null && !je.getJavaScriptProject().isOnIncludepath(je)))
			return null;

		switch (je.getElementType()) {
			case IJavaScriptElement.TYPE:
				IType type= ((IType)je).getDeclaringType();
				if (type == null)
					return je;
				else
					return findInputForJavaElement(type);
			case IJavaScriptElement.JAVASCRIPT_UNIT:
				return getTypeForCU((IJavaScriptUnit)je);
			case IJavaScriptElement.CLASS_FILE:
				return findInputForJavaElement(((IClassFile)je).getType());
			case IJavaScriptElement.IMPORT_DECLARATION:
				return findInputForJavaElement(je.getParent());
			case IJavaScriptElement.PACKAGE_DECLARATION:
			case IJavaScriptElement.IMPORT_CONTAINER:
				IJavaScriptElement parent= je.getParent();
				if (parent instanceof IJavaScriptUnit) {
					return getTypeForCU((IJavaScriptUnit)parent);
				}
				else if (parent instanceof IClassFile)
					return findInputForJavaElement(parent);
				return null;
			default:
				if (je instanceof IMember)
					return findInputForJavaElement(((IMember)je).getDeclaringType());
		}
		return null;
	}

	/*
	 * Implements method from IViewPart.
	 */
	public void saveState(IMemento memento) {
		super.saveState(memento);
		fMemberFilterActionGroup.saveState(memento);
	}

	protected void restoreState(IMemento memento) {
		super.restoreState(memento);
		fMemberFilterActionGroup.restoreState(memento);
		getViewer().getControl().setRedraw(false);
		getViewer().refresh();
 		getViewer().getControl().setRedraw(true);
	}

	protected void hookViewerListeners() {
		super.hookViewerListeners();
		getViewer().addDoubleClickListener(new IDoubleClickListener() {
			public void doubleClick(DoubleClickEvent event) {
				TreeViewer viewer= (TreeViewer)getViewer();
				Object element= ((IStructuredSelection)event.getSelection()).getFirstElement();
				if (viewer.isExpandable(element))
					viewer.setExpandedState(element, !viewer.getExpandedState(element));
			}
		});
	}

	boolean isInputAWorkingCopy() {
		Object input= getViewer().getInput();
		if (input instanceof IJavaScriptElement) {
			IJavaScriptUnit cu= (IJavaScriptUnit)((IJavaScriptElement)input).getAncestor(IJavaScriptElement.JAVASCRIPT_UNIT);
			if (cu != null)
				return cu.isWorkingCopy();
		}
		return false;
	}

	protected void restoreSelection() {
		IEditorPart editor= getViewSite().getPage().getActiveEditor();
		if (editor != null)
			setSelectionFromEditor(editor);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
	 */
	public void propertyChange(PropertyChangeEvent event) {
		if (MembersOrderPreferenceCache.isMemberOrderProperty(event.getProperty())) {
			getViewer().refresh();
		}
	}

	/*
	 * @see org.eclipse.wst.jsdt.internal.ui.browsing.JavaBrowsingPart#dispose()
	 */
	public void dispose() {
		if (fMemberFilterActionGroup != null) {
			fMemberFilterActionGroup.dispose();
			fMemberFilterActionGroup= null;
		}
		if (fCategoryFilterActionGroup != null) {
			fCategoryFilterActionGroup.dispose();
			fCategoryFilterActionGroup= null;
		}
		super.dispose();
		JavaScriptPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
	}
}
