
package de.ivu.elect.was.exchange.ergebnis.eml;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.zip.ZipOutputStream;

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

import de.ivu.elect.EmlExportMapperHelper;
import de.ivu.elect.business.CurrentCustomer;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.ergebnis.control.ErgebnisDaten;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportContext;
import de.ivu.elect.business.ergebnis.control.ErgebnisExportObject;
import de.ivu.elect.business.ergebnis.entity.Gewaehlt;
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.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.boundary.Eml520CandidateComparator;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.kandidatur.entity.NlWasListenkandidatur;
import de.ivu.elect.business.person.entity.NlPersonendaten;
import de.ivu.elect.business.security.boundary.EmlSignKeyService;
import de.ivu.elect.business.staat.entity.NlWvpStaat;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.business.wahlvorschlag.entity.NlListe;
import de.ivu.elect.nl.eml.EmlWrapper;
import de.ivu.elect.nl.eml.readable.Eml520CandidateWrapper;
import de.ivu.elect.nl.eml.readable.Eml520ContestWrapper;
import de.ivu.elect.nl.eml.readable.Eml520Wrapper;
import de.ivu.elect.nl.eml.readable.ManagingAuthorityWrapper;
import de.ivu.elect.util.FileUtil;
import de.ivu.elect.wus.exchange.wahlvorschlag.Eml230Mapper;

public class Eml520ErgebnisExportMapper extends AbstractEml5xxErgebnisExportMapper {

  private static final long serialVersionUID = 1L;

  public static final String EML_ID = "520";

  public static final String FILE_FORMAT_NAME = "eml.520.file.format";

  public static final FileFormat FILE_FORMAT = new FileFormat(FILE_FORMAT_NAME, FileType.ZIP);

  @Inject
  private transient Eml230Mapper eml230Mapper;

  @Inject
  private transient EmlExportMapperHelper emlExportMapperHelper;

  @Inject
  private transient EmlSignKeyService emlSignKeyService;

  @Inject
  private transient CurrentCustomer currentCustomer;

  public Eml520ErgebnisExportMapper() {
    super();
  }

  @Override
  public void serialize(ErgebnisExportObject ergebnisExportObject, OutputStream os, ErgebnisExportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    EmlWrapper eml = EmlWrapper.createEml(EML_ID, context.getNow());

    // ELECTVAPP-780
    ErgebnisDaten ergebnisDaten = ergebnisExportObject.getErgebnisDaten();
    List<NlPersonendaten> alleKandidaturen = ergebnisDaten.getKandidaturen().stream().map(NlPersonendaten::new).collect(Collectors.toList());
    Map<String, String> shortCodeNachExterneId = new HashMap<>();

    eml.getContent().setIssueDate(null); // ELECTVAPP-491

    Eml520Wrapper eml520Wrapper = new Eml520Wrapper(eml.getContent());

    mappeWahldaten(ergebnisExportObject, eml520Wrapper, ergebnisDaten.getWurzelgebiet(), () -> {
      List list = ergebnisDaten.getGebiete();

      return list;
    });

    // ELECTVAPP-399
    fillManagingAuthority(eml520Wrapper.getManagingAuthority(), ergebnisExportObject, context);

    List<Gewaehlt> gewaehlte = ergebnisDaten.getGewaehlte();

    Map<PolitischeGruppe, List<Listenkandidatur>> gewaehlteKandidaturenProGruppe = bestimmeGewaehlteKandidatuenProGruppe(gewaehlte,
        ergebnisDaten.getEntityAufStimmzettelDaten());

    Eml520ContestWrapper eml520ContestWrapper = eml520Wrapper.addContest();

    fillContest(ergebnisExportObject, context, context.getWahl(), eml520ContestWrapper); // ELECT-13666

    boolean useShortCode = NlWahlart.valueOf(context.getWahl()).isUseShortCodeForCandidateIdIn510dUnd520(); //

    gewaehlteKandidaturenProGruppe.entrySet().stream().sorted(Comparator.comparing(u -> u.getKey().getRelativePosition())).forEach(e -> {
      PolitischeGruppe politischeGruppe = e.getKey();

      if (new NlPolitischeGruppe(politischeGruppe).isBlancoListe()) {
        eml520ContestWrapper.addElectedParty(Long.toString(politischeGruppe.getRelativePosition()), "");
      } else {
        eml520ContestWrapper.addElectedParty(Long.toString(politischeGruppe.getRelativePosition()), politischeGruppe.getName());
      }

      List<Listenkandidatur> listenkandidaturen = e.getValue();

      // ELECTVAPP-1340
      AtomicLong candidateId = new AtomicLong(0L);

      // ELECTVAPP-1341
      listenkandidaturen.sort(new Eml520CandidateComparator(k -> isUeberVorzugGewaehlt(new NlWasListenkandidatur(k)), k -> getNumberOfVotes(k, ergebnisDaten),
          k -> getShortCode(k, ergebnisDaten),
          k -> getStimmzettelPosition(k, ergebnisDaten), this::getKleinsteWahlkreisNummer));

      for (Listenkandidatur listenkandidatur : listenkandidaturen) {
        NlWasListenkandidatur nlListenkandidatur = new NlWasListenkandidatur(listenkandidatur);

        Eml520CandidateWrapper candidateWrapper = eml520ContestWrapper.addElectedCandidate(getRanking(nlListenkandidatur));
        candidateWrapper.setId(Long.toString(candidateId.addAndGet(1))); // ELECTVAPP-1340
        if (useShortCode /*ELECT-13766 ELECTVAPP-1707 */) {
          candidateWrapper.setShortCode(nlListenkandidatur.getShortCode(alleKandidaturen, shortCodeNachExterneId, currentCustomer.getCustomer()));
        }
        candidateWrapper.setInitials(nlListenkandidatur.getInitialen()); // ELECT-9564
        candidateWrapper.setFirstName(nlListenkandidatur.getVorname());// ELECT-9564
        candidateWrapper.setLastName(nlListenkandidatur.getFamilienname());
        candidateWrapper.setNamePrefix(nlListenkandidatur.getFamiliennameBestandteil());

        if (NlListe.isPublishGender(nlListenkandidatur.getListe())) {
          candidateWrapper.setGender(eml230Mapper.mappeGeschlecht(nlListenkandidatur.getGeschlecht()));
        }

        // ELECT-14252 wie in de.ivu.elect.wus.exchange.wahlvorschlag.AbstractEml230WahlvorschlagExportMapper.addListe

        NlWvpStaat nlWvpStaat = NlWvpStaat.valueOfIso3166Alpha2(nlListenkandidatur.getWohnanschrift().getStaat());

        if (!nlWvpStaat.isBekannt() && nlListenkandidatur.getWohnanschrift().getStaat() == null) {
          nlWvpStaat = NlWvpStaat.NIEDERLANDE; // ELECT-14252 leer is NL, passt wohl besser...
        }

        if (nlWvpStaat.isNiederlande()/* ELECTVAPP-783 */) {
          candidateWrapper.setLocalityName(nlListenkandidatur.getWohnanschrift().getOrt()); // ELECT-9796
        } else {
          candidateWrapper.setCountryNameCode(nlWvpStaat.getIso3166Alpha2(), nlListenkandidatur.getEmlLocalityName()/* ELECTVAPP-1110 */); // ELECT-9796
          candidateWrapper.setLocalityName(null); // ELECT-14252
        }
      }
    });

    boolean singleEmlXmlFileExported = true;

    try (ZipOutputStream zos = new ZipOutputStream(os)) {
      Wahldurchfuehrung wahldurchfuehrung = ergebnisDaten.getWahl();

      emlExportMapperHelper.writeEml(eml, zos, createEmlXmlFileName(ergebnisExportObject, context), ergebnisDaten.getWurzelgebiet(), context,
          retrievePrivateKey(wahldurchfuehrung, context, emlSignKeyService), retrievePublicKey(wahldurchfuehrung, context, emlSignKeyService),
          retrieveNlX509Metadata(wahldurchfuehrung, context, emlSignKeyService));

      zos.finish();
    } catch (IOException e) {
      throw new MappingException(e.getMessage(), e);
    }
  }

  private Long getKleinsteWahlkreisNummer(Listenkandidatur listenkandidatur) {
    return new NlWasListenkandidatur(listenkandidatur).getKleinsteWahlkreisNummer();
  }

  private Long getStimmzettelPosition(Listenkandidatur k, ErgebnisDaten ergebnisDaten) {
    Long result = k.getStimmzettelPosition(ergebnisDaten.getEntityAufStimmzettelDaten()).getPosition();
    if (result == null) {
      return 0L;
    }
    return result;
  }

  private String getShortCode(Listenkandidatur k, ErgebnisDaten ergebnisDaten) {
    return new NlWasListenkandidatur(k).getShortCode(Collections.emptyList(), Collections.emptyMap(), getCustomer());
  }

  private Long getNumberOfVotes(Listenkandidatur k, ErgebnisDaten ergebnisDaten) {
    Map<Listenkandidatur, Long> stimmenAllerKanddaten = new HashMap<>();
    stimmenAllerKanddaten.put(k, getStimmenAufWahlgebiet(k, ergebnisDaten));

    for (Listenkandidatur listenkandidatur : k.getIdentischVerknuepfteListenkandidaturen()) {
      stimmenAllerKanddaten.put(listenkandidatur, getStimmenAufWahlgebiet(listenkandidatur, ergebnisDaten));
    }

    return stimmenAllerKanddaten.values().stream().mapToLong(l -> l).sum();
  }

  private Long getStimmenAufWahlgebiet(Listenkandidatur k, ErgebnisDaten ergebnisDaten) {
    Long stimmenFuerListenkandidatur = ergebnisDaten.getStimmenFuerListenkandidatur(k, k.getGebiet().castTo(Wahldurchfuehrungsgebiet.class).getEntity());
    if (stimmenFuerListenkandidatur == null) {
      return 0L;
    }
    return stimmenFuerListenkandidatur;
  }

  private BigInteger getRanking(NlWasListenkandidatur nlListenkandidatur) {
    // Semantik zu klären
    if (isUeberVorzugGewaehlt(nlListenkandidatur)) {
      return BigInteger.ONE;
    }

    return BigInteger.valueOf(2);
  }

  private boolean isUeberVorzugGewaehlt(NlWasListenkandidatur nlListenkandidatur) {
    return nlListenkandidatur.isUeberVorzugGewaehlt();
  }

  private Map<PolitischeGruppe, List<Listenkandidatur>> bestimmeGewaehlteKandidatuenProGruppe(List<Gewaehlt> gewaehlte, EntityAufStimmzettelDaten entityAufStimmzettelDaten) {

    Map<PolitischeGruppe, List<Listenkandidatur>> result = new HashMap<>();

    Map<PolitischeGruppe, Set<Listenkandidatur>> listenkandidaturenProGruppe = new HashMap<>();

    for (Gewaehlt gewaehlt : gewaehlte) {
      if (gewaehlt.getGewaehltStatus().isGewaehlt()) {
        listenkandidaturenProGruppe.computeIfAbsent(gewaehlt.getKandidatur().getGruppe(), x -> new HashSet<>())
            .add(gewaehlt.getKandidatur().castTo(Listenkandidatur.class).getEntity());
      }
    }

    for (Map.Entry<PolitischeGruppe, Set<Listenkandidatur>> entry : listenkandidaturenProGruppe.entrySet()) {
      PolitischeGruppe politischeGruppe = entry.getKey();

      Set<Listenkandidatur> kandidaten = new HashSet<>();
      for (Listenkandidatur listenkandidatur : entry.getValue()) {
        if (!isVerknupefteKandidaturenSchonVorhanden(listenkandidatur, kandidaten)) {
          kandidaten.add(listenkandidatur);
        }
      }
      List<Listenkandidatur> listenkandidaturen = kandidaten.stream()
          .sorted(Comparator.comparing(u -> notNull(u.getStimmzettelPosition(entityAufStimmzettelDaten).getPosition())))
          .collect(Collectors.toList());

      result.put(politischeGruppe, listenkandidaturen);

    }

    return result;

  }

  private Long notNull(Long position) {
    if (position == null) {
      return 0L;
    }
    return position;
  }

  private boolean isVerknupefteKandidaturenSchonVorhanden(Listenkandidatur listenkandidatur, Set<Listenkandidatur> kandidaten) {
    return listenkandidatur.getIdentischVerknuepfteKandidaturen().stream().anyMatch(kandidaten::contains);
  }

  @Override // ELECTVAPP-1534
  protected String createSimpleFileName(ErgebnisExportObject ergebnisExportObject, ErgebnisExportContext context) {
    String simpleFileName = getMessage("eml.resultat.prefix");
    simpleFileName += "_";
    simpleFileName += ergebnisExportObject.getErgebnisDaten().getWahl().getKennzeichen();

    return FileUtil.normalizeFileNamePreserveCase(simpleFileName);
  }

  @Override
  protected boolean isErgebnisNotwendig() {
    return true;
  }

  @Override
  protected boolean isGsbNotwendig() {
    return false;
  }

  @Override
  protected boolean isHsbNotwendig() {
    return false;
  }

  @Override
  protected boolean isCsbNotwendig() {
    return true;
  }

  @Override
  protected boolean isGebietAuswahlNotwendig() {
    return false;
  }

  @Override
  protected boolean isErfassungsgebietAuswahlNotwendig() {
    return false;
  }

  @Nullable
  @Override
  protected Wahldurchfuehrungsgebiet bestimmeGebietFuerDaten(ErgebnisExportObject ergebnisExportObject, ErgebnisExportContext context) {
    return bestimmeOberstesGebiet(ergebnisExportObject, context);
  }

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

  @Override
  public boolean isSignEml(Wahl wahl) {
    return false; // nicht notwendig siehe https://support.elect-it.com/wiki/display/VAPP/EML-Import+validation
  }

  @Override
  protected void fillManagingAuthority(ManagingAuthorityWrapper managingAuthority, ErgebnisExportObject ergebnisExportObject, ErgebnisExportContext context) {
    super.fillManagingAuthority(managingAuthority, ergebnisExportObject, context);
    managingAuthority.setAuthorityId("CSB"); // ELECTVAPP-474
  }

  @Override
  protected String getAuthorityIdPrefix(ErgebnisExportObject ergebnisExportObject) {
    return ""; // siehe oben, wird überschrieben
  }

  @Override
  protected String createdByAuthorityIdPrefix(ErgebnisExportObject ergebnisExportObject) {
    return "";
  }

  @Override
  protected boolean is510a() {
    return false;
  }

  @Override
  protected boolean is510b() {
    return false;
  }

  @Override
  protected boolean is510c() {
    return false;
  }

  @Override
  protected boolean is510d() {
    return false;
  }

  @Override
  protected boolean is520() {
    return true;
  }
}
