
package de.ivu.elect.business.wahlvorschlag.entity;

import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;

import javax.annotation.Nullable;

import de.ivu.elect.business.gebietsbaum.control.GebietByBreadthFirstComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppe;
import de.ivu.elect.business.kandidatur.boundary.AbstractListenkandidaturDAO;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;

public class StelInformationenFormatter {

  private final EntityAufStimmzettelDaten entityAufStimmzettelDaten;

  @Nullable
  private final AbstractListenkandidaturDAO listenkandidaturDAO;

  @Deprecated // Listenkandidaturdao mitgeben (Geschwindigkeit)
  public StelInformationenFormatter(EntityAufStimmzettelDaten entityAufStimmzettelDaten) {
    this(entityAufStimmzettelDaten, null);
  }

  public StelInformationenFormatter(EntityAufStimmzettelDaten entityAufStimmzettelDaten, @Nullable  AbstractListenkandidaturDAO listenkandidaturDAO) {
    super();

    this.entityAufStimmzettelDaten = entityAufStimmzettelDaten;
    this.listenkandidaturDAO = listenkandidaturDAO;
  }

  public String formatListeStelListen(Liste liste, Collection<Liste> listenByGruppe, BiFunction<StelInformationen, List<Liste>, String> formatterEinzelliste,
      BiFunction<StelInformationen, List<Liste>, String> formatterIdentischeListe, BiFunction<StelInformationen, List<Liste>, String> formatterListengruppe,
                                      BiFunction<StelInformationen, List<Liste>, String>
                                          formatterListengruppeUnecht) {
    Map<WahlvorschlagPK, Liste> listenByGruppePerPk = collectListenPerPk(listenByGruppe);

    StelInformationen stelInformationen = NlListe.getStelInformationen(liste, listenByGruppePerPk.values(), entityAufStimmzettelDaten, listenkandidaturDAO);

    List<Liste> listen = collectStelListen(stelInformationen, listenByGruppePerPk::get);

    if (stelInformationen.isEinzelliste()) {
      return formatterEinzelliste.apply(stelInformationen, listen);
    }

    if (stelInformationen.isIdentischeListe()) {
      return formatterIdentischeListe.apply(stelInformationen, listen);
    }

    if (stelInformationen.isEchterStel()) {
      return formatterListengruppe.apply(stelInformationen, listen);
    }
    return formatterListengruppeUnecht.apply(stelInformationen, listen);
  }

  // nicht benutzt
//  public String formatListeStelWahlgebiete(Liste liste, Collection<Liste> listenByGruppe, BiFunction<StelInformationen, List<Gebiet>, String> formatterEinzelliste,
//      BiFunction<StelInformationen, List<Gebiet>, String> formatterIdentischeListe, BiFunction<StelInformationen, List<Gebiet>, String> formatterListengruppe) {
//    Map<WahlvorschlagPK, Liste> listenByGruppePerPk = collectListenPerPk(listenByGruppe);
//
//    StelInformationen stelInformationen = NlListe.getStelInformationen(liste, listenByGruppePerPk.values(), entityAufStimmzettelDaten);
//
//    List<Gebiet> wahlgebiete = collectStelWahlgebiete(stelInformationen, listenByGruppePerPk::get);
//
//    if (stelInformationen.isEinzelliste()) {
//      return formatterEinzelliste.apply(stelInformationen, wahlgebiete);
//    }
//
//    if (stelInformationen.isIdentischeListe()) {
//      return formatterIdentischeListe.apply(stelInformationen, wahlgebiete);
//    }
//
//    return formatterListengruppe.apply(stelInformationen, wahlgebiete);
//  }

  public String formatGruppeStelListen(Collection<Liste> listenByGruppe, BiFunction<StelInformationen, List<Liste>, String> formatterEinzelliste,
      BiFunction<StelInformationen, List<Liste>, String> formatterIdentischeListe, BiFunction<Collection<StelInformationen>, List<Liste>, String> formatterListengruppe) {
    Map<WahlvorschlagPK, Liste> listenByGruppePerPk = collectListenPerPk(listenByGruppe);

    Set<StelInformationen> einzelneStels = NlPolitischeGruppe.bestimmeStelInformationen(listenByGruppePerPk.values(), entityAufStimmzettelDaten);

    for (StelInformationen stelInformationen : einzelneStels) {
      if (stelInformationen.isEinzelliste()) {
        return formatterEinzelliste.apply(stelInformationen, collectStelListen(stelInformationen, listenByGruppePerPk::get));
      }

      if (stelInformationen.isIdentischeListe()) {
        return formatterIdentischeListe.apply(stelInformationen, collectStelListen(stelInformationen, listenByGruppePerPk::get));
      }
    }

    return formatterListengruppe.apply(einzelneStels, collectStelListen(einzelneStels, listenByGruppePerPk::get));
  }

  public List<String> formatGruppeStelWahlgebietePerStel(Collection<Liste> listenByGruppe, BiFunction<StelInformationen, List<Gebiet>, String> formatterEinzelliste,
      BiFunction<StelInformationen, List<Gebiet>, String> formatterIdentischeListe, BiFunction<StelInformationen, List<Gebiet>, String> formatterListengruppe) {
    Map<WahlvorschlagPK, Liste> listenByGruppePerPk = collectListenPerPk(listenByGruppe);

    Set<StelInformationen> einzelneStels = NlPolitischeGruppe.bestimmeStelInformationen(listenByGruppePerPk.values(), entityAufStimmzettelDaten);

    for (StelInformationen stelInformationen : einzelneStels) {
      if (stelInformationen.isEinzelliste()) {
        return singletonList(formatterEinzelliste.apply(stelInformationen, collectStelWahlgebiete(stelInformationen, listenByGruppePerPk::get)));
      }

      if (stelInformationen.isIdentischeListe()) {
        return singletonList(formatterIdentischeListe.apply(stelInformationen, collectStelWahlgebiete(stelInformationen, listenByGruppePerPk::get)));
      }
    }

    return einzelneStels.stream() //
        .filter(stelInformationen -> stelInformationen.getStelSortierung() != null) //
        .sorted(Comparator.comparing(StelInformationen::getStelSortierung)) //
        .map(stelInformationen -> formatterListengruppe.apply(stelInformationen, collectStelWahlgebiete(stelInformationen, listenByGruppePerPk::get))) //
        .collect(toList());
  }

  private Map<WahlvorschlagPK, Liste> collectListenPerPk(Collection<Liste> listenByGruppe) {
    return listenByGruppe.stream() //
        .filter(AbstractWahlvorschlag::isZugelassenOderUnbestimmt) //
        .collect(toMap(Liste::getPk, Function.identity()));
  }

  private List<Gebiet> collectStelWahlgebiete(StelInformationen stelInformationen, Function<WahlvorschlagPK, Liste> attachFct) {
    return collectStelListen(stelInformationen, attachFct).stream() //
        .map(AbstractWahlvorschlag::getGebiet) //
        .sorted(GebietByBreadthFirstComparator.INSTANCE) //
        .collect(toList());
  }

  private List<Liste> collectStelListen(Collection<StelInformationen> einzelneStels, Function<WahlvorschlagPK, Liste> attachFct) {
    return einzelneStels.stream() //
        .flatMap(stelInformationen -> collectStelListen(stelInformationen, attachFct).stream()) //
        .distinct() //
        .collect(toList());
  }

  private List<Liste> collectStelListen(StelInformationen stelInformationen, Function<WahlvorschlagPK, Liste> attachFct) {
    return stelInformationen.getListenDesStels().stream() //
        .map(attachFct) //
        .filter(Objects::nonNull) //
        .collect(toList());
  }
}
