/* 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.io;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import net.mapeadores.util.exceptions.InternalResourceException;
import net.mapeadores.util.exceptions.SwitchException;
import net.mapeadores.util.io.IOUtils;
import net.mapeadores.util.xml.AppendableXMLWriter;
import net.mapeadores.util.xml.XMLPart;
import net.mapeadores.util.xml.XMLUtils;
import net.mapeadores.util.xml.XMLWriter;


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

    private final ZipOutputStream zipOutputStream;
    private final OdZip odZip;

    private OdZipEngine(ZipOutputStream zipOutputStream, OdZip odZip) {
        this.zipOutputStream = zipOutputStream;
        this.odZip = odZip;
    }

    public static void run(OutputStream destination, OdZip odZip) throws IOException {
        try (ZipOutputStream os = new ZipOutputStream(destination)) {
            OdZipEngine engine = new OdZipEngine(os, odZip);
            engine.run();
        }
    }

    private void run() throws IOException {
        zipManifest();
        zipContent();
        zipStyles();
        zipSettings();
        zipPictures();
    }

    private void zipManifest() throws IOException {
        ZipEntry zipEntry = new ZipEntry("META-INF/manifest.xml");
        zipOutputStream.putNextEntry(zipEntry);
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(zipOutputStream, "UTF-8"));
        AppendableXMLWriter xmlWriter = XMLUtils.toXMLWriter(writer);
        XMLUtils.appendXmlDeclaration(xmlWriter);
        ManifestXMLPart manifestXMLPart = new ManifestXMLPart(xmlWriter, odZip);
        manifestXMLPart.writeManifest();
        writer.flush();
        zipOutputStream.closeEntry();
    }

    private void zipContent() throws IOException {
        OdSource contentOdSource = odZip.contentOdSource();
        ZipEntry zipEntry = new ZipEntry("content.xml");
        zipOutputStream.putNextEntry(zipEntry);
        if (contentOdSource != null) {
            contentOdSource.writeStream(zipOutputStream);
        } else {
            try (InputStream is = getResourceStream("empty-content.xml")) {
                IOUtils.copy(is, zipOutputStream);
            }
        }
        zipOutputStream.closeEntry();
    }

    private void zipStyles() throws IOException {
        OdSource stylesOdSource = odZip.stylesOdSource();
        ZipEntry zipEntry = new ZipEntry("styles.xml");
        zipOutputStream.putNextEntry(zipEntry);
        if (stylesOdSource != null) {
            stylesOdSource.writeStream(zipOutputStream);
        } else {
            try (InputStream is = getResourceStream("empty-styles.xml")) {
                IOUtils.copy(is, zipOutputStream);
            }
        }
        zipOutputStream.closeEntry();
    }

    private void zipSettings() throws IOException {
        OdSource settingsOdSource = odZip.settingsOdSource();
        if (settingsOdSource == null) {
            return;
        }
        ZipEntry zipEntry = new ZipEntry("settings.xml");
        zipOutputStream.putNextEntry(zipEntry);
        settingsOdSource.writeStream(zipOutputStream);
        zipOutputStream.closeEntry();
    }

    private void zipPictures() throws IOException {
        Pictures pictures = odZip.pictures();
        if (pictures == null) {
            return;
        }
        for (Pictures.Entry picture : pictures.getEntryList()) {
            ZipEntry zipEntry = new ZipEntry(picture.getHref());
            zipOutputStream.putNextEntry(zipEntry);
            picture.getOdSource().writeStream(zipOutputStream);
        }
    }

    private InputStream getResourceStream(String name) {
        InputStream inputStream = getClass().getResourceAsStream("resources/" + name);
        if (inputStream == null) {
            throw new InternalResourceException("resources/" + name);
        }
        return inputStream;
    }

    private static String getMediaType(short odType) {
        switch (odType) {
            case OdUtils.OD_TEXT:
                return "application/vnd.oasis.opendocument.text";
            case OdUtils.OD_SPREADSHEET:
                return "application/vnd.oasis.opendocument.spreadsheet";
            default:
                throw new SwitchException("unknown odType = " + odType);

        }
    }


    private static class ManifestXMLPart extends XMLPart {

        private final OdZip odZip;

        private ManifestXMLPart(XMLWriter xmlWriter, OdZip odZip) {
            super(xmlWriter);
            this.odZip = odZip;
        }

        public void writeManifest() throws IOException {
            startOpenTag("manifest:manifest");
            addAttribute("xmlns:manifest", "urn:oasis:names:tc:opendocument:xmlns:manifest:1.0");
            endOpenTag();
            appendFileEntry(getMediaType(odZip.odType()), "/");
            appendFileEntry("text/xml", "content.xml");
            appendFileEntry("text/xml", "styles.xml");
            OdSource settingsOdSource = odZip.settingsOdSource();
            if (settingsOdSource != null) {
                appendFileEntry("text/xml", "settings.xml");
            }
            Pictures pictures = odZip.pictures();
            if (pictures != null) {
                for (Pictures.Entry picture : pictures.getEntryList()) {
                    appendFileEntry("", picture.getHref());
                }
            }
            closeTag("manifest:manifest");
        }

        private void appendFileEntry(String mediaType, String fullPath) throws IOException {
            startOpenTag("manifest:file-entry");
            addAttribute("manifest:media-type", mediaType);
            addAttribute("manifest:full-path", fullPath);
            closeEmptyTag();
        }

    }

}
