//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 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 implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.authoring.ui.views;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.InternalEList;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.epf.authoring.ui.AuthoringUIPlugin;
import org.eclipse.epf.authoring.ui.AuthoringUIResources;
import org.eclipse.epf.common.serviceability.MsgBox;
import org.eclipse.epf.common.serviceability.MsgDialog;
import org.eclipse.epf.library.ILibraryManager;
import org.eclipse.epf.library.LibraryService;
import org.eclipse.epf.library.edit.FeatureValueWrapperItemProvider;
import org.eclipse.epf.library.edit.ui.UserInteractionHelper;
import org.eclipse.epf.library.edit.util.Misc;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.library.ui.LibraryUIManager;
import org.eclipse.epf.uma.BreakdownElementDescription;
import org.eclipse.epf.uma.ContentDescription;
import org.eclipse.epf.uma.DescribableElement;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.NamedElement;
import org.eclipse.epf.uma.ProcessComponent;
import org.eclipse.epf.uma.util.ContentDescriptionFactory;
import org.eclipse.epf.uma.util.IMethodLibraryPersister;
import org.eclipse.epf.uma.util.UmaUtil;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;

/**
 * A helper class for managing the views and editors in the Authoring
 * perspective.
 * 
 * @author Phong Nguyen Le
 * @author Kelvin Low
 * @since 1.0
 */
public final class ViewHelper {

	/**
	 * Prompts the user to save the open library if it has been modified.
	 * 
	 * @return <code>true<code> if the user saved or discarded the change,
	 * 		   <code>false</code> if the user cancelled the action that
	 *         triggered this call.
	 */
	public static boolean promptSave() {
		ILibraryManager manager = (ILibraryManager) LibraryService
				.getInstance().getCurrentLibraryManager();
		if (manager != null && manager.isMethodLibraryModified()) {
			int ret = MsgBox.prompt(AuthoringUIResources.saveLibraryDialog_title, //$NON-NLS-1$
					AuthoringUIResources.saveLibraryDialog_text, //$NON-NLS-1$
					SWT.YES | SWT.NO | SWT.CANCEL);
			switch (ret) {
			case SWT.YES:
				try {
					LibraryService.getInstance().saveCurrentMethodLibrary();
				} catch (Exception e) {
					MsgDialog dialog = AuthoringUIPlugin.getDefault()
							.getMsgDialog();
					dialog
							.displayError(
									AuthoringUIResources.saveLibraryDialog_title, //$NON-NLS-1$
									AuthoringUIResources.saveLibraryError_msg, //$NON-NLS-1$
									AuthoringUIResources.error_reason, e); //$NON-NLS-1$

					return dialog
							.displayPrompt(
									AuthoringUIResources.openLibraryDialog_title, //$NON-NLS-1$
									AuthoringUIResources.openLibraryDialog_text); //$NON-NLS-1$
				}
				break;
			case SWT.NO:
				// Discard all changes by resetting all resources as unchanged.
				manager.discardMethodLibraryChanges();
				break;
			case SWT.CANCEL:
				return false;
			}
		}
		return true;
	}

	public static int promptSaveInt() {
		ILibraryManager manager = (ILibraryManager) LibraryService
				.getInstance().getCurrentLibraryManager();
		if (manager != null && manager.isMethodLibraryModified()) {
			int ret = MsgBox.prompt(AuthoringUIResources.saveLibraryDialog_title, //$NON-NLS-1$
					AuthoringUIResources.saveLibraryDialog_text, //$NON-NLS-1$
					SWT.YES | SWT.NO | SWT.CANCEL);
			switch (ret) {
			case SWT.YES:
				try {
					LibraryService.getInstance().saveCurrentMethodLibrary();
				} catch (Exception e) {
					MsgDialog dialog = AuthoringUIPlugin.getDefault()
							.getMsgDialog();
					dialog
							.displayError(
									AuthoringUIResources.saveLibraryDialog_title, //$NON-NLS-1$
									AuthoringUIResources.saveLibraryError_msg, //$NON-NLS-1$
									AuthoringUIResources.error_reason, e); //$NON-NLS-1$
				}
				return SWT.YES;
			case SWT.NO:
				// Discard all changes by resetting all resources as unchanged.
				manager.discardMethodLibraryChanges();
				return SWT.NO;
			case SWT.CANCEL:
				return SWT.CANCEL;
			}
		}

		return SWT.CANCEL;
	}

	public static void closeAllEditors() {
		PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
				.closeAllEditors(true);
	}

	public static void closeMessageView() {
		PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage()
				.hideView(MessageView.getView());
	}

	public static Object handleDangling(Object object) {
		if (object instanceof MethodElement
				&& ((EObject) object).eResource() == null) {
			AuthoringUIPlugin.getDefault().getMsgDialog().displayError(
					AuthoringUIResources.errorDialog_title, //$NON-NLS-1$
					AuthoringUIResources.bind(AuthoringUIResources.elementAlreadyDeletedError_msg, ((MethodElement) object).getName()));
			return null;
		} else if (object instanceof FeatureValueWrapperItemProvider) {
			FeatureValueWrapperItemProvider adapter = (FeatureValueWrapperItemProvider) object;
			Object value = adapter.getValue();
			if (value instanceof MethodElement
					&& ((EObject) value).eResource() == null) {
				Object owner = TngUtil.unwrap(adapter.getParent(value));
				if (owner instanceof ItemProviderAdapter) {
					// This is a UI item provider.
					owner = ((ItemProviderAdapter) owner).getTarget();
				}
				String ownerName = ((MethodElement) owner).getName();
				EStructuralFeature feature = adapter.getFeature();
				if (feature != null) {
					if (AuthoringUIPlugin
							.getDefault()
							.getMsgDialog()
							.displayPrompt(
									AuthoringUIResources.deleteDialog_title, //$NON-NLS-1$						
									AuthoringUIResources.bind(AuthoringUIResources.ViewHelper_alreadydeletedconfirm_text, ((MethodElement) value)
									.getName(), ownerName))) { //$NON-NLS-1$
						// Remove the association.
						if (feature.isMany()) {
							((Collection) ((EObject) owner).eGet(feature))
									.remove(value);
						}
					}
				} else {
					AuthoringUIPlugin
							.getDefault()
							.getMsgDialog()
							.displayError(
									AuthoringUIResources.errorDialog_title, //$NON-NLS-1$
									AuthoringUIResources.bind(AuthoringUIResources.elementAlreadyDeletedError_msg, ((MethodElement) value)
									.getName()));
				}
				return null;
			}
		}
		return object;
	}

	/* TODO: Is this still needed?
	public static void loadAllAndSaveAll() {
		final MethodLibrary lib = LibraryService.getInstance()
				.getCurrentMethodLibrary();
		if (lib == null)
			return;

		// Do the work within an operation because this is a long running
		// activity that modifies the workbench.
		WorkspaceModifyOperation operation = new WorkspaceModifyOperation() {
			// This is the method that gets invoked when the operation runs.
			public void execute(IProgressMonitor monitor) {
				monitor.beginTask(AuthoringUIResources
						.getString("AuthoringUI.upgradingLibraryTask.name"), 3); //$NON-NLS-1$
				try {
					try {
						monitor.worked(1);
						monitor
								.setTaskName(AuthoringUIResources
										.getString("AuthoringUI.loadingLibraryElementsTask.name")); //$NON-NLS-1$
						ModelStorage.loadAllProxies(lib);
					} catch (Exception e) {
						AuthoringUIPlugin
								.getDefault()
								.getMsgDialog()
								.displayError(
										AuthoringUIResources
												.getString("AuthoringUI.upgradeLibraryDialog.title"), //$NON-NLS-1$
										AuthoringUIResources
												.getString("AuthoringUI.upgradeLibraryError.msg"), //$NON-NLS-1$
										AuthoringUIResources
												.getString("AuthoringUI.upgradeLibraryError.reason"), //$NON-NLS-1$
										e);
						return;
					}
					try {
						monitor.worked(1);
						monitor
								.setTaskName(AuthoringUIResources
										.getString("AuthoringUI.savingUpgradedElementsTask.name")); //$NON-NLS-1$
						MultiFileResourceSetImpl resourceSet = (MultiFileResourceSetImpl) lib
								.eResource().getResourceSet();
						resourceSet.save(LibraryProcessor.getInstance()
								.getSaveOptions(), true);
					} catch (Exception e) {
						AuthoringUIPlugin
								.getDefault()
								.getMsgDialog()
								.displayError(
										AuthoringUIResources
												.getString("AuthoringUI.upgradeLibraryDialog.title"), //$NON-NLS-1$
										AuthoringUIResources
												.getString("AuthoringUI.upgradeLibraryError.msg"), //$NON-NLS-1$
										AuthoringUIResources
												.getString("AuthoringUI.saveUpgradedLibraryError.reason"), //$NON-NLS-1$
										e);
					}
				} finally {
					monitor.done();
				}
			}
		};

		try {
			// Run the operation and display the progress.
			new ProgressMonitorDialog(Display.getDefault().getActiveShell())
					.run(true, false, operation);
		} catch (Exception e) {
			AuthoringUIPlugin
					.getDefault()
					.getMsgDialog()
					.displayError(
							AuthoringUIResources
									.getString("AuthoringUI.upgradeLibraryDialog.title"), //$NON-NLS-1$
							AuthoringUIResources
									.getString("AuthoringUI.upgradeLibraryError.msg"), //$NON-NLS-1$
							AuthoringUIResources
									.getString("AuthoringUI.internalError.reason"), //$NON-NLS-1$
							e);
		}
	}
	*/

	/**
	 * Checks if the current selection is locked.
	 */
	public static boolean isLocked(IStructuredSelection selection) {
		for (Iterator iter = selection.iterator(); iter.hasNext();) {
			Object element = iter.next();
			while (element instanceof ITreeItemContentProvider) {
				element = ((ITreeItemContentProvider) element).getParent(null);
			}
			element = TngUtil.unwrap(element);
			if (element instanceof EObject
					&& TngUtil.isLocked((EObject) element)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Fixes the content description GUIDs.
	 */
	public static void fixContentDescriptionGUIDs() {
		final MethodLibrary lib = LibraryService.getInstance()
				.getCurrentMethodLibrary();
		if (lib == null)
			return;

		IRunnableWithProgress runnable = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor)
					throws InvocationTargetException, InterruptedException {
				HashSet modifiedResources = new HashSet();
				for (Iterator iter = lib.eAllContents(); iter.hasNext();) {
					InternalEObject element = (InternalEObject) iter.next();
					if (element.eProxyURI() == null) {
						if (element instanceof ContentDescription) {
							ContentDescription content = (ContentDescription) element;
							DescribableElement container = (DescribableElement) element
									.eContainer();
							if (container != null) {
								String guid = UmaUtil.generateGUID(container
										.getGuid());
								if (!guid.equals(content.getGuid())) {
									content.setGuid(guid);
									modifiedResources.add(content.eResource());
									modifiedResources
											.add(container.eResource());
								}
							}
						}
					} else {
						AuthoringUIPlugin
								.getDefault()
								.getLogger()
								.logError(
										"Unresolved proxy in '" + element.eResource().getURI().toFileString() + "': " + element); //$NON-NLS-1$ //$NON-NLS-2$
					}
				}

				monitor.subTask(AuthoringUIResources.savingFilesTask_name); //$NON-NLS-1$
				IMethodLibraryPersister.FailSafeMethodLibraryPersister persister = ContentDescriptionFactory
						.getMethodLibraryPersister().getFailSafePersister();
				try {
					for (Iterator iter = modifiedResources.iterator(); iter
							.hasNext();) {
						Resource resource = (Resource) iter.next();
						monitor.subTask(AuthoringUIResources.bind(AuthoringUIResources.savingTask_name, resource.getURI().toFileString()));
						persister.save(resource);
					}
					persister.commit();
				} catch (Exception e) {
					persister.rollback();
					throw new WrappedException(e);
				}
			}

		};
		UserInteractionHelper
				.runWithProgress(
						runnable,
						AuthoringUIResources.fixingContentDescriptionGUIDsTask_name); //$NON-NLS-1$
	}

	private static String checkProxy(InternalEObject element, EReference ref,
			Object value) {
		if (value instanceof InternalEObject) {
			InternalEObject eObj = (InternalEObject) value;
			if (eObj.eIsProxy()) {
				EObject resolved = element.eResolveProxy(eObj);
				String errMsg = null;
				if (resolved == eObj) {
					errMsg = "Unresolved proxy"; //$NON-NLS-1$
				} else if (!ref.getEType().isInstance(resolved)) {
					errMsg = "Invalid data"; //$NON-NLS-1$
				}
				if (errMsg != null) {
					String path;
					if (element instanceof NamedElement) {
						path = ref.getEType().getName()
								+ "(" + Misc.getPathRelativeToLibrary((NamedElement) element) + ")." + ref.getName() + " = "; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					} else {
						path = ""; //$NON-NLS-1$
					}
					return errMsg
							+ " in '" + element.eResource().getURI().toFileString() + "': " + path + resolved; //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		}
		return null;
	}

	public static void checkLibraryHealth() {
		final MethodLibrary lib = LibraryService.getInstance()
				.getCurrentMethodLibrary();
		if (lib == null)
			return;

		IRunnableWithProgress runnable = new IRunnableWithProgress() {

			public void run(IProgressMonitor monitor)
					throws InvocationTargetException, InterruptedException {
				AuthoringUIPlugin.getDefault().getLogger().logInfo(
						"++++ LIBRARY HEALTH CHECK REPORT - START +++"); //$NON-NLS-1$
				StringWriter strWriter = new StringWriter();
				PrintWriter printWriter = new PrintWriter(strWriter);
				printWriter.println();
				printWriter
						.println("UNRESOLVED/INVALID PROXIES IN X-REFERENCES"); //$NON-NLS-1$
				printWriter
						.println("------------------------------------------"); //$NON-NLS-1$
				for (Iterator iter = lib.eAllContents(); iter.hasNext();) {
					InternalEObject element = (InternalEObject) iter.next();
					if (element.eProxyURI() == null) {
						if (element instanceof ContentDescription) {
							ContentDescription content = (ContentDescription) element;
							DescribableElement container = (DescribableElement) element
									.eContainer();
							if (container != null) {
								String guid = UmaUtil.generateGUID(container
										.getGuid());
								if (!guid.equals(content.getGuid())) {
									AuthoringUIPlugin
											.getDefault()
											.getLogger()
											.logError(
													"ContentDescription with invalid GUID: " + content.getGuid() + " in '" + content.eResource().getURI().toFileString() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
								}
							} else {
								AuthoringUIPlugin
										.getDefault()
										.getLogger()
										.logError(
												"ContentDescription without a container: " + content); //$NON-NLS-1$
							}

							if (content instanceof BreakdownElementDescription) {
								// check if the content.xmi of the process is in
								// the right place
								ProcessComponent procComp = UmaUtil
										.getProcessComponent(content);
								if (procComp != null) {
									String modelPath = procComp.eResource()
											.getURI().toFileString();
									File dir = new File(modelPath)
											.getParentFile();
									String contentPath = content.eResource()
											.getURI().toFileString();

									// System.out.println("model path: " +
									// modelPath);
									// System.out.println("content path: " +
									// contentPath);
									// System.out.println();

									File contentDir = new File(contentPath)
											.getParentFile();
									if (!dir.equals(contentDir)) {
										AuthoringUIPlugin
												.getDefault()
												.getLogger()
												.logError(
														"Content file of " + container.eClass().getName() + " '" + container.getName() + "' in '" + modelPath + " is misplaced: " + contentPath); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
									}
								}
							}
						}

						// check for unresolved proxies in cross references
						ArrayList xReferences = new ArrayList(element.eClass()
								.getEAllReferences());
						xReferences.removeAll(element.eClass()
								.getEAllContainments());
						for (Iterator iterator = xReferences.iterator(); iterator
								.hasNext();) {
							EReference ref = (EReference) iterator.next();
							Object value = element.eGet(ref, false);
							if (ref.isMany()) {
								if (value instanceof InternalEList) {
									InternalEList list = (InternalEList) value;
									for (Iterator iter1 = list.basicIterator(); iter1
											.hasNext();) {
										String msg = checkProxy(element, ref,
												iter1.next());
										if (msg != null) {
											printWriter.println(msg);
										}
									}
								}
							} else {
								String msg = checkProxy(element, ref, value);
								if (msg != null) {
									printWriter.println(msg);
								}
							}
						}
					} else {
						AuthoringUIPlugin
								.getDefault()
								.getLogger()
								.logError(
										"Unresolved proxy in '" + element.eResource().getURI().toFileString() + "': " + element); //$NON-NLS-1$ //$NON-NLS-2$
					}

				}
				AuthoringUIPlugin.getDefault().getLogger().logError(
						strWriter.toString());
				AuthoringUIPlugin.getDefault().getLogger().logInfo(
						"++++ LIBRARY HEALTH CHECK REPORT - END +++"); //$NON-NLS-1$
			}

		};
		if (UserInteractionHelper
				.runWithProgress(runnable, AuthoringUIResources.viewHelper_performHealthCheck)) { //$NON-NLS-1$
			String title = AuthoringUIResources.viewHelperHealthCheckDialog_title; //$NON-NLS-1$
			String message = AuthoringUIResources.viewHelperHealthCheckDialog_message; //$NON-NLS-1$
			AuthoringUIPlugin.getDefault().getMsgDialog().displayInfo(title,
					message);
		}
	}

	public static void removeInvalidReferences() {
		final MethodLibrary lib = LibraryService.getInstance()
				.getCurrentMethodLibrary();
		if (lib == null)
			return;

		IRunnableWithProgress runnable = new IRunnableWithProgress() {
			public void run(IProgressMonitor monitor)
					throws InvocationTargetException, InterruptedException {
				HashSet modifiedResources = new HashSet();
				for (Iterator iter = lib.eAllContents(); iter.hasNext();) {
					InternalEObject element = (InternalEObject) iter.next();
					if (element.eProxyURI() == null) {
						ArrayList xReferences = new ArrayList(element.eClass()
								.getEAllReferences());
						xReferences.removeAll(element.eClass()
								.getEAllContainments());
						for (Iterator iterator = xReferences.iterator(); iterator
								.hasNext();) {
							EReference ref = (EReference) iterator.next();
							Object value = element.eGet(ref, false);
							if (ref.isMany()) {
								if (value instanceof InternalEList) {
									InternalEList list = (InternalEList) value;
									ArrayList invalidProxies = new ArrayList();
									for (Iterator iter1 = list.basicIterator(); iter1
											.hasNext();) {
										Object v = iter1.next();
										if (isInvalidReference(element, ref, v)) {
											invalidProxies.add(v);
										}
									}
									if (!invalidProxies.isEmpty()) {
										removeInvalidReferences(element, list,
												invalidProxies);
										modifiedResources.add(element
												.eResource());
									}
								}
							} else {
								if (isInvalidReference(element, ref, value)) {
									element.eSet(ref, null);
									modifiedResources.add(element.eResource());
								}
							}
						}
					} else {
						AuthoringUIPlugin
								.getDefault()
								.getLogger()
								.logError(
										"Unresolved proxy in '" + element.eResource().getURI().toFileString() + "': " + element); //$NON-NLS-1$ //$NON-NLS-2$
					}
				}

				monitor.subTask(AuthoringUIResources.savingFilesTask_name); //$NON-NLS-1$
				IMethodLibraryPersister.FailSafeMethodLibraryPersister persister = ContentDescriptionFactory
						.getMethodLibraryPersister().getFailSafePersister();
				try {
					for (Iterator iter = modifiedResources.iterator(); iter
							.hasNext();) {
						Resource resource = (Resource) iter.next();
						monitor.subTask(AuthoringUIResources.bind(AuthoringUIResources.savingTask_name, resource.getURI().toFileString()));
						persister.save(resource);
					}
					persister.commit();
				} catch (Exception e) {
					persister.rollback();
					throw new WrappedException(e);
				}
			}

			private boolean isInvalidReference(InternalEObject element,
					EReference ref, Object value) {
				if (value instanceof InternalEObject) {
					InternalEObject eObj = (InternalEObject) value;
					if (eObj.eIsProxy()) {
						EObject resolved = element.eResolveProxy(eObj);
						if (!ref.getEType().isInstance(resolved)) {
							return true;
						}
					}
				}
				return false;
			}

			private void removeInvalidReferences(InternalEObject element,
					InternalEList values, Collection invalidProxies) {
				ArrayList list = new ArrayList(values.basicList());
				list.removeAll(invalidProxies);
				values.clear();
				int max = list.size() - 1;
				for (int i = max; i > -1; i--) {
					list.set(i, element.eResolveProxy((InternalEObject) list
							.get(i)));
				}
				values.addAll(list);
			}

		};
		UserInteractionHelper.runWithProgress(runnable, AuthoringUIResources.deletingInvalidReferencesTask_name); //$NON-NLS-1$

	}

	public static void reloadCurrentLibaryOnRollbackError(Shell shell) {
		reloadCurrentLibrary(shell, AuthoringUIResources.ViewHelper_reloadLibOnRollbackError); //$NON-NLS-1$
	}

	public static void reloadCurrentLibrary(Shell shell, String message) {
		if (shell == null) {
			MsgBox.getDefaultShell();
		}
		String title = AuthoringUIResources.reloadDialog_title; //$NON-NLS-1$
		if (message == null) {
			message = AuthoringUIResources.reloadDialog_message; //$NON-NLS-1$
		}
		AuthoringUIPlugin.getDefault().getMsgDialog().displayInfo(title,
				message);

		// The library needs to be reloaded.
		String libDir = LibraryService.getInstance()
				.getCurrentMethodLibraryPath();
		LibraryUIManager.getInstance().openLibrary(libDir);
	}

	public static IViewPart openView(String viewId) {
		try {
			IWorkbenchPage activePage = PlatformUI.getWorkbench()
					.getActiveWorkbenchWindow().getActivePage();
			if (activePage != null) {
				IViewPart view = activePage.findView(viewId);
				if (view == null) {
					view = activePage.showView(viewId);
				}
				return view;
			}
		} catch (Exception e) {
			AuthoringUIPlugin.getDefault().getMsgDialog().displayError(
					AuthoringUIResources.errorDialog_title, //$NON-NLS-1$
					AuthoringUIResources.internalError_msg, //$NON-NLS-1$
					e);
		}
		return null;

	}
}
