package org.etsi.mts.tdl.helper;



import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
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.emf.mwe.utils.StandaloneSetup;
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.GrammarUtil;
import org.eclipse.xtext.IGrammarAccess;
import org.eclipse.xtext.resource.IResourceServiceProvider;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.eclipse.xtext.serializer.impl.Serializer;
import org.etsi.mts.tdl.Package;
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 org.osgi.framework.Bundle;

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, boolean derived) throws Exception {
		if (derived) {
			IFile file = resourceToFile(resource);
			if (file != null) {
				if (!file.exists())
					file.create(new ByteArrayInputStream(new byte[0]), false, null);
				file.setDerived(derived, new NullProgressMonitor());
			}
		}
		resource.save(Collections.emptyMap());
	}
	
	public static IFile resourceToFile(Resource resource) {
		URI uri = resource.getURI();
		return ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(uri.toPlatformString(true)));
	}


	/**
	 * 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 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;
	}
	
	public static Set<String> getTdlGrammarKeywords() {
		injector = new TDLtxStandaloneSetup().createInjectorAndDoEMFRegistration();
		IGrammarAccess grammarAccess = injector.getInstance(IGrammarAccess.class);
		if (grammarAccess != null)
			return GrammarUtil.getAllKeywords(grammarAccess.getGrammar());
		return Collections.EMPTY_SET;
	}

	private static final String TDL_MODEL_NAME = "tdl";
	private static final String HTTP_MODEL_NAME = "http";
	private static final String JAVA_MODEL_NAME = "java";
	private static final String[] MODEL_FILE_EXTENSIONS = new String[] {
			"tdl", "tdlan2", "tdltx", "tdltxi"
	};
	
	public static IProject getProjectForResource(Resource resource) {
		IWorkspaceRoot ws = org.eclipse.core.resources.ResourcesPlugin.getWorkspace().getRoot();
		URI resourceUri = resource.getURI();
		IFile modelFile = null;
		if (resourceUri.isPlatformResource()) {
			modelFile = ws.getFile(new Path(resourceUri.toPlatformString(true)));
		} else if (resourceUri.isFile()) {
			// TODO
			modelFile = ws.getFile(new Path(resourceUri.toFileString()));
//			String uriString = resourceUri.toFileString();
//			java.net.URI uri = new java.net.URI(uriString);
//			IFile[] files = ws.findFilesForLocationURI(uri);
		}
		if (modelFile != null)
			return modelFile.getProject();
		return null;
	}
	
	abstract static class FileFinder implements IResourceVisitor {
		
		public IFile file;

		@Override
		public boolean visit(IResource resource) throws CoreException {
			if (file != null)
				return false;
			if (resource.getType() != IResource.FILE)
				return true;
			if (mathces((IFile) resource)) {
				this.file = (IFile) resource;
			}
			return file == null;
		}
		
		abstract protected boolean mathces(IFile file);
	}
	
	public static Package getTdlPackage(Resource resource) {
		return getKnownPackage(resource, TDL_MODEL_NAME);
	}
	
	public static Package getHttpPackage(Resource resource) {
		return getKnownPackage(resource, HTTP_MODEL_NAME);
	}
	
	public static Package getJavaPackage(Resource resource) {
		return getKnownPackage(resource, JAVA_MODEL_NAME);
	}
	
	private static Package getKnownPackage(Resource resource, String packageName) {
		try {
			IProject prj = getProjectForResource(resource);
			if (prj != null) {
				final Set<String> names = new HashSet<>();
				for (String ext : MODEL_FILE_EXTENSIONS) {
					names.add(packageName + "." + ext);
				}
				FileFinder finder = new FileFinder() {
					@Override
					protected boolean mathces(IFile file) {
						return names.contains(file.getName().toLowerCase());
					}
				};
				prj.accept(finder);
				if (finder.file != null) {
					URI uri = URI.createPlatformResourceURI(finder.file.getFullPath().toString(), true);
					Resource tdlResource = resource.getResourceSet().getResource(uri, true);
					return (Package) tdlResource.getContents().get(0);
				} else {
					//TODO: somewhat hacky way to load resources from the library if not found locally 
					if (Platform.isRunning()) {
						Bundle bundle = Platform.getBundle("org.etsi.mts.tdl.library");
						ArrayList<Package> packages = new ArrayList<>();
						bundle.findEntries("/","*.tdltx", true).asIterator().forEachRemaining(e-> {
							org.eclipse.emf.common.util.URI pURI = org.eclipse.emf.common.util.URI.createURI(e.toString());
							if (names.contains(pURI.lastSegment().toLowerCase())) {
								Resource tdlResource = resource.getResourceSet().getResource(pURI, true);
								packages.add((Package) tdlResource.getContents().get(0));
							}
						});
						return packages.get(0);
					}
				}
			}
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new RuntimeException(e);
		}
		return null;
	}

	/**
	 * 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;
	}

}
