package org.etsi.mts.tdl.tools.to.docx.poi;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.poi.xwpf.usermodel.IBodyElement;
import org.apache.poi.xwpf.usermodel.ParagraphAlignment;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFStyles;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlException;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.XtextPackage;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.etsi.mts.tdl.Behaviour;
import org.etsi.mts.tdl.Block;
import org.etsi.mts.tdl.CombinedBehaviour;
import org.etsi.mts.tdl.CompoundBehaviour;
import org.etsi.mts.tdl.ConditionalBehaviour;
import org.etsi.mts.tdl.LocalExpression;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.SingleCombinedBehaviour;
import org.etsi.mts.tdl.TestConfiguration;
import org.etsi.mts.tdl.TestDescription;
import org.etsi.mts.tdl.resources.ResourceHandler;
import org.etsi.mts.tdl.structuredobjectives.StructuredTestObjective;
import org.etsi.mts.tdl.structuredobjectives.TestObjectiveVariant;
import org.etsi.mts.tdl.structuredobjectives.VariantBinding;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTR;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTbl;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTText;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff;

public class Generator {

	int index = 0;
	String templateFilename = "resource/template.docx";
	public static String selectedTemplate = "TO_4_TABLE_TEMPLATE_EDITHELP";
	XWPFDocument template;
	private boolean hierarchical = true;
	//DONE: integrate with model
	//DONE: check export and build configuration
	//DONE: supersede current implementation
	//TODO: move filters where applicable
	//DONE: add support for headings based on packages
	//TODO: check for other relevant legacy adaptations
	//DONE: update all other icons
	//DONE: make configurable
	//TODO: Restructure and refactor

	public void generate(Resource resource, String filename, String title) throws Exception {
		generate(resource, filename, title, selectedTemplate);
	}
	
	public void generate(Resource resource, String filename, String title, String templateName) throws Exception {
		selectedTemplate = templateName;

		XWPFDocument document = createDocument();

		template = loadTemplate();
		loadStyles(document, template);
		
		createTitle(title, document);
		
		int hLevel = 3;
		String prefix = "X.Y"; //TODO: export to configuration
		if (hierarchical ) {
			generatePackageHeadings(resource, document, prefix, hLevel);
		} else {
			//flat generation
			EObject p = resource.getContents().get(0);
			List<StructuredTestObjective> stos = EcoreUtil2.getAllContentsOfType(p, StructuredTestObjective.class);
			generateSTOs(document, stos, prefix);
			List<TestDescription> tpds = EcoreUtil2.getAllContentsOfType(p, TestDescription.class);
			generateTPDs(document, tpds, prefix);
		}
		
		storeDocument(filename, document);
		template.close();
	}

	private void generatePackageHeadings(Resource resource, XWPFDocument document, String prefix, int hLevel) {
		//hierarchical generation
		List<Package> packages = getContentsOfType(resource, Package.class);		
		generatePackageHeadings(document, packages, prefix, hLevel);
	}

	private void generatePackageHeadings(XWPFDocument document, List<Package> packages, String prefix, int hLevel) {
		int i = 1;
		for (Package p : packages) {
			generatePackageHeading(document, prefix, i, p, hLevel);
			List<StructuredTestObjective> stos = getContentsOfType(p, StructuredTestObjective.class);
			String localPrefix = prefix+"."+i;
			generateSTOs(document, stos, localPrefix);
			generateTPDs(document, getContentsOfType(p, TestDescription.class), localPrefix);
			generatePackageHeadings(document, getContentsOfType(p, Package.class), localPrefix, hLevel+1);
			i++;
		}
	}

	private void generateSTOs(XWPFDocument document, List<StructuredTestObjective> stos, String prefix) {
		int i = 1;
		for (StructuredTestObjective sto : stos) {
			String sectionTitle = sto.getName();
			LinkedHashMap<String, String> map = getSTOReplacementMap(sto);
			generateTable(document, sectionTitle, map, prefix, i);
			LinkedHashMap<String, LinkedHashMap<String,String>> variants = getSTOVariantsMap(sto);
			generateVariants(document, variants, sectionTitle, prefix, i);
			i++;
		}
	}

	private void generateTPDs(XWPFDocument document, List<TestDescription> tpds, String prefix) {
		int i = 1;
		for (TestDescription tpd : tpds) {
			if (tpd.getAnnotation().stream().anyMatch(a -> a.getKey().getName().startsWith("Test Purpose"))) {
				//TODO: add constraints and validation, e.g. Objective present, etc.
				String sectionTitle = tpd.getName();
				LinkedHashMap<String, String> map = getTPDReplacementMap(tpd);
				generateTable(document, sectionTitle, map, prefix, i);
				//TODO: variants? reuse?
	//			LinkedHashMap<String, LinkedHashMap<String,String>> variants = getSTOVariantsMap(sto);
	//			generateVariants(document, variants);
				i++;
			}
		}
		
		//createTableToCtoUpdateFields(document);
	}

	private void createTableToCtoUpdateFields(XWPFDocument document) {
//		Create table of figures field. Word will updating that field while opening the file.
		XWPFParagraph paragraph = document.createParagraph();
		CTSimpleField toc = paragraph.getCTP().addNewFldSimple();
		toc.setInstr("TOC \\c \"tab\" \\* MERGEFORMAT");
		toc.setDirty(STOnOff.TRUE); // set dirty to forcing update
//		TODO: remove again? or move to top?
	}

	
	private void generatePackageHeading(XWPFDocument document, String prefix, int i, Package p, int hLevel) {
		System.out.println(p.getName());
		XWPFParagraph par = document.createParagraph();
		par.setStyle(template.getStyles().getStyleWithName("heading "+hLevel).getStyleId());
		XWPFRun titleRun = par.createRun();
		titleRun.setText(prefix+"."+i);
		titleRun.addTab();
		titleRun.setText(p.getName());
	}

	//TODO: extract and reuse
	private <T> List<T> getContentsOfType(Resource resource, Class<T> type) {
		List<T> elements = resource.getContents()
				.stream()
				.filter(e -> type.isInstance(e))
				.map(e -> (T) e)
				.collect(Collectors.toList())
				;
		return elements;
	}

	private <T> List<T> getContentsOfType(EObject eObject, Class<T> type) {
		List<T> elements = eObject.eContents()
				.stream()
				.filter(e -> type.isInstance(e))
				.map(e -> (T) e)
				.collect(Collectors.toList())
				;
		return elements;
	}

	
	//TODO: cleanup and restructure
	private void generateVariants(XWPFDocument document,
			LinkedHashMap<String, LinkedHashMap<String, String>> variants, String sectionTitle, String prefix, int v) {
		LinkedHashSet<String> headers = new LinkedHashSet<>();
		for (LinkedHashMap<String, String> e : variants.values()) {
			headers.addAll(e.keySet());
		}
		if (headers.size() > 0) {
			createCaption("Variants for "+sectionTitle, document, prefix, v);
			
			XWPFTable table = loadTemplate(template, selectedTemplate+"_VARIANT");
			if (table == null) {
				return;
			}
//			XWPFParagraph par = document.createParagraph();
//			//par.setStyle("heading 2");
//			par.setSpacingBeforeLines(120);
//			par.setSpacingAfterLines(120);
//			
//			//optional: title
//			XWPFRun titleRun = par.createRun();
//			titleRun.setText("Variants");
//			titleRun.setFontSize(24);

			CTTbl tbl = document.getDocument().getBody().insertNewTbl(document.getDocument().getBody().sizeOfTblArray()); 
			tbl.set(table.getCTTbl()); 
			XWPFTable target = new XWPFTable(tbl, document);
			target.getRow(0).getCell(0)
				.getParagraphs().get(0)
				.getRuns().get(0)
				.setText("REPLACE", 0);
			target.removeRow(0);

			ArrayList<String> hList = new ArrayList<>(headers);
			target.getCTTbl().addNewTblGrid();
			for (int i = 0; i<hList.size(); i++) {
				if (i > 0) {
//					target.addNewCol();
//					XWPFTableCell cell = target.getRow(0).addNewTableCell();
					XWPFTableCell cell = target.getRow(0).createCell();
//					target.addNewCol();
				}
				XWPFTableCell cell = target.getRow(0).getCell(i+1);
				cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(1200));
//				cell.getCTTc().getTcPr().getTcW().setW(BigInteger.valueOf(100));
//				cell.setWidth("10");
//				cell.setWidthType(TableWidthType.PCT);
//				cell.setText(hList.get(i));

				XWPFParagraph p = target.getRow(0).getCell(0).getParagraphs().get(0);
//				cell.getParagraphs().get(0).setStyle(p.getStyle());
				cell.getParagraphs().get(0).getCTP().set(p.getCTP());
				cell.getParagraphs().get(0).getCTP().getRList().get(0).getTList().get(0).setStringValue(hList.get(i));
//				cell
//					.getParagraphs().get(0)
//					.getRuns().get(0)
//					.setText(hList.get(i), 0);
			}
			int r = 1;
			for (String id : variants.keySet()) {
				XWPFTableRow row = target.getRow(r);
				XWPFParagraph rp = target.getRow(1).getCell(0).getParagraphs().get(0);
//				row.getCell(0).getParagraphs().get(0).getRuns().get(0).setText(id,0);
//				cell.getParagraphs().get(0).createRun().getCTR().set(p.getRuns().get(0).getCTR());
				if (r > 1) {
					target.createRow();
					//XWPFTableCell hCell = target.getRow(r).createCell();
				}
				XWPFTableCell hCell = target.getRow(r).getCell(0);
				hCell.getParagraphs().get(0).getCTP().set(rp.getCTP());
				hCell.getParagraphs().get(0).getCTP().getRList().get(0).getTList().get(0).setStringValue(id);
				for (int i = 0; i<hList.size(); i++) {
					if (r == 1 && i > 0) {
//						XWPFTableCell cell = target.getRow(r).addNewTableCell();
						XWPFTableCell cell = target.getRow(r).createCell();
//						target.addNewCol();
					}
					XWPFTableCell cell = target.getRow(r).getCell(i+1);
					cell.getCTTc().addNewTcPr().addNewTcW().setW(BigInteger.valueOf(1200));
//					cell.getCTTc().getTcPr().getTcW().setW(BigInteger.valueOf(100));
//					cell.setWidth("10");
//					cell.setWidthType(TableWidthType.PCT);
					if (variants.get(id).containsKey(hList.get(i))) {
//						cell.setText(variants.get(id).get(hList.get(i)));
						XWPFParagraph p = target.getRow(r).getCell(0).getParagraphs().get(0);
//						cell.getParagraphs().get(0).createRun().getCTR().set(p.getRuns().get(0).getCTR());
						cell.getParagraphs().get(0).getCTP().set(p.getCTP());
						cell.getParagraphs().get(0).getCTP().getRList().get(0).getTList().get(0).setStringValue(variants.get(id).get(hList.get(i)));
//						cell
//							.getParagraphs().get(0)
//							.getRuns().get(0)
//							.setText(variants.get(id).get(hList.get(i)), 0);
					}
				}
				r++;
			}
			
			target.getCTTbl().addNewTblGrid();
			for (XWPFTableCell cell : target.getRow(0).getTableCells()) {
			    table.getCTTbl().getTblGrid().addNewGridCol().setW(BigInteger.valueOf(3200));
			}
			XWPFParagraph par = document.createParagraph();
		}

	}


	void generateExample(String filename, String title) throws Exception {
		XWPFDocument document = createDocument();

		template = loadTemplate();
		loadStyles(document, template);
		
		createTitle(title, document);
		String prefix = "X.Y.1";
		for (int i = 0; i < 5; i++) {
			String sectionTitle = "Test "+i;
			LinkedHashMap<String, String> map = getReplacementMap();
			generateTable(document, sectionTitle, map, prefix, i);
		}
		
		storeDocument(filename, document);
		template.close();
	}

	private XWPFDocument loadTemplate() throws Exception {
		URI templateLocation = ResourceHandler.getSourceUri(this.getClass(), "org.etsi.mts.tdl.tools.to.docx.poi", templateFilename);
		template = loadTemplateDocument(templateLocation);
		return template;
	}

	private void generateTable(XWPFDocument document, String sectionTitle,
			LinkedHashMap<String, String> map, String prefix, int i) {

		//TODO: make configurable
		//TODO: include caption in template?
		createCaption("Test Purpose "+sectionTitle, document, prefix, i);
//		createCaptionWithFieldCodes("Test Purpose "+sectionTitle, document, prefix, i);
		//clone table
		XWPFTable table = loadTemplate(template, selectedTemplate);

		CTTbl tbl = document.getDocument().getBody().insertNewTbl(document.getDocument().getBody().sizeOfTblArray()); 
		tbl.set(table.getCTTbl()); 
		XWPFTable target = new XWPFTable(tbl, document);
		target.getRow(0).getCell(0)
			.getParagraphs().get(0)
			.getRuns().get(0)
			.setText("REPLACE", 0);
		target.removeRow(0);
				
		makeReplacements(target, map);
		cleanUp(target);

		XWPFParagraph par = document.createParagraph();
	}

	private void cleanUp(XWPFTable target) {
		//clean up
		//collect colored rows and colors to clear
		LinkedHashMap<String, TreeSet<Integer>> colors = new LinkedHashMap<>();
		List<String> cleanColors = new ArrayList<>();
		int rn = 0;
		for (XWPFTableRow r : target.getRows()) {
			for (XWPFTableCell c : r.getTableCells()) {
				if (c.getColor()!=null && !c.getColor().equals("auto")) {
					colors.putIfAbsent(c.getColor(), new TreeSet<>());
					colors.get(c.getColor()).add(rn);
					if (c.getText().trim().isEmpty()) {
						cleanColors.add(c.getColor());
					}
				}
			}
			rn++;
		}
		//clear colored rows
		int o = 0;
		for (String c : colors.keySet()) {
			if (cleanColors.contains(c)) {
				for (int r : colors.get(c)) {
					target.removeRow(r-o);
					o++;
				}
			}
		}
		//reset remaining colors
		for (XWPFTableRow r : target.getRows()) {
			for (XWPFTableCell c : r.getTableCells()) {
				if (c.getColor()!=null && !c.getColor().equals("auto")) {
					c.setColor(null);
					c.getCTTc().getTcPr().getShd().unsetThemeFill();
//					c.getCTTc().getTcPr().getShd().unsetThemeFillTint();
				}
				if (c.getColor()!=null) {
				}
			}
		}
	}

	private void makeReplacements(XWPFTable target, LinkedHashMap<String, String> map) {
		//replace
		for (XWPFTableRow r : target.getRows()) {
			for (XWPFTableCell c : r.getTableCells()) {
				for (XWPFParagraph p : c.getParagraphs()) {
					for (XWPFRun run : p.getRuns()) {
						if (map.containsKey(run.getText(0))) {
							String text = map.get(run.getText(0));
							if (text == null || text.isEmpty()) {
								for (XWPFRun rr : p.getRuns()) {
									rr.setText("", 0);
								}
							} else {
								String[] lines = text.split("\n");
								run.setText("", 0);
								int l = 0;
								for (String line : lines) {
									l++;
									run.setText(line);
									if (l < lines.length) {
										run.addBreak();
									}
								}
							}
						}
					}
				}
			}
		}
	}
	
	private LinkedHashMap<String, LinkedHashMap<String, String>> getSTOVariantsMap(StructuredTestObjective sto) {
		LinkedHashMap<String, LinkedHashMap<String, String>> variants = new LinkedHashMap<>();
		if (sto.getVariants() != null) {
			for (TestObjectiveVariant v : sto.getVariants().getVariants()) {
				variants.put(v.getName(), new LinkedHashMap<>());
				if (v.getDescription() != null) {
					variants.get(v.getName()).put("Description", v.getDescription()
							.replaceAll("\"", "")
							.replaceAll("\\s*\n\\s+", " ")
							);
				}
				if (!v.getObjectiveURI().isEmpty()) {
					variants.get(v.getName()).put("Reference", String.join("\n",v.getObjectiveURI()));
				}
				if (!v.getPicsReference().isEmpty()) {
					String pics = String.join(" ", v.getPicsReference()
							.stream()
							.map(p -> NodeModelUtils.getNode(p).getText())
							.collect(Collectors.toList()))
							.trim()
							;
					variants.get(v.getName()).put("PICS", pics);
				}
				for (VariantBinding b : v.getBindings()) {
					String value = NodeModelUtils.getNode(b.getValue()).getText();
					String boundTo = NodeModelUtils.getNode(b.getBoundTo()).getText();
					variants.get(v.getName()).put(value, boundTo);
				}
			}
		}
		return variants;
	}


	private LinkedHashMap<String, String> getTPDReplacementMap(TestDescription tpd) {
		//map
		//TODO: complete, refine, restructure
		LinkedHashMap<String,String> map = new LinkedHashMap<>();
		map.put(Placeholders.NAME, tpd.getName());
		//TODO: what if 0? what if more than 1?
		//TODO: make more robust
		//DONE: remove formatting spaces?
		if (tpd.getTestObjective().size() == 1) {
			map.put(Placeholders.DESCRIPTION, tpd.getTestObjective().get(0).getDescription()
					.replaceAll("\"", "")
					.replaceAll("\\s*\n\\s+", " ")
					);
			String uri = String.join("\n", tpd.getTestObjective().get(0).getObjectiveURI()).trim();
			map.put(Placeholders.URI, uri.replaceAll("\"",""));
		} else {
			//TODO: handle more adequately
			String error = "N/A (multiple or no Test Objectives linked)";
			map.put(Placeholders.DESCRIPTION, error);
			map.put(Placeholders.URI, error);
		}
		TestConfiguration configuration = tpd.getTestConfiguration();
		String config = "";
		if (configuration != null) {
			if (configuration.getName()!=null) {
				config = configuration.getName();
			}
		}
		map.put(Placeholders.CONFIGURATION, config);
		//TODO: how is this addressed? annotations? what about and/or? -> not supported yet
		String pics = "N/A";
		//TODO: make more robust
		SingleCombinedBehaviour tpdBehaviour = (SingleCombinedBehaviour) tpd.getBehaviourDescription().getBehaviour();
		EList<LocalExpression> guard = tpdBehaviour.getBlock().getGuard();
		if (!guard.isEmpty()) {
			ICompositeNode guardNode = NodeModelUtils.findActualNodeFor(guard.get(0));
			String tokenText = NodeModelUtils.getTokenText(guardNode);
			pics = tokenText
//					.replaceAll("\\(|\\)", "")
					;
		}
//		String pics = String.join(" ", tpd.getPicsReference()
//				.stream()
//				.map(p -> NodeModelUtils.getNode(p).getText())
//				.collect(Collectors.toList()))
//				.trim()
//				;
		map.put(Placeholders.PICS, pics);
		
		map.put(Placeholders.INITIAL, "");
		map.put(Placeholders.EXPECTED, "");
		map.put(Placeholders.FINAL, "");

		Behaviour behaviour = tpd.getBehaviourDescription().getBehaviour();
		if (behaviour instanceof CompoundBehaviour) {
			Block block = ((CompoundBehaviour) behaviour).getBlock();
			for (Behaviour b : block.getBehaviour()) {
				if (!b.getAnnotation().isEmpty()) { //TODO: skip altogether if no annotations found?
					if (b.getAnnotation().get(0).getKey().getName().startsWith("Initial")) {
						//TODO: filter extra new line
						ICompositeNode node = NodeModelUtils.getNode(b);
						String initialConditions = node.getText();
						initialConditions = initialConditions.replaceAll("\\s*Initial conditions", "");
						initialConditions = filterSource(initialConditions, "\n", "\\w").trim();
						initialConditions = filterComments(node, initialConditions);

						map.put(Placeholders.INITIAL, initialConditions);
					} else if  (b.getAnnotation().get(0).getKey().getName().startsWith("Expected")) {
						ICompositeNode node = NodeModelUtils.getNode(b);
						String expected = node.getText();
						expected = expected.replaceAll("\\s*Expected behaviour", "");
						expected = filterSource(expected, "\n", "ensure").trim();
						expected = filterComments(node, expected);
						map.put(Placeholders.EXPECTED, expected);
						
						EList<Behaviour> expectedBehaviours = ((CompoundBehaviour) b).getBlock().getBehaviour();
						if (expectedBehaviours.size() == 2) {
							String when = NodeModelUtils.getNode(expectedBehaviours.get(0)).getText();
							String then = NodeModelUtils.getNode(expectedBehaviours.get(1)).getText();
							//TODO: a bit of a hack
							when = filterSource(when, "\n", "\\s\\s\\s\\s\\w");
							when = filterComments(node, when);
							//then = filterSource(then, "\n", "\\s+\\w");
							//then = filterSource(then, "\n", "\\s+\\w");
							then = filterSource(then, "\n", "\\s\\s\\s\\s\\w");
							then = filterComments(node, then);

							map.put(Placeholders.WHEN, "when {"+when+"\n}");
							map.put(Placeholders.THEN, "then {"+then+"\n}");
						}
					} else if  (b.getAnnotation().get(0).getKey().getName().startsWith("Final")) {
						ICompositeNode node = NodeModelUtils.getNode(b);
						String finalConditions = node.getText();
						finalConditions = finalConditions.replaceAll("\\s*Final conditions", "");
						finalConditions = filterSource(finalConditions, "\n", "\\w").trim();
						finalConditions = filterComments(node, finalConditions);

						map.put(Placeholders.FINAL, finalConditions);
						//TODO: filter extra new line
					} else {
						//TODO: Handle other unknown blocks
					}
				}
			}
		}

		return map;
	}

	private String filterComments(ICompositeNode node, String expected) {
		//DONE: filter empty lines?
		//DONE: export to method 
		//TODO: update, expose
		String filterPrefix = "//!";
		ArrayList<String> filteredComments = new ArrayList<>();
		for (var n : node.getLeafNodes()) {
			if (n.getGrammarElement() instanceof TerminalRule && ((TerminalRule) n.getGrammarElement()).getName().equals("SL_COMMENT")) {
				if (n.getText().trim().startsWith(filterPrefix)) {
					filteredComments.add(n.getText().trim());
				}
			}
		}
		for (var c : filteredComments) {
			expected = expected.replace(c, ""); 
		}
		expected = expected.replaceAll("\n[\\s\\t]+\n", "\n");
		System.out.println(expected);
		return expected;
	}

	
	private LinkedHashMap<String, String> getSTOReplacementMap(StructuredTestObjective sto) {
		//map
		//TODO: complete, refine, restructure
		//TODO: transfer to variants as well
		LinkedHashMap<String,String> map = new LinkedHashMap<>();
		map.put(Placeholders.NAME, sto.getName());
		map.put(Placeholders.DESCRIPTION, sto.getDescription()
				.replaceAll("\"", "")
				.replaceAll("\\s*\n\\s+", " ")
				);
		String uri = String.join("\n", sto.getObjectiveURI()).trim();
		map.put(Placeholders.URI, uri.replaceAll("\"",""));
		TestConfiguration configuration = sto.getConfiguration();
		String config = "";
		if (configuration != null) {
			if (configuration.getName()!=null) {
				config = configuration.getName();
			}
		}
		map.put(Placeholders.CONFIGURATION, config);
		String pics = String.join(" ", sto.getPicsReference()
				.stream()
				.map(p -> NodeModelUtils.getNode(p).getText())
				.collect(Collectors.toList()))
				.trim()
				;
		map.put(Placeholders.PICS, pics);
		map.put(Placeholders.INITIAL, "");
		if (sto.getInitialConditions()!=null) {
			//TODO: filter extra new line
			ICompositeNode node = NodeModelUtils.getNode(sto.getInitialConditions());
			String initialConditions = node.getText();
			initialConditions = initialConditions.replaceAll("\\s*Initial conditions", "");
			initialConditions = filterSource(initialConditions, "\n", "\\w").trim();
			initialConditions = filterComments(node, initialConditions);

			map.put(Placeholders.INITIAL, initialConditions);
		}
		
		if (sto.getExpectedBehaviour()!=null) {
			ICompositeNode node = NodeModelUtils.getNode(sto.getExpectedBehaviour());
			String expected = node.getText();
			expected = expected.replaceAll("\\s*Expected behaviour", "");
			expected = filterSource(expected, "\n", "ensure").trim();
			expected = filterComments(node, expected);
			map.put(Placeholders.EXPECTED, expected);
		} else {
			map.put(Placeholders.EXPECTED, "");
		}

		map.put(Placeholders.FINAL, "");
		if (sto.getFinalConditions()!=null) {
			//TODO: filter extra new line
			ICompositeNode node = NodeModelUtils.getNode(sto.getFinalConditions());
			String finalConditions = node.getText();
			finalConditions = finalConditions.replaceAll("\\s*Final conditions", "");
			finalConditions = filterSource(finalConditions, "\n", "\\w").trim();
			finalConditions = filterComments(node, finalConditions);
			map.put(Placeholders.FINAL, finalConditions);
		}

		if (sto.getExpectedBehaviour()!=null
				&& sto.getExpectedBehaviour().getWhenClause()!=null
				&& sto.getExpectedBehaviour().getThenClause()!=null
				) {
			String when = NodeModelUtils.getNode(sto.getExpectedBehaviour().getWhenClause()).getText();
			String then = NodeModelUtils.getNode(sto.getExpectedBehaviour().getThenClause()).getText();
			//TODO: a bit of a hack
			when = filterSource(when, "\n", "\\s\\s\\s\\s\\w");
			//then = filterSource(then, "\n", "\\s+\\w");
			then = filterSource(then, "\n", "\\s\\s\\s\\s\\w");
	
			map.put(Placeholders.WHEN, "when {"+when+"\n}");
			map.put(Placeholders.THEN, "then {"+then+"\n}");
		}
		return map;
	}

	private String filterSource(String source, String prefix, String offsetKeyword) {
		Matcher matcher = Pattern.compile(prefix+"(.+?)"+offsetKeyword).matcher(source);
		String offset = "";
		if (matcher.find()) {
			offset = matcher.group(1)
					.replaceAll("\t", "    ");
			System.out.println("|"+offset+"|");
		}
		source = source
				.replaceAll("\t", "    ") //tabs
				.replaceAll("\n"+offset, "\n") //leading space
//				.replaceAll("\n        ", "\n") //leading space -> detect automatically
				.replaceAll("[\\s\n\r]+"+prefix, prefix)
				.replaceAll(" \\((typed|predefined)\\) ", " ") //shall be optional
				.replaceAll(" entity ", " ") //shall be optional
				.replaceAll("\\^", "") //shall be optional
				.replaceAll(";", "") //shall be optional
//				.replaceAll("\\(|\\)", "") //shall be optional
				;
		return source;
	}

	private LinkedHashMap<String, String> getReplacementMap() {
		//map
		LinkedHashMap<String,String> map = new LinkedHashMap<>();
		map.put(Placeholders.NAME, "Name");
		map.put(Placeholders.DESCRIPTION, "Descripion");
		map.put(Placeholders.URI, "URI");
		map.put(Placeholders.CONFIGURATION, "Configuration");
		map.put(Placeholders.PICS, "PICS");
		map.put(Placeholders.INITIAL, "Initial");
//			map.put(Placeholders.INITIAL, "");
		map.put(Placeholders.EXPECTED, "Expected\nWhen\n  {\n  }\nThen\n  {\n  }");
		map.put(Placeholders.FINAL, "Final");
		map.put(Placeholders.FINAL, "");
		map.put(Placeholders.WHEN, "When");
		map.put(Placeholders.THEN, "Then");
		return map;
	}

	private XWPFTable loadTemplate(XWPFDocument template, String selectedTemplate) {
		XWPFTable table = null;
		List<IBodyElement> bodyElements = template.getBodyElements();
		for (IBodyElement e : bodyElements) {
			if (e instanceof XWPFTable) {
				String id = ((XWPFTable) e).getRow(0).getCell(0).getText();
				if (id.trim().equals(selectedTemplate)) {
					table = (XWPFTable) e;
				}
			}
		}
		return table;
	}
	
	private XWPFDocument createDocument() {
		XWPFDocument report = new XWPFDocument();
		return report;
	}

	private XWPFDocument loadTemplateDocument(URI templateLocation) throws Exception {
		InputStream fis = (InputStream) templateLocation.toURL().openStream();
		XWPFDocument document = new XWPFDocument(fis);
		fis.close();
		return document;
	}

	
	private void storeDocument(String reportLocation, XWPFDocument report) {
		try {
			FileOutputStream out = new FileOutputStream(reportLocation);
			report.write(out);
			out.close();
			report.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}


	private void createTitle(String title, XWPFDocument report) {
		XWPFParagraph titleParagraph = report.createParagraph();
		titleParagraph.setAlignment(ParagraphAlignment.CENTER);
		XWPFRun titleRun = titleParagraph.createRun();
		titleRun.setText("Export: "+title);
		titleRun.setColor("444444");
		titleRun.setBold(true);
		titleRun.setFontFamily("Helvetica");
		titleRun.setFontSize(20);
	}

	public void createCaption(String text, XWPFDocument report, String prefix, int i) {
		//implementation without field codes and automated numbering
		XWPFParagraph paragraph = report.createParagraph();
	    paragraph.setStyle("TH");
	    paragraph.setSpacingAfter(0); // Set spacing after to 0. So caption will follow immediately under the figure.
	    XWPFRun run = paragraph.createRun();
	    run.setText("Table "+prefix+"-"+i+": " + text);
	}
	
	
	public void createCaptionWithFieldCodes(String text, XWPFDocument report, String prefix, int i) {
		//implementation with field codes if needed, more complex and requires 
		XWPFParagraph paragraph = report.createParagraph();
	    paragraph.setStyle("TH");
	    paragraph.setSpacingAfter(0); // Set spacing after to 0. So caption will follow immediately under the figure.
	    XWPFRun run = paragraph.createRun();
	    run.setText("Table ");
	    CTR ctr;
	    CTRPr ctrpr;
	    CTText ctText;

	    CTSimpleField seq = paragraph.getCTP().addNewFldSimple();
	    String sectionNumberPrefix = " Heading 3 2 \\s "; // TODO: Does not quite work yet, updating references causes error, probably needs headings to use field codes as well
	    seq.setInstr(sectionNumberPrefix);
	    ctr = seq.addNewR();
	    ctrpr = ctr.addNewRPr();
	    ctrpr.addNewNoProof();
	    ctText = ctr.addNewT();
	    ctText.setStringValue(""+prefix); //set numbers manually? alternatively need to manually or automatically force update

	    ctr = paragraph.getCTP().addNewR();
	    ctr.addNewNoBreakHyphen();

	    seq = paragraph.getCTP().addNewFldSimple();
	    String tableNumberText = " SEQ tab \\* ARABIC \\s 2 ";
	    seq.setInstr(tableNumberText);
	    ctr = seq.addNewR();
	    ctrpr = ctr.addNewRPr();
	    ctrpr.addNewNoProof();
	    ctText = ctr.addNewT();
	    ctText.setStringValue(""+i); //set numbers manually? alternatively need to manually or automatically force update

	    run = paragraph.createRun(); // needed or numbers are at end, ie Figure : some text1.1
	    run.setText(": " + text);
	}

	private void loadStyles(XWPFDocument report, XWPFDocument templateDocument) {
		XWPFStyles newStyles = report.createStyles();
		try {
			newStyles.setStyles(templateDocument.getStyle());
		} catch (XmlException | IOException e) {
			e.printStackTrace();
		}
	}

}


