/*
 * This file has been developed at the University of Munich, Chair for Programming & Software Engineering.
 * 
 * This file is licensed under the Eclipse Public License (EPL) 1.0
 * 
 */
package eu.mdd4soa.trans.smm2java.impl;

import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.gmt.modisco.java.ClassDeclaration;
import org.eclipse.gmt.modisco.java.FieldDeclaration;
import org.eclipse.gmt.modisco.java.InterfaceDeclaration;
import org.eclipse.gmt.modisco.java.Model;
import org.eclipse.gmt.modisco.java.Package;
import org.eclipse.gmt.modisco.java.Type;
import org.eclipse.gmt.modisco.java.TypeDeclaration;
import org.eclipse.gmt.modisco.java.emf.JavaFactory;

import eu.mdd4soa.smm.exception.TransformationException;
import eu.mdd4soa.smm.statik.ExceptionType;
import eu.mdd4soa.smm.statik.InterfaceOperation;
import eu.mdd4soa.smm.statik.InterfaceType;
import eu.mdd4soa.smm.statik.MessageProperty;
import eu.mdd4soa.smm.statik.MessageType;
import eu.mdd4soa.smm.statik.Participant;
import eu.mdd4soa.smm.statik.ProvidedService;
import eu.mdd4soa.smm.statik.SMMType;
import eu.mdd4soa.smm.statik.Service;
import eu.mdd4soa.trans.smm2java.EMFUtil;
import eu.mdd4soa.trans.smm2java.JMHelper;
import eu.mdd4soa.trans.smm2java.impl.PartnerTracker.Partner;

public class StaticConverter {

	public static void convert(Model model, Participant participant, Package rootPackage, TypeTracker typeTracker, PartnerTracker partners)
			throws TransformationException {
		StaticConverter c= new StaticConverter(model, participant, rootPackage, typeTracker, partners);
		c.execute();
	}

	private Model fModel;

	private Participant fParticipant;

	private Package fRootPackage;

	private TypeTracker fTypeTracker;

	private PartnerTracker fPartners;

	public StaticConverter(Model model, Participant p, Package rootPackage, TypeTracker t, PartnerTracker partners) {
		fModel= model;
		fParticipant= p;
		fRootPackage= rootPackage;
		fTypeTracker= t;
		fPartners= partners;

	}

	private void execute() throws TransformationException {

		// First, grab the utility types
		handleUtilities();

		// Then, generate static part
		handleMessageTypes();
		handleServiceInterfaces();
	}


	/**
	 * Generate the utility classes in the util package.
	 * 
	 * @throws TransformationException
	 */
	private void handleUtilities() throws TransformationException {

		Model loadedUtilModel= EMFUtil.loadUtils();

		fModel.getCompilationUnits().addAll(loadedUtilModel.getCompilationUnits());
		fModel.getOwnedElements().addAll(loadedUtilModel.getOwnedElements());
		fModel.getOrphanTypes().addAll(loadedUtilModel.getOrphanTypes());
	}

	private void handleMessageTypes() {

		EList<SMMType> types= fParticipant.getTypes();

		/*
		 * First, we create all message types (just the classes) and all
		 * primitive types....
		 */
		for (SMMType ismType : types) {

			if (ismType instanceof MessageType) {
				// handle message type
				TypeDeclaration typeDeclaration= fTypeTracker.createClass(fRootPackage.getName() + ".data." + ismType.getName());
				typeDeclaration.setModifier(JMHelper.createModifier("public"));
			}

			if (ismType instanceof ExceptionType) {
				TypeDeclaration typeDeclaration= fTypeTracker.createClass(fRootPackage.getName() + ".data." + ismType.getName());
				Type exceptionType= fTypeTracker.getType("java.lang.Exception");
				JMHelper.ensureImport(typeDeclaration.getOriginalCompilationUnit(), exceptionType);

				((ClassDeclaration) typeDeclaration).setSuperClass(JMHelper.createTypeAccessTo(exceptionType));
				typeDeclaration.setModifier(JMHelper.createModifier("public"));

				// ConstructorDeclaration fConstructor=
				// JavaFactory.eINSTANCE.createConstructorDeclaration();
				// fConstructor.setName(ismType.getName());
				// fConstructor.setModifier(JMHelper.createModifier("public"));
				// fConstructor.setBody(JavaFactory.eINSTANCE.createBlock());
				//
				// SuperConstructorInvocation invocation=
				// JavaFactory.eINSTANCE.createSuperConstructorInvocation();
				// fConstructor.getBody().getStatements().add(invocation);
			}
		}

		/*
		 * Add attributes afterwards (there might be circular references between
		 * msg types)
		 */
		for (SMMType ismType : types) {
			if (ismType instanceof MessageType) {
				MessageType msgType= ((MessageType) ismType);

				TypeDeclaration defType= (TypeDeclaration) fTypeTracker.getType(fRootPackage.getName() + ".data." + msgType.getName());

				EList<MessageProperty> attributes= msgType.getProperties();
				for (MessageProperty messageAttribute : attributes) {


					Type type= fTypeTracker.resolveTypeWithMultiplicityAndImports(defType.getOriginalCompilationUnit(), messageAttribute);
					if (type == null)
						throw new RuntimeException("Not found: " + messageAttribute.getType());

					FieldDeclaration fd= JMHelper.createField("private", messageAttribute.getIdentifier(), type);
					JMHelper.addField(defType, fd);

					// Getter:
					defType.getBodyDeclarations().add(JMHelper.createGetterMethod(fd));

					// Setter
					defType.getBodyDeclarations().add(JMHelper.createSetterMethod(fTypeTracker, fd));
				}
			}
		}
	}

	private void handleServiceInterfaces() {

		// Create the relevant packages.
		Package providedPackage= JavaFactory.eINSTANCE.createPackage();
		providedPackage.setName(fRootPackage.getName() + "." + "provided");
		fModel.getOwnedElements().add(providedPackage);

		Package requiredPackage= JavaFactory.eINSTANCE.createPackage();
		requiredPackage.setName(fRootPackage.getName() + "." + "required");
		fModel.getOwnedElements().add(requiredPackage);

		EList<Service> partners= fParticipant.getPartners();
		for (Service partnerPoint : partners) {

			InterfaceType partnerInterface= partnerPoint.getInterface();

			List<InterfaceOperation> providedOps= getProvidedOperations(partnerPoint instanceof ProvidedService, partnerInterface);
			List<InterfaceOperation> requiredOps= getRequiredOperations(partnerPoint instanceof ProvidedService, partnerInterface);

			if (!providedOps.isEmpty()) {
				// We create a provided interface which the orchestration must
				// implement.
				InterfaceDeclaration d= createServiceInterface(providedPackage, partnerInterface.getName() + "_Provided", providedOps);
				fPartners.addPartner(PartnerTracker.Type.PROVIDED, new Partner(partnerPoint, d, providedOps));
			}

			if (!requiredOps.isEmpty()) {
				// We create a required interface to be injected into the
				// orchestration.
				InterfaceDeclaration d= createServiceInterface(requiredPackage, partnerInterface.getName() + "_Required", requiredOps);
				fPartners.addPartner(PartnerTracker.Type.REQUIRED, new Partner(partnerPoint, d, requiredOps));
			}
		}
	}

	private InterfaceDeclaration createServiceInterface(Package providedPackage, String name, List<InterfaceOperation> providedOps) {

		InterfaceDeclaration iff= fTypeTracker.createInterface(providedPackage.getName() + "." + name);
		iff.setModifier(JMHelper.createModifier("public"));

		JMHelper.ensureImport(iff.getOriginalCompilationUnit(), fTypeTracker.getType("util.ServiceException"));

		for (InterfaceOperation interfaceOperation : providedOps)
			iff.getBodyDeclarations().add(fTypeTracker.createMethodForInterfaceOperation(iff.getOriginalCompilationUnit(), interfaceOperation));

		return iff;
	}


	private List<InterfaceOperation> getProvidedOperations(boolean isServiceInterface, InterfaceType partnerInterface) {
		if (isServiceInterface)
			return partnerInterface.getImplemented();
		else
			return partnerInterface.getUsed();
	}

	private List<InterfaceOperation> getRequiredOperations(boolean isServiceInterface, InterfaceType partnerInterface) {
		if (isServiceInterface)
			return partnerInterface.getUsed();
		else
			return partnerInterface.getImplemented();
	}


}
