/* OdLib_Io - Copyright (c) 2015-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 net.mapeadores.opendocument.transform;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;
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 javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import net.mapeadores.opendocument.io.ByteArrayOdSource;
import net.mapeadores.opendocument.io.CharSequenceOdSource;
import net.mapeadores.opendocument.io.OdUtils;
import net.mapeadores.opendocument.io.OdZip;
import net.mapeadores.opendocument.io.OdZipEngine;
import net.mapeadores.util.exceptions.NestedTransformerException;
import net.mapeadores.util.io.BufferErrorListener;
import net.mapeadores.util.io.IOUtils;
import net.mapeadores.util.io.StreamTransformer;
import net.mapeadores.util.io.StreamTransformerErrorHandler;


/**
 *
 * @author Vincent Calame
 */
public class ODStreamTransformerBuilder {

    private final short odType;
    private final String contentURI;
    private final URIResolver uriResolver;
    private StreamTransformerErrorHandler streamTransformerErrorHandler;
    private StreamSource streamSource;
    private byte[] stylesByteArray;
    private byte[] contentAutomaticStylesByteArray;
    private PictureHandler pictureHandler;

    public ODStreamTransformerBuilder(String extension, String contentURI, URIResolver uriResolver) {
        if (extension == null) {
            throw new IllegalArgumentException("extension is null");
        }
        if (contentURI == null) {
            throw new IllegalArgumentException("contentURI is null");
        }
        if (uriResolver == null) {
            throw new IllegalArgumentException("uriResolver is null");
        }
        this.odType = OdUtils.getOdType(extension);
        this.contentURI = contentURI;
        this.uriResolver = uriResolver;
    }

    public ODStreamTransformerBuilder setStyles(InputStream inputStream) throws IOException {
        setStyles(IOUtils.toByteArray(inputStream));
        return this;
    }

    public ODStreamTransformerBuilder setStyles(byte[] byteArray) {
        this.stylesByteArray = byteArray;
        return this;
    }

    public ODStreamTransformerBuilder setContentAutomaticStyles(InputStream inputStream) throws IOException {
        setContentAutomaticStyles(IOUtils.toByteArray(inputStream));
        return this;
    }

    public ODStreamTransformerBuilder setContentAutomaticStyles(byte[] byteArray) {
        this.contentAutomaticStylesByteArray = byteArray;
        return this;
    }

    public ODStreamTransformerBuilder setContentXsl(byte[] byteArray) {
        streamSource = new StreamSource(new ByteArrayInputStream(byteArray), contentURI);
        return this;
    }

    public ODStreamTransformerBuilder setContentXsl(String s) {
        streamSource = new StreamSource(new StringReader(s), contentURI);
        return this;
    }

    public ODStreamTransformerBuilder setContentXsl(InputStream inputStream) {
        streamSource = new StreamSource(inputStream, contentURI);
        return this;
    }

    public ODStreamTransformerBuilder setStreamTransformerErrorHandler(StreamTransformerErrorHandler streamTransformerErrorHandler) {
        this.streamTransformerErrorHandler = streamTransformerErrorHandler;
        return this;
    }

    public ODStreamTransformerBuilder setPictureHandler(PictureHandler pictureHandler) {
        this.pictureHandler = pictureHandler;
        return this;
    }

    public StreamTransformer toStreamTransformer() {
        URIResolver internalUriResolver = new InternalURIResolver(stylesByteArray, contentAutomaticStylesByteArray, this.uriResolver);
        Templates contentTemplate = initTemplates(streamSource, internalUriResolver);
        return new ODStreamTransformer(odType, contentTemplate, stylesByteArray, internalUriResolver, pictureHandler);
    }

    private Templates initTemplates(Source source, URIResolver uriResolver) {
        BufferErrorListener errorListener = new BufferErrorListener();
        TransformerFactory tFactory = TransformerFactory.newInstance();
        tFactory.setURIResolver(uriResolver);
        tFactory.setErrorListener(errorListener);
        try {
            Templates contentTemplate = tFactory.newTemplates(source);
            if (streamTransformerErrorHandler != null) {
                for (TransformerException te : errorListener.getErrorList()) {
                    streamTransformerErrorHandler.transformerError(te);
                }
                for (TransformerException te : errorListener.getWarningList()) {
                    streamTransformerErrorHandler.transformerWarning(te);
                }
            }
            return contentTemplate;
        } catch (TransformerException te) {
            streamTransformerErrorHandler.transformerError(te);
            return null;
        }
    }

    public static ODStreamTransformerBuilder init(String extension, String contentURI, URIResolver uriResolver) {
        return new ODStreamTransformerBuilder(extension, contentURI, uriResolver);
    }


    private static class ODStreamTransformer implements StreamTransformer {

        private final short odType;
        private final URIResolver uriResolver;
        private final Templates contentTemplate;
        private final byte[] stylesByteArray;
        private final PictureHandler pictureHandler;

        private ODStreamTransformer(short odType, Templates contentTemplate, byte[] stylesByteArray, URIResolver uriResolver, PictureHandler pictureHandler) {
            this.odType = odType;
            this.contentTemplate = contentTemplate;
            this.stylesByteArray = stylesByteArray;
            this.uriResolver = uriResolver;
            this.pictureHandler = pictureHandler;
        }

        @Override
        public String getMimeType() {
            return OdUtils.getMimeType(odType);
        }

        @Override
        public String getExtension() {
            return OdUtils.getExtension(odType);
        }

        @Override
        public void transform(Source source, OutputStream outputStream, Map<String, Object> transformerParameters, Map<String, String> outputProperties) throws TransformerException {
            OdZip odZip = OdZip.init(odType);
            if (stylesByteArray != null) {
                odZip.stylesOdSource(new ByteArrayOdSource(stylesByteArray));
            }
            Transformer transformer = contentTemplate.newTransformer();
            transformer.setURIResolver(uriResolver);
            for (Map.Entry<String, String> entry : outputProperties.entrySet()) {
                transformer.setOutputProperty(entry.getKey(), entry.getValue());
            }
            for (Map.Entry<String, Object> entry : transformerParameters.entrySet()) {
                transformer.setParameter(entry.getKey(), entry.getValue());
            }
            StringWriter writer = new StringWriter();
            transformer.transform(source, new StreamResult(writer));
            StringBuffer buf = writer.getBuffer();
            if (pictureHandler != null) {
                pictureHandler.check(buf);
                odZip.pictures(pictureHandler.toPictures());
            }
            odZip.contentOdSource(new CharSequenceOdSource(buf, "UTF-8"));
            try {
                OdZipEngine.run(outputStream, odZip);
            } catch (IOException ioe) {
                throw new TransformerException(ioe);
            } catch (NestedTransformerException nte) {
                throw nte.getTransformerException();
            }
        }

    }


    private static class InternalURIResolver implements URIResolver {

        private final URIResolver originalUriResolver;
        private final byte[] stylesByteArray;
        private final byte[] contentAutomaticStylesByteArray;

        private InternalURIResolver(byte[] stylesByteArray, byte[] contentAutomaticStylesByteArray, URIResolver originalUriResolver) {
            this.stylesByteArray = stylesByteArray;
            this.contentAutomaticStylesByteArray = contentAutomaticStylesByteArray;
            this.originalUriResolver = originalUriResolver;
        }

        @Override
        public Source resolve(String href, String base) throws TransformerException {
            if (href.equals("_content_automaticstyles.xml")) {
                if (contentAutomaticStylesByteArray != null) {
                    return new StreamSource(new ByteArrayInputStream(contentAutomaticStylesByteArray));
                }
            } else if (href.equals("styles.xml")) {
                if (stylesByteArray != null) {
                    return new StreamSource(new ByteArrayInputStream(stylesByteArray));
                }
            }
            Source source = originalUriResolver.resolve(href, base);
            if (source == null) {
                throw new TransformerException("(base: " + base + ")");
            } else {
                return source;
            }
        }

    }


}
