/* BdfServer_Get - Copyright (c) 2011-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.get.streamproducers.transformation;

import fr.exemole.bdfserver.api.BdfServer;
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.get.streamproducers.ExtractionXmlProducer;
import fr.exemole.bdfserver.tools.exportation.table.BdfTableExportUtils;
import fr.exemole.bdfserver.tools.exportation.transformation.BdfTransformationUtils;
import fr.exemole.bdfserver.tools.exportation.transformation.DefaultExtractionDefFactory;
import fr.exemole.bdfserver.tools.exportation.transformation.TransformerParameters;
import fr.exemole.bdfserver.tools.users.BdfUserUtils;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
import net.fichotheque.corpus.FicheMeta;
import net.fichotheque.corpus.Fiches;
import net.fichotheque.exportation.table.Cell;
import net.fichotheque.exportation.table.CellConverter;
import net.fichotheque.exportation.table.SubsetTable;
import net.fichotheque.exportation.transformation.NoDefaultTemplateException;
import net.fichotheque.exportation.transformation.SimpleTemplate;
import net.fichotheque.exportation.transformation.StreamTemplate;
import net.fichotheque.exportation.transformation.TemplateKey;
import net.fichotheque.exportation.transformation.TransformationKey;
import net.fichotheque.extraction.ExtractParameters;
import net.fichotheque.extraction.ExtractionContext;
import net.fichotheque.extraction.ExtractionSource;
import net.fichotheque.extraction.def.ExtractionDef;
import net.fichotheque.tools.extraction.ExtractionDefFilterEngine;
import net.fichotheque.tools.extraction.ExtractionEngine;
import net.fichotheque.tools.extraction.ExtractionEngineUtils;
import net.mapeadores.util.attr.Attributes;
import net.mapeadores.util.io.StreamProducer;
import net.mapeadores.util.servlets.exceptions.BadRequestException;
import net.mapeadores.util.text.ValidExtension;
import net.mapeadores.util.xml.XMLUtils;
import net.mapeadores.util.xml.XmlProducer;


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

    private final BdfParameters bdfParameters;
    private final BdfServer bdfServer;
    private final Map<String, String> outputProperties = new HashMap<String, String>();
    private ExtractionContext extractionContext;
    private TransformationKey transformationKey;
    private Fiches staticFiches = null;
    private String templateName = "";
    private Object dynamicObject = null;
    private String sourceString = null;
    private ValidExtension streamExtension = null;
    private String extraXml = null;
    private Predicate<FicheMeta> fichePredicate = null;

    public TransformationFactory(BdfParameters bdfParameters) {
        this.bdfParameters = bdfParameters;
        this.bdfServer = bdfParameters.getBdfServer();
    }

    public TransformationFactory extractionContext(ExtractionContext extractionContext) {
        this.extractionContext = extractionContext;
        return this;
    }

    public TransformationFactory transformationKey(TransformationKey transformationKey) {
        this.transformationKey = transformationKey;
        return this;
    }

    public TransformationFactory dynamicObject(Object dynamicObject) {
        this.dynamicObject = dynamicObject;
        return this;
    }

    public TransformationFactory templateName(String templateName) {
        if (templateName == null) {
            templateName = "";
        }
        this.templateName = templateName;
        return this;
    }

    public TransformationFactory sourceString(String sourceString) {
        this.sourceString = sourceString;
        return this;
    }

    public TransformationFactory streamExtension(ValidExtension streamExtension) {
        this.streamExtension = streamExtension;
        return this;
    }

    public TransformationFactory extraXml(String extraXml) {
        this.extraXml = extraXml;
        return this;
    }

    public TransformationFactory staticFiches(Fiches staticFiches) {
        this.staticFiches = staticFiches;
        return this;
    }

    public TransformationFactory fichePredicate(Predicate<FicheMeta> fichePredicate) {
        this.fichePredicate = fichePredicate;
        return this;
    }

    public TransformationFactory outputProperty(String name, String value) {
        outputProperties.put(name, value);
        return this;
    }

    public XmlProducer getSourceXmlProducer() {
        XmlProducer xmlProducer;
        if (sourceString != null) {
            xmlProducer = XMLUtils.toXmlProducer(sourceString);
        } else {
            XmlParams xmlParams = resolveXmlExtractionDef();
            ExtractionSource extractionSource = ExtractionEngineUtils.getExtractionSource(dynamicObject, xmlParams.extractParameters.getExtractionContext(), xmlParams.extractionDef, staticFiches);
            xmlProducer = new ExtractionXmlProducer(xmlParams.extractParameters, xmlParams.extractionDef, extractionSource, extraXml);
        }
        return xmlProducer;
    }

    public StreamProducer getStreamProducer() {
        StreamProducer streamProducer;
        if (streamExtension == null) {
            streamProducer = getSimpleTemplateProducer(getSimpleTemplate());
        } else {
            StreamTemplate streamTemplate = getStreamTemplate();
            if (streamTemplate instanceof StreamTemplate.Xslt) {
                streamProducer = getXsltStreamTemplateProducer((StreamTemplate.Xslt) streamTemplate);
            } else if (streamTemplate instanceof StreamTemplate.Properties) {
                streamProducer = getPropertiesStreamTemplateProducer((StreamTemplate.Properties) streamTemplate);
            } else {
                throw new BadRequestException("_ error.unsupported.unknownimplementation", streamTemplate.getClass().getCanonicalName());
            }
        }
        return streamProducer;
    }

    public static TransformationFactory init(BdfParameters bdfParameters) {
        return new TransformationFactory(bdfParameters);
    }

    private SimpleTemplate getSimpleTemplate() {
        TemplateKey templateKey;
        boolean useDefault;
        if (templateName.isEmpty()) {
            useDefault = true;
            templateKey = BdfUserUtils.getSimpleTemplateKey(bdfParameters, transformationKey);
        } else {
            useDefault = false;
            try {
                templateKey = TemplateKey.parse(transformationKey, templateName);
            } catch (ParseException pe) {
                throw new BadRequestException("_ error.wrong.templatename", templateName);
            }
        }
        SimpleTemplate simpleTemplate = bdfServer.getTransformationManager().getSimpleTemplate(templateKey, useDefault);
        if (simpleTemplate == null) {
            throw new BadRequestException("_ error.unknown.template", templateName);
        }
        return simpleTemplate;
    }

    private StreamTemplate getStreamTemplate() {
        if (streamExtension == null) {
            throw new IllegalStateException("streamExtension is null");
        }
        TemplateKey templateKey;
        boolean useDefault;
        if (templateName.isEmpty()) {
            useDefault = true;
            templateKey = BdfUserUtils.getStreamTemplateKey(bdfParameters, transformationKey, streamExtension);
        } else {
            useDefault = false;
            try {
                templateKey = TemplateKey.parse(transformationKey, streamExtension, templateName);
            } catch (ParseException pe) {
                throw new BadRequestException("_ error.wrong.templatename", templateName);
            }
        }
        TransformationManager transformationManager = bdfServer.getTransformationManager();
        StreamTemplate streamTemplate;
        try {
            streamTemplate = transformationManager.getStreamTemplate(templateKey, useDefault);
            if (streamTemplate == null) {
                throw new BadRequestException("_ error.unknown.template", templateName);
            }
        } catch (NoDefaultTemplateException ndte) {
            throw new BadRequestException("_ error.unsupported.defaulttemplate", streamExtension.toString(), transformationKey.getKeyString());
        }
        return streamTemplate;
    }

    private XmlParams resolveXmlExtractionDef() {
        TemplateKey templateKey;
        boolean userTemplate;
        if (templateName.isEmpty()) {
            userTemplate = true;
            templateKey = BdfUserUtils.getSimpleTemplateKey(bdfParameters, transformationKey);
        } else {
            userTemplate = false;
            try {
                templateKey = TemplateKey.parse(transformationKey + "/" + templateName);
            } catch (ParseException pe) {
                throw new BadRequestException("_ error.wrong.templatename", templateName);
            }
        }
        TransformationManager transformationManager = bdfServer.getTransformationManager();
        Attributes templateAttributes;
        ExtractionDef resolvedExtractionDef;
        if (templateKey.isSimpleTemplate()) {
            SimpleTemplate simpleTemplate = transformationManager.getSimpleTemplate(templateKey, userTemplate);
            if (simpleTemplate == null) {
                throw new BadRequestException("_ error.unknown.template", templateName);
            }
            templateAttributes = simpleTemplate.getAttributes();
            resolvedExtractionDef = simpleTemplate.getCustomExtractionDef();
        } else {
            StreamTemplate streamTemplate;
            try {
                streamTemplate = transformationManager.getStreamTemplate(templateKey, userTemplate);
                if (streamTemplate == null) {
                    throw new BadRequestException("_ error.unknown.template", templateName);
                }
            } catch (NoDefaultTemplateException ndte) {
                throw new BadRequestException("_ error.unsupported.defaulttemplate", templateKey.getExtension(), transformationKey.getKeyString());
            }
            if (!(streamTemplate instanceof StreamTemplate.Xslt)) {
                throw new BadRequestException("_ error.unsupported.noextractiontemplate", templateName);
            }
            templateAttributes = ((StreamTemplate.Xslt) streamTemplate).getAttributes();
            resolvedExtractionDef = ((StreamTemplate.Xslt) streamTemplate).getCustomExtractionDef();
        }
        if (resolvedExtractionDef == null) {
            resolvedExtractionDef = DefaultExtractionDefFactory.newInstance(bdfServer, transformationKey, templateAttributes);
            if (resolvedExtractionDef == null) {
                throw new BadRequestException("_ error.unsupported.defaultextractiondef", transformationKey.getKeyString());
            }
        }
        resolvedExtractionDef = ExtractionDefFilterEngine.run(resolvedExtractionDef, extractionContext);
        ExtractParameters extractParameters = BdfTransformationUtils.buildExtractParameters(extractionContext, templateAttributes, bdfParameters, fichePredicate);
        return new XmlParams(extractParameters, resolvedExtractionDef);
    }

    private String getExtractionString(Attributes attributes, ExtractionDef customExtractionDef) {
        if (customExtractionDef == null) {
            customExtractionDef = DefaultExtractionDefFactory.newInstance(bdfServer, transformationKey, attributes);
        }
        customExtractionDef = ExtractionDefFilterEngine.run(customExtractionDef, extractionContext);
        ExtractParameters extractParameters = BdfTransformationUtils.buildExtractParameters(extractionContext, attributes, bdfParameters, fichePredicate);
        ExtractionSource extractionSource = ExtractionEngineUtils.getExtractionSource(dynamicObject, extractionContext, customExtractionDef, staticFiches);
        return ExtractionEngine.init(extractParameters, customExtractionDef).extraXml(extraXml).run(extractionSource);
    }

    public StreamProducer getSimpleTemplateProducer(SimpleTemplate simpleTemplate) {
        String extractionString;
        if (this.sourceString != null) {
            extractionString = sourceString;
        } else {
            extractionString = getExtractionString(simpleTemplate.getAttributes(), simpleTemplate.getCustomExtractionDef());
        }
        TransformerParameters transformerParameters = TransformerParameters.build(bdfParameters)
                .check(simpleTemplate.getAttributes())
                .put(TransformationConstants.BDF_FICHOTHEQUEPATH_PARAMETER, "../")
                .put(TransformationConstants.EXTERNALTARGET_PARAMETER, "_blank");
        return new SimpleTemplateProducer(null, extractionString, simpleTemplate, transformerParameters);
    }

    private StreamProducer getXsltStreamTemplateProducer(StreamTemplate.Xslt xslStreamTemplate) {
        String extractionString;
        if (this.sourceString != null) {
            extractionString = sourceString;
        } else {
            extractionString = getExtractionString(xslStreamTemplate.getAttributes(), xslStreamTemplate.getCustomExtractionDef());
        }
        TransformerParameters transformerParameters = TransformerParameters.build(bdfParameters)
                .check(xslStreamTemplate.getAttributes());
        return new XsltStreamTemplateProducer(null, extractionString, xslStreamTemplate, transformerParameters, outputProperties);
    }

    private StreamProducer getPropertiesStreamTemplateProducer(StreamTemplate.Properties streamTemplate) {
        if ((dynamicObject == null) || (!(dynamicObject instanceof FicheMeta))) {
            throw new BadRequestException("_ error.unsupported.ficheonlytemplate");
        }
        String tableExportName = streamTemplate.getTableExportName();
        FicheMeta ficheMeta = (FicheMeta) dynamicObject;
        CellConverter cellConverter;
        if (tableExportName.isEmpty()) {
            SubsetTable subsetTable = BdfTableExportUtils.toDefaultSubsetTable(bdfServer, ficheMeta.getCorpus(), BdfTableExportUtils.ALL_FICHETABLEPARAMETERS, bdfParameters.getPermissionSummary());
            cellConverter = BdfTableExportUtils.toCellConverter(bdfServer, extractionContext, subsetTable);
        } else {
            cellConverter = BdfTableExportUtils.getCellConverterFromName(bdfServer, extractionContext, tableExportName);
            if (cellConverter == null) {
                throw new BadRequestException("_ error.unknown.tablexport", tableExportName);
            }
        }
        Cell[] cellArray = cellConverter.toCellArray(ficheMeta);
        if (cellArray == null) {
            throw new BadRequestException("_ error.empty.subsettable", tableExportName, ficheMeta.getSubsetKey());
        }
        return new PropertiesStreamTemplateProducer(null, cellArray, streamTemplate);
    }


    private static class XmlParams {

        private final ExtractParameters extractParameters;
        private final ExtractionDef extractionDef;

        private XmlParams(ExtractParameters extractParameters, ExtractionDef extractionDef) {
            this.extractParameters = extractParameters;
            this.extractionDef = extractionDef;
        }

    }

}
