package cz.brmlab.yodaqa.analysis.passage.biotagger;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.uima.fit.util.JCasUtil;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.tcas.Annotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;

import approxlib.tree.LblTree;

import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token;
import de.tudarmstadt.ukp.dkpro.core.api.syntax.type.dependency.Dependency;

/** Interface between UIMA CAS and approxlib's LblTree.  Basically builds
 * up LblTree from a given CAS (or its sub-span given by an annotation).
 *
 * We build a string descriptor of the parse tree, which we then pass to
 * LblTree's fromString() method.
 *
 * Use the static methods; do not instantiate the class on your own. */

public class LblTreeCASFactory {
	final Logger logger = LoggerFactory.getLogger(LblTreeCASFactory.class);

	protected JCas jcas;

	/* Internal dependency tree representation. */
	protected Map<Token, Integer> tokenToWordIdx = new HashMap<>();
	protected Multimap<Token, Dependency> tokenChildDeps = HashMultimap.create();
	protected Set<Token> roots = new HashSet<>();

	/** Instantiate the LblTree factory for a single run. */
	public LblTreeCASFactory(JCas jcas) {
		this.jcas = jcas;
	}

	/** Build internal tree representation from a sequence of tokens. */
	public void buildTree(Collection<Token> tokens, Collection<Dependency> deps) {
		int i = 0;
		for (Token t : tokens) {
			tokenToWordIdx.put(t, i);
			i++;
		}

		for (Dependency d : deps) {
			tokenChildDeps.put(d.getGovernor(), d);
		}

		roots.addAll(tokenChildDeps.keySet());
		for (Dependency d : deps) {
			roots.remove(d.getDependent());
		}
	}

	public LblTree toLblTree() {
		if (roots.isEmpty())
			return null;
		StringBuilder sb = new StringBuilder();
		sb.append("-1:{"); // treeID
		sb.append("-1:{"); // "virtual" root (we can have multiple true roots)
		for (Token root : roots) {
			sb.append("root");
			try {
				appendTokenToSB(sb, root, "root");
			} catch (StackOverflowError e) {
				// sometimes we get lost in recursion of appendTokenToSB(); FIXME look into why
				e.printStackTrace();
				return null;
			}
		}
		sb.append("}}");
		logger.debug(sb.toString());
		return LblTree.fromString(sb.toString());
	}

	protected void appendTokenToSB(StringBuilder sb, Token t, String dep) {
		sb.append("{");
		sb.append(tokenToWordIdx.get(t));
		sb.append(":");
		sb.append(t.getCoveredText().replaceAll("/", "_"));
		sb.append("/");
		sb.append(t.getLemma().getValue().replaceAll("/", "_"));
		sb.append("/");
		sb.append(t.getPos().getPosValue());
		sb.append("/");
		sb.append(dep);
		for (Dependency d : tokenChildDeps.get(t)) {
			sb.append("{");
			sb.append(d.getDependencyType());
			appendTokenToSB(sb, d.getDependent(), d.getDependencyType());
			sb.append("}");
		}
		sb.append("}");
	}

	public static LblTree casToTree(JCas jcas) {
		LblTreeCASFactory f = new LblTreeCASFactory(jcas);
		f.buildTree(JCasUtil.select(jcas, Token.class), JCasUtil.select(jcas, Dependency.class));
		return f.toLblTree();
	}

	public static LblTree spanToTree(JCas jcas, Annotation span) {
		LblTreeCASFactory f = new LblTreeCASFactory(jcas);
		f.buildTree(JCasUtil.selectCovered(Token.class, span), JCasUtil.selectCovered(Dependency.class, span));
		return f.toLblTree();
	}

}

@@ -173,16 +173,60 @@
 l.Set;%0A%0A
+import org.apache.commons.lang.StringUtils;%0A
 import o
@@ -1363,16 +1363,44 @@
 Set%3C%3E();
+%0A%09protected String sentence;
 %0A%0A%09/** I
@@ -1677,16 +1677,58 @@
  i = 0;%0A
+%09%09StringBuilder sb = new StringBuilder();%0A
 %09%09for (T
@@ -1782,16 +1782,102 @@
 %0A%09%09%09
-i++;%0A%09%09%7D
+sb.append(t.getCoveredText());%0A%09%09%09sb.append(%22 %22);%0A%09%09%09i++;%0A%09%09%7D%0A%09%09this.sentence = sb.toString();
 %0A%0A%09%09
@@ -2118,16 +2118,85 @@
 Empty())
+ %7B%0A%09%09%09logger.debug(%22%7B%7D --- no dependency tree roots%22, this.sentence);
 %0A%09%09%09retu
@@ -2204,16 +2204,20 @@
 n null;%0A
+%09%09%7D%0A
 %09%09String
@@ -2573,16 +2573,96 @@
 nto why%0A
+%09%09%09%09logger.debug(%22%7B%7D --- stack overflow, sb %7B%7D%22, this.sentence, sb.toString());%0A
 %09%09%09%09e.pr
@@ -2738,16 +2738,44 @@
 r.debug(
+%22%7B%7D --- %7B%7D%22, this.sentence, 
 sb.toStr

Include sentence text in debug print, plus also when giving up on some passages