
package de.ivu.elect.wus.exchange.wahlvorschlag.csv;

import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagExportContext;
import de.ivu.elect.business.kandidatur.entity.*;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.exchange.csv.CsvMarshaller;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.wus.exchange.kandidatur.WahlvorschlagExportMapper;

import javax.inject.Inject;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList;

public abstract class AbstractWahlvorschlagCsvExportMapper<T extends WahlvorschlagCsvDTO, M extends AbstractWahlvorschlagCsvInternExternMapper<T>> implements WahlvorschlagExportMapper {

  @Inject
  private transient Messages messages;

  @Inject
  protected transient CsvMarshaller csvMarshaller;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  public AbstractWahlvorschlagCsvExportMapper() {
    super();
  }

  @Override
  public boolean isAcceptContext(WahlvorschlagExportContext context) {
    Wahl wahl = context.getWahl();

    // NOTE, dass derzeit nur Listen, Listenkandidaturen und Direktkandidaturen unterstützt werden
    return wahl != null && (wahl.isWahlMitDirektkandidatengebieten() || wahl.isWahlMitListengebieten());
  }

  @Override
  public void serialize(WahlvorschlagExportObject exportObject, OutputStream os, WahlvorschlagExportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    try {
      List<T> dtos = createDTOs(context, exportObject);

      dtos = validiereUndFiltereDtos(dtos, context);

      if (!dtos.isEmpty()) {
        serializeDtos(os, fileKonfiguration, dtos);
      }
    } catch (Exception ex) {
      throw new MappingException("Die Daten konnten nicht ins CSV-Format exportiert werden.", ex);
    }
  }

  protected void serializeDtos(OutputStream os, FileKonfiguration fileKonfiguration, List<T> dtos) {
    csvMarshaller.serialize((Class<T>) dtos.get(0).getClass(), dtos, os, fileKonfiguration);
  }

  protected List<T> validiereUndFiltereDtos(List<T> dtos, WahlvorschlagExportContext context) {
    return dtos;
  }


  private List<T> createDTOs(WahlvorschlagExportContext context, WahlvorschlagExportObject exportObject) {
    List<T> result = new ArrayList<>();

    for (Wahlvorschlag wahlvorschlag : getWahlvorschlaegeExportierbar(context, exportObject.getWahlvorschlaege())) {
      Collection<Kandidatur> kandidaturen = exportObject.getKandidaturenPerWahlvorschlag().getOrDefault(wahlvorschlag, emptySet());
      Collection<Kandidatur> kandidaturenExportierbar = kandidaturen.stream().filter(kandidaturExportierbar(context)).collect(toList());

      if (kandidaturenExportierbar.isEmpty() && isLeereListenExportieren()) {
        result.add(createDTO(context, wahlvorschlag));
      }

      for (Kandidatur kandidatur : kandidaturenExportierbar) {
        result.add(createDTO(context, wahlvorschlag, kandidatur));
      }
    }

    return result;
  }

  protected boolean isLeereListenExportieren() {
    return true;
  }

  private List<Wahlvorschlag> getWahlvorschlaegeExportierbar(WahlvorschlagExportContext context, List<Wahlvorschlag> wahlvorschlaege) {
    return wahlvorschlaege.stream().filter(wahlvorschlagExportierbar(context)).collect(toList());
  }

  protected abstract Predicate<Wahlvorschlag> wahlvorschlagExportierbar(WahlvorschlagExportContext context);

  protected abstract Predicate<Kandidatur> kandidaturExportierbar(WahlvorschlagExportContext context);

  private T createDTO(WahlvorschlagExportContext context, Wahlvorschlag wahlvorschlag) {
    return getInternExternMapperMapper().mapFromWahlvorschlag(wahlvorschlag, getEntityAufStimmzettelDaten(), context);
  }

  private T createDTO(WahlvorschlagExportContext context, Wahlvorschlag wahlvorschlag, Kandidatur kandidatur) {
    T dto = createDTO(context, wahlvorschlag);

    EntityAufStimmzettelDaten entityAufStimmzettelDaten = getEntityAufStimmzettelDaten();

    if (kandidatur.isListenkandidatur()) {
      getInternExternMapperMapper().fillListenkandidatur(dto, castTo(kandidatur, Listenkandidatur.class), entityAufStimmzettelDaten, context);
    }

    if (kandidatur.isDirektkandidatur()) {
      getInternExternMapperMapper().fillDirektkandidatur(dto, castTo(kandidatur, Direktkandidatur.class), entityAufStimmzettelDaten, context);
    }

    if (kandidatur.isErsatzkandidatur()) {
      getInternExternMapperMapper().fillErsatzkandidatur(dto, castTo(kandidatur, Ersatzkandidatur.class), entityAufStimmzettelDaten, context);
    }

    return dto;
  }

  protected EntityAufStimmzettelDaten getEntityAufStimmzettelDaten() {
    return entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten();
  }

  protected abstract M getInternExternMapperMapper();

  protected String getMessage(String msgKey, Object... params) {
    return messages.getMessageForMessageKey(msgKey, params);
  }

  protected void addWarnMessage(String msgKey, Object... params) {
    messages.addWarnMessage(msgKey, params);
  }
}
