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

import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.statistik.control.StatistikLaufImportContext;
import de.ivu.elect.business.statistik.entity.StatistikKennzeichen;
import de.ivu.elect.business.statistik.entity.StatistikKennzeichenArt;
import de.ivu.elect.business.statistik.entity.StatistikLauf;
import de.ivu.elect.business.statistik.entity.StatistikLaufMitKennzeichen;
import de.ivu.elect.business.statistik.entity.StatistikLaufStatus;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.exchange.CsvImportMapper;
import de.ivu.elect.exchange.csv.CsvMarshaller;
import de.ivu.elect.exchange.statistik.StatistikLaufImportMapper;
import de.ivu.elect.jee.entity.customdata.EmptyCustomData;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.jee.faces.Messages;

public class StatistikLaufCsvImportMapper implements CsvImportMapper<List<StatistikLaufMitKennzeichen>, StatistikLaufImportContext>, StatistikLaufImportMapper {

  public static final String FILE_FORMAT_NAME = "file.format.statistik.lauf.import.default";

  @Inject
  protected transient CsvMarshaller csvMarshaller;

  @Inject
  protected transient Messages messages;

  public StatistikLaufCsvImportMapper() {
    super();
  }

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

  @Override
  public boolean isAcceptContext(StatistikLaufImportContext statistikLaufImportContext) {
    return statistikLaufImportContext.getWahltermin() != null;
  }

  @Override
  public List<StatistikLaufMitKennzeichen> deserialize(InputStream is, StatistikLaufImportContext statistikLaufImportContext, FileKonfiguration fileKonfiguration) throws MappingException {
    List<StatistikLaufMitKennzeichen> result = new ArrayList<>();

    List<StatistikLauf_ImportCsvDTO> alleCsvZeilen = deserialize(is, fileKonfiguration);
    Map<StatistikLauf_LaufCsvDTO, List<StatistikLauf_KennzeichenCsvDTO>> konkreteDTOsGruppiert = mapToKonkreteDTOs(alleCsvZeilen);

    for (Map.Entry<StatistikLauf_LaufCsvDTO, List<StatistikLauf_KennzeichenCsvDTO>> statistikLaufMitKennzeichenExternEntry : konkreteDTOsGruppiert.entrySet()) {
      StatistikLauf_LaufCsvDTO statistikLaufExtern = statistikLaufMitKennzeichenExternEntry.getKey();
      List<StatistikLauf_KennzeichenCsvDTO> statistikKennzeichensZumLaufExtern = statistikLaufMitKennzeichenExternEntry.getValue();

      StatistikLaufMitKennzeichen statistikLaufMitKennzeichenIntern = new StatistikLaufMitKennzeichen();

      statistikLaufMitKennzeichenIntern.setStatistikLauf(mapExternToIntern(statistikLaufExtern, statistikLaufImportContext));
      statistikLaufMitKennzeichenIntern.setKennzeichenZumLauf(mapExternToIntern(statistikKennzeichensZumLaufExtern, statistikLaufImportContext));

      result.add(statistikLaufMitKennzeichenIntern);
    }

    return result;
  }

  private List<StatistikLauf_ImportCsvDTO> deserialize(InputStream is, FileKonfiguration fileKonfiguration) {
    List<StatistikLauf_ImportCsvDTO> alleCsvZeilen = csvMarshaller.deserialize(StatistikLauf_ImportCsvDTO.class, is, fileKonfiguration);
    setZeilenNummer(alleCsvZeilen);

    return alleCsvZeilen;
  }

  private void setZeilenNummer(List<StatistikLauf_ImportCsvDTO> alleCsvZeilen) {
    for (int i = 0; i < alleCsvZeilen.size(); i++) {
      alleCsvZeilen.get(i).setZeilenNr((long) (i + 1));
    }
  }

  private Map<StatistikLauf_LaufCsvDTO, List<StatistikLauf_KennzeichenCsvDTO>> mapToKonkreteDTOs(List<StatistikLauf_ImportCsvDTO> alleCsvZeilen) {
    Map<StatistikLauf_LaufCsvDTO, List<StatistikLauf_KennzeichenCsvDTO>> result = new LinkedHashMap<>();

    StatistikLauf_LaufCsvDTO zuletztBetrachtetesLaufDTO = null;
    for (StatistikLauf_ImportCsvDTO csvZeile : alleCsvZeilen) {
      if (csvZeile.getZeilenArt() == null) {
        throw new IllegalStateException("statistik.lauf.import.mapper.zeilen.art.nicht.gesetzt");
      }

      if (csvZeile.getZeilenArt().equals(StatistikLaufCsvDTOZeilenArt.getZeilenArt_StatistikLauf())) {
        StatistikLauf_LaufCsvDTO laufCsvDTO = new StatistikLauf_LaufCsvDTO();

        laufCsvDTO.setWahlartName(csvZeile.getSpalte2());
        laufCsvDTO.setAusloeser(csvZeile.getSpalte3());
        laufCsvDTO.setStatus(csvZeile.getSpalte4());
        laufCsvDTO.setZeitpunktDesLaufs(mapToDate(csvZeile.getSpalte5()));
        laufCsvDTO.setBeschreibung(csvZeile.getSpalte6());
        laufCsvDTO.setStartDesLaufs(mapToDate(csvZeile.getSpalte7()));
        laufCsvDTO.setEndeDesLaufs(mapToDate(csvZeile.getSpalte8()));
        laufCsvDTO.setZeilenNr(csvZeile.getZeilenNr());

        result.put(laufCsvDTO, new ArrayList<>());

        zuletztBetrachtetesLaufDTO = laufCsvDTO;
      } else if (csvZeile.getZeilenArt().equals(StatistikLaufCsvDTOZeilenArt.getZeilenArt_StatistikKennzeichen())) {
        StatistikLauf_KennzeichenCsvDTO kennzeichenCsvDTO = new StatistikLauf_KennzeichenCsvDTO();

        kennzeichenCsvDTO.setGebietsartName(csvZeile.getSpalte2());
        kennzeichenCsvDTO.setGebietsNummer(csvZeile.getSpalte3());
        kennzeichenCsvDTO.setWahlartName(csvZeile.getSpalte4());
        kennzeichenCsvDTO.setKennzeichen(csvZeile.getSpalte5());
        kennzeichenCsvDTO.setQualifier(csvZeile.getSpalte6());
        kennzeichenCsvDTO.setWert(mapToLong(csvZeile.getSpalte7()));
        kennzeichenCsvDTO.setZeilenNr(csvZeile.getZeilenNr());

        if (zuletztBetrachtetesLaufDTO == null) {
          throw new IllegalStateException(messages.getMessageForMessageKey("statistik.lauf.import.mapper.erste.zeile.ist.nicht.statistiklauf"));
        }
        result.get(zuletztBetrachtetesLaufDTO).add(kennzeichenCsvDTO);
      } else {
        throw new IllegalStateException(messages.getMessageForMessageKey("statistik.lauf.import.mapper.zeilen.art.unbekannt", csvZeile.getZeilenArt()));
      }
    }

    return result;
  }

  private StatistikLauf mapExternToIntern(StatistikLauf_LaufCsvDTO laufExtern, StatistikLaufImportContext statistikLaufImportContext) {
    StatistikLauf laufIntern = new StatistikLauf();

    laufIntern.setAusloeser(laufExtern.getAusloeser());
    laufIntern.setBeschreibung(laufExtern.getBeschreibung());
    laufIntern.setEndeDesLaufs(laufExtern.getEndeDesLaufs());
    laufIntern.setStartDesLaufs(laufExtern.getStartDesLaufs());
    laufIntern.setZeitpunktDesLaufs(laufExtern.getZeitpunktDesLaufs());

    if (laufExtern.getWahlartName() != null) {
      Wahlart wahlart = new Wahlart();
      wahlart.setName(laufExtern.getWahlartName());

      Wahl wahl = new Wahl(EmptyCustomData.INSTANCE);
      wahl.setWahlart(wahlart);
      wahl.setWahltermin(statistikLaufImportContext.getWahltermin());
      laufIntern.setWahl(wahl);
    }

    laufIntern.setStatus(StatistikLaufStatus.valueOf(laufExtern.getStatus()));
    laufIntern.setWahltermin(statistikLaufImportContext.getWahltermin());

    return laufIntern;
  }

  private List<StatistikKennzeichen> mapExternToIntern(List<StatistikLauf_KennzeichenCsvDTO> kennzeichensExtern, StatistikLaufImportContext statistikLaufImportContext) {
    List<StatistikKennzeichen> result = new ArrayList<>();

    for (StatistikLauf_KennzeichenCsvDTO kennzeichenExtern : kennzeichensExtern) {
      result.add(mapExternToIntern(kennzeichenExtern, statistikLaufImportContext));
    }

    return result;
  }

  private StatistikKennzeichen mapExternToIntern(StatistikLauf_KennzeichenCsvDTO kennzeichenExtern, StatistikLaufImportContext statistikLaufImportContext) {
    StatistikKennzeichen kennzeichenIntern = new StatistikKennzeichen();

    if (kennzeichenExtern.getGebietsartName() != null && kennzeichenExtern.getGebietsNummer() != null) {
      Gebietsart gebietsart = new Gebietsart();
      gebietsart.setName(kennzeichenExtern.getGebietsartName());

      Gebiet gebiet = new Gebiet(EmptyCustomData.INSTANCE);
      gebiet.setNummer(kennzeichenExtern.getGebietsNummer());
      gebiet.setGebietsart(gebietsart);

      kennzeichenIntern.setGebiet(gebiet);
    }

    if (kennzeichenExtern.getWahlartName() != null) {
      Wahlart wahlart = new Wahlart();
      wahlart.setName(kennzeichenExtern.getWahlartName());

      Wahl wahl = new Wahl(EmptyCustomData.INSTANCE);
      wahl.setWahlart(wahlart);
      wahl.setWahltermin(statistikLaufImportContext.getWahltermin());
      kennzeichenIntern.setWahl(wahl);
    }

    kennzeichenIntern.setKennzeichen(StatistikKennzeichenArt.valueOf(kennzeichenExtern.getKennzeichen()));
    kennzeichenIntern.setQualifier(kennzeichenExtern.getQualifier());
    kennzeichenIntern.setWert(kennzeichenExtern.getWert());

    return kennzeichenIntern;
  }

  private Long mapToLong(String longAlsString) {
    if (longAlsString == null) {
      return null;
    }

    Long result;
    try {
      result = Long.parseLong(longAlsString);
    } catch (NumberFormatException e) {
      throw new IllegalStateException("Long als String: [" + longAlsString + "]konnte nicht geparst werden.");
    }

    return result;
  }

  private Date mapToDate(String date) {
    if (date == null) {
      return null;
    }

    DateFormat format = new SimpleDateFormat("dd.MM.yyyy");

    Date result;
    try {
      result = format.parse(date);
    } catch (ParseException e) {
      throw new IllegalStateException("Datum als String: [" + date + "] konnte nicht geparst werden");
    }

    return result;
  }

  @Override
  public Violations validateInputFormat(InputStream is, StatistikLaufImportContext statistikLaufImportContext, FileKonfiguration fileKonfiguration) {
    Violations violations = new Violations();

    List<StatistikLauf_ImportCsvDTO> alleCsvZeilen = deserialize(is, fileKonfiguration);
    validateZeilenArtGesetzt(violations, alleCsvZeilen);
    validateErsteZeileIstStatistikLauf(violations, alleCsvZeilen);

    Map<StatistikLauf_LaufCsvDTO, List<StatistikLauf_KennzeichenCsvDTO>> konkreteDTOsGruppiert = mapToKonkreteDTOs(alleCsvZeilen);

    for (Map.Entry<StatistikLauf_LaufCsvDTO, List<StatistikLauf_KennzeichenCsvDTO>> statistikLaufMitKennzeichenExternEntry : konkreteDTOsGruppiert.entrySet()) {
      StatistikLauf_LaufCsvDTO statistikLaufExtern = statistikLaufMitKennzeichenExternEntry.getKey();
      List<StatistikLauf_KennzeichenCsvDTO> statistikKennzeichensZumLaufExtern = statistikLaufMitKennzeichenExternEntry.getValue();

      validateStatistikLaufBesitztStatus(violations, statistikLaufExtern);

      validateStatistikKennzeichenBesitzenKennzeichen(violations, statistikKennzeichensZumLaufExtern);

      validateGebietTK(violations, statistikKennzeichensZumLaufExtern);
    }

    return violations;
  }

  private void validateZeilenArtGesetzt(Violations violations, List<StatistikLauf_ImportCsvDTO> alleCsvZeilen) {
    alleCsvZeilen.stream().filter(csvZeile -> StringUtils.isBlank(csvZeile.getZeilenArt())).forEach(csvZeile -> {
      violations.addError("statistik.lauf.import.zeilen.art.nicht.gesetzt", csvZeile.getZeilenNr());
    });
  }

  private void validateErsteZeileIstStatistikLauf(Violations violations, List<StatistikLauf_ImportCsvDTO> alleCsvZeilen) {
    if (!alleCsvZeilen.isEmpty()) {
      StatistikLauf_ImportCsvDTO ersteCsvZeile = alleCsvZeilen.get(0);

      if (!ersteCsvZeile.getZeilenArt().equals(StatistikLaufCsvDTOZeilenArt.getZeilenArt_StatistikLauf())) {
        violations.addError("statistik.lauf.import.mapper.erste.zeile.ist.nicht.statistiklauf");
      }
    }
  }

  private void validateStatistikLaufBesitztStatus(Violations violations, StatistikLauf_LaufCsvDTO statistikLaufExtern) {
    if (StringUtils.isBlank(statistikLaufExtern.getStatus())) {
      violations.addError("statistik.lauf.import.status.nicht.gesetzt", statistikLaufExtern.getZeilenNr());
    }
  }

  private void validateStatistikKennzeichenBesitzenKennzeichen(Violations violations, List<StatistikLauf_KennzeichenCsvDTO> statistikKennzeichensZumLaufExtern) {
    statistikKennzeichensZumLaufExtern.stream().filter(statistikKennzeichen -> StringUtils.isBlank(statistikKennzeichen.getKennzeichen())).forEach(statistikKennzeichen -> {
      violations.addError("statistik.kennzeichen.import.kennzeichen.nicht.gesetzt", statistikKennzeichen.getZeilenNr());
    });
  }

  private void validateGebietTK(Violations violations, List<StatistikLauf_KennzeichenCsvDTO> statistikKennzeichensZumLaufExtern) {
    for (StatistikLauf_KennzeichenCsvDTO statistikKennzeichenExtern : statistikKennzeichensZumLaufExtern) {
      if (statistikKennzeichenExtern.getGebietsNummer() == null && statistikKennzeichenExtern.getGebietsartName() != null
          || statistikKennzeichenExtern.getGebietsNummer() != null && statistikKennzeichenExtern.getGebietsartName() == null) {
        violations.addWarning("statistik.lauf.import.gebiet.tk.nicht.vollstaendig.angegeben", statistikKennzeichenExtern.getZeilenNr());
      }
    }
  }

  @Override
  public List<String> getCsvColumnHeaders(StatistikLaufImportContext context) {
    return csvMarshaller.extractHeader(StatistikLauf_LaufCsvDTO.class);
  }
}
