package org.etsi.mts.tdl.constraints.helper;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.etsi.mts.tdl.document.model.Content;
import org.etsi.mts.tdl.document.model.Document;

public class ConstraintExporter {

	private String generatedConstraints = "";

	private OutputFormat format;

	enum OutputFormat {
		EVL, OCL
	}

	public static void main(String[] args) {

		OutputFormat format = OutputFormat.EVL;
		String filename = "resources/es_20311901v010501p.docx";
		filename = "resources/es_20311904v010401p.docx";
		String[] extraImports = new String[0];
		String generatedPackage = "tdl";

		for (int i = 0; i < args.length; i++) {
			if (args[i].equalsIgnoreCase("--format"))
				if (args.length > i + 1) {
					if (args[i + 1].equalsIgnoreCase("ocl"))
						format = OutputFormat.OCL;
				}
			if (args[i].equalsIgnoreCase("--in"))
				if (args.length > i + 1) {
					filename = args[i + 1];
				}
			if (args[i].equalsIgnoreCase("--import"))
				if (args.length > i + 1) {
					extraImports = args[i + 1].split(",");
				}
			if (args[i].equalsIgnoreCase("--generated-package"))
				if (args.length > i + 1) {
					generatedPackage = args[i + 1];
				}
		}

		Document doc = DocumentHandler.loadDocument(new File(filename).getAbsolutePath());
		ConstraintExporter exporter = new ConstraintExporter();
		exporter.format = format;
		String title = doc.getTitle();
		title = title.replaceAll(" ", "-");
		String targetPath = "resources/generated/tdl-generated-" + title
				+ (format == OutputFormat.EVL ? ".evl" : ".ocl");
		exporter.exportConstraints(doc, generatedPackage, extraImports, targetPath);
	}

	public void exportConstraints(Document doc, String generatedPackage, String[] extraImports, String generatedConstraintsPath) {
		generatedConstraints = "";
		String imports = "";
		if (this.format == OutputFormat.OCL) {
			imports = "import 'http://www.etsi.org/spec/TDL/1.4.1'\n";
			for (String imp : extraImports) {
				imports += "import '" + imp + "'\n";
			}
			imports += "\n";
			imports += "package " + generatedPackage + "\n\n";
		} else
			imports = "import \"../library/common.eol\";\n" + "import \"../library/helper.eol\";\n"
					+ "import \"../library/debug.eol\";\n" + "\n";
		generatedConstraints += imports;
		processDocumentModel(doc);
		if (this.format == OutputFormat.OCL)
			generatedConstraints += "\n" + "endpackage" + "\n";
		Path gcp = Path.of(generatedConstraintsPath);
		try {
			Files.write(gcp, Arrays.asList(generatedConstraints.split("\n")), StandardOpenOption.WRITE,
					StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void processDocumentModel(Content c) {
		// DONE: Escape keywords
		// DONE: Export to file tdl-gen.evl (separate function)
		// TODO: Rename old one to tdl-ref.evl
		// TODO: Compare / Merge latest -> tdl.evl
		// TODO: Backport changes to source word document,
		// based on diff to tdl-gen.evl
		// generate again and repeat
		// -> in progress...
		// TODO: Test with change marks
		// TODO: Clean up all the lets
		// DONE: Handle special case of CompatibleConfiguration or
		// TODO: Restructure so that no special handling is needed in the future

		for (Content cx : c.getContent()) {
			if (cx.getText().equals("Constraints")) {
				String content = getConstraint(cx);
				generatedConstraints += content;
			}
			processDocumentModel(cx);
		}
	}

	private String getConstraint(Content cx) {
		if (this.format == OutputFormat.OCL)
			return getOclConstraint(cx);
		else
			return getEvlConstraint(cx);
	}

	//TODO:  enums shall be excluded
	Collection<Object> ignoreClasses = Arrays.asList("ParameterKind", "UnassignedMemberTreatment", "GateTypeKind", "ComponentInstanceRole");
	Pattern letPattern = Pattern.compile("\\s*let(.+?)\\s*in\\s(.*)", Pattern.DOTALL);
	Pattern isUniquePattern = Pattern.compile("->isUnique\\((.+?)\\)", Pattern.DOTALL);
	Pattern wsPattern = Pattern.compile("(\\s*)(.*)", Pattern.DOTALL);

	private String getEvlConstraint(Content cx) {
		String className = cx.getParent().getText().replaceAll(".+\\s", "");
		if (ignoreClasses.contains(className))
			return "";
		String content = "context " + className + " {" + "\n";
		for (Content cs : cx.getContent()) {
			var lines = Arrays.asList(cs.getText().split("\n"));
			var idIndex = 2;
			if (lines.size() > 3) {
				var title = lines.get(0);
				var description = lines.get(1);
				if (!lines.get(2).trim().startsWith("inv:")) {
					if (lines.get(0).trim().startsWith("inv:")) {
						idIndex = 0;
						title = "";
						description = "";
					}
//							ListIterator<String> linesIterator = lines.listIterator(2);
//							String next = linesIterator.next();
//							while (linesIterator.hasNext() && !next.trim().startsWith("inv:")) {
//								description+="\n  //"+next;
//								idIndex++;
//							}
				}
				var id = lines.get(idIndex).replaceAll("inv: ", "").replaceAll(":", "");
				var ocl = String.join("\n      ", lines.subList(idIndex + 1, lines.size()))
						.replaceAll("(import|function|guard)([\\W])", "`$1`$2")
						.replaceAll("::", "#")
						.replaceAll("oclContainer", "eContainer")
				// .replaceAll("\\s+", " ")
				;

				if (ocl.trim().startsWith("This")) {
					ocl = "    true" + " //" + ocl.trim();
				}

				Matcher m = letPattern.matcher(ocl);
				boolean isStatementBlock = false;
				if (m.matches()) {
					isStatementBlock = true;

					String vars = m.group(1);
					String expr = m.group(2);

					ocl = "{ ";
					String[] varDeclarations = vars.split(",");
					for (String v : varDeclarations) {
						// Not functional, just makes the output nicer
						Matcher wsm = wsPattern.matcher(v);
						if (wsm.matches())
							ocl += wsm.group(1) + "var " + wsm.group(2) + ";";
						else
							ocl += "var " + v + ";";
					}

					ocl += expr + ";";
					ocl += " }";
				}

				Matcher m2 = isUniquePattern.matcher(ocl);
				// Use . not -> otherwise Epsilon won't execute imported operation
				ocl = m2.replaceAll("->collect($1).isUnique()");

				content += "  //" + title + "\n";
				content += "  constraint " + id + " {" + "\n";
				content += "    check" + (isStatementBlock ? " " : ": ") + ocl + "\n";
				content += "    message:   self.prefix() + \n" + "               \"" + description + "\"" + "\n";
				content += "  }\n" + "\n";
			} else {
				content += "  //" + lines + "\n";
			}
		}
		content += "}\n" + "\n";
		return content;
	}

	private String getOclConstraint(Content cx) {
		String content = "context " + cx.getParent().getText().replaceAll(".+\\s", "") + "\n";
		boolean hasConstraints = false;
		for (Content cs : cx.getContent()) {
			var lines = Arrays.asList(cs.getText().split("\n"));
			var idIndex = 2;
			if (lines.size() > 3) {
				var title = lines.get(0);
				var description = lines.get(1);
				if (!lines.get(2).trim().startsWith("inv:")) {
					if (lines.get(0).trim().startsWith("inv:")) {
						idIndex = 0;
						title = "";
						description = "";
					}
				}
				var id = lines.get(idIndex).replaceAll("inv: ", "").replaceAll(":", "");
				var ocl = String.join("\n      ", lines.subList(idIndex + 1, lines.size()))
//						.replaceAll("(import|function|guard)([\\W])", "`$1`$2")
				// .replaceAll("\\s+", " ")
				;
//				ocl = ocl.replaceAll("OclInvalid", "true");

				if (ocl.trim().startsWith("This")) {
					ocl = "    true" + " --" + ocl.trim();
				}
				
				//Escape reserved words
				ocl = Pattern.compile("(?<=\\.)(body)(?=\\s|\\.|-|\\))").matcher(ocl).replaceAll("_'$1'");
				
				content += "  -- " + title + "\n";
				content += "  inv " + id + " ('" + description.replaceAll("\\'", "\\\\\\'") + "' + self.toString()):\n";
				content += "    " + ocl + "\n";
				content += "  \n\n";
				hasConstraints = true;
			} else {
				content += "  -- " + lines + "\n";
			}
		}
		if (!hasConstraints)
			return "";
		content += "\n" + "\n";
		return content;
	}

}
