/* 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.tools.exportation.transformation;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.configuration.LangConfiguration;
import fr.exemole.bdfserver.api.configuration.PathConfiguration;
import fr.exemole.bdfserver.api.exportation.transformation.TransformationConstants;
import fr.exemole.bdfserver.api.instruction.BdfParameters;
import fr.exemole.bdfserver.api.managers.TransformationManager;
import fr.exemole.bdfserver.api.namespaces.BdfUserSpace;
import fr.exemole.bdfserver.api.storage.TemplateStorage;
import fr.exemole.bdfserver.api.users.BdfUser;
import fr.exemole.bdfserver.tools.BdfServerUtils;
import fr.exemole.bdfserver.tools.BdfURI;
import fr.exemole.bdfserver.tools.exportation.transformation.compilers.TemplateCompiler;
import fr.exemole.bdfserver.tools.exportation.transformation.defaulttemplates.DefaultOptions;
import fr.exemole.bdfserver.tools.exportation.transformation.defaulttemplates.TemplateStorageUnitFactory;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Predicate;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import net.fichotheque.Fichotheque;
import net.fichotheque.corpus.Corpus;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.exportation.transformation.SimpleTemplate;
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.extraction.ExtractParameters;
import net.fichotheque.extraction.ExtractionConstants;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.extraction.def.ExtractionDef;
import net.fichotheque.namespaces.TransformationSpace;
import net.fichotheque.tools.exportation.transformation.TransformationCheck;
import net.fichotheque.tools.extraction.ExtractionDefFilterEngine;
import net.fichotheque.tools.extraction.ExtractionEngine;
import net.fichotheque.tools.extraction.ExtractionEngineUtils;
import net.fichotheque.tools.extraction.builders.ExtractParametersBuilder;
import net.fichotheque.utils.FichothequeUtils;
import net.fichotheque.utils.TransformationUtils;
import net.mapeadores.util.annotation.Nullable;
import net.mapeadores.util.attr.Attribute;
import net.mapeadores.util.attr.AttributeKey;
import net.mapeadores.util.attr.Attributes;
import net.mapeadores.util.attr.AttributesBuilder;
import net.mapeadores.util.exceptions.NestedTransformerException;
import net.mapeadores.util.localisation.Lang;
import net.mapeadores.util.localisation.LangContext;
import net.mapeadores.util.localisation.LocalisationUtils;
import net.mapeadores.util.logging.LineLog;
import net.mapeadores.util.logging.LineMessage;
import net.mapeadores.util.text.ValidExtension;


/**
 *
 * @author Vincent Calame
 */
public final class BdfTransformationUtils {

    public final static Attributes XSLT_DEFAULT_TEMPLATE_ATTRIBUTES = AttributesBuilder.init().appendValue(TransformationSpace.EXTRACTVERSION_KEY, String.valueOf(ExtractionConstants.INTERNATIONAL_VERSION)).toAttributes();
    public final static Attributes HTML_XSLT_DEFAULT_TEMPLATE_ATTRIBUTES = AttributesBuilder.init(XSLT_DEFAULT_TEMPLATE_ATTRIBUTES)
            .appendValue(TransformationSpace.EMPTYCOMPONENTS_KEY, "true")
            .toAttributes();
    public final static Attributes HTML_XSLT_CLEAN_TEMPLATE_ATTRIBUTES = AttributesBuilder.init(XSLT_DEFAULT_TEMPLATE_ATTRIBUTES)
            .appendValue(TransformationSpace.EMPTYCOMPONENTS_KEY, "false")
            .toAttributes();
    public final static Attributes ODT_XSLT_DEFAULT_TEMPLATE_ATTRIBUTES = AttributesBuilder.init(XSLT_DEFAULT_TEMPLATE_ATTRIBUTES)
            .appendValue(TransformationSpace.EMPTYCOMPONENTS_KEY, "false")
            .toAttributes();
    private final static TemplateDescription[] EMPTY_ARRAY = new TemplateDescription[0];

    private BdfTransformationUtils() {
    }

    public static TemplateDescription getTemplateDescription(TransformationManager transformationManager, TemplateKey templateKey) {
        TransformationDescription transformationDescription = transformationManager.getTransformationDescription(templateKey.getTransformationKey());
        if (templateKey.isSimpleTemplate()) {
            for (TemplateDescription templateDescription : transformationDescription.getSimpleTemplateDescriptionList()) {
                if (templateDescription.getTemplateKey().equals(templateKey)) {
                    return templateDescription;
                }
            }
        } else {
            for (TemplateDescription templateDescription : transformationDescription.getStreamTemplateDescriptionList()) {
                if (templateDescription.getTemplateKey().equals(templateKey)) {
                    return templateDescription;
                }
            }
        }
        return null;
    }


    public static String getFicheString(BdfParameters bdfParameters, String simpleTemplateName, FicheMeta ficheMeta, Lang lang, String fichothequePath) {
        BdfServer bdfServer = bdfParameters.getBdfServer();
        LangContext langContext = LocalisationUtils.toUserLangContext(lang);
        TransformationKey corpusTransformationKey = new TransformationKey(ficheMeta.getSubsetKey());
        TemplateKey templateKey;
        try {
            templateKey = TemplateKey.parse(corpusTransformationKey, simpleTemplateName);
        } catch (ParseException pe) {
            templateKey = TemplateKey.toDefault(corpusTransformationKey);
        }
        TransformationManager transformationManager = bdfServer.getTransformationManager();
        SimpleTemplate simpleTemplate = transformationManager.getSimpleTemplate(templateKey, true);
        ExtractionContext extractionContext = BdfServerUtils.initExtractionContextBuilder(bdfServer, langContext, bdfParameters.getPermissionSummary()).toExtractionContext();
        ExtractionDef extractionDef = simpleTemplate.getCustomExtractionDef();
        if (extractionDef == null) {
            extractionDef = DefaultExtractionDefFactory.newInstance(bdfServer, corpusTransformationKey, simpleTemplate.getAttributes());
        }
        extractionDef = ExtractionDefFilterEngine.run(extractionDef, extractionContext);
        ExtractParameters extractParameters = buildExtractParameters(extractionContext, simpleTemplate.getAttributes(), bdfParameters, null);
        String extractionString = ExtractionEngine.init(extractParameters, extractionDef).run(ExtractionEngineUtils.getExtractionSource(ficheMeta, extractionContext, extractionDef, null));
        TransformerParameters transformerParameters = TransformerParameters.build(lang)
                .check(simpleTemplate.getAttributes())
                .put(TransformationConstants.BDF_FICHOTHEQUEPATH_PARAMETER, fichothequePath);
        return simpleTemplate.transform(extractionString, transformerParameters.getMap());
    }

    public static TemplateStorage.Unit getHtmlDefaultUnit(BdfServer bdfServer, TransformationKey transformationKey) {
        DefaultOptions options = DefaultOptions.init()
                .withExtractionDef(false);
        return TemplateStorageUnitFactory.newXsltHtml(bdfServer, TemplateKey.toDefault(transformationKey), options, HTML_XSLT_DEFAULT_TEMPLATE_ATTRIBUTES);
    }

    public static TemplateStorage.Unit getStreamDefaultUnit(BdfServer bdfServer, TransformationKey transformationKey, String extension) {
        DefaultOptions options = DefaultOptions.init()
                .withExtractionDef(false);
        if (bdfServer.getTransformationManager().isDefaultStreamTemplateAvailable(transformationKey, extension)) {
            try {
                ValidExtension validExtension = ValidExtension.parse(extension);
                TemplateKey templateKey = TemplateKey.toDefault(transformationKey, validExtension);
                if (extension.equals("odt")) {
                    AttributesBuilder attributesBuilder = AttributesBuilder.init(ODT_XSLT_DEFAULT_TEMPLATE_ATTRIBUTES);
                    checkOdtTransformerAttributes(attributesBuilder, bdfServer, transformationKey);
                    return TemplateStorageUnitFactory.newXsltOdt(bdfServer, templateKey, options, attributesBuilder.toAttributes());
                } else {
                    return null;
                }
            } catch (ParseException pe) {
                return null;
            }
        } else {
            return null;
        }
    }

    public static Transformer newTransformer(BdfServer bdfServer, PathConfiguration pathConfiguration, Templates templates) {
        try {
            Transformer transformer = templates.newTransformer();
            transformer.setURIResolver(wrap(BdfURI.getURIResolver(bdfServer, pathConfiguration)));
            return transformer;
        } catch (TransformerException e) {
            throw new NestedTransformerException(e);
        }
    }

    public static URIResolver wrap(URIResolver uriResolver) {
        return new InternalURIResolver(uriResolver);
    }

    public static TransformerFactory newTransformationFactory(BdfServer bdfServer, PathConfiguration pathConfiguration) {
        TransformerFactory factory = TransformerFactory.newInstance();
        factory.setURIResolver(wrap(BdfURI.getURIResolver(bdfServer, pathConfiguration)));
        return factory;
    }

    public static ExtensionInfo[] getExtensionInfoArray(BdfServer bdfServer, TransformationCheck transformationCheck) {
        SortedMap<String, ExtensionInfo> resultMap = new TreeMap<String, ExtensionInfo>();
        Set<String> withDefaultSet = bdfServer.getTransformationManager().getDefaultStreamTemplateAvailableExtensionSet(transformationCheck.getTransformationKey());
        for (String extension : withDefaultSet) {
            TemplateDescription[] templateDescriptionArray = transformationCheck.getStreamTemplateDescriptionArray(extension);
            if (templateDescriptionArray == null) {
                templateDescriptionArray = EMPTY_ARRAY;
            }
            resultMap.put(extension, new ExtensionInfo(extension, getAction(extension), LocalisationUtils.toMessage("_ link.global.version", extension.toUpperCase()), true, templateDescriptionArray));
        }
        for (String availableExtension : transformationCheck.getExtensionArray()) {
            if (!resultMap.containsKey(availableExtension)) {
                TemplateDescription[] templateDescriptionArray = transformationCheck.getStreamTemplateDescriptionArray(availableExtension);
                resultMap.put(availableExtension, new ExtensionInfo(availableExtension, getAction(availableExtension), LocalisationUtils.toMessage("_ link.global.version", availableExtension.toUpperCase()), false, templateDescriptionArray));
            }
        }
        return resultMap.values().toArray(new ExtensionInfo[resultMap.size()]);
    }


    public static boolean isUniquelang(BdfServer bdfServer) {
        LangConfiguration langConfiguration = bdfServer.getLangConfiguration();
        if (langConfiguration.isAllLanguages()) {
            return false;
        }
        int count = langConfiguration.getWorkingLangs().size();
        if ((count == 1) && (langConfiguration.getSupplementaryLangs().isEmpty())) {
            return true;
        } else {
            return false;
        }
    }

    public static List<Corpus> getCorpusList(Fichotheque fichotheque, Attributes attributes) {
        Attribute corpusAttribute = attributes.getAttribute(TransformationSpace.CORPUS_KEY);
        if (corpusAttribute != null) {
            List<Corpus> list = new ArrayList<Corpus>();
            for (String value : corpusAttribute) {
                Corpus corpus = FichothequeUtils.getCorpus(fichotheque, value);
                if (corpus != null) {
                    list.add(corpus);
                }
            }
            if (!list.isEmpty()) {
                return list;
            }
        }
        return fichotheque.getCorpusList();
    }

    public static void checkOdtTransformerAttributes(AttributesBuilder attributesBuilder, BdfServer bdfServer, TransformationKey transformationKey) {
        if (!transformationKey.isCorpusTransformationKey()) {
            return;
        }
        Corpus corpus = (Corpus) bdfServer.getFichotheque().getSubset(transformationKey.toCorpusKey());
        if (corpus == null) {
            return;
        }
        Attributes corpusAttributes = corpus.getCorpusMetadata().getAttributes();
        addAttribute(attributesBuilder, corpusAttributes, TransformationSpace.FIELDBULLET_KEY);
        addAttribute(attributesBuilder, corpusAttributes, TransformationSpace.SECTIONEND_KEY);
        addAttribute(attributesBuilder, corpusAttributes, TransformationSpace.FICHENUMBER_KEY);
        addAttribute(attributesBuilder, corpusAttributes, TransformationSpace.RESOURCELOGO_KEY);
    }

    private static void addAttribute(AttributesBuilder attributesBuilder, Attributes corpusAttributes, AttributeKey attributeKey) {
        Attribute attribute = corpusAttributes.getAttribute(attributeKey);
        if (attribute != null) {
            attributesBuilder.appendValues(attribute);
        }
    }

    public static ExtractParameters buildExtractParameters(ExtractionContext extractionContext, Attributes templateAttributes, @Nullable BdfParameters bdfParameters, @Nullable Predicate<FicheMeta> fichePredicate) {
        int extractVersion = TransformationUtils.getExtractVersion(templateAttributes);
        boolean userOverride = false;
        boolean withEmpty = false;
        boolean withPosition = true;
        if (bdfParameters != null) {
            BdfUser bdfUser = bdfParameters.getBdfUser();
            if (bdfUser != null) {
                boolean hideEmpty = bdfUser.getPrefs().getBoolean(BdfUserSpace.HIDEEMPTY_KEY);
                if (hideEmpty) {
                    userOverride = true;
                    withEmpty = false;
                }
            }
        }
        if (!userOverride) {
            withEmpty = TransformationUtils.isWithEmptyComponents(templateAttributes, false);
        }
        return ExtractParametersBuilder.init(extractionContext, extractVersion)
                .setWithEmpty(withEmpty)
                .setWithPosition(withPosition)
                .setFichePredicate(fichePredicate)
                .toExtractParameters();
    }

    public static String getShouldNotOccurMessage(TemplateCompiler templateCompiler) {
        List<TemplateContentDescription> list = templateCompiler.flushTemplateContentDescriptionList();
        StringBuilder buf = new StringBuilder();
        for (TemplateContentDescription description : list) {
            for (LineLog lineLog : description.getLineLogs()) {
                if (buf.length() > 0) {
                    buf.append(" @@@ ");
                }
                buf.append(lineLog.getLineNumber());
                for (LineMessage message : lineLog.getLineMessageList()) {
                    buf.append(" | [");
                    buf.append(message.getCategory());
                    buf.append("] ");
                    buf.append(message.getMessageKey());
                    Object[] values = message.getMessageValues();
                    for (Object value : values) {
                        buf.append(" / ");
                        buf.append(value);
                    }
                }
            }
        }
        if (buf.length() == 0) {
            buf.append("Unknown compilation error");
        }
        return buf.toString();
    }

    private static String getAction(String extension) {
        switch (extension) {
            case "ods":
                return "action-Ods";
            case "odt":
                return "action-Odt";
            default:
                return "action-Test";
        }
    }


    private static class InternalURIResolver implements URIResolver {

        private final URIResolver originalUriResolver;

        private InternalURIResolver(URIResolver originalUriResolver) {
            this.originalUriResolver = originalUriResolver;
        }

        @Override
        public Source resolve(String href, String base) throws TransformerException {
            Source source = originalUriResolver.resolve(href, base);
            if (source == null) {
                throw new TransformerException("(base: " + base + ")");
            } else {
                return source;
            }
        }

    }

}
