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

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.configuration.PathConfiguration;
import fr.exemole.bdfserver.api.exportation.transformation.StreamTemplateFactory;
import fr.exemole.bdfserver.api.exportation.transformation.TransformationConstants;
import fr.exemole.bdfserver.api.storage.TemplateStorage;
import fr.exemole.bdfserver.tools.BdfURI;
import fr.exemole.bdfserver.tools.exportation.transformation.BdfPictureHandler;
import fr.exemole.bdfserver.tools.exportation.transformation.XsltStreamTemplateFactory;
import fr.exemole.bdfserver.tools.exportation.transformation.defaulttemplates.DefaultTemplateUtils;
import java.io.IOException;
import java.io.InputStream;
import javax.xml.transform.TransformerException;
import net.fichotheque.exportation.transformation.TemplateKey;
import net.fichotheque.extraction.def.ExtractionDef;
import net.fichotheque.tools.exportation.transformation.TemplateContentDescriptionBuilder;
import net.mapeadores.opendocument.css.output.StylesConversionEngine;
import net.mapeadores.opendocument.elements.ElementMaps;
import net.mapeadores.opendocument.transform.ODStreamTransformerBuilder;
import net.mapeadores.util.exceptions.ShouldNotOccurException;
import net.mapeadores.util.io.IOUtils;
import net.mapeadores.util.io.StreamTransformer;
import net.mapeadores.util.io.StreamTransformerErrorHandler;
import net.mapeadores.util.logging.LogUtils;
import net.mapeadores.util.logging.MessageHandler;
import net.mapeadores.util.xml.XMLUtils;


/**
 *
 * @author Vincent Calame
 */
public class XsltOdStreamTemplateCompiler extends StreamTemplateCompiler {

    private final static String CCS_MARKUP = "<!-- CSS -->";
    private final static String CONTENT_XSL = "content.xsl";
    private final static String EXTRACTION_PATH = "extraction.xml";
    private final static String STYLES_CSS = "styles.css";
    private final static String STYLES_XML = "styles.xml";

    public XsltOdStreamTemplateCompiler(BdfServer bdfServer, PathConfiguration pathConfiguration, TemplateKey templateKey, MessageHandler messageHandler) {
        super(bdfServer, pathConfiguration, templateKey, messageHandler);
    }

    @Override
    public StreamTemplateFactory compile(TemplateStorage.Unit storageUnit) throws CompilationException {
        Contents contents = new Contents(storageUnit);
        ExtractionDef customExtractionDef = getExtractionDef(storageUnit, EXTRACTION_PATH, false);
        StreamTransformer streamTransformer = contents.getStreamTransformer();
        return new XsltStreamTemplateFactory(storageUnit.getTemplateDef().getAttributes(), customExtractionDef, streamTransformer);
    }


    private class Contents {

        private final TemplateStorage.Unit storageUnit;
        private final String baseURI;
        private String contentXslt;
        private byte[] stylesByteArray;
        private byte[] automaticStylesByteArray;


        Contents(TemplateStorage.Unit storageUnit) {
            this.storageUnit = storageUnit;
            this.baseURI = TemplateCompilerUtils.getBaseURI(storageUnit.getTemplateKey());
        }

        private StreamTransformer getStreamTransformer() throws CompilationException {
            TemplateContentDescriptionBuilder xsltDescriptionBuilder = initContentXslt();
            initStyles();
            String systemId = baseURI + CONTENT_XSL;
            InternalStreamTransformerErrorHandler streamTransformerErrorHandler = new InternalStreamTransformerErrorHandler(systemId, xsltDescriptionBuilder);
            ODStreamTransformerBuilder odStreamTransformerBuilder = ODStreamTransformerBuilder
                    .init(storageUnit.getTemplateKey().getExtension(), systemId, BdfURI.getURIResolver(bdfServer, pathConfiguration))
                    .setStreamTransformerErrorHandler(streamTransformerErrorHandler)
                    .setPictureHandler(new BdfPictureHandler(bdfServer, pathConfiguration))
                    .setContentXsl(contentXslt);
            if (stylesByteArray != null) {
                odStreamTransformerBuilder.setStyles(stylesByteArray);
            }
            if (automaticStylesByteArray != null) {
                odStreamTransformerBuilder.setContentAutomaticStyles(automaticStylesByteArray);
            }
            StreamTransformer streamTransformer = odStreamTransformerBuilder.toStreamTransformer();
            if (streamTransformerErrorHandler.withError()) {
                throw new CompilationException();
            }
            return streamTransformer;
        }

        private String initTemplateContent(String name) {
            InputStream inputStream;
            try {
                inputStream = TemplateCompilerUtils.getContentInputStream(storageUnit, name);
            } catch (IOException ioe) {
                addError("_ error.exception.transformation_io", name, ioe.getMessage());
                return null;
            }
            if (inputStream == null) {
                return null;
            }
            try (InputStream is = inputStream) {
                String result = IOUtils.toString(is, "UTF-8");
                return result;
            } catch (IOException ioe) {
                addError("_ error.exception.transformation_io", name, ioe.getMessage());
                return null;
            }
        }

        private String initStylesCss() {
            String stylesCss = initTemplateContent(STYLES_CSS);
            if (stylesCss == null) {
                stylesCss = DefaultTemplateUtils.getDefaultStylesCss();
            } else {
                TemplateContentDescriptionBuilder templateContentDescriptionBuilder = newTemplateContentDescriptionBuilder(STYLES_CSS, false);
            }
            ElementMaps elementMaps = parseCss(stylesCss, baseURI, STYLES_CSS);
            String automaticStylesString = StylesConversionEngine.toAutomaticStylesXml(elementMaps);
            try {
                automaticStylesByteArray = automaticStylesString.getBytes("UTF-8");
            } catch (IOException ioe) {
                throw new ShouldNotOccurException(ioe);
            }
            StringBuilder stylesBuf = new StringBuilder();
            try {
                StylesConversionEngine.appendAllStyles(stylesBuf, elementMaps);
            } catch (IOException ioe) {
                throw new ShouldNotOccurException(ioe);
            }
            return stylesBuf.toString();
        }

        private void initStyles() throws CompilationException {
            String stylesFragment = initStylesCss();
            String stylesXml = initTemplateContent(STYLES_XML);
            if (stylesXml == null) {
                stylesXml = DefaultTemplateUtils.getDefaultStylesXML(bdfServer);
                String testError = XMLUtils.testXml(stylesXml);
                if (testError != null) {
                    addError("_ error.wrong.transformation.xml", BdfURI.toAbsoluteBdfURI("xml/odtstyles.xml"), testError);
                    throw new CompilationException();
                }
            } else {
                TemplateContentDescriptionBuilder templateContentDescriptionBuilder = newTemplateContentDescriptionBuilder(STYLES_XML, true);
                if (!TemplateCompilerUtils.testXml(stylesXml, templateContentDescriptionBuilder)) {
                    throw new CompilationException();
                }
            }
            int idx = stylesXml.indexOf(CCS_MARKUP);
            String completeXml = stylesXml;
            if (idx != -1) {
                completeXml = stylesXml.substring(0, idx) + stylesFragment + stylesXml.substring(idx + CCS_MARKUP.length());
            }
            try {
                stylesByteArray = completeXml.getBytes("UTF-8");
            } catch (IOException ioe) {
                throw new ShouldNotOccurException(ioe);
            }
        }

        private TemplateContentDescriptionBuilder initContentXslt() throws CompilationException {
            contentXslt = initTemplateContent(CONTENT_XSL);
            if (contentXslt == null) {
                addError("_ error.empty.transformation.ressource", CONTENT_XSL);
                throw new CompilationException();
            }
            TemplateContentDescriptionBuilder templateContentDescriptionBuilder = newTemplateContentDescriptionBuilder(CONTENT_XSL, true);
            if (!TemplateCompilerUtils.testXml(contentXslt, templateContentDescriptionBuilder)) {
                throw new CompilationException();
            }
            return templateContentDescriptionBuilder;
        }

    }


    private class InternalStreamTransformerErrorHandler implements StreamTransformerErrorHandler {

        private final String systemId;
        private final TemplateContentDescriptionBuilder templateContentDescriptionBuilder;
        private boolean withError = false;

        private InternalStreamTransformerErrorHandler(String systemId, TemplateContentDescriptionBuilder templateContentDescriptionBuilder) {
            this.systemId = systemId;
            this.templateContentDescriptionBuilder = templateContentDescriptionBuilder;
        }

        @Override
        public void missingResourceError(String resourceURI) {
            addError("_ error.empty.transformation.ressource", resourceURI);
            withError = true;
        }

        @Override
        public void transformerError(TransformerException transformerException) {
            LogUtils.handleTransformerException(TransformationConstants.ERROR_CATEGORY, transformerException, systemId, templateContentDescriptionBuilder);
            withError = true;
        }

        @Override
        public void transformerWarning(TransformerException transformerException) {
            LogUtils.handleTransformerException(TransformationConstants.WARNING_CATEGORY, transformerException, systemId, templateContentDescriptionBuilder);
        }

        private boolean withError() {
            return withError;
        }

    }

}
