package de.ids_mannheim.korap.query.serialize;

import de.ids_mannheim.korap.util.QueryException;
import org.antlr.runtime.tree.Tree;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author hanl
 * @date 10/12/2013
 */
public class QueryUtils {

    /**
     * Returns the category (or 'label') of the root of a ParseTree (ANTLR 4).
     *
     * @param node
     * @return
     */
    public static String getNodeCat(ParseTree node) {
        String nodeCat = node.toStringTree(PoliqarpPlusTree.poliqarpParser);
        Pattern p = Pattern.compile("\\((.*?)\\s"); // from opening parenthesis to 1st whitespace
        Matcher m = p.matcher(node.toStringTree(PoliqarpPlusTree.poliqarpParser));
        if (m.find()) {
            nodeCat = m.group(1);
        }
        return nodeCat;
    }

    /**
     * Returns the category (or 'label') of the root of a ParseTree (ANTLR 3).
     *
     * @param node
     * @return
     */
    public static String getNodeCat(Tree node) {
        String nodeCat = node.toStringTree();
        Pattern p = Pattern.compile("\\((.*?)\\s"); // from opening parenthesis to 1st whitespace
        Matcher m = p.matcher(node.toStringTree());
        if (m.find()) {
            nodeCat = m.group(1);
        }
        return nodeCat;
    }


    /**
     * Tests whether a certain node has a child by a certain name
     *
     * @param node     The parent node.
     * @param childCat The category of the potential child.
     * @return true iff one or more children belong to the specified category
     */
    public static boolean hasChild(Tree node, String childCat) {
        for (int i = 0; i < node.getChildCount(); i++) {
            if (getNodeCat(node.getChild(i)).equals(childCat)) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Tests whether a certain node has a child by a certain name
     *
     * @param node     The parent node.
     * @param childCat The category of the potential child.
     * @return true iff one or more children belong to the specified category
     */
    public static boolean hasChild(ParseTree node, String childCat) {
        for (int i = 0; i < node.getChildCount(); i++) {
            if (getNodeCat(node.getChild(i)).equals(childCat)) {
                return true;
            }
        }
        return false;
    }

    public static boolean hasDescendant(ParseTree node, String childCat) {
        for (int i = 0; i < node.getChildCount(); i++) {
            ParseTree child = node.getChild(i);
            if (getNodeCat(child).equals(childCat)) {
                return true;
            }
            if (hasDescendant(child, childCat)) {
                return true;
            }
        }
        return false;
    }

    public static List<Tree> getChildrenWithCat(Tree node, String nodeCat) {
        ArrayList<Tree> children = new ArrayList<Tree>();
        for (int i = 0; i < node.getChildCount(); i++) {
            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
                children.add(node.getChild(i));
            }
        }
        return children;
    }

    public static List<ParseTree> getChildrenWithCat(ParseTree node, String nodeCat) {
        ArrayList<ParseTree> children = new ArrayList<ParseTree>();
        for (int i = 0; i < node.getChildCount(); i++) {
            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
                children.add(node.getChild(i));
            }
        }
        return children;
    }

    public static List<ParseTree> getChildren(ParseTree node) {
        ArrayList<ParseTree> children = new ArrayList<ParseTree>();
        for (int i = 0; i < node.getChildCount(); i++) {
                children.add(node.getChild(i));
        }
        return children;
    }
    
    public static Tree getFirstChildWithCat(Tree node, String nodeCat) {
        for (int i = 0; i < node.getChildCount(); i++) {
            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
                return node.getChild(i);
            }
        }
        return null;
    }

    public static ParseTree getFirstChildWithCat(ParseTree node, String nodeCat) {
        for (int i = 0; i < node.getChildCount(); i++) {
            if (getNodeCat(node.getChild(i)).equals(nodeCat)) {
                return node.getChild(i);
            }
        }
        return null;
    }
    
    /**
     * Checks whether a node only serves as a container for another node (e.g. in (cq_segment ( cg_seg_occ ...)), the cq_segment node does not contain
     * any information and only contains the cq_seg_occ node.  
     * @param node The node to check
     * @return true iff the node is a container only.
     */
    public static boolean isContainerOnly(ParseTree node) {
    	String[] validNodeNamesArray = "cq_segment sq_segment element empty_segments".split(" ");
    	List<String> validNodeNames = Arrays.asList(validNodeNamesArray);
    	List<ParseTree> children = getChildren(node);
    	for (ParseTree child : children) {
    		if (validNodeNames.contains(getNodeCat(child))) {
    			return false;
    		}
    	}
    	return true;
    }

    public static void checkUnbalancedPars(String q) throws QueryException {
        int openingPars = StringUtils.countMatches(q, "(");
        int closingPars = StringUtils.countMatches(q, ")");
        int openingBrkts = StringUtils.countMatches(q, "[");
        int closingBrkts = StringUtils.countMatches(q, "]");
        int openingBrcs = StringUtils.countMatches(q, "{");
        int closingBrcs = StringUtils.countMatches(q, "}");
        if (openingPars != closingPars) throw new QueryException(
                "Your query string contains an unbalanced number of parantheses.");
        if (openingBrkts != closingBrkts) throw new QueryException(
                "Your query string contains an unbalanced number of brackets.");
        if (openingBrcs != closingBrcs) throw new QueryException(
                "Your query string contains an unbalanced number of braces.");
    }

    public static List<String> parseMorph(String stringTree) {

        ArrayList<String> morph = new ArrayList<String>();
        return morph;
    }


    public static String buildCypherQuery(String cypher, String ctypel, String ctyper,
                                          int cl, int cr, int page, int limit) {
        //todo: implies that there is only one type allowed!
        String sctypel = "", sctyper = "";
        switch (ctypel) {
            case "C":
                sctypel = "chars";
                break;
            case "T":
                sctypel = "tokens";
                break;
        }
        switch (ctyper) {
            case "C":
                sctyper = "chars";
                break;
            case "T":
                sctyper = "tokens";
                break;
        }

        StringBuffer buffer = new StringBuffer();
        buffer.append("<query><cypher><![CDATA[");
        buffer.append(cypher);
        buffer.append("]]></cypher>");
        buffer.append("<wordAliasPrefix>wtok_</wordAliasPrefix>");
        buffer.append("<contextColumn>sent</contextColumn>");
        buffer.append("<contextIdColumn>sid</contextIdColumn>");
        buffer.append("<textColumn>txt</textColumn>");
        buffer.append("<startIndex>");
        buffer.append(page);
        buffer.append("</startIndex>");
        buffer.append("<itemsPerPage>");
        buffer.append(limit);
        buffer.append("</itemsPerPage>");
        buffer.append("<context>");
        buffer.append("<left>");
        buffer.append("<" + sctypel + ">");
        buffer.append(cl);
        buffer.append("</" + sctypel + ">");
        buffer.append("</left>");
        buffer.append("<right>");
        buffer.append("<" + sctyper + ">");
        buffer.append(cr);
        buffer.append("</" + sctyper + ">");
        buffer.append("</right>");
        buffer.append("</context>");
        buffer.append("</query>");
        return buffer.toString();
    }

    public static String buildDotQuery(long sid, String graphdb_id) {
        StringBuffer b = new StringBuffer();
        b.append("<query>");
        b.append("<sentenceId>");
        b.append(sid);
        b.append("</sentenceId>");
        b.append("<gdbId>");
        b.append(graphdb_id);
        b.append("</gdbId>");
        b.append("<hls>");
        b.append("<hl>");
        b.append(40857);
        b.append("</hl>");
        b.append("<hl>");
        b.append(40856);
        b.append("</hl>");
        b.append("</hls>");
        b.append("</query>");

        return b.toString();
    }

    public String buildaggreQuery(String query) {
        StringBuffer b = new StringBuffer();
        b.append("<query><cypher><![CDATA[");
        b.append(query);
        b.append("]]></cypher>");
        b.append("<columns>");
        b.append("<column agg='true' sum='false'>");
        b.append("<cypherAlias>");
        b.append("aggBy");
        b.append("</cypherAlias>");
        b.append("<displayName>");
        b.append("Aggregate");
        b.append("</displayName>");
        b.append("</column>");

        b.append("<column agg='fals' sum='true'>");
        b.append("<cypherAlias>");
        b.append("cnt");
        b.append("</cypherAlias>");
        b.append("<displayName>");
        b.append("Count");
        b.append("</displayName>");
        b.append("</column>");
        b.append("</columns>");

        b.append("</query>");
        return b.toString();
    }

    @Deprecated
    public static Map addParameters(Map request, int page, int num, String cli, String cri,
                                    int cls, int crs, boolean cutoff) {
        Map ctx = new LinkedHashMap();
        List left = new ArrayList();
        left.add(cli);
        left.add(cls);
        List right = new ArrayList();
        right.add(cri);
        right.add(crs);
        ctx.put("left", left);
        ctx.put("right", right);

        request.put("startPage", page);
        request.put("count", num);
        request.put("context", ctx);
        request.put("cutOff", cutoff);

        return request;
    }

    public static void prepareContext(LinkedHashMap<String, Object> requestMap) {
        LinkedHashMap<String, Object> context = new LinkedHashMap<String, Object>();

        LinkedHashMap<String, Object> classMap = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> operands = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> operation = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> frame = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> classRef = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> spanRef = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> classRefOp = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> min = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> max = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> exclude = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> distances = new LinkedHashMap<String, Object>();
        LinkedHashMap<String, Object> inOrder = new LinkedHashMap<String, Object>();

        operation.put("@id", "group:operation/");
        operation.put("@type", "@id");

        classMap.put("@id", "group:class");
        classMap.put("@type", "xsd:integer");

        operands.put("@id", "group:operands");
        operands.put("@container", "@list");

        frame.put("@id", "group:frame/");
        frame.put("@type", "@id");

        classRef.put("@id", "group:classRef");
        classRef.put("@type", "xsd:integer");

        spanRef.put("@id", "group:spanRef");
        spanRef.put("@type", "xsd:integer");

        classRefOp.put("@id", "group:classRefOp");
        classRefOp.put("@type", "@id");

        min.put("@id", "boundary:min");
        min.put("@type", "xsd:integer");

        max.put("@id", "boundary:max");
        max.put("@type", "xsd:integer");

        exclude.put("@id", "group:exclude");
        exclude.put("@type", "xsd:boolean");

        distances.put("@id", "group:distances");
        distances.put("@container", "@list");

        inOrder.put("@id", "group:inOrder");
        inOrder.put("@type", "xsd:boolean");

        context.put("korap", "http://korap.ids-mannheim.de/ns/KorAP/json-ld/v0.1/");
        context.put("boundary", "korap:boundary/");
        context.put("group", "korap:group/");
        context.put("operation", operation);
        context.put("class", classMap);
        context.put("operands", operands);
        context.put("frame", frame);
        context.put("classRef", classRef);
        context.put("spanRef", spanRef);
        context.put("classRefOp", classRefOp);
        context.put("min", min);
        context.put("max", max);
        context.put("exclude", exclude);
        context.put("distances", distances);
        context.put("inOrder", inOrder);

        requestMap.put("@context", context);
    }



}
