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

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;

import javax.annotation.Nonnull;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;


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

public enum Stimmabgabeart {
  GESAMT {

    @Override
    public boolean isBetrachteErfassungsgebiet(Gebiet wahldurchfuehrungsgebiet, Supplier<Set<Gebiet>> ausgezaehlteGebiete) {
      return true;
    }

    @Override
    public Stimmabgabeart getStimmabgabeartFuerAlleGebiet() {
      return GESAMT;
    }
  },
  URNE {

    @Override
    public boolean isBetrachteErfassungsgebiet(Gebiet wahldurchfuehrungsgebiet, Supplier<Set<Gebiet>> ausgezaehlteGebiete) {
      return !wahldurchfuehrungsgebiet.isBriefwahlgebiet();
    }

    @Override
    public Stimmabgabeart getStimmabgabeartFuerAlleGebiet() {
      return URNE;
    }
  },
  BRIEF {

    @Override
    public boolean isBetrachteErfassungsgebiet(Gebiet wahldurchfuehrungsgebiet, Supplier<Set<Gebiet>> ausgezaehlteGebiete) {
      return wahldurchfuehrungsgebiet.isBriefwahlgebiet();
    }

    @Override
    public Stimmabgabeart getStimmabgabeartFuerAlleGebiet() {
      return BRIEF;
    }
  },

  AUSGEZAEHLT {
    @Override
    public boolean isBetrachteErfassungsgebiet(Gebiet wahldurchfuehrungsgebiet, Supplier<Set<Gebiet>> ausgezaehlteGebiete) {
      return ausgezaehlteGebiete.get().contains(wahldurchfuehrungsgebiet);
    }

    @Override
    public Stimmabgabeart getStimmabgabeartFuerAlleGebiet() {
      return GESAMT;
    }
  };

  /**
   * @param ausgezaehlteGebiete
   *          wird nur für AUSGEZAEHLT gebraucht, weil ein Gebiet von alleine nicht weiß, ob es ausgezählt ist
   * @return ob das Gebiet bei der betreffenden Stimmabgabeart betrachtet werden muss
   */
  public abstract boolean isBetrachteErfassungsgebiet(Gebiet wahldurchfuehrungsgebiet, Supplier<Set<Gebiet>> ausgezaehlteGebiete);

  public boolean isGesamt() {
    return this.equals(GESAMT);
  }

  public boolean isUrne() {
    return this.equals(URNE);
  }

  public boolean isBrief() {
    return this.equals(BRIEF);
  }

  /**
   * @param ausgezaehlteGebieteSupplier
   *          liefert die Menge der ausgezählten Gebiete. Wird nur für Stimmabgabeart.AUSGEZAEHLT gebraucht, weil ein
   *          Gebiet von alleine nicht weiß, ob es ausgezählt ist
   * @return ein Filter für Gebiete nach Stimmabgabeart
   */
  @Nonnull
  public Predicate<Gebiet> filterGebiete(Supplier<Set<Gebiet>> ausgezaehlteGebieteSupplier) {
    return gebiet -> isBetrachteErfassungsgebiet(gebiet, ausgezaehlteGebieteSupplier);
  }

  /**
   * @param ausgezaehlteGebieteSupplier
   *          liefert die Menge der ausgezählten Gebiete. Wird nur für Stimmabgabeart.AUSGEZAEHLT gebraucht, weil ein
   *          Gebiet von alleine nicht weiß, ob es ausgezählt ist
   * @return ein Filter für Stimmen nach Stimmabgabeart
   */
  @Nonnull
  public Predicate<Stimmen> filterStimmen(Supplier<Set<Gebiet>> ausgezaehlteGebieteSupplier) {
    Predicate<Gebiet> gebieteFilter = filterGebiete(ausgezaehlteGebieteSupplier);
    return s -> gebieteFilter.test(s.getGebiet());
  }

  public <G extends Gebiet> Collection<G> filterGebieteWennErfassungsgebiet(Collection<G> gebiete,
      Supplier<Set<Gebiet>> ausgezaehlteGebiete) {
    return gebiete.stream().filter(filterGebiete(ausgezaehlteGebiete).or(g -> !g.isErfassungsgebiet()))
        .collect(toList());
  }

  public static List<Gebiet> getUntergeordneteErfassungsgebieteNachStimmabgabeart(Gebiet gebiet,
      Stimmabgabeart stimmabgabeart, Supplier<Set<Gebiet>> ausgezaehlteGebiete) {
    List<Gebiet> result = new ArrayList<>();

    result.add(gebiet);
    result.addAll(gebiet.getInGebietshierarchieUntergeordneteGebiete());

    return getErfassungsgebieteMitStimmabgabeart(result, stimmabgabeart, ausgezaehlteGebiete);
  }

  public static List<Gebiet> getErfassungsgebieteMitStimmabgabeart(Collection<Gebiet> gebiete,
      Stimmabgabeart stimmabgabeart, Supplier<Set<Gebiet>> ausgezaehlteGebiete) {
    return gebiete.stream().filter(stimmabgabeart.filterGebiete(ausgezaehlteGebiete)).collect(toList());
  }

  public abstract Stimmabgabeart getStimmabgabeartFuerAlleGebiet() ;
}
