
package de.ivu.elect.wus.exchange.wahlvorschlag.csv;

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.kandidatur.control.WahlvorschlagImportContext;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.KandidaturExtendedTK;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagTK;
import de.ivu.elect.exchange.CsvImportMapper;
import de.ivu.elect.exchange.csv.CsvMarshaller;
import de.ivu.elect.jee.exchange.Violations;
import org.apache.commons.lang3.StringUtils;
import org.jsefa.DeserializationException;

import javax.inject.Inject;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static de.ivu.elect.business.wahlvorschlag.entity.AbstractWahlvorschlag.extractAllKandidaturen;

public class WahlvorschlagCsvImportMapper extends AbstractWahlvorschlagCsvImportMapper<DefaultWahlvorschlagCsvDTO> implements CsvImportMapper<List<Wahlvorschlag>, WahlvorschlagImportContext> {

  public static final FileFormat FILE_FORMAT = DefaultWahlvorschlagCsvDTO.FILE_FORMAT;

  @Inject
  protected transient WahlvorschlagCsvExternInternMapper wahlvorschlagCsvExternInternMapper;

  @Inject
  protected transient CsvMarshaller csvMarshaller;

  public WahlvorschlagCsvImportMapper() {
    super();
  }

  @Override
  public boolean isAcceptContext(WahlvorschlagImportContext context) {
    // NOTE, dass derzeit nur Listen, Listenkandidaturen und Direktkandidaturen unterstützt werden
    return context.getWahl() != null && (context.getWahl().isWahlMitDirektkandidatengebieten() || context.getWahl().isWahlMitListengebieten());
  }

  @Override
  public List<Wahlvorschlag> deserializeWahlvorschlaege(InputStream is, WahlvorschlagImportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    List<Wahlvorschlag> result = mapToWahlvorschlaege(deserialize(is, fileKonfiguration), context);

    vervollstaendigeKandidaturenAusDubletten(result, context);

    return result;
  }

  private List<DefaultWahlvorschlagCsvDTO> deserialize(InputStream is, FileKonfiguration fileKonfiguration) throws MappingException {
    try {
      return csvMarshaller.deserialize(DefaultWahlvorschlagCsvDTO.class, is, fileKonfiguration);
    } catch (DeserializationException rethrownEx) {
      throw new MappingException("Die CSV-Datei ist nicht lesbar.", rethrownEx);
    }
  }

  /**
   * Der Einfachheit halber muss eine Person, die auf zwei Gebieten antritt, nur auf einer CSV-Zeile notiert werden (das zweite Gebiet wird als ihre Dublette markiert). Solche "impliziten
   * Kandidaturen" werden hier mit Daten befüllt.
   */
  private void vervollstaendigeKandidaturenAusDubletten(List<Wahlvorschlag> wahlvorschlaege, WahlvorschlagImportContext context) {
    Map<WahlvorschlagTK, Wahlvorschlag> wahlvorschlaegePerTK = WahlvorschlagTK.mappeWahlvorschlagNachTK(wahlvorschlaege);
    Map<KandidaturExtendedTK, Kandidatur> kandidaturenPerTK = context.getKandidaturenNachTkFunction().apply(extractAllKandidaturen(wahlvorschlaege));

    for (Kandidatur kandidatur : new ArrayList<>(kandidaturenPerTK.values())) {
      for (PersonenVerknuepfung verknuepfung : kandidatur.getPersonenVerknuepfungen()) {
        Kandidatur otherKandidatur = verknuepfung.getOther(kandidatur).castTo(Kandidatur.class).getEntity();

        if (!kandidaturenPerTK.containsKey(context.getKandidaturExtendedTK(otherKandidatur))) { // d.h. die Kandidatur kommt noch nirgends vor und ist "implizit" angegeben worden
          Wahlvorschlag otherWahlvorschlag = otherKandidatur.getWahlvorschlag();

          if (!wahlvorschlaegePerTK.containsKey(otherWahlvorschlag.getWahlvorschlagTK())) { // d.h. auch den Wahlvorschlag hierzu gibt es noch nicht
            vervollstaendigeWahlvorschlagAusDublette(otherWahlvorschlag, kandidatur.getWahlvorschlag());

            wahlvorschlaege.add(otherWahlvorschlag);
            wahlvorschlaegePerTK.put(otherWahlvorschlag.getWahlvorschlagTK(), otherWahlvorschlag);
          } // ...ansonsten wird die Kandidatur dem richtigen Wahlvorschlag dann vom Importer zugeordnet

          kandidaturenPerTK.put(context.getKandidaturExtendedTK(otherKandidatur), otherKandidatur);
        }
      }
    }
  }

  private void vervollstaendigeWahlvorschlagAusDublette(Wahlvorschlag wahlvorschlagUnvollstaendig, Wahlvorschlag wahlvorschlagVorlage) {
    if (StringUtils.isBlank(wahlvorschlagUnvollstaendig.getKennwort())) {
      wahlvorschlagUnvollstaendig.setKennwort(wahlvorschlagVorlage.getKennwort());
      wahlvorschlagUnvollstaendig.setKurzname(wahlvorschlagVorlage.getKurzname());
    }
  }

  @Override
  protected Violations validateInputFormatInternal(InputStream is, WahlvorschlagImportContext wahlvorschlagImportContext, FileKonfiguration fileKonfiguration) {
    return new Violations();
  }

  @Override
  public FileFormat getFileFormat() {
    return FILE_FORMAT;
  }

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

  @Override
  protected Listenkandidatur mapToListenkandidatur(DefaultWahlvorschlagCsvDTO wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    return wahlvorschlagCsvExternInternMapper.mapToListenkandidatur(wahlvorschlagCsv, context);
  }

  @Override
  protected Liste mapToListeOhneListenkandidatur(DefaultWahlvorschlagCsvDTO wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    return wahlvorschlagCsvExternInternMapper.mapToListeOhneListenkandidatur(wahlvorschlagCsv, context);
  }

  @Override
  protected Direktkandidat mapToDirektkandidat(DefaultWahlvorschlagCsvDTO wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException {
    return wahlvorschlagCsvExternInternMapper.mapToDirektkandidat(wahlvorschlagCsv, context);
  }
}
