package org.etsi.mts.tdl.helper;



import java.io.IOException;
import java.util.Collections;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.common.OCLConstants;
import org.eclipse.ocl.pivot.internal.delegate.OCLInvocationDelegateFactory;
import org.eclipse.ocl.pivot.internal.delegate.OCLSettingDelegateFactory;
import org.eclipse.ocl.pivot.internal.delegate.OCLValidationDelegateFactory;
import org.eclipse.ocl.xtext.completeocl.CompleteOCLStandaloneSetup;
import org.eclipse.ocl.xtext.essentialocl.EssentialOCLStandaloneSetup;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.serializer.impl.Serializer;
import org.etsi.mts.tdl.TDLan2StandaloneSetup;
import org.etsi.mts.tdl.TDLtxStandaloneSetup;
import org.etsi.mts.tdl.TDLtxiStandaloneSetup;
import org.etsi.mts.tdl.TPLan2StandaloneSetup;
import org.etsi.mts.tdl.impl.tdlPackageImpl;
import org.etsi.mts.tdl.structuredobjectives.impl.StructuredObjectivesPackageImpl;

import com.google.inject.Injector;

/**
 * Static helper class to handle some of the standard boiler-plate code to work with textual TDL models.
 * Things might and likely will break.    
 * @author Philip Makedonski
 */
public class TDLHelper {
	static {
		TDLHelper.init();
	}
	
	public static Injector injector;
	private static XtextResourceSet resourceSet;

	/**
	 * Initialise the packages and OCL implementation and validation delegates for the implementation of operations and constraints.
	 * @throws IOException 
	 */
	public static void init() {
		Logger.getRootLogger().setLevel(Level.OFF);
		tdlPackageImpl.init();
		StructuredObjectivesPackageImpl.init();
		EssentialOCLStandaloneSetup.doSetup();
		CompleteOCLStandaloneSetup.doSetup();
		initializeValidator();
		resourceSet = getNewResourceSet();
	}

	/**
	 * Loads an existing resource from a file.
	 * @param filename location of the resource to be loaded.
	 * @return A resource loaded from the file.
	 */
	public static Resource load(String filename) {
		Resource resource = resourceSet.getResource(org.eclipse.emf.common.util.URI.createURI(filename), true);
		return resource;
	}
	
	/**
	 * Link / resolve all loaded resources.
	 */
	public static void link() {
		EcoreUtil.resolveAll(resourceSet);
	}

	/**
	 * Check all resources in the shared resourceSet. 
	 */
	public static void check() {
		//TODO: check automatically? make optional in case of performance or redundancy concerns?
//		TDLHelper.link();
		resourceSet.getResources().forEach(TDLHelper::check);
	}

	/**
	 * Check resource in the shared resourceSet. Load if needed.
	 * @param filename location of the resource to be checked.
	 */
	public static void check(String filename) {
		Resource r = TDLHelper.load(filename);
		check(r);
	}

	/**
	 * Check resource.
	 * @param r the resource to be checked.
	 */
	private static void check(Resource r) {
		r.getErrors().forEach(e -> System.out.println("Error: "+r.getURI().lastSegment()+": "+e.getLine()+": "+e.getMessage()));
		r.getWarnings().forEach(e -> System.out.println("Warning: "+r.getURI().lastSegment()+": "+e.getLine()+": "+e.getMessage()));
	}

	
	/**
	 * Creates a new resource at the specified location.
	 * @param filename Location of the resource to be loaded.
	 * @return An empty resource loaded from the file.
	 * @throws Exception 
	 */
	public static Resource create(String filename) throws Exception {
		Resource existing = resourceSet.getResource(org.eclipse.emf.common.util.URI.createFileURI(filename), false);
		if (existing != null) {
			existing.delete(Collections.emptyMap());
		}
		Resource resource = resourceSet.createResource(org.eclipse.emf.common.util.URI.createFileURI(filename));
		return resource;
	}

	/**
	 * Store resource contents in a file.  
	 * @param resource A resource to be saved.
	 * @throws Exception 
	 */
	public static void store(Resource resource) throws Exception {
		resource.save(Collections.emptyMap());
	}


	/**
	 * Get textual representation of resource contents.  
	 * @param resource A resource to be represented.
	 * @throws Exception 
	 */
	public static String getText(Resource resource) throws Exception {
//		if (resource instanceof XtextResource) {
//			return NodeModelUtils.getNode(resource.getContents().get(0)).getText();
//		} else {
			//TODO: this needs to be more robust for the desired format
			Serializer serializer = injector.getInstance(Serializer.class);  
			return serializer.serialize(resource.getContents().get(0));
//		}
	}
	
	/**
	 * Setup and return an XtextResourceSet with TDLan2 file support.
	 * @return A new XtextResourceSet.
	 */
	public static XtextResourceSet getNewResourceSet() {
		if (!Platform.isRunning()) {
			new org.eclipse.emf.mwe.utils.StandaloneSetup().setPlatformUri("./");
			injector = new TDLan2StandaloneSetup().createInjectorAndDoEMFRegistration();
			injector = new TPLan2StandaloneSetup().createInjectorAndDoEMFRegistration();
			injector = new TDLtxiStandaloneSetup().createInjectorAndDoEMFRegistration();
			//TODO: can this be configurable? or does it preclude tdlan2 from being loaded?
			injector = new TDLtxStandaloneSetup().createInjectorAndDoEMFRegistration();
		} else {
			injector = IResourceServiceProvider.Registry.INSTANCE.getResourceServiceProvider(URI.createFileURI("t.tdltx")).get(Injector.class);
		}
//		XtextResourceSet resourceSet = new XtextResourceSet();
		XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class);
		//TODO: seems to cause some issues, make configurable?
//		resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
//		resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.FALSE);
		return resourceSet;
	}

	/**
	 * Reset the shared resourceSet.
	 */
	public static void resetResourceSet() {
		resourceSet = getNewResourceSet();
	}	
	
	/**
	 * Initialise OCL validation and implementation delegates.
	 */
	public static void initializeValidator() {
		String oclDelegateURI = OCLConstants.OCL_DELEGATE_URI+"/Pivot";
		
	    EOperation.Internal.InvocationDelegate.Factory.Registry.INSTANCE.put(oclDelegateURI,
	        new OCLInvocationDelegateFactory(oclDelegateURI));
	    EStructuralFeature.Internal.SettingDelegate.Factory.Registry.INSTANCE.put(oclDelegateURI,
	        new OCLSettingDelegateFactory(oclDelegateURI));
	    EValidator.ValidationDelegate.Registry.INSTANCE.put(oclDelegateURI,
	        new OCLValidationDelegateFactory(oclDelegateURI));
	}

	/**
	 * Get the shared resourceSet.
	 */
	public static XtextResourceSet getResourceSet() {
		return resourceSet;
	}

}
