
package de.ivu.elect.business.personenpruefung.control.wahlrecht;

import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;

import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import javax.inject.Inject;

import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.personenpruefung.boundary.PersonPruefungKonfigurationCache;
import de.ivu.elect.business.personenpruefung.control.AbstractPersonendatenMatcher;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.AbstractWahlrechtGebietsartInReferenzGebietKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.HoechstalterAmStichtagKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.MindestalterAmStichtagKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.PersonenregisterAbgleichbarkeitKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.PflichtangabeKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.StaatsangehoerigkeitKriterium;
import de.ivu.elect.business.personenpruefung.control.wahlrecht.kriterium.WahlrechtsausschlussKriterium;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfiguration;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfigurationPersonentyp;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfigurationTyp;
import de.ivu.elect.business.wahl.entity.Wahltermin;

public abstract class AbstractPersonendatenWahlrechtMatcher extends AbstractPersonendatenMatcher {

  private static final long serialVersionUID = 1L;

  private static final Collection<Class< ? extends Personendaten>> DEFAULT_PERSONENDATEN_CLASSES = asList(Personendaten.class);

  @Inject
  transient PersonPruefungKonfigurationCache personPruefungKonfigurationCache;

  protected AbstractPersonendatenWahlrechtMatcher() {
    super();
  }

  @Override
  protected boolean matchesInternal(Personendaten personendaten) {
    return matchesPflichtangaben(personendaten) && matchesMindestalterAnStichtagen(personendaten) && matchesHoechstalterAnStichtagen(personendaten) && matchesWahlrechtsauschluesse(personendaten)
        && matchesStaatsangehoerigkeiten(personendaten) && matchesPersonenregisterAbgleichbarkeit(personendaten) && matchesMindestaufenthaltAnAnschriftArtenInGebietenAnStichtagen(personendaten)
        && matchesWahlrechtGebietsartInReferenzGebieten(personendaten);
  }

  protected Collection<Class< ? extends Personendaten>> getDefaultPersonendatenClasses() {
    return DEFAULT_PERSONENDATEN_CLASSES;
  }

  public boolean matchesPflichtangaben(Personendaten personendaten) {
    return allMatchIfNotEmpty(collectPflichtangabeKriterien(personendaten), personendaten);
  }

  public List<PflichtangabeKriterium> collectPflichtangabeKriterien(Personendaten personendaten) {
    return emptyList(); // ELECT-15697
  }

  public boolean matchesMindestalterAnStichtagen(Personendaten personendaten) {
    return allMatchIfNotEmpty(collectMindestalterAmStichtagKriterien(personendaten), personendaten);
  }

  public abstract List<MindestalterAmStichtagKriterium> collectMindestalterAmStichtagKriterien(Personendaten personendaten);

  protected MindestalterAmStichtagKriterium createMindestalterAmWahltagKriterium(Personendaten personendaten, int mindestalterInJahren) {
    return MindestalterAmStichtagKriterium.createMindestalterAmWahltagKriterium(getDefaultPersonendatenClasses(), getWahltag(personendaten), mindestalterInJahren);
  }

  protected MindestalterAmStichtagKriterium createMindestalterAmTagDerUnterschriftKriterium(Personendaten personendaten, Date stichtag, int mindestalterInJahren) {
    return MindestalterAmStichtagKriterium.createMindestalterAmTagDerUnterschriftKriterium(getDefaultPersonendatenClasses(), stichtag, mindestalterInJahren);
  }

  protected MindestalterAmStichtagKriterium createMindestalterAmTagDerPruefungKriterium(Personendaten personendaten, Date stichtag, int mindestalterInJahren) {
    return MindestalterAmStichtagKriterium.createMindestalterAmTagDerPruefungKriterium(getDefaultPersonendatenClasses(), stichtag, mindestalterInJahren);
  }

  protected MindestalterAmStichtagKriterium createMindestalterAmXtenTagVormWahltagKriterium(Personendaten personendaten, int mindestalterInJahren, int anzahlTageVormWahltag) {
    return MindestalterAmStichtagKriterium.createMindestalterAmXtenTagVormWahltagKriterium(getDefaultPersonendatenClasses(),
        calculateStichtagVormWahltagByAnzahlTage(personendaten, anzahlTageVormWahltag), anzahlTageVormWahltag, mindestalterInJahren);
  }

  public boolean matchesHoechstalterAnStichtagen(Personendaten personendaten) {
    return allMatchIfNotEmpty(collectHoechstalterAmStichtagKriterien(personendaten), personendaten);
  }

  protected HoechstalterAmStichtagKriterium createHoechstalterAmWahltagKriterium(Personendaten personendaten, int hoechstalterInJahren) {
    return HoechstalterAmStichtagKriterium.createHoechstalterAmWahltagKriterium(getDefaultPersonendatenClasses(), getWahltag(personendaten), hoechstalterInJahren);
  }

  protected HoechstalterAmStichtagKriterium createHoechstalterAmTagDesAmtsantrittsKriterium(Personendaten personendaten, Date stichtag, int hoechstalterInJahren) {
    return HoechstalterAmStichtagKriterium.createHoechstalterAmTagDesAmtsantrittsKriterium(getDefaultPersonendatenClasses(), stichtag, hoechstalterInJahren);
  }

  public abstract List<HoechstalterAmStichtagKriterium> collectHoechstalterAmStichtagKriterien(Personendaten personendaten);

  public boolean matchesWahlrechtsauschluesse(Personendaten personendaten) {
    return anyMatchIfNotEmpty(collectWahlrechtsausschlussKriterien(personendaten), personendaten);
  }

  public abstract List<WahlrechtsausschlussKriterium> collectWahlrechtsausschlussKriterien(Personendaten personendaten);

  public boolean matchesStaatsangehoerigkeiten(Personendaten personendaten) {
    return allMatchIfNotEmpty(collectStaatsangehoerigkeitKriterien(personendaten), personendaten);
  }

  public List<StaatsangehoerigkeitKriterium> collectStaatsangehoerigkeitKriterien(Personendaten personendaten) {
    Wahltermin wahltermin = personendaten.getWahltermin();

    requireNonNull(wahltermin, "Wahltermin darf nicht null sein");

    return asList(new StaatsangehoerigkeitKriterium(getDefaultPersonendatenClasses(), wahltermin.getBuergerZugelassen()));
  }

  public boolean matchesPersonenregisterAbgleichbarkeit(Personendaten personendaten) {
    return collectPersonenregisterAbgleichbarkeitKriterium(personendaten).matches(personendaten);
  }

  public abstract PersonenregisterAbgleichbarkeitKriterium collectPersonenregisterAbgleichbarkeitKriterium(Personendaten personendaten);

  public boolean matchesMindestaufenthaltAnAnschriftArtenInGebietenAnStichtagen(Personendaten personendaten) {
    return allMatchIfNotEmpty(collectMindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien(personendaten), personendaten);
  }

  public abstract List<MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium> collectMindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien(Personendaten personendaten);

  protected MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createAnschriftArtVormWahltagKriterium(Personendaten personendaten, WahlrechtAnschriftsart wahlrechtAnschriftsart) {
    return createMindestaufenthaltInMonatenAnAnschriftArtInGebietVormWahltagKriterium(personendaten, wahlrechtAnschriftsart, WahlrechtGebietsart.ALLE, 0);
  }

  protected MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmTagDerUnterschriftKriterium(Personendaten personendaten, Date stichtag,
      WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthaltInMonaten) {
    return MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium.createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmTagDerUnterschriftKriterium(getDefaultPersonendatenClasses(), stichtag,
        wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthaltInMonaten);
  }

  protected MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmTagDerPruefungKriterium(Personendaten personendaten, Date stichtag,
      WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthaltInMonaten) {
    return MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium.createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmTagDerPruefungKriterium(getDefaultPersonendatenClasses(), stichtag,
        wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthaltInMonaten);
  }

  protected MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietVormWahltagKriterium(Personendaten personendaten,
      WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthaltInMonaten) {
    return MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium.createMindestaufenthaltInMonatenAnAnschriftArtInGebietVormWahltagKriterium(getDefaultPersonendatenClasses(),
        getWahltag(personendaten), wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthaltInMonaten);
  }

  protected MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmXtenTagVormWahltagKriterium(Personendaten personendaten,
      WahlrechtAnschriftsart wahlrechtAnschriftsart, WahlrechtGebietsart wahlrechtGebietsart, int mindestaufenthaltInMonaten, int anzahlTageVormWahltag) {
    return MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium.createMindestaufenthaltInMonatenAnAnschriftArtInGebietAmXtenTagVormWahltagKriterium(getDefaultPersonendatenClasses(),
        calculateStichtagVormWahltagByAnzahlTage(personendaten, anzahlTageVormWahltag), anzahlTageVormWahltag, wahlrechtAnschriftsart, wahlrechtGebietsart, mindestaufenthaltInMonaten);
  }

  public boolean matchesWahlrechtGebietsartInReferenzGebieten(Personendaten personendaten) {
    return allMatchIfNotEmpty(collectWahlrechtGebietsartInReferenzGebietKriterien(personendaten), personendaten);
  }

  public abstract List< ? extends AbstractWahlrechtGebietsartInReferenzGebietKriterium> collectWahlrechtGebietsartInReferenzGebietKriterien(Personendaten personendaten);

  public boolean isTagDerUnterschriftRelevant(Personendaten personendaten) {
    return collectMindestalterAmStichtagKriterien(personendaten).stream().anyMatch(MindestalterAmStichtagKriterium::isStichtagTagDerUnterschrift)
        || collectHoechstalterAmStichtagKriterien(personendaten).stream().anyMatch(HoechstalterAmStichtagKriterium::isStichtagTagDerUnterschrift)
        || collectMindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien(personendaten).stream().anyMatch(MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium::isStichtagTagDerUnterschrift);
  }

  public boolean isTagDerPruefungRelevant(Personendaten personendaten) {
    return collectMindestalterAmStichtagKriterien(personendaten).stream().anyMatch(MindestalterAmStichtagKriterium::isStichtagTagDerPruefung)
        || collectHoechstalterAmStichtagKriterien(personendaten).stream().anyMatch(HoechstalterAmStichtagKriterium::isStichtagTagDerPruefung)
        || collectMindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterien(personendaten).stream().anyMatch(MindestaufenthaltAnAnschriftArtInGebietAmStichtagKriterium::isStichtagTagDerPruefung);
  }

  protected Optional<PersonPruefungKonfiguration> findPersonPruefungKonfiguration(PersonPruefungKonfigurationTyp typ, PersonPruefungKonfigurationPersonentyp personentyp, Personendaten personendaten) {
    return personPruefungKonfigurationCache.findPersonPruefungKonfiguration(typ, personentyp, personendaten);
  }
}
