/* BdfServer_DirectoryStorage  - Copyright (c) 2013-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.storage.directory.oldversions;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.BdfServerEditor;
import fr.exemole.bdfserver.api.EditSession;
import fr.exemole.bdfserver.api.managers.PermissionManager;
import fr.exemole.bdfserver.api.namespaces.BdfUserSpace;
import fr.exemole.bdfserver.api.roles.Role;
import fr.exemole.bdfserver.api.storage.BdfStorageException;
import fr.exemole.bdfserver.api.storage.BdfUserStorage;
import fr.exemole.bdfserver.api.storage.MigrationEngine;
import fr.exemole.bdfserver.api.users.BdfUserPrefs;
import fr.exemole.bdfserver.conf.BdfServerDirs;
import fr.exemole.bdfserver.conf.ConfConstants;
import fr.exemole.bdfserver.storage.directory.bdfdata.BdfdataConstants;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import fr.exemole.bdfserver.tools.users.BdfUserPrefsBuilder;
import fr.exemole.bdfserver.tools.users.dom.BdfUserPrefsDOMReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.text.ParseException;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import net.fichotheque.Fichotheque;
import net.fichotheque.SubsetKey;
import net.fichotheque.sphere.Redacteur;
import net.fichotheque.utils.SphereUtils;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.attr.Attributes;
import net.mapeadores.util.io.IOUtils;
import net.mapeadores.util.logging.LogUtils;
import net.mapeadores.util.xml.DOMUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;


/**
 * Série de tests de migration suite à des changements sur la structure
 * d'enregistrements des données
 *
 * @author Vincent Calame
 */
public class DirectoryMigrationEngine implements MigrationEngine {

    private final static AttributeKey BDFUSER_ROLES_KEY = AttributeKey.build(BdfUserSpace.BDFUSER_NAMESPACE, "roles");

    /*
     * Introduit à la révision 1615, il effectue les éléments suivants :
     * - le déplacement du répertoire de roles de vers conf/roles au lieu d'un répertoire à la racine
     * - la conversion des fichiers de users/ stockés préalablement avec RedacteurKey ainsi que l'extraction
     * de l'attribut bdfuser:rolees (stockage des rôles au niveau de l'utilisateur) pour l'intégrer dans PermissionManager
     * - la conversion des anciens fichiers Xml de chrono stockés dans chrono/ vers les fichiers chrono.
     * .0
     */
    private boolean roleMigration = false;
    private final File bdfdataDir;
    private final File backupDir;

    private DirectoryMigrationEngine(File bdfdataDir, File backupDir) {
        this.bdfdataDir = bdfdataDir;
        this.backupDir = backupDir;
    }

    @Override
    public void afterInitRun(BdfServer bdfServer) {
        try (EditSession session = bdfServer.initEditSession("MigrationEngine")) {
            BdfServerEditor bdfServerEditor = session.getBdfServerEditor();
            if (roleMigration) {
                migrateRoles(bdfServer, bdfServerEditor);
            }
            File oldTreesFile = new File(bdfdataDir, BdfdataConstants.CONF + "/ui/trees.xml");
            if (oldTreesFile.exists()) {
                migrateTrees(bdfServerEditor, oldTreesFile);
                oldTreesFile.delete();
            }
            File oldConfigurationManagerFile = new File(bdfdataDir, BdfdataConstants.CONF + "/configurationmanager.xml");
            if (oldConfigurationManagerFile.exists()) {
                migrateConfigurationManager(bdfServerEditor, oldConfigurationManagerFile);
                oldConfigurationManagerFile.delete();
            }
            File oldPolicyManagerFile = new File(bdfdataDir, BdfdataConstants.CONF + "/policymanager.xml");
            if (oldPolicyManagerFile.exists()) {
                migratePolicyManager(bdfServerEditor, oldPolicyManagerFile);
                oldPolicyManagerFile.delete();
            }
            File oldPermissionManagerFile = new File(bdfdataDir, BdfdataConstants.CONF + "/permissionmanager.xml");
            if (oldPermissionManagerFile.exists()) {
                migratePermissionManager(bdfServerEditor, oldPermissionManagerFile);
                oldPermissionManagerFile.delete();
            }
        }
    }

    public static DirectoryMigrationEngine init(BdfServerDirs dirs) {
        File bdfdataDir = dirs.getSubPath(ConfConstants.VAR_DATA, BdfdataConstants.ROOT);
        File backupDir = dirs.getSubPath(ConfConstants.VAR_BACKUP, BdfdataConstants.ROOT);
        DirectoryMigrationEngine migrationEngine = new DirectoryMigrationEngine(bdfdataDir, backupDir);
        migrationEngine.init();
        return migrationEngine;
    }

    private void init() {
        //Déplacement de basedefiches vers fichotheque
        testOldFichothequeDir(bdfdataDir);
        testOldFichothequeDir(backupDir);
        //Déplacement du répertoire roles dans conf/
        File oldRolesDir = new File(bdfdataDir, "roles");
        if (oldRolesDir.exists()) {
            File confDir = new File(bdfdataDir, BdfdataConstants.CONF);
            confDir.mkdirs();
            oldRolesDir.renameTo(new File(confDir, "roles"));
            roleMigration = true;
        }
        //Déplacement des anciens fichiers XML de chrono
        File fichothequeDir = new File(bdfdataDir, BdfdataConstants.FICHOTHEQUE);
        File oldChronoDir = new File(fichothequeDir, "chrono");
        if (oldChronoDir.exists()) {
            File corpusDir = new File(fichothequeDir, "corpus");
            ChronoDOMReader chronoDOMReader = new ChronoDOMReader();
            File[] chronoFiles = oldChronoDir.listFiles();
            int length = chronoFiles.length;
            try {
                for (int i = 0; i < length; i++) {
                    File chronoFile = chronoFiles[i];
                    if (!chronoFile.isDirectory()) {
                        String name = chronoFile.getName();
                        if (name.endsWith(".xml")) {
                            try {
                                SubsetKey subsetKey = SubsetKey.parse(name.substring(0, name.length() - 4));
                                if (subsetKey.isCorpusSubset()) {
                                    File currentCorpusDir = new File(corpusDir, subsetKey.getSubsetName());
                                    if (currentCorpusDir.exists()) {
                                        Document document = readDocument(chronoFile);
                                        chronoDOMReader.migrateChrono(currentCorpusDir, document.getDocumentElement());
                                    }
                                }
                            } catch (ParseException pe) {
                            }
                        }
                    }
                }
                IOUtils.deleteDirectory(oldChronoDir);
            } catch (IOException ioe) {
                throw new BdfStorageException(ioe);
            }
        }
        //Conversion de l'addenda unique
        File addendaDir = new File(fichothequeDir, "addenda");
        if (addendaDir.exists()) {
            convertUniqueAddenda(addendaDir);
        }
        File corpusAddendaDir = new File(fichothequeDir, "corpus_addenda");
        if (corpusAddendaDir.exists()) {
            for (File subDir : corpusAddendaDir.listFiles()) {
                String name = subDir.getName();
                if (subDir.isDirectory()) {
                    if (name.endsWith("_x")) {
                        subDir.renameTo(new File(corpusAddendaDir, name.substring(0, name.length() - 2) + "_doc"));
                    }
                } else if (name.endsWith("_x.xml")) {
                    subDir.renameTo(new File(corpusAddendaDir, name.substring(0, name.length() - 6) + "_doc.xml"));
                }
            }
        }
        //Déplacement des fiches XML d'interface
        File confDir = new File(bdfdataDir, BdfdataConstants.CONF);
        File uiDir = new File(confDir, "ui");
        if (uiDir.exists()) {
            File corpusDir = new File(uiDir, "corpus");
            if (!corpusDir.exists()) {
                corpusDir.mkdirs();
                for (File file : uiDir.listFiles()) {
                    String name = file.getName();
                    if (!file.isDirectory()) {
                        if (name.endsWith(".xml")) {
                            try {
                                SubsetKey subsetKey = SubsetKey.parse(name.substring(0, name.length() - 4));
                                if (subsetKey.isCorpusSubset()) {
                                    File subsetDir = new File(corpusDir, subsetKey.getSubsetName());
                                    subsetDir.mkdir();
                                    file.renameTo(new File(subsetDir, "_main.xml"));
                                }
                            } catch (ParseException pe) {

                            }
                        }
                    }
                }
            }
        }
    }

    private void testOldFichothequeDir(File rootDir) {
        if (rootDir == null) {
            return;
        }
        File oldFichothequeDir = new File(rootDir, "basedefiches");
        if (oldFichothequeDir.exists()) {
            File newFichothequeDir = new File(rootDir, BdfdataConstants.FICHOTHEQUE);
            newFichothequeDir.mkdirs();
            oldFichothequeDir.renameTo(newFichothequeDir);
        }
    }

    private void convertUniqueAddenda(File addendaDir) {
        File xDir = new File(addendaDir, "x");
        if (!xDir.exists()) {
            return;
        }
        File docFile = new File(addendaDir, "doc.xml");
        if (docFile.exists()) {
            return;
        }
        File docDir = new File(addendaDir, "doc");
        if (docDir.exists()) {
            return;
        }
        File xFile = new File(addendaDir, "x.xml");
        if (xFile.exists()) {
            Document document = DOMUtils.readDocument(xFile);
            Element rootElement = document.getDocumentElement();
            if (!rootElement.getTagName().equals("addenda-metadata")) {
                Map<Integer, String> nameMap = AddendaDOMReader.getNameMap(rootElement);
                for (Map.Entry<Integer, String> entry : nameMap.entrySet()) {
                    int documentid = entry.getKey();
                    File documentDir = new File(xDir, "d." + BdfServerUtils.getMillier(documentid) + File.separatorChar + documentid);
                    if (documentDir.exists()) {
                        File documentFile = new File(documentDir, "document.xml");
                        if (!documentFile.exists()) {
                            try (Writer writer = new OutputStreamWriter(new FileOutputStream(documentFile), "UTF-8")) {
                                writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document basename=\"" + entry.getValue() + "\"/>");
                            } catch (IOException ioe) {
                                throw new BdfStorageException(ioe);
                            }
                        }
                    }
                }
            }
            xFile.delete();
        }
        try (InputStream is = getClass().getResourceAsStream("doc.xml"); OutputStream os = new FileOutputStream(docFile)) {
            IOUtils.copy(is, os);
        } catch (IOException ioe) {
            throw new BdfStorageException(ioe);
        }
        xDir.renameTo(docDir);
    }

    private void migrateRoles(BdfServer bdfServer, BdfServerEditor bdfServerEditor) {
        File userDir = new File(bdfdataDir, BdfdataConstants.USERS);
        if (!userDir.exists()) {
            return;
        }
        Fichotheque fichotheque = bdfServer.getFichotheque();
        BdfUserStorage userStorage = bdfServer.getBdfUserStorage();
        PermissionManager permissionManager = bdfServer.getPermissionManager();
        File[] files = userDir.listFiles();
        int length = files.length;
        for (int i = 0; i < length; i++) {
            File f = files[i];
            if (f.isDirectory()) {
                continue;
            }
            String fileName = f.getName();
            if (fileName.endsWith(".xml")) {
                String redacteurKeyString = fileName.substring(0, fileName.length() - 4);
                try {
                    Redacteur redacteur = SphereUtils.parse(fichotheque, redacteurKeyString);
                    Document doc = readDocument(f);
                    BdfUserPrefsBuilder userPrefsBuilder = new BdfUserPrefsBuilder();
                    BdfUserPrefsDOMReader domReader = new BdfUserPrefsDOMReader(fichotheque, userPrefsBuilder);
                    domReader.readBdfUserPrefs(doc.getDocumentElement());
                    BdfUserPrefs userPrefs = userPrefsBuilder.toBdfUserPrefs();
                    Attributes userPrefsAttributes = userPrefs.getAttributes();
                    Attribute oldRoleAttribute = userPrefsAttributes.getAttribute(BDFUSER_ROLES_KEY);
                    if (oldRoleAttribute != null) {
                        for (String roleName : oldRoleAttribute) {
                            Role role = permissionManager.getRole(roleName);
                            if (role != null) {
                                bdfServerEditor.link(redacteur, role);
                            }
                        }
                        userPrefsBuilder = BdfUserPrefsBuilder.init()
                                .setDefaultFicheQuery(userPrefs.getDefaultFicheQuery())
                                .setWorkingLang(userPrefs.getWorkingLang())
                                .setCustomFormatLocale(userPrefs.getCustomFormatLocale())
                                .setCustomLangPreference(userPrefs.getCustomLangPreference());
                        for (Attribute otherAttribute : userPrefsAttributes) {
                            if (!otherAttribute.getAttributeKey().equals(BDFUSER_ROLES_KEY)) {
                                userPrefsBuilder.getAttributesBuilder().appendValues(otherAttribute);
                            }
                        }
                        userPrefs = userPrefsBuilder.toBdfUserPrefs();
                    }
                    userStorage.saveBdfUserPrefs(redacteur, userPrefs);
                    f.delete();
                } catch (SphereUtils.RedacteurLoginException rle) {
                    if (rle.getType() != SphereUtils.MALFORMED_KEY) {
                        f.delete();
                    }
                }
            }
        }
    }

    private void migrateTrees(BdfServerEditor bdfServerEditor, File file) {
        Document doc = readDocument(file);
        TreesDOMReader treesDOMReader = new TreesDOMReader(LogUtils.NULL_MESSAGEHANDLER);
        treesDOMReader.readTrees(bdfServerEditor, doc.getDocumentElement());
    }

    private void migrateConfigurationManager(BdfServerEditor bdfServerEditor, File file) {
        Document doc = readDocument(file);
        ConfigurationManagerDOMReader configurationManagerDOMReader = new ConfigurationManagerDOMReader(LogUtils.NULL_MESSAGEHANDLER);
        configurationManagerDOMReader.readConfigurationManager(bdfServerEditor, doc.getDocumentElement());
    }

    private void migratePolicyManager(BdfServerEditor bdfServerEditor, File file) {
        Document doc = readDocument(file);
        PolicyManagerDOMReader policyManagerDOMReader = new PolicyManagerDOMReader(bdfServerEditor, LogUtils.NULL_MESSAGEHANDLER);
        policyManagerDOMReader.readPolicyManager(doc.getDocumentElement());
    }

    private void migratePermissionManager(BdfServerEditor bdfServerEditor, File file) {
        Document doc = readDocument(file);
        PermissionManagerDOMReader policyManagerDOMReader = new PermissionManagerDOMReader(LogUtils.NULL_MESSAGEHANDLER);
        policyManagerDOMReader.readPermissionManager(bdfServerEditor, doc.getDocumentElement());
    }

    private static Document readDocument(File file) {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docbuilder = null;
        Document document = null;
        try {
            docbuilder = documentBuilderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException pce) {
            throw new BdfStorageException(pce);
        }
        try (InputStream is = new FileInputStream(file)) {
            document = docbuilder.parse(is);
        } catch (IOException | SAXException e) {
            throw new BdfStorageException(file, e);
        }
        return document;
    }

}
