/**
 * 
 * facade for ecoreModif classes (Modif ATL fabrics, EcoreModif IO Fabric, 
 * Refactoring, XML creation and code generation
 *
 *  Copyright (C) 2013 IDL
 * 
 * This program is free software: you can redistribute it and/or modify
 *  under the terms of the GNU General Public License V 3
 *  
 *  @authors Jean-Philippe Babau
 *  @author Paola Vallejo
 *  22/11/2013
 */

package uiModif;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.scope.DefaultComparisonScope;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;

















import dependency.DependencyPackage;
import dependency.Graph;
import migration.DerivedReference;
//import migration.Derived;
//import migration.DerivedReference;
import migration.Instance;
import migration.Migration;
import migration.MigrationPackage;
import migration.ReferencePath;
import migration.tools.MigrationRoundtrip;
import migration.tools.UtilEMF;
import migrationspecgen.MigrationSpecificationGenerator;
import modif.*;
import modif.util.*;
import refactor.Refactoring;
import ecoremodif.EattributeModif;
import ecoremodif.EclassModif;
import ecoremodif.EreferenceModif;
import ecoremodif.RootEcoreModif;
import ecoremodifutils.ModifIO;

public class modifService {

	protected RootEcoreModif theRootEcoreModif;
	protected RootEcoreModif theRootEcoreModifCopy;
	protected ModifIO modifIO ;

	protected Map<String, ArrayList<String>> superTypesMap;
	protected Map<String, ArrayList<String>> attributesMap;

	protected String initialEcoreFile;
	protected String inputEcoreFile;
	protected String originalEcoreFile;
	protected String keyEcoreFile;
	protected String toolEcoreFile;
	protected String refactoredEcoreFile;
	protected EPackage keyEcoreFilePackage;
	protected String modifFile;
	protected String originalModelFile;
	protected String migratedModelFile;
	protected String migrationFile;
	protected String toolOutputModelFile;
	protected String reversedModelFile;
	protected String recontextualizedKeysModelFile;
	protected String graphFile;
	protected String recontextualizedGraphModelFile;
	protected String recontextualizedFinalModelFile;
	protected String projectFile;

	protected ArrayList<String> hideClassList;
	protected ArrayList<String> flattenClassList;

	protected String refactoredWithoutKFileName;

	protected boolean isUML;

	protected Migration migration;

	protected Map<String, Map<String, Map<String, String>>> renamemap;
	protected Map<String, Map<String, ArrayList<ArrayList<String>>>> hidemap;
	protected Map<String, String> newReferenceName;

	private EList<ClassModification> eclassmodif;

	public modifService() {
		theRootEcoreModif = null;
		modifIO = new ModifIO();
	}

	/**
	 * 
	 * @param originalEF input Ecore
	 * @param keyEF input Ecore with keys
	 * @param modifF Modif specification
	 * @param inputMF input model
	 * @param migrationF migration specification
	 * @param toolOutputMF model processed by tool
	 * @param graphF dependency graph
	 * @param projectF project source folder
	 * @param isUML indicates that the input Ecore is UML
	 */

	public void setFiles(String projectF, String originalEF, String modifF, String processedMF, String graphF){
		this.projectFile = projectF;
		this.originalEcoreFile = originalEF;
		this.modifFile = modifF;
		this.toolOutputModelFile = processedMF;
		this.graphFile = graphF;
	}

	public void setFiles(String projectF, String originalEF, String modifF, String toolEF, String inputMF, String migrationF, String processedMF, String graphF){
		this.projectFile = projectF;
		this.originalEcoreFile = originalEF;
		this.modifFile = modifF;
		this.toolEcoreFile = toolEF;
		this.originalModelFile = inputMF;
		this.migrationFile = migrationF;
		this.toolOutputModelFile = processedMF;
		this.graphFile = graphF;
	}


	/**
	 * Creation of a new metamodel from a given input metamodel 
	 * extended with UUID attributes.
	 * @return Copy of the originalEcore with UUID
	 * 
	 */
	public EPackage addUUIDAttributes(){
		EPackage rootKeyPackage = null;
		EPackage temporalPackage = null;
		try{
			if(isUML){
				// Copy of  UMLPackage.eINSTANCE without operations
				//rootKeyPackage = UtilEMF.removeOppositeFeature(UtilEMF.removeAnnotations(UtilEMF.removeOperations(UtilEMF.addUUIDAttribute(EcoreUtil.copy(UMLPackage.eINSTANCE)))));				
				this.keyEcoreFile = this.projectFile+"/metamodel/UMLK.ecore";
				UtilEMF.saveMetamodel(rootKeyPackage, keyEcoreFile);
				rootKeyPackage = UtilEMF.loadMetamodel(keyEcoreFile);
			}else{
				// Add UUID attribute
				temporalPackage = UtilEMF.addUUIDAttribute(originalEcoreFile);
				// Remove duplicate UUID attribute
				rootKeyPackage = UtilEMF.removeDuplicateUUIDAttribute(temporalPackage);
				//rootKeyPackage = UtilEMF.removeOppositeFeature(rootKeyPackage);

				String[] strArray = originalEcoreFile.split("\\.");
				String modelName = strArray[strArray.length-2];

				this.keyEcoreFile = (originalEcoreFile.replace(modelName, modelName+"K"));
				UtilEMF.saveMetamodel(rootKeyPackage, keyEcoreFile, false);
			}
			System.out.println("[saving] file "+keyEcoreFile +": ok");	

		} catch (IOException e) {
			e.printStackTrace();
		}
		return rootKeyPackage;
	}

	/**
	 * Creation of UUID values
	 * @param UMLPackage with UUID
	 */
	public String addUUIDValues(EPackage rootPackage){
		String keyModelFile = null;
		try {
			File f = new File(originalModelFile);
			String[] strArray = originalModelFile.split("\\.");
			String modelName = strArray[0];
			if(isUML){
				// Loading UML, changing the metamodel, adding UUIDs, and saving :			
				//EObject modelRoot = UtilEMF.addUUIDValues(UtilEMF.changeMetamodel(null, UtilEMF.loadModel(originalModelFile, UMLPackage.eINSTANCE), rootPackage), rootPackage);
				//keyModelFile = originalModelFile.replace(modelName, modelName+"_k").replace(rootPackage.getName(), rootPackage.getName()+"k.xmi");
				//UtilEMF.saveModel(modelRoot, keyModelFile);
			}
			else{
				// Loading model, changing the metamodel, adding UUIDs, and saving :
				//System.out.println("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
				EObject modelRoot = UtilEMF.addUUIDValues(UtilEMF.changeMetamodel(null, UtilEMF.loadModel(originalModelFile, originalEcoreFile), rootPackage), rootPackage);
				keyModelFile = originalModelFile.replace(rootPackage.getName(), rootPackage.getName()+"k");
				UtilEMF.saveModel(modelRoot, keyModelFile);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return keyModelFile;
	}

	/**
	 * Deletion of UUID attributes
	 * @param metamodelRootPackage
	 */
	public void removeUUIDAttribute(EPackage metamodelRootPackage){
		EPackage metamodel = UtilEMF.removeUUIDAttribute(metamodelRootPackage);

	}



	/**
	 * Deletion of UUID values
	 * @throws IOException 
	 */
	public void removeUUIDSimpleMigration(String modelFileName, String refactoredMetamodelWithoutK, String refactoredMetamodelWithoutKName) throws IOException{
		EPackage rootPackage;
		String finalModelFile = null;
		File f = new File(originalModelFile);
		finalModelFile = modelFileName.replace("migrated", "final").replace(refactoredMetamodelWithoutKName+"k",refactoredMetamodelWithoutKName);

		// Loading model, deleting UUIDs and saving model
		rootPackage = UtilEMF.loadMetamodel(keyEcoreFile);

		EPackage originalPackage = UtilEMF.loadMetamodel(originalEcoreFile);

		String URI = originalPackage.getNsURI();
		EObject modelRoot = UtilEMF.changeMetamodel(UtilEMF.removeUUIDValues(UtilEMF.loadModel(modelFileName, rootPackage)), refactoredMetamodelWithoutK);
		try {
			UtilEMF.saveModel(modelRoot, finalModelFile);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		//String output = finalModelFile.replace("tempfinal", "final");		
		// add schema location to the migrated file
		//addSchemaLocation(finalModelFile, output, URI, metamodelName);

	}

	/**
	 *  Add schema location to a xmi file
	 * @param inputfile
	 * @param finalModelFile
	 * @param URI
	 * @throws IOException
	 */
	public void addSchemaLocation(String inputfile, String finalModelFile, String URI, String metamodelName) throws IOException {
		//xsi:schemaLocation="http://mde.vallejo.statechart ../metamodel/Statechart.ecore" 
		FileReader fr=new FileReader(inputfile);
		BufferedReader br=new BufferedReader(fr);

		File fout = new File(finalModelFile);
		FileOutputStream fos = new FileOutputStream(fout);

		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos));
		String line=br.readLine();

		while (line!=null) {
			String xmi = "xmlns:xmi=\"http://www.omg.org/XMI\"";
			//String nom = "Statechart.ecore";
			if(line.contains(xmi)){
				String newLine = line.replace(xmi, "xsi:schemaLocation=\""+ URI + " ../metamodel/"+ metamodelName +"\""+ " "+xmi);
				bw.write(newLine);
			}else{
				bw.write(line);
			}
			bw.newLine();  
			line=br.readLine();
		} 
		br.close();
		fr.close();  
		bw.close();
	}

	/**
	 * Deletion of UUID values
	 */
	public void removeUUIDValue(){
		try {
			EPackage rootPackage; 
			String finalModelFile = null;
			/*if(isUML){
				File f = new File(originalModelFile);
				String[] strArray = originalModelFile.split("\\.");
				String modelName = strArray[0];
				finalModelFile = (originalModelFile.replace(modelName, modelName+"_final").replace("file:",""));

				// Loading model, deleting UUIDs and saving model
				rootPackage = UtilEMF.loadMetamodel(keyEcoreFile);
				//EObject modelRoot = UtilEMF.changeMetamodel(null, UtilEMF.removeUUIDValues(UtilEMF.loadModel(recontextualizedGraphModelFile, rootPackage)), UMLPackage.eINSTANCE);
				//UtilEMF.saveModel(modelRoot, finalModelFile);
			}else{*/
			File f = new File(originalModelFile);
			String[] strArray = originalModelFile.split("\\.");
			String modelName = strArray[0];
			String modelMetamodel = strArray[1];
			//finalModelFile = (originalModelFile.replace(modelName, modelName+"_final").replace("file:",""));

			String[] strArrayTool = recontextualizedGraphModelFile.split("\\.");
			String toolMetamodel = strArrayTool[1];

			finalModelFile = recontextualizedGraphModelFile.replace("_recontextgraph", "").replace(toolMetamodel, modelMetamodel);

			// Loading model, deleting UUIDs and saving model
			rootPackage = UtilEMF.loadMetamodel(keyEcoreFile);

			EObject modelRoot = UtilEMF.changeMetamodel(UtilEMF.removeUUIDValues(UtilEMF.loadModel(recontextualizedGraphModelFile, rootPackage)), originalEcoreFile);
			UtilEMF.saveModel(modelRoot, finalModelFile);
			//}
			System.out.println("[saving] file "+finalModelFile +": ok");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	/**
	 * Generates the modif file able to refactor the domain metamodel
	 * 
	 * @param domainMetamodel metamodel to be refactored
	 * @param atlModif type of modif (NoModif or EraseAll)
	 * @param modifFile modif file name
	 * @throws IOException
	 */
	public void generateModifFile(String domainMetamodel, int atlModif, String modifFile) throws IOException{
		ModifIO aModifIO = new ModifIO();
		utilModifFactory anUtilModifFactory = new utilModifFactory();
		EPackage theRootEcore        = aModifIO.LoadEcore(domainMetamodel);

		Modifications theRootModif;

		if (atlModif==2) {
			theRootModif = anUtilModifFactory.generateEraseAll(theRootEcore);
		}
		else {
			theRootModif = anUtilModifFactory.generateNoModif(theRootEcore);
		}

		OutputStream f = new FileOutputStream(modifFile) ;

		ModifIO.SaveModif(theRootModif, modifFile);

	}

	/**
	 * Simplifies the modif file
	 * 
	 * @param modifF modif file
	 */
	public void Minimize(String modifF) {
		Minimize tool = new Minimize();
		Modifications modif = modifIO.LoadModif(modifF);

		tool.go(modif);

		try {
			ModifIO.SaveModif(modif, modifF);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Create the modif file
	 */
	public void CreateModif() {
		// fabric call from files
		if(keyEcoreFile == null){
			String[] strArray = originalEcoreFile.split("\\.");
			String modelName = strArray[strArray.length-2];
			this.keyEcoreFile = (originalEcoreFile.replace(modelName, modelName+"K"));
		}

		modifIO.setModifIO(keyEcoreFile, modifFile);
		theRootEcoreModif = modifIO.getEcoreModif();
		theRootEcoreModifCopy = theRootEcoreModif;
		buildSuperTypesMap(theRootEcoreModif);
	}

	/**
	 * Create the modif file
	 */
	public void CreateModif(boolean simpleRefactoring,  String metamodel) {
		EPackage rootkeyPackage;
		// fabric call from files
		if(simpleRefactoring){			
			modifIO.setModifIO(originalEcoreFile, modifFile);
			theRootEcoreModif = modifIO.getEcoreModif();
			theRootEcoreModifCopy = theRootEcoreModif;
			buildSuperTypesMap(theRootEcoreModif);
		}else{
			EPackage domainMM = UtilEMF.loadMetamodel(metamodel);
			boolean addKeys = true;
			// verifying if keys were already added
			for(TreeIterator<EObject> it = domainMM.eAllContents(); it.hasNext();){
				EObject next = it.next();
				if(next instanceof EClass){
					EClass c = (EClass) next;
					for(EAttribute ea : c.getEAllAttributes()){
						if(ea.getName().equals("UUID")){
							addKeys = false;
							break;
						}
					}
				}
			}

			// adding keys
			if(addKeys){ rootkeyPackage = addUUIDAttributes();
			}
			else{ rootkeyPackage = domainMM; }

			if(keyEcoreFile == null){
				String[] strArray = originalEcoreFile.split("\\.");
				String modelName = strArray[strArray.length-2];
				this.keyEcoreFile = (originalEcoreFile.replace(modelName, modelName+"K"));
			}
			modifIO.setModifIO(keyEcoreFile, modifFile);
			theRootEcoreModif = modifIO.getEcoreModif();
			theRootEcoreModifCopy = theRootEcoreModif;
			buildSuperTypesMap(theRootEcoreModif);
			buildHideList(theRootEcoreModif);

			//System.out.println(" (((   "  + this.hideClassList);
			buildFlattenList(theRootEcoreModif);
		}
	}


	/**
	 * Transforms the input ecore according to the operators of the modif file
	 */
	public void Refactor() {
		if (theRootEcoreModif != null) {
			if (Refactoring.isOk(theRootEcoreModif)) {
				// launch refactoring operators
				Refactoring.operate(theRootEcoreModif);
				// save the resulting ecore model		
				try {
					// Save refactored metamodel with UUID
					modifIO.save(projectFile+"\\"+theRootEcoreModif.getRoot().getNewName()+".ecore");	
					refactoredEcoreFile = projectFile+"\\"+theRootEcoreModif.getRoot().getNewName()+".ecore";
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
			}else System.out.println("\nChange the Modif model");	
		}
	}
	public EPackage RefactorSimpleMigration() {
		EPackage refactoredWithoutK = null;
		//String refactoredWithoutKFileName = null;
		if (theRootEcoreModif != null) {
			if (Refactoring.isOk(theRootEcoreModif)) {
				// launch refactoring operators
				Refactoring.operate(theRootEcoreModif);
				// save the resulting ecore model		
				try {
					// Save refactored metamodel with UUID
					modifIO.save(projectFile+"\\"+theRootEcoreModif.getRoot().getNewName()+".ecore");	
					refactoredEcoreFile = projectFile+"\\"+theRootEcoreModif.getRoot().getNewName()+".ecore";
					// Save refactored metamodel without UUID
					refactoredWithoutK = UtilEMF.removeUUIDAttribute(UtilEMF.loadMetamodel(refactoredEcoreFile));

					File f = new File(refactoredWithoutK.getNsURI());
					int idx = f.getName().lastIndexOf('K');
					int idx2 = refactoredWithoutK.getName().lastIndexOf('K');
					int idx3 = refactoredWithoutK.getNsPrefix().lastIndexOf('K');
					refactoredWithoutKFileName = (f.getParent()+"/"+f.getName().substring(0, idx)+".ecore").replace("file:\\", "");
					refactoredWithoutK.setName(refactoredWithoutK.getName().substring(0, idx2));
					refactoredWithoutK.setNsPrefix(refactoredWithoutK.getNsPrefix().substring(0, idx3));
					UtilEMF.saveMetamodel(refactoredWithoutK, refactoredWithoutKFileName);

				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}				
			}else System.out.println("\nChange the Modif model");	
		}
		return refactoredWithoutK;
	}

	public String getRefactoredWithoutKFileName(){
		return refactoredWithoutKFileName;
	}

	/**
	 * 
	 * @return EPackage of the refactored metamodel
	 */
	public EPackage getRefactoredMetamodel(){
		//EPackage refactoredMetamodel = UtilEMF.loadMetamodel(refactoredEcoreFile);
		return UtilEMF.loadMetamodel(refactoredEcoreFile);
	}


	/**
	/**
	 * Checks if the domain metamodel and the tool metamodel fully matches
	 * @return
	 */

	public boolean Compare(){
		boolean check;
		URI uri1 = URI.createFileURI(refactoredEcoreFile);
		URI uri2 = URI.createFileURI(toolEcoreFile);
		Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("ecore", new XMIResourceFactoryImpl());

		ResourceSet resourceSet1 = new ResourceSetImpl();
		ResourceSet resourceSet2 = new ResourceSetImpl();

		Resource resource = resourceSet1.getResource(uri1, true); 
		Resource resource2 = resourceSet2.getResource(uri2, true);
		IComparisonScope scope = new DefaultComparisonScope(resource, resource2, null);
		org.eclipse.emf.compare.Comparison comparison = EMFCompare.builder().build().compare(scope);

		List<org.eclipse.emf.compare.Diff> differences = ((org.eclipse.emf.compare.Comparison) comparison).getDifferences();

		if(differences.size() == 0){
			check = true;
		}else{
			check = false;
		}
		return check;
	}

	public void constructRenameMap(){
		HashMap<String, Map<String, Map<String, String>>>  RenameMap = new HashMap<String, Map<String, Map<String, String>>>();
		for( EclassModif  ecm : theRootEcoreModif.getAllClassModifications()){
			String oldNameClass = ecm.getModif().getOldName();
			String newNameClass = ecm.getModif().getNewName();

			Map <String, String> featuresNameMap = new HashMap<String, String>();

			for(EattributeModif att : ecm.getAllAttributes()){
				String oldNameAtt = att.getModif().getOldName();
				String newNameAtt = att.getModif().getNewName();
				featuresNameMap.put(oldNameAtt, newNameAtt);
			}
			for(EreferenceModif ref : ecm.getAllReferences()){
				String oldNameRef = ref.getModif().getOldName();
				String newNameRef = ref.getModif().getNewName();
				featuresNameMap.put(oldNameRef, newNameRef);
			}
			Map<String, Map<String, String>> tempMap = new HashMap<String, Map<String,String>>();
			tempMap.put(newNameClass, featuresNameMap);
			renamemap.put(oldNameClass, tempMap);
		}
	}


	/**
	 * Generates the Migration specification
	 * 
	 * @param inputModel
	 * @param keyModelFileName
	 * @throws IOException
	 * 
	 * @return Name of the migrated model
	 */
	public String GenerateMigrationSpecification(String inputModel, String keyModelFileName, 
			ArrayList<String> hideClassesList, ArrayList<String> flattenClassesList) throws IOException{	
		Modifications theRootModif = modifIO.LoadModif(modifFile);	
		renamemap = new HashMap<String, Map<String, Map<String, String>>>();
		constructRenameMap();
		if(keyModelFileName.contains(theRootModif.getRootPackageModification().getOldName().toLowerCase())){
			migratedModelFile = inputModel.replace("."+theRootModif.getRootPackageModification().getOldName().toLowerCase(), "_migrated."+theRootModif.getRootPackageModification().getNewName().toLowerCase());
		}
		MigrationSpecificationGenerator migrationSpecificationGenerator = new MigrationSpecificationGenerator(superTypesMap, attributesMap, theRootEcoreModif, theRootEcoreModifCopy, 
				theRootModif, projectFile, keyModelFileName, migratedModelFile, hideClassesList, flattenClassesList);
		migrationFile = migrationSpecificationGenerator.getMigrationFileName();
		hiddenInstances = migrationSpecificationGenerator.getHideInstances();
		newReferenceName = migrationSpecificationGenerator.getNewReferencesName();
		System.out.println("[saving] migration specification : ok.");
		return migratedModelFile;

	}

	public Map<String,String> getNewReferencesNameMap(){
		return this.newReferenceName;
	}

	public String getMigrationFileName(){
		return migrationFile;
	}

	ArrayList<String> hiddenInstances = new ArrayList<String>();
	private boolean result;

	ArrayList<String> getHiddenInstances(){
		return hiddenInstances;
	}

	/**
	 * Fills the map of instances whose EClasses are hidden
	 * 
	 * @param migration migration specification
	 * @return hidemap
	 */

	public Map<String, Map<String, ArrayList<ArrayList<String>>>> createHideMap(Migration migration){

		hidemap = new HashMap<String, Map<String, ArrayList<ArrayList<String>>>>();

		for(Instance instance : migration.getInstances()){

			if(!instance.getDerived().isEmpty()){

				Map<String, ArrayList<ArrayList<String>>> derivedReferences = new HashMap<String, ArrayList<ArrayList<String>>>();
				if((instance.getDerived()).size() ==  1){
					DerivedReference derived = instance.getDerived().get(0);
					String newReferenceName = derived.getNewReferenceName();
					ArrayList<ArrayList<String>> allDerived = new ArrayList<ArrayList<String>>();

					for(ReferencePath refPath : derived.getPath()){
						String refName = refPath.getReferenceName();
					}


					/*	for( DerivedReference derivedRef : derived.getDerivedReferences()){
						String refName = derivedRef.getReferenceName();
						String target = derivedRef.getTarget().getUUID();

						ArrayList<String> derivedR = new ArrayList<String>();
						derivedR.add(refName);
						derivedR.add(target);
						allDerived.add(derivedR);	
					}*/

					//derivedReferences.put(newReferenceName, allDerived);
				}
				hidemap.put(instance.getUUID(), derivedReferences);
			}
		}
		return hidemap;
	}

	/**
	 * Onward migration of a given input model to a new model
	 * conforming with the targeted tool's definition domain
	 */
	public MigrationRoundtrip Migration(Map<String,String> newReferenceNameMap, Map<String, Map<String, Map<String, String>>> renameMap) {
		MigrationRoundtrip migrt = null;
		try {
			Migration migration = (Migration) UtilEMF.loadModel(this.migrationFile, MigrationPackage.eINSTANCE);
			Map<String, Map<String, ArrayList<ArrayList<String>>>>  hideMap = new HashMap<String, Map<String, ArrayList<ArrayList<String>>>>();
			hideMap = createHideMap(migration);
			migrt = new MigrationRoundtrip(migration);
			EObject migratedModel;
			if(hasHide(migration)){ 
				migratedModel = migrt.onwardMigrationHide(migration, newReferenceNameMap, renameMap); }
			else{ 
				migratedModel = migrt.onwardMigration(); }
			migrt.serializeMigratedModel();
			System.out.println("[saving] migrated file : ok.");	
		} catch (IOException e) {
			e.printStackTrace();
		}
		return migrt;
	}

	/**
	 * Reverse migration of a processed model
	 * @param migrt migration
	 * @return migration object
	 */
	public MigrationRoundtrip ReverseMigration(MigrationRoundtrip migrt){
		try {
			this.migration = (Migration) UtilEMF.loadModel(this.migrationFile, MigrationPackage.eINSTANCE);
			EObject toolOutputModel = UtilEMF.loadModel(this.toolOutputModelFile, URI.createURI(migration.getOutputMetamodelURI()).path());
			String reversedModelName = null;
			if(isUML){
				String inputmodelName = migration.getInputModelURI();
				String[] strArray = inputmodelName.split("\\.");
				String modelName = strArray[0];
				reversedModelName = (inputmodelName.replace(modelName, modelName+"_reversed").replace("file:",""));
			}else{
				String inputmodelName = migration.getInputModelURI();
				String[] strArray = inputmodelName.split("\\.");
				String metamodelName = strArray[1];
				String[] strArrayTool = toolOutputModelFile.split("\\.");
				String toolPutputModelName = strArrayTool[0];
				String metamodelToolName = strArrayTool[1];
				reversedModelName = toolOutputModelFile.replace(toolPutputModelName, toolPutputModelName+"_reversed").replace(metamodelToolName, metamodelName);			
			}
			Map<String,Map<String,Map<String,String>>> inversedMap = reversedMap(renamemap);
			if(hasHide(migration)){			
				UtilEMF.saveModel(migrt.reverseMigration(toolOutputModel, inversedMap, migration), reversedModelName);
			}else{
				UtilEMF.saveModel(migrt.reverseMigration(toolOutputModel), reversedModelName);
			}
			System.out.println("[saving] reversed file : ok.");	
		} catch (IOException e) {
			e.printStackTrace();
		}
		return migrt;
	}

	/**
	 * Reversed hideMap for reverse migration
	 * 
	 * @param map
	 * @return map
	 */
	public Map<String, Map<String, ArrayList<ArrayList<String>>>> reversedHideMap(Map<String, Map<String, ArrayList<ArrayList<String>>>> map){
		Map<String, Map<String, ArrayList<ArrayList<String>>>> reversedMap = new HashMap<String, Map<String,ArrayList<ArrayList<String>>>>();		
		Map<String, ArrayList<ArrayList<String>>> derivedMap = new HashMap<String, ArrayList<ArrayList<String>>>();
		Map<String, ArrayList<ArrayList<String>>> reversedDerivedMap = new HashMap<String, ArrayList<ArrayList<String>>>();
		String key;
		String newKey2;

		for (Entry<String, Map<String, ArrayList<ArrayList<String>>>> e: map.entrySet()) {
			key = e.getKey();
			derivedMap = e.getValue();

			ArrayList<ArrayList<String>> derivedReferences = new ArrayList<ArrayList<String>>();
			String newReferenceName = null;

			for (Entry<String, ArrayList<ArrayList<String>>> entry: derivedMap.entrySet()) {
				newReferenceName = entry.getKey();
				derivedReferences = entry.getValue();


				ArrayList<ArrayList<String>> derivedRef = new ArrayList<ArrayList<String>>();

				String referenceName = null;
				for( ArrayList<String> dr : derivedReferences){
					referenceName = dr.get(0);
					String target = dr.get(1);

					ArrayList<String> newList = new ArrayList<String>();
					newList.add(newReferenceName);
					newList.add(target);
					derivedRef.add(newList);
					reversedDerivedMap.put(referenceName, derivedRef);
				}	

			}

			reversedMap.put(key, reversedDerivedMap);
		}
		return reversedMap;
	}

	/**
	 * Reversed names map for reverse migration
	 * 
	 * @param map
	 * @return map
	 */
	public Map<String, Map<String, Map<String, String>>> reversedMap(Map<String, Map<String,Map<String, String>>> map){
		Map<String, Map<String,Map<String, String>>> result = new HashMap<String, Map<String,Map<String, String>>>();
		Map<String, String> invfeaturesMap = new HashMap<String, String>();
		String key;
		Map<String, Map<String, String>> value = new HashMap<>();
		for (Entry<String, Map<String, Map<String, String>>> e: map.entrySet()) {
			key = e.getKey();
			value = e.getValue();
			String k = null;
			Map<String,String> v;
			Map<String, Map<String,String>> invertedValue = new HashMap<String, Map<String,String>>();
			for (Entry<String, Map<String,String>> ee: value.entrySet()) {
				k = ee.getKey();
				v = ee.getValue();
				for (Entry<String, String> entry : v.entrySet()){
					invfeaturesMap.put(entry.getValue(), entry.getKey());
				}
				invertedValue.put(key, invfeaturesMap);
			}
			result.put(k, invertedValue);
		}
		return result;
	}


	/**
	 * Recontextualization by keys of a reversed model
	 * 
	 * @param migrt migration
	 * @return migration object
	 */
	public void Recontextualization(MigrationRoundtrip migrt){
		try {
			String recontextualizedKeysModelFile = null;
			String inputmodelName = migration.getInputModelURI();

			File f = new File(inputmodelName);
			String[] strArray = inputmodelName.split("\\.");
			String modelName = strArray[0];		

			String metamodelName = strArray[1];

			String[] strArrayTool = toolOutputModelFile.split("\\.");
			String toolPutputModelName = strArrayTool[0];
			String metamodelToolName = strArrayTool[1];

			recontextualizedKeysModelFile = toolOutputModelFile.replace(toolPutputModelName, toolPutputModelName+"_recontextkey").replace(metamodelToolName, metamodelName);	

			UtilEMF.saveModel(migrt.recontextualizationKey(), recontextualizedKeysModelFile);
			System.out.println("[saving] recontextualized by key file : ok.");	
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		Graph g = (Graph) UtilEMF.loadModel(graphFile, DependencyPackage.eINSTANCE);
		try {

			String inputmodelName = migration.getInputModelURI();
			File f = new File(inputmodelName);
			String[] strArray = inputmodelName.split("\\.");
			String modelName = strArray[0];
			//recontextualizedGraphModelFile = (inputmodelName.replace(modelName, modelName+"_recontextgraph").replace("file:",""));
			recontextualizedFinalModelFile = (toolOutputModelFile.replace("_migrated", "_recontextgraph"));

			String[] strArrayTool = toolOutputModelFile.split("\\.");
			String toolPutputModelName = strArrayTool[0];
			String metamodelToolName = strArrayTool[1];

			String metamodelName = strArray[1];

			recontextualizedGraphModelFile = toolOutputModelFile.replace(toolPutputModelName, toolPutputModelName+"_recontextgraph").replace(metamodelToolName, metamodelName);	
			UtilEMF.saveModel(migrt.recontextualizationGraph(g),recontextualizedGraphModelFile);
			System.out.println("[saving] recontextualized by graph file : ok.");	
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public String getRecontexGraphFileName(){
		return recontextualizedFinalModelFile;
	}

	/**
	 * Recontextualization by graph of a recontextualized by graph model
	 * 
	 * @param migrt migration 
	 * @return
	 */
	public MigrationRoundtrip RecontextualizationGraph(MigrationRoundtrip migrt) {
		Graph g = (Graph) UtilEMF.loadModel(graphFile, DependencyPackage.eINSTANCE);
		try {
			UtilEMF.saveModel(migrt.recontextualizationGraph(g),  recontextualizedGraphModelFile);
			System.out.println("[saving] recontextualized by graph file : ok.");	
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}


	public void buildSuperTypesMap(RootEcoreModif rootEcoreModif){
		superTypesMap = new HashMap<String, ArrayList<String>>();
		attributesMap = new HashMap<String, ArrayList<String>>();
		for( EClassifier c : rootEcoreModif.getRoot().getEcore().getEClassifiers()){
			ArrayList<String> supertypes = new ArrayList<String>();
			ArrayList<String> attributes = new ArrayList<String>();
			if(c instanceof EClass){
				for(EAttribute a : ((EClass)c).getEAttributes()){ attributes.add(a.getName()); }
				attributesMap.put(c.getName(), attributes);
				for(EClass st : ((EClass)c).getEAllSuperTypes()){ supertypes.add(st.getName()); }
				superTypesMap.put(c.getName(), supertypes);
			}
		}
	}

	private boolean isAbstract (RootEcoreModif rootEcoreModif, ClassModification ecm){
		boolean result = false;
		for(EClassifier ec : rootEcoreModif.getRoot().getEcore().getEClassifiers()){
			if(((EClass)ec).isAbstract()){
				if(ec.getName().equals(ecm.getOldName())){
					result = true;
					break;
				}
			}
		}
		return result;
	}

	private boolean hasSuperTypes(RootEcoreModif rootEcoreModif, ClassModification ecm){
		boolean result = false;
		for(EClassifier ec : rootEcoreModif.getRoot().getEcore().getEClassifiers()){
			if(!((EClass)ec).getEAllSuperTypes().isEmpty()){
				if(ec.getName().equals(ecm.getOldName())){
					result = true;
					break;
				}
			}
		}
		return result;
	}

	/**
	 * Builds the list of classes marked with the hide operator
	 * @param rootEcoreModif
	 * @return hideClassList
	 */
	public void buildHideList(RootEcoreModif rootEcoreModif){	
		hideClassList = new ArrayList<String>();
		for( ClassModification ecm : rootEcoreModif.getRoot().getModif().getClassModification()){
			if(ecm.isHide()){
				if(hasSuperTypes(rootEcoreModif, ecm)){
					hideClassList.add(ecm.getOldName());
				}
			}
		}
	}

	/**
	 * Builds the list of classes marked with the flatten operator
	 * @param rootEcoreModif
	 * @return flattenClassList
	 */
	public void buildFlattenList(RootEcoreModif rootEcoreModif){	
		flattenClassList = new ArrayList<String>();
		for( ClassModification ecm : rootEcoreModif.getRoot().getModif().getClassModification()){
			if(ecm.isFlatten()){ 
				if(hasSuperTypes(rootEcoreModif, ecm)){
					flattenClassList.add(ecm.getOldName()); 
				}
			}
		}
	}


	/**
	 * 
	 * @return list of classes marked with hide operator
	 */
	public ArrayList<String> getHideClassList(){
		return hideClassList;
	}

	/**
	 * 
	 * @return list of classes marked with flatten operator
	 */
	public ArrayList<String> getFlattenClassList(){
		return flattenClassList;
	}

	public boolean hasHide(Migration migration){
		boolean hide = false;
		for(Instance i : migration.getInstances()){
			//System.out.println(" i// "+ i.getUUID());
			if(!i.getDerived().isEmpty()){
				hide = true;
				break;
			}
		}
		return hide;
	}
	
	public Map<String, Map<String, Map<String, String>>>  getRenameMap(){
		return renamemap;
	}
}
