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

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.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.data.JSONLDEntity;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.data.RDFaEntity;
import ru.yandex.webmaster3.core.semantic.semantic_document_parser.rdfa.exceptions.JsonCollidingKeywordsRDFaException;
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.JSONLDExpansionException;
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.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by aleksart on 09.04.15.
 */
public class SemanticAPI_v1 extends SemanticAPI {

    private static ObjectMapper om = new ObjectMapper();

    public JsonNode serialize2JsonNode(final String doc, final String url, final FrontEnd frontEnd,
                                       final LanguageContext languageContext,
                                       String id, boolean only_errors, APIVersion version) throws RDFaDisallowKeyException, IOException, JSONException {
        return om.readTree(serializePublicAPI(doc, url, frontEnd, languageContext, id, only_errors, version));
    }


    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 {
        return publicAPIResult(doc, url, frontEnd, languageContext, id, only_errors, version).serialize2json(version).toString();
    }

    public SemanticValidatorResult publicAPIResult(final String doc, final String url, final FrontEnd frontEnd,
                                                   final LanguageContext languageContext,
                                                   String id, boolean only_errors, APIVersion version) throws RDFaDisallowKeyException, JSONException {
        //TODO: Если запрос пришел с фронта, то isVerifier==true
        List<Microdata> mds = MicrodataUtils.extractMD(doc, url, false, true);
        Pair<List<Microdata>, List<Microdata>> jsonMds = Cu.split(mds, m -> !(m instanceof ru.yandex.semantic.jsonld.JSONLDEntity));
        List<RDFaException> rdFaExceptions = new ArrayList<>();
        MultiMap<Integer, JsonCollidingKeywordsRDFaException> duplicateFieldsExceptionsMap = new MultiMap<>();

        Pair<List<RDFaEntity>, List<RDFaException>> rdfas = ExperimentalExtractor.getResults(doc, url, version);
        boolean isNotForParse = this.checkForDisallowKey(rdfas.first);

        for (RDFaException e : rdfas.getSecond()) {
            if (e instanceof JsonCollidingKeywordsRDFaException) {
                duplicateFieldsExceptionsMap.append(e.getHash(), (JsonCollidingKeywordsRDFaException) e);
            } else {
                rdFaExceptions.add(e);
            }
        }

        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;
            try {
                entities = jsonldParser.expandDocument(entity, url);
            } catch (JSONLDExpansionException e) {
                RDFaEntity empty = new JSONLDEntity(null, null);
                rdFaExceptions.add(e.getRDFaException(empty));
                entities = Cf.list(empty);
            }
            if (duplicateFieldsExceptionsMap.containsKey(entity.count)) {
                for (JsonCollidingKeywordsRDFaException ex : duplicateFieldsExceptionsMap.get(entity.count)) {
                    for (RDFaEntity en : entities) {
                        rdFaExceptions.add(new JsonCollidingKeywordsRDFaException(ex.isCritical, en, ex.getMessage(), ex.location));
                    }
                }
            }
            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, rdFaExceptions);
        return new SemanticValidatorResult(id, jsonMds.first, jsonMds.second, r.first, rdfaJsonld.first, expandedJsons, exceptions, only_errors);
    }
}
