package de.ivu.elect.was.exchange.diff;

import java.math.BigInteger;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import javax.annotation.Nullable;

import de.ivu.elect.business.stimmeingabe.boundary.NlVoteType;
import de.ivu.elect.nl.eml.readable.AbstractEmlElectionWrapper;
import de.ivu.elect.nl.eml.readable.Eml510ContestWrapper;
import de.ivu.elect.nl.eml.readable.Eml510Wrapper;
import de.ivu.elect.nl.eml.readable.GroupVotesSection;
import de.ivu.elect.nl.eml.readable.TotalVotesWrapper;
import de.ivu.wahl.wus.oasis.eml.EML;

public class Eml510ErgebnisDifferenzBerechner {

  private final Eml510Wrapper eml510Vorher;

  private final Eml510Wrapper eml510Nachher;

  public Eml510ErgebnisDifferenzBerechner(EML eml510Vorher, EML eml510Nachher) {
    this.eml510Vorher = new Eml510Wrapper(eml510Vorher);
    this.eml510Nachher = new Eml510Wrapper(eml510Nachher);
  }

  @Nullable
  public EmlErgebnisDifferenz berechneDifferenz() {

    validateMetadata();

    for (var eml510ContestVorher : eml510Vorher.getContest()) {
      var eml510ContestNachher = findeContest(eml510ContestVorher, eml510Nachher);
      if (eml510ContestNachher != null) {
        var emlErgebnisDifferenz = new EmlErgebnisDifferenz();

        // Erstmal nur totalvotes....
        if (eml510ContestVorher.getTotalVotes().size() > 1) {
          throw new IllegalStateException("Totalvotes before: " + eml510ContestVorher.getTotalVotes().size());
        }
        if (eml510ContestNachher.getTotalVotes().size() > 1) {
          throw new IllegalStateException("Totalvotes after: " + eml510ContestNachher.getTotalVotes().size());
        }

        if (!eml510ContestVorher.getTotalVotes().isEmpty() && !eml510ContestNachher.getTotalVotes().isEmpty()) {

          var totalVoteVorher = eml510ContestVorher.getTotalVotes().get(0);
          var totalVoteNachher = eml510ContestNachher.getTotalVotes().get(0);

          for (var entryVorher : totalVoteVorher.getVotesWrapper().getCandidateVotesPerGroup().entrySet()) {
            var entryNachher = findeGroupVotesEntryNachher(totalVoteNachher, entryVorher);
            if (entryNachher != null) {
              var groupVotesSectionVorher = entryVorher.getKey();
              var kandidatenStimmenVorher = entryVorher.getValue();

              var groupVotesSectionNachher = entryNachher.getKey();
              var kandidatenStimmenNachher = entryNachher.getValue();

              var listenId = groupVotesSectionVorher.getId();

              emlErgebnisDifferenz.addListenstimmenUnterschied(listenId, getListenStimmen(groupVotesSectionVorher),
                  getListenStimmen(groupVotesSectionNachher));

              for (var kandidatenEntriesVorher : kandidatenStimmenVorher.entrySet()) {
                var kandidatenEntriesNachher = findeKandidatenEntriesNachher(kandidatenStimmenNachher, kandidatenEntriesVorher);
                if (kandidatenEntriesNachher != null) {
                  var kandidatVorher = kandidatenEntriesVorher.getKey();
                  var stimmenVorher = kandidatenEntriesVorher.getValue();

                  var stimmenNachher = kandidatenEntriesNachher.getValue();

                  emlErgebnisDifferenz.addListenkandidaturstimmenUnterschied(listenId, kandidatVorher, parseLong(stimmenVorher),
                      parseLong(stimmenNachher));
                }
              }
            }
          }

          var validVotesVorher = totalVoteVorher.getVotesWrapper().getValidVotes();
          var validVotesNacher = totalVoteNachher.getVotesWrapper().getValidVotes();

          for (var entryVorher : validVotesVorher.entrySet()) {
            var voteTypeVorher = entryVorher.getKey();
            emlErgebnisDifferenz.addVoteTypeUnterschied(NlVoteType.valueOf(voteTypeVorher.name()), parseLong(entryVorher.getValue()),
                parseLong(validVotesNacher.get(voteTypeVorher)));
          }

        }
        return emlErgebnisDifferenz;
      }
    }
    return null;
  }

  private void validateMetadata() {
    if (eml510Vorher.getContest().size() != 1) {
      throw new IllegalStateException("Contests: " + eml510Vorher.getContest());
    }
    validate(e -> e.getContent().getId(), "Id");
    validate(e -> e.getManagingAuthority().getAuthorityId(), "AuthorityIdentifier");
    validate(e -> e.getManagingAuthority().getAuthorityName(), "ManagingAuthority");
    validate(e -> e.getElectionIdentifierStructure().getId(), "ElectionIdentifier");
    validate(e -> e.getElectionIdentifierStructure().getElectionName(), "ElectionName");
    validate(e -> e.getElectionIdentifierStructure().getElectionCategory(), "ElectionCategory");
    validate(AbstractEmlElectionWrapper::getElectionDate, "ElectionDate");
    validate(e -> eml510Vorher.getContest().size(), "Contests");
    validate(e -> eml510Vorher.getContest().get(0).getContestId(), "ContestId");
    validate(e -> eml510Vorher.getContest().get(0).getContestName(), "ContestName");
  }

  private void validate(Function<Eml510Wrapper, Object> getter, String message) {
    if (!Objects.equals(getter.apply(eml510Vorher), getter.apply(eml510Nachher))) {
      throw new IllegalStateException("Invalid: " + message + ", " + getter.apply(eml510Vorher) + " <> " + getter.apply(eml510Nachher));
    }
  }

  @Nullable
  private Map.Entry<String, BigInteger> findeKandidatenEntriesNachher(Map<String, BigInteger> kandidatenStimmenNachher, Map.Entry<String, BigInteger> kandidatenEntriesVorher) {
    return kandidatenStimmenNachher.entrySet().stream()
        .filter(entry -> Objects.equals(entry.getKey(), kandidatenEntriesVorher.getKey()))
        .findFirst()
        .orElse(null);
  }

  @Nullable
  private Map.Entry<GroupVotesSection, Map<String, BigInteger>> findeGroupVotesEntryNachher(TotalVotesWrapper totalVotesWrapper, Map.Entry<GroupVotesSection, Map<String, BigInteger>> entryVorher) {
    return totalVotesWrapper.getVotesWrapper().getCandidateVotesPerGroup().entrySet().stream()
        .filter(entry -> Objects.equals(entry.getKey().getId(), entryVorher.getKey().getId()))
        .findFirst()
        .orElse(null);
  }

  @Nullable
  private Long getListenStimmen(GroupVotesSection groupVotesSection) {
    return parseLong(groupVotesSection.getVotes());
  }

  @Nullable
  private Long parseLong(BigInteger value) {
    if (value == null) {
      return null;
    }
    return value.longValue();

  }

  @Nullable
  private Eml510ContestWrapper findeContest(Eml510ContestWrapper eml510ContestVorher, Eml510Wrapper eml510Nachher) {
    return eml510Nachher.getContest().stream()
        .filter(eml510ContestNachher -> Objects.equals(eml510ContestVorher.getContestId(), eml510ContestNachher.getContestId()))
        .findFirst()
        .orElse(null);
  }
}
