/* BdfServer - Copyright (c) 2010-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.zip;

import fr.exemole.bdfserver.api.BdfExtensionReference;
import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.groups.GroupDef;
import fr.exemole.bdfserver.api.managers.BalayageManager;
import fr.exemole.bdfserver.api.managers.ExtensionManager;
import fr.exemole.bdfserver.api.managers.GroupManager;
import fr.exemole.bdfserver.api.managers.PasswordManager;
import fr.exemole.bdfserver.api.managers.PermissionManager;
import fr.exemole.bdfserver.api.managers.PolicyManager;
import fr.exemole.bdfserver.api.managers.ScrutariExportManager;
import fr.exemole.bdfserver.api.managers.SelectionManager;
import fr.exemole.bdfserver.api.managers.SqlExportManager;
import fr.exemole.bdfserver.api.managers.TableExportManager;
import fr.exemole.bdfserver.api.managers.TransformationManager;
import fr.exemole.bdfserver.api.managers.UiManager;
import fr.exemole.bdfserver.api.policies.UserAllow;
import fr.exemole.bdfserver.api.roles.Role;
import fr.exemole.bdfserver.api.storage.BdfUserStorage;
import fr.exemole.bdfserver.api.subsettree.SubsetTree;
import fr.exemole.bdfserver.api.ui.UiComponents;
import fr.exemole.bdfserver.api.users.BdfUserPrefs;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import fr.exemole.bdfserver.tools.storage.StorageUtils;
import fr.exemole.bdfserver.tools.subsettree.TreeFilterEngine;
import fr.exemole.bdfserver.xml.configuration.LangConfigurationXMLPart;
import fr.exemole.bdfserver.xml.groups.GroupDefXMLPart;
import fr.exemole.bdfserver.xml.policies.ThesaurusPolicyXMLPart;
import fr.exemole.bdfserver.xml.policies.UserAllowXMLPart;
import fr.exemole.bdfserver.xml.roles.RedacteurListXMLPart;
import fr.exemole.bdfserver.xml.roles.RoleDefXMLPart;
import fr.exemole.bdfserver.xml.subsettree.TreeXMLPart;
import fr.exemole.bdfserver.xml.transformation.TemplateDefXMLPart;
import fr.exemole.bdfserver.xml.ui.UiComponentsXMLPart;
import fr.exemole.bdfserver.xml.users.BdfUserPrefsXMLPart;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import net.fichotheque.Fichotheque;
import net.fichotheque.Subset;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.addenda.Addenda;
import net.fichotheque.addenda.Document;
import net.fichotheque.addenda.Version;
import net.fichotheque.album.Album;
import net.fichotheque.album.AlbumConstants;
import net.fichotheque.album.Illustration;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.FicheAPI;
import net.fichotheque.junction.JunctionKey;
import net.fichotheque.eligibility.SubsetEligibility;
import net.fichotheque.exportation.balayage.BalayageContentDescription;
import net.fichotheque.exportation.balayage.BalayageDescription;
import net.fichotheque.exportation.scrutari.ScrutariExportDef;
import net.fichotheque.exportation.sql.SqlExportDef;
import net.fichotheque.exportation.table.TableExportContentDescription;
import net.fichotheque.exportation.table.TableExportDescription;
import net.fichotheque.exportation.transformation.TemplateContentDescription;
import net.fichotheque.exportation.transformation.TemplateDescription;
import net.fichotheque.exportation.transformation.TemplateKey;
import net.fichotheque.exportation.transformation.TransformationDescription;
import net.fichotheque.exportation.transformation.TransformationKey;
import net.fichotheque.selection.SelectionDef;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.sphere.Sphere;
import net.fichotheque.thesaurus.Motcle;
import net.fichotheque.thesaurus.Thesaurus;
import net.fichotheque.thesaurus.policies.DynamicEditPolicy;
import net.fichotheque.thesaurus.policies.PolicyProvider;
import net.fichotheque.utils.EligibilityUtils;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.xml.defs.ScrutariExportDefXMLPart;
import net.fichotheque.xml.defs.SelectionDefXMLPart;
import net.fichotheque.xml.defs.SqlExportDefXMLPart;
import net.fichotheque.xml.defs.TableExportDefXMLPart;
import net.fichotheque.xml.storage.AddendaMetadataXMLPart;
import net.fichotheque.xml.storage.AlbumMetadataXMLPart;
import net.fichotheque.xml.storage.AttributesStorageXMLPart;
import net.fichotheque.xml.storage.CorpusMetadataXMLPart;
import net.fichotheque.xml.storage.JunctionStorageXMLPart;
import net.fichotheque.xml.storage.DocumentStorageXMLPart;
import net.fichotheque.xml.storage.FicheStorageXMLPart;
import net.fichotheque.xml.storage.FichothequeMetadataXMLPart;
import net.fichotheque.xml.storage.MotcleStorageXMLPart;
import net.fichotheque.xml.storage.RedacteurStorageXMLPart;
import net.fichotheque.xml.storage.SphereListXMLPart;
import net.fichotheque.xml.storage.SphereMetadataXMLPart;
import net.fichotheque.xml.storage.ThesaurusMetadataXMLPart;
import net.fichotheque.xml.storage.ThesaurusTreeXMLPart;
import net.mapeadores.util.io.DocStream;
import net.mapeadores.util.io.IOUtils;
import net.mapeadores.util.io.ResourceFolder;
import net.mapeadores.util.io.ResourceStorage;
import net.mapeadores.util.text.RelativePath;
import net.mapeadores.util.xml.AppendableXMLWriter;
import net.mapeadores.util.xml.XMLUtils;
import net.fichotheque.junction.Junction;
import net.fichotheque.junction.Junctions;


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

    private final SubsetEligibility subsetEligibility;
    private final BdfServer bdfServer;
    private final Fichotheque fichotheque;
    private final Subset[] subsetArray;
    private final String fichothequeRoot = "var/bdfdata/fichotheque/";
    private final String confRoot = "var/bdfdata/conf/";
    private final String usersRoot = "var/bdfdata/users/";
    private final String rscdataRoot = "var/rscdata/";
    private final String transformationdataRoot = "var/transformationdata/";
    private final String tableexportdataRoot = "var/tableexportdata/";
    private final String balayagedataRoot = "var/balayagedata/";
    private final String extdataRoot = "var/extdata/";
    private ZipOutputStream zipOutputStream;
    private BufferedWriter bufWriter;

    public BdfServerZip(BdfServer bdfServer, SubsetEligibility subsetEligibility) {
        this.bdfServer = bdfServer;
        if (subsetEligibility == null) {
            this.subsetEligibility = EligibilityUtils.ALL_SUBSET_ELIGIBILITY;
        } else {
            this.subsetEligibility = subsetEligibility;
        }
        this.fichotheque = bdfServer.getFichotheque();
        subsetArray = FichothequeUtils.getSortedSubsetArray(fichotheque, subsetEligibility);
    }

    public void zip(ZipOutputStream zipOutputStream) throws IOException {
        this.bufWriter = new BufferedWriter(new OutputStreamWriter(zipOutputStream, "UTF-8"));
        this.zipOutputStream = zipOutputStream;
        zipFichotheque();
        zipConf();
        zipUsers();
        zipRscdata();
        zipTransformationdata();
        zipTableexportdata();
        zipBalayagedata();
        zipExtdata();
    }

    private void zipRscdata() throws IOException {
        ResourceStorage resourceStorage = bdfServer.getVarResourceStorage();
        ResourceFolder root = resourceStorage.getRoot();
        zipResources(resourceStorage, root, RelativePath.EMPTY);
    }

    private void zipResources(ResourceStorage resourceStorage, ResourceFolder folder, RelativePath folderPath) throws IOException {
        for (String resourceName : folder.getResourceNameList()) {
            RelativePath relativePath = folderPath.buildChild(resourceName);
            zipOutputStream.putNextEntry(new ZipEntry(rscdataRoot + relativePath.toString()));
            DocStream docStream = resourceStorage.getResourceDocStream(relativePath);
            try (InputStream is = docStream.getInputStream()) {
                IOUtils.copy(is, zipOutputStream);
            }
        }
        for (ResourceFolder subfolder : folder.getSubfolderList()) {
            zipResources(resourceStorage, subfolder, folderPath.buildChild(subfolder.getName()));
        }
    }

    private void zipTableexportdata() throws IOException {
        TableExportManager tableExportManager = bdfServer.getTableExportManager();
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
        TableExportDefXMLPart xmlPart = new TableExportDefXMLPart(xmlWriter);
        for (TableExportDescription tableExportDescription : tableExportManager.getTableExportDescriptionList()) {
            List<TableExportContentDescription> filteredList = filter(tableExportDescription);
            if (!filteredList.isEmpty()) {
                String tableExportName = tableExportDescription.getName();
                String tableExportRoot = tableexportdataRoot + tableExportName + "/";
                zipOutputStream.putNextEntry(new ZipEntry(tableExportRoot + "_def.xml"));
                xmlWriter.appendXMLDeclaration();
                xmlPart.addTableExportDef(tableExportDescription.getTableExportDef());
                bufWriter.flush();
                for (TableExportContentDescription tableExportContentDescription : filteredList) {
                    String contentPath = tableExportContentDescription.getPath();
                    zipOutputStream.putNextEntry(new ZipEntry(tableExportRoot + contentPath));
                    bufWriter.write(tableExportManager.getTableExportContent(tableExportName, contentPath));
                    bufWriter.flush();
                }
            }
        }
    }

    private void zipBalayagedata() throws IOException {
        BalayageManager balayageManager = bdfServer.getBalayageManager();
        for (BalayageDescription balayageDescription : balayageManager.getBalayageDescriptionList()) {
            zipBalayageSubdir(balayageManager, balayageDescription, "");
            zipBalayageSubdir(balayageManager, balayageDescription, "extraction");
            zipBalayageSubdir(balayageManager, balayageDescription, "xslt");
        }
    }

    private void zipBalayageSubdir(BalayageManager balayageManager, BalayageDescription balayageDescription, String subDir) throws IOException {
        String name = balayageDescription.getName();
        for (BalayageContentDescription contentDescription : balayageDescription.getBalayageContentDescriptionList(subDir)) {
            String path = contentDescription.getPath();
            zipOutputStream.putNextEntry(new ZipEntry(balayagedataRoot + name + "/" + path));
            String content = balayageManager.getBalayageContent(name, path);
            if (content == null) {
                content = "";
            }
            bufWriter.write(content);
            bufWriter.flush();
        }
    }

    private void zipTransformationdata() throws IOException {
        zipTransformation(TransformationKey.COMPILATION_INSTANCE);
        zipTransformation(TransformationKey.FORMAT_INSTANCE);
        zipTransformation(TransformationKey.SECTION_INSTANCE);
        zipTransformation(TransformationKey.STATTHESAURUS_INSTANCE);
        zipTransformation(TransformationKey.INVERSETHESAURUS_INSTANCE);
        zipTransformation(TransformationKey.MEMENTO_INSTANCE);
        for (Corpus corpus : fichotheque.getCorpusList()) {
            if (subsetEligibility.accept(corpus)) {
                zipTransformation(new TransformationKey(corpus.getSubsetKey()));
            }
        }
    }

    private void zipTransformation(TransformationKey transformationKey) throws IOException {
        TransformationDescription transformationDescription = bdfServer.getTransformationManager().getTransformationDescription(transformationKey);
        for (TemplateDescription templateDescription : transformationDescription.getSimpleTemplateDescriptionList()) {
            zipTemplate(templateDescription);
        }
        for (TemplateDescription templateDescription : transformationDescription.getStreamTemplateDescriptionList()) {
            zipTemplate(templateDescription);
        }

    }

    private void zipTemplate(TemplateDescription templateDescription) throws IOException {
        TransformationManager transformationManager = bdfServer.getTransformationManager();
        TemplateKey templateKey = templateDescription.getTemplateKey();
        String templateRoot = transformationdataRoot + templateKey.getTransformationKey() + "/" + templateKey.getName();
        if (templateKey.isStreamTemplate()) {
            templateRoot = templateRoot + "." + templateKey.getExtension() + "/";
        } else {
            templateRoot = templateRoot + "/";
        }
        zipOutputStream.putNextEntry(new ZipEntry(templateRoot + "_type"));
        bufWriter.append(templateDescription.getType());
        bufWriter.flush();
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
        TemplateDefXMLPart xmlPart = new TemplateDefXMLPart(xmlWriter);
        zipOutputStream.putNextEntry(new ZipEntry(templateRoot + "_def.xml"));
        xmlWriter.appendXMLDeclaration();
        xmlPart.addTemplateDef(templateDescription.getTemplateDef());
        bufWriter.flush();
        for (TemplateContentDescription templateContentDescription : templateDescription.getTemplateContentDescriptionList()) {
            zipOutputStream.putNextEntry(new ZipEntry(templateRoot + templateContentDescription.getPath()));
            try (InputStream is = transformationManager.getTemplateStorageContent(templateKey, templateContentDescription.getPath()).getInputStream()) {
                IOUtils.copy(is, zipOutputStream);
            }
        }
    }

    private void zipExtdata() throws IOException {
        ExtensionManager extensionManager = bdfServer.getExtensionManager();
        for (BdfExtensionReference bdfExtensionReference : extensionManager.getBdfExtensionReferenceList()) {
            File dir = extensionManager.getBdfExtensionStorage().getDataDirectory(bdfExtensionReference.getRegistrationName());
            if (dir.exists()) {
                zipDir(dir, extdataRoot);
            }
        }
    }

    private void zipDir(File dir, String rootPath) throws IOException {
        String dirPath = rootPath + dir.getName() + "/";
        for (File f : dir.listFiles()) {
            if (f.isDirectory()) {
                zipDir(f, dirPath);
            } else {
                zipOutputStream.putNextEntry(new ZipEntry(dirPath + f.getName()));
                try (InputStream is = new FileInputStream(f)) {
                    IOUtils.copy(is, zipOutputStream);
                }
            }
        }
    }

    private void zipUsers() throws IOException {
        BdfUserStorage bdfUserStorage = bdfServer.getBdfUserStorage();
        for (Subset subset : subsetArray) {
            if (subset instanceof Sphere) {
                Sphere sphere = (Sphere) subset;
                String sphereName = sphere.getSubsetName();
                for (Redacteur redacteur : sphere.getRedacteurList()) {
                    BdfUserPrefs bdfUserPrefs = bdfUserStorage.getBdfUserPrefs(redacteur);
                    int id = redacteur.getId();
                    if (bdfUserPrefs != null) {
                        zipOutputStream.putNextEntry(new ZipEntry(usersRoot + sphereName + "/r." + BdfServerUtils.getMillier(id) + "/" + id + "/prefs.xml"));
                        AppendableXMLWriter prefsXmlWriter = XMLUtils.toXMLWriter(bufWriter);
                        prefsXmlWriter.appendXMLDeclaration();
                        BdfUserPrefsXMLPart bdfUserPrefsXMLPart = new BdfUserPrefsXMLPart(prefsXmlWriter);
                        bdfUserPrefsXMLPart.addPrefs(bdfUserPrefs);
                        bufWriter.flush();
                    }
                }
            }
        }
    }

    private void zipConf() throws IOException {
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter, 0);
        zipOutputStream.putNextEntry(new ZipEntry(confRoot + "lang-configuration.xml"));
        LangConfigurationXMLPart langConfigurationXMLPart = new LangConfigurationXMLPart(xmlWriter);
        xmlWriter.appendXMLDeclaration();
        langConfigurationXMLPart.addLangConfiguration(bdfServer.getLangConfiguration());
        bufWriter.flush();
        PasswordManager passwordManager = bdfServer.getPasswordManager();
        String[] passwordArray = passwordManager.toPasswordArray();
        if (passwordArray.length > 0) {
            zipOutputStream.putNextEntry(new ZipEntry(confRoot + "passwd"));
            StorageUtils.writePasswordArray(bufWriter, passwordArray);
            bufWriter.flush();
        }
        List<String> activeExtensionList = BdfServerUtils.toActiveExtensionList(bdfServer.getExtensionManager().getBdfExtensionReferenceList());
        if (!activeExtensionList.isEmpty()) {
            zipOutputStream.putNextEntry(new ZipEntry(confRoot + "extensions"));
            StorageUtils.writeExtensionList(bufWriter, activeExtensionList);
            bufWriter.flush();
        }
        zipGroups();
        zipPolicies();
        zipRedacteurLists();
        zipRoles();
        zipSelection();
        zipSqlExport();
        zipScrutariExport();
        zipSubsetTree(SubsetKey.CATEGORY_CORPUS, xmlWriter);
        zipSubsetTree(SubsetKey.CATEGORY_THESAURUS, xmlWriter);
        zipSubsetTree(SubsetKey.CATEGORY_SPHERE, xmlWriter);
        zipSubsetTree(SubsetKey.CATEGORY_ALBUM, xmlWriter);
        zipSubsetTree(SubsetKey.CATEGORY_ADDENDA, xmlWriter);
        UiManager uiManager = bdfServer.getUiManager();
        UiComponentsXMLPart uiComponentsXMLPart = new UiComponentsXMLPart(xmlWriter);
        for (Subset subset : subsetArray) {
            if (subset instanceof Corpus) {
                Corpus corpus = (Corpus) subset;
                UiComponents uiComponents = uiManager.getMainUiComponents(corpus);
                zipOutputStream.putNextEntry(new ZipEntry(confRoot + "ui/corpus/" + corpus.getSubsetName() + "/_main.xml"));
                xmlWriter.appendXMLDeclaration();
                uiComponentsXMLPart.appendUiComponents(uiComponents);
                bufWriter.flush();
            }
        }
    }

    private void zipGroups() throws IOException {
        GroupManager groupManager = bdfServer.getGroupManager();
        List<GroupDef> groupDefList = groupManager.getGroupDefList();
        if (!groupDefList.isEmpty()) {
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
            GroupDefXMLPart groupDefXMLPart = new GroupDefXMLPart(xmlWriter);
            for (GroupDef groupDef : groupDefList) {
                zipOutputStream.putNextEntry(new ZipEntry(confRoot + "groups/" + groupDef.getName() + ".xml"));
                xmlWriter.appendXMLDeclaration();
                groupDefXMLPart.addGroupDef(groupDef);
                bufWriter.flush();
            }
        }
    }

    private void zipPolicies() throws IOException {
        PolicyManager policyManager = bdfServer.getPolicyManager();
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
        UserAllowXMLPart userAllowXMLPart = new UserAllowXMLPart(xmlWriter);
        UserAllow userAllow = policyManager.getUserAllow();
        zipOutputStream.putNextEntry(new ZipEntry(confRoot + "policies/user-allow.xml"));
        xmlWriter.appendXMLDeclaration();
        userAllowXMLPart.addUserAllow(userAllow);
        bufWriter.flush();
        PolicyProvider policyProvider = policyManager.getPolicyProvider();
        ThesaurusPolicyXMLPart thesaurusPolicyXMLPart = new ThesaurusPolicyXMLPart(xmlWriter);
        for (Subset subset : subsetArray) {
            if (subset instanceof Thesaurus) {
                DynamicEditPolicy dynamicEditPolicy = policyProvider.getDynamicEditPolicy((Thesaurus) subset);
                if (!dynamicEditPolicy.equals(DynamicEditPolicy.NONE_TYPE)) {
                    SubsetKey subsetKey = subset.getSubsetKey();
                    zipOutputStream.putNextEntry(new ZipEntry(confRoot + "policies/" + subsetKey + ".xml"));
                    xmlWriter.appendXMLDeclaration();
                    thesaurusPolicyXMLPart.addThesaurusPolicy(subsetKey, dynamicEditPolicy);
                    bufWriter.flush();
                }
            }
        }
    }

    private void zipRedacteurLists() throws IOException {
        PermissionManager permissionManager = bdfServer.getPermissionManager();
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
        RedacteurListXMLPart redacteurListXMLPart = new RedacteurListXMLPart(xmlWriter);
        List<Redacteur> redacteurList = permissionManager.getAdminRedacteurList();
        zipOutputStream.putNextEntry(new ZipEntry(confRoot + "redacteurlists/admin.xml"));
        xmlWriter.appendXMLDeclaration();
        redacteurListXMLPart.addRedacteurList(redacteurList);
        bufWriter.flush();
        List<Role> roleList = permissionManager.getRoleList();
        for (Role role : roleList) {
            redacteurList = permissionManager.getRedacteurList(role);
            zipOutputStream.putNextEntry(new ZipEntry(confRoot + "redacteurlists/role_" + role.getName() + ".xml"));
            xmlWriter.appendXMLDeclaration();
            redacteurListXMLPart.addRedacteurList(redacteurList);
            bufWriter.flush();
        }
    }

    private void zipRoles() throws IOException {
        List<Role> roleList = bdfServer.getPermissionManager().getRoleList();
        if (!roleList.isEmpty()) {
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
            RoleDefXMLPart xmlPart = new RoleDefXMLPart(xmlWriter);
            for (Role role : roleList) {
                zipOutputStream.putNextEntry(new ZipEntry(confRoot + "roles/" + role.getName() + ".xml"));
                xmlWriter.appendXMLDeclaration();
                xmlPart.addRoleDef(role, subsetEligibility);
                bufWriter.flush();
            }
        }
    }

    private void zipSelection() throws IOException {
        SelectionManager selectionManager = bdfServer.getSelectionManager();
        List<SelectionDef> selectionDefList = selectionManager.getSelectionDefList();
        if (!selectionDefList.isEmpty()) {
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
            SelectionDefXMLPart xmlPart = new SelectionDefXMLPart(xmlWriter);
            for (SelectionDef selectionDef : selectionDefList) {
                zipOutputStream.putNextEntry(new ZipEntry(confRoot + "selection/" + selectionDef.getName() + ".xml"));
                xmlWriter.appendXMLDeclaration();
                xmlPart.addSelectionDef(selectionDef);
                bufWriter.flush();
            }
        }
    }

    private void zipSqlExport() throws IOException {
        SqlExportManager sqlExportManager = bdfServer.getSqlExportManager();
        List<SqlExportDef> sqlExportDefList = sqlExportManager.getSqlExportDefList();
        if (!sqlExportDefList.isEmpty()) {
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
            SqlExportDefXMLPart xmlPart = new SqlExportDefXMLPart(xmlWriter);
            for (SqlExportDef sqlExportDef : sqlExportDefList) {
                zipOutputStream.putNextEntry(new ZipEntry(confRoot + "sqlexport/" + sqlExportDef.getName() + ".xml"));
                xmlWriter.appendXMLDeclaration();
                xmlPart.addSqlExportDef(sqlExportDef);
                bufWriter.flush();
            }
        }
    }

    private void zipScrutariExport() throws IOException {
        ScrutariExportManager scrutariExportManager = bdfServer.getScrutariExportManager();
        List<ScrutariExportDef> scrutariExportDefList = scrutariExportManager.getScrutariExportDefList();
        if (!scrutariExportDefList.isEmpty()) {
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
            ScrutariExportDefXMLPart xmlPart = new ScrutariExportDefXMLPart(xmlWriter);
            for (ScrutariExportDef scrutariExportDef : scrutariExportDefList) {
                zipOutputStream.putNextEntry(new ZipEntry(confRoot + "scrutariexport/" + scrutariExportDef.getName() + ".xml"));
                xmlWriter.appendXMLDeclaration();
                xmlPart.addScrutariExportDef(scrutariExportDef);
                bufWriter.flush();
            }
        }
    }

    private void zipSubsetTree(short category, AppendableXMLWriter xmlWriter) throws IOException {
        SubsetTree subsetTree = bdfServer.getTreeManager().getSubsetTree(category);
        subsetTree = TreeFilterEngine.selection(subsetEligibility, subsetTree);
        TreeXMLPart treeXMLPart = new TreeXMLPart(xmlWriter);
        zipOutputStream.putNextEntry(new ZipEntry(confRoot + "trees/" + SubsetKey.categoryToString(category) + ".xml"));
        xmlWriter.appendXMLDeclaration();
        treeXMLPart.addTree(subsetTree);
        bufWriter.flush();
    }

    private void zipFichotheque() throws IOException {
        zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "metadata.xml"));
        AppendableXMLWriter fichothequeMetadataXmlWriter = XMLUtils.toXMLWriter(bufWriter);
        fichothequeMetadataXmlWriter.appendXMLDeclaration();
        FichothequeMetadataXMLPart fichothequeMetadataXMLPart = new FichothequeMetadataXMLPart(fichothequeMetadataXmlWriter);
        fichothequeMetadataXMLPart.appendFichothequeMetadata(fichotheque.getFichothequeMetadata());
        bufWriter.flush();
        for (Subset subset : subsetArray) {
            if (subset instanceof Corpus) {
                zipCorpus((Corpus) subset);
            } else if (subset instanceof Thesaurus) {
                zipThesaurus((Thesaurus) subset);
            } else if (subset instanceof Sphere) {
                zipSphere((Sphere) subset);
            } else if (subset instanceof Addenda) {
                zipAddenda((Addenda) subset);
            } else if (subset instanceof Album) {
                zipAlbum((Album) subset);
            }
        }
        int subsetLength = subsetArray.length;
        for (int i = 0; i < subsetLength; i++) {
            Subset subset = subsetArray[i];
            Predicate<SubsetItem> predicate = subsetEligibility.getPredicate(subset);
            for (SubsetItem subsetItem : subset.getSubsetItemList()) {
                if (testSubsetItem(subsetItem, predicate)) {
                    Junctions autoJunctions = fichotheque.getJunctions(subsetItem, subset);
                    for (Junctions.Entry entry : autoJunctions.getEntryList()) {
                        SubsetItem otherSubsetItem = entry.getSubsetItem();
                        if (testSubsetItem(otherSubsetItem, predicate)) {
                            if (otherSubsetItem.getId() > subsetItem.getId()) {
                                zipJunction(entry.getJunction());
                            }
                        }
                    }
                    for (int k = (i + 1); k < subsetLength; k++) {
                        Subset otherSubset = subsetArray[k];
                        Predicate<SubsetItem> otherPredicate = subsetEligibility.getPredicate(otherSubset);
                        Junctions junctions = fichotheque.getJunctions(subsetItem, otherSubset);
                        for (Junctions.Entry entry : junctions.getEntryList()) {
                            SubsetItem otherSubsetItem = entry.getSubsetItem();
                            if (testSubsetItem(otherSubsetItem, otherPredicate)) {
                                zipJunction(entry.getJunction());
                            }
                        }
                    }
                }
            }
        }

    }

    private void zipCorpus(Corpus corpus) throws IOException {
        Predicate<SubsetItem> predicate = subsetEligibility.getPredicate(corpus);
        String corpusName = corpus.getSubsetName();
        String entryPath = fichothequeRoot + "corpus/";
        Subset masterSubset = corpus.getMasterSubset();
        if (masterSubset != null) {
            String masterSubsetName = masterSubset.getSubsetName();
            if (masterSubset instanceof Corpus) {
                entryPath = entryPath + "_mc_" + masterSubsetName + "_" + corpusName + ".xml";
            } else if (masterSubset instanceof Thesaurus) {
                entryPath = entryPath + "_mt_" + masterSubsetName + "_" + corpusName + ".xml";
            } else {
                throw new UnsupportedOperationException("Unkwnown Master Subset implementation : " + masterSubset.getClass().getName());
            }
        } else {
            entryPath = entryPath + corpusName + ".xml";
        }
        zipOutputStream.putNextEntry(new ZipEntry(entryPath));
        AppendableXMLWriter metadataXmlWriter = XMLUtils.toXMLWriter(bufWriter);
        metadataXmlWriter.appendXMLDeclaration();
        CorpusMetadataXMLPart corpusMetadataXMLPart = new CorpusMetadataXMLPart(metadataXmlWriter);
        corpusMetadataXMLPart.appendCorpusMetadata(corpus.getCorpusMetadata());
        bufWriter.flush();
        for (FicheMeta ficheMeta : corpus.getFicheMetaList()) {
            if (testSubsetItem(ficheMeta, predicate)) {
                int id = ficheMeta.getId();
                FicheAPI fiche = corpus.getFicheAPI(ficheMeta, true);
                String ficheracine = fichothequeRoot + "corpus/" + corpusName + "/f." + BdfServerUtils.getMillier(id) + "/" + id + "/";
                zipOutputStream.putNextEntry(new ZipEntry(ficheracine + "fiche.xml"));
                AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
                xmlWriter.appendXMLDeclaration();
                FicheStorageXMLPart ficheStorageXMLPart = new FicheStorageXMLPart(xmlWriter);
                ficheStorageXMLPart.appendFiche(fiche);
                bufWriter.flush();
                if (!ficheMeta.getAttributes().isEmpty()) {
                    zipOutputStream.putNextEntry(new ZipEntry(ficheracine + "attributes.xml"));
                    AppendableXMLWriter attributesXmlWriter = XMLUtils.toXMLWriter(bufWriter);
                    attributesXmlWriter.appendXMLDeclaration();
                    AttributesStorageXMLPart attributesStorageXMLPart = new AttributesStorageXMLPart(xmlWriter);
                    attributesStorageXMLPart.appendAttributes(ficheMeta.getAttributes());
                    bufWriter.flush();
                }
                zipOutputStream.putNextEntry(new ZipEntry(ficheracine + "chrono.txt"));
                StorageUtils.writeChrono(bufWriter, ficheMeta);
                bufWriter.flush();
            }
        }
    }

    private void zipThesaurus(Thesaurus thesaurus) throws IOException {
        Predicate<SubsetItem> predicate = subsetEligibility.getPredicate(thesaurus);
        String thesaurusName = thesaurus.getSubsetName();
        zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "thesaurus/" + thesaurusName + ".xml"));
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
        xmlWriter.appendXMLDeclaration();
        ThesaurusMetadataXMLPart thesaurusMetadataXMLPart = new ThesaurusMetadataXMLPart(xmlWriter);
        thesaurusMetadataXMLPart.appendThesaurusMetadata(thesaurus.getThesaurusMetadata());
        bufWriter.flush();
        zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "thesaurus/" + thesaurusName + "/tree.xml"));
        AppendableXMLWriter treeXmlWriter = XMLUtils.toXMLWriter(bufWriter);
        treeXmlWriter.appendXMLDeclaration();
        ThesaurusTreeXMLPart thesaurusTreeXMLPart = new ThesaurusTreeXMLPart(treeXmlWriter);
        thesaurusTreeXMLPart.appendThesaurus(thesaurus, predicate);
        bufWriter.flush();
        zipMotcleList(thesaurusName, thesaurus.getFirstLevelList(), predicate);
    }

    private void zipSphere(Sphere sphere) throws IOException {
        Predicate<SubsetItem> predicate = subsetEligibility.getPredicate(sphere);
        String sphereName = sphere.getSubsetName();
        zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "sphere/" + sphereName + ".xml"));
        AppendableXMLWriter metadataXmlWriter = XMLUtils.toXMLWriter(bufWriter);
        metadataXmlWriter.appendXMLDeclaration();
        SphereMetadataXMLPart sphereMetadataXMLPart = new SphereMetadataXMLPart(metadataXmlWriter);
        sphereMetadataXMLPart.appendSphereMetadata(sphere.getSphereMetadata());
        bufWriter.flush();
        zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "sphere/" + sphereName + "/list.xml"));
        AppendableXMLWriter listXmlWriter = XMLUtils.toXMLWriter(bufWriter);
        listXmlWriter.appendXMLDeclaration();
        SphereListXMLPart sphereListXMLPart = new SphereListXMLPart(listXmlWriter);
        sphereListXMLPart.appendSphereList(sphere, predicate);
        bufWriter.flush();
        for (Redacteur redacteur : sphere.getRedacteurList()) {
            if (testSubsetItem(redacteur, predicate)) {
                int id = redacteur.getId();
                zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "sphere/" + sphereName + "/r." + BdfServerUtils.getMillier(id) + "/" + id + ".xml"));
                AppendableXMLWriter redacteurXmlWriter = XMLUtils.toXMLWriter(bufWriter);
                redacteurXmlWriter.appendXMLDeclaration();
                RedacteurStorageXMLPart redacteurStorageXMLPart = new RedacteurStorageXMLPart(redacteurXmlWriter);
                redacteurStorageXMLPart.appendRedacteur(redacteur);
                bufWriter.flush();
            }
        }
    }

    private void zipAddenda(Addenda addenda) throws IOException {
        Predicate<SubsetItem> predicate = subsetEligibility.getPredicate(addenda);
        String addendaName = addenda.getSubsetName();
        zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "addenda/" + addendaName + ".xml"));
        AppendableXMLWriter metadataXmlWriter = XMLUtils.toXMLWriter(bufWriter);
        metadataXmlWriter.appendXMLDeclaration();
        AddendaMetadataXMLPart addendaMetadataXMLPart = new AddendaMetadataXMLPart(metadataXmlWriter);
        addendaMetadataXMLPart.appendAddendaMetadata(addenda.getAddendaMetadata());
        bufWriter.flush();
        for (Document document : addenda.getDocumentList()) {
            if (testSubsetItem(document, predicate)) {
                int id = document.getId();
                String documentRoot = fichothequeRoot + "addenda/" + addendaName + "/d." + BdfServerUtils.getMillier(id) + "/" + id + "/";
                for (Version version : document.getVersionList()) {
                    zipOutputStream.putNextEntry(new ZipEntry(documentRoot + "doc." + version.getExtension()));
                    try (InputStream is = version.getInputStream()) {
                        IOUtils.copy(is, zipOutputStream);
                    }
                }
                zipOutputStream.putNextEntry(new ZipEntry(documentRoot + "document.xml"));
                AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
                xmlWriter.appendXMLDeclaration();
                DocumentStorageXMLPart documentStorageXMLPart = new DocumentStorageXMLPart(xmlWriter);
                documentStorageXMLPart.appendDocument(document);
                bufWriter.flush();
            }
        }
    }

    private void zipAlbum(Album album) throws IOException {
        Predicate<SubsetItem> predicate = subsetEligibility.getPredicate(album);
        String subsetName = album.getSubsetName();
        zipOutputStream.putNextEntry(new ZipEntry(fichothequeRoot + "album/" + subsetName + ".xml"));
        AppendableXMLWriter metadataXmlWriter = XMLUtils.toXMLWriter(bufWriter);
        metadataXmlWriter.appendXMLDeclaration();
        AlbumMetadataXMLPart albumMetadataXMLPart = new AlbumMetadataXMLPart(metadataXmlWriter);
        albumMetadataXMLPart.appendAlbumMetadata(album.getAlbumMetadata());
        bufWriter.flush();
        for (Illustration illustration : album.getIllustrationList()) {
            if (testSubsetItem(illustration, predicate)) {
                int id = illustration.getId();
                String illustrationracine = fichothequeRoot + "album/" + subsetName + "/i." + BdfServerUtils.getMillier(id) + "/" + id + "/";
                zipOutputStream.putNextEntry(new ZipEntry(illustrationracine + "img." + illustration.getFormatType()));
                try (InputStream is = illustration.getInputStream(AlbumConstants.ORIGINAL_SPECIALDIM)) {
                    IOUtils.copy(is, zipOutputStream);
                }
            }
        }
    }

    private void zipMotcleList(String thesaurusName, List<Motcle> motcleList, Predicate<SubsetItem> predicate) throws IOException {
        for (Motcle motcle : motcleList) {
            int id = motcle.getId();
            if ((predicate != null) && (!predicate.test(motcle))) {
                continue;
            }
            String motcleEntryPath = fichothequeRoot + "thesaurus/" + thesaurusName + "/m." + BdfServerUtils.getMillier(id) + "/" + id + ".xml";
            zipOutputStream.putNextEntry(new ZipEntry(motcleEntryPath));
            AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
            xmlWriter.appendXMLDeclaration();
            MotcleStorageXMLPart motcleStorageXMLPart = new MotcleStorageXMLPart(xmlWriter);
            motcleStorageXMLPart.appendMotcle(motcle);
            bufWriter.flush();
            zipMotcleList(thesaurusName, motcle.getChildList(), predicate);
        }
    }

    private void zipJunction(Junction junction) throws IOException {
        JunctionKey junctionKey = junction.getJunctionKey();
        SubsetKey subsetKey1 = junctionKey.getSubsetKey1();
        SubsetKey subsetKey2 = junctionKey.getSubsetKey2();
        int id1 = junctionKey.getId1();
        int id2 = junctionKey.getId2();
        StringBuilder buf = new StringBuilder();
        buf.append(fichothequeRoot);
        buf.append(subsetKey1.getCategoryString());
        buf.append("_");
        buf.append(subsetKey2.getCategoryString());
        buf.append("/");
        buf.append(subsetKey1.getSubsetName());
        buf.append("_");
        buf.append(subsetKey2.getSubsetName());
        buf.append("/c.");
        buf.append(BdfServerUtils.getMillier(id1));
        buf.append("_");
        buf.append(BdfServerUtils.getMillier(id2));
        buf.append("/");
        buf.append(id1);
        buf.append("_");
        buf.append(id2);
        buf.append(".xml");
        zipOutputStream.putNextEntry(new ZipEntry(buf.toString()));
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(bufWriter);
        xmlWriter.appendXMLDeclaration();
        JunctionStorageXMLPart junctionStorageXMLPart = new JunctionStorageXMLPart(xmlWriter);
        junctionStorageXMLPart.appendJunction(junction);
        bufWriter.flush();
    }

    private boolean testSubsetItem(SubsetItem subsetItem, Predicate<SubsetItem> predicate) {
        if (subsetItem == null) {
            return false;
        }
        return predicate.test(subsetItem);
    }

    private List<TableExportContentDescription> filter(TableExportDescription tableExportDescription) {
        List<TableExportContentDescription> result = new ArrayList<TableExportContentDescription>();
        for (TableExportContentDescription tableExportContentDescription : tableExportDescription.getTableExportContentDescriptionList()) {
            SubsetKey subsetKey = tableExportContentDescription.getSubsetKey();
            if ((subsetKey == null) || (subsetEligibility.accept(subsetKey))) {
                result.add(tableExportContentDescription);
            }
        }
        return result;
    }

}
