/* BdfServer - Copyright (c) 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.exportation.transformation.TransformationConstants;
import fr.exemole.bdfserver.api.managers.TransformationManager;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import net.fichotheque.SubsetItem;
import net.fichotheque.SubsetKey;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.fiche.FicheBlocks;
import net.fichotheque.exportation.transformation.SimpleTemplate;
import net.fichotheque.exportation.transformation.TemplateKey;
import net.fichotheque.exportation.transformation.TransformationKey;
import net.fichotheque.extraction.ExtractParameters;
import net.fichotheque.extraction.ExtractionConstants;
import net.fichotheque.extraction.ExtractionSource;
import net.fichotheque.extraction.def.ExtractionDef;
import net.fichotheque.format.FormatContext;
import net.fichotheque.format.FormatSource;
import net.fichotheque.format.formatters.ExtractionFormatter;
import net.fichotheque.format.formatters.FicheBlockFormatter;
import net.fichotheque.pointeurs.SubsetItemPointeur;
import net.fichotheque.tools.extraction.ExtractionEngine;
import net.fichotheque.tools.extraction.ExtractionEngineUtils;
import net.fichotheque.tools.extraction.builders.ExtractParametersBuilder;
import net.fichotheque.utils.EligibilityUtils;
import net.fichotheque.utils.TransformationUtils;
import net.fichotheque.xml.extraction.ExtractionXMLUtils;
import net.mapeadores.util.attr.Attributes;
import net.mapeadores.util.localisation.Lang;


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

    private final static ParamResolver EMPTY_PARAMMAP = new ParamResolver();

    private TransformationFormatterFactory() {

    }

    public static ExtractionFormatter newExtractionFormatter(TransformationManager transformationManager, String name, Map<String, Object> options) {
        if ((name == null) || (name.equals(FormatContext.XML_SPECIAL_FORMATTER_NAME))) {
            return new XmlExtractionFormatter(options);
        }
        try {
            TemplateKey templateKey = TemplateKey.parse(TransformationKey.FORMAT_INSTANCE, name);
            SimpleTemplate simpleTemplate = transformationManager.getSimpleTemplate(templateKey, false);
            if (simpleTemplate != null) {
                return new SimpleTemplateExtractionFormatter(simpleTemplate, options);
            } else {
                return null;
            }
        } catch (ParseException pe) {
            return null;
        }
    }

    public static FicheBlockFormatter newFicheBlockFormatter(TransformationManager transformationManager, String name, Map<String, Object> options) {
        if ((name != null) && (name.equals(FormatContext.XML_SPECIAL_FORMATTER_NAME))) {
            return new XmlFicheBlockFormatter(options);
        }
        try {
            TemplateKey templateKey = TemplateKey.parse(TransformationKey.SECTION_INSTANCE, name);
            SimpleTemplate simpleTemplate = transformationManager.getSimpleTemplate(templateKey, false);
            if (simpleTemplate != null) {
                return new SimpleTemplateFicheBlockFormatter(simpleTemplate, options);
            } else {
                return null;
            }
        } catch (ParseException pe) {
            return null;
        }
    }

    private static ParamResolver convertToParamResolver(Map<String, Object> options) {
        if (options.isEmpty()) {
            return EMPTY_PARAMMAP;
        }
        ParamResolver paramResolver = new ParamResolver();
        paramResolver.convertOptions(options);
        return paramResolver;
    }

    private static ExtractOptions buildExtractOptions(Map<String, Object> options, Attributes attributes) {
        int extractVersion = ExtractionConstants.INTERNATIONAL_VERSION;
        boolean withEmpty = false;
        boolean withPosition = false;
        if (attributes != null) {
            extractVersion = TransformationUtils.getExtractVersion(attributes);
            withEmpty = TransformationUtils.isWithEmptyComponents(attributes, false);
        }
        for (Map.Entry<String, Object> entry : options.entrySet()) {
            Object value = entry.getValue();
            switch (entry.getKey()) {
                case "withempty":
                    withEmpty = (value.equals(Boolean.TRUE));
                    break;
                case "withposition":
                    withPosition = (value.equals(Boolean.TRUE));
                    break;
                case "version":
                    if (value instanceof Integer) {
                        extractVersion = ((Integer) value);
                    }
                    break;
            }
        }
        ExtractOptions extractOptions = new ExtractOptions(extractVersion);
        extractOptions.withEmpty = withEmpty;
        extractOptions.withPosition = withPosition;
        return extractOptions;
    }


    private static abstract class AbstractExtractionFormatter implements ExtractionFormatter {

        private final ExtractOptions extractOptions;

        protected AbstractExtractionFormatter(ExtractOptions extractOptions) {
            this.extractOptions = extractOptions;
        }

        protected String getExtraction(FormatSource formatSource) {
            FormatSource.ExtractionInfo extractionInfo = formatSource.getExtractionInfo();
            if (extractionInfo == null) {
                return "";
            }
            String errorLog = extractionInfo.getErrorLog();
            if (errorLog != null) {
                return errorLog;
            }
            SubsetItemPointeur pointeur = formatSource.getSubsetItemPointeur();
            SubsetItem currentSubsetItem = pointeur.getCurrentSubsetItem();
            if (currentSubsetItem == null) {
                return "";
            }
            String extractionString;
            String objectName = "extraction_" + extractOptions.getSuffix();
            Object currentExtraction = pointeur.getCurrentObject(objectName);
            if (currentExtraction != null) {
                extractionString = currentExtraction.toString();
            } else {
                extractionString = getExtractionString(formatSource, currentSubsetItem, extractionInfo.getExtractionDef());
                pointeur.putCurrentObject(objectName, extractionString);
            }
            return extractionString;
        }

        protected String getExtractionString(FormatSource formatSource, SubsetItem subsetItem, ExtractionDef extractionDef) {
            ExtractParametersBuilder extractParametersBuilder = extractOptions.initExtractParametersBuilder(formatSource);
            Predicate<SubsetItem> predicate = formatSource.getGlobalPredicate();
            Predicate<FicheMeta> ficheMetaPredicate;
            if (predicate == null) {
                ficheMetaPredicate = EligibilityUtils.ALL_FICHE_PREDICATE;
            } else {
                ficheMetaPredicate = EligibilityUtils.toFichePredicate(predicate);
            }
            ExtractParameters extractParameters = extractParametersBuilder
                    .setFichePredicate(ficheMetaPredicate)
                    .toExtractParameters();
            ExtractionSource extractionSource = ExtractionEngineUtils.getExtractionSource(subsetItem, formatSource.getExtractionContext(), extractionDef, null);
            return ExtractionEngine.init(extractParameters, extractionDef).omitXmlDeclaration(true).run(extractionSource);
        }

    }


    private static class XmlExtractionFormatter extends AbstractExtractionFormatter {

        private XmlExtractionFormatter(Map<String, Object> options) {
            super(buildExtractOptions(options, null));
        }

        @Override
        public String formatExtraction(FormatSource formatSource) {
            return getExtraction(formatSource);
        }

    }


    private static class SimpleTemplateExtractionFormatter extends AbstractExtractionFormatter {

        private final SimpleTemplate simpleTemplate;
        private final ParamResolver paramResolver;

        private SimpleTemplateExtractionFormatter(SimpleTemplate simpleTemplate, Map<String, Object> options) {
            super(buildExtractOptions(options, simpleTemplate.getAttributes()));
            this.simpleTemplate = simpleTemplate;
            this.paramResolver = convertToParamResolver(options);
        }

        @Override
        public String formatExtraction(FormatSource formatSource) {
            String extractionString = getExtraction(formatSource);
            return simpleTemplate.transform(extractionString, paramResolver.toTransformerParameters(formatSource, simpleTemplate.getAttributes()));
        }

    }


    private static class XmlFicheBlockFormatter implements FicheBlockFormatter {

        private final ExtractOptions extractOptions;

        private XmlFicheBlockFormatter(Map<String, Object> options) {
            this.extractOptions = buildExtractOptions(options, null);
        }

        @Override
        public String formatFicheBlocks(FicheBlocks ficheBlocks, FormatSource formatSource, SubsetKey defaultCorpusKey) {
            ExtractParameters extractParameters = extractOptions.initExtractParametersBuilder(formatSource)
                    .toExtractParameters();
            return ExtractionXMLUtils.ficheBlocksToExtractionString(ficheBlocks, extractParameters, defaultCorpusKey);
        }

    }


    private static class SimpleTemplateFicheBlockFormatter implements FicheBlockFormatter {

        private final ExtractOptions extractOptions;
        private final SimpleTemplate simpleTemplate;
        private final ParamResolver paramResolver;

        private SimpleTemplateFicheBlockFormatter(SimpleTemplate simpleTemplate, Map<String, Object> options) {
            this.extractOptions = buildExtractOptions(options, simpleTemplate.getAttributes());
            this.simpleTemplate = simpleTemplate;
            this.paramResolver = convertToParamResolver(options);
        }

        @Override
        public String formatFicheBlocks(FicheBlocks ficheBlocks, FormatSource formatSource, SubsetKey defaultCorpusKey) {
            ExtractParameters extractParameters = extractOptions.initExtractParametersBuilder(formatSource)
                    .toExtractParameters();
            String extractionString = ExtractionXMLUtils.ficheBlocksToExtractionString(ficheBlocks, extractParameters, defaultCorpusKey);
            return simpleTemplate.transform(extractionString, paramResolver.toTransformerParameters(formatSource, simpleTemplate.getAttributes()));
        }

    }


    private static class ExtractOptions {

        private final int extractVersion;
        protected boolean withEmpty = false;
        protected boolean withPosition = false;

        private ExtractOptions(int extractVersion) {
            this.extractVersion = extractVersion;
        }

        protected ExtractParametersBuilder initExtractParametersBuilder(FormatSource formatSource) {
            return ExtractParametersBuilder.init(formatSource.getExtractionContext(), extractVersion)
                    .setWithPosition(withPosition)
                    .setWithEmpty(withEmpty);
        }

        protected String getSuffix() {
            return "version_" + extractVersion + "_" + withEmpty + "_" + withPosition;
        }


    }


    private static class ParamResolver {

        private final Map<String, Object> otherParamMap = new HashMap<String, Object>();
        private boolean ficheLang = false;
        private Lang lang = null;

        private ParamResolver() {

        }

        private void convertOptions(Map<String, Object> options) {
            for (Map.Entry<String, Object> entry : options.entrySet()) {
                Object value = entry.getValue();
                switch (entry.getKey()) {
                    case "startlevel":
                        if (value instanceof Integer) {
                            otherParamMap.put(TransformationConstants.STARTLEVEL_PARAMETER, value);
                        }
                        break;
                    case "externaltarget":
                        otherParamMap.put(TransformationConstants.EXTERNALTARGET_PARAMETER, value);
                        break;
                    case "lang":
                        if (value.equals("_fiche")) {
                            ficheLang = true;
                            lang = null;
                        } else {
                            try {
                                lang = Lang.parse(value.toString());
                                ficheLang = false;
                            } catch (ParseException pe) {
                                lang = null;
                                ficheLang = false;
                            }
                        }
                        break;
                }
            }
        }

        private Map<String, Object> toTransformerParameters(FormatSource formatSource, Attributes attributes) {
            Lang workingLang = null;
            if (lang != null) {
                workingLang = lang;
            } else if (ficheLang) {
                SubsetItem currentSubsetItem = formatSource.getSubsetItemPointeur().getCurrentSubsetItem();
                if ((currentSubsetItem != null) && (currentSubsetItem instanceof FicheMeta)) {
                    workingLang = ((FicheMeta) currentSubsetItem).getLang();
                }
            }
            if (workingLang == null) {
                workingLang = formatSource.getDefaultLang();
            }
            TransformerParameters transformerParameters = TransformerParameters.build(workingLang)
                    .check(attributes)
                    .putAll(otherParamMap);
            return transformerParameters.getMap();
        }

    }

}
