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

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.serializer.impl.Serializer;
import org.etsi.mts.tdl.Annotation;
import org.etsi.mts.tdl.AnnotationType;
import org.etsi.mts.tdl.Comment;
import org.etsi.mts.tdl.Element;
import org.etsi.mts.tdl.structuredobjectives.EntityBinding;
import org.etsi.mts.tdl.structuredobjectives.EntityReference;
import org.etsi.mts.tdl.structuredobjectives.Event;
import org.etsi.mts.tdl.structuredobjectives.EventOccurrence;
import org.etsi.mts.tdl.structuredobjectives.EventOccurrenceSpecification;
import org.etsi.mts.tdl.structuredobjectives.EventReference;
import org.etsi.mts.tdl.structuredobjectives.EventSequence;
import org.etsi.mts.tdl.structuredobjectives.EventTemplateOccurrence;
import org.etsi.mts.tdl.structuredobjectives.ExpectedBehaviour;
import org.etsi.mts.tdl.structuredobjectives.InitialConditions;
import org.etsi.mts.tdl.structuredobjectives.PICSReference;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.TPLan2StandaloneSetup;
import org.etsi.mts.tdl.TimeConstraint;
import org.etsi.mts.tdl.tdlFactory;
import org.etsi.mts.tdl.structuredobjectives.StructuredTestObjective;
import org.osgi.framework.Bundle;

import java.util.logging.Logger;

import com.google.inject.Injector;

public class TemplateApp {

	private WordTemplateTestObjective wordTemplateExporter;
	private String templateName = 
			"TO_1_TABLE_TEMPLATE"
//			"TO_2_TABLE_TEMPLATE"
//			"TO_3_TABLE_TEMPLATE_ONEM2M"
			;
	protected static Logger log;
	protected Injector injector;
	private boolean filterKeywords = true;
	private String templateFileName = "TO_TableTemplates.docx";
	public static boolean inlineEventOccurrenceTemplates = true;

	public void generate(String inputPath, Package p) {
		templateFileName = "TO_TableTemplatesR1.docx";
		String outputPath = new File(inputPath).getParentFile().getAbsolutePath();
		String fileName = new File(inputPath).getName();
		try {
			process(p, outputPath+"/"+fileName+".docx");
		} catch (Docx4JException e) {
			e.printStackTrace();
		}
	}

	public void generate(String inputPath, Resource r) {
		templateFileName = "TO_TableTemplatesR1.docx";
		String outputPath = new File(inputPath).getParentFile().getAbsolutePath();
		String fileName = new File(inputPath).getName();
		try {
			for (EObject o : r.getContents()) {
				process((Package)o, outputPath+"/"+fileName+".docx");
			}
		} catch (Docx4JException e) {
			e.printStackTrace();
		}
	}

	public void attachSources(Resource resource) {
		Package packageContainer = (Package) resource.getContents().get(0);

		Package predefined = tdlFactory.eINSTANCE.createPackage();
		predefined.setName("PREDEFINED");
		packageContainer.getNestedPackage().add(predefined);
		AnnotationType source = tdlFactory.eINSTANCE.createAnnotationType();
		source.setName("SOURCE");
		predefined.getPackagedElement().add(source);
		
		List<Element> describedElements = new ArrayList<Element>();
		describedElements.addAll(EcoreUtil2.getAllContentsOfType(packageContainer, InitialConditions.class));
		describedElements.addAll(EcoreUtil2.getAllContentsOfType(packageContainer, ExpectedBehaviour.class));
		describedElements.addAll(EcoreUtil2.getAllContentsOfType(packageContainer, EventSequence.class));
//		describedElements.addAll(EcoreUtil2.getAllContentsOfType(packageContainer, Event.class));
//		describedElements.addAll(EcoreUtil2.getAllContentsOfType(packageContainer, TimeConstraint.class));
//		describedElements.addAll(EcoreUtil2.getAllContentsOfType(packageContainer, EventReference.class));
		for (Element e : describedElements) {
			ICompositeNode node = NodeModelUtils.getNode(e);
			Annotation a = tdlFactory.eINSTANCE.createAnnotation();
			a.setKey(source);
			String text = node.getText();
			text = text.replaceAll("\t", "    ");
			String indent = getIndent(text, 1);
			text = text.trim();
			text = text.replaceAll("\n"+indent, "\n");
			a.setValue(text);
			e.getAnnotation().add(a);
		}
	}

    public String getIndent(String s, int start)
    {

        int limit = s.length();
        int i = start;

        for (; i < limit; i++)
        {	
            if (!String.valueOf(s.charAt(i)).equals(" "))
            {
                break;
            }
        }

        return s.substring(start, i);
    }

	
	public String serialise(EObject o) {
		Injector injector = new TPLan2StandaloneSetup().createInjectorAndDoEMFRegistration();
		String s = "";
		Serializer serializer = injector.getInstance(Serializer.class); 

		try {  
			s += serializer.serialize(o);  
		} catch (Exception ex) { // fall back:  
			System.out.println("Object could not be serialized"); 
			System.out.println(ex.getMessage());
			ex.printStackTrace();
		}  
		
		return s;
	}


	public void inlineEventOccurrenceTemplates(Resource r) {
		for (EObject o : r.getContents()) {
			inlineEventOccurrenceTemplates((Package)o);
		}
	}
	
	public void inlineEventOccurrenceTemplates(Package p) {
		List<EventSequence> eSequences = EcoreUtil2.getAllContentsOfType(p, EventSequence.class);
		for (EventSequence es : eSequences) {
			for (EventOccurrence eo : es.getEvents()) {
				if (eo instanceof EventTemplateOccurrence) {
					EventOccurrenceSpecification eSpec = ((EventTemplateOccurrence) eo).getEventTemplate().getEventSpecification();
					int i = es.getEvents().indexOf(eo);
					EventOccurrenceSpecification templateOccurrence = EcoreUtil.copy(eSpec);
					for (Comment c : eo.getComment()) {
						templateOccurrence.getComment().add(EcoreUtil.copy(c));
					}
					if (((EventTemplateOccurrence) eo).getOccurrenceArgument() != null) {
						templateOccurrence.setEventArgument(((EventTemplateOccurrence) eo).getOccurrenceArgument());
					}
					if (!((EventTemplateOccurrence) eo).getEntityBinding().isEmpty()) {
						for (EntityBinding b : ((EventTemplateOccurrence) eo).getEntityBinding()) {
							EntityReference occurrenceEntity = b.getOccurrenceEntity();
							if ((b.getTemplateEntity().getEntity() !=null 
									&& templateOccurrence.getEntityReference().getEntity() != null
									&& b.getTemplateEntity().getEntity().equals(templateOccurrence.getEntityReference().getEntity()))
								||
								(b.getTemplateEntity().getComponent() !=null 
								&& templateOccurrence.getEntityReference().getComponent() != null
								&& b.getTemplateEntity().getComponent().equals(templateOccurrence.getEntityReference().getComponent()))
							) {
								templateOccurrence.setEntityReference(occurrenceEntity);
							}
							//TODO: also compare qualifiers?
							//TODO: handle component references
							for (EntityReference er : templateOccurrence.getOppositeEntityReference()) {
								if ((b.getTemplateEntity().getEntity() !=null
										&& er.getEntity() !=null
										&& b.getTemplateEntity().getEntity().equals(er.getEntity()))
									||
									(b.getTemplateEntity().getComponent() !=null
									&& er.getComponent() !=null
									&& b.getTemplateEntity().getComponent().equals(er.getComponent()))
								) {
									int oi = templateOccurrence.getOppositeEntityReference().indexOf(er);
									templateOccurrence.getOppositeEntityReference().set(oi, occurrenceEntity);
								}
							}
						}
					}
					es.getEvents().set(i, templateOccurrence);
				}
			}
		}
	}
	
	
	public void process(Package p, String outputPath) throws Docx4JException {
		wordTemplateExporter = new WordTemplateTestObjective();
		//TODO: make user accessible, safe, and configurable
	    Bundle bundle = Platform.getBundle("org.etsi.mts.tdl.tools.to.docx");
	    String template = bundle.getLocation().substring(15)+templateFileName;
	    String empty = bundle.getLocation().substring(15)+wordTemplateExporter.getInputFileName();
	    try {
	    	template = org.eclipse.core.runtime.FileLocator.toFileURL(bundle.getEntry(templateFileName)).getPath();
			empty = org.eclipse.core.runtime.FileLocator.toFileURL(bundle.getEntry("empty.docx")).getPath();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		wordTemplateExporter.setTemplateFileName(template);
		wordTemplateExporter.setInputFileName(empty);
		wordTemplateExporter.init();
		setTemplateName("TO_4_TABLE_TEMPLATE_EDITHELP");
		wordTemplateExporter.selectTemplateTable(getTemplateName());
		
		List<StructuredTestObjective> toList = EcoreUtil2.getAllContentsOfType(p, StructuredTestObjective.class);
		for (StructuredTestObjective to : toList) {
			makeTable(to);
		}
		
		wordTemplateExporter.writeDocumentToFile(outputPath);
	}
	public void makeTable(StructuredTestObjective to) throws Docx4JException {
		wordTemplateExporter.createNewTableFromSelectedTemplate();
		
        List<String> toNmLabelStrList = new ArrayList<>();
        List<String> toConfLabelStrList = new ArrayList<>();
        List<String> toDescrLabelStrList = new ArrayList<>(); 
        List<String> toURIofObjectiveLabelStrList = new ArrayList<>();
        List<String> toPICSselectionLabelStrList = new ArrayList<>();
        List<String> toInitialConditionLabelStrList = new ArrayList<>();
        List<String> toExpectedBehaviourLabelStrList = new ArrayList<>();
        List<String> toWhenLabelStrList = new ArrayList<>();
        List<String> toThenLabelStrList = new ArrayList<>();
        List<String> toFinalBehaviourLabelStrList = new ArrayList<>();
        List<String> whenDirect = new ArrayList<>();
        List<String> thenDirect = new ArrayList<>();
        
        toNmLabelStrList.add(to.getName());
        String description = to.getDescription().replaceAll("(\\s)+", " ");
        description = description.replaceAll("\"", "");
		toDescrLabelStrList.add(description);
        
        String e = " ";
		if (to.getObjectiveURI().isEmpty()) {
        	toURIofObjectiveLabelStrList.add(e);
        } else {
        	for (String u : to.getObjectiveURI()) {
        		u = u.replaceAll("\"", "");
        		toURIofObjectiveLabelStrList.add(u);
        	}
        }

        if (to.getPicsReference().isEmpty()) {
        	toPICSselectionLabelStrList.add(e);
        } else {
        	String l = "";
        	for (PICSReference p : to.getPicsReference()) {
        		if (p.getComment().size()>0) {
        			l+=" "+p.getComment().get(0).getBody() + " ";
        		}
        		
        		l+= p.getPics().getName();
        	}
        	toPICSselectionLabelStrList.add(l);
        }
        
        String indent = "    ";
        if (to.getInitialConditions() != null) {
        	toInitialConditionLabelStrList.add("with {");
			toInitialConditionLabelStrList.addAll(getSequenceSource(to.getInitialConditions().getConditions(), indent));
        	toInitialConditionLabelStrList.add("}");
        } else {
        	toInitialConditionLabelStrList.add(e);
        }
        
        if (to.getExpectedBehaviour() != null) {
        	toExpectedBehaviourLabelStrList.add("ensure that {");
        	String i = indent;
        	if (to.getExpectedBehaviour().getWhenClause().getEvents().size()>0) {
        		i=indent+indent;
        	}
			List<String> whenSource = getSequenceSource(to.getExpectedBehaviour().getWhenClause(), indent);
        	if (whenSource.size()>0) {
        		toWhenLabelStrList.add(indent+"when {");
        		toWhenLabelStrList.addAll(whenSource);
        		toWhenLabelStrList.add(indent+"}");
        	}
        	List<String> thenSource = getSequenceSource(to.getExpectedBehaviour().getThenClause(), indent);
        	if (whenSource.size()>0) {
        		toThenLabelStrList.add(indent+"then {");
        		toThenLabelStrList.addAll(thenSource);
        		toThenLabelStrList.add(indent+"}");
        	} else {
        		toExpectedBehaviourLabelStrList.addAll(thenSource);
        	}
        	toExpectedBehaviourLabelStrList.addAll(toWhenLabelStrList);
        	toExpectedBehaviourLabelStrList.addAll(toThenLabelStrList);
        	toExpectedBehaviourLabelStrList.add("}");
        } else {
        	toExpectedBehaviourLabelStrList.add(e);
        }

        if (to.getFinalConditions() != null) {
        	toFinalBehaviourLabelStrList.addAll(getSequenceSource(to.getFinalConditions().getConditions(),indent));
        } else {
        	toFinalBehaviourLabelStrList.add(e);
        }
        
		wordTemplateExporter.substitutePlaceholder(toNmLabelStrList, wordTemplateExporter.NAME);
		wordTemplateExporter.substitutePlaceholder(toDescrLabelStrList, wordTemplateExporter.DESCRIPTION);
		wordTemplateExporter.substitutePlaceholder(toURIofObjectiveLabelStrList, wordTemplateExporter.URI);
		wordTemplateExporter.substitutePlaceholder(toConfLabelStrList, wordTemplateExporter.CONFIGURATION);
		wordTemplateExporter.substitutePlaceholder(toPICSselectionLabelStrList, wordTemplateExporter.PICS);
		wordTemplateExporter.substitutePlaceholder(toInitialConditionLabelStrList, wordTemplateExporter.INITIAL);
		wordTemplateExporter.substitutePlaceholder(toExpectedBehaviourLabelStrList, wordTemplateExporter.EXPECTED);
		wordTemplateExporter.substitutePlaceholder(toFinalBehaviourLabelStrList, wordTemplateExporter.FINAL);
		wordTemplateExporter.substitutePlaceholder(toWhenLabelStrList, wordTemplateExporter.WHEN);
		wordTemplateExporter.substitutePlaceholder(whenDirect, wordTemplateExporter.WHEN_DIRECTION);
		wordTemplateExporter.substitutePlaceholder(toThenLabelStrList, wordTemplateExporter.THEN);
		wordTemplateExporter.substitutePlaceholder(thenDirect, wordTemplateExporter.THEN_DIRECTION);

		wordTemplateExporter.addWorkingTableToDocument();
		
		String EMPTY_STR = "";
		wordTemplateExporter.addParagraphToDocument(EMPTY_STR );
	}

	private List<String> getSequenceSource(EventSequence sequence, String indent) {
		List<String> source = new ArrayList<>();
		String s = "";
		boolean hasSourceAnnotations = false; 
		for (Annotation a : sequence.getAnnotation()) {
			if (a.getKey().getName().equals("SOURCE")) {
				hasSourceAnnotations = true;
				s = a.getValue();
				s = filterSource(s);
				//v = v.replaceAll("_","\\\\\\\\_");
				//v = v.replaceAll("  ","~~");
			}
		}
		if (!hasSourceAnnotations) {
			s = serialise(sequence);
			s = filterSource(s);
		}
		
		List<String> lines = Arrays.asList(s.split("\n"));
		boolean first = true;
		for (String line : lines) {
			if (line.trim().length() > 0) {
				if (first) {
					source.add(indent+indent+indent+line);
					first = false;
				} else {
					source.add(line);
				}
				System.out.println(source.get(source.size()-1));
			}
		}
		if (source.isEmpty()) {
			source.add(" ");
		}
		return source;
	}

	private String filterSource(String s) {
		String filtered = s;
		if (filterKeywords) {
			filtered = filtered.replaceAll("\"in\"", "in");
			filtered = filtered.replaceAll("entity\\s?", "");
			filtered = filtered.replaceAll("\\(literal\\) ","");
			filtered = filtered.replaceAll("\\(predefined\\) ","");
			filtered = filtered.replaceAll("\\(typed\\) ","");
			filtered = filtered.replaceAll("\t","    ");
		}
		return filtered;
	}

	public String getTemplateName() {
		return templateName;
	}

	public void setTemplateName(String templateName) {
		this.templateName = templateName;
	}
    
}
