/*
 * generated by Xtext 2.25.0
 */
package org.etsi.mts.tdl.formatting2;

import java.util.List;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.formatting2.AbstractJavaFormatter;
import org.eclipse.xtext.formatting2.FormatterPreferenceKeys;
import org.eclipse.xtext.formatting2.IFormattableDocument;
import org.eclipse.xtext.formatting2.IHiddenRegionFormatter;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.preferences.MapBasedPreferenceValues;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;
import org.etsi.mts.tdl.Element;
import org.etsi.mts.tdl.ElementImport;
import org.etsi.mts.tdl.PackageableElement;
import org.etsi.mts.tdl.ParameterBinding;
import org.etsi.mts.tdl.ParameterMapping;
import org.etsi.mts.tdl.StructuredDataType;
import org.etsi.mts.tdl.EnumDataType;
import org.etsi.mts.tdl.StructuredDataInstance;
import org.etsi.mts.tdl.TestObjective;
import org.etsi.mts.tdl.TimeLabel;
import org.etsi.mts.tdl.tdlPackage;
import org.etsi.mts.tdl.TimeConstraint;
import org.etsi.mts.tdl.ComponentType;
import org.etsi.mts.tdl.Connection;
import org.etsi.mts.tdl.Annotation;
import org.etsi.mts.tdl.Extension;
import org.etsi.mts.tdl.DataElementMapping;
import org.etsi.mts.tdl.DataElementUse;
import org.etsi.mts.tdl.DataUse;
import org.etsi.mts.tdl.TestConfiguration;
import org.etsi.mts.tdl.TestDescription;
import org.etsi.mts.tdl.Behaviour;
import org.etsi.mts.tdl.Block;
import org.etsi.mts.tdl.ExceptionalBehaviour;
import org.etsi.mts.tdl.PeriodicBehaviour;
import org.etsi.mts.tdl.SimpleDataInstance;
import org.etsi.mts.tdl.Comment;
import org.etsi.mts.tdl.FormalParameter;
import org.etsi.mts.tdl.LocalExpression;
import org.etsi.mts.tdl.Member;
import org.etsi.mts.tdl.MemberAssignment;
import org.etsi.mts.tdl.NamedElement;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.structuredobjectives.EventOccurrence;
import org.etsi.mts.tdl.structuredobjectives.EventReference;
import org.etsi.mts.tdl.structuredobjectives.ExpectedBehaviour;
import org.etsi.mts.tdl.structuredobjectives.FinalConditions;
import org.etsi.mts.tdl.structuredobjectives.InitialConditions;
import org.etsi.mts.tdl.structuredobjectives.LiteralValue;
import org.etsi.mts.tdl.structuredobjectives.PICSReference;
import org.etsi.mts.tdl.structuredobjectives.StructuredTestObjective;
import org.etsi.mts.tdl.structuredobjectives.Value;
import org.etsi.mts.tdl.structuredobjectives.Content;
import org.etsi.mts.tdl.structuredobjectives.EntityReference;
import org.etsi.mts.tdl.structuredobjectives.StructuredObjectivesPackage;
import org.etsi.mts.tdl.services.TDLtxGrammarAccess;

import com.google.inject.Inject;

public class TDLtxFormatter extends AbstractJavaFormatter {
	@Inject
	private TDLtxGrammarAccess _grammarAccess;
	
	//NOTE: Backported from indentation-based syntax.. added some refinements
	//TODO: Clean-up, merge, validate, and supersede downstream

	protected void format(Block e, IFormattableDocument doc) {
		for (EObject m : e.eContents()) {
			if (!(m instanceof Annotation) && !(m instanceof LocalExpression)) {
				//TODO: handle nested behaviour
				doc.prepend(m, p->p.newLine());
				doc.surround(m, p->p.indent());
				doc.append(m, p->p.newLine());
			} else if (m instanceof Annotation) {
				doc.append(m, p->p.newLine());
			} else {
			}
			doc.format(m);
		}
	}

	protected void format(LiteralValue e, IFormattableDocument doc) {
//		doc.surround(e, indent());
//		doc.surround(e, newLine());
		List<ISemanticRegion> keywords = this.textRegionExtensions.regionFor(e).keywords(",");
		for (ISemanticRegion f : keywords) {
			doc.surround(f, indent());
		}

		for (EObject m : e.eContents()) {
			if ((m instanceof Content)) {
				doc.prepend(m, newLine());
			} else {
			}
			doc.format(m);
		}
	}

	protected void format(Content e, IFormattableDocument doc) {
		doc.surround(e, indent());
//		doc.surround(e, newLine());
		List<ISemanticRegion> keywords = this.textRegionExtensions.regionFor(e).keywords(",");
		for (ISemanticRegion f : keywords) {
			doc.surround(f, indent());
		}
		for (EObject m : e.eContents()) {
			if ((m instanceof Content)) {
				//TODO: works but possibly fragile
				doc.prepend(m, newLine());
				if (!((Content)m).getContent().isEmpty() ||
						(((Content)m).getValue()!=null &&
						!((LiteralValue) ((Content)m).getValue()).getContent().isEmpty())) {
					doc.append(m, newLine());
				}
			} else {
			}
			doc.format(m);
		}
	}

	
	protected void format(Element e, IFormattableDocument doc) {
		List<ISemanticRegion> open = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.getBEGINRule());
		List<ISemanticRegion> close = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.getENDRule());
		if (!open.isEmpty()) {
			doc.append(open.get(0), newLine());
			doc.interior(open.get(0), close.get(0), indent());
		}
//		doc.interior(e, indent);
		if (e instanceof Behaviour) {
//			List<ISemanticRegion> assignments = this.textRegionExtensions.regionFor(e).assignments(this._grammarAccess.findAssignments(this._grammarAccess.getTimeConstraintFragmentRule()).toArray(new Assignment[] {}));
//			for (var a : assignments) {
//				doc.prepend(a.getPreviousSemanticRegion(), newLine());
//				doc.surround(a.getPreviousSemanticRegion(), indent);
//				doc.append(a, newLine());
//			}
			List<ISemanticRegion> features = this.textRegionExtensions.regionFor(e).features(
					tdlPackage.Literals.BEHAVIOUR__TEST_OBJECTIVE
//					tdlPackage.Literals.ATOMIC_BEHAVIOUR__TIME_CONSTRAINT, 
//					tdlPackage.Literals.ATOMIC_BEHAVIOUR__TIME_LABEL 
					);
			for (ISemanticRegion f : features) {
				//TODO: this depends on keywords..
				doc.prepend(f.getPreviousSemanticRegion(), newLine());
				////doc.surround(f.getPreviousSemanticRegion(), indent);
				doc.append(f, newLine());
			}
		}
		if (e instanceof Connection) {
			doc.prepend(e, newLine());
		}

		if (e instanceof EventOccurrence) {
			doc.prepend(e, newLine());
			doc.surround(e, indent());
			doc.append(e, newLine());
			//TODO: expand custom formatting - when then, event occurrence arguments, etc.
		}

		if (e instanceof ExpectedBehaviour) {

		}
		
		for (EObject m : e.eContents()) {
			if (m instanceof TimeLabel ||
					m instanceof TimeConstraint //||
//					m instanceof Comment
					) { 
				doc.append(m, newLine());
			} else if (
					m instanceof Annotation ||
					m instanceof Comment
					
			) {
				if (!(e instanceof EntityReference || e instanceof EventReference )) {
					doc.prepend(m, newLine());
					doc.append(m, newLine());
				}  else {
					//doc.prepend(m, newLine());
				}
			} else if (m instanceof EntityReference) {
				doc.prepend(m, newLine());
			} else if (m instanceof EventOccurrence) {
				doc.prepend(m, newLine());
				doc.append(m, newLine());
			} else if (
					m instanceof Annotation ||
					m instanceof Comment || 
					m instanceof TestObjective || //?
					m instanceof ExceptionalBehaviour ||
					m instanceof PeriodicBehaviour
					) {
				doc.prepend(m, newLine());
				doc.surround(m, indent());
				doc.append(m, newLine());
			}
			doc.format(m);
		}
	}

	public Procedure1<? super IHiddenRegionFormatter> indent() {
		Procedure1<? super IHiddenRegionFormatter> indent = p->p.indent();
		return indent;
	}

	public Procedure1<? super IHiddenRegionFormatter> newLine() {
		Procedure1<? super IHiddenRegionFormatter> newLine = p->p.newLine();
		return newLine;
	}

	
	protected void format(NamedElement e, IFormattableDocument doc) {
		doc.prepend(e, newLine());
//		doc.interior(e, indent());
//		doc.format(e);
//		doc.interior(e, indent());

		if (e instanceof StructuredDataType) {
//			var open = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.getBEGINRule());
//			var close = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.getENDRule());
//
//			doc.append(open.get(0), newLine());
//			doc.interior(open.get(0), close.get(0), indent());

			if (!((StructuredDataType) e).getMember().isEmpty()) {
//				doc.interior(
//						doc.append(this.textRegionExtensions.regionFor(e).keyword("("), r->r.newLine()()), 
//						doc.prepend(this.textRegionExtensions.regionFor(e).keyword(")"), r->r.newLine()()), 
//						r->r.indent()());
			}
			int size = ((StructuredDataType)e).getMember().size();
			int i = 0;
			for (Member m : ((StructuredDataType)e).getMember()) {
				i++;
				doc.prepend(m, newLine());
				doc.surround(m, indent());
				if (i==size) {
					doc.append(m, newLine());
				}
				doc.format(m);
			}
		}

		if (e instanceof StructuredDataInstance) {
			int size = ((StructuredDataInstance)e).getMemberAssignment().size();
			int i = 0;
			for (MemberAssignment m : ((StructuredDataInstance)e).getMemberAssignment()) {
				i++;
				doc.prepend(m, newLine());
				doc.surround(m, indent());
//				doc.append(m, newLine());
				if (i==size) {
					doc.append(m, newLine());
				}
				doc.format(m);
			}
		}

		if (e instanceof EnumDataType) {
			int size = ((EnumDataType)e).getValue().size();
			int i = 0;
			for (SimpleDataInstance m : ((EnumDataType)e).getValue()) {
				i++;
				doc.prepend(m, newLine());
				doc.surround(m, indent());
//				doc.append(m, newLine()());
				if (i==size) {
					doc.append(m, newLine());
				}
				doc.format(m);
			}
		}

		
		if (e instanceof ComponentType ||
				e instanceof TestConfiguration ||
				e instanceof TestDescription) {
			int i = 0;
			for (EObject m : e.eContents()) {
				i++;
				if (!(m instanceof Annotation) && 
						!(m instanceof Extension) &&
						!(m instanceof FormalParameter)) {
					doc.prepend(m, newLine());
					doc.surround(m, indent());
					//doc.append(m, newLine());
					doc.format(m);
				}
				if (i==e.eContents().size()) {
					doc.append(m, newLine());
				}
			}
		}

		if (e instanceof DataElementMapping) {
			for (ParameterMapping m : ((DataElementMapping)e).getParameterMapping()) {
				doc.prepend(m, newLine());
				doc.surround(m, indent());
//				doc.append(m, newLine());
				doc.format(m);
			}
			
			List<ISemanticRegion> close = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.getENDRule());
			if (!close.isEmpty()) {
				doc.prepend(close.get(0), newLine());
			}

		}
		
		//Test Objective Only
		if (e instanceof TestObjective) {

//			ISemanticRegion dKeyword = this.textRegionExtensions.regionFor(e).keyword("Description:");
//			doc.prepend(dKeyword, newLine());
//			doc.surround(dKeyword, indent());
//			ISemanticRegion rKeyword = this.textRegionExtensions.regionFor(e).keyword("References:");
//			doc.prepend(rKeyword, newLine());
//			doc.surround(rKeyword, indent());
			
			List<ISemanticRegion> features = this.textRegionExtensions.regionFor(e).features(
					tdlPackage.Literals.TEST_OBJECTIVE__DESCRIPTION,
					tdlPackage.Literals.TEST_OBJECTIVE__OBJECTIVE_URI
//					tdlPackage.Literals.ATOMIC_BEHAVIOUR__TIME_CONSTRAINT, 
//					tdlPackage.Literals.ATOMIC_BEHAVIOUR__TIME_LABEL 
					);
			for (ISemanticRegion f : features) {
				//TODO: this depends on keywords..
				doc.prepend(f.getPreviousSemanticRegion(), newLine());
				doc.surround(f.getPreviousSemanticRegion(), indent());
				doc.append(f, newLine());
			}

			if (e instanceof StructuredTestObjective) {
				//TODO: this depends on keywords..
				List<ISemanticRegion> keywords = this.textRegionExtensions.regionFor(e).keywords("Configuration:", "PICS:");
				for (ISemanticRegion f : keywords) {
					doc.prepend(f, newLine());
					doc.surround(f, indent());
				}
				
				InitialConditions ic = ((StructuredTestObjective) e).getInitialConditions();
				doc.prepend(ic, newLine());
				doc.surround(ic, indent());
				doc.append(ic, newLine());
				doc.format(ic);
				ExpectedBehaviour eb = ((StructuredTestObjective) e).getExpectedBehaviour();
				doc.prepend(eb, newLine());
				doc.surround(eb, indent());
				doc.append(eb, newLine());
				doc.format(eb);
				FinalConditions fc = ((StructuredTestObjective) e).getFinalConditions();
				doc.prepend(fc, newLine());
				doc.surround(fc, indent());
				doc.append(fc, newLine());
				doc.format(fc);
			}

		}
		//All
		//DONE: exclude initial conditions -> not triggered
		ISemanticRegion withKeyword = this.textRegionExtensions.regionFor(e).keyword("with");
		if (withKeyword!=null) {
			doc.surround(withKeyword, newLine());
			doc.append(withKeyword, indent());
//			doc.interior(
//					withKeyword.getNextHiddenRegion().getNextSemanticRegion(), 
//					this.textRegionExtensions.nextHiddenRegion(e).getNextSemanticRegion(), 
//					indent()
//					);
		}
		//doc.prepend(withKeyword, indent());
		//withKeyword.getNextSemanticRegion();
		//		var rbs = this.textRegionExtensions.regionFor(e).ruleCallTo(this._grammarAccess.());
			
		
		ISemanticRegion noteKeyword = this.textRegionExtensions.regionFor(e).keyword("Note");
		if (noteKeyword!=null) {
			doc.prepend(noteKeyword, newLine());
		}
		ISemanticRegion atKeyword = this.textRegionExtensions.regionFor(e).keyword("@");
		if (atKeyword!=null) {
			doc.prepend(atKeyword, newLine());
		}
		for (Comment c : e.getComment()) {
			if (e.getComment().size() > 1) {
				doc.prepend(c, newLine());
				doc.surround(c, indent());
			} else {
				if (!(e instanceof Member)) {
					doc.surround(c, indent());
				}
				if (!(e instanceof EntityReference)) {
					doc.append(c, newLine());
				}
			}
			doc.format(c);
		}
		
		for (Annotation a : e.getAnnotation()) {
			if (e.getAnnotation().size() > 1) {
				doc.prepend(a, newLine());
				doc.surround(a, indent());
			} else {
				doc.append(a, newLine());
			}
			doc.format(a); 
		}
		

		List<ISemanticRegion> closeItems = this.textRegionExtensions.allRegionsFor(e).keywords("]");
		for (ISemanticRegion r : closeItems) {
			doc.surround(r, newLine());
		}
		
//		List<ISemanticRegion> comments = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.get);
//		if (!comments.isEmpty()) {
//			doc.append(comments.get(0), newLine());
//		}
//		List<ISemanticRegion> annotations = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.getAnnotationFragmentRule());
//		if (!annotations.isEmpty()) {
//			doc.surround(annotations.get(0), newLine());
//		}
		
	}

	protected void format(org.etsi.mts.tdl.TestDescription e, IFormattableDocument doc) {
		for (Annotation a : e.getAnnotation()) {
			doc.surround(a, p->p.newLine());
//			doc.append(a, p->p.noIndentation());
			doc.format(a);
//			doc.prepend(a, p->p.newLine());
		}
		for (TestObjective a : e.getTestObjective()) {
			doc.append(a, p->p.newLine());
//			doc.append(a, p->p.noIndentation());
			doc.format(a);
		}
		for (Comment c : e.getComment()) {
			doc.append(c, p->p.newLine());
//			doc.append(c, p->p.noIndentation());
			doc.format(c);
		}

		for (EObject m : e.eContents()) {
			doc.format(m);
		}
	}

	protected void format(org.etsi.mts.tdl.DataUse e, IFormattableDocument doc) {
		for (ParameterBinding m : e.getArgument()) {
			doc.prepend(m, newLine());
			doc.surround(m, indent());
//			doc.append(m, p->p.newLine());
			doc.format(m);
		}
		if (e instanceof DataElementUse) {
			for (DataUse m : ((DataElementUse) e).getItem()) {
				doc.prepend(m, p->p.newLine());
				doc.surround(m, p->p.indent());
//			doc.append(m, p->p.newLine());
				doc.format(m);
			}
		}
		
		List<ISemanticRegion> closeParen = this.textRegionExtensions.regionFor(e).ruleCallsTo(this._grammarAccess.getRParenRule());
		if (!closeParen.isEmpty()) {
			doc.prepend(closeParen.get(0), p->p.newLine());
		}
		List<ISemanticRegion> closeItems = this.textRegionExtensions.regionFor(e).keywords("]");
		if (!closeItems.isEmpty()) {
			doc.prepend(closeItems.get(0), p->p.newLine());
		}


	}

	
	protected void format(org.etsi.mts.tdl.Package _package, IFormattableDocument doc) {
		// TODO: format HiddenRegions around keywords, attributes, cross references, etc.
		if (getPreferences() instanceof MapBasedPreferenceValues) {
			MapBasedPreferenceValues preferences = (MapBasedPreferenceValues) getPreferences();
			preferences.put(FormatterPreferenceKeys.indentation, "    ");
		}
		//TODO: repeated formatting doesn't work very well..
		
		Package e = _package;
		for (Annotation a : e.getAnnotation()) {
			doc.append(a, p->p.newLine());
			doc.append(a, p->p.noIndentation());
			doc.format(a);
		}
		for (Comment c : e.getComment()) {
			doc.append(c, p->p.newLine());
			doc.append(c, p->p.noIndentation());
			doc.format(c);
		}
		doc.interior(_package, p->p.indent());

		List<ISemanticRegion> dots = this.textRegionExtensions.allRegionsFor(e).keywords(".", "@", ",", "::");
		for (ISemanticRegion dot : dots) {
			doc.surround(dot, p -> p.noSpace());
		}
		for (ElementImport elementImport : _package.getImport()) {
			doc.surround(elementImport, p -> p.setNewLines(1));
			doc.format(elementImport);
		}
		for (PackageableElement packageableElement : _package.getPackagedElement()) {
			doc.surround(packageableElement, p -> p.setNewLines(1));
			doc.format(packageableElement);
		}


		for (org.etsi.mts.tdl.Package __package : _package.getNestedPackage()) {
			doc.format(__package);
		}
	}
	
}
