
package de.ivu.elect.dokumente.konverter;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

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

import org.apache.commons.io.IOUtils;

import de.ivu.document.generator.template.OpenOfficeKonfiguration;
import de.ivu.elect.business.datei.boundary.DateiExportDAO;
import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.datei.entity.DateiExportPK;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.dokumente.boundary.AbstractDokumentGenerator;
import de.ivu.elect.business.dokumente.control.DokumentGenerierenSupportedOpenOfficeFileFormats;
import de.ivu.elect.business.dokumente.control.ElectDokumentContext;
import de.ivu.elect.business.dokumente.control.FileTypeMapper;
import de.ivu.elect.business.dokumente.control.OpenOfficeKonfigurationFactory;
import de.ivu.elect.business.dokumente.control.ProgressMonitorWrapper;
import de.ivu.elect.business.dokumente.entity.DokumentVorlage;
import de.ivu.elect.business.dokumente.entity.DokumentVorlageArt;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.exchange.ExchangeOdtKonvertierer;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.presentation.dokumente.DokumentErzeugenResult;
import de.ivu.elect.presentation.dokumente.DokumentGenerierenProgressMonitor;


import static java.util.Collections.singletonList;

/**
 * Delegiert die Erstellung und Befüllung an einen bestehenden DokumentGenerator.
 */
public abstract class AbstractDokumentGeneratorDocumentMapper<C extends ElectDokumentContext, E, D extends ElectDokumentContext> extends AbstractDocumentMapper<C> {

  @Inject
  private transient DateiExportDAO dateiExportDAO;

  @Inject
  private transient DokumentGenerierenSupportedOpenOfficeFileFormats dokumentGenerierenSupportedOpenOfficeFileFormats;

  @Inject
  private transient DokumentGenerierenProgressMonitor dokumentGenerierenProgressMonitor;

  @Inject
  private transient OpenOfficeKonfigurationFactory openOfficeKonfigurationFactory;

  @Inject
  private transient ExchangeOdtKonvertierer exchangeOdtKonvertierer;

  protected AbstractDokumentGeneratorDocumentMapper() {
    super();
  }

  public DokumentVorlageArt getDokumentVorlageArt() {
    return getDokumentGenerator().getDokumentVorlageArt();
  }

  public FileType getDokumentVorlageFileType() {
    return FileType.ODT;
  }

  public List<FileType> getTargetFileTypesSupported() {
    return dokumentGenerierenSupportedOpenOfficeFileFormats.getSupportedFileTypes();
  }

  public abstract void addProperties(FormBuilder formBuilder, C context);

  public void serialize(OutputStream os, C context, String fileFormatName, FileType fileType, D dokumentContext, E dokumentEntities, @Nullable DokumentVorlage dokumentVorlage,
      boolean closeOutputStream) throws Exception {
    FileFormat targetFileFormat = new FileFormat(fileFormatName, fileType);

    if (isConvertDokumentSupported(context)) {
      FileType sourceFileType = FileType.ODT;
      FileFormat sourceFileFormat = new FileFormat(fileFormatName, sourceFileType);

      DateiExportPK sourceDateiExportPK = context.get(createContextParameterKey(context, dokumentContext, sourceFileFormat), DateiExportPK.class);

      if (sourceDateiExportPK != null && !Objects.equals(sourceFileType, fileType)) {
        convertDokument(os, context, targetFileFormat, dokumentContext, sourceFileFormat, sourceDateiExportPK);
      } else {
        generateDokument(os, context, targetFileFormat, dokumentContext, dokumentEntities, dokumentVorlage);
      }
    } else {
      generateDokument(os, context, targetFileFormat, dokumentContext, dokumentEntities, dokumentVorlage);
    }

    if (closeOutputStream) {
      try {
        os.close();
      } catch (Exception ex) {
        warn("FAILED to close stream", ex);
      }
    }
  }

  private void generateDokument(OutputStream os, C context, FileFormat targetFileFormat, D dokumentContext, E dokumentEntities, @Nullable DokumentVorlage dokumentVorlage) throws Exception {
    if (dokumentVorlage == null) {
      throw new IllegalArgumentException("Erforderliche Dokumentvorlage konnte nicht gefunden werden [art = " + getDokumentVorlageArt() + ", fileType = " + getDokumentVorlageFileType() + "]");
    }

    Datei datei = generateDokument(context, targetFileFormat, dokumentContext, dokumentEntities, dokumentVorlage).map(this::attachDatei).orElse(null);

    if (datei != null) {
      try (InputStream is = datei.createInputStream(importExportStreamCreator)) {
        IOUtils.copy(is, os);
      }
    }
  }

  private Optional<DateiExportPK> generateDokument(C context, FileFormat targetFileFormat, D dokumentContext, E dokumentEntities, DokumentVorlage dokumentVorlage) throws Exception {
    info("Generiere Dokument [targetFileFormat = " + targetFileFormat + ", dokumentVorlage = " + dokumentVorlage.getName() + "]");

    DokumentErzeugenResult dokumentErzeugenResult = getDokumentGenerator().generiereDokument(singletonList(dokumentEntities), dokumentVorlage, null, targetFileFormat.getFileType(),
        dokumentContext.getWahl(), dokumentContext.getWahltermin(), dokumentContext, dokumentGenerierenProgressMonitor);

    Exception exception = dokumentErzeugenResult.getException();

    if (exception != null) {
      throw exception;
    }

    Optional<DateiExportPK> dateiExport = dokumentErzeugenResult.getDateiExport();

    if (dateiExport.isPresent()) {
      context.put(createContextParameterKey(context, dokumentContext, targetFileFormat), dateiExport.get()); // ELECT-10670, generierte Datei zur Wiederverwendung merken...
    }

    return dateiExport;
  }

  private void convertDokument(OutputStream os, C context, FileFormat targetFileFormat, D dokumentContext, FileFormat sourceFileFormat, DateiExportPK dateiExportPK) throws Exception {
    Datei datei = attachDatei(dateiExportPK);

    if (datei != null) {
      info("Konvertiere BEREITS ERZEUGTES/GECACHTES Dokument [targetFileFormat = " + targetFileFormat + ", sourceFileFormat = " + sourceFileFormat + ", cache key = "
          + createContextParameterKey(context, dokumentContext, sourceFileFormat) + ", datei = " + datei.getOriginalDateiname() + "]");

      try (InputStream is = datei.createInputStream(importExportStreamCreator)) {
        OpenOfficeKonfiguration openOfficeKonfiguration = openOfficeKonfigurationFactory.createConfig();

        exchangeOdtKonvertierer.konvertiereDokument(is, FileTypeMapper.mappe(sourceFileFormat.getFileType()), null, os,
            FileTypeMapper.mappe(targetFileFormat.getFileType()), openOfficeKonfiguration, new ProgressMonitorWrapper(dokumentGenerierenProgressMonitor));
      }
    }
  }

  protected boolean isConvertDokumentSupported(C context) {
    return false;
  }

  protected String createContextParameterKey(C context, D dokumentContext, FileFormat fileFormat) {
    return "AbstractDokumentGeneratorDocumentMapper#DateiExport@" + fileFormat.hashCode();
  }

  @Nullable
  private Datei attachDatei(DateiExportPK dateiExportPK) {
    if (dateiExportPK != null) {
      try {
        return dateiExportDAO.findByPrimaryKey(dateiExportPK).getDatei();
      } catch (Exception ex) {
        warn("FAILED to find Datei [dateiExportPK = " + dateiExportPK + "]", ex);
      }
    }

    return null;
  }

  protected abstract AbstractDokumentGenerator<E, D> getDokumentGenerator();
}
