/* BdfServer - Copyright (c) 2020-2025 Vincent Calame - Exemole
 * Logiciel libre donné sous triple licence :
 * 1) selon les termes de la CeCILL V2
 * 2) selon les termes de l’EUPL V.1.1
 * 3) selon les termes de la GNU GPLv3
 * Voir le fichier licences.txt
 */


package fr.exemole.bdfserver.tools.externalsource;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.fichotheque.ExistingIdException;
import net.fichotheque.FichothequeEditor;
import net.fichotheque.externalsource.ExternalItem;
import net.fichotheque.externalsource.ExternalSource;
import net.fichotheque.namespaces.BdfSpace;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.ThesaurusEditor;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.io.IOUtils;
import net.mapeadores.util.json.JSONArray;
import net.mapeadores.util.json.JSONObject;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.Langs;


/**
 *
 * @author Vincent Calame
 */
public class WikidataExternalSource implements ExternalSource {

    private final static Pattern QID_PATTERN = Pattern.compile("^Q[0-9]+$");

    public WikidataExternalSource() {

    }

    @Override
    public boolean isIdalphaBased() {
        return true;
    }

    @Override
    public Motcle getMotcle(FichothequeEditor fichothequeEditor, Thesaurus localThesaurus, int id) {
        return getMotcle(fichothequeEditor, localThesaurus, "Q" + id);
    }

    @Override
    public Motcle getMotcle(FichothequeEditor fichothequeEditor, Thesaurus localThesaurus, String idalpha) {
        Matcher matcher = QID_PATTERN.matcher(idalpha);
        if (matcher.matches()) {
            String qId = idalpha;
            return createEntityMotcle(fichothequeEditor, localThesaurus, qId);
        } else {
            return null;
        }
    }

    @Override
    public Motcle getMotcle(FichothequeEditor fichothequeEditor, Thesaurus localThesaurus, String label, Lang lang) {
        return getMotcle(fichothequeEditor, localThesaurus, label);
    }

    @Override
    public Collection<ExternalItem> search(String query, Lang lang) {
        JSONArray searchArray = getSearchArray(query, lang);
        if (searchArray == null) {
            return ExternalSourceUtils.EXTERNALITEM_EMPTYLIST;
        }
        int length = searchArray.length();
        if (length == 0) {
            return ExternalSourceUtils.EXTERNALITEM_EMPTYLIST;
        }
        List<ExternalItem> result = new ArrayList<ExternalItem>();
        for (int i = 0; i < length; i++) {
            JSONObject resultObject = searchArray.getJSONObject(i);
            String idalpha = resultObject.getString("id");
            String title = resultObject.getString("label");
            String description = resultObject.optString("description");
            result.add(ExternalSourceUtils.toExternalItem(idalpha, title, description));
        }
        return result;
    }

    private Motcle createEntityMotcle(FichothequeEditor fichothequeEditor, Thesaurus localThesaurus, String qId) {
        JSONObject entity = getEntity(qId);
        if (entity == null) {
            return null;
        }
        ThesaurusEditor thsedit = fichothequeEditor.getThesaurusEditor(localThesaurus);
        Motcle newMotcle;
        try {
            newMotcle = thsedit.createMotcle(-1, qId);
        } catch (ExistingIdException | ParseException e) {
            throw new ShouldNotOccurException(e);
        }
        JSONObject labels = entity.getJSONObject("labels");
        Langs langs = localThesaurus.getThesaurusMetadata().getAuthorizedLangs();
        for (Lang lang : langs) {
            String langCode = lang.toString().toLowerCase();
            if (labels.has(langCode)) {
                JSONObject label = labels.getJSONObject(langCode);
                thsedit.putLabel(newMotcle, lang, label.optString("value"));
            }
        }
        fichothequeEditor.putAttribute(newMotcle, BdfSpace.URL_KEY, "https://www.wikidata.org/wiki/" + qId);
        return newMotcle;
    }

    private JSONObject getEntity(String qId) {
        JSONObject mainObject;
        try {
            mainObject = getMainObject(qId);
            if (mainObject == null) {
                return null;
            }
        } catch (IOException ioe) {
            return null;
        }
        if (mainObject.has("entities")) {
            JSONObject entities = mainObject.getJSONObject("entities");
            if (entities.has(qId)) {
                return entities.getJSONObject(qId);
            }
        }
        return null;
    }

    private JSONArray getSearchArray(String query, Lang lang) {
        JSONObject mainObject;
        try {
            mainObject = getSearchObject(query, lang);
            if (mainObject == null) {
                return null;
            }
        } catch (IOException ioe) {
            return null;
        }
        if (mainObject.has("search")) {
            return mainObject.getJSONArray("search");
        }
        return null;
    }

    private JSONObject getMainObject(String qId) throws IOException {
        URL url = new URL("https://www.wikidata.org/wiki/Special:EntityData/" + qId + ".json");
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        checkConnection(connection);
        int reponse = connection.getResponseCode();
        if (reponse != HttpURLConnection.HTTP_OK) {
            return null;
        }
        String jsonText;
        try (InputStream is = connection.getInputStream()) {
            jsonText = IOUtils.toString(is, "UTF-8");
        }
        return new JSONObject(jsonText);
    }

    private JSONObject getSearchObject(String query, Lang lang) throws IOException {
        StringBuilder buf = new StringBuilder("https://www.wikidata.org/w/api.php?action=wbsearchentities&search=");
        buf.append(URLEncoder.encode(query, "UTF-8"));
        buf.append("&format=json&errorformat=plaintext&language=");
        buf.append(lang.toString());
        buf.append("&uselang=");
        buf.append(lang.toString());
        buf.append("&type=item");
        URL url = new URL(buf.toString());
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        checkConnection(connection);
        int reponse = connection.getResponseCode();
        if (reponse != HttpURLConnection.HTTP_OK) {
            return null;
        }
        String jsonText;
        try (InputStream is = connection.getInputStream()) {
            jsonText = IOUtils.toString(is, "UTF-8");
        }
        return new JSONObject(jsonText);
    }

    private void checkConnection(HttpURLConnection urlConnection) {
        urlConnection.setRequestProperty("User-Agent", "BdfExternalSource/1.0 (https://framagit.org/fichotheque/bdf)");
    }

}
