package de.ids_mannheim.korap.query.serialize;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.*;

/**
 * serializes a map of meta queries to JSON-LD. Currently this only works for simple mode queries (simple AND relation, except for date ranges)
 * Expert mode requires a full blown parser (since different combinations of OR and AND relations are possible)
 * also normalizes dates to year-month-day
 * <p/>
 * <p/>
 * User: hanl
 * Date: 11/14/13
 * Time: 2:03 PM
 */
public class MetaQuerySerializer {


    private String metaString = "{\n" +
            "    \"meta\": [\n" +
            "        {\n" +
            "            \"@type\": \"korap:meta-filter\",\n" +
            "            \"@value\": {\n" +
            "                \"@type\": \"korap:group\",\n" +
            "                \"relation\": \"and\",\n" +
            "                \"operands\": [\n" +
            "                    {\n" +
            "                        \"@type\": \"korap:term\",\n" +
            "                        \"field\": \"korap:field#author\",\n" +
            "                        \"@value\": \"Goethe\"\n" +
            "                    },\n" +
            "                    {\n" +
            "                        \"@type\": \"korap:group\",\n" +
            "                        \"field\": \"korap:field#pubDate\",\n" +
            "                        \"relation\": \"between\",\n" +
            "                        \"operands\": [\n" +
            "                            {\n" +
            "                                \"@type\": \"korap:date\",\n" +
            "                                \"@value\": \"2013-12-5\"\n" +
            "                            },\n" +
            "                            {\n" +
            "                                \"@type\": \"korap:date\",\n" +
            "                                \"@value\": \"2013-12-5\"\n" +
            "                            }\n" +
            "                        ]\n" +
            "                    }\n" +
            "                ]\n" +
            "            }\n" +
            "        }\n" +
            "    ]\n" +
            "}";
    private ObjectMapper mapper;
    private MetaTypes types;

    public MetaQuerySerializer() {
        this.mapper = new ObjectMapper();
        this.types = new MetaTypes();
    }

    // construct pubdate range query as "pubDate:'<date>' ~ pubDate:'<date>'"
    //todo: how to handle regex types?
    // only handles AND relation between query attributes and values!
    // value pair : pubdate=<date>, pubPlace=<place>, etc.
    public List serializeQueries(Map<String, String> queries, TYPE type) {
        boolean extend, single = true; //single = true;
        boolean multypes = queries.keySet().size() > 1;
        List<Map> metavalue;
        String def_key = null;
        if (queries.size() > 1)
            single = false;
        switch (type) {
            case EXTEND:
                extend = true;
                break;
            default:
                extend = false;
                break;
        }

        List value = new LinkedList();
        List<String> dates = new LinkedList<>();
        for (String key : queries.keySet()) {
            if (!multypes)
                def_key = key;
            if (queries.get(key).contains("~") | queries.get(key).contains(">") |
                    queries.get(key).contains("<")) {
                dates.add(queries.get(key));
                continue;
            }

//            if (queries.get(key).contains("~")) {
//                dr = queries.get(key).split("~");
//                Map fd = types.createTerm(dr[0].trim(), "korap:date");
//                Map td = types.createTerm(dr[1].trim(), "korap:date");
//                Map dg = types.createGroup("between", key, Arrays.asList(fd, td));
//                value.add(dg);
//                continue;
//            } else if (queries.get(key).contains(">")) {
//                dr = queries.get(key).split(">");
//                Map fd = types.createTerm(dr[0].trim(), "korap:date");
//                Map td = types.createTerm(dr[1].trim(), "korap:date");
//                Map dg = types.createGroup("between", key, Arrays.asList(fd, td));
//                value.add(dg);
//                continue;
//            } else if (queries.get(key).contains("<")) {
//                dr = queries.get(key).split("<");
//                Map fd = types.createTerm(dr[0].trim(), "korap:date");
//                Map td = types.createTerm(dr[1].trim(), "korap:date");
//                Map dg = types.createGroup("between", key, Arrays.asList(fd, td));
//                value.add(dg);
//                continue;
//            }

            Map term;
            if (multypes)
                term = types.createTerm(key, null, queries.get(key).trim(), null);
            else
                term = types.createTerm(def_key, null, queries.get(key).trim(), null);
            value.add(term);
        }

        String[] proc = processDates(dates);
        int idx = 3;
        if (proc[0] != null && proc[0].equals("r")) {
            Map term1 = types.createTerm(proc[1], "korap:date");
            Map term2 = types.createTerm(proc[2], "korap:date");
            Map group = types.createGroup("between", "pubDate", Arrays.asList(term1, term2));
            value.add(group);
        } else if (proc[1] != null) {
            Map term1 = types.createTerm(proc[1], "korap:date");
            Map group = types.createGroup("since", "pubDate", Arrays.asList(term1));
            value.add(group);
        } else if (proc[2] != null) {
            Map term1 = types.createTerm(proc[2], "korap:date");
            Map group = types.createGroup("until", "pubDate", Arrays.asList(term1));
            value.add(group);
        }


        for (int i = idx; i < proc.length; i++) {
            if (proc[i] != null) {
                Map term1 = types.createTerm(proc[i], "korap:date");
                Map group = types.createGroup("until", "pubDate", Arrays.asList(term1));
                value.add(group);
            }
        }


        // todo: missing: - takes only one resource, but resources can be chained!
        if (single) {
            if (extend)
                metavalue = Arrays.asList(types.createMetaExtend((Map) value.get(0)));
            else
                metavalue = Arrays.asList(types.createMetaFilter((Map) value.get(0)));
        } else {
            Map group;
            if (!multypes)
                group = types.createGroup("and", def_key, value);
            else
                group = types.createGroup("and", null, value);
            if (extend)
                metavalue = Arrays.asList(types.createMetaExtend(group));
            else
                metavalue = Arrays.asList(types.createMetaFilter(group));
        }
        return metavalue;
    }

    //todo: resource id must be added!
    public String stringify(Map<String, String> queries, TYPE type) throws IOException {
        Map f = new HashMap();
        f.put("meta", serializeQueries(queries, type));
        return mapper.writeValueAsString(f);
    }

    //fixme: only allows for one until and since entry!!
    private String[] processDates(List<String> dates) {
        if (dates.isEmpty())
            return new String[3];
        boolean range = false;
        String[] el = new String[dates.size() + 3];
        int idx = 3;
        for (String value : dates) {
            if (value.contains("<")) {
                String[] sp = value.split("<");
                el[1] = types.formatDate(Long.valueOf(sp[1]), MetaTypes.YMD);
            } else if (value.contains(">")) {
                String[] sp = value.split(">");
                el[2] = types.formatDate(Long.valueOf(sp[1]), MetaTypes.YMD);
            } else if (value.contains("~")) {
                range = true;
                String[] sp = value.split("~");
                el[1] = types.formatDate(Long.valueOf(sp[0]), MetaTypes.YMD);
                el[2] = types.formatDate(Long.valueOf(sp[1]), MetaTypes.YMD);
            } else {
                el[idx] = types.formatDate(Long.valueOf(value), MetaTypes.YMD);
                idx++;
            }

        }
        if (range)
            el[0] = "r";
        return el;
    }

    public JsonNode jsonify(Map<String, String> queries) {
        List s = serializeQueries(queries, TYPE.FILTER);
        return mapper.valueToTree(s);
    }


    public enum TYPE {
        EXTEND, FILTER
    }


}
