package org.texttechnologylab.project.gruppe_05_1.nlp;

import com.mongodb.client.model.Filters;
import com.mongodb.client.model.UpdateOneModel;
import com.mongodb.client.model.Updates;
import com.mongodb.client.model.WriteModel;
import de.tudarmstadt.ukp.dkpro.core.api.lexmorph.type.pos.POS;
import de.tudarmstadt.ukp.dkpro.core.api.metadata.type.DocumentMetaData;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Sentence;
import de.tudarmstadt.ukp.dkpro.core.api.segmentation.type.Token;
import de.tudarmstadt.ukp.dkpro.core.api.syntax.type.dependency.Dependency;
import org.apache.commons.io.FileUtils;
import org.apache.uima.UIMAException;
import org.apache.uima.cas.CASException;
import org.apache.uima.fit.factory.JCasFactory;
import org.apache.uima.fit.util.JCasUtil;
import org.apache.uima.jcas.JCas;
import org.apache.uima.resource.ResourceInitializationException;
import org.apache.uima.resource.metadata.TypeSystemDescription;
import org.apache.uima.util.CasCreationUtils;
import org.apache.uima.util.XMLInputSource;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.dkpro.core.io.xmi.XmiWriter;
import org.hucompute.textimager.uima.type.category.CategoryCoveredTagged;
import org.texttechnologylab.DockerUnifiedUIMAInterface.DUUIComposer;
import org.texttechnologylab.DockerUnifiedUIMAInterface.driver.DUUIDockerDriver;
import org.texttechnologylab.DockerUnifiedUIMAInterface.driver.DUUIRemoteDriver;
import org.texttechnologylab.DockerUnifiedUIMAInterface.driver.DUUIUIMADriver;
import org.texttechnologylab.DockerUnifiedUIMAInterface.lua.DUUILuaContext;
import org.texttechnologylab.annotation.NamedEntity;
import org.texttechnologylab.project.gruppe_05_1.database.MongoDBHandler;
import org.texttechnologylab.project.gruppe_05_1.database.domainimp.speeches.Speech_MongoDB_Impl;
import org.texttechnologylab.project.gruppe_05_1.util.Logger;
import org.texttechnologylab.project.gruppe_05_1.xml.speeches.Interfaces.Content;
import org.texttechnologylab.project.gruppe_05_1.xml.speeches.Interfaces.Speech;
import org.texttechnologylab.uima.type.Sentiment;
import org.xml.sax.SAXException;

import java.io.*;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static org.apache.uima.fit.factory.AnalysisEngineFactory.createEngineDescription;

public class NlpUtils {

    // common class-attributes
    private static DUUIComposer pComposer = null;
    private static final int iWorkers = 1;
    private static final String TYPE_SYSTEM_DESCRIPTOR_PATH = "/speeches/TypeSystem.xml.gz";
    private static final int MAX_FEATURE_LENGTH = 10000;


    public static void createNlpData() {
        // Source: Dr. Abrami - Beispiel        TODO
        duuiInit();
        runDockerDriver();
        try {
            runVideos();
        } catch (Exception e) {
            Logger.error("Exception: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        // Source:
        casInit();
        createSentenceInfo();
        createNAmedEntities();
        createSentimentInfo();
    }

    private static void duuiInit() {

        DUUILuaContext ctx;
        try {
            ctx = new DUUILuaContext().withJsonLibrary();
        } catch (IOException e) {
            Logger.error("IOException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        try {
            pComposer = new DUUIComposer()
                    .withSkipVerification(true)     // wir überspringen die Verifikation aller Componenten =)
                    .withLuaContext(ctx)            // wir setzen den definierten Kontext
                    .withWorkers(iWorkers);         // wir geben dem Composer eine Anzahl an Threads mit.
        } catch (URISyntaxException e) {
            Logger.error("URISyntaxException: " + e.getMessage());
            Logger.error(e.getMessage());
            throw new RuntimeException(e);
        }

        DUUIUIMADriver uima_driver = new DUUIUIMADriver();
        DUUIRemoteDriver remoteDriver = new DUUIRemoteDriver();
        DUUIDockerDriver dockerDriver = null;
        try {
            dockerDriver = new DUUIDockerDriver();
        } catch (IOException e) {
            Logger.error("IOException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        } catch (UIMAException e) {
            Logger.error("UIMAException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        } catch (SAXException e) {
            Logger.error("SAXException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        // Hinzufügen der einzelnen Driver zum Composer
        // pComposer.addDriver(uima_driver, remoteDriver, dockerDriver);    // ORIG
        pComposer.addDriver(uima_driver);

    }

    /**
     * Initialization of a sample CAS document
     * @return JCas object
     */
    public static JCas getCas() {
        // init a CAS with a static text.
        JCas pCas;
        try {
            pCas = JCasFactory.createText("Ich finde dieses Programm läuft sehr gut. Ich überlege wie ich dieses für meine Bachelor-Arbeit nachnutzen kann.", "de");
        } catch (ResourceInitializationException e) {
            Logger.error("ResourceInitializationException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        } catch (CASException e) {
            Logger.error("CASException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        // Define some metadata to serialize the CAS with the xmi writer
        DocumentMetaData dmd = new DocumentMetaData(pCas);
        dmd.setDocumentId("test");
        dmd.setDocumentTitle("DUUI Test-Dokument");
        dmd.addToIndexes();

        return pCas;
    }


    public static void runDockerDriver() {

        // reset existing pipeline-components
        pComposer.resetPipeline();

        // Please note that the Docker images are first downloaded when they are called up for the first time.
        try {
            pComposer.add(new DUUIDockerDriver.Component("docker.texttechnologylab.org/textimager-duui-spacy-single-de_core_news_sm:0.1.4")
                    .withImageFetching()
                    .withScale(iWorkers)
                    .build());
        } catch (Exception e) {
            Logger.error("Exception: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        try {
            pComposer.add(new DUUIDockerDriver.Component("docker.texttechnologylab.org/gervader_duui:1.0.2")
                    .withParameter("selection", "text") // nessecary
                    .withImageFetching()
                    .withScale(iWorkers)
                    .build());
        } catch (Exception e) {
            Logger.error("Exception: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        JCas tCas = getCas();

        try {
            pComposer.run(tCas);
        } catch (Exception e) {
            Logger.error("Exception: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        JCasUtil.select(tCas, Sentence.class).forEach(sentence -> {
            Logger.info(sentence.getBegin()+"-"+sentence.getEnd()+": "+sentence.getCoveredText());
            Logger.info(JCasUtil.selectCovered(org.hucompute.textimager.uima.type.Sentiment.class, sentence).toString());
        });
    }

    private static void casInit() {
        JCas jcas;
        try {
            jcas = JCasFactory.createJCas();
        } catch (ResourceInitializationException e) {
            Logger.error("ResourceInitializationException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        } catch (CASException e) {
            Logger.error("CASException: " + e.getMessage());
            Logger.error(Arrays.toString(e.getStackTrace()));
            throw new RuntimeException(e);
        }

        jcas.setDocumentLanguage("de");
        jcas.setDocumentText(
                "Frau Präsidentin! Liebe Kolleginnen! Liebe Kollegen! Der Jahreswirtschaftsbericht war heute Gegenstand der Kabinettssitzung. Der Kollege Robert Habeck wird ihn im Wirtschaftsausschuss vorstellen, und morgen besteht ja auch hier Gelegenheit zur Debatte. Eine erfreuliche Nachricht ist, dass die Inflation zurückgeht. Eine der größten Bedrohungen für unsere wirtschaftliche Entwicklung und auch für den Lebensstandard der Bürgerinnen und Bürger ist die Geldentwertung. Im Zusammenwirken der Geldpolitik der Europäischen Zentralbank und der moderat restriktiven Fiskalpolitik der Bundesregierung haben wir große Fortschritte erzielt. Ich glaube, wir dürfen sagen: Die Entwicklung der Inflation ist nun beherrschbar geworden. Auf der anderen Seite ist die wirtschaftliche Entwicklung unseres Landes nicht zufriedenstellend. Wir haben eine zu geringe wirtschaftliche Dynamik. Dies hat damit zu tun, dass wir einen steigenden Zins und reduzierte Nachfrage auf den Weltmärkten aufgrund einer sich abkühlenden globalen Konjunktur haben, und auch die Folgen des Ausfalls günstiger fossiler Energieimporte nach Deutschland sind zu verzeichnen. Aber diese gegenwärtige Wachstumsschwäche unseres Landes passt sich ein in ein längeres Bild. In den vergangenen zehn Jahren, seit 2014, ist Deutschland in allen internationalen Standortvergleichen Schritt für Schritt zurückgefallen. Wir haben es also nicht mit einfachen Erklärungen zu tun, sondern mit strukturellen Aufgaben, denen wir uns stellen müssen. Ich leite daraus zwei Dinge ab: Erstens. Einfache Erklärungen sind nicht zutreffend. Zweitens. Alle, die in den vergangenen zehn Jahren Verantwortung getragen haben und heute Verantwortung tragen, sind aufgefordert, daran mitzuwirken, die Wachstumsdynamik in unserem Land zu verbessern. Ich will dazu konkret Folgendes sagen: Erstens. Die Bundesregierung setzt sich für eine Belebung auf unserem Arbeitsmarkt ein; denn der Mangel an Arbeitskräften ist eine Wachstumsbremse. In diesem Zusammenhang haben wir mit dem Fachkräfteeinwanderungsgesetz einen großen Fortschritt erzielt. Ihm müssen nun weitere Anstrengungen bei der Mobilisierung der Reserven unseres Arbeitsmarktes folgen. Zweitens. Die Bürokratiebelastung ist auf einem Allzeithoch seit dem Jahr 2012, in dem erstmals die Bürokratiebelastung gemessen worden ist. Mit den Meseberger Beschlüssen des Bundeskabinetts zum Bürokratieabbau werden diese Belastungen auf ein Allzeittief sinken, und wir müssen weitere Anstrengungen unternehmen, unsere Betriebe zu entfesseln. Drittens. Wir investieren auf einem Rekordniveau in Schiene, Straße, Wasserstraße, digitale Netze und in unsere Energieinfrastruktur. Aber diesen Anstrengungen müssen weitere folgen, insbesondere müssen jetzt die Rahmenbedingungen für private Investitionen und die Mobilisierung privaten Kapitals in Deutschland und Europa optimiert werden. Viertens. Unser steuerliches Umfeld, unsere steuerlichen Rahmenbedingungen müssen wettbewerbsfähiger werden. Die Bundesregierung und der Gesetzgeber haben ja bereits mit dem Zukunftsfinanzierungsgesetz Veränderungen vorgenommen, die sehr positiv wahrgenommen werden. Nun geht es darum, mit dem Wachstumschancengesetz einen weiteren Baustein gemeinsam zu beschließen, um im Bereich von Investitionsanreizen, zum Beispiel in der Baukonjunktur, und durch die Stärkung von privaten Forschungsvorhaben unserer Wirtschaft einen echten Schub zu geben. Hier kommt auch der Opposition eine besondere Verantwortung zu, mit dazu beizutragen, das wirtschaftliche Umfeld zu verbessern. Weitere Anstrengungen im Bereich der Steuerpolitik werden zu diskutieren sein. Liebe Kolleginnen und Kollegen, wir haben es gemeinsam im Zusammenwirken in Europa vermocht, die Geldentwertung unter Kontrolle zu bringen. Nun steht die nächste große Aufgabe vor uns: eine Wirtschaftswende, damit auch die Wachstumsdynamik unseres Landes wieder größer wird. "
        );

        Collection<Sentence> sentences = JCasUtil.select(jcas, Sentence.class);
        for (Sentence sentence : sentences) {
            // The text described by an annotation can be output using the getCoveredText() method
            sentence.getCoveredText();

            // Retrieve nested annotations, for example all tokens of a sentence
            Collection<Token> tokens = JCasUtil.selectCovered(Token.class, sentence);


        }
        for (Token token : JCasUtil.select(jcas, Token.class)) {
            Logger.debug(token.getCoveredText() + '\t' + token.getPosValue());
        }


        for (NamedEntity entity : JCasUtil.select(jcas, NamedEntity.class)) {
            if (entity.getValue().equals("PER")) {
                Logger.debug("Person: " + entity.getCoveredText());
            } else if (entity.getValue().equals("LOC")) {
                Logger.debug("Location: " + entity.getCoveredText());
            }
        }

        for (Sentence sentence : JCasUtil.select(jcas, Sentence.class)) {
            Logger.debug(sentence.getCoveredText());
            for (Sentiment sentiment : JCasUtil.selectCovered(Sentiment.class, sentence)) {
                Logger.debug("Sentiment: " + sentiment.getSentiment());
            }
        }

    }


    /**
     * Execution of video processing via DUUI using the RemoteDriver
     * @throws Exception in case of an error
     */
    public static void runVideos() throws Exception {

        // laden eines Videos aus dem Ressourcen-Ordner
        ClassLoader classLoader = NlpUtils.class.getClassLoader();
        URL fVideo = classLoader.getResource("example.mp4");    // TODO

        // convertieren eines Videos in einen Base64-String
        assert fVideo != null;
        File fFile = new File(fVideo.getPath());
        byte[] bFile = FileUtils.readFileToByteArray(fFile);
        String encodedString = Base64.getEncoder().encodeToString(bFile);
        String pMimeType = Files.probeContentType(Path.of(fVideo.getPath()));

        // erzeugng einer Beispiel-Cas
        JCas pCas = getCas();

        // erstellen einer view namens "video" und setzen des Mimetpes sowie der SofaData
        JCas videoCas = pCas.createView("video");
        videoCas.setSofaDataString(encodedString, pMimeType);
        videoCas.setDocumentLanguage("de");

        // erstellen einer weiteren View, für die Transcription
        JCas transcriptCas = pCas.createView("transcript");

        // this takes some time, because the video is not small, but also not to large.
        pComposer.add(new DUUIRemoteDriver.Component("http://whisperx.lehre.texttechnologylab.org")
                .withScale(iWorkers)
                .withSourceView("video")            // where is the video
                .withTargetView("transcript")       // where the transcript must be annotated
                .build());

        // alternativ
//        pComposer.add(new DUUIDockerDriver.Component("docker.texttechnologylab.org/duui_whisperx:0.1")
//                .withScale(iWorkers)
//                .withSourceView("video")
//                .withTargetView("transcript")
//                .build());

        // write into XMI for debugging purposes.
        pComposer.add(new DUUIUIMADriver.Component(createEngineDescription(XmiWriter.class,
                XmiWriter.PARAM_TARGET_LOCATION, "/tmp/",
                XmiWriter.PARAM_PRETTY_PRINT, true,
                XmiWriter.PARAM_OVERWRITE, true,
                XmiWriter.PARAM_VERSION, "1.1",
                XmiWriter.PARAM_COMPRESSION, "GZIP"
        )).build());

        pComposer.run(pCas);

        // select some data
        JCasUtil.selectAll(videoCas).forEach(System.out::println);

        // select some data
        JCasUtil.selectAll(transcriptCas).forEach(System.out::println);


    }

    private static void createSentenceInfo() {

    }

    private static void createNAmedEntities() {

    }

    private static void createSentimentInfo() {

    }

    public static void runRemoteDriver() throws Exception {
        DUUILuaContext luaContext = new DUUILuaContext().withJsonLibrary();
        pComposer = new DUUIComposer()
                .withSkipVerification(true)
                .withLuaContext(luaContext)
                .withWorkers(iWorkers);

        pComposer.addDriver(new DUUIRemoteDriver());
        MongoDBHandler mongoDBHandler = new MongoDBHandler();
        Bson filter = Filters.exists("analysisResults", false);
        List<Speech> unprocessedSpeeches = mongoDBHandler.retrieveAllSpeeches(filter);
        if (unprocessedSpeeches.isEmpty()) {
            Logger.warn("No unprocessed speeches found");
        }
        else {
            pComposer.resetPipeline();

            pComposer.add(new DUUIRemoteDriver.Component("http://spacy.lehre.texttechnologylab.org")
                    .withScale(iWorkers)
                    .build());
            pComposer.add(new DUUIRemoteDriver.Component("http://gervader.lehre.texttechnologylab.org")
                    .withScale(iWorkers)
                    .build());
            List<WriteModel<Document>> bulkOperations = new ArrayList<>();
            for (Speech rede : unprocessedSpeeches) {
                JCas jCas;
                jCas = rede.toCas();
                pComposer.run(jCas);
                Logger.info("Anzahl Sentiment-Annotationen: " + JCasUtil.select(jCas, org.hucompute.textimager.uima.type.Sentiment.class).size());
                Document analysisResults = extractAnnotations(jCas);
                Bson updateFilter = Filters.eq("speechKey", rede.getSpeechKey());
                Bson update = new Document("$set", new Document("analysisResults", analysisResults));
                bulkOperations.add(new UpdateOneModel<>(updateFilter, update));
            }
            if (!bulkOperations.isEmpty()) {
                Logger.debug("Processing of " + bulkOperations.size() + " documents finished");
                Logger.debug("uploading...");
                mongoDBHandler.bulkWriteNlpData(bulkOperations);
                Logger.debug("Bulk write completed for " + bulkOperations.size() + " documents.");
                mongoDBHandler.close();
            }
        }
    }


    /**
     * @param jCas
     * Liest die Annotations aus dem JCas Objekt und speichet sie in Listen ab jeweils
     */
    public static Document extractAnnotations(JCas jCas) {
        Document analysisResults = new Document();

        // Tokens: Include POS, Lemma, etc.
        List<Document> tokens = new ArrayList<>();
        for (Token token : JCasUtil.select(jCas, Token.class)) {
            Document tokenDoc = new Document()
                    .append("text", token.getCoveredText())
                    .append("pos", token.getPos().getPosValue())
                    .append("lemma", token.getLemma().getValue());
            tokens.add(tokenDoc);
        }
        analysisResults.append("tokens", tokens);

        // Sentences
        List<String> sentences = JCasUtil.select(jCas, Sentence.class).stream()
                .map(Sentence::getCoveredText)
                .collect(Collectors.toList());
        analysisResults.append("sentences", sentences);

        // Dependencies
        List<Document> dependencies = new ArrayList<>();
        for (Dependency dep : JCasUtil.select(jCas, Dependency.class)) {
            Document depDoc = new Document()
                    .append("type", dep.getDependencyType())
                    .append("governor", dep.getGovernor().getCoveredText())
                    .append("dependent", dep.getDependent().getCoveredText());
            dependencies.add(depDoc);
        }
        analysisResults.append("dependencies", dependencies);

        // Named Entities
        List<Document> namedEntities = new ArrayList<>();
        for (de.tudarmstadt.ukp.dkpro.core.api.ner.type.NamedEntity ne : JCasUtil.select(jCas, de.tudarmstadt.ukp.dkpro.core.api.ner.type.NamedEntity.class)) {
            Document neDoc = new Document()
                    .append("text", ne.getCoveredText())
                    .append("type", ne.getValue());
            namedEntities.add(neDoc);
        }
        analysisResults.append("namedEntities", namedEntities);

        // Sentiment
        List<Document> sentiments = new ArrayList<>();
        for (org.hucompute.textimager.uima.type.Sentiment sentiment :
                JCasUtil.select(jCas, org.hucompute.textimager.uima.type.Sentiment.class)) {

            Document sentimentDoc = new Document()
                    .append("begin", sentiment.getBegin())
                    .append("end", sentiment.getEnd())
                    .append("score", sentiment.getSentiment())
                    .append("subjectivity", sentiment.getSubjectivity());

            // Check for VaderSentiment subtype
            if (sentiment instanceof org.hucompute.textimager.uima.type.VaderSentiment) {
                org.hucompute.textimager.uima.type.VaderSentiment vader =
                        (org.hucompute.textimager.uima.type.VaderSentiment) sentiment;
                sentimentDoc
                        .append("pos", vader.getPos())
                        .append("neu", vader.getNeu())
                        .append("neg", vader.getNeg());
            }
            sentiments.add(sentimentDoc);
        }
        analysisResults.append("sentiments", sentiments);

        List<Document> topics = new ArrayList<>();
        for (CategoryCoveredTagged topic : JCasUtil.select(jCas, CategoryCoveredTagged.class)) {
            Document topicDoc = new Document()
                    .append("topic", topic.getValue())
                    .append("score", topic.getScore())
                    .append("tags", topic.getTags())
                    .append("text", topic.getCoveredText());
            topics.add(topicDoc);
        }
        topics.sort((d1, d2) -> Double.compare(d2.getDouble("score"), d1.getDouble("score")));
        analysisResults.append("topics", topics);
        return analysisResults;
    }
}
