package ru.yandex.webmaster3.core.semantic.semantic_document_parser;

import org.json.*;
import ru.yandex.common.util.collections.Cu;
import ru.yandex.common.util.collections.MultiMap;
import ru.yandex.common.util.functional.Filter;
import ru.yandex.common.util.functional.Function;
import ru.yandex.common.util.json.Jsoner;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.MicrodataUtils;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microdata.data.Microdata;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microformats.FrontEnd;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microformats.MicroformatsUtils;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microformats.data.MicroformatData;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.RDFaUtils;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.data.RDFaEntity;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.serialize.util.APIVersion;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Ivan Nikolaev <ivannik@yandex-team.ru>
 */
public class SemanticValidatorResult {
    private final String id;
    private final List<Microdata> microdatas;
    private final List<Microdata> jsonMicrodata;
    private final List<MicroformatData> microformatDatas;
    private final List<RDFaEntity> rdfaEntities;
    private final List<RDFaEntity> jsonldEntities;
    private final MultiMap<Integer, JSONObject> exceptionMultiMap;
    private final boolean only_errors;

    public SemanticValidatorResult(
            String id,
            List<Microdata> microdatas,
            List<Microdata> jsonMicrodata,
            List<MicroformatData> microformatDatas,
            List<RDFaEntity> rdfaEntities,
            List<RDFaEntity> jsonldEntities,
            MultiMap<Integer, JSONObject> exceptionMultiMap,
            boolean only_errors) {
        this.id = id;
        this.microdatas = microdatas;
        this.jsonMicrodata = jsonMicrodata;
        this.microformatDatas = microformatDatas;
        this.rdfaEntities = rdfaEntities;
        this.jsonldEntities = jsonldEntities;
        this.exceptionMultiMap = exceptionMultiMap;
        this.only_errors = only_errors;
    }

    public String getId() {
        return id;
    }

    public List<Microdata> getMicrodatas() {
        return microdatas;
    }

    public List<Microdata> getJsonMicrodata() {
        return jsonMicrodata;
    }

    public List<MicroformatData> getMicroformatDatas() {
        return microformatDatas;
    }

    public List<RDFaEntity> getRdfaEntities() {
        return rdfaEntities;
    }

    public List<RDFaEntity> getJsonldEntities() {
        return jsonldEntities;
    }

    public MultiMap<Integer, JSONObject> getExceptionMultiMap() {
        return exceptionMultiMap;
    }

    public boolean isOnly_errors() {
        return only_errors;
    }

    public boolean isEmpty() {
        return exceptionMultiMap.isEmpty() &&
                microdatas.isEmpty() &&
                jsonMicrodata.isEmpty() &&
                microformatDatas.isEmpty() &&
                jsonldEntities.isEmpty() &&
                !Cu.filter(rdfaEntities, new Filter<RDFaEntity>() {
                    @Override
                    public boolean fits(RDFaEntity entity) {
                        return !entity.isEmpty();
                    }
                }).iterator().hasNext();
    }

    public Map<String, List<JSONObject>> convertToMap(APIVersion version) throws JSONException {
        Map<String, List<JSONObject>> result = new HashMap<>();
        List<JSONObject> jsonArray = new ArrayList<>();
        for (final Microdata md : microdatas) {
            jsonArray.add(MicrodataUtils.md2Json(md, exceptionMultiMap, only_errors, version));
        }
        result.put("microdata", jsonArray);

        jsonArray = new ArrayList<>();

        for (final RDFaEntity entity : rdfaEntities) {
            if (entity.isRoot) {
                JSONObject rdfa = RDFaUtils.toAPIJson(entity, exceptionMultiMap, only_errors, version);
                if (rdfa.keys().hasNext() || rdfa.length() == 1) {
                    jsonArray.add(rdfa);
                }
            }
        }
        result.put("rdfa", jsonArray);

        jsonArray = new ArrayList<>();

        for (final RDFaEntity entity : jsonldEntities) {
            jsonArray.add(RDFaUtils.toAPIJson(entity, exceptionMultiMap, only_errors, version));
        }


        if (exceptionMultiMap.containsKey(FrontEnd.HASH_OF_JSON_EXPANSION)) {
            JSONObject errorObj = new JSONObject();
            for (JSONObject e : exceptionMultiMap.get(FrontEnd.HASH_OF_JSON_EXPANSION)) {
                errorObj.append(APIVersion.getErrorKey(version), APIVersion.serializeToArrayForOldVersion(version, e));
            }
            jsonArray.add(errorObj);
        }

        result.put("json-ld", jsonArray);

        jsonArray = new ArrayList<>();
        for (MicroformatData microformatData : microformatDatas) {
            jsonArray.add(MicroformatsUtils.toJson(microformatData, exceptionMultiMap, only_errors, version));
        }
        result.put("microformat", jsonArray);
        return result;
    }

    public JSONObject serialize2json(APIVersion version) throws JSONException {
        if (exceptionMultiMap.keySet().isEmpty() && only_errors) {
            return new JSONObject().put("status", "204");
        }

        JSONObject rootObject = new JSONObject();

        if (!("".equals(id))) {
            rootObject.put("id", id);
        } else {
            rootObject.put("id", JSONObject.NULL);
        }

        JSONObject resultObject;

        if (version.equals(APIVersion.VERSION_0_1) || version.equals(APIVersion.VERSION_1_0)) {
            resultObject = rootObject;
        } else {
            resultObject = new JSONObject();
            rootObject.put("data", resultObject);
        }

        for (final Microdata md : microdatas) {
            resultObject.append("microdata", MicrodataUtils.md2Json(md, exceptionMultiMap, only_errors, version));
        }
        if (microdatas.isEmpty()) {
            resultObject.put("microdata", new JSONArray());
        }
        for (final RDFaEntity entity : rdfaEntities) {
            if (entity.isRoot) {
                JSONObject rdfa = RDFaUtils.toAPIJson(entity, exceptionMultiMap, only_errors, version);
                if (rdfa.keys().hasNext() || rdfa.length() == 1) {
                    resultObject.append("rdfa", rdfa);
                }
            }
        }
        if (!resultObject.has("rdfa")) {
            resultObject.put("rdfa", new JSONArray());
        }
        for (final RDFaEntity entity : jsonldEntities) {
            resultObject.append("json-ld", RDFaUtils.toAPIJson(entity, exceptionMultiMap, only_errors, version));
        }

        if (jsonldEntities.isEmpty()) {
            resultObject.put("json-ld", new JSONArray());
        }
        if (exceptionMultiMap.containsKey(FrontEnd.HASH_OF_JSON_EXPANSION)) {
            JSONObject errorObj = new JSONObject();
            for (JSONObject e : exceptionMultiMap.get(FrontEnd.HASH_OF_JSON_EXPANSION)) {
                errorObj.append(APIVersion.getErrorKey(version), APIVersion.serializeToArrayForOldVersion(version, e));
            }
            resultObject.append("json-ld", errorObj);
        }
        resultObject.put("microformat", Cu.map(new Function<MicroformatData, JSONObject>() {
            @Override
            public JSONObject apply(final MicroformatData o) {
                return MicroformatsUtils.toJson(o, exceptionMultiMap, only_errors, version);
            }
        }, microformatDatas));
        return rootObject;
    }
}
