package de.hub.mse.ttc2020.solution.translationlayer;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;

public class Task4TranslationLayer {
	// keep track of previous versions for traceability 
	Map<EObject, EObject> traces = new HashMap<>();
	
	EObject getTrace(EObject o) {
		if (traces.containsKey(o)) {
			return traces.get(o);
		}
		return null;
	}
	
	void captureTrace(EObject o, EObject trace) {
		this.traces.put(o, trace);
	}
	
	public void clearTraces() {
		this.traces.clear();
	}
	
	// EMF packages for each of the model versions
	EPackage model1;
	EPackage model2;
	
	public Task4TranslationLayer(EPackage model1, EPackage model2) {
		this.model1 = model1;
		this.model2 = model2;
	}
	
	// M1 -> M2
	public EObject migrate(EObject container) {
		EObject dog = getDogInContainer(container);
		EObject dog2 = migrateDog(dog);
		return buildContainer2(dog2);
	}
	
	// M2 -> M1
	public EObject migrateBack(EObject container) {
		EObject dog = getDogInContainer(container);
		EObject dog1 = migrateBackDog(dog);
		return buildContainer1(dog1);
	}
	
	// M1 -> M2
	public EObject migratePerson(EObject instance) {
		// create M2 instances
		EClass class1 = instance.eClass();
		EClass class2 = (EClass) model2.getEClassifier("Person");
		EObject instance2 = model2.getEFactoryInstance().create(class2);
		
		// transfer name field
		instance2.eSet(class2.getEStructuralFeature("name"), instance.eGet(class1.getEStructuralFeature("name")));

		// apply transformation for age <-> ybirth
		int age = Integer.parseInt(instance.eGet(class1.getEStructuralFeature("age")).toString());
		instance2.eSet(class2.getEStructuralFeature("ybirth"), ageToYbirth(age));

		return instance2;
	}
	
	// M2 -> M1
	public EObject migrateBackPerson(EObject instance) {
		// create M2 instances
		EClass class1 = (EClass) model1.getEClassifier("Person");
		EClass class2 = instance.eClass();
		EObject instance1 = model1.getEFactoryInstance().create(class1);
		
		// transfer name field
		instance1.eSet(class1.getEStructuralFeature("name"), instance.eGet(class2.getEStructuralFeature("name")));
	
		// apply transformation for age <-> ybirth
		int ybirth = Integer.parseInt(instance.eGet(class2.getEStructuralFeature("ybirth")).toString());
		instance1.eSet(class1.getEStructuralFeature("age"), ybirthToAge(ybirth));
		
		return instance1;
	}
	
	// M1 -> M2
	public EObject migrateDog(EObject instance) {
		// create M1 instances
		EClass class1 = instance.eClass();
		EClass class2 = (EClass) model2.getEClassifier("Dog");
		EObject instance2 = model2.getEFactoryInstance().create(class2);
		
		// transfer name field
		instance2.eSet(class2.getEStructuralFeature("name"), instance.eGet(class1.getEStructuralFeature("name")));
		// store trace for age field
		captureTrace(instance2, instance);
		
		// delegate migration of owner
		EObject owner = (EObject) instance.eGet(class1.getEStructuralFeature("owner"));
		EObject owner2 = migratePerson(owner);
		instance2.eSet(class2.getEStructuralFeature("owner"), owner2);
		
		return instance2;
	}
	
	// M2 -> M1
	public EObject migrateBackDog(EObject instance) {
		// create M2 instances
		EClass class2 = instance.eClass();
		EClass class1 = (EClass) model1.getEClassifier("Dog");
		EObject instance1 = model1.getEFactoryInstance().create(class1);
		
		// transfer name field
		instance1.eSet(class1.getEStructuralFeature("name"), instance.eGet(class2.getEStructuralFeature("name")));
		
		// check for trace for age field
		EObject trace = getTrace(instance);
		if (null != trace) {
			// restore from trace
			instance1.eSet(class1.getEStructuralFeature("age"), trace.eGet(class1.getEStructuralFeature("age")));
		} else {
			// otherwise use default value
			instance1.eSet(class1.getEStructuralFeature("age"), -1);
		}
		
		// delegate migration of owner
		EObject owner = (EObject) instance.eGet(class2.getEStructuralFeature("owner"));
		EObject owner1 = migrateBackPerson(owner);
		instance1.eSet(class1.getEStructuralFeature("owner"), owner1);

		return instance1;
	}
	
	
	// utilities for birth date computations
	static int ybirthToAge(int ybirth) {
		return Calendar.getInstance().get(Calendar.YEAR) - ybirth;
	}
	
	static int ageToYbirth(int age) {
		return Calendar.getInstance().get(Calendar.YEAR) - age;
	}
	
	
	// boilerplate to construct and de-construct Container instances 
	EObject getDogInContainer(EObject container) {
		EClass containerClass = container.eClass();
		EObject dog = (EObject) container.eGet(containerClass.getEStructuralFeature("dog"));
		return dog;
	}
	
	EObject buildContainer1(EObject dog) {
		EClass containerClass1 = (EClass) model1.getEClassifier("Container");
		EClass dogClass = dog.eClass();
		
		EObject container1 = model1.getEFactoryInstance().create(containerClass1);
		container1.eSet(containerClass1.getEStructuralFeature("person"), dog.eGet(dogClass.getEStructuralFeature("owner")));
		container1.eSet(containerClass1.getEStructuralFeature("dog"), dog);
		return container1;
	}
	
	EObject buildContainer2(EObject dog) {
		EClass containerClass2 = (EClass) model2.getEClassifier("Container");
		EClass dogClass = dog.eClass();
		
		EObject container2 = model2.getEFactoryInstance().create(containerClass2);
		container2.eSet(containerClass2.getEStructuralFeature("person"), dog.eGet(dogClass.getEStructuralFeature("owner")));
		container2.eSet(containerClass2.getEStructuralFeature("dog"), dog);
		return container2;
	}
}
