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

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.personenpruefung.boundary.PersonPruefungKonfigurationDAO;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfiguration;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfigurationTK;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfigurationStatus;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;

public class PersonPruefungKonfigurationInit implements Serializable {

  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LoggerFactory.getLogger(PersonPruefungKonfigurationInit.class);

  @Inject
  private transient Instance<PersonPruefungKonfigurationDefault> personPruefungKonfigurationDefaults;

  @Inject
  private transient PersonPruefungKonfigurationDAO personPruefungKonfigurationDAO;

  public void init(Wahl wahl) {
    LOG.info("init: " + wahl);
    init(wahl.getWahltermin(), wahl);
  }

  public void init(Wahltermin wahltermin) {
    LOG.info("init: " + wahltermin);
    init(wahltermin, null);
  }

  protected void init(Wahltermin wahltermin, @Nullable Wahl wahl) {
    boolean changed = false;
    Map<PersonPruefungKonfigurationTK, PersonPruefungKonfiguration> vorhandeneNachTK = getVorhandeneKonfigurationenNachName(wahltermin, wahl);

    Set<PersonPruefungKonfigurationTK> tksStandard = new HashSet<>();

    for (PersonPruefungKonfigurationDefault personPruefungKonfigurationDefault : personPruefungKonfigurationDefaults) {
      if (personPruefungKonfigurationDefault.accept(wahltermin, wahl)) {
        for (PersonPruefungKonfiguration standard : personPruefungKonfigurationDefault.getPersonPruefungKonfigurationenDefault(wahltermin, wahl)) {
          standard.setStatus(PersonPruefungKonfigurationStatus.AKTIVIERT_DEFAULT);
          tksStandard.add(standard.getPersonPruefungKonfigurationTK());
          PersonPruefungKonfiguration vorhanden = vorhandeneNachTK.get(standard.getPersonPruefungKonfigurationTK());
          if (vorhanden == null) {
            changed = true;
            standard.setWahltermin(wahltermin);
            standard.setWahl(wahl);
            personPruefungKonfigurationDAO.persist(standard);
            LOG.info("PersonPruefungKonfiguration angelegt: " + standard.getName() + ", " + wahltermin + ", " + wahl);
          } else {
            if (vorhanden.getStatus().isStandard()) {

              boolean thisChanged = update(standard, vorhanden);

              if (thisChanged) {
                changed = true;
                LOG.info("PersonPruefungKonfiguration aktualisiert: " + standard.getName() + ", " + wahltermin + ", " + wahl);
              }
            }
          }
        }
      }
    }

    for (Map.Entry<PersonPruefungKonfigurationTK, PersonPruefungKonfiguration> entry : vorhandeneNachTK.entrySet()) {
      if (!tksStandard.contains(entry.getKey())) {
        LOG.info("PersonPruefungKonfiguration gelöscht: " + entry.getKey() + ", " + wahltermin + ", " + wahl);
        personPruefungKonfigurationDAO.remove(entry.getValue());
        changed = true;
      }
    }

    if (changed) {
      personPruefungKonfigurationDAO.flush(); // damit vom Importer gefunden
    }
  }

  public boolean update(PersonPruefungKonfiguration standard, PersonPruefungKonfiguration vorhanden) {
    vorhanden.setTyp(standard.getTyp());
    vorhanden.setPersonentyp(standard.getPersonentyp());
    vorhanden.setName(standard.getName());
    vorhanden.setBeschreibung(standard.getBeschreibung());
    boolean thisChanged = vorhanden.isAttributeChanged();

    String vorhandenXML = vorhanden.getCustomDataXml();

    if (!vorhandenXML.equals(standard.getCustomDataXml())) {
      thisChanged = true;
      vorhanden.setCustomDataXml(standard.getCustomDataXml());
    }
    return thisChanged;
  }

  @Nonnull
  private Map<PersonPruefungKonfigurationTK, PersonPruefungKonfiguration> getVorhandeneKonfigurationenNachName(Wahltermin wahltermin, @Nullable Wahl wahl) {
    return bestimmeVorhandene(wahltermin, wahl).stream()
        .collect(Collectors.toMap(PersonPruefungKonfiguration::getPersonPruefungKonfigurationTK, personPruefungKonfiguration -> personPruefungKonfiguration,
            (a, b) -> b));
  }

  private List<PersonPruefungKonfiguration> bestimmeVorhandene(Wahltermin wahltermin, Wahl wahl) {
    if (wahl != null) {
      return personPruefungKonfigurationDAO.findByWahl(wahl);
    }
    return personPruefungKonfigurationDAO.findByWahltermin(wahltermin);
  }

  public void updatePersonPruefungKonfiguration(Wahltermin wahltermin, Wahl wahl, Collection<PersonPruefungKonfiguration> neueKonfigurationen) {
    if (neueKonfigurationen == null || neueKonfigurationen.isEmpty()) {
      return; // nichts zu tun
    }

    Map<PersonPruefungKonfigurationTK, PersonPruefungKonfiguration> vorhandeneNachTk = getVorhandeneKonfigurationenNachName(wahltermin, wahl);

    for (PersonPruefungKonfiguration neu : neueKonfigurationen) {
      PersonPruefungKonfiguration vorhanden = vorhandeneNachTk.get(neu.getPersonPruefungKonfigurationTK());
      if (vorhanden == null) {
        LOG.warn("PersonPruefungKonfiguration: " + neu.getPersonPruefungKonfigurationTK() + " nicht vorhanden => ignoriert");
      } else {
        if (neu.getStatus().isStandard()) {
          if (!neu.getStatus().isAktiv()) {
            LOG.warn("PersonPruefungKonfiguration: " + neu.getPersonPruefungKonfigurationTK() + " deaktiviert");
            vorhanden.setStatus(neu.getStatus());
          } else {
            LOG.warn("PersonPruefungKonfiguration: " + neu.getPersonPruefungKonfigurationTK() + " Standard -> kein Update");
          }
        } else {
          update(neu, vorhanden);
          vorhanden.setStatus(neu.getStatus());
        }
      }
    }
  }

}
