/*
* Copyright (c) 2005 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/


package com.nokia.sdt.uidesigner.ui.utils;

import com.nokia.carbide.internal.api.updater.IProjectUpdateSession;
import com.nokia.carbide.updater.CarbideUpdaterPlugin;
import com.nokia.cpp.internal.api.utils.core.*;
import com.nokia.cpp.internal.api.utils.ui.WorkbenchUtils;
import com.nokia.sdt.component.IComponent;
import com.nokia.sdt.component.IComponentSet;
import com.nokia.sdt.component.adapter.CommonAttributes;
import com.nokia.sdt.component.adapter.IAttributes;
import com.nokia.sdt.datamodel.IDesignerDataModel;
import com.nokia.sdt.datamodel.LoadResult;
import com.nokia.sdt.datamodel.adapter.IComponentInstance;
import com.nokia.sdt.displaymodel.IDisplayModel;
import com.nokia.sdt.displaymodel.IDisplayModel.LayoutAreaConfiguration;
import com.nokia.sdt.displaymodel.adapter.IContainer;
import com.nokia.sdt.displaymodel.adapter.ILayoutObject;
import com.nokia.sdt.editor.EditorServices;
import com.nokia.sdt.editor.IDesignerEditor;
import com.nokia.sdt.uidesigner.ui.DesignerEditorPage;
import com.nokia.sdt.uidesigner.ui.UIDesignerPlugin;
import com.nokia.sdt.uidesigner.ui.editparts.ModelObjectEditPart;
import com.nokia.sdt.uidesigner.ui.figure.ScrollingContainerFigure.ScrollPane;
import com.nokia.sdt.uimodel.UIModelPlugin;
import com.nokia.sdt.utils.*;
import com.nokia.sdt.workspace.IDesignerDataModelSpecifier;
import com.swtdesigner.ResourceManager;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.*;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.geometry.*;
import org.eclipse.draw2d.text.FlowPage;
import org.eclipse.draw2d.text.TextFlow;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.*;
import org.eclipse.gef.palette.*;
import org.eclipse.gef.requests.CreateRequest;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.*;
import org.eclipse.ui.views.contentoutline.ContentOutline;

import java.lang.reflect.InvocationTargetException;
import java.util.*;

public class EditorUtils {

	public static final String CONSTRAINT = "CreateRequest.constraint"; //$NON-NLS-1$
	private final static QualifiedName QUALIFIED_LAST_ZOOM = 
		new QualifiedName(UIDesignerPlugin.getDefault().toString(), "LastZoomLevel"); //$NON-NLS-1$
	private static final CreateRequest CREATE_REQ = new CreateRequest();

	public final static String LAST_CONFIG = "LayoutSizeComboContributionItem.LastSetConfiguration"; //$NON-NLS-1$
	public final static QualifiedName QUALIFIED_LAST_CONFIG = 
						new QualifiedName(UIDesignerPlugin.getDefault().toString(), LAST_CONFIG);

	private static final Point PRIVATE_POINT = new Point();

	static boolean isRootContainer(EObject obj) {
		IComponentInstance componentInstance = Adapters.getComponentInstance(obj);
		EObject parent = componentInstance.getParent();
		return parent == null || Adapters.getLayoutContainer(parent) == null;
	}
	
	public static boolean canContainChild(EObject parent, EObject child) {
		if (parent == null)
			return false;
		
		IComponentInstance parentInstance = Adapters.getComponentInstance(parent);
		IDesignerDataModel dataModel = parentInstance.getDesignerDataModel();
		if (isRootContainer(parent) && isNonLayoutObject(child, dataModel))
			return true;
		
		IContainer container = Adapters.getContainer(parent);
		if (container == null)
			return false;

		// for layout objects, explicitly test
		ILayoutObject layoutObject = Adapters.getLayoutObject(child);
		if (layoutObject != null)
			return container.canContainChild(Adapters.getComponentInstance(child), null);
		
		// otherwise, see if the container can contain objects of the component type
		IComponent component = getComponent(child, dataModel);
		if (component != null)
			return container.canContainComponent(component, null);
		
		return false;
	}
	
	public static boolean isNonLayoutObject(EObject object, IDesignerDataModel model) {
		return isNonLayoutComponent(getComponent(object, model));
	}
	
	public static IComponent getComponent(EObject object, IDesignerDataModel model) {
		IComponentInstance componentInstance = Adapters.getComponentInstance(object);
		if (componentInstance != null) {
			IDesignerDataModel componentModel = componentInstance.getDesignerDataModel();
			// if the component instance comes from the clipboard, use the other model
			if (componentModel == null) // try other model
				componentModel = model;
			if (componentModel == null)
				return null;
			IComponentSet componentSet = componentModel.getComponentSet();
			Check.checkContract(componentSet != null);
			return componentSet.lookupComponent(componentInstance.getComponentId());
		}
		return null;
		
	}

	public static void saveLastZoom(IDesignerEditor editor, double level) {
		IResource resource = editor.getDataModelResource();
		try {
			resource.setPersistentProperty(QUALIFIED_LAST_ZOOM, String.valueOf(level));
		} 
		catch (CoreException e) {} // don't do anything if this fails
	}
	
	public static double getSavedZoom(IDesignerEditor editor) {
		double level = 1.0;
		IResource resource = editor.getDataModelResource();
		try {
			String levelAsString = resource.getPersistentProperty(QUALIFIED_LAST_ZOOM);
			if (levelAsString != null)
				level = Double.parseDouble(levelAsString);
		} 
		// don't do anything if this fails
		catch (CoreException e) {} 
		catch (NumberFormatException x) {}
		return level;
	}

    public static Image getUnknownSmallIcon() {
        return ResourceManager.getPluginImage(UIDesignerPlugin.getDefault(), "icons/unknown_sm.png"); //$NON-NLS-1$
    }

    public static Image getUnknownLargeIcon() {
        return ResourceManager.getPluginImage(UIDesignerPlugin.getDefault(), "icons/unknown.png"); //$NON-NLS-1$
    }
    
    public static Point getAbsoluteLocation(EObject object, IDesignerDataModel dataModel) {
		EObject rootContainer = dataModel.getRootContainers()[0];
		ILayoutObject layoutObject = Adapters.getLayoutObject(object);
		
		// this may happen in malconfigured component tree
		if (layoutObject == null)
			return new Point(0, 0);
		
		org.eclipse.swt.graphics.Rectangle bounds = layoutObject.getBounds();
		Rectangle d2dBounds = new Rectangle(bounds);
		Point absLocation = d2dBounds.getLocation();

		if (object.equals(rootContainer))
			return new Point(0, 0);
		
		EObject parent = Adapters.getComponentInstance(object).getParent();
		while (!parent.equals(rootContainer)) {
			ILayoutObject parentLayoutObject = Adapters.getLayoutObject(parent);
			// this may happen in malconfigured component tree
			if (parentLayoutObject == null)
				break;
			absLocation.translate(
					new Rectangle(parentLayoutObject.getBounds()).getLocation());
			parent = Adapters.getComponentInstance(parent).getParent();
		}
		
		return absLocation;
	}

	public static EObject getCommonDirectParent(Collection potentialSiblings) {
		// return the common direct parent only if all members of collection are siblings
		EObject parent = null;
		for (Iterator iter = potentialSiblings.iterator(); iter.hasNext();) {
			EObject object = (EObject) iter.next();
			EObject curParent = Adapters.getComponentInstance(object).getParent();
			if (parent == null)
				parent = curParent;
			else if (!curParent.equals(parent))
				return null;
		}
		
		return parent;
	}
	
	// Taken from GEF code for tooltip figure creation
	public static IFigure createToolTip(String message) {
		if (message == null || message.length() == 0)
			return null;

		FlowPage fp = new FlowPage() {
			public Dimension getPreferredSize(int w, int h) {
				Dimension d = super.getPreferredSize(-1, -1);
				if (d.width > 150)
					d = super.getPreferredSize(150, -1);
				return d;
			}
		};
		fp.setOpaque(true);
		fp.setBorder(new MarginBorder(0, 2, 1, 0));
		TextFlow tf = new TextFlow();
		tf.setText(message);
		fp.add(tf);
		return fp;
	}

	public static EditPart getPrimaryEditPartForObject(IDesignerEditor editor, EObject object) {
		EditPartViewer upperViewer = editor.getUpperGraphicalViewer();
		EditPart part = (EditPart) upperViewer.getEditPartRegistry().get(object);
		if (part == null) {
			EditPartViewer lowerViewer = editor.getLowerGraphicalViewer();
			part = (EditPart) lowerViewer.getEditPartRegistry().get(object);
		}
		return part;
	}
	
	private static boolean isOutlineActive(IDesignerEditor editor) {
		Check.checkArg(editor);
		IEditorPart editorPart = (IEditorPart) editor.getAdapter(IEditorPart.class);
		IWorkbenchPartSite workbenchPartSite = editorPart.getSite();
		IWorkbenchPage workbenchPage = workbenchPartSite.getPage();
		IWorkbenchPart activePart = workbenchPage.getActivePart();
		return activePart instanceof ContentOutline;
	}
	
	public static void setDisplayModelSelectedObjects(IDisplayModel displayModel, Collection selectedObjects) {
		if (selectedObjects.isEmpty())
			return;
		
		EObject[] objects = (EObject[]) selectedObjects.toArray(new EObject[selectedObjects.size()]);
		displayModel.setSelectedObjects(objects);
	}

	public static void setSelectionToAffectedObjects(IDesignerEditor editor, Collection affectedObjects) {
		setDisplayModelSelectedObjects(editor.getDisplayModel(), affectedObjects);
		
		boolean setFocusToGraphicalViewer = !isOutlineActive(editor);
		List selectedParts = new ArrayList();
		for (Iterator iter = affectedObjects.iterator(); iter.hasNext();) {
			EObject object = (EObject) iter.next();
			EditPart part = getPrimaryEditPartForObject(editor, object);
			if (part != null) {
				selectedParts.add(part);
				if (setFocusToGraphicalViewer)
					part.getViewer().getControl().setFocus();
			}
		}
		if (!selectedParts.isEmpty()) {
			StructuredSelection selection = new StructuredSelection(selectedParts);
			editor.getSelectionManager().setSelection(selection);
		}
		else {
			if (setFocusToGraphicalViewer)
				editor.getUpperGraphicalViewer().getControl().setFocus();
			editor.getSelectionManager().setSelection(StructuredSelection.EMPTY);
		}
	}
	
	public static void eraseLastTargetFeedback(EditPartViewer viewer) {
		ModelObjectEditPart lastTargetEditPart = 
			(ModelObjectEditPart) viewer.getProperty(DesignerEditorPage.LAST_FEEDBACK_TARGET);
		if (lastTargetEditPart != null) {
			lastTargetEditPart.eraseTargetFeedback(CREATE_REQ);
		}
	}

	public static boolean isTransient(Object object) {
		return findTransientObject(object) != null;
	}
	
	public static boolean isSameObjectOrChild(EObject root, EObject object) {
		if (object != null) {
			if (object.equals(root))
				return true;
			
			IComponentInstance instance = Adapters.getComponentInstance(object);
			if (instance != null) {
				return isSameObjectOrChild(root, instance.getParent());
			}
		}
		
		return false;
	}

	public static EObject findTransientObject(Object object) {
		if (object instanceof EObject) {
			if (Adapters.isTransientObject((EObject) object))
				return (EObject) object;
			
			IComponentInstance componentInstance = Adapters.getComponentInstance((EObject) object);
			if (componentInstance != null) 
				return  findTransientObject(componentInstance.getParent());
		}
		
		return null;
	}

	public static IFigure findDeepestChildAt(IFigure parent, int x, int y) {
		PRIVATE_POINT.setLocation(x, y);
		parent.translateFromParent(PRIVATE_POINT);
		int locx = PRIVATE_POINT.x;
		int locy = PRIVATE_POINT.y;
		IFigure child;
		List children = parent.getChildren();
		for (int i = children.size(); i > 0;) {
			i--;
			child = (IFigure) children.get(i);
			if (child.isVisible()) {
				if (parent instanceof ScrollPane) {
					ScrollPane scrollPane = (ScrollPane) parent;
					child = scrollPane.findFigureAt(locx, locy);
				}
				else
					child = findDeepestChildAt(child, locx, locy);
				if (child != null)
					return child;
			}
		}
		if (parent.containsPoint(x, y))
			return parent;
		
		return null;
	}

	public static boolean canReorderChildren(EObject object, List<EObject> children) {
		IContainer container = Adapters.getContainer(object);
		if (container == null)
			return false;
		
		for (EObject child : children) {
			if (!container.canReorderChild(child))
				return false;
		}

		return true;
	}
	
	public static void replacePaletteRoot(final DesignerEditorPage editor, final PaletteRoot newRoot) {
		// We must do this after any potential DND completion event 
		// referencing some stale palette entry has been processed.
		Display.getCurrent().asyncExec(new Runnable() {
			public void run() {
				DefaultEditDomain editDomain = editor.getEditDomain();
				Check.checkState(editDomain != null);
				PaletteViewer paletteViewer = editDomain.getPaletteViewer();
				Check.checkContract(paletteViewer != null);
				paletteViewer.getPaletteRoot().setDefaultEntry(null);
				paletteViewer.setActiveTool(null);
				editDomain.setPaletteRoot(newRoot);
				paletteViewer.getPaletteRoot().setDefaultEntry(newRoot.getDefaultEntry());
				editor.setOpenDrawerNames(null);
			}
		});
	}

	public static List<String> getOpenPaletteDrawerNames(DesignerEditorPage editor, PaletteRoot palette) {
		List<String> openDrawerNames = editor.getOpenDrawerNames();
		if (openDrawerNames != null)
			return openDrawerNames;
		
		openDrawerNames = new ArrayList<String>();
		DefaultEditDomain editDomain = editor.getEditDomain();
		Check.checkState(editDomain != null);
		PaletteViewer paletteViewer = editDomain.getPaletteViewer();
		Check.checkContract(paletteViewer != null);
		List drawers = palette.getChildren();
		for (Iterator iter = drawers.iterator(); iter.hasNext();) {
			PaletteEntry entry = (PaletteEntry) iter.next();
			if (entry instanceof PaletteDrawer) {
				PaletteDrawer drawer = (PaletteDrawer) entry;
				if (paletteViewer.isExpanded(drawer))
					openDrawerNames.add(drawer.getLabel());
			}
		}
		
		editor.setOpenDrawerNames(openDrawerNames);
		return openDrawerNames;
	}
	
	private static boolean equalEntries(PaletteEntry entry1, PaletteEntry entry2) {
		PaletteDrawer d1 = null;
		PaletteDrawer d2 = null;
		if (entry1 instanceof PaletteDrawer)
			d1 = (PaletteDrawer) entry1;
		if (entry2 instanceof PaletteDrawer)
			d2 = (PaletteDrawer) entry2;
		
		if ((d1 != null) && (d2 != null)) {
			if (d1.getLabel().equals(d2.getLabel())) {
				List children1 = d1.getChildren();
				List children2 = d2.getChildren();
				if (children1.size() != children2.size())
					return false;
				
				// assume they're in the same order
				Iterator<PaletteEntry> iter1 = children1.iterator();
				Iterator<PaletteEntry> iter2 = children2.iterator();
				while (iter1.hasNext() && iter2.hasNext()) {
					PaletteEntry e1 = (PaletteEntry) iter1.next();
					PaletteEntry e2 = (PaletteEntry) iter2.next();
					if (!equalEntries(e1, e2))
						return false;
				}
			}
		}
		
		if ((entry1 instanceof CombinedTemplateCreationEntry)) {
			return entry1.equals(entry2);
		}
		
		return entry1.getLabel().equals(entry2.getLabel());
	}

	public static boolean equalPalettes(PaletteRoot p1, PaletteRoot p2) {
		if (((p1 == null) || (p2 == null)) && (p1 != p2))
			return false;
		
		List children1 = p1.getChildren();
		List children2 = p2.getChildren();
		if (children1.size() != children2.size())
			return false;
		
		// assume they're in the same order
		Iterator<PaletteEntry> iter1 = children1.iterator();
		Iterator<PaletteEntry> iter2 = children2.iterator();
		while (iter1.hasNext() && iter2.hasNext()) {
			PaletteEntry entry1 = (PaletteEntry) iter1.next();
			PaletteEntry entry2 = (PaletteEntry) iter2.next();
			if (!equalEntries(entry1, entry2))
				return false;
		}
		return true;
	}
	
	/**
	 * Test whether parent can contain at least one child of children. Test components only
	 * because this is checking clipboard and ILayoutObject adapter is not available.
	 * @param parent EObject
	 * @param children Collection<EObject>
	 * @param statuses List<IStatus> contains IStatus for children that cannot be contained
	 * @return true if one child can be contained by parent 
	 */
	public static boolean canContainAtLeastOneComponent(EObject parent, 
							Collection<EObject> children, List<IStatus> statuses) {
		if (children.isEmpty())
			return true;
		
		IContainer container = Adapters.getContainer(parent);
		if (container == null)
			return false;

		boolean canContainOneComponent = false;
		StatusHolder statusHolder = null;
		for (Iterator iter = children.iterator(); iter.hasNext();) {
			statusHolder = new StatusHolder();
			EObject object = (EObject) iter.next();
			IComponent component = Adapters.getComponent(object);
			if (container.canContainComponent(component, statusHolder)) {
				canContainOneComponent = true;
				break;
			}
			else
				statuses.add(statusHolder.getStatus());
		}
		
		return canContainOneComponent;
	}

	public static LayoutAreaConfiguration getSavedConfiguration(IDisplayModel displayModel, IResource resource) {
		String savedConfig = null;
		
		// first try the resource
		try {
			if (resource != null)
				savedConfig = resource.getPersistentProperty(QUALIFIED_LAST_CONFIG);
		} 
		catch (CoreException e) {} // don't do anything if this fails

		// then try the plugin setting
		if ((savedConfig == null) || (savedConfig.length() == 0))
			savedConfig = UIModelPlugin.getDefault().getPreferenceStore().getString(LAST_CONFIG);

		for (Iterator iter = displayModel.getLayoutAreaConfigurations().iterator(); iter.hasNext();) {
			LayoutAreaConfiguration aConfig = (LayoutAreaConfiguration) iter.next();
			if (aConfig.getID().equals(savedConfig)) {
				return aConfig;
			}
		}
		
		return null;
	}
	
	public static void saveCurrentLayout(IDesignerEditor editor) {
		if (editor == null)
			return;
		IDisplayModel displayModel = editor.getDisplayModel();
		LayoutAreaConfiguration currentLayout = displayModel.getCurrentConfiguration();
		// save in the resource
		if (currentLayout != null) {
			IDesignerDataModel dataModel = editor.getDataModel();
			IResource resource = dataModel.getModelSpecifier().getPrimaryResource();
			try {
				resource.setPersistentProperty(EditorUtils.QUALIFIED_LAST_CONFIG, currentLayout.getID());
			} 
			catch (CoreException e) {} // don't do anything if this fails
		}
	}
	
	public static boolean isScrollableVertically(Object object) {
		if (object instanceof EObject) {
			IAttributes attributes = Adapters.getAttributes((EObject) object);
			return attributes.getBooleanAttribute(
					CommonAttributes.VERTICAL_SCROLLABLE_CONTAINER, false);
		}
		
		return false;
	}
	
	public static boolean isScrollableHorizontally(Object object) {
		if (object instanceof EObject) {
			IAttributes attributes = Adapters.getAttributes((EObject) object);
			return attributes.getBooleanAttribute(
					CommonAttributes.HORIZONTAL_SCROLLABLE_CONTAINER, false);
		}
		
		return false;
	}
	
	public static void tryToUpdateProject(IProject project) {
		doUpdateSession(project, true);
	}
	
	public static void tryToUpgradeProjectDesigns(IProject project) {	
		doUpdateSession(project, false);
	}
	
	static class UpdaterRunnable implements IRunnableWithProgress, IProjectUpdateSession.IStateListener {
		private final IProjectUpdateSession session;
		private boolean waiting = true;

		UpdaterRunnable(IProjectUpdateSession session) {
			this.session = session;
			session.addStateListener(this);
		}

		public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
			session.updateProjects();
			while (waiting) {
				Thread.sleep(100);
			}
		}

		public void stateChanged(IProjectUpdateSession session) {
			if (session.getState() == IProjectUpdateSession.State.COMPLETE) {
				waiting = false;
				session.removeStateListener(this);
			}
		}
	}
	
	private static void doUpdateSession(IProject project, boolean showProjectUpdateDialog) {
		Shell shell = WorkbenchUtils.getSafeShell();
		if (shell == null || !shell.isVisible())
			return;
	
		IProjectUpdateSession updateSession = CarbideUpdaterPlugin.getProjectUpdateManager().createSession();
		updateSession.addUpdatableProject(project);
		updateSession.addRefactoringProject(project);
		updateSession.setShowProjectUpdateDialog(showProjectUpdateDialog);
		updateSession.setSilentIfEmpty(true);
		
		UpdaterRunnable operation = new UpdaterRunnable(updateSession);
		try {
			new ProgressMonitorDialog(shell).run(true, false, operation);
		} catch (InvocationTargetException x) {
			UIDesignerPlugin.getDefault().log(x);
			
		} catch (InterruptedException x) {
			UIDesignerPlugin.getDefault().log(x);
		}
	}

	public static IDesignerDataModel loadModel(IDesignerDataModelSpecifier modelSpecifier, StatusHolder holder) {
		LoadResult loadResult = modelSpecifier.load();
		IStatus status = loadResult.getStatus();
		if (status != null) {
			// try to use the message from the IStatus. If none default to generic error
			String reason = status.getMessage();
			if (TextUtils.isEmpty(reason)) {
				reason = Strings.getString("AbstractDesignerDataModelEditor.LoadFailedError"); //$NON-NLS-1$
			}
			holder.setStatus(makeOrReportEditorOpenStatus(reason, status)); //$NON-NLS-1$		
		}
		return loadResult.getModel();
	}

	public static IStatus makeOrReportEditorOpenStatus(String reasonString, IStatus status) {
        if (status == null) {
        	status = Logging.newStatus(UIDesignerPlugin.getDefault(), IStatus.ERROR, reasonString);
        }
		Logging.log(UIDesignerPlugin.getDefault(), status);
		return status;
	}
	
	public static boolean isRequestForTargetFeedback(Request request) {
		return RequestConstants.REQ_ADD.equals(request.getType()) || 
					RequestConstants.REQ_CREATE.equals(request.getType()) ||
							RequestConstants.REQ_MOVE.equals(request.getType());
	}

	public static boolean isNonLayoutComponent(IAdaptable adaptable) {
		if (adaptable != null) {
			IAttributes attributes = (IAttributes) adaptable.getAdapter(IAttributes.class);
			if (attributes != null)
				return attributes.getBooleanAttribute(CommonAttributes.IS_NON_LAYOUT_OBJECT, false);
		}
		
		return false;
	}

	public static boolean getNoTransientChildClipping(EObject object) {
		IAttributes attributes = Adapters.getAttributes(object);
		if (attributes != null)
			return attributes.getBooleanAttribute(CommonAttributes.NO_TRANSIENT_CHILD_CLIPPING, false);
		return false;
	}
}

