/* BdfServer - Copyright (c) 2013-2025 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.impl;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.configuration.PathConfiguration;
import fr.exemole.bdfserver.api.exportation.transformation.SimpleTemplateFactory;
import fr.exemole.bdfserver.api.exportation.transformation.StreamTemplateFactory;
import fr.exemole.bdfserver.api.managers.TransformationManager;
import fr.exemole.bdfserver.api.storage.BdfStorageException;
import fr.exemole.bdfserver.api.storage.StorageContent;
import fr.exemole.bdfserver.api.storage.TemplateStorage;
import fr.exemole.bdfserver.tools.configuration.PathConfigurationBuilder;
import fr.exemole.bdfserver.tools.exportation.transformation.DistTemplateManager;
import fr.exemole.bdfserver.tools.exportation.transformation.TransformationAvailabilities;
import fr.exemole.bdfserver.tools.exportation.transformation.compilers.CompilationException;
import fr.exemole.bdfserver.tools.exportation.transformation.compilers.SimpleTemplateCompiler;
import fr.exemole.bdfserver.tools.exportation.transformation.compilers.StreamTemplateCompiler;
import fr.exemole.bdfserver.tools.exportation.transformation.compilers.TemplateCompilerUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.fichotheque.EditOrigin;
import net.fichotheque.exportation.transformation.NoDefaultTemplateException;
import net.fichotheque.exportation.transformation.SimpleTemplate;
import net.fichotheque.exportation.transformation.StreamTemplate;
import net.fichotheque.exportation.transformation.TemplateDef;
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.tools.exportation.transformation.TemplateDescriptionBuilder;
import net.fichotheque.tools.exportation.transformation.TransformationDescriptionBuilder;
import net.fichotheque.utils.TransformationUtils;
import net.mapeadores.util.io.IOUtils;


/**
 *
 * @author Vincent Calame
 */
class TransformationManagerImpl implements TransformationManager {

    private final BdfServer bdfServer;
    private final TemplateStorage templateStorage;
    private final DistTemplateManager distTemplateManager;
    private final Map<TransformationKey, TransformationDescription> transformationDescriptionMap = new HashMap<TransformationKey, TransformationDescription>();
    private final Map<TemplateKey, SimpleTemplateFactory> simpleTemplateFactoryMap = new HashMap<TemplateKey, SimpleTemplateFactory>();
    private final Map<TemplateKey, StreamTemplateFactory> streamTemplateFactoryMap = new HashMap<TemplateKey, StreamTemplateFactory>();

    TransformationManagerImpl(BdfServer bdfServer, TemplateStorage templateStorage) {
        this.bdfServer = bdfServer;
        this.templateStorage = templateStorage;
        this.distTemplateManager = new DistTemplateManager(bdfServer);
    }

    @Override
    public synchronized void clearDistTransformer() {
        distTemplateManager.clear();
    }

    @Override
    public synchronized void update() {
        transformationDescriptionMap.clear();
        simpleTemplateFactoryMap.clear();
        streamTemplateFactoryMap.clear();
        distTemplateManager.clear();
        updateStorage(getPathConfiguration());
        bdfServer.getTableExportManager().update();
    }

    @Override
    public synchronized TemplateDescription updateTemplateDescription(TemplateKey templateKey) {
        if (templateKey.isDist()) {
            throw new IllegalArgumentException("templateKey.isDist()");
        }
        return reinitTemplateDescription(templateKey, getPathConfiguration());
    }

    @Override
    public synchronized TemplateDescription createTemplate(TemplateStorage.Unit templateStorageUnit, EditOrigin editOrigin) throws IOException {
        templateStorage.createTemplate(templateStorageUnit, editOrigin);
        TemplateKey templateKey = templateStorageUnit.getTemplateKey();
        if (templateKey.isDist()) {
            throw new IllegalArgumentException("templateKey.isDist()");
        }
        update();
        TransformationDescription transformationDescription = transformationDescriptionMap.get(templateKey.getTransformationKey());
        if (templateKey.isStreamTemplate()) {
            for (TemplateDescription templateDescription : transformationDescription.getStreamTemplateDescriptionList()) {
                if (templateDescription.getTemplateKey().equals(templateKey)) {
                    return templateDescription;
                }
            }
        } else {
            for (TemplateDescription templateDescription : transformationDescription.getSimpleTemplateDescriptionList()) {
                if (templateDescription.getTemplateKey().equals(templateKey)) {
                    return templateDescription;
                }
            }
        }
        throw new IllegalStateException("transformationDescription must contain templateDescription");
    }

    @Override
    public synchronized void putTemplateDef(TemplateDef templateDef, EditOrigin editOrigin) {
        TemplateKey templateKey = templateDef.getTemplateKey();
        if (templateKey.isDist()) {
            throw new IllegalArgumentException("templateKey.isDist()");
        }
        templateStorage.saveTemplateDef(templateDef, editOrigin);
    }

    @Override
    public synchronized String getTemplateContent(TemplateKey templateKey, String contentPath) {
        StorageContent storageContent = templateStorage.getStorageContent(templateKey, contentPath);
        if (storageContent != null) {
            try (InputStream is = storageContent.getInputStream()) {
                return IOUtils.toString(is, "UTF-8");
            } catch (IOException ioe) {
                throw new BdfStorageException(ioe);
            }
        } else {
            return null;
        }
    }

    @Override
    public synchronized StorageContent getTemplateStorageContent(TemplateKey templateKey, String contentPath) {
        return templateStorage.getStorageContent(templateKey, contentPath);
    }

    @Override
    public synchronized TemplateDescription putTemplateContent(TemplateKey templateKey, String path, InputStream inputStream, EditOrigin editOrigin) throws IOException {
        if (templateKey.isDist()) {
            throw new IllegalArgumentException("templateKey.isDist()");
        }
        templateStorage.saveStorageContent(templateKey, path, inputStream, editOrigin);
        return reinitTemplateDescription(templateKey, getPathConfiguration());
    }

    @Override
    public synchronized TemplateDescription removeTemplateContent(TemplateKey templateKey, String path, EditOrigin editOrigin) {
        if (templateKey.isDist()) {
            throw new IllegalArgumentException("templateKey.isDist()");
        }
        boolean done = templateStorage.removeStorageContent(templateKey, path, editOrigin);
        if (done) {
            return reinitTemplateDescription(templateKey, getPathConfiguration());
        } else {
            return null;
        }
    }

    @Override
    public synchronized TransformationDescription getTransformationDescription(TransformationKey transformationKey) {
        TransformationDescription transformationDescription = transformationDescriptionMap.get(transformationKey);
        if (transformationDescription == null) {
            transformationDescription = TransformationUtils.getEmptyTransformationDescription(transformationKey);
        }
        return transformationDescription;
    }

    @Override
    public synchronized boolean containsTemplate(TemplateKey templateKey) {
        if (templateKey.isDist()) {
            return distTemplateManager.containsTemplate(templateKey);
        } else {
            if (templateKey.isSimpleTemplate()) {
                return simpleTemplateFactoryMap.containsKey(templateKey);
            } else {
                return streamTemplateFactoryMap.containsKey(templateKey);
            }
        }
    }

    @Override
    public synchronized SimpleTemplate getSimpleTemplate(TemplateKey templateKey, boolean useDefault) {
        if (!templateKey.isSimpleTemplate()) {
            throw new IllegalArgumentException("not a simple template key");
        }
        if (templateKey.isDist()) {
            return distTemplateManager.getSimpleTemplate(templateKey, useDefault);
        }
        SimpleTemplateFactory simpleTemplateFactory = simpleTemplateFactoryMap.get(templateKey);
        if (simpleTemplateFactory != null) {
            return simpleTemplateFactory.newInstance();
        } else if (useDefault) {
            return distTemplateManager.getDefaultSimpleTemplate(templateKey.getTransformationKey());
        } else {
            return null;
        }
    }

    @Override
    public synchronized StreamTemplate getStreamTemplate(TemplateKey templateKey, boolean useDefault) throws NoDefaultTemplateException {
        if (templateKey.isSimpleTemplate()) {
            throw new IllegalArgumentException("not stream template key");
        }
        if (templateKey.isDist()) {
            return distTemplateManager.getStreamTemplate(templateKey, useDefault);
        }
        StreamTemplateFactory streamTemplateFactory = streamTemplateFactoryMap.get(templateKey);
        if (streamTemplateFactory != null) {
            return streamTemplateFactory.newInstance();
        } else if (useDefault) {
            return distTemplateManager.getDefaultStreamTemplate(templateKey.getTransformationKey(), templateKey.getValidExtension());
        } else {
            return null;
        }
    }

    @Override
    public synchronized boolean removeTemplate(TemplateKey templateKey, EditOrigin editOrigin) {
        boolean done = templateStorage.removeTemplate(templateKey, editOrigin);
        if (done) {
            update();
        }
        return done;
    }

    @Override
    public Set<String> getDefaultStreamTemplateAvailableExtensionSet(TransformationKey transformationKey) {
        return distTemplateManager.getDefaultStreamTemplateAvailableExtensionSet(transformationKey);
    }

    private TemplateDescription reinitTemplateDescription(TemplateKey templateKey, PathConfiguration pathConfiguration) {
        TemplateStorage.Unit templateStorageUnit = templateStorage.getTemplateStorageUnit(templateKey);
        TransformationKey transformationKey = templateKey.getTransformationKey();
        TemplateDescription newTemplateDescription = checkTemplateDescription(templateStorageUnit, pathConfiguration);
        TransformationDescriptionBuilder transformationDescriptionBuilder = new TransformationDescriptionBuilder(transformationKey);
        TransformationDescription transformationDescription = transformationDescriptionMap.get(transformationKey);
        if (transformationDescription != null) {
            transformationDescriptionBuilder
                    .addTemplateDescriptions(transformationDescription.getSimpleTemplateDescriptionList())
                    .addTemplateDescriptions(transformationDescription.getStreamTemplateDescriptionList());
        }
        transformationDescriptionBuilder.addTemplateDescription(newTemplateDescription);
        transformationDescriptionMap.put(transformationKey, transformationDescriptionBuilder.toTransformationDescription());
        return newTemplateDescription;
    }

    private void updateStorage(PathConfiguration pathConfiguration) {
        TemplateStorage.Unit[] array = templateStorage.checkStorage();
        Map<TransformationKey, List<TemplateStorage.Unit>> map = new HashMap<TransformationKey, List<TemplateStorage.Unit>>();
        for (TemplateStorage.Unit storageUnit : array) {
            TransformationKey transformationKey = storageUnit.getTemplateKey().getTransformationKey();
            List<TemplateStorage.Unit> list = map.get(transformationKey);
            if (list == null) {
                list = new ArrayList<TemplateStorage.Unit>();
                map.put(transformationKey, list);
            }
            list.add(storageUnit);
        }
        for (TransformationKey transformationKey : map.keySet()) {
            TransformationDescriptionBuilder transformationDescriptionBuilder = new TransformationDescriptionBuilder(transformationKey);
            List<TemplateStorage.Unit> list = map.get(transformationKey);
            for (TemplateStorage.Unit templateStorageDescription : list) {
                TemplateDescription templateDescription = checkTemplateDescription(templateStorageDescription, pathConfiguration);
                transformationDescriptionBuilder.addTemplateDescription(templateDescription);
            }
            transformationDescriptionMap.put(transformationKey, transformationDescriptionBuilder.toTransformationDescription());
        }
    }

    private TemplateDescription checkTemplateDescription(TemplateStorage.Unit templateStorageUnit, PathConfiguration pathConfiguration) {
        if (templateStorageUnit.getTemplateKey().isSimpleTemplate()) {
            return checkSimpleTemplateDescription(templateStorageUnit, pathConfiguration);
        } else {
            return checkStreamTemplateDescription(templateStorageUnit, pathConfiguration);
        }
    }

    private TemplateDescription checkSimpleTemplateDescription(TemplateStorage.Unit templateStorageUnit, PathConfiguration pathConfiguration) {
        TemplateKey templateKey = templateStorageUnit.getTemplateKey();
        String type = templateStorageUnit.getType();
        if (!TransformationAvailabilities.isValidTemplateType(type, templateKey)) {
            return TemplateDescriptionBuilder.buildUnknownType(templateStorageUnit.getTemplateDef(), type);
        }
        TemplateDescriptionBuilder templateDescriptionBuilder = new TemplateDescriptionBuilder(templateStorageUnit.getTemplateDef(), type);
        SimpleTemplateCompiler simpleTemplateCompiler = TemplateCompilerUtils.getSimpleTemplateCompiler(type, bdfServer, pathConfiguration, templateKey, templateDescriptionBuilder.getMessageHandler());
        boolean done;
        try {
            SimpleTemplateFactory simpleTemplateFactory = simpleTemplateCompiler.compile(templateStorageUnit);
            simpleTemplateFactoryMap.put(templateKey, simpleTemplateFactory);
            done = true;
        } catch (CompilationException ce) {
            simpleTemplateFactoryMap.remove(templateKey);
            done = false;
        }
        templateDescriptionBuilder.addAll(simpleTemplateCompiler.flushTemplateContentDescriptionList());
        TemplateDescription templateDescription = templateDescriptionBuilder.toTemplateDescription();
        if ((!done) && (TransformationUtils.isUseableTemplateState(templateDescription.getState()))) {
            throw new IllegalStateException("Compiler error : simpleTemplateFactory is null and state is useable");
        }
        return templateDescription;
    }

    private TemplateDescription checkStreamTemplateDescription(TemplateStorage.Unit templateStorageUnit, PathConfiguration pathConfiguration) {
        TemplateKey templateKey = templateStorageUnit.getTemplateKey();
        String type = templateStorageUnit.getType();
        if (!TransformationAvailabilities.isValidTemplateType(type, templateKey)) {
            return TemplateDescriptionBuilder.buildUnknownType(templateStorageUnit.getTemplateDef(), type);
        }
        TemplateDescriptionBuilder templateDescriptionBuilder = new TemplateDescriptionBuilder(templateStorageUnit.getTemplateDef(), type);
        StreamTemplateCompiler streamTemplateCompiler = TemplateCompilerUtils.getStreamTemplateCompiler(type, bdfServer, pathConfiguration, templateKey, templateDescriptionBuilder.getMessageHandler());
        boolean done;
        try {
            StreamTemplateFactory streamTemplateFactory = streamTemplateCompiler.compile(templateStorageUnit);
            streamTemplateFactoryMap.put(templateKey, streamTemplateFactory);
            done = true;
        } catch (CompilationException ce) {
            streamTemplateFactoryMap.remove(templateKey);
            done = false;
        }
        templateDescriptionBuilder.addAll(streamTemplateCompiler.flushTemplateContentDescriptionList());
        TemplateDescription templateDescription = templateDescriptionBuilder.toTemplateDescription();
        if ((!done) && (TransformationUtils.isUseableTemplateState(templateDescription.getState()))) {
            throw new IllegalStateException("Compiler error : streamTemplateFactory is null and state is useable");
        }
        return templateDescription;
    }

    private PathConfiguration getPathConfiguration() {
        return PathConfigurationBuilder.build(bdfServer);
    }

}
