/* BdfServer_Get - Copyright (c) 2015-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.roles.FichePermission;
import fr.exemole.bdfserver.api.roles.Permission;
import fr.exemole.bdfserver.api.roles.Role;
import fr.exemole.bdfserver.api.subsettree.GroupNode;
import fr.exemole.bdfserver.api.subsettree.SubsetNode;
import fr.exemole.bdfserver.api.subsettree.SubsetTree;
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.RoleUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import net.fichotheque.Subset;
import net.fichotheque.SubsetKey;
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.xml.XMLUtils;
import net.mapeadores.util.xml.XMLWriter;


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

    private final String fileName;
    private final BdfServer bdfServer;
    private final BdfUser bdfUser;
    private final Role[] roleArray;
    private final MessageLocalisation messageLocalisation;
    private final SheetNameChecker sheetNameChecker;
    private final int defaultRoleIndex;


    public RoleOdsProducer(BdfServer bdfServer, BdfUser bdfUser, Role role, String fileName) {
        this.fileName = fileName;
        this.bdfServer = bdfServer;
        this.bdfUser = bdfUser;
        if (role != null) {
            roleArray = new Role[1];
            roleArray[0] = role;
        } else {
            List<Role> roleList = bdfServer.getPermissionManager().getRoleList();
            roleArray = roleList.toArray(new Role[roleList.size()]);
        }
        this.defaultRoleIndex = getDefaultRoleIndex(roleArray);
        this.messageLocalisation = bdfServer.getL10nManager().getMessageLocalisation(bdfUser);
        this.sheetNameChecker = new SheetNameChecker();
    }

    @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 {
        List<TableInfo> list = new ArrayList<TableInfo>();
        addTableInfo(SubsetKey.CATEGORY_CORPUS, list);
        addTableInfo(SubsetKey.CATEGORY_THESAURUS, list);
        addTableInfo(SubsetKey.CATEGORY_SPHERE, list);
        addTableInfo(SubsetKey.CATEGORY_ALBUM, list);
        addTableInfo(SubsetKey.CATEGORY_ADDENDA, list);
        OdsOptions odsOptions = BdfServerUtils.buildOdsOptions(bdfServer, "bdf://this/css/ods/role.css");
        OdZipEngine.run(outputStream, OdZip.spreadSheet()
                .stylesOdSource(OdUtils.toStyleOdSource(odsOptions.elementMaps(), true))
                .contentOdSource(new ContentOdSource(odsOptions, list))
                .settingsOdSource(getSettingsOdSource(list))
        );
    }

    private void addTableInfo(short subsetCategory, List<TableInfo> list) {
        String tableName = messageLocalisation.toString(BdfHtmlUtils.getSubsetCollectionLocKey(subsetCategory));
        if (tableName == null) {
            tableName = SubsetKey.categoryToString(subsetCategory);
        }
        tableName = sheetNameChecker.checkName(tableName);
        OdTableDef odTableDef = getTableDef(tableName, subsetCategory);
        List<PermissionBySubset> permissionBySubsetList = new ArrayList<PermissionBySubset>();
        checkPermission(permissionBySubsetList, bdfServer.getTreeManager().getSubsetTree(subsetCategory).getNodeList());
        if (!permissionBySubsetList.isEmpty()) {
            list.add(new TableInfo(subsetCategory, odTableDef, permissionBySubsetList));
        }
    }

    private OdTableDef getTableDef(String tableName, short subsetCategory) {
        return OdTableDefBuilder.init(tableName)
                .addStandard("Subsets", "SubsetTitle")
                .addStandards(roleArray.length, "Role")
                .toOdTableDef();
    }

    private void checkPermission(List<PermissionBySubset> permissionBySubsetList, List<SubsetTree.Node> nodeList) {
        int roleCount = roleArray.length;
        for (SubsetTree.Node node : nodeList) {
            if (node instanceof SubsetNode) {
                SubsetKey subsetKey = ((SubsetNode) node).getSubsetKey();
                Subset subset = bdfServer.getFichotheque().getSubset(subsetKey);
                if (subset != null) {
                    PermissionBySubset permissionBySubset = new PermissionBySubset(subset, roleCount);
                    for (int j = 0; j < roleCount; j++) {
                        Permission permission = roleArray[j].getPermissionBySubsetKey(subsetKey);
                        if (permission != null) {
                            permissionBySubset.setPermission(j, permission);
                        }
                    }
                    permissionBySubsetList.add(permissionBySubset);
                }
            } else if (node instanceof GroupNode) {
                checkPermission(permissionBySubsetList, ((GroupNode) node).getSubnodeList());
            }
        }
    }

    private OdSource getSettingsOdSource(List<TableInfo> tableInfoList) {
        List<TableSettings> settingsList = new ArrayList<TableSettings>();
        for (TableInfo tableInfo : tableInfoList) {
            settingsList.add(TableSettings.init(tableInfo.odTableDef.getTableName())
                    .fixedRows(1)
                    .fixedColumns(1));
        }
        return OdUtils.getSettingsOdSource(settingsList);
    }


    private static int getDefaultRoleIndex(Role[] roleArray) {
        int length = roleArray.length;
        for (int i = 0; i < length; i++) {
            if (RoleUtils.isDefaultRole(roleArray[i])) {
                return i;
            }
        }
        return -1;
    }


    private class ContentOdSource implements OdSource {

        private final OdsOptions odsOptions;
        private final List<TableInfo> tableInfoList;

        private ContentOdSource(OdsOptions odsOptions, List<TableInfo> tableInfoList) {
            this.odsOptions = odsOptions;
            this.tableInfoList = tableInfoList;
        }

        @Override
        public void writeStream(OutputStream outputStream) throws IOException {
            StyleManager styleManager = initStyleManager();
            TempStorageAppendable bodyBuffer = new TempStorageAppendable();
            ContentXMLPart xmlPart = new ContentXMLPart(XMLUtils.toXMLWriter(bodyBuffer, 3), styleManager);
            for (TableInfo tableInfo : tableInfoList) {
                xmlPart.addTable(tableInfo);
            }
            xmlPart.insertErrorLogTable(odsOptions.odLog(), sheetNameChecker.checkName("css log"));
            OdUtils.writeSpreadSheetDocumentContent(outputStream, bodyBuffer, styleManager);
        }

        private StyleManager initStyleManager() {
            StyleManagerBuilder styleManagerBuilder = new StyleManagerBuilder();
            styleManagerBuilder.setElementMaps(odsOptions.elementMaps());
            for (TableInfo tableInfo : tableInfoList) {
                styleManagerBuilder.addTableDef(tableInfo.odTableDef);
            }
            return styleManagerBuilder.toStyleManager();
        }

    }


    private class ContentXMLPart extends OdsXMLPart {

        private final Lang workingLang;

        private ContentXMLPart(XMLWriter xmlWriter, StyleManager styleManager) {
            super(xmlWriter, styleManager);
            this.workingLang = bdfUser.getWorkingLang();

        }

        public void addTable(TableInfo tableInfo) throws IOException {
            int roleCount = roleArray.length;
            tableStart(tableInfo.odTableDef.getTableName());
            rowStart();
            emptyCell();
            for (int i = 0; i < roleCount; i++) {
                Role role = roleArray[i];
                StringBuilder buf = new StringBuilder();
                String name = role.getName();
                if (RoleUtils.isDefaultRole(role)) {
                    buf.append(messageLocalisation.toString("_ label.global.role_default"));
                } else {
                    buf.append('[');
                    buf.append(name);
                    buf.append(']');
                }
                String roleTitle = role.getTitleLabels().seekLabelString(workingLang, null);
                if (roleTitle != null) {
                    buf.append(" ");
                    buf.append(roleTitle);
                }
                stringCell(buf.toString(), "RoleTitle");
            }
            rowEnd();
            for (PermissionBySubset couple : tableInfo.permissionBySubsetList) {
                Subset subset = couple.getSubset();
                StringBuilder buf = new StringBuilder();
                buf.append(FichothequeUtils.getTitle(subset, workingLang));
                buf.append(" [");
                buf.append(subset.getSubsetName());
                buf.append("]");
                rowStart();
                stringCell(buf.toString());
                for (int j = 0; j < roleCount; j++) {
                    boolean defaultRole = (j == defaultRoleIndex);
                    addPermission(couple.getPermission(j), defaultRole);
                }
                rowEnd();
            }
            tableEnd();
        }

        private void addPermission(Permission permission, boolean defaultRole) throws IOException {
            if (permission == null) {
                if (defaultRole) {
                    stringCell("S", "Standard");
                } else {
                    stringCell("", "Status");
                }
            } else {
                switch (permission.getLevel()) {
                    case Permission.ADMIN_LEVEL:
                        stringCell("A", "Admin");
                        break;
                    case Permission.CUSTOM_LEVEL:
                        String value = getCustomPermissionValue(permission.getCustomPermission());
                        stringCell(value, "Custom");
                        break;
                    case Permission.STANDARD_LEVEL:
                        stringCell("S", "Standard");
                        break;
                    case Permission.NONE_LEVEL:
                        stringCell("N", "None");
                        break;
                    default:
                        stringCell("", "Status");
                }
            }
        }

        private String getCustomPermissionValue(Permission.CustomPermission customPermission) {
            if (customPermission instanceof FichePermission) {
                return getFichePermissionValue((FichePermission) customPermission);
            } else {
                return "?";
            }
        }

        private String getFichePermissionValue(FichePermission fichePermission) {
            StringBuilder buf = new StringBuilder();
            buf.append("R=");
            buf.append(RoleUtils.fichePermissionTypeToString(fichePermission.read()));
            short write = fichePermission.write();
            if (write != FichePermission.NONE) {
                buf.append(" W=");
                buf.append(RoleUtils.fichePermissionTypeToString(write));
            }
            if (fichePermission.create()) {
                buf.append(" C");
            }
            return buf.toString();
        }

    }


    private static class TableInfo {

        private final short subsetCategory;
        private final OdTableDef odTableDef;
        private final List<PermissionBySubset> permissionBySubsetList;

        private TableInfo(short subsetCategory, OdTableDef odTableDef, List<PermissionBySubset> permissionBySubsetList) {
            this.subsetCategory = subsetCategory;
            this.odTableDef = odTableDef;
            this.permissionBySubsetList = permissionBySubsetList;
        }

    }


    private static class PermissionBySubset {

        private final Subset subset;
        private final Permission[] permissionArray;

        private PermissionBySubset(Subset subset, int count) {
            this.subset = subset;
            this.permissionArray = new Permission[count];
        }

        public Subset getSubset() {
            return subset;
        }

        public void setPermission(int i, Permission permission) {
            permissionArray[i] = permission;
        }

        public Permission getPermission(int i) {
            return permissionArray[i];
        }

    }

}
