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

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import ru.yandex.common.util.collections.Cf;
import ru.yandex.common.util.collections.Cu;
import ru.yandex.common.util.collections.MultiMap;
import ru.yandex.common.util.collections.Pair;
import ru.yandex.common.util.functional.Filter;
import ru.yandex.common.util.functional.Function;
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.microformats.exceptions.MFException;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.microformats.spec.instances.*;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.RDFaUtils;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.data.JSONLDEntity;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.data.RDFaEntity;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.data.RDFaProperty;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.data.RDFaValueProperty;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.exceptions.RDFaDisallowKeyException;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.exceptions.RDFaException;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.jsonld.JSONLDParser;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.transformers.ExperimentalExtractor;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.serialize.util.APIVersion;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.tanker.LanguageContext;

import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: rasifiel
 * Date: 05.09.13
 * Time: 20:24
 */
public class SemanticAPI {

    private static final Function<RDFaEntity, JSONObject> RDFA2JSON = new Function<RDFaEntity, JSONObject>() {
        @Override
        public JSONObject apply(final RDFaEntity arg) {
            try {
                return new JSONObject(arg.asJson().toString());
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
        }
    };

    private static final Set<String> DEFAULT_FORMATS = Cf.set("microdata", "rdfa", "json-ld", "microformat");

    public static String parseDoc(final String doc, final String url, final boolean parseMf, final boolean newFormat, final int indent) throws JSONException {
        return parseDoc(doc, url, parseMf, newFormat, indent, DEFAULT_FORMATS, APIVersion.VERSION_1_1);
    }

    public static String parseDoc(final String doc, final String url, final boolean parseMf, final boolean newFormat, final int indent, final Set<String> formats) throws JSONException {
        return parseDoc(doc, url, parseMf, newFormat, indent, formats, APIVersion.VERSION_1_1);
    }


    public static String parseDoc(final String doc, final String url, final boolean parseMf, final boolean newFormat, final int indent, final Set<String> formats, APIVersion version) throws JSONException {
        version = APIVersion.VERSION_1_0;
        JSONObject resultObject = new JSONObject();
        List<Microdata> mds = MicrodataUtils.extractMD(doc, url, false, false);
        Pair<List<Microdata>, List<Microdata>> jsonMds = Cu.split(mds, new Filter<Microdata>() {
            @Override
            public boolean fits(final Microdata m) {
                return !(m instanceof ru.yandex.semantic.jsonld.JSONLDEntity);
            }
        });
        resultObject.put("microdata", MicrodataUtils.MD2JSON(version).map(jsonMds.first));
        Pair<List<RDFaEntity>, List<RDFaException>> rdfas = ExperimentalExtractor.getResults(doc, url, version);
        Pair<List<RDFaEntity>, List<RDFaEntity>> rdfaJsonld = Cu.split(rdfas.first, new Filter<RDFaEntity>() {
            @Override
            public boolean fits(final RDFaEntity o) {
                return !(o instanceof JSONLDEntity);
            }
        });
        resultObject.put("rdfa", RDFA2JSON.map(rdfaJsonld.first));
        List<JSONObject> rdfaJsons = RDFA2JSON.map(rdfaJsonld.second);
        List<JSONObject> mdsJsons = MicrodataUtils.MD2JSON(version).map(jsonMds.second);
        rdfaJsons.addAll(mdsJsons);
        resultObject.put("json-ld", rdfaJsons);
        if (parseMf) {
            Pair<List<MicroformatData>, List<MFException>> r = MicroformatsUtils.extractMF(doc, url,
                    MicroformatsManager.managerForMFsAndIncluded(HCard.getInstance(), HRecipe.getInstance(),
                            HReview.getInstance(), HReviewAggregate.getInstance(), HCalendar.getInstance(),
                            HAtom.getInstance(), HResume.getInstance()), false);
            resultObject.put("microformat", Cu.map(new Function<MicroformatData, JSONObject>() {
                @Override
                public JSONObject apply(final MicroformatData o) {
                    return MicroformatsUtils.toJson(o, Cf.<Integer, JSONObject>newMultiMap(), false, APIVersion.VERSION_0_1);
                }
            }, r.first));
        }

        Set keySet = Cf.set(resultObject.keys());
        for (final Object k : keySet) {
            if (!formats.contains(k)) {
                resultObject.remove(String.valueOf(k));
            }
        }
        if (newFormat) {
            return resultObject.toString(indent);
        } else {
            JSONArray resultArray = new JSONArray();
            final Iterator keys = resultObject.keys();
            while (keys.hasNext()) {
                Object key = keys.next();
                JSONArray a = resultObject.getJSONArray(String.valueOf(key));
                for (int i = 0; i < a.length(); i++) {
                    resultArray.put(a.get(i));
                }
            }
            return resultArray.toString(indent);
        }
    }

    public static JSONObject serialize2json(final String id, final List<Microdata> microdatas, final List<Microdata> jsonMicrodata,
                                            final List<MicroformatData> microformatDatas, final List<RDFaEntity> rdfaEntities,
                                            final List<RDFaEntity> jsonldEntities,
                                            final MultiMap<Integer, JSONObject> exceptionMultiMap, APIVersion version) throws JSONException {
        JSONObject resultObject = new JSONObject();
        if (!("".equals(id))) {
            resultObject.put("id", id);
        } else {
            resultObject.put("id", JSONObject.NULL);
        }
        for (final Microdata md : microdatas) {
            resultObject.append("microdata", MicrodataUtils.md2Json(md, exceptionMultiMap, version));
        }
        if (microdatas.isEmpty()) {
            resultObject.put("microdata", new JSONArray());
        }
        for (final RDFaEntity entity : rdfaEntities) {
            if (entity.isRoot) {
                JSONObject rdfa = RDFaUtils.toAPIJson(entity, exceptionMultiMap, false, APIVersion.VERSION_0_1);
                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, false, APIVersion.VERSION_0_1));
        }
        if (jsonldEntities.isEmpty()) {
            resultObject.put("json-ld", new JSONArray());
        }
        resultObject.put("microformat", Cu.map(new Function<MicroformatData, JSONObject>() {
            @Override
            public JSONObject apply(final MicroformatData o) {
                return MicroformatsUtils.toJson(o, exceptionMultiMap, false, APIVersion.VERSION_0_1);
            }
        }, microformatDatas));
        return resultObject;
    }

    public String serializePublicAPI(final String doc, final String url, final FrontEnd frontEnd, final LanguageContext languageContext, String id, boolean only_errors, APIVersion version) throws RDFaDisallowKeyException, JSONException {
        List<Microdata> mds = MicrodataUtils.extractMD(doc, url, false, false);
        Pair<List<Microdata>, List<Microdata>> jsonMds = Cu.split(mds, new Filter<Microdata>() {
            @Override
            public boolean fits(final Microdata m) {
                return !(m instanceof ru.yandex.semantic.jsonld.JSONLDEntity);
            }
        });
        Pair<List<RDFaEntity>, List<RDFaException>> rdfas = ExperimentalExtractor.getResults(doc, url, version);
        boolean isNotForParse = this.checkForDisallowKey(rdfas.first);
        if (isNotForParse) {
            throw new RDFaDisallowKeyException(false, rdfas.getFirst().get(0), "");
        }

        Pair<List<RDFaEntity>, List<RDFaEntity>> rdfaJsonld = Cu.split(rdfas.first, new Filter<RDFaEntity>() {
            @Override
            public boolean fits(final RDFaEntity o) {
                return !(o instanceof JSONLDEntity);
            }
        });
        JSONLDParser jsonldParser = frontEnd.getJsonldParserFactory().createParser();
        List<RDFaEntity> expandedJsons = new ArrayList<RDFaEntity>();
        for (RDFaEntity entity : rdfaJsonld.second) {
            List<RDFaEntity> entities = jsonldParser.expandDocument(entity, url);
            expandedJsons.addAll(entities);
        }


        Pair<List<MicroformatData>, List<MFException>> r = MicroformatsUtils.extractMF(doc, url,
                MicroformatsManager.managerForMFsAndIncluded(HCard.getInstance(), HRecipe.getInstance(),
                        HReview.getInstance(), HReviewAggregate.getInstance(), HCalendar.getInstance(),
                        HAtom.getInstance(), HResume.getInstance()), false);
        MultiMap<Integer, JSONObject> exceptions = frontEnd.getProcessor(languageContext).getExceptions(mds, r.first, rdfaJsonld.first, expandedJsons,
                r.second, rdfas.getSecond());
        return serialize2json(id, jsonMds.first, jsonMds.second, r.first, rdfaJsonld.first, expandedJsons, exceptions, version).toString();
    }

    public boolean checkForDisallowKey(List<RDFaEntity> rdfas) {
        for (RDFaEntity entity : rdfas) {
            if (entity.hasProperty("http://webmaster.yandex.ru/vocabularies/disallow_api")) {
                List<RDFaProperty> e = entity.getProperty("http://webmaster.yandex.ru/vocabularies/disallow_api");
                for (RDFaProperty vp : e) {
                    if (vp instanceof RDFaValueProperty) {
                        if ("semantic".equals(((RDFaValueProperty) vp).getValue())) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }


}
