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

import java.math.BigInteger;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

import javax.annotation.Nullable;

import de.ivu.elect.nl.eml.readable.GroupVotesSection;
import de.ivu.elect.nl.eml.readable.IdShortCode;
import de.ivu.elect.nl.eml.readable.VoteType;

public class EmlVotes {

  private final static Comparator<String> LONG_THEN_STRING_COMPARATOR = (u, v) -> {

    Long longU = parse(u);
    Long longV = parse(v);

    if (longU == null && longV != null) {
      return 1;
    }
    if (longV == null && longU != null) {
      return -1;
    }

    if (longU != null /*&& longV != null*/) {
      return longU.compareTo(longV);
    }

    return u.compareTo(v);
  };

  protected final static Comparator<IdShortCode> ID_SHORT_CODE_COMPARATOR = (u, v) -> {
    if (u == v) {
      return 0;
    }
    if (u == null) {
      return 1;
    }
    return u.compareTo(v);
  };

  private final static Comparator<GroupVotesSection> GROUP_VOTES_SECTION_COMPARATOR = (u, v) -> LONG_THEN_STRING_COMPARATOR.compare(u.getId(), v.getId());

  @Nullable
  private static Long parse(String s) {
    try {
      return Long.parseLong(s);
    } catch (Exception e) {
      return null;
    }

  }

  private final Map<GroupVotesSection, Map<IdShortCode, BigInteger>> candidateVotes = new TreeMap<>(EmlVotes.GROUP_VOTES_SECTION_COMPARATOR);

  private final Map<VoteType, BigInteger> validVotes = new LinkedHashMap<>();

  public Map<GroupVotesSection, Map<IdShortCode, BigInteger>> getCandidateVotes() {
    return candidateVotes;
  }

  public Map<VoteType, BigInteger> getValidVotes() {
    return validVotes;
  }

  public void put(VoteType voteType, BigInteger anzahl) {
    validVotes.put(voteType, anzahl);
  }

  public Map<IdShortCode, BigInteger> getGroupVotes(GroupVotesSection groupVotesSection) {
    return candidateVotes.computeIfAbsent(groupVotesSection, x -> new HashMap<>() /*new TreeMap<>(EmlVotes.ID_SHORT_CODE_COMPARATOR)*/);
  }

  public void add(EmlVotes other) {
    addCandidateVotes(other.getCandidateVotes());
    addValidVotes(other.getValidVotes());
  }

  private void addCandidateVotes(Map<GroupVotesSection, Map<IdShortCode, BigInteger>> otherCandidateVotes) {

    for (Map.Entry<GroupVotesSection, Map<IdShortCode, BigInteger>> entry : otherCandidateVotes.entrySet()) {
      GroupVotesSection otherGroupVotesSection = entry.getKey();
      GroupVotesSection thisGroupVotesSection = findeOderLegeAn(otherGroupVotesSection);

      BigInteger otherVotes = otherGroupVotesSection.getVotes();
      BigInteger thisVotes = thisGroupVotesSection.getVotes();
      BigInteger resultVotes = add(thisVotes, otherVotes);
      thisGroupVotesSection.setVotes(resultVotes);

      Map<IdShortCode, BigInteger> otherCandidats = entry.getValue();
      Map<IdShortCode, BigInteger> thisCandidates = getGroupVotes(thisGroupVotesSection);
      Map<IdShortCode, BigInteger> resultCandidateVotes = kombiniere(thisCandidates, otherCandidats);

      this.candidateVotes.put(thisGroupVotesSection, resultCandidateVotes);
    }

  }

  protected static Map<IdShortCode, BigInteger> kombiniere(Map<IdShortCode, BigInteger> one, Map<IdShortCode, BigInteger> two) {
    Map<IdShortCode, BigInteger> result = new HashMap<>();// new TreeMap<>(EmlVotes.ID_SHORT_CODE_COMPARATOR);
    result.putAll(one);

    for (Map.Entry<IdShortCode, BigInteger> entry : two.entrySet()) {
      IdShortCode key = entry.getKey();
      BigInteger oneValue = result.get(key);
      BigInteger twoValue = entry.getValue();
      result.put(key, add(oneValue, twoValue));
    }

    return result;
  }

  private static BigInteger add(BigInteger one, BigInteger two) {
    if (one == null) {
      return two;
    }
    if (two == null) {
      return one;
    }
    return one.add(two);

  }

  private GroupVotesSection findeOderLegeAn(GroupVotesSection otherGroupVotesSection) {
    for (GroupVotesSection groupVotesSection : candidateVotes.keySet()) {
      if (groupVotesSection.equals(otherGroupVotesSection)) {
        return groupVotesSection;
      }
    }
    return new GroupVotesSection(otherGroupVotesSection.getId(), otherGroupVotesSection.getRegisteredName(), null);
  }

  private void addValidVotes(Map<VoteType, BigInteger> otherValidVotes) {
    for (Map.Entry<VoteType, BigInteger> entry : otherValidVotes.entrySet()) {
      BigInteger thisValue = validVotes.getOrDefault(entry.getKey(), null);
      BigInteger otherValue = entry.getValue();
      validVotes.put(entry.getKey(), add(thisValue, otherValue));
    }

  }

}
;