package de.ids_mannheim.korap.response.match;

import org.apache.lucene.util.FixedBitSet;
import de.ids_mannheim.korap.response.Match;
import de.ids_mannheim.korap.response.match.Relation;
import static de.ids_mannheim.korap.util.KrillString.*;
import java.util.*;
import java.io.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/*
  Class for elements with highlighting information
*/
public class HighlightCombinatorElement {

	// Number -1:     Match
	// Number -99998: Context
	private final static int CONTEXT = -99998;
	
    // Type 0: Textual data
    // Type 1: Opening
    // Type 2: Closing
	// Type 3: Empty
    public byte type;

    public int number = 0;

    public String characters;
    public boolean terminal = true;

	// Logger
	private final static Logger log = LoggerFactory.getLogger(Match.class);

	// This advices the java compiler to ignore all loggings
    public static final boolean DEBUG = false;

    // Constructor for highlighting elements
    public HighlightCombinatorElement (byte type, int number) {
        this.type = type;
        this.number = number;
    };


    // Constructor for highlighting elements,
    // that may not be terminal, i.e. they were closed and will
    // be reopened for overlapping issues.
    public HighlightCombinatorElement (byte type, int number,
                                       boolean terminal) {
        this.type = type;
        this.number = number;
        this.terminal = terminal;
    };


    // Constructor for textual data
    public HighlightCombinatorElement (String characters) {
        this.type = (byte) 0;
        this.characters = characters;
    };


    // Return html fragment for this combinator element
	public String toHTML (Match match, FixedBitSet level, byte[] levelCache, HashSet joins) {

        // Opening
        if (this.type == 1) {
            StringBuilder sb = new StringBuilder();

            // This is the surrounding match mark
            if (this.number == -1) {
                sb.append("<mark>");
            }

			// This is context
			else if (this.number == CONTEXT) {
				// DO nothing
			}

			// This is a relation target
            else if (this.number < -1) {

				// Create id
				String id = escapeHTML(
					match.getPosID(match.getClassID(this.number))
					);

				// ID already in use - create join
				if (joins.contains(id)) {
					sb.append("<span xlink:show=\"other\" data-action=\"join\" xlink:href=\"#")
						.append(id)
						.append("\">");
				}

				// Not yet in use - create
				else {
					sb.append("<span xml:id=\"")
						.append(id)
						.append("\">");
					joins.add(id);
				};
            }

			// This is an annotation
            else if (this.number >= 256) {
                sb.append("<span ");
                if (this.number < 2048) {
                    sb.append("title=\"")
                            .append(escapeHTML(
                                    match.getAnnotationID(this.number)))
                            .append('"');
                }

				// This is a relation source
                else {
                    Relation rel = match.getRelationID(this.number);

					if (DEBUG) {
						log.trace("Annotation is a relation with id {}", this.number);
						log.trace("Resulting in relation {}: {}-{}", rel.annotation, rel.refStart, rel.refEnd);
					};

                    sb.append("xlink:title=\"")
						.append(escapeHTML(rel.annotation))
						.append("\" xlink:show=\"none\" xlink:href=\"#")
						.append(escapeHTML(match.getPosID(rel.refStart, rel.refEnd)))
						.append('"');
                };
                sb.append('>');
            }

            // This is a highlight
			// < 256
            else {
                // Get the first free level slot
                byte pos;
                if (levelCache[this.number] != '\0') {
                    pos = levelCache[this.number];
                }
                else {
                    pos = (byte) level.nextSetBit(0);
                    level.clear(pos);
                    levelCache[this.number] = pos;
                };
                sb.append("<mark class=\"class-").append(this.number)
                        .append(" level-").append(pos).append("\">");
            };
            return sb.toString();
        }

        // This is a Closing tag
        else if (this.type == 2) {
			if (this.number == CONTEXT)
				return "";
			
            if (this.number < -1 || this.number >= 256)
                return "</span>";

            if (this.number == -1)
                return "</mark>";

            if (this.terminal)
                level.set((int) levelCache[this.number]);
            return "</mark>";
        }

		// Empty element
		else if (this.type == 3) {
			return "<span class=\"pb\" data-after=\"" + number + "\"></span>";
		};

        // HTML encode primary data
        return escapeHTML(this.characters);
    };


    // Return bracket fragment for this combinator element
    public String toBrackets (Match match) {
        if (this.type == 1) {
            StringBuilder sb = new StringBuilder();

            // Match
            if (this.number == -1) {
                sb.append("[");
            }

			// This is context
			else if (this.number == CONTEXT) {
				// DO nothing
			}

            // Identifier
            else if (this.number < -1) {
                sb.append("{#");
                sb.append(match.getClassID(this.number));
                sb.append(':');
            }

            // Highlight, Relation, Span
            else {
                sb.append("{");
                if (this.number >= 256) {
                    if (this.number < 2048)
                        sb.append(match.getAnnotationID(this.number));
                    else {
                        Relation rel = match.getRelationID(this.number);
                        sb.append(rel.annotation);
                        sb.append('>').append(rel.refStart);

						if (rel.refEnd != -1)
							sb.append('-').append(rel.refEnd);
                    };
                    sb.append(':');
                }
                else if (this.number != 0)
                    sb.append(this.number).append(':');
            };

            return sb.toString();
        }
        else if (this.type == 2) {

			// This is context
			if (this.number == CONTEXT)
				return "";
			
            if (this.number == -1)
                return "]";
            return "}";
        };
        return escapeBrackets(this.characters);
    };
};
