
package de.ivu.elect.exchange.csv.raw;

import static de.ivu.elect.business.exchange.boundary.ExchangeValueDecorators.MESSAGES_DECORATOR_CSV_INJECTION;
import static de.ivu.elect.business.exchange.boundary.ExchangeValueDecorators.MESSAGES_DECORATOR_NULL_TO_EMPTY;
import static de.ivu.elect.business.exchange.boundary.ExchangeValueDecorators.MESSAGES_DECORATOR_REMOVE_SHY;
import static de.ivu.elect.exchange.csv.AutoCloseableCsvSerializer.fixNullToEmpty;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.List;

import javax.annotation.Nonnull;

import org.jsefa.csv.CsvIOFactory;
import org.jsefa.csv.CsvSerializer;
import org.jsefa.csv.lowlevel.config.QuoteMode;

import de.ivu.elect.business.datei.entity.CsvAnfuehrungszeichen;
import de.ivu.elect.business.datei.entity.CsvMaskierungszeichen;
import de.ivu.elect.business.datei.entity.CsvTrennzeichen;
import de.ivu.elect.business.datei.entity.CsvZeichensatz;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.exchange.boundary.ExchangeValueDecorators;
import de.ivu.elect.business.exchange.control.CsvFileKonfiguration;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.exchange.csv.ElectCsvIOFactory;

public abstract class AbstractRawCsvExportMapper implements Serializable {

  private static final long serialVersionUID = 1L;

  private final ExchangeValueDecorators exchangeValueDecorators;

  private final ExchangeValueDecorators exchangeValueDecoratorsCsvInjection;

  protected AbstractRawCsvExportMapper() {
    super();

    this.exchangeValueDecorators = new ExchangeValueDecorators();
    this.exchangeValueDecoratorsCsvInjection = new ExchangeValueDecorators(
        asList(//
                MESSAGES_DECORATOR_NULL_TO_EMPTY, //
                MESSAGES_DECORATOR_REMOVE_SHY,
                MESSAGES_DECORATOR_CSV_INJECTION//
        ));
  }

  protected boolean isInjectSave() {
    return false; // ELECTVAPP-754
  }

  protected QuoteMode getQuoteMode() {
    return QuoteMode.ON_DEMAND;
  }

  protected boolean isTrennzeichenNachLetztemFeld() {
    return true;
  }

  protected CsvMaskierungszeichen setMaskierungszeichen() {
    return CsvMaskierungszeichen.BACKSLASH;
  }//ELECTVAPP-937

  protected CsvZeichensatz setCsvZeichensatz() {
    return getCsvZeichensatz();
  }//ELECTVAPP-1251;

  protected abstract String getFileFormatName();

  protected CsvZeichensatz getCsvZeichensatz() {
    return CsvZeichensatz.UTF_8;
  }

  protected void writeHeader(CsvSerializer serializer, List<String> strings) {
    serializer.writeHeader(fixNullToEmpty(formatExchangeValues(strings)));
  }

  protected void writeFields(CsvSerializer serializer, List<String> values) {
    for (String value : values) {
      writeField(serializer, value);
    }
  }

  protected void writeField(CsvSerializer serializer, String value) {
    if (isInjectSave()) {
      writeFieldInjectionSave(serializer, value, getQuoteMode());
    } else {
      serializer.getLowLevelSerializer().writeField(formatExchangeValue(value), getQuoteMode());
    }
  }

  protected void writeMinus(CsvSerializer serializer) { //ELECT-11205
    serializer.getLowLevelSerializer().writeField(formatExchangeValue("-"), getQuoteMode());
  }

  protected void writeLong(CsvSerializer serializer, Long value) {
    serializer.getLowLevelSerializer().writeField(formatExchangeValue(value == null ? "" : Long.toString(value)), getQuoteMode());
  }

  private void writeFieldInjectionSave(CsvSerializer serializer, String value, QuoteMode quoteMode) {
    serializer.getLowLevelSerializer().writeField(formatExchangeValueCSVInjection(value), quoteMode);
  }

  protected void nextLine(CsvSerializer serializer) {
    serializer.getLowLevelSerializer().finishRecord();
  }

  @Nonnull
  protected CsvSerializer openSerializer(OutputStream os) {
    CsvFileKonfiguration csvFileKonfiguration = createDefaultCsvExportFileKonfiguration();

    CsvSerializer serializer = createCsvIOFactory(csvFileKonfiguration).createSerializer();

    try {
      serializer.open(createWriter(os, csvFileKonfiguration.getZeichensatz()));
    } catch (Exception ex) {
      throw new RuntimeException(ex.getMessage(), ex);
    }

    return serializer;
  }

  protected void closeSerializer(CsvSerializer serializer) {
    serializer.flush();
    serializer.close(closeOutputStreamOnCloseSerializer());
  }

  protected abstract boolean closeOutputStreamOnCloseSerializer();

  protected CsvIOFactory createCsvIOFactory(CsvFileKonfiguration csvFileKonfiguration) {
    return ElectCsvIOFactory.create(csvFileKonfiguration, DefaultRawCsvExportMapperDTO.class);
  }

  // Cf. AutoCloseableCsvSerializer#createWriter!
  private Writer createWriter(OutputStream os, CsvZeichensatz zeichensatz) throws IOException {
    Charset charset = zeichensatz.toCharset();

    Writer writer = new OutputStreamWriter(os, charset);
    writer.write(zeichensatz.getCharsetBom().map(bom -> new String(bom.getBytes(), charset)).orElse(getByteOrderMarkFallback(charset)));

    return writer;
  }

  // NOTE, dass diese Methode obsolet ist, sobald CsvZeichensatz#getCharsetBom eine tatsächliche BOM liefert!
  protected String getByteOrderMarkFallback(Charset charset) {
    // TODO ELECT-5818
    return "";
  }

  protected List<String> formatExchangeValues(List<String> values) {
    return values.stream().map(exchangeValueDecorators).collect(toList());
  }

  protected String formatExchangeValue(String value) {
    return exchangeValueDecorators.apply(value); // ELECT-5329 "shy" filtern
  }

  protected String formatExchangeValueCSVInjection(String value) {
    return exchangeValueDecoratorsCsvInjection.apply(value); //ELECTVAPP-754 filter =, +, - or @.
  }

  public CsvFileKonfiguration createDefaultCsvExportFileKonfiguration() {
    return new CsvFileKonfiguration() {

      @Override
      public FileFormat getFileFormat() {
        return new FileFormat(getFileFormatName(), FileType.CSV);
      }

      @Override
      public CsvZeichensatz getZeichensatz() {
        return setCsvZeichensatz();
      }

      @Override
      public Boolean isWithHeader() {
        return true;
      }

      @Override
      public Long getAnzahlKopfzeilen() {
        return 1L;
      }

      @Override
      public CsvTrennzeichen getTrennzeichen() {
        return CsvTrennzeichen.SEMIKOLON;
      }

      @Override
      public CsvAnfuehrungszeichen getAnfuehrungszeichen() {
        return CsvAnfuehrungszeichen.DOPPELT;
      }

      @Override
      public CsvMaskierungszeichen getMaskierungszeichen() {
        return setMaskierungszeichen();
      }

      @Override
      public Boolean isNutzeTrennzeichenNachLetztemFeld() {
        return isTrennzeichenNachLetztemFeld();
      }
    };
  }
}
