package org.etsi.mts.tdl.standalone;

import de.ugoe.cs.swe.TTCN3StandaloneSetup;
import java.io.File;
import java.io.FilenameFilter;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.epsilon.evl.execute.UnsatisfiedConstraint;
import org.eclipse.ocl.pivot.ExpressionInOCL;
import org.eclipse.ocl.pivot.utilities.OCL;
import org.eclipse.ocl.pivot.utilities.ParserException;
import org.eclipse.ocl.pivot.utilities.Query;
import org.eclipse.xtext.EcoreUtil2;
import org.etsi.mts.tdl.Extension;
import org.etsi.mts.tdl.GateType;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.StructuredDataType;
import org.etsi.mts.tdl.tdlPackage;
import org.etsi.mts.tdl.asn2tdl.ASN2TDLTranslator;
import org.etsi.mts.tdl.constraints.evl.Validator;
import org.etsi.mts.tdl.helper.TDLHelper;
import org.etsi.mts.tdl.json2tdl.JSON2TDLTranslator;
import org.etsi.mts.tdl.json2tdl.TDL2JSONTranslator;
import org.etsi.mts.tdl.openapi2tdl.next.OpenAPI2TDLTranslatorNext;
import org.etsi.mts.tdl.openapi2tdl.next.doc.Doc;
import org.etsi.mts.tdl.tools.to.docx.poi.Generator;
import org.etsi.mts.tdl.transform.AbstractTranslator;
import org.etsi.mts.tdl.ttcn3.Transform;

public class Standalone {
	static String sourceExtension = "tdltx";
	static String targetExtension = "tdltx";
	static String openapiExtension = "yaml";
	static String asnExtension = "asn";
	static String jsonExtension = "json";
	private List<MODE> modes = new ArrayList<>();
	private String path = "";
	private boolean recursive = true;

	enum MODE {
		all, 
		list, debug, evaluateOCL, 
		validateOCL, validate, 
		translate, exportDoc, exportJSON,
		importOpenAPI, importASN1, importJSON,
		openAPIDoc,exportTTCN3
	}
	
	public static void main(String[] args) throws Exception {
		Standalone app = new Standalone();
		//TODO: expose as arguments
		
//		app.modes.add(MODE.importJSON);
//		app.modes.add(MODE.exportJSON);
		
		app.path = "examples/openapi/reqres.yaml";
//		app.modes.add(MODE.importOpenAPI);
//		app.modes.add(MODE.list);
//		app.path = "examples/json/model.json";
		app.path = "examples/validation/Naming.tdltx";
		app.path = "examples/validation/Example.tdltx";
		app.recursive = false;
		app.modes.add(MODE.list);
		app.modes.add(MODE.validate);
		app.modes.add(MODE.exportDoc);
		app.modes.add(MODE.exportTTCN3);
//		targetExtension = "tdl";
//		app.modes.add(MODE.translate);
		
//		app.path = "examples/validation/Naming.tdltx.tdl";
//		app.modes.add(MODE.validateOCL);
		
		if (app.selected(MODE.list)) {
			String path = pathOrDefault(app.path, "examples/basics");
//			app.processElements(path, app::listElements);
			app.processElements(path, app::listElements);
		}
		
		//TODO: may fail
		//TODO: may fail due to literal value use without data type
		if (app.selected(MODE.validate)) {
			String path = pathOrDefault(app.path, "examples/basics");
			app.processElements(path, app::validate);
		}

		//TODO: may fail
		if (app.selected(MODE.translate)) {
			String path = pathOrDefault(app.path, "examples/basics");
			app.processElements(path, app::translate);
		}
		
		if (app.selected(MODE.importJSON)) {
			String path = pathOrDefault(app.path, "examples/json");
			app.processElements(path, jsonExtension, app::importJSON);
		}

		if (app.selected(MODE.exportJSON)) {
			String path = pathOrDefault(app.path, "examples/json/model.json-generated.tdltx");
//			path = "examples/json/e-line-examples-c-vlan-bridge.json-generated.tdltx";
			app.processElements(path, jsonExtension, app::exportJSON);
		}

		
		if (app.selected(MODE.exportDoc)) {
			String path = pathOrDefault(app.path, "examples/validation/Example.tdltx");
			app.processElements(path, app::exportDoc);
		}

		if (app.selected(MODE.exportTTCN3)) {
			String path = pathOrDefault(app.path, "examples/validation/Example.tdltx");
			app.processElements(path, app::exportTTCN3);
		}

		if (app.selected(MODE.validateOCL)) {
			String path = pathOrDefault(app.path, "examples/validation/Example.tdltx");
			app.processElements(path, app::validateOCL);
		}
		
		//TODO: can only be used in isolation as it interferes with OCL delegates afterwards..
		if (app.selected(MODE.evaluateOCL)) {
			String path = pathOrDefault(app.path, "examples/validation/Example.tdltx");
			app.processElements(path, app::evaluateOCLInline);
		}

		if (app.selected(MODE.importOpenAPI)) {
			String path = pathOrDefault(app.path, "examples/openapi");
			app.processElements(path, openapiExtension, app::importOpenAPI);
			//NOTE: keep in mind weird errors if legacy OpenAPI importer is imported as well in manifest (especially if it is first)
		}

		if (app.selected(MODE.openAPIDoc)) {
			String path = pathOrDefault(app.path, "examples/openapi");
			app.processElements(path, openapiExtension, app::documentOpenAPI);
			//NOTE: keep in mind weird errors if legacy OpenAPI importer is imported as well in manifest (especially if it is first)
		}

		if (app.selected(MODE.importASN1)) {
			String path = pathOrDefault(app.path, "examples/asn1");
			app.processElements(path, asnExtension, app::importASN1);
		}

		if (app.selected(MODE.debug)) {
			//individual file without dependencies
			app.processElements("examples/basics/Sample.tdltx", app::listElements);

			//individual file with missing dependencies
			app.processElements("examples/basics/ImportSample.tdltx", app::listElements);
			//validation will fail due to unresolved items
		}		
		
		//TODO: minimal example for TO translation
		//TODO: minimal example for TTCN-3 generation
		//TODO: clean up keywords automatically
	}

	private static String pathOrDefault(String path, String defaultPath) {
		return path.isEmpty() ? defaultPath : path;
	}

	private boolean selected(MODE m) {
		return modes.contains(MODE.all) || modes.contains(m);
	}

	private void processElements(String path, Consumer<String> operation) throws Exception {
		TDLHelper.resetResourceSet();
		processElements(path, sourceExtension, operation);
	}
	
	private void processElements(String path, String extension, Consumer<String> operation) throws Exception {
		if (recursive) {
			processElementsRecursive(path, extension, operation);
		} else {
			processElementsFlat(path, extension, operation);
		}
	}

	private void processElementsRecursive(String path, String extension, Consumer<String> operation) throws Exception {
		System.out.println("Processing recursively: "+path);
		if (extension.contains("tdl")) {
			Files.walk(Path.of(path))
				.filter(e->e.getFileName().toString().endsWith(extension))
				.forEach(e->TDLHelper.load(e.toAbsolutePath().toString()));
			TDLHelper.link();
			TDLHelper.check();
		}
		Files.walk(Path.of(path))
				.filter(e->e.getFileName().toString().endsWith(extension))
				.forEach(e->operation.accept(e.toAbsolutePath().toString()));
	}

	private void processElementsFlat(String path, String extension, Consumer<String> operation) throws Exception {
		File target = new File(path);
		//TODO: make recursive?
		if (target.isDirectory()) {
			FilenameFilter filter = (FilenameFilter) (dir, name) -> name.endsWith(extension);
			File[] files = target.listFiles(filter);
			Arrays.sort(files, (a, b) -> a.getName().compareTo(b.getName()));
			//load all resources first
			if (extension.contains("tdl")) {
				for (File f : files) {
					System.out.println("Loading: "+f.getAbsolutePath());
					TDLHelper.load(f.getAbsolutePath());
				}
				TDLHelper.link();
				TDLHelper.check();
			}
			for (File f : files) {
				System.out.println("Processing: "+f.getAbsolutePath());
				operation.accept(f.getAbsolutePath());
			}
		} else {
			if (extension.contains("tdl")) {
				System.out.println("Loading: "+target.getAbsolutePath());
				TDLHelper.load(target.getAbsolutePath());
				TDLHelper.link();
				TDLHelper.check();
			}
			System.out.println("Processing: "+target.getAbsolutePath());
			operation.accept(target.getAbsolutePath());
		}
	}

	private void listElements(String path) {
		Resource resource = TDLHelper.load(path);
		Package p = (Package) resource.getContents().get(0);
		System.out.println("Package: "+p.getName());
		p.getNestedPackage().forEach(e -> {
			System.out.println("  " + e.getName());
			System.out.println("    " + e.eClass().getName() + " : " + e.getQualifiedName());
		});
		p.getPackagedElement().forEach(e -> {
			System.out.println("  " + e.getName());
			System.out.println("    " + e.eClass().getName() + " : " + e.getQualifiedName());
			if (e instanceof StructuredDataType) {
				EList<Extension> extension = ((StructuredDataType) e).getExtension();
				if (!extension.isEmpty()) {
					System.out.println("    Extends: " + extension.stream()
						.map(x-> x.getExtending().getName())
						.collect(Collectors.joining(", "))
					);
				}
			}
		});
	}
	
	private void validate(String path) {
		Resource resource = TDLHelper.load(path);
		Validator validator = new Validator();
		//TODO: make robust against unresolved imports
		//TODO: report parsing / resolution errors
		try {
			List<UnsatisfiedConstraint> violations = validator.validate(resource);
			validator.dumpViolations(violations, true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void validateOCL(String path) {
		Resource resource = TDLHelper.load(path);
		Validator validator = new Validator();
		validator.validateOCL2(resource);
	}
	
	private void evaluateOCLInline(String path) {
		Resource resource = TDLHelper.load(path);
		OCL ocl = OCL.newInstance(TDLHelper.getResourceSet());
		EClass contextEClass = tdlPackage.Literals.GATE_TYPE;
		try {
			ExpressionInOCL query = ocl.createQuery(contextEClass,
					"self.allDataTypes()"
					);
			Query queryEval = ocl.createQuery(query);
			for (var o : EcoreUtil2.getAllContentsOfType(resource.getContents().get(0), GateType.class)) {
				System.out.println(o.allDataTypes());
				System.out.println(queryEval.evaluateUnboxed(o));
			}
		} catch (ParserException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void translate(String path) {
		Resource resource = TDLHelper.load(path);
		try {
			Resource target = TDLHelper.create(resource.getURI().path() + "." + targetExtension);
			target.getContents().addAll(EcoreUtil.copyAll(resource.getContents()));
			TDLHelper.store(target, true);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void importData(String path, AbstractTranslator translator) {
		File source = new File(path);
		try {
			translator.setTargetResource(TDLHelper.create(source.getAbsolutePath()+"-generated."+targetExtension));
			translator.initTargetResource(translator.cleanName(source.getName()));
			translator.translate(source.getAbsolutePath());
			TDLHelper.store(translator.getTargetResource(), true);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	private void importOpenAPI(String path) {
		importData(path, new OpenAPI2TDLTranslatorNext());
	}

	private void documentOpenAPI(String path) {
		Doc doc = new Doc();
		try {
			//TODO: make configurable
			doc.processModel(path, true);
			Files.writeString(Path.of(path+"-RQ-ICS-TSS.md"), doc.getContent());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	
	private void importASN1(String path) {
		importData(path, new ASN2TDLTranslator());
	}

	private void importJSON(String path) {
		importData(path, new JSON2TDLTranslator());
	}

	private void exportJSON(String path) {
		Resource resource = TDLHelper.load(path);
		TDL2JSONTranslator translator = new TDL2JSONTranslator();
		translator.transform((Package) resource.getContents().get(0));
	}

	private void exportDoc(String path) {
		Resource resource = TDLHelper.load(path);
		Generator generator = new Generator();
		String target = path+".docx";
		try {
			generator.generate(
					resource, target, 
					"Generated from "+resource.getURI().lastSegment(), 
					"TO_4_TABLE_TEMPLATE_EDITHELP"
					);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void exportTTCN3(String path) {
		try {
			new TTCN3StandaloneSetup().createInjectorAndDoEMFRegistration();
			Resource resource = TDLHelper.load(path);
			Transform transformer = new Transform();
			Resource ir = TDLHelper.create(path+"-generated.ttcn3m");
			transformer.transform(resource, ir);
			//This is important otherwise ghost references may occur
			ir.unload();
			Resource tr = TDLHelper.create(path+"-generated.ttcn3");
			transformer.transform(resource, tr);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	
//	private void importOpenAPIAll(String path) {
//		File target = new File(path);
//		if (target.isDirectory()) {
//			FilenameFilter filter = (FilenameFilter) (dir, name) -> name.endsWith(sourceExtension);
//			File[] files = target.listFiles(filter);
//			Arrays.sort(files, (a, b) -> a.getName().compareTo(b.getName()));
//			for (File f : files) {
//				importOpenAPISingle(f.getAbsolutePath());
//			}
//		} else {
//			importOpenAPISingle(target.getAbsolutePath());
//		}
//	}

}
