/*
 * ElectionResultCollectorImpl
 *
 * Created on 09.03.2009
 * Copyright (c) 2009 IVU Traffic Technologies AG
 */
package de.ivu.wahl.result.result;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import de.ivu.wahl.result.Candidate;
import de.ivu.wahl.result.determination.GeneralList;
import de.ivu.wahl.result.determination.P2List;
import de.ivu.wahl.result.determination.P3List;
import de.ivu.wahl.result.drawlots.Decision;
import de.ivu.wahl.result.drawlots.DecisionType;
import de.ivu.wahl.result.drawlots.DrawingLotsAlternative;

/**
 * The ElectionResultCollectorImpl stores all relevant information so it can later be compared to the expected results.
 *
 * @author , IVU Traffic Technologies AG
 */
public class ElectionResultCollectorImpl implements ElectionResultCollector {

  private final SortedMap<AssignmentKey, AssignmentValue> assignmentsSorted = new TreeMap<>();
  private final List<Assignment> assignments = new ArrayList<>();
  private final List<Candidate> electedCandidates = new ArrayList<>();
  private final List<CandidateResult> candidateResultsElectedHere = new ArrayList<>();
  private final Map<P2List, Collection<CandidateResult>> candidateResultsMap = new HashMap<>();
  private final Map<P2List, Long> totalSeatsAssigned = new HashMap<>();

  private final List<DHondtSeatDistribution> dHondtSeatDistributions = new ArrayList<>();
  private int numberOfUnassignedSeatsBeforeDHondt; // temporary state
  private final List<SubDistribution> subDistributions = new ArrayList<>();
  private final List<Anomaly> anomaliesToBePublishedInP22 = new ArrayList<>();

  private long numberOfVotes;
  private long numberOfSeats;
  private long preferentialBarrierInPercent;
  private boolean mustExceedPreferentialBarrier;

  // Only for debugging
  private final Set<AssignmentKey> debugSet = new HashSet<>();
  private Decision decision;

  public ElectionResultCollectorImpl() {
    super();
  }

  @Override
  public void setElectoralQuota(long numberOfVotes, long numberOfSeats) {
    this.numberOfVotes = numberOfVotes;
    this.numberOfSeats = numberOfSeats;
  }

  @Override
  public void setPreferentialBarrierInPercent(long preferentialBarrierInPercent) {
    this.preferentialBarrierInPercent = preferentialBarrierInPercent;
  }

  @Override
  public void setMustExceedPreferentialBarrier(boolean mustExceedPreferentialBarrier) {
    this.mustExceedPreferentialBarrier = mustExceedPreferentialBarrier;
  }

  @Override
  public void addDHondtFractions(GeneralList list, GeneralList parent, Distribution distribution,
                                 long priorSeats, long noOfDHondtSeats, List<DHondtFraction> dHondtFractions, long votes) {

    DHondtSeatDistribution dHondtSeatDistribution = new DHondtSeatDistribution();
    dHondtSeatDistribution.setSeats((int) priorSeats);
    dHondtSeatDistribution.setResidualSeats((int) noOfDHondtSeats);
    dHondtSeatDistribution.setTotalNumberOfSeatsToDistribute(numberOfUnassignedSeatsBeforeDHondt); // see OSV-1481
    dHondtSeatDistribution.setList(list);
    dHondtSeatDistribution.setParent(parent);
    dHondtSeatDistribution.setDistributionType(distribution);

    dHondtSeatDistribution.addDHondtFractions(dHondtFractions);

    dHondtSeatDistributions.add(dHondtSeatDistribution);
  }

  /**
   * Information for reports only. nothing.
   */
  @Override
  public void subDistributionStarted(P3List p3List, long votes, long seats,
                                     Distribution distribution) {
    SubDistribution subDistribution = new SubDistribution();
    subDistribution.setSeats((int) seats);
    subDistribution.setVotes((int) votes);
    subDistribution.setP3List(p3List);
    subDistributions.add(subDistribution);
  }

  @Override
  public void assignSeats(Assignment assignment) {
    assignments.add(assignment);

    AssignmentKey assignmentKey = new AssignmentKey(assignment);
    AssignmentValue assignmentValue = new AssignmentValue(assignment);
    AssignmentValue previous = assignmentsSorted.put(assignmentKey, assignmentValue);

    // Debug code
    assert previous == null;
    debugSet.add(assignmentKey);
  }

  @Override
  public void addAnomaly(Anomaly anomaly) {
    if (anomaly.isShallBePublishedInModelP22()) {
      anomaliesToBePublishedInP22.add(anomaly);
    }
  }

  @Override
  public void candidateResult(CandidateResult candidateResult) {
    candidateResultsMap.computeIfAbsent(candidateResult.getP2List(), k -> new ArrayList<>()).add(candidateResult);
    if (candidateResult.isElectedHere()) {
      electedCandidates.add(candidateResult.getCandidate());
      candidateResultsElectedHere.add(candidateResult);
    }
  }

  @Override
  public void setConflict(DecisionType type, List<? extends DrawingLotsAlternative> alternatives) {
    decision = new Decision(type, alternatives, -1);
  }

  @Override
  public void totalSeatsAssigned(P2List p2List, long seats) {
    totalSeatsAssigned.put(p2List, seats);
  }

  /**
   * @return for each P2List the candidates in the order of the new list positions. Dead candidates
   * are omitted.
   */
  private Map<P2List, List<Candidate>> getListPositions(boolean old) {
    Map<P2List, List<Candidate>> result = new HashMap<>();
    for (P2List p2List : candidateResultsMap.keySet()) {
      Collection<CandidateResult> candidateResults = candidateResultsMap.get(p2List);
      int size = candidateResults.size();
      Candidate[] candidates = new Candidate[size];
      for (CandidateResult candidateResult : candidateResults) {
        if (!old && candidateResult.isDeceased()) {
          size--;
        } else {
          int pos;
          if (old) {
            pos = candidateResult.getOldListPosition();
          } else {
            pos = candidateResult.getNewListPosition();
          }
          assert pos > 0 && pos <= size : "New position in list incorrect: " + pos + ", minimum: 1, maximum: " + size;
          candidates[pos - 1] = candidateResult.getCandidate();
        }
      }
      List<Candidate> list = new ArrayList<>();
      for (int i = 0; i < size; i++) {
        Candidate candidate = candidates[i];
        assert candidate != null : "Candidate missing in P2-list " + p2List.getName() + " in position " + (i + 1);
        list.add(candidate);
      }
      result.put(p2List, list);
    }

    return result;
  }

  @Override
  public void setNumberOfUnassignedSeatsBeforeDHondt(int numberOfUnassignedSeatsBeforeDHondt) {
    // Remember this for later
    this.numberOfUnassignedSeatsBeforeDHondt = numberOfUnassignedSeatsBeforeDHondt;
  }

  @Override
  public ElectionResult getElectionResult() {
    return new Output();
  }

  private class Output implements ElectionResult {

    @Override
    public long getNumberOfVotes() {
      return numberOfVotes;
    }

    @Override
    public long getNumberOfSeats() {
      return numberOfSeats;
    }

    @Override
    public long getPreferentialBarrierInPercent() {
      return preferentialBarrierInPercent;
    }

    @Override
    public boolean isMustExceedPreferentialBarrier() {
      return mustExceedPreferentialBarrier;
    }

    @Override
    public List<Candidate> getElectedCandidates() {
      return Collections.unmodifiableList(electedCandidates);
    }

    @Override
    public List<CandidateResult> getCandidateResultsElectedHere() {
      return Collections.unmodifiableList(candidateResultsElectedHere);
    }

    @Override
    public List<Assignment> getAssignments() {
      return Collections.unmodifiableList(assignments);
    }

    @Override
    public SortedMap<AssignmentKey, AssignmentValue> getAssignmentsSorted() {
      return Collections.unmodifiableSortedMap(assignmentsSorted);
    }

    @Override
    public Map<P2List, List<Candidate>> getOldListPositions() {
      return getListPositions(true);
    }

    @Override
    public Map<P2List, List<Candidate>> getNewListPositions() {
      return getListPositions(false);
    }

    @Override
    public Decision getConflict() {
      return decision;
    }

    @Override
    public Map<P2List, Long> getTotalSeatsAssigned() {
      return totalSeatsAssigned;
    }

    @Override
    public List<DHondtSeatDistribution> getDHondtSeatDistributions() {
      return Collections.unmodifiableList(dHondtSeatDistributions);
    }

    @Override
    public List<SubDistribution> getSubDistributions() {
      return subDistributions;
    }

    @Override
    public List<Anomaly> getAnomaliesToBePublishedInP22() {
      return Collections.unmodifiableList(anomaliesToBePublishedInP22);
    }

    /**
     * @return Display the receiver as tab separated table to create an easy overview of all
     * assignments
     */
    @Override
    public String asTable() {
      String tab = "\t";
      String cr = "\n";
      StringBuilder builder = new StringBuilder();
      builder.append("index")
              .append(tab)
              .append("parent")
              .append(tab)
              .append("list")
              .append(tab)
              .append("seats")
              .append(tab)
              .append("lot")
              .append(tab)
              .append("type");
      builder.append(cr);
      GeneralListKey lastParent = new GeneralListKey();
      for (Entry<AssignmentKey, AssignmentValue> entry : assignmentsSorted.entrySet()) {
        AssignmentKey key = entry.getKey();
        AssignmentValue value = entry.getValue();

        if (!AssignmentType.TRIVIAL.equals(value.getAssignmentType())) {
          GeneralListKey parent = key.getAssignmentParentKey();
          if (parent.isP3Parent() && !lastParent.isP3Parent() || !parent.isP3Parent() && lastParent.isP3Parent()
                  || !parent.isP3Parent() && !parent.equals(lastParent)) {
            builder.append(cr);
          }
          lastParent = parent;

          builder.append(key.getIndex());
          builder.append(tab);
          builder.append(lastParent.getKey());
          builder.append(tab);
          builder.append(key.getListKey().getKey());
          builder.append(tab);
          builder.append(value.getSeats());
          builder.append(tab);
          builder.append(value.isByLot() ? "LOT" : "");
          builder.append(tab);
          builder.append(value.getAssignmentType().getKey());
          builder.append(cr);
        }
      }

      builder.append(assignmentsSorted);
      builder.append(")");

      return builder.toString();
    }

    private String getKeyFromList(GeneralList list) {
      if (list == null) {
        return "";
      } else {
        return list.getKey();
      }
    }
  }
}
