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

import static org.apache.commons.lang3.StringUtils.defaultIfBlank;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import javax.annotation.Nullable;
import javax.ejb.Stateless;
import javax.inject.Inject;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.datei.boundary.DateiExportDAO;
import de.ivu.elect.business.datei.control.FileExchange;
import de.ivu.elect.business.datei.entity.BinaryUnit;
import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.datei.entity.DateiExport;
import de.ivu.elect.business.datei.entity.DateiExportPK;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.datei.entity.ServerFileType;
import de.ivu.elect.business.dokumente.control.ElectOdtKonvertierer;
import de.ivu.elect.business.dokumente.control.LayoutVorlageFinder;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.ImportExportStreamCreator;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.exchange.entity.DateiExportArt;
import de.ivu.elect.business.exchange.entity.DateiExportArtFileNames;
import de.ivu.elect.business.wahl.entity.WahlOderWahltermin;
import de.ivu.elect.jee.faces.Messages;

@Stateless
public class DateiKonverter implements Serializable {

  private static final long serialVersionUID = 1L;

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

  private static final int BUFFER_SIZE_IN_BYTES = BinaryUnit.bytesOf(1, BinaryUnit.MEBIBYTE);

  @Inject
  private transient Messages messages;

  @Inject
  private transient ImportExportStreamCreator streamCreator;

  @Inject
  private transient DateiExportDAO dateiExportDAO;

  @Inject
  private transient FileExchange fileExchange;

  @Inject
  private transient ElectOdtKonvertierer odtKonvertierer;

  @Inject
  private transient LayoutVorlageFinder layoutVorlageFinder;

  public DateiKonverter() {
    super();
  }

  public DateiExportPK konvertiere(DateiExportPK sourcePK, FileType targetFileType) {
    LOG.info("Konvertierung - start [sourcePK = " + sourcePK + ", targetFileType = " + targetFileType + "]");

    try {
      DateiExport source = dateiExportDAO.findByPrimaryKey(sourcePK);
      Datei sourceDatei = source.getDatei();
      byte[] vorlageContent = layoutVorlageFinder.getVorlageContent(WahlOderWahltermin.from(sourceDatei.getWahltermin(), sourceDatei.getWahl()));
      FileType sourceFileType = sourceDatei.getFileType();

      String fileFormatName = sourceDatei.getFileFormat().getName();

      FileFormat targetFileFormat = new FileFormat(fileFormatName, targetFileType);

      if (sourceFileType.isZipFile()) {
        targetFileFormat = new FileFormat(fileFormatName, FileType.ZIP);
      }

      DateiExportArt dateiExportArt = DateiExportArt.GENERIERTES_DOKUMENT;

      DateiExport target = fileExchange.createAndPersistDateiExport(dateiExportArt, targetFileFormat, new DateiExportArtFileNames(getMessages(), dateiExportArt), source.getMandant(),
          sourceDatei.getWahl(), ServerFileType.GENERIERTES_DOKUMENT);

      try (InputStream is = sourceDatei.createInputStream(streamCreator)) {
        Datei targetDatei = target.getDatei();

        try (OutputStream os = targetDatei.createOutputStream(streamCreator)) {
          LOG.info("Konvertierung - " + sourceDatei.getDateiname() + " [fileFormat = " + fileFormatName + " sourcePK = " + sourcePK + ", sourceFileType = " + sourceFileType + ", targetFileType = "
              + targetFileType + "]");

          if (sourceFileType.isZipFile()) {
            konvertiereZip(is, os, targetFileType, vorlageContent);
          } else {
            konvertiereOpenDocument(is, os, sourceFileType, targetFileType, vorlageContent);
          }
        }
      }

      DateiExportPK targetPK = target.getPk();

      LOG.info("Konvertierung - done [sourcePK = " + sourcePK + ", targetPK = " + targetPK + ", targetFileType = " + targetFileType + "]");

      return targetPK;
    } catch (Exception ex) {
      LOG.error("Konvertierung - fehlgeschlagen [sourcePK = " + sourcePK + ", targetFileType = " + targetFileType + "]", ex);

      throw new RuntimeException(ex);
    }
  }

  private void konvertiereZip(InputStream is, OutputStream os, FileType targetFileType, @Nullable byte[] ottContent) throws IOException, MappingException {
    try (ZipInputStream zis = new ZipInputStream(is) {

      @Override
      public void close() throws IOException {
        // nicht schließen
      }
    }) {
      try (ZipOutputStream zos = new ZipOutputStream(os)) {
        ZipEntry sourceZipEntry;

        // NOTE, dass jede Datei in Unterverzeichnissen ein eigener ZipEntry ist und Verzeichnisse deshalb nicht gesondert behandelt werden müssen!
        while ((sourceZipEntry = zis.getNextEntry()) != null) {
          String sourceZipEntryName = sourceZipEntry.getName();

          FileType sourceFileType = FileType.fromFilename(sourceZipEntryName);

          if (sourceFileType.isOpenDocumentFile() && !sourceFileType.equals(targetFileType)) {
            String targetZipEntryName = defaultIfBlank(FilenameUtils.removeExtension(sourceZipEntryName), sourceZipEntryName) + "." + targetFileType.getFileExtension();

            ZipEntry targetZipEntry = new ZipEntry(targetZipEntryName);
            zos.putNextEntry(targetZipEntry);

            try (ByteArrayOutputStream bos = new ByteArrayOutputStream(BUFFER_SIZE_IN_BYTES)) {
              LOG.info("\tKonvertierung - konvertiere " + sourceZipEntryName + " [sourceFileType = " + sourceFileType + ", targetFileType = " + targetFileType + "]");

              konvertiereOpenDocument(zis, bos, sourceFileType, targetFileType, ottContent);

              bos.flush();

              IOUtils.write(bos.toByteArray(), zos);
            }
          } else {
            LOG.info("\tKonvertierung - kopiere     " + sourceZipEntryName + " [sourceFileType = " + sourceFileType + ", targetFileType = " + targetFileType + "]");

            ZipEntry targetZipEntry = new ZipEntry(sourceZipEntryName);
            zos.putNextEntry(targetZipEntry);

            IOUtils.copy(zis, zos);
          }

          zos.closeEntry();
          zis.closeEntry();
        }

        zos.finish();
      }
    }
  }

  private void konvertiereOpenDocument(InputStream is, OutputStream os, FileType sourceFileType, FileType targetFileType, @Nullable byte[] ottContent) throws MappingException, IOException {
    if (ottContent == null) {
      odtKonvertierer.konvertiere(is, null, os, sourceFileType, targetFileType);
    } else {
      try (ByteArrayInputStream ottBis = new ByteArrayInputStream(ottContent)) {
        odtKonvertierer.konvertiere(is, ottBis, os, sourceFileType, targetFileType);
      }
    }
  }

  protected Messages getMessages() {
    return messages;
  }
}
