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

import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.endsWith;
import static org.apache.commons.lang3.StringUtils.startsWith;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;

import javax.inject.Inject;

import de.ivu.elect.business.datei.control.CsvDateiLeserMapper;
import de.ivu.elect.business.datei.entity.CsvDatei;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.exchange.control.CsvFileKonfiguration;
import de.ivu.elect.business.exchange.control.DefaultCsvFileKonfiguration;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.exchange.csv.CsvMarshaller;

public class DefaultCsvDateiLeserMapper implements CsvDateiLeserMapper {

  private static final long serialVersionUID = 1L;

  @Inject
  private transient CsvMarshaller csvMarshaller;

  public DefaultCsvDateiLeserMapper() {
    super();
  }

  @Override
  public List<String[]> mapRaw(CsvDatei csvDatei, List<String> lines) {
    return map(csvDatei, lines, false);
  }

  @Override
  public List<String[]> mapColumnCountNormalized(CsvDatei csvDatei, List<String> lines) {
    return map(csvDatei, lines, true);
  }

  protected List<String[]> map(CsvDatei csvDatei, List<String> lines, boolean columnCountNormalized) {
    List<List<String>> result = new ArrayList<>();

    List<List<String>> linesDeserialized = new ArrayList<>();
    int linesDeserializedMaxColumnCount = 0;

    for (DefaultCsvDTO dto : deserialize(createCsvFileKonfiguration(csvDatei), lines)) {
      List<String> values = dto.toStringList();

      linesDeserialized.add(values);
      linesDeserializedMaxColumnCount = Math.max(linesDeserializedMaxColumnCount, values.size());
    }

    result.addAll(linesDeserialized);

    if (columnCountNormalized) {
      result.clear();

      for (List<String> values : linesDeserialized) {
        List<String> valuesHavingMaxColumnCount = new ArrayList<>(values);

        for (int i = valuesHavingMaxColumnCount.size(); i < linesDeserializedMaxColumnCount; i++) {
          valuesHavingMaxColumnCount.add(" ");
        }

        result.add(valuesHavingMaxColumnCount);
      }
    }

    return result.stream() //
        .map(values -> values.toArray(new String[values.size()])) //
        .collect(toList());
  }

  private List<DefaultCsvDTO> deserialize(CsvFileKonfiguration csvFileKonfiguration, List<String> lines) {
    return deserialize(csvFileKonfiguration, DefaultCsvDTO.class, createInputString(csvFileKonfiguration, lines));
  }

  protected List<DefaultCsvDTO> deserialize(CsvFileKonfiguration csvFileKonfiguration, Class<DefaultCsvDTO> objectType, String input) {
    return csvMarshaller.deserialize(objectType, new ByteArrayInputStream(input.getBytes(csvFileKonfiguration.getZeichensatz().toCharset())), csvFileKonfiguration);
  }

  protected CsvFileKonfiguration createCsvFileKonfiguration(CsvDatei csvDatei) {
    return new DefaultCsvFileKonfiguration(csvDatei, new FileFormat("", FileType.CSV)) {

      private static final long serialVersionUID = 1L;

      @Override
      public Long getAnzahlKopfzeilen() {
        // Immer alle Zeilen einlesen, Kopfzeile wird ggf. später ausgeblendet!
        return 0L;
      }
    };
  }

  private String createInputString(CsvFileKonfiguration csvFileKonfiguration, List<String> lines) {
    List<String> result = new ArrayList<>(lines);

    for (int i = 0; i < result.size(); i++) {
      result.set(i, normalizeInputStringLine(csvFileKonfiguration, result.get(i)));
    }

    return String.join(System.lineSeparator(), result);
  }

  protected String normalizeInputStringLine(CsvFileKonfiguration csvFileKonfiguration, String line) {
    String result = line;

    // NOTE ELECT-6039, leere Spalten (bspw. ;;) durch einen nicht leeren Wert ersetzen, so dass der Standardwert
    // CsvDateiLeserMapperDTO#NOT_MAPPED beim Deserialisieren ersetzt wird und gefiltert werden kann.
    String fieldDelimiter = Character.toString(csvFileKonfiguration.getTrennzeichen().getCharacter());

    if (startsWith(result, fieldDelimiter)) {
      result = " " + result;
    }

    if (endsWith(result, fieldDelimiter)) {
      result = result + " ";
    }

    String emptyField = fieldDelimiter + fieldDelimiter;

    while (result.contains(emptyField)) {
      result = result.replace(emptyField, fieldDelimiter + " " + fieldDelimiter);
    }

    return result;
  }
}
