
package de.ivu.elect.business.dokumente.control;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.inject.Inject;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.document.generator.template.OpenOfficeKonfiguration;
import de.ivu.elect.business.datei.entity.BinaryUnit;
import de.ivu.elect.business.datei.entity.DateiExport;
import de.ivu.elect.business.datei.entity.DateiFileNames;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.datei.entity.FileTypeResolver;
import de.ivu.elect.business.dokumente.entity.DokumentGeneratorArt;
import de.ivu.elect.business.dokumente.entity.DokumentGenerierenProgress;
import de.ivu.elect.business.dokumente.entity.DokumentKonverterResult;
import de.ivu.elect.business.dokumente.entity.FileContent;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.exchange.ExchangeOdtKonvertierer;


import static de.ivu.elect.business.dokumente.entity.DokumentGeneratorArt.OPEN_OFFICE_GENERATOR;
import static de.ivu.elect.business.wahl.entity.WahlOderWahltermin.from;

public class OpenOfficeDokumentMerger extends AbstractDokumentMerger implements DokumentMerger {

  private static final Logger LOG = LoggerFactory.getLogger(OpenOfficeDokumentMerger.class);

  private static final int BUFFER_SIZE_IN_BYTES = BinaryUnit.bytesOf(64, BinaryUnit.KIBIBYTE);

  @Inject
  private transient FileTypeResolver fileTypeResolver;

  @Inject
  private transient OpenOfficeKonfigurationFactory openOfficeDokumentKonfiguration;

  @Inject
  private transient DokumentGenerierenSupportedOpenOfficeFileFormats dokumentGenerierenSupportedOpenOfficeFileFormats;

  @Inject
  private transient LayoutVorlageFinder layoutVorlageFinder;

  @Inject
  private transient ExchangeOdtKonvertierer exchangeOdtKonvertierer;

  public OpenOfficeDokumentMerger() {
    super();
  }

  @Override
  public DokumentGeneratorArt getArt() {
    return OPEN_OFFICE_GENERATOR;
  }

  @Override
  public DateiExport generiereDatei(File templateFile, DokumentKonverterResult contentData, DokumentExtraFileOutput dokumentExtraFileOutput, Wahl wahl, Wahltermin wahltermin, FileType zieldateiTyp,
      DateiFileNames dateiFileNames, ProgressMonitor monitor) {
    if (!dokumentGenerierenSupportedOpenOfficeFileFormats.getSupportedFileTypes().contains(zieldateiTyp)) {
      throw new UnsupportedOperationException("Nur die Dateitypen " + dokumentGenerierenSupportedOpenOfficeFileFormats.getSupportedFileTypes() + " werden unterstützt, nicht aber: " + zieldateiTyp);
    }

    byte[] vorlageContent = layoutVorlageFinder.getVorlageContent(from(wahltermin, wahl));

    try {
      if (isDokumentVorlageArchive(templateFile)) {
        return generiereOpenOfficeZipDatei(contentData, dokumentExtraFileOutput, zieldateiTyp, wahl, wahltermin, dateiFileNames, monitor, templateFile, vorlageContent);
      } else {
        return generiereOpenOfficeEinzelneDatei(contentData, dokumentExtraFileOutput, zieldateiTyp, wahl, wahltermin, dateiFileNames, monitor, templateFile, vorlageContent);
      }
    } catch (IOException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  private boolean isDokumentVorlageArchive(File templateFile) {
    return fileTypeResolver.getFileType(templateFile).orElse(FileType.ODT).isDokumentVorlageArchive();
  }

  private DateiExport generiereOpenOfficeZipDatei(DokumentKonverterResult contentData, DokumentExtraFileOutput dokumentExtraFileOutput, FileType zieldateiTyp, Wahl wahl, Wahltermin wahltermin,
      DateiFileNames dateiFileNames, ProgressMonitor monitor, File zipTemplateFile, byte[] vorlageContent) throws IOException {
    DateiExport dateiExport = createDateiExport(wahl, wahltermin, FileType.ZIP, dateiFileNames);

    OpenOfficeKonfiguration konfiguration = getKonfiguration();

    try (OutputStream exportOs = createOutputStreamAndDateiExport(dateiExport)) {
      try (ZipOutputStream zos = new ZipOutputStream(exportOs)) {

        boolean isPlatzhalterErsetzenInOdmDatei = true;
        boolean isPlatzhalterErsetzenInOttDatei = true;
        try (FileInputStream fis = new FileInputStream(zipTemplateFile)) {
          try (ZipInputStream zis = new ZipInputStream(fis)) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
              String entryName = entry.getName();
              // Durch diese Dateien wird die Konvertierung von .odm Dateien "ausgeschaltet"
              if ("Platzhalter-nicht-ersetzen-in-odm.txt".equals(entryName)) {
                isPlatzhalterErsetzenInOdmDatei = false;
              }
              if ("Platzhalter-nicht-ersetzen-in-ott.txt".equals(entryName)) {
                isPlatzhalterErsetzenInOttDatei = false;
              }
            }
          }
        }

        try (FileInputStream fis = new FileInputStream(zipTemplateFile)) {
          try (ZipInputStream zis = new ZipInputStream(fis)) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
              checkCanceled(monitor);

              String entryName = entry.getName();
              // Platzhalter ersetzen für .odt, und ggf. noch für .odm und .ott  Dateien
              if (isPlatzhalterErsetzen(entryName, isPlatzhalterErsetzenInOdmDatei, isPlatzhalterErsetzenInOttDatei)) {
                String targetName = entryName; // Input = Output
                if (!FileType.ODT.equals(zieldateiTyp)) {
                  targetName = entryName.substring(0, entryName.length() - 3) + zieldateiTyp.getFileExtension(); // Extension ersetzen...
                }
                zos.putNextEntry(new ZipEntry(targetName));
                LOG.info("Merge Datei: '" + entryName + "' nach: '" + targetName + "'");

                monitor.progress(DokumentGenerierenProgress.MERGE_DATEI, "'" + entryName + "' -> '" + targetName + "'");

                File templateFile = File.createTempFile("dokumentvorlage" + System.currentTimeMillis(), "." + FileType.ODT.getFileExtension());

                try (FileOutputStream fos = new FileOutputStream(templateFile)) {
                  IOUtils.copy(zis, fos);
                }
                try (InputStream ottIs = createVorlagenInputStream(vorlageContent)) {
                  try (ByteArrayOutputStream bos = new ByteArrayOutputStream(BUFFER_SIZE_IN_BYTES)) { // Schließen des zos verhindern...
                    exchangeOdtKonvertierer.merge(templateFile, contentData.getObjects(), ottIs, bos, new ProgressMonitorWrapper(monitor),
                        FileTypeMapper.mappe(zieldateiTyp), konfiguration);
                    bos.writeTo(zos);
                  }
                }

              } else {
                LOG.info("Kopiere Datei: " + entryName);
                monitor.progress(DokumentGenerierenProgress.KOPIERE_DATEI, "'" + entryName + "'");

                zos.putNextEntry(new ZipEntry(entryName)); // Input = Output
                IOUtils.copy(zis, zos);
              }
              zos.closeEntry();
            }
          }
        }
        checkCanceled(monitor);

        if (dokumentExtraFileOutput.isExtraOutputSupported()) {
          List<FileContent> files = dokumentExtraFileOutput.getFiles();
          if (!files.isEmpty()) {
            monitor.progress(DokumentGenerierenProgress.KOPIERE_EXTRA_FILES);
            for (FileContent fileContent : files) {
              checkCanceled(monitor);
              zos.putNextEntry(new ZipEntry(fileContent.getFilename()));
              zos.write(fileContent.getContent());
              zos.closeEntry();
            }
          }
        }
        zos.finish();
      }
    }

    return dateiExport;
  }

  private boolean isPlatzhalterErsetzen(String entryName, boolean isPlatzhalterErsetzenInOdmDatei, boolean isPlatzhalterErsetzenInOttDatei) {
    if (entryName.toLowerCase().endsWith(".odt")) {
      return true;
    }
    if (isPlatzhalterErsetzenInOttDatei && entryName.toLowerCase().endsWith(".ott")) {
      return true;
    }
    if (isPlatzhalterErsetzenInOdmDatei && entryName.toLowerCase().endsWith(".odm")) {
      return true;
    }
    return false;
  }

  private DateiExport generiereOpenOfficeEinzelneDatei(DokumentKonverterResult contentData, DokumentExtraFileOutput dokumentExtraFileOutput, FileType zieldateiTyp, Wahl wahl, Wahltermin wahltermin,
      DateiFileNames dateiFileNames, ProgressMonitor monitor, File templateFile, byte[] vorlageContent) throws IOException {
    if (dokumentExtraFileOutput.isExtraOutputSupported() && !dokumentExtraFileOutput.getFiles().isEmpty()) {
      throw new IllegalStateException("Die Ausgabe einer einzelnen Datei kann keinen weiteren Content erzeugen. Die Dokumentvorlage muss eine ZIP-Datei sein"); // Sollte nicht passieren, siehe de.ivu.elect.business.dokumente.boundary.AbstractDokumentGenerator.erzeugeDokumentExtraFileOutput()
    }

    DateiExport dateiExport = createDateiExport(wahl, wahltermin, zieldateiTyp, dateiFileNames);

    try (InputStream ottIs = createVorlagenInputStream(vorlageContent)) {
      try (OutputStream os = createOutputStreamAndDateiExport(dateiExport)) {
        exchangeOdtKonvertierer.merge(templateFile, contentData.getObjects(), ottIs, os, new ProgressMonitorWrapper(monitor),
            FileTypeMapper.mappe(zieldateiTyp), getKonfiguration());
      }
    }

    return dateiExport;
  }

  private InputStream createVorlagenInputStream(byte[] vorlageContent) {
    if (vorlageContent == null) {
      return null;
    }
    return new ByteArrayInputStream(vorlageContent);
  }

  private OpenOfficeKonfiguration getKonfiguration() {
    return openOfficeDokumentKonfiguration.createConfig();
  }
}
