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

import static java.util.Collections.emptyList;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collection;
import java.util.List;

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

import org.apache.commons.io.FileUtils;
import org.primefaces.model.DefaultStreamedContent;

import de.ivu.elect.business.benutzerverwaltung.entity.Benutzer;
import de.ivu.elect.business.datei.control.FileExchange;
import de.ivu.elect.business.datei.entity.CsvDatei;
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.DateiFileNames;
import de.ivu.elect.business.datei.entity.ServerFileType;
import de.ivu.elect.business.exchange.entity.DateiExportArt;
import de.ivu.elect.business.exchange.entity.DateiExportArtFileNames;
import de.ivu.elect.business.konfiguration.control.StreamCreator;
import de.ivu.elect.business.mandant.entity.EntityMitMandant;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.person.control.PersonendatenDateiExportCreate;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.system.boundary.SystemLogWriter;
import de.ivu.elect.business.system.entity.SystemLogQuelle;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.performance.PerformanceLog;
import de.ivu.elect.jee.performance.PerformanceLogTransactionHandling;
import de.ivu.elect.jee.performance.ViewType;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;

// Abgeleitete Klasse mit @Stateless annotieren!
@PerformanceLog(viewType = ViewType.EXPORT, transactionHandling = PerformanceLogTransactionHandling.IMMEDIATELY)
public abstract class AbstractExporter<ExportObject, C> extends AbstractImporterExporter<C> {

  @Inject
  protected transient SystemLogWriter systemLogWriter;

  @Inject
  protected transient FileExchange fileExchange;

  @Inject
  protected transient ImportExportStreamCreator streamCreator;

  @Inject
  protected transient PersonendatenDateiExportCreate personendatenDateiExportCreate;

  public AbstractExporter() {
    super();
  }

  public void addProperties(FormBuilder formBuilder, @Nullable FileFormat fileFormat, C context) {
    getExchange().addProperties(formBuilder, fileFormat, context, null);
  }

  @Override
  protected abstract ExportExchange<ExportObject, C> getExchange();

  protected ExportObject getExportObject(C context, FileFormat fileFormat, DateiExport dateiExport) {
    return getExportObject(context);
  }

  protected abstract ExportObject getExportObject(C context);

  protected abstract DateiExportArt getDateiExportArt();

  @Inject
  protected void setImportExportStreamCreator(ImportExportStreamCreator streamCreator) {
    this.streamCreator = streamCreator;
  }

  public DateiExportPK export(FileFormat fileFormat, C context, Mandant mandant, Wahl wahl, Wahltermin wahltermin) {
    DateiExport dateiExport = createDateiExport(fileFormat, context, mandant, wahl, wahltermin);

    processContextAndDateiExportIfNecessary(context, dateiExport);

    FileKonfiguration fileKonfiguration = createFileKonfiguration(fileFormat, dateiExport.getDatei());

    ExportObject exportObject = getExportObject(context, fileFormat, dateiExport);

    try (OutputStream os = createOutputStreamAndDateiExport(fileFormat, context, dateiExport)) {
      getExchange().export(exportObject, os, fileKonfiguration, context);
    } catch (MappingException | IOException ex) {
      handleExportException(ex, wahltermin, wahl);

      throw new RuntimeException(ex.getMessage(), ex);
    }

    try {
      postExport(exportObject, dateiExport, fileFormat, context, mandant, wahl, wahltermin);
    } catch (Exception ex) {
      handlePostExportException(ex, wahltermin, wahl);

      throw ex;
    }

    return dateiExport.getPk();
  }

  protected void handleExportException(Exception ex, Wahltermin wahltermin, Wahl wahl) {
    if (isWriteSystemLogMessageOnException()) {
      EntityMitMandant entityMitMandant = wahl;
      if (entityMitMandant == null) {
        entityMitMandant = wahltermin;
      }
      systemLogWriter.writeErrorLogMessageFuerMandant(entityMitMandant, getMessage("SystemLogQuelle.EXPORTER.nachricht", getMessage(getDateiExportArt())), ex, SystemLogQuelle.EXPORTER);
    }
  }

  protected void handlePostExportException(Exception ex, Wahltermin wahltermin, Wahl wahl) {
    handleExportException(ex, wahltermin, wahl);
  }

  // ELECT-5536, bspw. langlaufender Export, dann Exception persistieren.
  protected boolean isWriteSystemLogMessageOnException() {
    return false;
  }

  protected void postExport(ExportObject exportObject, DateiExport dateiExport, FileFormat fileFormat, C context, Mandant mandant, Wahl wahl, Wahltermin wahltermin) {
    if (context instanceof AbstractExchangeContext) {
      AbstractExchangeContext exchangeContext = (AbstractExchangeContext) context;

      if (exchangeContext.hasPostExportTargetDirectory()) {
        kopiereDateiInServerVerzeichnis(dateiExport, context, fileFormat, exchangeContext);
      }
    }
  }

  private void kopiereDateiInServerVerzeichnis(DateiExport dateiExport, C context, FileFormat fileFormat, AbstractExchangeContext exchangeContext) {
    Datei datei = dateiExport.getDatei();
    DefaultStreamedContent defaultStreamedContent = datei.getDefaultStreamedContent(getStreamCreator(datei.getFileFormat(), context));

    try {
      FileUtils.copyInputStreamToFile(defaultStreamedContent.getStream(),
          new File(exchangeContext.getPostExportTargetDirectory(), getPostExportFileName(defaultStreamedContent.getName(), context, fileFormat)));
    } catch (IOException ex) {
      throw new RuntimeException(ex.getMessage(), ex);
    }
  }

  protected String getPostExportFileName(String defaultName, C context, FileFormat fileFormat) {
    return defaultName;
  }

  protected DateiFileNames getDateiExportFileNames(FileFormat fileFormat, C context) {
    if (fileFormat != null) {
      return getExchange().getDateiFileNames(getDateiExportArt(), fileFormat, context);
    }

    return new DateiExportArtFileNames(getMessages(), getDateiExportArt());
  }

  private DateiExport createDateiExport(FileFormat fileFormat, C context, Mandant mandant, Wahl wahl, Wahltermin wahltermin) {
    return createDateiExport(fileFormat, getDateiExportFileNames(fileFormat, context), context, mandant, wahl, wahltermin);
  }

  protected DateiExport createDateiExport(FileFormat fileFormat, DateiFileNames dateiFileNames, C context, Mandant mandant, Wahl wahl, Wahltermin wahltermin) {
    DateiExport dateiExport = fileExchange.createAndPersistDateiExport(getDateiExportArt(), fileFormat, dateiFileNames, mandant, wahl, wahltermin, ServerFileType.EXPORTIERTE_DATEI);

    setzeMitOderOhneKopfzeile(fileFormat, dateiExport);

    return dateiExport;
  }

  private void setzeMitOderOhneKopfzeile(FileFormat fileFormat, DateiExport dateiExport) {
    if (isExportHeader() && fileFormat.getFileType().isCsvFile()) {
      ((CsvDatei) dateiExport.getDatei()).setAnzahlKopfzeilen(1L);
    }
  }

  protected void processContextAndDateiExportIfNecessary(C context, DateiExport dateiExport) {
    // nothing to do (per default)
  }

  protected OutputStream createOutputStreamAndDateiExport(FileFormat fileFormat, C context, DateiExport dateiExport) {
    return dateiExport.getDatei().createOutputStream(getStreamCreator(fileFormat, context));
  }

  protected StreamCreator getStreamCreator(FileFormat fileFormat, C context) {
    return streamCreator;
  }

  protected boolean isExportHeader() {
    return false;
  }

  protected void protokollierePersonendatenDateiExport(Collection< ? extends Personendaten> personendaten, DateiExport dateiExport, Benutzer benutzer) {
    protokollierePersonendatenDateiExport(personendaten, dateiExport, benutzer, emptyList());
  }

  // Cf. AbstractDokumentGenerator#protokollierePersonendatenDateiExport!
  protected void protokollierePersonendatenDateiExport(Collection< ? extends Personendaten> personendaten, DateiExport dateiExport, Benutzer benutzer, List<String> bemerkungen) {
    personendatenDateiExportCreate.save(personendaten, dateiExport, benutzer, bemerkungen);
  }
}
