
package de.ivu.elect.was.exchange.stimmeingabe;

import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.datei.entity.FileType;
import de.ivu.elect.business.exchange.control.FileFormat;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gebietsbaum.boundary.WahldurchfuehrungsgebietDAO;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.control.StimmeingabeExportContext;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.wahl.entity.Wahldurchfuehrung;
import de.ivu.elect.interfaces.was.stimmeingabe.ErfassteStimmen;
import de.ivu.elect.jee.exchange.XmlMarshaller;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaBooleanProperty;

import javax.annotation.Nullable;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import java.io.OutputStream;
import java.util.*;
import java.util.stream.Collectors;

public abstract class AbstractWasStimmeingabeXMLExportMapper extends AbstractWasEingabeXMLExportMapper implements StimmeingabeExportMapper {

  public static final String FILE_FORMAT_NAME = "file.format.stimmeingaben";

  public static final String FEHLENDE_GEBIETE_ALS_GELOESCHT_AUSGEBEN = "AbstractWasStimmeingabeXMLExportMapper.FEHLENDE_GEBIETE_ALS_GELOESCHT_AUSGEBEN";
  public static final String IN_ANDEREN_GEBIETEN_ERFASSTE_GEBIETE_AUSGEBEN = "AbstractWasStimmeingabeXMLExportMapper.IN_ANDEREN_GEBIETEN_ERFASSTE_GEBIETE_AUSGEBEN";
  public static final String ERFASSUNGSSTATUS_AUSGEBEN = "AbstractWasStimmeingabeXMLExportMapper.ERFASSUNGSSTATUS_AUSGEBEN";

  @Inject
  private transient XmlMarshaller<ErfassteStimmen> xmlMarshaller;

  @Inject
  private transient Messages messages;


  @Inject
  private transient WahldurchfuehrungsgebietDAO wahldurchfuehrungsgebietDAO;

  protected AbstractWasStimmeingabeXMLExportMapper() {
    super();
  }

  @Override
  public boolean isAcceptContext(StimmeingabeExportContext context) {
    return context.getWahldurchfuehrung() != null;
  }

  @Override
  public FileFormat getFileFormat() {
    return new FileFormat(FILE_FORMAT_NAME, FileType.XML);
  }

  @Inject
  private transient Instance<ErfassteStimmenDecorator> erfassteStimmenDecorators;

  @Override
  public void serialize(List<Ergebniseingang> ergebniseingaenge, OutputStream os, StimmeingabeExportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    var erfassteStimmen = mappe(ergebniseingaenge,
        isFehlendeGebieteAlsGeloeschtAusgeben(context),
        isInAnderenGebieteErfassteAusgeben(context),
        isErfassungsstatusAusgeben(context),
        context.isNurEingangsartenVonWahl(),
        context.getWahldurchfuehrung());

    if (!erfassteStimmenDecorators.isUnsatisfied()) {
      for (var erfassteStimmenDecorator : erfassteStimmenDecorators) {
        erfassteStimmenDecorator.decorate(erfassteStimmen, ergebniseingaenge, context, fileKonfiguration);
      }
    }

    serialize(erfassteStimmen,
        os, fileKonfiguration);
  }

  public void serialize(ErfassteStimmen erfassteStimmen, OutputStream os, FileKonfiguration fileKonfiguration) throws MappingException {
    try {
      xmlMarshaller.serialize(erfassteStimmen, os);
    } catch (Exception ex) {
      throw new MappingException(ex.getMessage(), ex);
    }
  }

  public ErfassteStimmen mappe(List<Ergebniseingang> ergebniseingaenge,
                               boolean fehlendeGebieteAlsGeloeschtAusgeben,
                               boolean inAnderenGebieteErfassteAusgeben,
                               boolean erfassungsStatusExportiere,
                               boolean nurEingangsartVonWahl,
                               Wahldurchfuehrung wahldurchfuehrung) {
    var erfassteStimmen = new ErfassteStimmen();

    Map<Ergebniseingangsart, Set<Gebiet>> gebieteMitEingang = new EnumMap<>(Ergebniseingangsart.class);
    for (var value : Ergebniseingangsart.values()) {
      gebieteMitEingang.put(value, new HashSet<>());
    }


    mappeWahl(erfassteStimmen, wahldurchfuehrung);

    var maps = new WasStimmeingabeXMLExportMapperMaps(wahlInternExternMapperUtil);

    for (var ergebniseingang : ergebniseingaenge) {
      if (isGeloeschterEingang(ergebniseingang)) {
        erfassteStimmen.getGeloeschterErgebniseingaenge().add(mappeGeloescht(ergebniseingang, maps));
      } else {
        erfassteStimmen.getErgebniseingaenge().add(mappe(ergebniseingang, maps));
      }

      gebieteMitEingang.get(ergebniseingang.getErgebniseingangsart()).add(ergebniseingang.getGebiet());
    }


    Ergebniseingangsart nurErgebniseingangsart = null;
    if (nurEingangsartVonWahl) {
      nurErgebniseingangsart = wahldurchfuehrung.getErgebniseingangsart();
    }

    if (fehlendeGebieteAlsGeloeschtAusgeben || inAnderenGebieteErfassteAusgeben || erfassungsStatusExportiere) {
      var alleGebiete = wahldurchfuehrungsgebietDAO.findByWahl(wahldurchfuehrung);
      if (fehlendeGebieteAlsGeloeschtAusgeben) {
        for (var wahldurchfuehrungsgebiet : alleGebiete) {
          if (wahldurchfuehrungsgebiet.isErfassungsgebiet()) {
            geloeschtenEingangAufnehmen(erfassteStimmen, gebieteMitEingang, maps, wahldurchfuehrungsgebiet, nurErgebniseingangsart);
          }
        }
      }
      if (inAnderenGebieteErfassteAusgeben) {
        for (var wahldurchfuehrungsgebiet : alleGebiete) {
          if (wahldurchfuehrungsgebiet.getErfasstIn() != null) {
            geloeschtenEingangAufnehmen(erfassteStimmen, gebieteMitEingang, maps, wahldurchfuehrungsgebiet, nurErgebniseingangsart);
          }
        }
      }

      if (erfassungsStatusExportiere) {
        for (var gebiet : alleGebiete) {
          if (gebiet.isErfassungsgebietLautGebietsart()/*Beinhaltet auch ausbleibende*/) {
            erfassteStimmen.getEingabestatusAktualisierungen().add(mappeAktualisierung(gebiet, maps, nurErgebniseingangsart));
          }
        }
      }
    }


    return erfassteStimmen;
  }

  private Set<Gebiet> bestimmeGebiete(Map<Ergebniseingangsart, Set<Gebiet>> gebieteMitEingang) {
    return gebieteMitEingang.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
  }

  private void geloeschtenEingangAufnehmen(ErfassteStimmen erfassteStimmen,
                                           Map<Ergebniseingangsart,
                                               Set<Gebiet>> gebieteMitEingang,
                                           WasStimmeingabeXMLExportMapperMaps maps,
                                           Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet,
                                           @Nullable Ergebniseingangsart nurErgebniseingangsart) {
    for (var entry : gebieteMitEingang.entrySet()) {
      if (nurErgebniseingangsart == null || nurErgebniseingangsart.equals(entry.getKey())) {
        if (!entry.getValue().contains(wahldurchfuehrungsgebiet)) {
          erfassteStimmen.getGeloeschterErgebniseingaenge().add(mappeGeloescht(-1L, wahldurchfuehrungsgebiet, entry.getKey(), maps));
          entry.getValue().add(wahldurchfuehrungsgebiet);
        }
      }
    }
  }

  public static boolean isGeloeschterEingang(Ergebniseingang ergebniseingang) {
    return ergebniseingang.getStimmeingaben().isEmpty();
  }

  @Override
  public void addProperties(FormBuilder formBuilder, StimmeingabeExportContext context, @Nullable Datei datei) {
    StimmeingabeExportMapper.super.addProperties(formBuilder, context, datei);

    formBuilder.addProperty(new LambdaBooleanProperty(
        messages.getMessageForMessageKey("file.format.stimmeingaben.gebiete.ohne.erfassung.als.geloescht.exportieren"),
        false,
        b -> context.put(FEHLENDE_GEBIETE_ALS_GELOESCHT_AUSGEBEN, b),
        () -> isFehlendeGebieteAlsGeloeschtAusgeben(context))
    );
    formBuilder.addProperty(new LambdaBooleanProperty(
        messages.getMessageForMessageKey("file.format.stimmeingaben.in.anderen.gebieten.erfasst.ausgeben"),
        false,
        b -> context.put(IN_ANDEREN_GEBIETEN_ERFASSTE_GEBIETE_AUSGEBEN, b),
        () -> isInAnderenGebieteErfassteAusgeben(context))
    );
    formBuilder.addProperty(new LambdaBooleanProperty(
        messages.getMessageForMessageKey("file.format.stimmeingaben.erfassungsstatus.exportieren"),
        false,
        b -> context.put(ERFASSUNGSSTATUS_AUSGEBEN, b),
        () -> isErfassungsstatusAusgeben(context))
    );
  }

  private static boolean isFehlendeGebieteAlsGeloeschtAusgeben(StimmeingabeExportContext context) {
    return Boolean.TRUE.equals(context.get(FEHLENDE_GEBIETE_ALS_GELOESCHT_AUSGEBEN, Boolean.class));
  }

  private static boolean isInAnderenGebieteErfassteAusgeben(StimmeingabeExportContext context) {
    return Boolean.TRUE.equals(context.get(IN_ANDEREN_GEBIETEN_ERFASSTE_GEBIETE_AUSGEBEN, Boolean.class));
  }

  private static boolean isErfassungsstatusAusgeben(StimmeingabeExportContext context) {
    return Boolean.TRUE.equals(context.get(ERFASSUNGSSTATUS_AUSGEBEN, Boolean.class));
  }
}
