
package de.ivu.elect.presentation.verteilung;

import static de.ivu.elect.business.verteilung.boundary.VerteilungContext.fixNonPositiveToZero;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;

import de.ivu.elect.business.verteilung.boundary.VerteilungAlgorithmus;
import de.ivu.elect.business.verteilung.boundary.VerteilungContext;
import de.ivu.elect.business.verteilung.boundary.VerteilungErgebnis;
import de.ivu.elect.business.verteilung.boundary.VerteilungTeilnehmer;
import de.ivu.elect.business.verteilung.boundary.VerteilungTeilnehmerErgebnis;
import de.ivu.elect.business.verteilung.boundary.VerteilungZuteilungsdivisor;

public class VerteilungBerechnenViewErgebnis {

  private final VerteilungContext context;

  private final VerteilungErgebnis ergebnis;

  private final long anzahlStimmen;

  private final long anzahlSitzeVerteiltVorab;

  private final long anzahlSitzeVerteiltInVerteilung;

  private final long anzahlSitzeVerteiltInLosverfahren;

  private final List<VerteilungBerechnenViewTeilnehmerErgebnis> teilnehmerErgebnisse;

  private final Optional<VerteilungTeilnehmerErgebnis> teilnehmerErgebnisTeilnehmerIsInMehrheitssicherung;

  private final List<VerteilungTeilnehmerErgebnis> teilnehmerErgebnisseTeilnehmerIsInLosverfahren;

  public VerteilungBerechnenViewErgebnis(VerteilungBerechnenViewContext context) {
    this(context, emptyMap());
  }

  public VerteilungBerechnenViewErgebnis(VerteilungBerechnenViewContext context, Map<VerteilungTeilnehmer, Long> anzahlSitzeVerteiltVorabPerTeilnehmer) {
    super();

    this.context = context.getVerteilungContext();

    this.ergebnis = context.verteile(anzahlSitzeVerteiltVorabPerTeilnehmer);

    this.teilnehmerErgebnisse = new ArrayList<>();

    int anzahlQuotientenRelevant = this.ergebnis.getAnzahlQuotientenRelevant();

    Collection<VerteilungTeilnehmer> teilnehmerMitTeilnehmerErgebnisInVerteilung = new HashSet<>();

    // Ergebnisse aus der Verteilung...
    for (VerteilungTeilnehmerErgebnis teilnehmerErgebnisInVerteilung : this.ergebnis.collectTeilnehmerErgebnisse()) {
      VerteilungTeilnehmer teilnehmerInVerteilung = teilnehmerErgebnisInVerteilung.getTeilnehmer();
      teilnehmerMitTeilnehmerErgebnisInVerteilung.add(teilnehmerInVerteilung);

      this.teilnehmerErgebnisse
          .add(new VerteilungBerechnenViewTeilnehmerErgebnis(this.context, teilnehmerErgebnisInVerteilung, anzahlQuotientenRelevant, teilnehmerErgebnisInVerteilung.getQuotienten()));
    }

    // Ergebnisse NICHT aus der Verteilung (Anzahl Sitze vorab festgelegt)...
    long anzahlStimmenNichtInVerteilung = 0L;

    long anzahlSitzeVerteiltVorabNichtInVerteilung = 0L;

    for (Entry<VerteilungTeilnehmer, Long> entry : anzahlSitzeVerteiltVorabPerTeilnehmer.entrySet()) {
      VerteilungTeilnehmer teilnehmer = entry.getKey();

      if (!teilnehmerMitTeilnehmerErgebnisInVerteilung.contains(teilnehmer)) {
        long teilnehmerAnzahlStimmen = teilnehmer.getAnzahlStimmen();
        anzahlStimmenNichtInVerteilung += teilnehmerAnzahlStimmen;

        long teilnehmerAnzahlSitzeAnspruch = fixNonPositiveToZero(entry.getValue());
        anzahlSitzeVerteiltVorabNichtInVerteilung += teilnehmerAnzahlSitzeAnspruch;

        this.teilnehmerErgebnisse.add(Math.min(teilnehmer.getIndexZeroBased(), this.teilnehmerErgebnisse.size()), new VerteilungBerechnenViewTeilnehmerErgebnis(this.context, teilnehmer, false, false,
            true, teilnehmerAnzahlStimmen, teilnehmerAnzahlSitzeAnspruch, teilnehmerAnzahlSitzeAnspruch, 0L, anzahlQuotientenRelevant, emptyList()));
      }
    }

    this.anzahlStimmen = this.ergebnis.getAnzahlStimmen() + anzahlStimmenNichtInVerteilung;

    this.anzahlSitzeVerteiltVorab = this.ergebnis.getAnzahlSitzeVerteiltVorab() + anzahlSitzeVerteiltVorabNichtInVerteilung;
    this.anzahlSitzeVerteiltInVerteilung = this.ergebnis.getAnzahlSitzeVerteiltInVerteilung();
    this.anzahlSitzeVerteiltInLosverfahren = this.ergebnis.getAnzahlSitzeVerteiltInLosverfahren();

    this.teilnehmerErgebnisTeilnehmerIsInMehrheitssicherung = this.ergebnis.findTeilnehmerErgebnisTeilnehmerIsInMehrheitssicherung();

    this.teilnehmerErgebnisseTeilnehmerIsInLosverfahren = this.ergebnis.collectTeilnehmerErgebnisseTeilnehmerIsInLosverfahren();
  }

  public VerteilungContext getContext() {
    return context;
  }

  public VerteilungAlgorithmus getAlgorithmus() {
    return ergebnis.getAlgorithmus();
  }

  public Optional<VerteilungZuteilungsdivisor> getZuteilungsdivisor() {
    return ergebnis.getZuteilungsdivisor();
  }

  public long getAnzahlStimmen() {
    return anzahlStimmen;
  }

  public long getAnzahlSitzeVerteilt() {
    return getAnzahlSitzeVerteiltVorab() + getAnzahlSitzeVerteiltInVerteilung();
  }

  public long getAnzahlSitzeVerteiltVorab() {
    return anzahlSitzeVerteiltVorab;
  }

  public long getAnzahlSitzeVerteiltInVerteilung() {
    return anzahlSitzeVerteiltInVerteilung;
  }

  public long getAnzahlSitzeVerteiltInLosverfahren() {
    return anzahlSitzeVerteiltInLosverfahren;
  }

  public List<VerteilungBerechnenViewTeilnehmerErgebnis> collectTeilnehmerErgebnisse() {
    return teilnehmerErgebnisse;
  }

  public Optional<VerteilungTeilnehmerErgebnis> findTeilnehmerErgebnisTeilnehmerIsInMehrheitssicherung() {
    return teilnehmerErgebnisTeilnehmerIsInMehrheitssicherung;
  }

  public List<VerteilungTeilnehmerErgebnis> collectTeilnehmerErgebnisseTeilnehmerIsInLosverfahren() {
    return teilnehmerErgebnisseTeilnehmerIsInLosverfahren;
  }

  public Optional<Exception> getException() {
    return ergebnis.getException();
  }
}
