/* BdfServer - Copyright (c) 2010-2024 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.balayage.engine.runners;

import fr.exemole.bdfserver.api.BdfServer;
import fr.exemole.bdfserver.api.balayage.BalayageTemplate;
import fr.exemole.bdfserver.api.balayage.FileGenerationListener;
import fr.exemole.bdfserver.api.balayage.TemplateProvider;
import fr.exemole.bdfserver.tools.balayage.TemplateProviderBuilder;
import fr.exemole.bdfserver.tools.balayage.engine.BalayageLog;
import fr.exemole.bdfserver.tools.balayage.engine.BalayageParameters;
import fr.exemole.bdfserver.tools.balayage.engine.EngineUtils;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import net.fichotheque.exportation.balayage.BalayageConstants;
import net.fichotheque.exportation.balayage.BalayageDef;
import net.fichotheque.exportation.balayage.BalayageOutput;
import net.fichotheque.exportation.balayage.BalayageUnit;
import net.mapeadores.util.io.BufferErrorListener;
import net.mapeadores.util.localisation.LangContext;
import net.mapeadores.util.localisation.Langs;
import net.mapeadores.util.localisation.ListLangContext;
import net.mapeadores.util.localisation.UserLangContext;
import net.mapeadores.util.text.ValueResolver;
import net.mapeadores.util.xml.json.XmlToJson;


/**
 *
 * @author Vincent Calame
 */
public class TransformOutputRunner extends AbstractOutputRunner {

    private final BalayageUnit balayageUnit;
    private final FileGenerationListener fileGenerationListener;
    private final BalayageDef balayageDef;
    private String langOption = BalayageConstants.LANGOPTION_UNKNOWN;
    private String[] langArray;
    private TemplateProvider templateProvider;
    private Transformer posttransformTransformer;

    public TransformOutputRunner(BalayageParameters balayageParameters, int outputIndex, BalayageOutput output, int balayageUnitIndex) {
        super(balayageParameters, outputIndex, output, balayageUnitIndex);
        this.balayageUnit = output.getBalayageUnit();
        BdfServer bdfServer = balayageParameters.getBdfServer();
        balayageDef = balayageParameters.getBalayageDef();
        this.fileGenerationListener = balayageParameters.getFileGenerationListener();
        initLangArray();
        langOption = output.getLangOption();
        if (langOption.equals(BalayageConstants.LANGOPTION_UNKNOWN)) {
            langOption = balayageDef.getDefaultLangOption();
        }
        if (!balayageDef.ignoreTransformation()) {
            InternalTemplateProviderBuilderErrorHandler transformerBuilderErrorHandler = new InternalTemplateProviderBuilderErrorHandler();
            TemplateProviderBuilder templateProviderBuilder = new TemplateProviderBuilder(bdfServer, balayageParameters.getPathConfiguration(), balayageDef, transformerBuilderErrorHandler);
            templateProviderBuilder.setCustomURIResolver(balayageParameters.getCustomURIResolver());
            templateProvider = templateProviderBuilder.build(output);
            if (templateProvider != null) {
                File balayageRootDirectory = balayageParameters.getBalayageRootDirectory();
                String absolutePath = balayageRootDirectory.getAbsolutePath();
                if (!absolutePath.endsWith(File.separator)) {
                    absolutePath = absolutePath + File.separator;
                }
                templateProvider.setBalayageRoot(absolutePath);
            }
            String posttransform = output.getPosttransform();
            if (!posttransform.isEmpty()) {
                posttransformTransformer = XmlToJson.newTransformer(posttransform);
            }
        }
    }


    private class InternalTemplateProviderBuilderErrorHandler implements TemplateProviderBuilder.ErrorHandler {

        private InternalTemplateProviderBuilderErrorHandler() {
        }

        @Override
        public void transformerError(String xsltPath, BufferErrorListener errorListener) {
            isValid = false;
            balayageLog.addOutputError(balayageUnitIndex, outputIndex, output, BalayageLog.TRANSFORMER_EXCEPTION, xsltPath, errorListener.getErrorMessage());
        }

        @Override
        public void wrongXslPath(String xsltPath) {
            balayageLog.addBalayageUnitError(balayageUnitIndex, balayageUnit, BalayageLog.TRANSFORMER_NOT_FOUND, xsltPath, null);
            isValid = false;
        }

        @Override
        public void undefinedXslPath() {
            balayageLog.addOutputError(balayageUnitIndex, outputIndex, output, BalayageLog.NO_XSLTPATH, null, null);
            isValid = false;
        }

        @Override
        public void wrongBalayageUnitType() {
        }

    }

    private void initLangArray() {
        Langs langs = output.getCustomLangs();
        if (langs != null) {
            langArray = langs.toStringArray();
        } else {
            LangContext langContext = balayageParameters.getExtractParameters().getExtractionContext().getLangContext();
            if (langContext instanceof ListLangContext) {
                ListLangContext listLangContext = (ListLangContext) langContext;
                int langSize = listLangContext.size();
                langArray = new String[langSize];
                for (int i = 0; i < langSize; i++) {
                    langArray[i] = listLangContext.get(i).getLang().toString();
                }
            } else if (langContext instanceof UserLangContext) {
                UserLangContext userLangContext = (UserLangContext) langContext;
                langArray = new String[1];
                langArray[0] = userLangContext.getWorkingLang().toString();
            } else {
                langArray = new String[0];
            }
        }
    }

    public void write(Object extractionObject, ValueResolver valueResolver, String extractionString) {
        if ((balayageDef.ignoreTransformation()) && (!(extractionString instanceof String))) {
            throw new IllegalStateException("withTransformation may  be false only if extractionResult is an instance of String");
        }
        String fileName = EngineUtils.getFileName(output, valueResolver, output.getBalayageUnit().getType(), !balayageDef.ignoreTransformation());
        String path = EngineUtils.getPath(output, valueResolver);
        if (langOption.equals(BalayageConstants.LANGOPTION_NONE)) {
            write(extractionObject, fileName, path, langArray[0], extractionString);
        } else {
            for (String lang : langArray) {
                write(extractionObject, fileName, path, lang, extractionString);
            }
        }
    }

    private void write(Object extractionObject, String fileName, String path, String lang, String extractionString) {
        File directory = rootDirectory;
        if (langOption.equals(BalayageConstants.LANGOPTION_DIR)) {
            directory = new File(directory, lang);
        }
        if (path != null) {
            directory = new File(directory, path);
        }
        prepareDirectory(directory);
        File file = new File(directory, fileName);
        boolean done;
        if (templateProvider != null) {
            templateProvider.setCurrentFileName(fileName);
            templateProvider.setCurrentLang(lang);
            templateProvider.setCurrentPath(path);
            done = writeTransformation(extractionObject, file, extractionString);
        } else {
            done = writeXml(file, (String) extractionString);
        }
        if (done) {
            if (fileGenerationListener != null) {
                fileGenerationListener.generateFile(lang, path, fileName);
            }
            balayageLog.addFileGeneration(balayageUnitIndex, outputIndex, file);
        }
    }

    boolean writeXml(File file, String extractionString) {
        try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) {
            writer.write(extractionString);
            return true;
        } catch (IOException ioe) {
            String msg = ioe.getMessage();
            String message = ioe.getClass().getName();
            if (msg != null) {
                message = message + " : " + msg;
            }
            balayageLog.addOutputError(balayageUnitIndex, outputIndex, output, BalayageLog.IO_EXCEPTION, file.getName(), message);
            return false;
        }
    }

    private boolean writeTransformation(Object extractionObject, File file, String extractionString) {
        try (OutputStream os = new BufferedOutputStream(new FileOutputStream(file))) {
            OutputStream outputStream;
            ByteArrayOutputStream bufOuputStream;
            if (posttransformTransformer == null) {
                outputStream = os;
                bufOuputStream = null;
            } else {
                bufOuputStream = new ByteArrayOutputStream();
                outputStream = bufOuputStream;
            }
            BalayageTemplate balayageTemplate = templateProvider.getBalayageTemplate(extractionObject);
            balayageTemplate.transform(extractionString, outputStream);
            if (posttransformTransformer != null) {
                Source source = new StreamSource(new ByteArrayInputStream(bufOuputStream.toByteArray()));
                posttransformTransformer.transform(source, new StreamResult(os));
            }
            return true;
        } catch (Exception e) {
            Throwable throwable = getCause(e);
            String msg = throwable.getMessage();
            String message = throwable.getClass().getName();
            if (msg != null) {
                message = message + " : " + msg;
            }
            balayageLog.addOutputError(balayageUnitIndex, outputIndex, output, BalayageLog.TRANSFORMATION_EXCEPTION, file.getName(), message);
            return false;
        }
    }

    @Override
    public void dispose() {
        super.dispose();
        templateProvider = null;
        langArray = null;
    }

    private Throwable getCause(Throwable e) {
        Throwable throwable = e.getCause();
        if (throwable == null) {
            return e;
        } else {
            return getCause(throwable);
        }
    }

}
