package de.ugoe.cs.cpdp.decentApp;

import java.io.File;
import java.net.URISyntaxException;
import java.util.HashMap;

import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.epsilon.common.util.StringProperties;
import org.eclipse.epsilon.emc.emf.EmfModel;
import org.eclipse.epsilon.emc.emf.InMemoryEmfModel;
import org.eclipse.epsilon.eol.exceptions.models.EolModelLoadingException;
import org.eclipse.epsilon.eol.models.IModel;

import de.ugoe.cs.cpdp.decentApp.models.arffx.ARFFxPackage;
import de.ugoe.cs.cpdp.decentApp.models.decent.DECENTPackage;

/**
 * Class for handling decent and arffx model files.
 * 
 * @author Philip Makedonski, Fabian Trautsch
 *
 */

public class DECENTEpsilonModelHandler {
	private HashMap<String, Object> metaModelCache = new HashMap<>();
	private boolean useDECENTBinary = false;
	private boolean useARFFxBinary = false;

	public static String metaPath = "./decent/models/";

	/**
	 * Returns the decent model as IModel instance
	 * 
	 * @param decentModelLocation location of the decent model file
	 * @param read indicates if the model should be read from
	 * @param write indicates if data should be written in the model
	 * @return EmFModel (IModel) instance from the decent model, which was loaded
	 * @throws Exception
	 */
	public IModel getDECENTModel(String decentModelLocation, boolean read, boolean write) throws Exception { 

		EmfModel model;
		
		if (isUseDECENTBinary()) {
			unregisterMetaModels("");
			if (!read) {
				new File(decentModelLocation).delete();
				new File(decentModelLocation+"bin").delete();
			}
			DECENTResourceTool tool = new DECENTResourceTool();
			if (new File(decentModelLocation).exists() && !new File(decentModelLocation+"bin").exists()) {
				Resource resource = tool.loadResourceFromXMI(decentModelLocation,"decent", DECENTPackage.eINSTANCE);
				tool.storeBinaryResourceContents(resource.getContents(), decentModelLocation+"bin", "decentbin");
			}
			
			Resource resourceBin = tool.loadResourceFromBinary(decentModelLocation+"bin","decentbin", DECENTPackage.eINSTANCE);
			//alternative pattern
//			model = createInMemoryEmfModel("DECENT", resourceLocation, "../DECENT.Meta/model/DECENTv3.ecore", read, write, resourceBin, DECENTPackage.eINSTANCE);
//			restoreMetaModels();

			//NOTE: Adding the package is essential as otherwise epsilon breaks
			model = new InMemoryEmfModel("DECENT", resourceBin, DECENTPackage.eINSTANCE);
			model.setStoredOnDisposal(write);
			model.setReadOnLoad(read);
			model.setCachingEnabled(true);
			restoreMetaModels();		
		} else {
			model = createEmfModel("DECENT", decentModelLocation, metaPath+"DECENTv3.ecore", read, write);
		}

		return model;
	}

	/**
	 * Converts the decent model to a binary form
	 * 
	 * @param location of the decent model file
	 */
	public void convertDECENTModelToBinary(String location) {
		unregisterMetaModels("");
		DECENTResourceTool tool = new DECENTResourceTool();
		Resource resource = tool.loadResourceFromXMI(location+"/model.decent","decent", DECENTPackage.eINSTANCE);
		tool.storeBinaryResourceContents(resource.getContents(), location+"/model.decent"+"bin", "decentbin");
		restoreMetaModels();		
	}

	/**
	 * Converts the decent model to a xmi form
	 * 
	 * @param location of the decent model file
	 */
	
	public void convertDECENTModelToXMI(String location) {
		unregisterMetaModels("");
		DECENTResourceTool tool = new DECENTResourceTool(); 
		Resource resource = tool.loadResourceFromBinary(location+"/model.decentbin","decentbin", DECENTPackage.eINSTANCE);
		restoreMetaModels();		
		tool.storeResourceContents(resource.getContents(), location+"/model.decent", "decent");
	}

	/**
	 * Returns the arffx model as IModel instance
	 * 
	 * @param arffxModelLocation location of the arffx model file
	 * @param read indicates if the model should be read from
	 * @param write indicates if data should be written in the model
	 * @return EmFModel (IModel) instance from the arffx model, which was loaded
	 * @throws Exception
	 */
	
	public IModel getARFFxModel(String arffxModelLocation, boolean read, boolean write) throws Exception {
		
		EmfModel model;
		
		if (isUseARFFxBinary()) {
			unregisterMetaModels("");
			if (!read) {
				new File(arffxModelLocation).delete();
				new File(arffxModelLocation+"bin").delete();
			}
			ARFFxResourceTool tool = new ARFFxResourceTool();
			if (new File(arffxModelLocation).exists() && !new File(arffxModelLocation+"bin").exists()) {
				Resource resource = tool.loadResourceFromXMI(arffxModelLocation,"arffx", ARFFxPackage.eINSTANCE);
				tool.storeBinaryResourceContents(resource.getContents(), arffxModelLocation+"bin", "arffxbin");
			}
			
			Resource resourceBin = tool.loadResourceFromBinary(arffxModelLocation+"bin","arffxbin", ARFFxPackage.eINSTANCE);
			//alternative pattern
//			model = createInMemoryEmfModel("DECENT", resourceLocation, "../DECENT.Meta/model/DECENTv3.ecore", read, write, resourceBin, DECENTPackage.eINSTANCE);
//			restoreMetaModels();

			//NOTE: Adding the package is essential as otherwise epsilon breaks
			model = new InMemoryEmfModel("ARFFx", resourceBin, ARFFxPackage.eINSTANCE);
//			model.getModelImpl().getURI().toFileString()
			model.setStoredOnDisposal(write);
			model.setReadOnLoad(read);
			model.setCachingEnabled(true);
			restoreMetaModels();		
		} else {
			model = createEmfModel("ARFFx", arffxModelLocation, metaPath+"ARFFx.ecore", read, write);
		}
		
		return model;
	}


	/**
	 * Converts an arffx model to a binary version
	 * 
	 * @param location of the arffx model
	 */
	public void convertARFFxModelToBinary(String location) {
		unregisterMetaModels("");
		ARFFxResourceTool tool = new ARFFxResourceTool();
		Resource resource = tool.loadResourceFromXMI(location+"/model.arffx","arffx", ARFFxPackage.eINSTANCE);
		tool.storeBinaryResourceContents(resource.getContents(), location+"/model.arffx"+"bin", "arffxbin");
		restoreMetaModels();		
	}
	
	/**
	 * Converts an arffx model to xmi
	 * 
	 * @param location of the arffx model
	 */

	public void convertARFFxModelToXMI(String location) {
		unregisterMetaModels("");
		ARFFxResourceTool tool = new ARFFxResourceTool(); 
		Resource resource = tool.loadResourceFromBinary(location+"/model.arffxbin","arffxbin", DECENTPackage.eINSTANCE);
		restoreMetaModels();		
		tool.storeResourceContents(resource.getContents(), location+"/model.arffx", "arffx");
	}


	/**
	 * Returns the log model as IModel instance
	 * 
	 * @param logModelLocation location of the log model file
	 * @param read indicates if the model should be read from
	 * @param write indicates if data should be written in the model
	 * @return EmFModel (IModel) instance from the log model, which was loaded
	 * @throws Exception
	 */
	
	public IModel getLOGModel(String logModelLocation, boolean read, boolean write) throws Exception {
		if (!new File(logModelLocation).exists()) {
			read = false;
		}
		IModel model = createEmfModel("LOG", logModelLocation, metaPath +"LOG.ecore", read, write);
		System.setProperty("epsilon.logFileAvailable", "true");
		return model;
	}

	/**
	 * Creates an EMF Model
	 * 
	 * @param name of the  emf model
	 * @param model  name of the model
	 * @param metamodel name of the metamodel
	 * @param readOnLoad indicates if the model should be read on load
	 * @param storeOnDisposal indicates if the model should be stored on disposal
	 * @return
	 * @throws EolModelLoadingException
	 * @throws URISyntaxException
	 */
	
	@SuppressWarnings("deprecation")
	protected EmfModel createEmfModel(String name, String model, 
			String metamodel, boolean readOnLoad, boolean storeOnDisposal) 
					throws EolModelLoadingException, URISyntaxException {
		EmfModel emfModel = new EmfModel();
		StringProperties properties = new StringProperties();
		properties.put(EmfModel.PROPERTY_NAME, name);
		properties.put(EmfModel.PROPERTY_ALIASES, name);
		properties.put(EmfModel.PROPERTY_FILE_BASED_METAMODEL_URI, 
				"file:/" + getFile(metamodel).getAbsolutePath());
		properties.put(EmfModel.PROPERTY_MODEL_URI, 
				"file:/" + getFile(model).getAbsolutePath());
		properties.put(EmfModel.PROPERTY_IS_METAMODEL_FILE_BASED, "true");
		properties.put(EmfModel.PROPERTY_READONLOAD, readOnLoad + "");
		properties.put(EmfModel.PROPERTY_CACHED, "true");
		properties.put(EmfModel.PROPERTY_STOREONDISPOSAL, 
				storeOnDisposal + "");
		emfModel.load(properties, "");
		//System.out.println(emfModel.allContents());
		return emfModel;
	}

	/**
	 * Returns a new File instance from the given filename
	 * 
	 * @param fileName of the file
	 * @return
	 * @throws URISyntaxException
	 */
	public File getFile(String fileName) throws URISyntaxException {;
		return new File(fileName);
	}

	/**
	 * Restores the metamodels, so that they are registered in the
	 * EPackage registry
	 */
	private void restoreMetaModels() {
		for (String key : metaModelCache .keySet()) {
			EPackage.Registry.INSTANCE.put(key, metaModelCache.get(key));
		};
	}

	/**
	 * Unregister the metamodels from the EPackage registry
	 * 
	 * @param filter for filtering out certain instances
	 */
	private void unregisterMetaModels(String filter) {
		for (String key : EPackage.Registry.INSTANCE.keySet()) {
			if (key.contains(filter)) {
				metaModelCache.put(key, EPackage.Registry.INSTANCE.get(key));
			}
		};
		for (String key : metaModelCache .keySet()) {
			EPackage.Registry.INSTANCE.remove(key);
		};
	}
	
	/**
	 * Returns true if decent binary model is used
	 * @return
	 */

	public boolean isUseDECENTBinary() {
		return useDECENTBinary;
	}

	/**
	 * Sets the boolean which indicates, if the decent binary
	 * model is used
	 * @param useDECENTBinary
	 */
	public void setUseDECENTBinary(boolean useDECENTBinary) {
		this.useDECENTBinary = useDECENTBinary;
	}

	/**
	 * Returns true if arffx binary model is used
	 * @return
	 */
	public boolean isUseARFFxBinary() {
		return useARFFxBinary;
	}
	
	/**
	 * Sets the boolean which indicates, if the arffx binary
	 * model is used
	 * @param useARFFxBinary
	 */

	public void setUseARFFxBinary(boolean useARFFxBinary) {
		this.useARFFxBinary = useARFFxBinary;
	}

	
}
