//------------------------------------------------------------------------------
// 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.importing.xml.services;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
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.Platform;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.sdo.EDataObject;
import org.eclipse.epf.common.serviceability.MsgDialog;
import org.eclipse.epf.common.serviceability.VersionUtil;
import org.eclipse.epf.dataexchange.util.ContentProcessor;
import org.eclipse.epf.dataexchange.util.IResourceHandler;
import org.eclipse.epf.export.xml.services.FeatureManager;
import org.eclipse.epf.export.xml.services.XMLLibrary;
import org.eclipse.epf.importing.services.FileModifyChecker;
import org.eclipse.epf.importing.services.LibraryImportManager;
import org.eclipse.epf.importing.xml.ImportXMLPlugin;
import org.eclipse.epf.importing.xml.ImportXMLResources;
import org.eclipse.epf.library.LibraryService;
import org.eclipse.epf.library.services.SafeUpdateController;
import org.eclipse.epf.library.util.LibraryUtil;
import org.eclipse.epf.library.util.ResourceUtil;
import org.eclipse.epf.persistence.refresh.RefreshJob;
import org.eclipse.epf.xml.uma.CapabilityPattern;
import org.eclipse.epf.xml.uma.ContentCategory;
import org.eclipse.epf.xml.uma.ContentCategoryPackage;
import org.eclipse.epf.xml.uma.ContentPackage;
import org.eclipse.epf.xml.uma.DeliveryProcess;
import org.eclipse.epf.xml.uma.Discipline;
import org.eclipse.epf.xml.uma.Domain;
import org.eclipse.epf.xml.uma.MethodElement;
import org.eclipse.epf.xml.uma.MethodLibrary;
import org.eclipse.epf.xml.uma.MethodPackage;
import org.eclipse.epf.xml.uma.MethodPlugin;
import org.eclipse.epf.xml.uma.ProcessComponent;
import org.eclipse.epf.xml.uma.ProcessPackage;
import org.eclipse.epf.xml.uma.WorkOrder;
import org.eclipse.epf.xml.uma.WorkOrderType;
import org.eclipse.osgi.util.NLS;

/**
 * Xml import service class to process the importing task
 * 
 * @author Jinhua Xi
 * @since 1.0
 */
public class ImportXMLService {
	
	private boolean error = false;
	
	private Map renameElementMap = new HashMap();
	
	private boolean overwrite = false;
	
	private IStatus fileCheckedOutStatus = null;
	
	private String xmlPath;

	private XMLLibrary xmlLib;

	private UmaLibrary umaLib;

	private ImportXMLogger logger = new ImportXMLogger();

	private List discardedElements = new ArrayList();

	// map xml WorkOrder to uma WorkOrder
	Map workOrderMap = new HashMap();

	public ImportXMLService() {

	}

	public boolean loadXml(final String xmlPath) {
		String versionError = versionCheck(xmlPath,	ImportXMLResources.importXMLWizard_title);		
		if (versionError != null) {
			return false;
		}
				
		this.xmlPath = xmlPath;

		logger.logMessage("Loading xml library ..."); //$NON-NLS-1$
		this.xmlLib = new XMLLibrary(null, logger, xmlPath);
		this.xmlLib.load();
		return true;
	}

	public File getLogPath() {
		return logger.getLogPath();
	}

	public void dispose() {

		discardedElements.clear();
		workOrderMap.clear();
		logger.dispose();
	}

	public void doImport(IProgressMonitor monitor) throws Exception {
		boolean refresh = RefreshJob.getInstance().isEnabled();
		try {
			if (refresh) {
				// disable resource refreshing during import
				//
				RefreshJob.getInstance().setEnabled(false);
			}
			IResourceHandler handler = new ImportResourceHandler(new File(
					xmlPath).getParentFile(), new File(LibraryService
					.getInstance().getCurrentMethodLibraryPath()));

			ContentProcessor contentProc = new ContentProcessor(handler,
					this.logger);

			this.xmlLib.fixLibrary();

			// make sure all the dependent plugins are there
			List referencedPlugins = xmlLib.getReferencedPlugins();
			if (referencedPlugins.size() > 0) {
				org.eclipse.epf.uma.MethodLibrary library = org.eclipse.epf.library.LibraryService
						.getInstance().getCurrentMethodLibrary();
				if (library != null) {
					List plugins = library.getMethodPlugins();
					for (Iterator it = plugins.iterator(); it.hasNext();) {
						String id = ((org.eclipse.epf.uma.MethodPlugin) it
								.next()).getGuid();
						referencedPlugins.remove(id);
					}
				}
			}

			if (referencedPlugins.size() > 0) {
				for (Iterator it = referencedPlugins.iterator(); it.hasNext();) {
					String id = (String) it.next();
					logger.logError(NLS.bind(ImportXMLResources.importXMLService_missing_plugin, id),
							null);
				}

				throw new Exception(
						ImportXMLResources.importXMLService_error_missing_plugins);
			}

			this.umaLib = new UmaLibrary(contentProc, logger, overwrite);

			EDataObject xmlRoot = this.xmlLib.getRoot();
			EDataObject umaRoot = this.umaLib.getRoot();
			
			logger.logMessage("create target library elements ..."); //$NON-NLS-1$
			creatEDataObjectTree(xmlRoot, umaRoot);

			logger
					.logMessage("copying all feature values for target library elements ..."); //$NON-NLS-1$
			iteratEDataObject((MethodElement) xmlRoot);

			logger.logMessage("fix the target library ..."); //$NON-NLS-1$
			umaLib.fixLibrary();

			LibraryImportManager.handleNameReplace(renameElementMap);
			
			// delete the un-needed elements
			logger.logMessage("delete unneeded elements ..."); //$NON-NLS-1$
			umaLib.deleteElements();
			
			final List modifiedFiles = FileModifyChecker.getModifiedFiles(LibraryService.getInstance().
										getCurrentMethodLibrary().eResource().getResourceSet());
			final IStatus fileCheckedOutStatus = FileModifyChecker.syncExecCheckModify(modifiedFiles);
			
			if (fileCheckedOutStatus.isOK()) {
			
				logger.logMessage("saving target library ..."); //$NON-NLS-1$
	
				// don't refresh the workspace
				LibraryUtil.saveLibrary(LibraryService.getInstance()
						.getCurrentMethodLibrary(), false, false);
	
				logger.logMessage("re-open target library ..."); //$NON-NLS-1$
				LibraryService.getInstance().reopenCurrentMethodLibrary();
	
				logger.logMessage("import completed successfully ..."); //$NON-NLS-1$
			} else {
				SafeUpdateController.syncExec(new Runnable() {
					public void run() {
						String title = ImportXMLResources.importXMLWizard_title; 
						String msg = ImportXMLResources.importXMLService_import_failed; 
						new MsgDialog(ImportXMLPlugin.getDefault())
							.displayError(title, msg, fileCheckedOutStatus);
						}
				});
			}	
						
			ResourceUtil.refreshResources(LibraryService.getInstance()
					.getCurrentMethodLibrary(), monitor);

		} catch (Exception e) {
			logger.logError(NLS.bind(ImportXMLResources.importXMLService_import_failed, e.getMessage()), e);

			throw e;

		} finally {
			if (refresh) {
				// re-enable resource refreshing
				//
				RefreshJob.getInstance().setEnabled(true);
			}
		}
	}

	private void creatEDataObjectTree(EDataObject xmlObj, EDataObject umaObj) {

		for (Iterator it = xmlObj.eContents().iterator(); it.hasNext();) {
			EDataObject child = (EDataObject) it.next();
			createRmcObject(child, umaObj);
		}
	}

	private void createRmcObject(EDataObject xmlElement,
			EDataObject targetContainer) {

		// get the targetContainer's containing feature
		EStructuralFeature feature = xmlElement.eContainmentFeature();
		createRmcObject(xmlElement, targetContainer, feature.getName());
	}

	private void createRmcObject(EDataObject xmlElement,
			EDataObject targetContainer, String containmentXmlFeature) {

		try {
			if (xmlElement == null) {
				return;
			}

			EDataObject umaElement = null;

			// don't create content category package since in uma, it's
			// seperated based on type
			if (xmlElement instanceof MethodPlugin) {
				MethodPlugin plugin = (MethodPlugin) xmlElement;
				umaElement = umaLib.createMethodPlugin(plugin.getId(), plugin
						.getName());
			} else if (xmlElement instanceof ContentCategoryPackage) {
				// don't create, do nothing
				// mark as discarded so we don't need to copy it's attributes
				setDiscarded(((ContentCategoryPackage) xmlElement).getId());

			} else if ((xmlElement instanceof ContentCategory)
					&& !(xmlElement.eContainer() instanceof Domain 
							|| xmlElement.eContainer() instanceof Discipline)
					|| (xmlElement.eContainer() instanceof ContentCategoryPackage)) {
				// create the content categories for root content categories,
				// not for sub-domain and sub-discipline
				// 154143 - import xml did not reserve the sub-discipline structure
				String pluginId = getPlugin(xmlElement).getId();
				umaElement = umaLib.createContentCategory(pluginId, xmlElement
						.eClass().getName(), xmlElement.getType().getName(),
						xmlLib.getElementId(xmlElement));
			} else {

				// if this is root level package, find the corresponding
				// container for the UMA object
				if (xmlElement instanceof MethodPackage
						&& xmlElement.eContainer() instanceof MethodPlugin) {
					if (xmlElement instanceof ContentPackage) {
						targetContainer = umaLib
								.getContentRootPackage(targetContainer);
					} else if (xmlElement instanceof ProcessComponent) {
						Object proc = ((ProcessComponent) xmlElement)
								.getProcess();
						if (proc instanceof CapabilityPattern) {
							targetContainer = umaLib
									.getCapabilityPatternRootPackage(targetContainer);
						} else if (proc instanceof DeliveryProcess) {
							targetContainer = umaLib
									.getDeliveryProcessRootPackage(targetContainer);
						}
					} else if (xmlElement instanceof ProcessPackage) {
						List procs = xmlLib
								.getAllProcesses((MethodPackage) xmlElement);
						if (procs != null && procs.size() > 0) {
							Object proc = procs.get(0);
							if (proc instanceof CapabilityPattern) {
								targetContainer = umaLib
										.getCapabilityPatternRootPackage(targetContainer);
							} else if (proc instanceof DeliveryProcess) {
								targetContainer = umaLib
										.getDeliveryProcessRootPackage(targetContainer);
							}
						}
					}
				}
				// get the containment feature so we can create the object of
				// the same type
				umaElement = umaLib.createElement(targetContainer,
						containmentXmlFeature, xmlElement.eClass().getName(),
						xmlElement.getType().getName(), xmlLib
								.getElementId(xmlElement));

				// xml WorkOrder does not have an id, need to map the uma Object
				if (xmlElement instanceof WorkOrder) {
					workOrderMap.put(xmlElement, umaElement);
				}
			}

			// recursive,
			// note, uma element might be null since we can't determine the
			// owner of the element yet
			creatEDataObjectTree(xmlElement, umaElement);

		} catch (Exception e) {
			// log the error and continue???
			e.printStackTrace();
		}
	}

	private MethodPlugin getPlugin(EObject element) {
		while ((element != null) && !(element instanceof MethodPlugin)) {
			element = element.eContainer();
		}

		return (MethodPlugin) element;
	}

	private EDataObject getRmcObject(MethodElement xmlObj) {
		if (xmlObj instanceof MethodLibrary) {
			return umaLib.getRoot();
		} else {
			String id = xmlObj.getId();
			return this.umaLib.getElement(id);
		}
	}

	private void iteratEDataObject(MethodElement srcObj) {

		logger
				.logMessage(" === processing element " + srcObj.getType().getName() + ": " + srcObj.getName()); //$NON-NLS-1$ //$NON-NLS-2$

		if (isProcessed(srcObj.getId())) {
			return;
		}
		setProcessed(srcObj.getId());

		// if object is not created, it's xml wrapper package
		if (srcObj instanceof ContentCategoryPackage) {

			// iterate the child packages
			for (Iterator it = ((ContentCategoryPackage) srcObj)
					.getContentCategory().iterator(); it.hasNext();) {
				iteratEDataObject((MethodElement) it.next());
			}
			return;
		}

		EDataObject targetObj = getRmcObject(srcObj);
		EClass eClass = srcObj.eClass();

		EList features = eClass.getEAllStructuralFeatures();
		boolean isNewElement = umaLib.isNewElement(srcObj.getId()) || srcObj instanceof MethodLibrary;
		
		for (Iterator it = features.iterator(); it.hasNext();) {
			EStructuralFeature feature = (EStructuralFeature) it.next();
						
			if (FeatureManager.INSTANCE.isUnneededXmlFeature(feature.getName())) {
				continue;
			}

			Object value = srcObj.eGet(feature);
			if (value == null) {
				continue;
			}

			try {
				if (value instanceof List) {
					for (Iterator itv = ((List) value).iterator(); itv
							.hasNext();) {
						Object src_value = itv.next();

						if (src_value instanceof String) {

							// it's an id reference
							umaLib.setReferenceValue(targetObj, feature
									.getName(), (String) src_value);
						} else if (src_value instanceof MethodElement) {
							// set the reference
							MethodElement e = (MethodElement) src_value;
							String guid = e.getId();

							// don't set the containment feature value since
							// it's already handled
							// when creating the library structure
							if (!isDiscarded(guid) && e.eContainer() != srcObj) {
								umaLib.setReferenceValue(targetObj, feature
										.getName(), guid);
							}

							iteratEDataObject((MethodElement) src_value);
						} else if (src_value instanceof WorkOrder) {
							// WorkOrder is not method element and does not have
							// an id, need special handling
							// set the prede
							WorkOrder wr = (WorkOrder) src_value;
							Object umaWr = workOrderMap.get(wr);
							umaLib.setWorkOrder(umaWr, wr.getValue());
						} else {
							logger
									.logMessage(NLS.bind(ImportXMLResources.importXMLService_element_not_handled, src_value));
						}
					}
				} else if (feature instanceof EReference) {

					// get the xml object and iterate again
					Object src_value = value;

					if (src_value instanceof WorkOrder) {
						logger.logMessage("workorder value"); //$NON-NLS-1$
					} else if (src_value instanceof String
							&& !isDiscarded((String) src_value)) {
						// it's an id reference
						umaLib.setReferenceValue(targetObj, feature.getName(),
								(String) src_value);
					} else if (src_value instanceof MethodElement) {
						// set the reference
						String guid = ((MethodElement) src_value).getId();
						if (guid == null) {
							logger
									.logWarning(NLS.bind(ImportXMLResources.importXMLService_element_without_id, ((MethodElement) src_value)
									.getName()));
						}
						if (!isDiscarded(guid)) {
							umaLib.setReferenceValue(targetObj, feature
									.getName(), guid);
						}
						iteratEDataObject((MethodElement) src_value);
					} else {
						logger
								.logWarning(NLS.bind(ImportXMLResources.importXMLService_element_not_handled, src_value));
					}

				} else {
					if (targetObj instanceof ContentCategoryPackage) {
						logger.logMessage("hrre"); //$NON-NLS-1$
					}

					if (value instanceof WorkOrderType) {
						value = ((WorkOrderType) value).getName();
					}

					boolean rename = false;
					if (!isNewElement && feature == org.eclipse.epf.xml.uma.UmaPackage.eINSTANCE.getNamedElement_Name()) {
						String oldName = (String)targetObj.eGet(org.eclipse.epf.uma.UmaPackage.eINSTANCE.getNamedElement_Name());
						String newName = (String)srcObj.eGet(feature);				
						if (!newName.equals(oldName)) {
							Object[] entryVal = new Object[2];
							entryVal[0] = targetObj;
							entryVal[1] = newName;
							renameElementMap.put(srcObj.getId(), entryVal);
							rename = true;
						}
					}
					if (!rename) {
						umaLib.setAtributeFeatureValue(targetObj,
								feature.getName(), value);
					}
				}
			} catch (Exception e) {
				// log the error and continue???
				e.printStackTrace();
			}

		}

	}

	// goid of processed element
	private List processedElements = new ArrayList();

	private boolean isProcessed(String guid) {
		return processedElements.contains(guid);
	}

	private void setProcessed(String guid) {
		if (!processedElements.contains(guid)) {
			processedElements.add(guid);
		}
	}

	private boolean isDiscarded(String id) {
		return discardedElements.contains(id);
	}

	private void setDiscarded(String id) {
		discardedElements.add(id);
	}
	
	public void setOverwrite(boolean b) {
		overwrite = b;
	}	
	
	private static String versionCheck(final String xmlPath, final String title) {
		final String[] ret = new String[1];
		ret[0] = null;
		SafeUpdateController.syncExec(new Runnable() {	
			public void run() {				
				VersionUtil.VersionCheckInfo info = VersionUtil.checkLibraryVersion(new File(xmlPath),
						VersionUtil.FILETYPE_XML);
				if (info == null) {
					ret[0] = NLS.bind(ImportXMLResources.versionMismatch_oldData_unknown, new Object[] {
							Platform.getProduct().getName()});					
				} else {
					if (info.result < 0) {
						if (info.toolID.equals(VersionUtil.getPrimaryToolID())) {
							ret[0] = NLS.bind(ImportXMLResources.versionMismatch_oldData, new Object[] {
									info.toolVersion, Platform.getProduct().getName()});
						} else {
							ret[0] = NLS.bind(ImportXMLResources.versionMismatch_oldData_unknown, new Object[] {
									Platform.getProduct().getName()});					
						}
					} else if (info.result > 0) {
						if (info.toolID.equals(VersionUtil.getPrimaryToolID())) {
							ret[0] = NLS.bind(ImportXMLResources.versionMismatch_oldTool, new Object[] {
								info.toolVersion, Platform.getProduct().getName()});
						} else {
							ret[0] = NLS.bind(ImportXMLResources.versionMismatch_oldTool_unknown, new Object[] {
									Platform.getProduct().getName()});
						}
					} 
				}
				if (ret[0] != null) {
					ImportXMLPlugin.getDefault().getMsgDialog().displayError(title, ret[0]);					
				}
			}
		});		
		return ret[0];
	}
	
}
