//------------------------------------------------------------------------------
// 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.library.xmi.internal.migration;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.util.EContentsEList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.EContentsEList.FeatureIterator;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.epf.common.serviceability.MsgBox;
import org.eclipse.epf.library.edit.TngAdapterFactory;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.library.util.ResourceUtil;
import org.eclipse.epf.library.xmi.XMILibraryPlugin;
import org.eclipse.epf.persistence.MultiFileResourceSetImpl;
import org.eclipse.epf.persistence.migration.IMigrator;
import org.eclipse.epf.persistence.migration.MigrationUtil;
import org.eclipse.epf.persistence.util.PersistenceResources;
import org.eclipse.epf.persistence.util.PersistenceUtil;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.VariabilityType;
import org.eclipse.epf.uma.ecore.impl.MultiResourceEObject;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.swt.widgets.Composite;

/**
 * @author Phong Nguyen Le - Jun 12, 2006
 * @since  1.0
 */
public class Migrator102 implements IMigrator {
	private static void updateStatus(IProgressMonitor monitor, String msg) {
		if (monitor != null) {
			monitor.subTask(msg);
			monitor.worked(1);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				//
			}
		} else {
			System.out.println(msg);
		}
	}

	private Collection proxiesToRemove = new ArrayList();
	private Map proxyToFileMap = new HashMap();
	private HashMap proxyToFileWithLoadErrorMap = new HashMap();
	private ArrayList notFoundProxies = new ArrayList();
	private ArrayList proxiesWithUnnormalizedURI = new ArrayList();
	private MethodLibrary lib;

	/* (non-Javadoc)
	 * @see org.eclipse.epf.persistence.migration.IMigrator#migrate(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
	 */
	public void migrate(String libPath, IProgressMonitor monitor) throws Exception {
		ResourceUtil.open(new File(libPath).getParent(), monitor);
		
		MultiFileResourceSetImpl resourceSet = null;
		try {
			// set 1.0.2 default values so data can be correctly loaded
			//
			setOldDefaultValues();
			
			// load the library
			//
			updateStatus(monitor, PersistenceResources.loadLibraryTask_name);
			resourceSet = new MultiFileResourceSetImpl(false);
			lib = resourceSet.loadLibrary(libPath);
			
			// verify the library
			//
			updateStatus(monitor, "Verifying...");
			verify();

			removeUnresolvedReferences(monitor);
			
			// load all elements in memory
			//
			updateStatus(monitor, PersistenceResources.loadResourcesTask_name);
			for (Iterator iter = lib.eAllContents(); iter.hasNext();) {
				EObject element = (EObject) iter.next();
				if (element instanceof MethodElement) {
					try {
						for (Iterator iterator = element.eCrossReferences()
								.iterator(); iterator.hasNext();) {
							iterator.next();
						}
					} catch (Exception e) {
						CommonPlugin.INSTANCE.log(e);
						System.err
						.println("Error iterate thru cross references of element: " + element); //$NON-NLS-1$
					}
					update((MethodElement) element, monitor);
				}			
			}

			removeOldDefaultValues();
			
			// check modified resources for writable before saving them
			//
			checkModifiedResources();
			
			// save all files
			//
			updateStatus(monitor, PersistenceResources.saveLibraryTask_name);			
			resourceSet.save(null, true);

			updateStatus(monitor, PersistenceResources.refreshLibraryFilesTask_name);
			ResourceUtil.refreshResources(lib, monitor);
		}
		finally {
			if(resourceSet != null) {
				resourceSet.reset();
				resourceSet = null;
			}
		}
	}

	/**
	 * 
	 */
	private void checkModifiedResources() {
		do {
			ResourceSet resourceSet = lib.eResource().getResourceSet();
			ArrayList readOnlyResources = new ArrayList();
			String pluginId = XMILibraryPlugin.getDefault().getId();
			MultiStatus status = new MultiStatus(pluginId, 0, "Cannot write to file(s)", null);
			for (Iterator iter = resourceSet.getResources().iterator(); iter.hasNext();) {
				Resource resource = (Resource) iter.next();
				File file = new File(resource.getURI().toFileString());
				if(!file.canWrite()) {
					readOnlyResources.add(resource);
					status.add(new Status(IStatus.ERROR, pluginId, 0, file.toString(), null));
				}
			}
			if(!status.isOK()) {
				String title = "Read-only file(s)";
				String msg = "The files listed in Details are read-only. Make them writable and click on Retry to continue.";
				ErrorDialog errDlg = new ErrorDialog(MsgBox.getDefaultShell(), title, msg, status, IStatus.OK
						| IStatus.INFO | IStatus.WARNING | IStatus.ERROR) {
					/* (non-Javadoc)
					 * @see org.eclipse.jface.dialogs.ErrorDialog#createButtonsForButtonBar(org.eclipse.swt.widgets.Composite)
					 */
					protected void createButtonsForButtonBar(Composite parent) {
				        // create Retry, Cancel and Details buttons
				        createButton(parent, IDialogConstants.OK_ID, "Retry",
				                true);
				        
				        createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
				        
				        createDetailsButton(parent);
					}
					
					/* (non-Javadoc)
					 * @see org.eclipse.jface.dialogs.ErrorDialog#open()
					 */
					public int open() {
						showDetailsArea();						
						return super.open();
					}

				};
				if(errDlg.open() == IDialogConstants.CANCEL_ID) {
					throw new OperationCanceledException();
				}
			} else {
				return;
			}
		} while(true);
	}

	/**
	 * @param monitor
	 */
	private void removeUnresolvedReferences(IProgressMonitor monitor) {
		if(proxiesToRemove.isEmpty()) return;
		updateStatus(monitor, "Removing unresolved references");
		HashSet GUIDs = new HashSet();
		for (Iterator iter = proxiesToRemove.iterator(); iter.hasNext();) {
			InternalEObject proxy = (InternalEObject) iter.next();
			GUIDs.add(proxy.eProxyURI().fragment());
			EcoreUtil.remove(proxy);
		}
		for (Iterator iter = lib.eAllContents(); iter.hasNext();) {
			EObject element = (EObject) iter.next();
			for (EContentsEList.FeatureIterator iterator = (FeatureIterator) element.eCrossReferences()
					.iterator(); iterator.hasNext();) {
				InternalEObject obj = (InternalEObject) iterator.next();
				if(obj.eIsProxy() && GUIDs.contains(obj.eProxyURI().fragment())) {
					EStructuralFeature feature = iterator.feature();
					if(feature.isChangeable() && !feature.isDerived()) {
						if(feature.isMany()) {
							((List)element.eGet(feature)).remove(obj);
						}
						else {
							element.eSet(feature, null);
						}
					}
				}
			}
		}
	}

	/**
	 * @param lib
	 */
	private void verify() {
		notFoundProxies.clear();
		proxiesToRemove.clear();
		proxyToFileMap.clear();
		proxyToFileWithLoadErrorMap.clear();
		proxiesWithUnnormalizedURI.clear();
		
		Collection proxies = PersistenceUtil.getProxies(lib);
		if(!proxies.isEmpty()) {
			ResourceSet resourceSet = lib.eResource().getResourceSet();
			URIConverter uriConverter = resourceSet.getURIConverter();
			for (Iterator iter = proxies.iterator(); iter.hasNext();) {
				InternalEObject proxy = (InternalEObject) iter.next();
				URI uri = proxy.eProxyURI();
				URI normalizedURI = uriConverter.normalize(uri);
				if(normalizedURI == null) {
					proxiesWithUnnormalizedURI.add(proxy);
				}
				else {
					File file = new File(normalizedURI.toFileString());
					if(!file.exists()) {
						proxyToFileMap.put(proxy, file);
					}
					else {
						try {
							Resource resource = resourceSet.getResource(normalizedURI.trimFragment(), true);
							if(resource.getEObject(normalizedURI.fragment()) == null) {
								notFoundProxies.add(proxy);
							}
						}
						catch(Exception e) {
							String errMsg = e.getMessage() != null ? e.getMessage() : e.toString();
							proxyToFileWithLoadErrorMap.put(proxy, new Object[] { file, errMsg });
						}
					}
				}
			}
		}
		
		if(!proxyToFileMap.isEmpty()) {
			// promp user to resolve missing files
			//
			List list = new ArrayList(proxyToFileMap.keySet());		
			final String FILE_PATH = "File path";
			final String ELEMENT_PATH = "Element path";
			ILabelProvider labelProvider = new AdapterFactoryLabelProvider(
					TngAdapterFactory.INSTANCE
					.getNavigatorView_ComposedAdapterFactory()) {

				/* (non-Javadoc)
				 * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider#getText(java.lang.Object)
				 */
				public String getText(Object object) {
					File file = (File) proxyToFileMap.get(object);
					return file.getAbsolutePath() + " (" + TngUtil.getLabelWithPath(object) + ')'; //$NON-NLS-1$
				}

				/* (non-Javadoc)
				 * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider#getColumnText(java.lang.Object, int)
				 */
				public String getColumnText(Object object, int columnIndex) {
					switch(columnIndex) {
					case 0: return proxyToFileMap.get(object).toString();
					case 1: return TngUtil.getLabelWithPath(object);
					}
					return null;
				}

			};

			try {
				String msg = "The following files are missing. Click OK if you want to remove every reference to the selected files in the library";
				SelectionDialog dlg = new SelectionDialog(MsgBox.getDefaultShell(), list, labelProvider, msg);

				dlg.setTitle("Missing file(s)");
				dlg.setBlockOnOpen(true);
				dlg.setInitialElementSelections(list);
				dlg.setColumnProperties(new String[] {
						FILE_PATH, ELEMENT_PATH
				});
				if (dlg.open() == Dialog.CANCEL) {
					throw new OperationCanceledException();
				}
				Object objs[] = dlg.getResult();
				if(objs == null) {
					throw new OperationCanceledException();
				}
				else {
					for (Iterator iter = list.iterator(); iter.hasNext();) {
						proxiesToRemove.add(iter.next());

					}
				}
			} finally {
				labelProvider.dispose();
			}
		}
		
		// prompt user to resolve files that can not be loaded
		//
		if(!proxyToFileWithLoadErrorMap.isEmpty()) {
			List list = new ArrayList(proxyToFileWithLoadErrorMap.keySet());		
			final String FILE_PATH = "File path";
			final String LOAD_ERROR = "Load error";
			ILabelProvider labelProvider = new AdapterFactoryLabelProvider(
					TngAdapterFactory.INSTANCE
					.getNavigatorView_ComposedAdapterFactory()) {

				/* (non-Javadoc)
				 * @see org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider#getColumnText(java.lang.Object, int)
				 */
				public String getColumnText(Object object, int columnIndex) {
					Object[] arr = (Object[]) proxyToFileMap.get(object);
					if(columnIndex < 2) {
						return arr[columnIndex].toString();
					}
					return null;
				}

			};

			try {
				String msg = "The following file(s) could not be loaded. Click OK if you want to remove every reference to the selected files in the library";
				SelectionDialog dlg = new SelectionDialog(MsgBox.getDefaultShell(), list, labelProvider, msg);

				dlg.setTitle("File(s) with load error");
				dlg.setBlockOnOpen(true);
				dlg.setInitialElementSelections(list);
				dlg.setColumnProperties(new String[] {
						FILE_PATH, LOAD_ERROR
				});
				if (dlg.open() == Dialog.CANCEL) {
					throw new OperationCanceledException();
				}
				Object objs[] = dlg.getResult();
				if(objs == null) {
					throw new OperationCanceledException();
				}
				else {
					for (Iterator iter = list.iterator(); iter.hasNext();) {
						proxiesToRemove.add(iter.next());

					}
				}
			} finally {
				labelProvider.dispose();
			}
		}
		
		ArrayList proxiesToRetain = new ArrayList();
		proxies.addAll(proxyToFileMap.keySet());
		proxies.addAll(proxyToFileWithLoadErrorMap.keySet());
		proxies.removeAll(proxiesToRemove);
		
		if(proxiesToRetain.isEmpty()) {
			proxiesToRemove.addAll(notFoundProxies);
			proxiesToRemove.addAll(proxiesWithUnnormalizedURI);
		}
		
		String msg = "Summary of unresolved proxies:";
		msg += "\n  Not found proxies: " + notFoundProxies;
		msg += "\n  Proxies with unnormalized URI: " + proxiesWithUnnormalizedURI;
		XMILibraryPlugin.getDefault().getLogger().logInfo(msg);
	}

	/**
	 * 
	 */
	private void removeOldDefaultValues() {
		MultiResourceEObject.removeDefaultValue(UmaPackage.eINSTANCE.getMethodPlugin_UserChangeable());
	}

	/**
	 * @param e 
	 * 
	 */
	private void adjustToNewDefaultValues(MethodElement e) {
		if(e instanceof MethodPlugin) {
			((MultiResourceEObject)e).removeFeatureWithOverridenDefaultValue(UmaPackage.eINSTANCE.getMethodPlugin_UserChangeable());
		}
	}

	/**
	 * 
	 */
	private void setOldDefaultValues() {
		MultiResourceEObject.setDefaultValue(UmaPackage.eINSTANCE.getMethodPlugin_UserChangeable(), Boolean.FALSE);
	}

	private void update(MethodElement e, IProgressMonitor monitor) throws Exception {
		adjustToNewDefaultValues(e);
		
		if (e instanceof Activity) {
			Activity act = (Activity)e;
			VariabilityType type = act.getVariabilityType();
			if (type == VariabilityType.CONTRIBUTES_LITERAL) {
				act.setVariabilityType(VariabilityType.LOCAL_CONTRIBUTION_LITERAL);
			} else if (type == VariabilityType.REPLACES_LITERAL) {
				act.setVariabilityType(VariabilityType.LOCAL_REPLACEMENT_LITERAL);
			}
		}
		MigrationUtil.formatValue(e);
	}

}
