/*******************************************************************************
 * Copyright (c) 2009, 2015 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *     IBM Corporation - ongoing bug fixes and enhancements
 *******************************************************************************/
package org.eclipse.debug.internal.ui.viewers.model;

import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.TreeModelViewer;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.jface.viewers.ViewerLabel;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;

/**
 * Specialized tree viewer which displays only sub-tree of a full model.
 *
 * @since 3.5
 */
public class SubTreeModelViewer extends TreeModelViewer {

	/**
	 * The tree path in the model to the root element of this viewer.
	 */
	private TreePath fRootPath = TreePath.EMPTY;

	/**
	 * Viewer delegate that content and label providers refer to for viewer data.
	 */
	private DelegatingTreeModelViewer fDelegatingViewer;

	/**
	 * @return Returns the root element's model tree path.
	 */
	public TreePath getRootPath() {
		return fRootPath;
	}

	public SubTreeModelViewer(Composite parent, int style, IPresentationContext context) {
		super(parent, style, context);
	}

	/**
	 * Sets the viewer's input and root element's path
	 *
	 * @param input New viewer input.
	 * @param rootPath New root element path.
	 */
	public void setInput(Object input, TreePath rootPath) {
		fRootPath = rootPath;
		super.setInput(input);
	}

	/**
	 * A proxy for the sub tree viewer which is given to the content and
	 * label providers.  It translates the sub-tree paths in the viewer to the
	 * full model paths that the providers expect.
	 */
	public class DelegatingTreeModelViewer extends Viewer
		implements IInternalTreeModelViewer
	{
		@Override
		public void reveal(TreePath path, int index) {
			if (path.startsWith(fRootPath, null)) {
				SubTreeModelViewer.this.reveal(createSubPath(path), index);
			}
		}

		@Override
		public void replace(Object parentOrTreePath, int index, Object element) {
			if (parentOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)parentOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.replace(createSubPath(path), index, element);
				}
			} else {
				SubTreeModelViewer.this.replace(parentOrTreePath, index, element);
			}
		}

		@Override
		public void setChildCount(Object elementOrTreePath, int count) {
			if (elementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)elementOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.setChildCount(createSubPath(path), count);
				}
			} else {
				SubTreeModelViewer.this.setChildCount(elementOrTreePath, count);
			}
		}

		@Override
		public void setHasChildren(Object elementOrTreePath, boolean hasChildren) {
			if (elementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)elementOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.setHasChildren(createSubPath(path), hasChildren);
				}
			} else {
				SubTreeModelViewer.this.setHasChildren(elementOrTreePath, hasChildren);
			}
		}

		@Override
		public void autoExpand(TreePath elementPath) {
			// not supported
		}

		@Override
		public void setExpandedState(Object elementOrTreePath, boolean expanded) {
			if (elementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)elementOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.setExpandedState(createSubPath(path), expanded);
				}
			} else {
				SubTreeModelViewer.this.setExpandedState(elementOrTreePath, expanded);
			}
		}

		@Override
		public void expandToLevel(Object elementOrTreePath, int level) {
			if (elementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)elementOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.expandToLevel(createSubPath(path), level);
				}
			} else {
				SubTreeModelViewer.this.expandToLevel(elementOrTreePath, level);
			}
		}

		@Override
		public void remove(Object elementOrTreePath) {
			if (elementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)elementOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.remove(createSubPath(path));
				}
			} else {
				SubTreeModelViewer.this.remove(elementOrTreePath);
			}
		}

		@Override
		public void remove(Object parentOrTreePath, final int index) {
			if (parentOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)parentOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.remove(createSubPath(path), index);
				}
			} else {
				SubTreeModelViewer.this.remove(parentOrTreePath, index);
			}
		}

		@Override
		public void insert(Object parentOrTreePath, Object element, int position) {
			if (parentOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)parentOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					SubTreeModelViewer.this.insert(createSubPath(path), element, position);
				}
			} else {
				SubTreeModelViewer.this.insert(parentOrTreePath, element, position);
			}
		}

		@Override
		public boolean getExpandedState(Object elementOrTreePath) {
			if (elementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)elementOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					return SubTreeModelViewer.this.getExpandedState(createSubPath(path));
				}
			} else {
				return SubTreeModelViewer.this.getExpandedState(elementOrTreePath);
			}
			return false;
		}

		@Override
		public int getChildCount(TreePath path) {
			if (path.startsWith(fRootPath, null)) {
				return SubTreeModelViewer.this.getChildCount(createSubPath(path));
			}
			return -1;
		}

		@Override
		public boolean getHasChildren(Object elementOrTreePath) {
			if (elementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)elementOrTreePath;
				if (path.startsWith(fRootPath, null)) {
					return SubTreeModelViewer.this.getHasChildren(createSubPath(path));
				}
			} else {
				return SubTreeModelViewer.this.getHasChildren(elementOrTreePath);
			}
			return false;
		}

		@Override
		public Object getChildElement(TreePath path, int index) {
			if (path.startsWith(fRootPath, null)) {
				return SubTreeModelViewer.this.getChildElement(createSubPath(path), index);
			}
			return null;
		}

		@Override
		public TreePath getTopElementPath() {
			return createFullPath(SubTreeModelViewer.this.getTopElementPath());
		}

		@Override
		public int findElementIndex(TreePath parentPath, Object element) {
			if (parentPath.startsWith(fRootPath, null)) {
				return SubTreeModelViewer.this.findElementIndex(createSubPath(parentPath), element);
			}
			return -1;
		}

		@Override
		public boolean getElementChildrenRealized(TreePath parentPath) {
			if (parentPath.startsWith(fRootPath, null)) {
				return SubTreeModelViewer.this.getElementChildrenRealized(createSubPath(parentPath));
			}
			return true;
		}

		@Override
		public void setElementData(TreePath path, int numColumns, String[] labels, ImageDescriptor[] images, FontData[] fontDatas, RGB[] foregrounds, RGB[] backgrounds) {
			if (path.startsWith(fRootPath, null)) {
				SubTreeModelViewer.this.setElementData(createSubPath(path), numColumns, labels, images, fontDatas, foregrounds, backgrounds);
			}
		}

		@Override
		public Control getControl() {
			return SubTreeModelViewer.this.getControl();
		}

		@Override
		public Object getInput() {
			return SubTreeModelViewer.this.getInput();
		}

		@Override
		public ISelection getSelection() {
			return SubTreeModelViewer.this.getSelection();
		}

		@Override
		public void refresh() {
			SubTreeModelViewer.this.refresh();
		}

		@Override
		public void setInput(Object input) {
			SubTreeModelViewer.this.setInput(input);
		}

		@Override
		public void setSelection(ISelection selection, boolean reveal) {
			SubTreeModelViewer.this.setSelection(selection, reveal);
		}

		@Override
		public String[] getVisibleColumns() {
			 return SubTreeModelViewer.this.getVisibleColumns();
		}

		@Override
		public void addLabelUpdateListener(ILabelUpdateListener listener) {
			SubTreeModelViewer.this.addLabelUpdateListener(listener);
		}

		@Override
		public void addModelChangedListener(IModelChangedListener listener) {
			SubTreeModelViewer.this.addModelChangedListener(listener);
		}

		@Override
		public void addStateUpdateListener(IStateUpdateListener listener) {
			SubTreeModelViewer.this.addStateUpdateListener(listener);
		}

		@Override
		public void addViewerUpdateListener(IViewerUpdateListener listener) {
			SubTreeModelViewer.this.addViewerUpdateListener(listener);
		}

		@Override
		public int getAutoExpandLevel() {
			return SubTreeModelViewer.this.getAutoExpandLevel();
		}

		@Override
		public Display getDisplay() {
			return SubTreeModelViewer.this.getDisplay();
		}

		@Override
		public ViewerLabel getElementLabel(TreePath path, String columnId) {
			return SubTreeModelViewer.this.getElementLabel(path, columnId);
		}

		@Override
		public IPresentationContext getPresentationContext() {
			return SubTreeModelViewer.this.getPresentationContext();
		}

		@Override
		public void removeLabelUpdateListener(ILabelUpdateListener listener) {
			SubTreeModelViewer.this.removeLabelUpdateListener(listener);
		}

		@Override
		public void removeModelChangedListener(IModelChangedListener listener) {
			SubTreeModelViewer.this.removeModelChangedListener(listener);
		}

		@Override
		public void removeStateUpdateListener(IStateUpdateListener listener) {
			SubTreeModelViewer.this.removeStateUpdateListener(listener);
		}

		@Override
		public void removeViewerUpdateListener(IViewerUpdateListener listener) {
			SubTreeModelViewer.this.removeViewerUpdateListener(listener);
		}

		@Override
		public boolean saveElementState(TreePath path, ModelDelta delta, int deltaFlags) {
			return SubTreeModelViewer.this.saveElementState(path, delta, deltaFlags);
		}

		@Override
		public void setAutoExpandLevel(int level) {
			SubTreeModelViewer.this.setAutoExpandLevel(level);
		}

		@Override
		public void setSelection(ISelection selection, boolean reveal, boolean force) {
			SubTreeModelViewer.this.setSelection(selection, reveal, force);
		}

		@Override
		public boolean trySelection(ISelection selection, boolean reveal, boolean force) {
			return SubTreeModelViewer.this.trySelection(selection, reveal, force);
		}

		@Override
		public void updateViewer(IModelDelta delta) {
			SubTreeModelViewer.this.updateViewer(delta);
		}


		@Override
		public ViewerFilter[] getFilters() {
			return SubTreeModelViewer.this.getFilters();
		}

		@Override
		public void addFilter(ViewerFilter filter) {
			SubTreeModelViewer.this.addFilter(filter);
		}

		@Override
		public void setFilters(ViewerFilter... filters) {
			SubTreeModelViewer.this.setFilters(filters);
		}

		@Override
		public boolean overrideSelection(ISelection current, ISelection candidate) {
			return SubTreeModelViewer.this.overrideSelection(current, candidate);
		}

		@Override
		public void refresh(Object element) {
			SubTreeModelViewer.this.refresh(element);
		}

		@Override
		public void update(Object element) {
			SubTreeModelViewer.this.update(element);
		}

		@Override
		public void clearSelectionQuiet() {
			SubTreeModelViewer.this.clearSelectionQuiet();
		}

		@Override
		public TreePath[] getElementPaths(Object element) {
			TreePath[] subViewerPaths = SubTreeModelViewer.this.getElementPaths(element);
			TreePath[] retVal = new TreePath[subViewerPaths.length];
			for (int i = 0; i < subViewerPaths.length; i++) {
				retVal[i] = createFullPath(subViewerPaths[i]);
			}
			return retVal;
		}

		@Override
		public boolean getElementChecked(TreePath path) {
			return SubTreeModelViewer.this.getElementChecked(createSubPath(path));
		}

		@Override
		public boolean getElementGrayed(TreePath path) {
			return SubTreeModelViewer.this.getElementGrayed(createSubPath(path));
		}

		@Override
		public void setElementChecked(TreePath path, boolean checked, boolean grayed) {
			SubTreeModelViewer.this.setElementChecked(createSubPath(path), checked, grayed);
		}
	}


	/**
	 * Delegating content provider.  It translates all the calls to the
	 * underlying content provider to use full model tree paths.
	 */
	private class SubTreeModelContentProvider implements ITreeModelContentProvider {

		private final TreeModelContentProvider fBaseProvider;

		public SubTreeModelContentProvider() {
			fBaseProvider = new TreeModelContentProvider();
		}

		@Override
		public void updateHasChildren(TreePath path) {
			fBaseProvider.updateHasChildren(createFullPath(path));
		}

		@Override
		public void updateChildCount(TreePath path, int currentChildCount) {
			fBaseProvider.updateChildCount(createFullPath(path), currentChildCount);
		}

		@Override
		public void updateElement(TreePath parentPath, int viewIndex) {
			fBaseProvider.updateElement(createFullPath(parentPath), viewIndex);
		}

		@Override
		public int viewToModelCount(TreePath parentPath, int count) {
			return fBaseProvider.viewToModelCount(createFullPath(parentPath), count);
		}

		@Override
		public int viewToModelIndex(TreePath parentPath, int index) {
			return fBaseProvider.viewToModelIndex(createFullPath(parentPath), index);
		}

		@Override
		public void addModelChangedListener(IModelChangedListener listener) {
			fBaseProvider.addModelChangedListener(listener);
		}

		@Override
		public void preserveState(TreePath subPath) {
			fBaseProvider.preserveState(createFullPath(subPath));
		}

		@Override
		public void addStateUpdateListener(IStateUpdateListener listener) {
			fBaseProvider.addStateUpdateListener(listener);
		}

		@Override
		public void addViewerUpdateListener(IViewerUpdateListener listener) {
			fBaseProvider.addViewerUpdateListener(listener);
		}

		@Override
		public int getModelDeltaMask() {
			return fBaseProvider.getModelDeltaMask();
		}

		@Override
		public int modelToViewChildCount(TreePath parentPath, int count) {
			return fBaseProvider.modelToViewChildCount(createFullPath(parentPath), count);
		}

		@Override
		public int modelToViewIndex(TreePath parentPath, int index) {
			return fBaseProvider.modelToViewIndex(createFullPath(parentPath), index);
		}

		@Override
		public void removeModelChangedListener(IModelChangedListener listener) {
			fBaseProvider.removeModelChangedListener(listener);
		}

		@Override
		public void removeStateUpdateListener(IStateUpdateListener listener) {
			fBaseProvider.removeStateUpdateListener(listener);
		}

		@Override
		public void removeViewerUpdateListener(IViewerUpdateListener listener) {
			fBaseProvider.removeViewerUpdateListener(listener);
		}

		@Override
		public void setModelDeltaMask(int mask) {
			fBaseProvider.setModelDeltaMask(mask);
		}

		@Override
		public boolean areTreeModelViewerFiltersApplicable(Object parentElement) {
			return fBaseProvider.areTreeModelViewerFiltersApplicable(parentElement);
		}

		@Override
		public boolean shouldFilter(Object parentElementOrTreePath, Object element) {
			if (parentElementOrTreePath instanceof TreePath) {
				TreePath path = (TreePath)parentElementOrTreePath;
				return fBaseProvider.shouldFilter(createFullPath(path), element);
			} else {
				return fBaseProvider.shouldFilter(parentElementOrTreePath, element);
			}

		}

		@Override
		public void unmapPath(TreePath path) {
			fBaseProvider.unmapPath(createFullPath(path));
		}

		@Override
		public void updateModel(IModelDelta delta, int mask) {
			fBaseProvider.updateModel(delta, mask);
		}

		@Override
		public TreePath[] getParents(Object element) {
			// Not used
			return null;
		}

		@Override
		public void cancelRestore(TreePath path, int flags) {
			fBaseProvider.cancelRestore(createFullPath(path), flags);
		}

		@Override
		public void dispose() {
			fBaseProvider.dispose();
		}

		@Override
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
			fBaseProvider.inputChanged(fDelegatingViewer, oldInput, newInput);
		}

		@Override
		public void postInputChanged(IInternalTreeModelViewer viewer,
				Object oldInput, Object newInput) {
			fBaseProvider.postInputChanged(viewer, oldInput, newInput);
		}

		@Override
		public boolean setChecked(TreePath path, boolean checked) {
			return fBaseProvider.setChecked(createFullPath(path), checked);
		}

	}

	/**
	 * Delegating label provider.  It translates all the calls to the
	 * underlying label provider to use full model tree paths.
	 */
	private class SubTreeModelLabelProvider extends ColumnLabelProvider
		implements ITreeModelLabelProvider
	{

		private final TreeModelLabelProvider fBaseProvider;

		public SubTreeModelLabelProvider(IInternalTreeModelViewer viewer) {
			fBaseProvider = new TreeModelLabelProvider(viewer);
		}

		@Override
		public boolean update(TreePath elementPath) {
			return fBaseProvider.update( createFullPath(elementPath) );
		}

		@Override
		public void addLabelUpdateListener(ILabelUpdateListener listener) {
			fBaseProvider.addLabelUpdateListener(listener);
		}

		@Override
		public Color getColor(RGB rgb) {
			return fBaseProvider.getColor(rgb);
		}

		@Override
		public Font getFont(FontData fontData) {
			return fBaseProvider.getFont(fontData);
		}

		@Override
		public Image getImage(ImageDescriptor descriptor) {
			return fBaseProvider.getImage(descriptor);
		}

		@Override
		public void removeLabelUpdateListener(ILabelUpdateListener listener) {
			fBaseProvider.removeLabelUpdateListener(listener);
		}

		@Override
		public void addListener(ILabelProviderListener listener) {
			fBaseProvider.addListener(listener);
		}

		@Override
		public void dispose() {
			fBaseProvider.dispose();
			super.dispose();
		}

		@Override
		public boolean isLabelProperty(Object element, String property) {
			return fBaseProvider.isLabelProperty(element, property);
		}

		@Override
		public void removeListener(ILabelProviderListener listener) {
			fBaseProvider.removeListener(listener);
		}
	}

	private TreePath createFullPath(TreePath subPath) {
		if (fRootPath == null) {
			return TreePath.EMPTY;
		}

		Object[] segments = new Object[fRootPath.getSegmentCount() + subPath.getSegmentCount()];
		for (int i = 0; i < fRootPath.getSegmentCount(); i++) {
			segments[i] = fRootPath.getSegment(i);
		}
		for (int i = 0; i < subPath.getSegmentCount(); i++) {
			segments[i + fRootPath.getSegmentCount()] = subPath.getSegment(i);
		}
		return new TreePath(segments);
	}

	private TreePath createSubPath(TreePath fullPath) {
		if (fRootPath == null) {
			return TreePath.EMPTY;
		}

		if (fullPath.getSegmentCount() <= fRootPath.getSegmentCount()) {
			return TreePath.EMPTY;
		}
		Object[] segments = new Object[fullPath.getSegmentCount() - fRootPath.getSegmentCount()];
		for (int i = 0; i < segments.length; i++) {
			segments[i] = fullPath.getSegment(i + fRootPath.getSegmentCount());
		}
		return new TreePath(segments);
	}

	private DelegatingTreeModelViewer getDelegatingViewer() {
		if (fDelegatingViewer == null) {
			fDelegatingViewer = new DelegatingTreeModelViewer();
		}
		return fDelegatingViewer;
	}

	@Override
	protected ITreeModelContentProvider createContentProvider() {
		return new SubTreeModelContentProvider();
	}

	@Override
	protected ITreeModelLabelProvider createLabelProvider() {
		return new SubTreeModelLabelProvider(getDelegatingViewer());
	}


}
