/* BdfServer_Get - Copyright (c) 2011-2024 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.get.streamproducers;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.function.Predicate;
import net.fichotheque.Fichotheque;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.croisement.Croisements;
import net.fichotheque.permission.PermissionSummary;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.metadata.ThesaurusMetadata;
import net.fichotheque.utils.FichothequeUtils;
import net.mapeadores.opendocument.io.OdSource;
import net.mapeadores.opendocument.io.OdXML;
import net.mapeadores.opendocument.io.OdZip;
import net.mapeadores.opendocument.io.OdZipEngine;
import net.mapeadores.util.io.StreamProducer;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.mimetype.MimeTypeConstants;
import net.mapeadores.util.xml.AppendableXMLWriter;
import net.mapeadores.util.xml.XMLPart;
import net.mapeadores.util.xml.XMLUtils;
import net.mapeadores.util.xml.XMLWriter;


/**
 *
 * @author Vincent Calame
 */
public class StatsThesaurusOdsProducer implements StreamProducer {

    private final String fileName;
    private final Thesaurus thesaurus;
    private final PermissionSummary permissionSummary;
    private final Lang lang;
    private final Predicate<FicheMeta> fichePredicate;

    public StatsThesaurusOdsProducer(String fileName, Thesaurus thesaurus, PermissionSummary permissionSummary, Lang lang, Predicate<FicheMeta> fichePredicate) {
        this.fileName = fileName;
        this.thesaurus = thesaurus;
        this.permissionSummary = permissionSummary;
        this.lang = lang;
        this.fichePredicate = fichePredicate;
    }

    @Override
    public String getMimeType() {
        return MimeTypeConstants.ODS;
    }

    @Override
    public String getCharset() {
        return null;
    }

    @Override
    public String getFileName() {
        return fileName;
    }

    @Override
    public void writeStream(OutputStream outputStream) throws IOException {
        OdZipEngine.run(outputStream, OdZip.spreadSheet()
                .contentOdSource(new ContentOdSource())
        );
    }


    private class ContentOdSource implements OdSource {

        private ContentOdSource() {
        }

        @Override
        public void writeStream(OutputStream outputStream) throws IOException {
            BufferedWriter buf = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(buf);
            xmlWriter.appendXMLDeclaration();
            StatsThesaurusXMLPart statsThesaurusXMLPart = new StatsThesaurusXMLPart(xmlWriter, thesaurus, permissionSummary, lang, fichePredicate);
            statsThesaurusXMLPart.start();
            statsThesaurusXMLPart.addTable();
            statsThesaurusXMLPart.end();
            buf.flush();
        }

    }


    private static class StatsThesaurusXMLPart extends XMLPart {

        private final Thesaurus thesaurus;
        private final Predicate<FicheMeta> fichePredicate;
        private final SubsetKey thesaurusKey;
        private final Corpus[] corpusArray;
        private final boolean withIdalpha;
        private final boolean isBabelien;
        private final Lang thesaurusLang;
        private final MotcleStats motcleStats;

        private StatsThesaurusXMLPart(XMLWriter xmlWriter, Thesaurus thesaurus, PermissionSummary permissionSummary, Lang thesaurusLang, Predicate<FicheMeta> fichePredicate) {
            super(xmlWriter);
            this.thesaurus = thesaurus;
            this.thesaurusKey = thesaurus.getSubsetKey();
            ThesaurusMetadata thesaurusMetaData = thesaurus.getThesaurusMetadata();
            short thesaurusType = thesaurusMetaData.getThesaurusType();
            this.withIdalpha = (thesaurusType == ThesaurusMetadata.IDALPHA_TYPE);
            this.isBabelien = (thesaurusType == ThesaurusMetadata.BABELIEN_TYPE);
            this.thesaurusLang = thesaurusLang;
            this.corpusArray = FichothequeUtils.toCorpusArray(thesaurus.getFichotheque(), permissionSummary.getSubsetAccessPredicate());
            this.motcleStats = new MotcleStats();
            this.fichePredicate = fichePredicate;
        }

        public void start() throws IOException {
            OdXML.openDocumentContent(this);
            OdXML.openBody(this);
            OdXML.openSpreadsheet(this);
        }

        public void end() throws IOException {
            OdXML.closeSpreadsheet(this);
            OdXML.closeBody(this);
            OdXML.closeDocumentContent(this);
        }

        public void addTable() throws IOException {
            String thesaurusName = thesaurusKey.getSubsetName();
            OdXML.openTable(this, thesaurusName);
            addFirstRow();
            for (Motcle motcle : thesaurus.getFirstLevelList()) {
                addMotcleRow(motcle);
            }
            OdXML.closeTable(this);
        }

        private void addMotcleRow(Motcle motcle) throws IOException {
            motcleStats.updateStats(motcle);
            int total = motcleStats.getTotal();
            OdXML.openTableRow(this);
            OdXML.addNumberTableCell(this, motcle.getId());
            if (withIdalpha) {
                OdXML.addStringTableCell(this, motcle.getIdalpha());
            }
            OdXML.addStringTableCell(this, motcle.getLabelString(thesaurusLang));
            OdXML.addNumberTableCell(this, total);
            int[] parCroisement = motcleStats.getParCroisement();
            for (int i = 0; i < parCroisement.length; i++) {
                int ct = parCroisement[i];
                if (ct == 0) {
                    OdXML.addEmptyTableCell(this);
                } else {
                    OdXML.addNumberTableCell(this, ct);
                }
            }
            OdXML.closeTableRow(this);
            for (Motcle child : motcle.getChildList()) {
                addMotcleRow(child);
            }
        }

        private void addFirstRow() throws IOException {
            OdXML.openTableRow(this);
            OdXML.addStringTableCell(this, "id");
            if (withIdalpha) {
                OdXML.addStringTableCell(this, "idalpha");
            }
            if (isBabelien) {
                OdXML.addStringTableCell(this, "lib");
            } else {
                OdXML.addStringTableCell(this, "lib_" + thesaurusLang.toString());
            }
            OdXML.addStringTableCell(this, "_total");
            for (Corpus corpus : corpusArray) {
                OdXML.addStringTableCell(this, corpus.getSubsetName());
            }
            OdXML.closeTableRow(this);
        }


        private class MotcleStats {

            private int total = 0;
            private final int[] byCorpus;

            MotcleStats() {
                byCorpus = new int[corpusArray.length];
            }

            void updateStats(Motcle motcle) {
                Fichotheque fichotheque = motcle.getFichotheque();
                total = 0;
                int length = corpusArray.length;
                for (int i = 0; i < length; i++) {
                    Croisements croisements = fichotheque.getCroisements(motcle, corpusArray[i]);
                    int ct = 0;
                    if (fichePredicate != null) {
                        for (Croisements.Entry entry : croisements.getEntryList()) {
                            if (fichePredicate.test((FicheMeta) entry.getSubsetItem())) {
                                ct++;
                            }
                        }
                    } else {
                        ct = croisements.getEntryList().size();
                    }
                    byCorpus[i] = ct;
                    total = total + ct;
                }
            }

            public int getTotal() {
                return total;
            }

            public int[] getParCroisement() {
                return byCorpus;
            }

        }

    }

}
