
package de.ivu.elect.dokumente.ergebnis.zusammenstellung;

import static org.apache.commons.lang3.StringUtils.leftPad;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

import de.ivu.elect.business.ergebnis.entity.Stimmabgabeart;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformationByNummerComparator;
import de.ivu.elect.util.CaseAndDiacriticIgnoringComparator;
import de.ivu.elect.was.exchange.ergebnis.zusammenstellung.ErgebnisZusammenstellungTabelle;

public class DefaultErgebnisTabellarischTabelleSortierung implements ErgebnisTabellarischTabelleSortierung {

  private static final String ZWISCHENSUMMENZEILE_NAME_URNENWAHL_GESAMT = "Urnenwahl gesamt";

  private static final String ZWISCHENSUMMENZEILE_NAME_BRIEFWAHL_GESAMT = "Briefwahl gesamt";

  public DefaultErgebnisTabellarischTabelleSortierung() {
    super();
  }

  @Override
  public BiFunction<WahlverwaltungGebietInformation, Stimmabgabeart, String> getLabelFctGruppierungStimmabgabearten() {
    return this::getLabelGruppierungStimmabgabearten;
  }

  protected String getLabelGruppierungStimmabgabearten(WahlverwaltungGebietInformation gebietInformation, Stimmabgabeart stimmabgabeart) {
    String name = gebietInformation.getName();

    if (stimmabgabeart.isUrne()) {
      return name + " Urnenwahl";
    }

    if (stimmabgabeart.isBrief()) {
      return name + " Briefwahl";
    }

    return name + " gesamt";
  }

  @Override
  public BiFunction<WahlverwaltungGebietInformation, Stimmabgabeart, String> getLabelFctZwischensummenGruppierungGesamt() {
    return this::getLabelZwischensummenGruppierungGesamt;
  }

  protected String getLabelZwischensummenGruppierungGesamt(WahlverwaltungGebietInformation gebietInformation, Stimmabgabeart stimmabgabeart) {
    return gebietInformation.getName() + " gesamt";
  }

  @Override
  public BiFunction<WahlverwaltungGebietInformation, Stimmabgabeart, String> getLabelFctZwischensummenGruppierungStimmabgabearten() {
    return this::getLabelFctZwischensummenGruppierungStimmabgabearten;
  }

  protected String getLabelFctZwischensummenGruppierungStimmabgabearten(WahlverwaltungGebietInformation gebietInformation, Stimmabgabeart stimmabgabeart) {
    if (stimmabgabeart.isUrne()) {
      return ZWISCHENSUMMENZEILE_NAME_URNENWAHL_GESAMT;
    }

    if (stimmabgabeart.isBrief()) {
      return ZWISCHENSUMMENZEILE_NAME_BRIEFWAHL_GESAMT;
    }

    if (stimmabgabeart.isGesamt()) {
      return "Gesamt";
    }

    return stimmabgabeart.name();
  }

  @Override
  public Comparator<ErgebnisTabellarischZeile> comparator(ErgebnisZusammenstellungTabelle tabelle) {
    return (one, another) -> comparatorByGebiet(tabelle).thenComparing(ErgebnisTabellarischZeileByStimmabgabeartComparator.ASCENDING).compare(one, another);
  }

  private Comparator<ErgebnisTabellarischZeile> comparatorByGebiet(ErgebnisZusammenstellungTabelle tabelle) {
    List<ErgebnisTabellarischTabelleSortierungGruppierung> gruppierungshierarchieDesc = collectGruppierungshierarchieDesc(tabelle);

    Comparator<ErgebnisTabellarischZeile> result = new ErgebnisTabellarischZeileByGebietComparator(gruppierungshierarchieDesc.get(0).getAccessor());

    for (int i = 1; i < gruppierungshierarchieDesc.size(); i++) {
      result = result.thenComparing(new ErgebnisTabellarischZeileByGebietComparator(gruppierungshierarchieDesc.get(i).getAccessor()));
    }

    return result;
  }

  @Override
  public List<ErgebnisTabellarischTabelleSortierungGruppierung> collectGruppierungshierarchieDesc(ErgebnisZusammenstellungTabelle tabelle) {
    List<ErgebnisTabellarischTabelleSortierungGruppierung> result = new ArrayList<>();

    for (ErgebnisTabellarischTabelleSortierungGruppierungArt gruppierungArt : collectGruppierungArtenDesc(tabelle)) {
      switch (gruppierungArt) {
        case BUNDESLAND :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getBundesland, ErgebnisTabellarischZwischensummen.BUNDESLAND_STIMMABGABEART,
              ErgebnisTabellarischZwischensummen.BUNDESLAND_GESAMT));
          break;
        case LANDKREIS :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getKreis, ErgebnisTabellarischZwischensummen.KREIS_STIMMABGABEART,
              ErgebnisTabellarischZwischensummen.KREIS_GESAMT));
          break;
        case GEMEINDEVERBAND :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getGemeindeverband,
              ErgebnisTabellarischZwischensummen.GEMEINDEVERBAND_STIMMABGABEART, ErgebnisTabellarischZwischensummen.GEMEINDEVERBAND_GESAMT));
          break;
        case GEMEINDE :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getGemeinde, ErgebnisTabellarischZwischensummen.GEMEINDE_STIMMABGABEART,
              ErgebnisTabellarischZwischensummen.GEMEINDE_GESAMT));
          break;
        case STADTBEZIRK :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getStadtbezirk, ErgebnisTabellarischZwischensummen.STADTBEZIRK_STIMMABGABEART,
              ErgebnisTabellarischZwischensummen.STADTBEZIRK_GESAMT));
          break;
        case ORTSBEZIRK :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getOrtsbezirk, ErgebnisTabellarischZwischensummen.ORTSBEZIRK_STIMMABGABEART,
              ErgebnisTabellarischZwischensummen.ORTSBEZIRK_GESAMT));
          break;
        case STADTTEIL :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getStadtteil, ErgebnisTabellarischZwischensummen.STADTTEIL_STIMMABGABEART,
              ErgebnisTabellarischZwischensummen.STADTTEIL_GESAMT));
          break;
        case ORTSTEIL :
          result.add(new ErgebnisTabellarischTabelleSortierungGruppierung(gruppierungArt, ErgebnisTabellarischZeile::getOrtsteil, ErgebnisTabellarischZwischensummen.ORTSTEIL_STIMMABGABEART,
              ErgebnisTabellarischZwischensummen.ORTSTEIL_GESAMT));
          break;
        default :
          break;
      }
    }

    return result;
  }

  protected List<ErgebnisTabellarischTabelleSortierungGruppierungArt> collectGruppierungArtenDesc(ErgebnisZusammenstellungTabelle tabelle) {
    List<ErgebnisTabellarischTabelleSortierungGruppierungArt> result = new ArrayList<>();

    if (tabelle.getKreise().size() > 1) { // ELECT-16622
      result.add(ErgebnisTabellarischTabelleSortierungGruppierungArt.LANDKREIS);
    }

    result.add(ErgebnisTabellarischTabelleSortierungGruppierungArt.GEMEINDEVERBAND); // Gemeindeverband hat Vorrang vor Gemeinde...
    result.add(ErgebnisTabellarischTabelleSortierungGruppierungArt.GEMEINDE);

    return result;
  }

  static class ErgebnisTabellarischZeileByStimmabgabeartComparator implements Comparator<ErgebnisTabellarischZeile> {

    private static final List<Stimmabgabeart> STIMMABGABEARTEN_ASCENDING = List.of(Stimmabgabeart.URNE, Stimmabgabeart.BRIEF, Stimmabgabeart.GESAMT);

    public static final Comparator<ErgebnisTabellarischZeile> ASCENDING = new ErgebnisTabellarischZeileByStimmabgabeartComparator();

    public static final Comparator<ErgebnisTabellarischZeile> DESCENDING = Collections.reverseOrder(ASCENDING);

    ErgebnisTabellarischZeileByStimmabgabeartComparator() {
      super();
    }

    @Override
    public int compare(ErgebnisTabellarischZeile one, ErgebnisTabellarischZeile another) {
      return compareStimmabgabeart(one.getStimmabgabeart(), another.getStimmabgabeart());
    }

    private int compareStimmabgabeart(Stimmabgabeart one, Stimmabgabeart another) {
      int indexOfOne = STIMMABGABEARTEN_ASCENDING.indexOf(one);
      int indexOfAnother = STIMMABGABEARTEN_ASCENDING.indexOf(another);

      if (indexOfOne < 0) {
        if (indexOfAnother < 0) {
          return 0;
        }

        return +1; // Unbekannter Wert vs. bekannter Wert -> unbekannter Wert nach hinten...
      }

      if (indexOfAnother < 0) {
        return -1; // Bekannter Wert vs. unbekannter Wert -> unbekannter Wert nach hinten...
      }

      return Integer.compare(indexOfOne, indexOfAnother);
    }
  }

  static class ErgebnisTabellarischZeileByGebietComparator implements Comparator<ErgebnisTabellarischZeile> {

    private static final Comparator<WahlverwaltungGebietInformation> GEBIET_COMPARATOR = WahlverwaltungGebietInformationByNummerComparator.ASCENDING;

    private static final Comparator<String> GEBIET_NUMMER_COMPARATOR = CaseAndDiacriticIgnoringComparator.DEFAULT_INSTANCE;

    private final Function<ErgebnisTabellarischZeile, WahlverwaltungGebietInformation> gebietFct;

    ErgebnisTabellarischZeileByGebietComparator(Function<ErgebnisTabellarischZeile, WahlverwaltungGebietInformation> gebietFct) {
      super();

      this.gebietFct = gebietFct;
    }

    @Override
    public int compare(ErgebnisTabellarischZeile one, ErgebnisTabellarischZeile another) {
      return compareGebiet(gebietFct.apply(one), gebietFct.apply(another));
    }

    private int compareGebiet(WahlverwaltungGebietInformation one, WahlverwaltungGebietInformation another) {
      int comparedByGebiet = GEBIET_COMPARATOR.compare(one, another);

      if (comparedByGebiet == 0) {
        return compareNummer(one.getNummer(), another.getNummer());
      }

      return comparedByGebiet;
    }

    private int compareNummer(String one, String another) {
      return GEBIET_NUMMER_COMPARATOR.compare(leftPad(one, 25, "0"), leftPad(another, 25, "0"));
    }
  }
}
