/* BdfServer - Copyright (c) 2020-2023 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.uml;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.managers.UiManager;
import fr.exemole.bdfserver.api.ui.SubsetIncludeUi;
import fr.exemole.bdfserver.api.ui.UiComponent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.include.ExtendedIncludeKey;
import net.fichotheque.utils.FichothequeUtils;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.MessageLocalisation;
import net.mapeadores.util.uml.PumlUtils;


/**
 *
 * @author Vincent Calame
 */
public class CorpusPumlWriter {

    private final static int SATELLITE = 1;
    private final static int MASTER = 2;
    private final BdfServer bdfServer;
    private final Lang lang;
    private final MessageLocalisation messageLocalisation;
    private final Corpus corpus;
    private final SortedMap<SubsetKey, LinkInfo> infoMap = new TreeMap<SubsetKey, LinkInfo>();
    private final List<LinkInfo> thesaurusList = new ArrayList<LinkInfo>();
    private final List<LinkInfo> corpusList = new ArrayList<LinkInfo>();
    private final List<LinkInfo> satelliteList = new ArrayList<LinkInfo>();

    public CorpusPumlWriter(BdfServer bdfServer, Lang lang, MessageLocalisation messageLocalisation, Corpus corpus) {
        this.bdfServer = bdfServer;
        this.lang = lang;
        this.messageLocalisation = messageLocalisation;
        this.corpus = corpus;
        scanLinkInfo();
    }

    public void write(Appendable appendable) throws IOException {
        SubsetKey corpusKey = corpus.getSubsetKey();
        PumlUtils.start(appendable);
        PumlUtils.title(appendable, FichothequeUtils.getTitle(corpus, lang));
        PumlUtils.hideEmpties(appendable);
        PumlUtils.orthoLineType(appendable);
        BdfPumlUtils.buildCorpusUmlClass(corpus, lang, messageLocalisation).write(appendable, 0);
        for (LinkInfo linkInfo : infoMap.values()) {
            Subset subset = linkInfo.subset;
            if (!subset.getSubsetKey().equals(corpusKey)) {
                BdfPumlUtils.buildUmlClass(subset, lang).write(appendable, 0);
            }
        }
        appendable.append("\n");
        for (LinkInfo linkInfo : infoMap.values()) {
            linkInfo.write(appendable, corpusKey);
        }
        writeHiddenLinks(appendable);
        PumlUtils.end(appendable);
    }

    private void scanLinkInfo() {
        Subset masterSubset = corpus.getMasterSubset();
        List<Corpus> satelliteCorpusList = corpus.getSatelliteCorpusList();
        if (masterSubset != null) {
            LinkInfo linkInfo = new LinkInfo(masterSubset, MASTER);
            infoMap.put(masterSubset.getSubsetKey(), linkInfo);
        }
        for (Corpus satelliteCorpus : satelliteCorpusList) {
            LinkInfo linkInfo = new LinkInfo(satelliteCorpus, SATELLITE);
            infoMap.put(satelliteCorpus.getSubsetKey(), linkInfo);
        }
        UiManager uiManager = bdfServer.getUiManager();
        for (UiComponent uiComponent : uiManager.getMainUiComponents(corpus).getUiComponentList()) {
            addLinkInfo(uiComponent, false);
        }
        for (Corpus satelliteCorpus : satelliteCorpusList) {
            for (UiComponent uiComponent : uiManager.getMainUiComponents(satelliteCorpus).getUiComponentList()) {
                addLinkInfo(uiComponent, true);
            }
        }
        for (LinkInfo linkInfo : infoMap.values()) {
            if (!linkInfo.isSpecial()) {
                switch (linkInfo.getSubsetKey().getCategory()) {
                    case SubsetKey.CATEGORY_CORPUS:
                        corpusList.add(linkInfo);
                        break;
                    case SubsetKey.CATEGORY_THESAURUS:
                        thesaurusList.add(linkInfo);
                        break;
                }
            } else if (linkInfo.special == SATELLITE) {
                satelliteList.add(linkInfo);
            }
        }
    }

    private void addLinkInfo(UiComponent uiComponent, boolean master) {
        if (!(uiComponent instanceof SubsetIncludeUi)) {
            return;
        }
        ExtendedIncludeKey eik = ((SubsetIncludeUi) uiComponent).getExtendedIncludeKey();
        if (eik.isMaster() != master) {
            return;
        }
        SubsetKey otherKey = eik.getSubsetKey();
        String mode = eik.getMode();
        LinkInfo linkInfo = infoMap.get(otherKey);
        if (linkInfo == null) {
            Subset otherSubset = bdfServer.getFichotheque().getSubset(otherKey);
            if (otherSubset != null) {
                linkInfo = new LinkInfo(otherSubset);
                linkInfo.addMode(mode);
            }
            infoMap.put(otherKey, linkInfo);
        } else {
            linkInfo.addMode(mode);
        }
    }

    private void writeHiddenLinks(Appendable appendable) throws IOException {
        if (!thesaurusList.isEmpty()) {
            if (!satelliteList.isEmpty()) {
                writeHiddenLinks(appendable, satelliteList, thesaurusList);
            }
        }
        if (!corpusList.isEmpty()) {
            if (!thesaurusList.isEmpty()) {
                writeHiddenLinks(appendable, thesaurusList, corpusList);
            } else if (!satelliteList.isEmpty()) {
                writeHiddenLinks(appendable, satelliteList, corpusList);
            }
        }
    }

    private void writeHiddenLinks(Appendable appendable, List<LinkInfo> upList, List<LinkInfo> downList) throws IOException {
        int upSize = upList.size();
        int downSize = downList.size();
        if (upSize == downSize) {
            for (int i = 0; i < upSize; i++) {
                LinkInfo upInfo = upList.get(i);
                LinkInfo downInfo = downList.get(i);
                writeHidden(appendable, upInfo, downInfo);
            }
        } else if (upSize > downSize) {
            int diff = ((upSize - downSize) / 2);
            for (int i = 0; i < downSize; i++) {
                LinkInfo upInfo = upList.get(i + diff);
                LinkInfo downInfo = downList.get(i);
                writeHidden(appendable, upInfo, downInfo);
            }
        } else {
            int upIndex = 0;
            int count = 0;
            int quotient = downSize / upSize;
            int reste = downSize % upSize;
            for (int i = 0; i < downSize; i++) {
                LinkInfo upInfo = upList.get(upIndex);
                LinkInfo downInfo = downList.get(i);
                writeHidden(appendable, upInfo, downInfo);
                count++;
                if (count == quotient) {
                    if (reste > 0) {
                        reste--;
                        count--;
                    } else {
                        upIndex++;
                        count = 0;
                    }
                }
            }
        }
    }

    private void writeHidden(Appendable appendable, LinkInfo upInfo, LinkInfo downInfo) throws IOException {
        appendable.append(upInfo.getSubsetKey().getKeyString());
        appendable.append(" -[hidden]- ");
        appendable.append(downInfo.getSubsetKey().getKeyString());
        appendable.append("\n");
    }

    public static String toString(BdfServer bdfServer, Lang lang, MessageLocalisation messageLocalisation, Corpus corpus) {
        CorpusPumlWriter corpusPumlWriter = new CorpusPumlWriter(bdfServer, lang, messageLocalisation, corpus);
        try {
            StringBuilder buf = new StringBuilder();
            corpusPumlWriter.write(buf);
            return buf.toString();
        } catch (IOException ioe) {
            throw new ShouldNotOccurException();
        }
    }


    private static class LinkInfo {

        private final Subset subset;
        private final SortedSet<String> modeSet = new TreeSet<String>();
        private final int special;

        private LinkInfo(Subset subset) {
            this.subset = subset;
            this.special = 0;
        }

        private LinkInfo(Subset subset, int special) {
            this.subset = subset;
            this.special = special;
        }

        private boolean isSpecial() {
            return (special != 0);
        }

        private void addMode(String mode) {
            modeSet.add(mode);
        }

        private SubsetKey getSubsetKey() {
            return subset.getSubsetKey();
        }

        private void write(Appendable appendable, SubsetKey corpusKey) throws IOException {
            SubsetKey subsetKey = subset.getSubsetKey();
            writeSpecial(appendable, corpusKey, subsetKey);
            for (String mode : modeSet) {
                appendable.append(corpusKey.getKeyString());
                writeRelation(appendable, corpusKey, subsetKey);
                appendable.append(subsetKey.getKeyString());
                if (mode.length() > 0) {
                    appendable.append(" : ");
                    appendable.append(mode);
                }
                appendable.append("\n");
            }
        }

        private void writeRelation(Appendable appendable, SubsetKey mainCorpusKey, SubsetKey subsetKey) throws IOException {
            if (special != 0) {
                if (subsetKey.isThesaurusSubset()) {
                    appendable.append(" <-down- ");
                } else {
                    switch (special) {
                        case SATELLITE:
                            appendable.append(" -down- ");
                            break;
                        case MASTER:
                            appendable.append(" -up- ");
                            break;
                        default:
                            appendable.append(" -- ");
                    }
                }
            } else {
                appendable.append(BdfPumlUtils.getRelation(subsetKey.getCategory()));
            }
        }

        private void writeSpecial(Appendable appendable, SubsetKey mainCorpusKey, SubsetKey subsetKey) throws IOException {
            if (special == 0) {
                return;
            }
            appendable.append(mainCorpusKey.getKeyString());
            switch (special) {
                case SATELLITE:
                    appendable.append(" *-down- ");
                    break;
                case MASTER:
                    appendable.append(" -up-* ");
                    break;
                default:
                    appendable.append(" -- ");
            }
            appendable.append(subsetKey.getKeyString());
            appendable.append("\n");
        }

    }

}
