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

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import javax.faces.validator.Validator;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerParameter;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.konfiguration.entity.CustomParameterFilter;
import de.ivu.elect.business.konfiguration.entity.CustomParameterKategorie;
import de.ivu.elect.business.konfiguration.entity.CustomParameterTemplate;
import de.ivu.elect.business.konfiguration.entity.CustomParameterWertTyp;
import de.ivu.elect.business.konfiguration.entity.KonfigurationNotify;
import de.ivu.elect.business.security.Customer;
import de.ivu.elect.business.stimmzettel.control.AbstractStimmzettelKopfdatumValidator;
import de.ivu.elect.business.stimmzettel.control.NichtNegativZahlStimmzettelKopfdatumValidator;
import de.ivu.elect.business.stimmzettel.control.StimmzettelInsgesamtAnzahlKopdatumValidator;
import de.ivu.elect.business.stimmzettel.control.StimmzettelInsgesamtKleinerMitWahlscheinKopfdatumValidator;
import de.ivu.elect.business.stimmzettel.control.WahlberechtigteGesamtKopfdatumValidator;
import de.ivu.elect.business.stimmzettel.control.WertErforderlichStimmzettelKopfdatumValidator;


import static de.ivu.elect.business.konfiguration.entity.CustomParameterKategorie.STIMMZETTEL_ERFASSUNG;
import static de.ivu.elect.business.konfiguration.entity.CustomParameterKategorie.STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL;
import static de.ivu.elect.business.konfiguration.entity.CustomParameterKategorie.STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL_UNGUELTIG;
import static de.ivu.elect.business.konfiguration.entity.CustomParameterKategorie.STIMMZETTEL_ERFASSUNG_KOPFDATEN_WAHLBERECHTIGTE;
import static de.ivu.elect.business.konfiguration.entity.CustomParameterWertTyp.ENUM;
import static de.ivu.elect.business.konfiguration.entity.CustomParameterWertTyp.LONG;
import static de.ivu.elect.business.konfiguration.entity.CustomParameterWertTyp.VOID;
import static de.ivu.elect.business.security.Customer.WAS;
import static de.ivu.elect.business.security.Customer.WES;
import static de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumAbgabeart.NICHT_ERFASSEN;
import static de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumAbgabeart.NUR_URNE;
import static de.ivu.elect.business.stimmzettel.entity.StimmzettelKopfdatumAbgabeart.URNE_UND_BRIEF;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

public enum StimmzettelKopfdatumArt implements CustomParameterTemplate {

  ERFASSUNGSTEAM(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, ENUM, StimmzettelTeam.class, WES, WAS) {
    @Override
    public boolean isKopfdatum() {
      return false;
    }

    @Override
    public boolean isAlsHauptTeamInWasImportieren() {
      return false;
    }
  },
  ERFASSUNG_ABGESCHLOSSEN(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, LONG, WES, WAS) {
    @Override
    public boolean isKopfdatum() {
      return false;
    }

    @Override
    public boolean isErfassungAbgschlossen() {
      return true;
    }
  },
  VERSIONSNUMMER(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, LONG, WES, WAS) {
    @Override
    public boolean isVersionsnummer() {
      return true;
    }
  },
  VERSIONSNUMMER_ERFASSUNGSTEAM_B(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, LONG, WES, WAS) {
    @Override
    public boolean isHilfsTeamVersion() {
      return true;
    }
  },
  VERSIONSNUMMER_ERFASSUNGSTEAM_C(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, LONG, WES, WAS) {
    @Override
    public boolean isHilfsTeamVersion() {
      return true;
    }

  },
  VERSIONSNUMMER_ERFASSUNGSTEAM_D(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, LONG, WES, WAS) {
    @Override
    public boolean isHilfsTeamVersion() {
      return true;
    }

  },
  VERSIONSNUMMER_ERFASSUNGSTEAM_E(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, LONG, WES, WAS) {
    @Override
    public boolean isHilfsTeamVersion() {
      return true;
    }

  },
  VERSIONSNUMMER_ERFASSUNGSTEAM_F(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, LONG, WES, WAS) {
    @Override
    public boolean isHilfsTeamVersion() {
      return true;
    }

  },

  ONLINE_ERFASSUNGSSTATUS_HAUPTTEAM(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, ENUM, WES, WAS),

  ONLINE_ERFASSUNGSSTATUS_TEAM_B(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, ENUM, WES, WAS) {
    @Override
    public boolean isHilfsteamOnlineErfassungStatus() {
      return true;
    }

    @Override
    public boolean isAlsHauptTeamInWasImportieren() {
      return false; // nur Wert von Hilfsteam akzeotieren
    }

  },
  ONLINE_ERFASSUNGSSTATUS_TEAM_C(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, ENUM, WES, WAS) {
    @Override
    public boolean isHilfsteamOnlineErfassungStatus() {
      return true;
    }

    @Override
    public boolean isAlsHauptTeamInWasImportieren() {
      return false; // nur Wert von Hilfsteam akzeotieren
    }

  },
  ONLINE_ERFASSUNGSSTATUS_TEAM_D(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, ENUM, WES, WAS) {
    @Override
    public boolean isHilfsteamOnlineErfassungStatus() {
      return true;
    }

    @Override
    public boolean isAlsHauptTeamInWasImportieren() {
      return false; // nur Wert von Hilfsteam akzeotieren
    }

  },
  ONLINE_ERFASSUNGSSTATUS_TEAM_E(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, ENUM, WES, WAS) {
    @Override
    public boolean isHilfsteamOnlineErfassungStatus() {
      return true;
    }

    @Override
    public boolean isAlsHauptTeamInWasImportieren() {
      return false; // nur Wert von Hilfsteam akzeotieren
    }

  },
  ONLINE_ERFASSUNGSSTATUS_TEAM_F(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, ENUM, WES, WAS) {
    @Override
    public boolean isHilfsteamOnlineErfassungStatus() {
      return true;
    }

    @Override
    public boolean isAlsHauptTeamInWasImportieren() {
      return false; // nur Wert von Hilfsteam akzeotieren
    }

  },

  STIMMZETTEL_GEPRUEFT(STIMMZETTEL_ERFASSUNG, URNE_UND_BRIEF, ENUM, WES, WAS),

  ANZAHL_WAHLBERECHTIGTE_GESAMT(STIMMZETTEL_ERFASSUNG_KOPFDATEN_WAHLBERECHTIGTE, NUR_URNE, LONG,
      singletonList(new WahlberechtigteGesamtKopfdatumValidator()), WES, WAS),
  ANZAHL_WAHLBERECHTIGTE_OHNE_WAHLSCHEIN(STIMMZETTEL_ERFASSUNG_KOPFDATEN_WAHLBERECHTIGTE, NUR_URNE, LONG, WES, WAS),
  ANZAHL_WAHLBERECHTIGTE_MIT_WAHLSCHEIN(STIMMZETTEL_ERFASSUNG_KOPFDATEN_WAHLBERECHTIGTE, NUR_URNE, LONG, WES, WAS),

  ANZAHL_STIMMZETTEL_GESAMT(STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL, URNE_UND_BRIEF, LONG,
      asList(new StimmzettelInsgesamtKleinerMitWahlscheinKopfdatumValidator(), new StimmzettelInsgesamtAnzahlKopdatumValidator()), WES, WAS),
  ANZAHL_STIMMZETTEL_MIT_WAHLSCHEIN(STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL, NUR_URNE, LONG, WES, WAS),

  ANZAHL_STIMMZETTEL_GUELTIG_GESAMT(STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL, URNE_UND_BRIEF, LONG, WES, WAS) {
    @Override
    public boolean isReadonly() {
      return true;
    }

    @Override
    public boolean isBerechneterWert() {
      return true;
    }

    @Override
    public String berechneWert(Map<StimmzettelKopfdatumArt, List<StimmzettelKopfdatum>> kopfdatenNachArt, List<Stimmzettel> stimmzettelList) {
      long result = 0L;

      try {
        long anzahlStimmzettelGueltigUnveraendert = Long.parseLong(
            StimmzettelKopfdatumArt.ANZAHL_STIMMZETTEL_UNVERAENDERT.berechneWert(kopfdatenNachArt, stimmzettelList));

        result += anzahlStimmzettelGueltigUnveraendert;
      } catch (Exception ignored) {
      }

      long anzahlStimmzettelGueltigVeraendert = stimmzettelList.stream().filter(stimmzettel -> stimmzettel.getGueltigkeit().isGueltig()).count();
      result += anzahlStimmzettelGueltigVeraendert;

      return Long.toString(result);
    }
  },

  ANZAHL_STIMMZETTEL_UNGUELTIG_GESAMT(STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL_UNGUELTIG, URNE_UND_BRIEF, LONG, WES, WAS) {
    @Override
    public boolean isReadonly() {
      return true;
    }

    @Override
    public boolean isBerechneterWert() {
      return true;
    }

    @Override
    public String berechneWert(Map<StimmzettelKopfdatumArt, List<StimmzettelKopfdatum>> kopfdatenNachArt, List<Stimmzettel> stimmzettelList) {
      long result = 0;

      List<StimmzettelKopfdatum> ungueltigNichtGekennzeichnet = kopfdatenNachArt
          .getOrDefault(StimmzettelKopfdatumArt.ANZAHL_STIMMZETTEL_UNGUELTIG_NICHT_GEKENNZEICHNET, emptyList());

      for (StimmzettelKopfdatum stimmzettelKopfdatum : ungueltigNichtGekennzeichnet) {
        String wert = stimmzettelKopfdatum.getWert();

        if (StringUtils.isNotBlank(wert)) {
          try {
            result += Long.parseLong(wert);
          } catch (Exception e) {
            // ignore
          }
        }
      }

      result += stimmzettelList.stream().filter(stimmzettel -> stimmzettel.getGueltigkeit().isUngueltig()).count();

      return Long.toString(result);
    }
  },
  ANZAHL_STIMMZETTEL_UNGUELTIG_NICHT_GEKENNZEICHNET(STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL_UNGUELTIG, URNE_UND_BRIEF, LONG, WES, WAS),
  ANZAHL_STIMMZETTEL_UNGUELTIG_AUS_EINZELERFASSUNG(STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL_UNGUELTIG, URNE_UND_BRIEF, LONG, WES, WAS) {
    @Override
    public boolean isReadonly() {
      return true;
    }

    @Override
    public String berechneWert(Map<StimmzettelKopfdatumArt, List<StimmzettelKopfdatum>> kopfdatenNachArt, List<Stimmzettel> stimmzettelList) {
      return Long.toString(stimmzettelList.stream().filter(stimmzettel -> stimmzettel.getGueltigkeit().isUngueltig())
          .count()); // ELECT-9066 Alle ungültigen aus Einzelerfassung ausgeben
    }

    @Override
    public boolean isBerechneterWert() {
      return true;
    }

  },

  ANZAHL_STIMMZETTEL_UNVERAENDERT(STIMMZETTEL_ERFASSUNG_KOPFDATEN_ANZAHL_STIMMZETTEL, URNE_UND_BRIEF, LONG, WES, WAS) {
    @Override
    public boolean isSummeAusGruppen() {
      return true;
    }

    @Override
    public String berechneWert(Map<StimmzettelKopfdatumArt, List<StimmzettelKopfdatum>> kopfdatenNachArt, List<Stimmzettel> stimmzettelList) {
      long result = 0L;

      for (StimmzettelKopfdatum stimmzettelKopfdatum : kopfdatenNachArt.getOrDefault(this, emptyList())) {
        if (stimmzettelKopfdatum.getGruppe() != null) {
          String wert = stimmzettelKopfdatum.getWert();
          if (StringUtils.isNotBlank(wert)) {
            try {
              result += Long.parseLong(wert);
            } catch (Exception e) {
              // ignore
            }
          }
        }
      }

      return Long.toString(result);
    }

    @Override
    public boolean isBerechneterWert() {
      return true;
    }

    @Override
    public boolean isFuerGruppenErfassen() {
      return true;
    }

  },
  WAEHLERBENANNTE_KANDIDATUREN(STIMMZETTEL_ERFASSUNG, NICHT_ERFASSEN, VOID, WES, WAS) {
    @Override
    public boolean isKopfdatum() {
      return false;
    }
  };

  public static final Set<StimmzettelKopfdatumArt> ONLINE_ERFASSUNGSSTATUS = EnumSet.of(
      ONLINE_ERFASSUNGSSTATUS_HAUPTTEAM,
      ONLINE_ERFASSUNGSSTATUS_TEAM_B,
      ONLINE_ERFASSUNGSSTATUS_TEAM_C,
      ONLINE_ERFASSUNGSSTATUS_TEAM_D,
      ONLINE_ERFASSUNGSSTATUS_TEAM_E,
      ONLINE_ERFASSUNGSSTATUS_TEAM_F
  );

  public static final String ABGESCHLOSSEN_WERT = "1";

  public static final String NICHT_ABGESCHLOSSEN_WERT = "0";

  private final StimmzettelKopfdatumAbgabeart stimmzettelKopfdatumAbgabeart;

  private final List<Customer> customers;

  private final Predicate<Customer> customersExcludedFilter;

  private final CustomParameterKategorie kategorie;

  private final CustomParameterWertTyp wertTyp;

  private final List<AbstractStimmzettelKopfdatumValidator> stimmzettelKopfdatumValidators;

  private final Supplier<List<String>> wertStringValues;

  private final Supplier<Function<String, String>> wertStringValueToLabelFct;

  private final Class<? extends Enum<?>> wertEnumClass;

  private final Class<? extends CustomParameterFilter<BenutzerParameter, ? extends Enum<?>>> filter;

  StimmzettelKopfdatumArt(CustomParameterKategorie kategorie, StimmzettelKopfdatumAbgabeart stimmzettelKopfdatumAbgabeart, CustomParameterWertTyp wertTyp, Customer... customers) {
    this(kategorie, stimmzettelKopfdatumAbgabeart, wertTyp, emptyList(), customers);
  }

  StimmzettelKopfdatumArt(CustomParameterKategorie kategorie, StimmzettelKopfdatumAbgabeart stimmzettelKopfdatumAbgabeart, CustomParameterWertTyp wertTyp, List<AbstractStimmzettelKopfdatumValidator> wertValidators, Customer... customers) {
    this(kategorie, stimmzettelKopfdatumAbgabeart, wertTyp, null, null, wertValidators, customers);
  }

  StimmzettelKopfdatumArt(CustomParameterKategorie kategorie, StimmzettelKopfdatumAbgabeart stimmzettelKopfdatumAbgabeart, CustomParameterWertTyp wertTyp, Class<? extends Enum<?>> wertEnumClass, Customer... customers) {
    this(kategorie, stimmzettelKopfdatumAbgabeart, wertTyp, wertEnumClass, null, emptyList(), customers);
  }

  <E extends Enum<?>> StimmzettelKopfdatumArt(CustomParameterKategorie kategorie, StimmzettelKopfdatumAbgabeart stimmzettelKopfdatumAbgabeart, CustomParameterWertTyp wertTyp, Class<E> wertEnumClass, Class<? extends CustomParameterFilter<BenutzerParameter, E>> filter,
                                              List<AbstractStimmzettelKopfdatumValidator> wertValidators, Customer... customers) {
    this(kategorie, stimmzettelKopfdatumAbgabeart, wertTyp, wertEnumClass, filter, Collections::emptyList, Function::identity, wertValidators, c -> false,
        customers);
  }

  <E extends Enum<?>> StimmzettelKopfdatumArt(CustomParameterKategorie kategorie, StimmzettelKopfdatumAbgabeart stimmzettelKopfdatumAbgabeart, CustomParameterWertTyp wertTyp, Class<E> wertEnumClass, Class<? extends CustomParameterFilter<BenutzerParameter, E>> filter,
                                              Supplier<List<String>> wertStringValues, Supplier<Function<String, String>> wertStringValueToLabelFct, List<AbstractStimmzettelKopfdatumValidator> wertValidators, Predicate<Customer> customersExcludedFilter,
                                              Customer... customers) {
    this.stimmzettelKopfdatumAbgabeart = stimmzettelKopfdatumAbgabeart;
    this.customers = asList(customers);
    this.customersExcludedFilter = customersExcludedFilter;

    this.kategorie = kategorie;

    this.wertTyp = wertTyp;

    this.wertStringValues = wertStringValues;
    this.wertStringValueToLabelFct = wertStringValueToLabelFct;
    this.stimmzettelKopfdatumValidators = wertValidators;

    this.wertEnumClass = wertEnumClass;
    this.filter = filter;
  }

  @Override
  public List<Customer> getCustomers() {
    return customers;
  }

  @Override
  public CustomParameterKategorie getKategorie() {
    return kategorie;
  }

  @Override
  public KonfigurationNotify getKonfigurationNotify() {
    return KonfigurationNotify.NICHT_BENACHRICHTIGEN; // ELECT-16141, LEGACY...
  }

  @Override
  public String getKey() {
    return name();
  }

  @Override
  public CustomParameterWertTyp getWertTyp() {
    return wertTyp;
  }

  @Override
  public Class<? extends Enum<?>> getWertEnumClass() {
    return wertEnumClass;
  }

  @Override
  public Supplier<java.util.List<String>> getWertStringValues() {
    return wertStringValues;
  }

  @Override
  public Supplier<Function<String, String>> getWertStringValueToLabelFct() {
    return wertStringValueToLabelFct;
  }

  @Override
  public List<Validator> getWertValidators() {
    return emptyList();
  }

  @Override
  public boolean isCustomerExcluded(Customer customer) {
    return customersExcludedFilter.test(customer);
  }

  @Override
  public Class<? extends CustomParameterFilter<BenutzerParameter, ? extends Enum<?>>> getFilter() {
    return filter;
  }

  public boolean isSummeAusGruppen() {
    return false;
  }

  public boolean isFuerGruppenErfassen() {
    return false;
  }

  public boolean isKopfzahlZuErfassen(Gebiet gebiet) {
    return stimmzettelKopfdatumAbgabeart.isZuErfassem(gebiet);
  }

  public List<AbstractStimmzettelKopfdatumValidator> getStimmzettelKopfdatumValidators() {
    List<AbstractStimmzettelKopfdatumValidator> result = new ArrayList<>();
    result.add(new WertErforderlichStimmzettelKopfdatumValidator());
    result.add(new NichtNegativZahlStimmzettelKopfdatumValidator());
    result.addAll(stimmzettelKopfdatumValidators);
    return result;
  }

  public boolean isReadonly() {
    return false;
  }

  public boolean isKopfdatum() {
    return true;
  }

  public String berechneWert(Map<StimmzettelKopfdatumArt, List<StimmzettelKopfdatum>> kopfdatenNachArt, List<Stimmzettel> stimmzettelList) {
    throw new UnsupportedOperationException(name());
  }

  public boolean isHilfsteamWert() {
    return isHilfsTeamVersion() || isHilfsteamOnlineErfassungStatus();
  }

  public boolean isHilfsteamOnlineErfassungStatus() {
    return false;
  }

  public boolean isHilfsTeamVersion() {
    return false;
  }

  public boolean isVersionsnummer() {
    return isHilfsTeamVersion();
  }

  public boolean isBerechneterWert() {
    // ELECTKOM-807
    return false;
  }

  public boolean isErfassungAbgschlossen() {
    return false;
  }

  public boolean isCustomDataJsonSupported() {
    return false;
  }

  public boolean isAlsHauptTeamInWasImportieren() {
    return true;
  }

  public static StimmzettelKopfdatumArt bestimmeOnlineErfassungstatusArt(StimmzettelTeam stimmzettelTeam) {
    switch (stimmzettelTeam) {
      case OHNE_TEAM:
      case HAUPTTEAM_A:
        return StimmzettelKopfdatumArt.ONLINE_ERFASSUNGSSTATUS_HAUPTTEAM;
      case HILFSTEAM_B:
        return StimmzettelKopfdatumArt.ONLINE_ERFASSUNGSSTATUS_TEAM_B;
      case HILFSTEAM_C:
        return StimmzettelKopfdatumArt.ONLINE_ERFASSUNGSSTATUS_TEAM_C;
      case HILFSTEAM_D:
        return StimmzettelKopfdatumArt.ONLINE_ERFASSUNGSSTATUS_TEAM_D;
      case HILFSTEAM_E:
        return StimmzettelKopfdatumArt.ONLINE_ERFASSUNGSSTATUS_TEAM_E;
      case HILFSTEAM_F:
        return StimmzettelKopfdatumArt.ONLINE_ERFASSUNGSSTATUS_TEAM_F;
    }

    throw new IllegalArgumentException("Unbekanntes Team: " + stimmzettelTeam);

  }
}
