package io.lumify.core;

import io.lumify.core.model.termMention.TermMentionModel;
import io.lumify.core.model.textHighlighting.OffsetItem;
import io.lumify.core.model.textHighlighting.TermMentionOffsetItem;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;

public class EntityHighlighter {
    public String getHighlightedText(String text, Iterable<TermMentionModel> termMentions) {
        List<OffsetItem> offsetItems = convertTermMentionsToOffsetItems(termMentions);
        return getHighlightedText(text, 0, offsetItems);
    }

    // TODO: change to use an InputStream?
    public static String getHighlightedText(String text, int textStartOffset, List<OffsetItem> offsetItems) throws JSONException {
        text = text.replaceAll("<", "&lt;").replaceAll("<", "&gt;");

        Collections.sort(offsetItems);
        StringBuilder result = new StringBuilder();
        PriorityQueue<Integer> endOffsets = new PriorityQueue<Integer>();
        int lastStart = textStartOffset;
        for (int i = 0; i < offsetItems.size(); i++) {
            OffsetItem offsetItem = offsetItems.get(i);

            boolean overlapsPreviousItem = false;
            if (offsetItem instanceof TermMentionOffsetItem) {
                for (int j = 0; j < i; j++) {
                    OffsetItem compareItem = offsetItems.get(j);
                    if (compareItem instanceof TermMentionOffsetItem && (compareItem.getEnd() >= offsetItem.getEnd()
                            || compareItem.getEnd() > offsetItem.getStart())) {
                        overlapsPreviousItem = true;
                        offsetItems.remove(i--);
                        break;
                    }
                }
            }
            if (overlapsPreviousItem) {
                continue;
            }
            if (offsetItem.getStart() < textStartOffset || offsetItem.getEnd() < textStartOffset) {
                continue;
            }
            if (!offsetItem.shouldHighlight()) {
                continue;
            }

            while (endOffsets.size() > 0 && endOffsets.peek() <= offsetItem.getStart()) {
                int end = endOffsets.poll();
                result.append(text.substring(lastStart - textStartOffset, end - textStartOffset));
                result.append("</span>");
                lastStart = end;
            }
            result.append(text.substring(lastStart - textStartOffset, (int) (offsetItem.getStart() - textStartOffset)));

            JSONObject infoJson = offsetItem.getInfoJson();

            result.append("<span");
            result.append(" class=\"");
            result.append(StringUtils.join(offsetItem.getCssClasses(), " "));
            result.append("\"");
            if (offsetItem.getTitle() != null) {
                result.append(" title=\"");
                result.append(StringEscapeUtils.escapeHtml(offsetItem.getTitle()));
                result.append("\"");
            }
            result.append(" data-info=\"");
            result.append(StringEscapeUtils.escapeHtml(infoJson.toString()));
            result.append("\"");
            result.append(">");
            endOffsets.add((int) offsetItem.getEnd());
            lastStart = (int) offsetItem.getStart();
        }

        while (endOffsets.size() > 0) {
            int end = endOffsets.poll();
            result.append(text.substring(lastStart - textStartOffset, end - textStartOffset));
            result.append("</span>");
            lastStart = end;
        }
        result.append(text.substring(lastStart - textStartOffset));

        return result.toString();
    }

    public List<OffsetItem> convertTermMentionsToOffsetItems(Iterable<TermMentionModel> termMentions) {
        ArrayList<OffsetItem> termMetadataOffsetItems = new ArrayList<OffsetItem>();
        for (TermMentionModel termMention : termMentions) {
            termMetadataOffsetItems.add(new TermMentionOffsetItem(termMention));
        }
        return termMetadataOffsetItems;
    }
}

@@ -924,78 +924,8 @@
 n %7B%0A
-        text = text.replaceAll(%22%3C%22, %22&lt;%22).replaceAll(%22%3C%22, %22&gt;%22);%0A%0A
     
@@ -2313,38 +2313,48 @@
   result.append(
-text.s
+escapeHtml(safeS
 ubstring(lastSta
@@ -2338,32 +2338,38 @@
 l(safeSubstring(
+text, 
 lastStart - text
@@ -2396,32 +2396,33 @@
 extStartOffset))
+)
 ;%0A              
@@ -2514,38 +2514,48 @@
   result.append(
-text.s
+escapeHtml(safeS
 ubstring(lastSta
@@ -2539,32 +2539,38 @@
 l(safeSubstring(
+text, 
 lastStart - text
@@ -2631,16 +2631,17 @@
 ffset)))
+)
 ;%0A%0A     
@@ -3516,38 +3516,48 @@
   result.append(
-text.s
+escapeHtml(safeS
 ubstring(lastSta
@@ -3541,32 +3541,38 @@
 l(safeSubstring(
+text, 
 lastStart - text
@@ -3607,16 +3607,17 @@
 Offset))
+)
 ;%0A      
@@ -3713,14 +3713,24 @@
 end(
-text.s
+escapeHtml(safeS
 ubst
@@ -3734,16 +3734,22 @@
 bstring(
+text, 
 lastStar
@@ -3769,16 +3769,17 @@
 Offset))
+)
 ;%0A%0A     
@@ -3810,24 +3810,613 @@
 g();%0A    %7D%0A%0A
+    private static String safeSubstring(String text, int beginIndex) %7B%0A        beginIndex = Math.min(beginIndex, text.length());%0A        return text.substring(beginIndex);%0A    %7D%0A%0A    private static String safeSubstring(String text, int beginIndex, int endIndex) %7B%0A        beginIndex = Math.min(beginIndex, text.length());%0A        endIndex = Math.min(endIndex, text.length());%0A        return text.substring(beginIndex, endIndex);%0A    %7D%0A%0A    private static String escapeHtml(String text) %7B%0A        return text.replaceAll(%22%3C%22, %22&lt;%22).replaceAll(%22%3C%22, %22&gt;%22).replaceAll(%22&%22, %22&amp;%22);%0A    %7D%0A%0A
     public L

fix ampersands in text highlighting [Fixes #70945162]