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

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.managers.PermissionManager;
import net.fichotheque.permission.PermissionSummary;
import fr.exemole.bdfserver.api.roles.Role;
import fr.exemole.bdfserver.api.users.BdfUser;
import fr.exemole.bdfserver.html.BdfHtmlUtils;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import fr.exemole.bdfserver.tools.roles.PermissionSummaryBuilder;
import fr.exemole.bdfserver.tools.roles.RoleUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import net.fichotheque.FichothequeConstants;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.sphere.Sphere;
import net.fichotheque.utils.FichothequeUtils;
import net.mapeadores.opendocument.io.OdSource;
import net.mapeadores.opendocument.io.OdUtils;
import net.mapeadores.opendocument.io.OdZip;
import net.mapeadores.opendocument.io.OdZipEngine;
import net.mapeadores.opendocument.io.SheetNameChecker;
import net.mapeadores.opendocument.io.odtable.OdTableDef;
import net.mapeadores.opendocument.io.odtable.OdTableDefBuilder;
import net.mapeadores.opendocument.io.odtable.OdsOptions;
import net.mapeadores.opendocument.io.odtable.OdsXMLPart;
import net.mapeadores.opendocument.io.odtable.StyleManager;
import net.mapeadores.opendocument.io.odtable.StyleManagerBuilder;
import net.mapeadores.opendocument.io.odtable.TableSettings;
import net.mapeadores.util.io.StreamProducer;
import net.mapeadores.util.io.TempStorageAppendable;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.MessageLocalisation;
import net.mapeadores.util.mimetype.MimeTypeConstants;
import net.mapeadores.util.models.EmailCore;
import net.mapeadores.util.models.PersonCore;
import net.mapeadores.util.xml.XMLUtils;
import net.mapeadores.util.xml.XMLWriter;


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

    private final BdfServer bdfServer;
    private final BdfUser bdfUser;
    private final Sphere sphere;
    private final boolean all;
    private final SheetNameChecker sheetNameChecker;
    private final MessageLocalisation messageLocalisation;
    private final PermissionManager permissionManager;

    public SphereOdsProducer(BdfServer bdfServer, BdfUser bdfUser, Sphere sphere) {
        this.bdfServer = bdfServer;
        this.bdfUser = bdfUser;
        this.sphere = sphere;
        this.all = (sphere == null);
        this.messageLocalisation = bdfServer.getL10nManager().getMessageLocalisation(bdfUser);
        this.sheetNameChecker = new SheetNameChecker();
        this.permissionManager = bdfServer.getPermissionManager();
    }

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

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

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

    @Override
    public void writeStream(OutputStream outputStream) throws IOException {
        OdsOptions odsOptions = BdfServerUtils.buildOdsOptions(bdfServer, "bdf://this/css/ods/sphere.css");
        Tables tables = new Tables();
        OdZipEngine.run(outputStream, OdZip.spreadSheet()
                .stylesOdSource(OdUtils.toStyleOdSource(odsOptions.elementMaps(), true))
                .contentOdSource(new ContentOdSource(odsOptions, tables))
                .settingsOdSource(OdUtils.getSettingsOdSource(tables.getSettings()))
        );
    }

    private static String toAllStringKey(Redacteur redacteur) {
        return redacteur.getSubsetName() + "_" + redacteur.getLogin();
    }


    private class Tables {

        private final String mainTableName;
        private final List<CategoryInfo> categoryInfoList = new ArrayList<CategoryInfo>();
        private final TreeMap<String, Redacteur> activeMap = new TreeMap<String, Redacteur>();
        private final TreeMap<String, Redacteur> readonlyMap = new TreeMap<String, Redacteur>();
        private final TreeMap<String, Redacteur> inactiveMap = new TreeMap<String, Redacteur>();

        private Tables() {
            String tableName;
            if (all) {
                tableName = messageLocalisation.toString("_ label.selection.users");
                if (tableName == null) {
                    tableName = "_all";
                }
            } else {
                tableName = FichothequeUtils.getTitleWithKey(sphere, bdfUser.getWorkingLang());
            }
            this.mainTableName = sheetNameChecker.checkName(tableName);
            initCategory(SubsetKey.CATEGORY_CORPUS);
            initCategory(SubsetKey.CATEGORY_THESAURUS);
            initCategory(SubsetKey.CATEGORY_SPHERE);
            initCategory(SubsetKey.CATEGORY_ALBUM);
            initCategory(SubsetKey.CATEGORY_ADDENDA);
            init();
        }

        private void init() {
            if (all) {
                for (Sphere sphere : bdfServer.getFichotheque().getSphereList()) {
                    for (Redacteur redacteur : sphere.getRedacteurList()) {
                        Map<String, Redacteur> map = getMap(redacteur);
                        map.put(toAllStringKey(redacteur), redacteur);
                    }
                }
            } else {
                for (Redacteur redacteur : sphere.getRedacteurList()) {
                    Map<String, Redacteur> map = getMap(redacteur);
                    map.put(redacteur.getLogin(), redacteur);
                }
            }
        }

        public String getMainTableName() {
            return mainTableName;
        }

        public Collection<TableSettings> getSettings() {
            int fixedColumns;
            if (all) {
                fixedColumns = 2;
            } else {
                fixedColumns = 1;
            }
            List<TableSettings> list = new ArrayList<TableSettings>();
            list.add(TableSettings.init(mainTableName).fixedRows(1).fixedColumns(fixedColumns));
            for (CategoryInfo categoryInfo : categoryInfoList) {
                list.add(TableSettings.init(categoryInfo.tableName).fixedRows(1).fixedColumns(fixedColumns));
            }
            return list;
        }

        private void initCategory(short subsetCategory) {
            CategoryInfo categoryInfo = new CategoryInfo(subsetCategory);
            if (!categoryInfo.isEmpty()) {
                categoryInfoList.add(categoryInfo);
            }
        }

        private void addTableDefs(StyleManagerBuilder styleManagerBuilder) {
            int firstCols;
            if (all) {
                firstCols = 4;
            } else {
                firstCols = 3;
            }
            OdTableDef mainTableDef = OdTableDefBuilder.init(mainTableName)
                    .addStandards(firstCols)
                    .addStandard("Email")
                    .addStandard("Status")
                    .addStandard("Roles")
                    .toOdTableDef();
            styleManagerBuilder.addTableDef(mainTableDef);
            addRoleTableDef(styleManagerBuilder);
        }

        private void addRoleTableDef(StyleManagerBuilder styleManagerBuilder) {
            int firstCols;
            if (all) {
                firstCols = 2;
            } else {
                firstCols = 1;
            }
            for (CategoryInfo categoryInfo : categoryInfoList) {
                styleManagerBuilder.addTableDef(OdTableDefBuilder.init(categoryInfo.tableName)
                        .addStandards(firstCols)
                        .addStandards(categoryInfo.subsetArray.length, "Subset")
                        .toOdTableDef());
            }
        }

        private void addMain(XMLWriter xmlWriter, StyleManager styleManager) throws IOException {
            SphereXMLPart sphereXMLPart = new SphereXMLPart(xmlWriter, styleManager);
            sphereXMLPart.addTable(mainTableName, activeMap, readonlyMap, inactiveMap);
        }


        private void addRoles(XMLWriter xmlWriter, StyleManager styleManager) throws IOException {
            List<PermissionEntry> permissionList = new ArrayList<PermissionEntry>();
            for (Redacteur redacteur : activeMap.values()) {
                permissionList.add(new PermissionEntry(redacteur, PermissionSummaryBuilder.build(permissionManager, redacteur)));
            }
            RolesXMLPart xmlPart = new RolesXMLPart(xmlWriter, styleManager);
            for (CategoryInfo categoryInfo : categoryInfoList) {
                xmlPart.addTable(categoryInfo, permissionList);
            }
        }

        private TreeMap<String, Redacteur> getMap(Redacteur redacteur) {
            switch (redacteur.getStatus()) {
                case FichothequeConstants.INACTIVE_STATUS:
                    return inactiveMap;
                case FichothequeConstants.READONLY_STATUS:
                    return readonlyMap;
                default:
                    return activeMap;

            }
        }

    }


    private class ContentOdSource implements OdSource {

        private final OdsOptions odsOptions;
        private final Tables tables;

        private ContentOdSource(OdsOptions odsOptions, Tables tables) {
            this.odsOptions = odsOptions;
            this.tables = tables;
        }

        @Override
        public void writeStream(OutputStream outputStream) throws IOException {
            StyleManager styleManager = initStyleManager();
            TempStorageAppendable bodyBuffer = new TempStorageAppendable();
            XMLWriter xmlWriter = XMLUtils.toXMLWriter(bodyBuffer, 3);

            tables.addMain(xmlWriter, styleManager);
            tables.addRoles(xmlWriter, styleManager);
            OdUtils.writeSpreadSheetDocumentContent(outputStream, bodyBuffer, styleManager);
        }

        private StyleManager initStyleManager() {
            StyleManagerBuilder styleManagerBuilder = StyleManagerBuilder.init()
                    .setElementMaps(odsOptions.elementMaps());
            tables.addTableDefs(styleManagerBuilder);
            return styleManagerBuilder.toStyleManager();
        }

    }


    private class SphereXMLPart extends OdsXMLPart {


        private SphereXMLPart(XMLWriter xmlWriter, StyleManager styleManager) {
            super(xmlWriter, styleManager);
        }

        public void addTable(String name, TreeMap<String, Redacteur> activeMap, TreeMap<String, Redacteur> readonlyMap, TreeMap<String, Redacteur> inactiveMap) throws IOException {
            tableStart(name);
            addFirstRow();
            addMap(activeMap);
            addMap(readonlyMap);
            addMap(inactiveMap);
            tableEnd();
        }

        private void addMap(Map<String, Redacteur> map) throws IOException {
            for (Map.Entry<String, Redacteur> mapEntry : map.entrySet()) {
                Redacteur redacteur = mapEntry.getValue();
                addRedacteur(redacteur);
            }
        }

        private void addRedacteur(Redacteur redacteur) throws IOException {
            rowStart();
            stringCell(redacteur.getLogin(), "Login");
            if (all) {
                stringCell(redacteur.getSubsetName());
            }
            PersonCore personCore = redacteur.getPersonCore();
            stringCell(personCore.getSurname());
            stringCell(personCore.getForename());
            EmailCore emailCore = redacteur.getEmailCore();
            if (emailCore != null) {
                stringCell(emailCore.getAddrSpec());
            } else {
                emptyCell();
            }
            addStatus(redacteur);
            if (!redacteur.isInactive()) {
                StringBuilder buf = new StringBuilder();
                for (Role role : permissionManager.getRoleList(redacteur)) {
                    if (buf.length() > 0) {
                        buf.append(", ");
                    }
                    buf.append(role.getName());
                }
                stringCell(buf.toString());
            }
            rowEnd();
        }

        private void addFirstRow() throws IOException {
            rowStart();
            stringCell(messageLocalisation.toString("_ label.session.login"), "ColumnHeader");
            if (all) {
                stringCell(messageLocalisation.toString("_ label.session.sphere"), "ColumnHeader");
            }
            stringCell(messageLocalisation.toString("_ label.sphere.surname"), "ColumnHeader");
            stringCell(messageLocalisation.toString("_ label.sphere.forename"), "ColumnHeader");
            stringCell(messageLocalisation.toString("_ label.sphere.email"), "ColumnHeader");
            stringCell(messageLocalisation.toString("_ label.sphere.userstatus"), "ColumnHeader");
            stringCell(messageLocalisation.toString("_ title.sphere.rolelist"), "ColumnHeader");
            rowEnd();
        }

        private void addStatus(Redacteur redacteur) throws IOException {
            if (permissionManager.isAdmin(redacteur)) {
                stringCell("A", "Admin");
            } else {
                switch (redacteur.getStatus()) {
                    case FichothequeConstants.INACTIVE_STATUS:
                        stringCell("D", "Inactive");
                        break;
                    case FichothequeConstants.READONLY_STATUS:
                        stringCell("C", "Readonly");
                        break;
                    default:
                        stringCell("B", "User");
                }
            }
        }

    }


    private class RolesXMLPart extends OdsXMLPart {


        private RolesXMLPart(XMLWriter xmlWriter, StyleManager styleManager) {
            super(xmlWriter, styleManager);
        }

        private void addTable(CategoryInfo categoryInfo, List<PermissionEntry> entryList) throws IOException {
            Subset[] array = categoryInfo.subsetArray;
            tableStart(categoryInfo.tableName);
            addFirstRow(array);
            for (PermissionEntry permissionEntry : entryList) {
                rowStart();
                stringCell(permissionEntry.getRedacteur().getLogin(), "Login");
                if (all) {
                    stringCell(permissionEntry.getRedacteur().getSubsetName());
                }
                PermissionSummary permissionSummary = permissionEntry.getPermissionSummary();
                if (permissionSummary.isFichothequeAdmin()) {
                    for (Subset subset : array) {
                        stringCell("A", "Admin");
                    }
                } else {
                    for (Subset subset : array) {
                        addPermission(permissionSummary, subset.getSubsetKey());
                    }
                }
                rowEnd();
            }
            tableEnd();
        }

        private void addFirstRow(Subset[] array) throws IOException {
            Lang lang = bdfUser.getWorkingLang();
            rowStart();
            stringCell(messageLocalisation.toString("_ label.session.login"), "ColumnHeader");
            if (all) {
                stringCell(messageLocalisation.toString("_ label.session.sphere"), "ColumnHeader");
            }
            for (Subset subset : array) {
                stringCell(FichothequeUtils.getTitle(subset, lang));
            }
            rowEnd();
        }

        private void addPermission(PermissionSummary permissionSummary, SubsetKey subsetKey) throws IOException {
            if (permissionSummary.isSubsetAdmin(subsetKey)) {
                stringCell("A", "Admin");
                return;
            }
            int readLevel = permissionSummary.getReadLevel(subsetKey);
            if (readLevel == PermissionSummary.SUMMARYLEVEL_0_NONE) {
                stringCell("N", "None");
                return;
            }
            if (!subsetKey.isCorpusSubset()) {
                stringCell("S", "Standard");
                return;
            }
            int writeLevel = permissionSummary.getWriteLevel(subsetKey);
            boolean canCreate = permissionSummary.canCreate(subsetKey);
            if ((canCreate) && (readLevel == PermissionSummary.SUMMARYLEVEL_4_ALL) && (writeLevel == PermissionSummary.SUMMARYLEVEL_1_FICHE_OWN)) {
                stringCell("S", "Standard");
                return;
            }
            StringBuilder buf = new StringBuilder();
            buf.append("R=");
            buf.append(RoleUtils.permissionSummaryLevelToString(readLevel));
            if (writeLevel != PermissionSummary.SUMMARYLEVEL_0_NONE) {
                buf.append(" W=");
                buf.append(RoleUtils.permissionSummaryLevelToString(writeLevel));
            }
            if (canCreate) {
                buf.append(" C");
            }
            stringCell(buf.toString(), "Custom");
        }

    }


    private class CategoryInfo {

        private final short category;
        private final String tableName;
        private final Subset[] subsetArray;

        private CategoryInfo(short category) {
            this.category = category;
            this.subsetArray = FichothequeUtils.toSubsetArray(bdfServer.getFichotheque(), category);
            String locKey = BdfHtmlUtils.getSubsetCollectionLocKey(category);
            String title = messageLocalisation.toString(locKey);
            if (title == null) {
                title = SubsetKey.categoryToString(category);
            }
            this.tableName = sheetNameChecker.checkName(title);
        }

        public boolean isEmpty() {
            return (subsetArray.length == 0);
        }


    }


    private static class PermissionEntry {

        private final Redacteur redacteur;
        private final PermissionSummary permissionSummary;

        private PermissionEntry(Redacteur redacteur, PermissionSummary permissionSummary) {
            this.redacteur = redacteur;
            this.permissionSummary = permissionSummary;
        }

        public Redacteur getRedacteur() {
            return redacteur;
        }

        public PermissionSummary getPermissionSummary() {
            return permissionSummary;
        }

    }


}
