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

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.persistence.Transient;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.control.GruppeByRelativerPositionComparator;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietDAO;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietExtendedDAO;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebiet;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietTK;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;

public abstract class AbstractGruppe implements GruppeBase {

  private static final long serialVersionUID = 1L;

  public AbstractGruppe() {
    super();
  }

  public boolean hasKurzname() {
    return !isBlank(getKurzname());
  }

  public boolean hasName() {
    return !isBlank(getName());
  }

  public boolean hasSchluessel() {
    return !isBlank(getSchluessel());
  }

  public boolean isInReihenfolgeEnthalten(GruppeImGebietDAO gruppeImGebietDAO) {
    return gruppeImGebietDAO.findByCountPositionenFuerGruppe((Gruppe) this) > 0;
  }

  public boolean isInReihenfolgeEnthalten(GruppeImGebietExtendedDAO gruppeImGebietExtendedDAO) {
    return gruppeImGebietExtendedDAO.findByCountPositionenFuerGruppe((Gruppe) this) > 0;
  }

  // Zur Anzeige an der Oberfläche
  @Override
  public Boolean getInPraesentation() {
    // "System-Gruppen" werden gesondert in der Präsentation behandelt (Für politsische Gruppen wird diese Methode überschrieben)
    return null;
  }

  // Zur Anzeige an der Oberfläche
  public Boolean getMinderheit() {
    // "System-Gruppen" sind nie Minderheit(Für politsische Gruppen wird diese Methode überschrieben)
    return null;
  }

  @Transient
  private transient Supplier<Map<Gebiet, GruppeImGebiet>> gruppeImGebietNachGebiet;

  @Nullable
  @Deprecated // HANDLE WITH CARE
  public Supplier<Map<Gebiet, GruppeImGebiet>> getGruppeImGebietNachGebiet() {
    return gruppeImGebietNachGebiet;
  }

  public void setGruppeImGebietNachGebiet(Supplier<Map<Gebiet, GruppeImGebiet>> gruppeImGebietNachGebiet) {
    this.gruppeImGebietNachGebiet = gruppeImGebietNachGebiet;
  }

  @Deprecated // teure Operation
  public Collection<GruppeImGebiet> getGruppenImGebiet() {
    return getGruppeImGebietsByGruppe();
  }

  public void addGruppeImGebiet(GruppeImGebiet gruppeImGebiet) {
    addGruppeImGebietByGruppe(gruppeImGebiet);
  }

  public void removeGruppeImGebiet(GruppeImGebiet gruppeImGebiet) {
    removeGruppeImGebietByGruppe(gruppeImGebiet);
  }

  public void removeAllGruppenImGebiet() {
    removeAllGruppeImGebietByGruppes();
  }

  @Deprecated // teuer, greift auf getGruppeImGebietsByGruppe zu
  public String getNameAufGebiet(Gebiet gebiet) {
    return getPropertyAufGebietResolver().getNameAufGebiet(gebiet);
  }

  @Deprecated // teuer, greift auf getGruppeImGebietsByGruppe zu
  public String getKurznameAufGebiet(Gebiet gebiet) {
    return getPropertyAufGebietResolver().getKurznameAufGebiet(gebiet);
  }

  @Deprecated // teuer, greift auf getGruppeImGebietsByGruppe zu
  public String getZusatzbezeichnungAufGebiet(Gebiet gebiet) {
    return getPropertyAufGebietResolver().getZusatzbezeichnungAufGebiet(gebiet);
  }

  @Deprecated // teuer, greift auf getGruppeImGebietsByGruppe zu
  public GruppeImGebiet getGruppeImGebietInDerDerNameOderKurznameWechselt(Gebiet gebiet) {
    return getPropertyAufGebietResolver().getGruppeImGebietInDerDerNameOderKurznameWechselt(gebiet);
  }

  public boolean isParteiWaehlergruppeOderVereinigung() {
    return isPartei() || isWaehlergruppe() || isVereinigung();
  }

  public boolean isEinzelbewerber() {
    return isPolitischeGruppe() && GruppeArt.EINZELBEWERBER.equals(getGruppeArt());
  }

  public boolean isWaehlergruppe() {
    return isPolitischeGruppe() && GruppeArt.WAEHLERGRUPPE.equals(getGruppeArt());
  }

  public boolean isPartei() {
    return isPolitischeGruppe() && GruppeArt.PARTEI.equals(getGruppeArt());
  }

  public boolean isVereinigung() {
    return isPolitischeGruppe() && GruppeArt.VEREINIGUNG.equals(getGruppeArt());
  }

  public boolean isReferendumVorlage() {
    return isPolitischeGruppe() && GruppeArt.REFERENDUM_VORLAGE.equals(getGruppeArt());
  }

  public boolean isSystemGruppe() {
    // ELECT-9435
    if (getGruppeArt() != null) {
      if (!getGruppeArt().isPolitischeGruppe()) {
        return true;
      }
    }
    return !isPolitischeGruppeInternal();
  }

  public boolean isPolitischeGruppe() {
    return !isSystemGruppe(); // ELECT-9446
  }

  /**
   * Hinweis: Wird von PolitischeGruppe überschrieben
   */
  protected boolean isPolitischeGruppeInternal() {
    return false;
  }

  public boolean isGruppeEqualsSystemGruppe(SystemGruppe systemGruppe, SystemGruppeEigenschaften systemGruppeEigenschaften) {
    return isSystemGruppe() && systemGruppeEigenschaften.isGruppeEqualsSystemGruppe(this, systemGruppe);
  }

  private transient SystemGruppe systemGruppe;

  public SystemGruppe getSystemGruppe(SystemGruppeEigenschaften systemGruppeEigenschaften) {
    if (systemGruppe == null) {
      for (SystemGruppe systemGruppenKandidat : SystemGruppe.values()) {
        if (getSchluessel().equals(systemGruppeEigenschaften.getSchluessel(systemGruppenKandidat))) {
          systemGruppe = systemGruppenKandidat;
          break;
        }
      }

    }
    if (systemGruppe == null) {
      throw new IllegalStateException("Kann keine System-Gruppe aus Schluessel: '" + getSchluessel() + "' ableiten");
    }
    return systemGruppe;
  }

  private transient GruppePropertyAufGebietResolver gruppePropertyAufGebietResolver;

  @Deprecated // teuer, greift auf getGruppeImGebietsByGruppe zu
  private GruppePropertyAufGebietResolver getPropertyAufGebietResolver() {
    if (gruppePropertyAufGebietResolver == null || hasChangedGruppeImGebietsByGruppe()) {
      gruppePropertyAufGebietResolver = new GruppePropertyAufGebietResolver((Gruppe) this, this::getGruppeImGebietNackTk);
    }

    return gruppePropertyAufGebietResolver;
  }

  @Transient
 private transient Map<GruppeImGebietTK, GruppeImGebiet> gruppeImGebietNackTk;

  private Map<GruppeImGebietTK, GruppeImGebiet> getGruppeImGebietNackTk() {
    if (gruppeImGebietNackTk==null || hasChangedGruppeImGebietsByGruppe()){
      gruppeImGebietNackTk = GruppeImGebiet.mappeNachGruppeGebietSchluessel(getGruppeImGebietsByGruppe());
    }
    return gruppeImGebietNackTk;
  }

  public abstract boolean hasChangedGruppeImGebietsByGruppe();

  public boolean hatZugelassenenDirektkandidatInGebietshierarchie(Gebiet gebiet, Map<Gruppe, Set<Wahlvorschlag>> alleWahlvorschlaegeNachGruppe) {
    // SystemGruppen haben keine Direktkandidaten
    return false;
  }

  public boolean hatListeInGebietshierarchie(Gebiet gebiet, Map<Gruppe, Set<Wahlvorschlag>> alleWahlvorschlaegeNachGruppe) {
    // SystemGruppen haben keine Listen
    return false;
  }

  public boolean isLogoVorhanden() {
    return getLogo() != null;
  }

  public static void relativePositionSetzenWennNoetig(Collection<Gruppe> neueEntities, Collection<Gruppe> entitiesInDb) {
    Map<Long, Gruppe> gruppeAnPosition = new HashMap<>();
    Set<Long> vergebenePositionen = new HashSet<>();
    for (Gruppe neueEntity : neueEntities) {
      if (neueEntity.getRelativePosition() != null) {
        vergebenePositionen.add(neueEntity.getRelativePosition());
        gruppeAnPosition.put(neueEntity.getRelativePosition(), neueEntity);
      }
    }
    for (Gruppe gruppe : entitiesInDb) {
      if (gruppe.getRelativePosition() != null) {
        vergebenePositionen.add(gruppe.getRelativePosition());
        gruppeAnPosition.put(gruppe.getRelativePosition(), gruppe);
      }
    }

    long letzteRelativePosition = 1L;
    for (Gruppe neueEntity : neueEntities) {
      if (neueEntity.getRelativePosition() == null || gruppeAnPosition.get(neueEntity.getRelativePosition()) != neueEntity) {
        while (vergebenePositionen.contains(letzteRelativePosition)) {
          letzteRelativePosition++;
        }
        vergebenePositionen.add(letzteRelativePosition);
        neueEntity.setRelativePosition(letzteRelativePosition);
      }
    }
  }

  @Transient
  private transient Long berechnetePosition = null;

  public Long getBerechnetePosition(Collection< ? extends Gruppe> alleGruppen) {

    if (berechnetePosition == null) {
      berechnetePosition = bestimmeBerechnetePosition(alleGruppen);
    }
    return berechnetePosition;
  }

  protected Long bestimmeBerechnetePosition(Collection< ? extends Gruppe> alleGruppen) {
    // wenn Position fixiert, dann diese zurückgeben
    if (getVorgegebenePosition() != null) {
      return getVorgegebenePosition();
    }

    Set<Long> benutztePositionen = alleGruppen.stream().filter(gruppe -> gruppe.getVorgegebenePosition() != null).map(Gruppe::getVorgegebenePosition).collect(Collectors.toSet());

    List<Gruppe> alleGruppenSortiertNachRelativerPosition = new ArrayList<>(alleGruppen);

    alleGruppenSortiertNachRelativerPosition.sort(new GruppeByRelativerPositionComparator());

    long aktuellePosition = 1;
    for (Gruppe gruppe : alleGruppenSortiertNachRelativerPosition) {
      if (gruppe.equals(this)) {
        while (benutztePositionen.contains(aktuellePosition)) {
          aktuellePosition++;
        }
        return aktuellePosition;
      }

      if (gruppe.getVorgegebenePosition() == null) {
        while (benutztePositionen.contains(aktuellePosition)) {
          aktuellePosition++;
        }
        benutztePositionen.add(aktuellePosition);
        aktuellePosition++;
      }
    }
    return aktuellePosition;
  }

  public static Gruppe findeSystemGruppe(Collection<Gruppe> gruppen, SystemGruppe systemGruppe, SystemGruppeEigenschaften systemGruppeEigenschaften) {
    for (Gruppe gruppe : gruppen) {
      if (gruppe.isGruppeEqualsSystemGruppe(systemGruppe, systemGruppeEigenschaften)) {
        return gruppe;
      }
    }
    throw new IllegalStateException("Gruppe " + systemGruppe + " nicht gefunden, habe: " + gruppen);
  }
}
