
package de.ivu.elect.business.stimmeingabe.control;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import de.ivu.elect.business.ergebnis.entity.Stimmenart;
import de.ivu.elect.business.stimmeingabe.control.ErgebniseingangEingabeHashCodeGenerator.StimmeingabeIndiziert;
import de.ivu.elect.util.NullComparator;

/**
 * Erste Implementierung eines (DB-seitig) suchbaren Hash-Codes für einen Ergebniseingang. Beachte, dass die Stimmen anhand der Stimmzettelposition sortiert werden, so dass diese Implementierung NICHT
 * FÜR PLAUSIS ÜBER VERSCHIEDENE STIMMZETTELGEBIETE (Wahlkreise, Stimmkreise, ..) funktioniert, sofern diese Plausis dieselben Gruppen an denselben Positionen/Indexen vergleichen müssen!!!
 */
public class ErgebniseingangEingabeHashCodeCalculatorByStimmenartAndAnzahlPerWahlvorschlagStimmzettelPosition implements ErgebniseingangEingabeHashCodeCalculator {

  private static final long serialVersionUID = 1L;

  public ErgebniseingangEingabeHashCodeCalculatorByStimmenartAndAnzahlPerWahlvorschlagStimmzettelPosition() {
    super();
  }

  @Override
  public String calculateHashCode(Map<Stimmenart, List<StimmeingabeIndiziert>> stimmeingabenPerStimmenart) {
    return calculateHashCode(stimmeingabenPerStimmenart, collectStimmenartenRelevantSorted(), "", Long.MAX_VALUE);
  }

  private String calculateHashCode(Map<Stimmenart, List<StimmeingabeIndiziert>> stimmeingabenPerStimmenart, List<Stimmenart> stimmenartenSorted, String stimmenartSeparator, long numberOfGruppen) {
    return calculateHashCodeByStimmenartAndAnzahlPerWahlvorschlagStimmzettelPosition(stimmeingabenPerStimmenart, stimmenartenSorted, stimmenartSeparator, numberOfGruppen);
  }

  @Override
  public String calculateHashCodeSearchPatternByNumberOfGruppen(Map<Stimmenart, List<StimmeingabeIndiziert>> stimmeingabenPerStimmenart, String wildcardCharacter, long numberOfGruppen) {
    return calculateHashCodeSearchPatternByNumberOfGruppen(stimmeingabenPerStimmenart, collectStimmenartenRelevantSorted(), wildcardCharacter, numberOfGruppen);
  }

  private String calculateHashCodeSearchPatternByNumberOfGruppen(Map<Stimmenart, List<StimmeingabeIndiziert>> stimmeingabenPerStimmenart, List<Stimmenart> stimmenartenSorted, String wildcardCharacter,
      long numberOfGruppen) {
    if (numberOfGruppen < 1) {
      return "DO NOT MATCH ANYTHING";
    }

    StringBuilder result = new StringBuilder();
    result.append(wildcardCharacter);
    result.append(calculateHashCode(stimmeingabenPerStimmenart, stimmenartenSorted, wildcardCharacter, numberOfGruppen));
    result.append(wildcardCharacter);

    return result.toString();
  }

  private String calculateHashCodeByStimmenartAndAnzahlPerWahlvorschlagStimmzettelPosition(Map<Stimmenart, List<StimmeingabeIndiziert>> stimmeingabenPerStimmenart, List<Stimmenart> stimmenartenSorted,
      String stimmenartSeparator, long numberOfGruppen) {
    StringBuilder result = new StringBuilder();

    for (Stimmenart stimmenart : stimmenartenSorted) {
      if (stimmeingabenPerStimmenart.containsKey(stimmenart)) {
        if (result.length() > 0) {
          result.append(stimmenartSeparator);
        }

        result.append(calculateHashCodeByStimmenartAndAnzahlPerWahlvorschlagStimmzettelPosition(stimmenart, stimmeingabenPerStimmenart.get(stimmenart), numberOfGruppen));
      }
    }

    return result.toString();
  }

  private CharSequence calculateHashCodeByStimmenartAndAnzahlPerWahlvorschlagStimmzettelPosition(Stimmenart stimmenart, List<StimmeingabeIndiziert> stimmeingaben, long numberOfGruppen) {
    List<StimmeingabeIndiziert> stimmeingabenSorted = filterWahlvorschlagStimmeingabenSortedByDefault(stimmeingaben);

    if (stimmeingabenSorted.isEmpty()) {
      return "";
    }

    StringBuilder result = new StringBuilder();
    result.append(stimmenart.name()).append(":");

    for (int i = 0; i < Math.min(stimmeingabenSorted.size(), numberOfGruppen); i++) {
      StimmeingabeIndiziert stimmeingabe = stimmeingabenSorted.get(i);

      result.append(stimmeingabe.getWahlvorschlagStimmzettelPosition()).append("=").append(stimmeingabe.getAnzahl());
      result.append(";");
    }

    return result;
  }

  private List<StimmeingabeIndiziert> filterWahlvorschlagStimmeingabenSortedByDefault(List<StimmeingabeIndiziert> stimmeingaben) {
    return sortByHashCodeOrder(stimmeingaben.stream() //
        .filter(stimmeingabe -> !stimmeingabe.isZwischensummeEingabe() && stimmeingabe.isWahlvorschlagStimmeingabe() && stimmeingabe.hasWahlvorschlagStimmzettelPosition()) //
        .collect(toList()));
  }

  @Override
  public List<StimmeingabeIndiziert> sortByHashCodeOrder(List<StimmeingabeIndiziert> stimmeingaben) {
    List<StimmeingabeIndiziert> result = new ArrayList<>(stimmeingaben);
    result.sort(compareByHashCodeOrder());

    return result;
  }

  private Comparator<StimmeingabeIndiziert> compareByHashCodeOrder() {
    return (one, another) -> {
      if (one == null || another == null) {
        return NullComparator.NON_NULL_BEFORE_NULL_COMPARATOR.compare(one, another);
      }

      int comparedByWahlvorschlagStimmzettelPosition = compareByPosition().compare(one.getWahlvorschlagStimmzettelPosition(), another.getWahlvorschlagStimmzettelPosition());

      if (comparedByWahlvorschlagStimmzettelPosition == 0) {
        /*
         * Einzelnen Wahlvorschlag nach vorn...
         */
        if (one.isWahlvorschlagStimmeingabe()) {
          if (!another.isWahlvorschlagStimmeingabe()) {
            return -1;
          }
        }

        if (another.isWahlvorschlagStimmeingabe()) {
          return 1;
        }

        /*
         * Einzelne Listenkandidatur nach vorn oder zwei Listenkandidaturen nach Position vergleichen...
         */
        if (one.isListenkandidaturStimmeingabe()) {
          if (!another.isListenkandidaturStimmeingabe()) {
            return -1;
          }

          return compareByPosition().compare(one.getListenkandidaturStimmzettelPosition(), another.getListenkandidaturStimmzettelPosition());
        }

        if (another.isListenkandidaturStimmeingabe()) {
          return 1;
        }
      }

      return comparedByWahlvorschlagStimmzettelPosition;
    };
  }

  private Comparator<Long> compareByPosition() {
    return (one, another) -> {
      if (one == null || another == null) {
        return NullComparator.NON_NULL_BEFORE_NULL_COMPARATOR.compare(one, another);
      }

      return one.compareTo(another);
    };
  }

  private List<Stimmenart> collectStimmenartenRelevantSorted() {
    return asList( //
        Stimmenart.DIREKTSTIMME, //

        Stimmenart.LISTENSTIMME, //
        Stimmenart.LISTENSTIMME_ABZUZIEHEN, //

        Stimmenart.REFERENDUM_STIMME //
    );
  }
}
