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

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;
import javax.inject.Inject;

import de.ivu.elect.business.datei.entity.Datei;
import de.ivu.elect.business.exchange.control.FileKonfiguration;
import de.ivu.elect.business.exchange.control.MappingException;
import de.ivu.elect.business.gruppe.control.ImporterFindVorhandeneGruppeVerhalten;
import de.ivu.elect.business.gruppeimgebiet.entity.GruppeImGebietTK;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImportContext;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImporterFindVorhandeneGruppeVerhaltenProperty;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImporterZulassungsVerhalten;
import de.ivu.elect.business.kandidatur.control.WahlvorschlagImporterZulassungsVerhaltenProperty;
import de.ivu.elect.business.kandidatur.entity.*;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlrechtspruefung.boundary.WahlZulassungspruefungFactory;
import de.ivu.elect.business.wahlrechtspruefung.control.WahlZulassungspruefungNotInject;
import de.ivu.elect.business.wahlvorschlag.entity.Direktkandidat;
import de.ivu.elect.business.wahlvorschlag.entity.DirektkandidatTK;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.ListeTK;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.LambdaEnumProperty;
import de.ivu.elect.wus.exchange.wahlvorschlag.WahlvorschlagImportMapper;


import static de.ivu.elect.util.MapUtil.putIfNewOrReturnExisting;
import static de.ivu.elect.util.MapUtil.putUnique;

public abstract class AbstractWahlvorschlagCsvImportMapper<T extends WahlvorschlagCsvDTO> implements WahlvorschlagImportMapper {

  @Inject
  private transient WahlZulassungspruefungFactory wahlZulassungspruefungFactory;
  private WahlZulassungspruefungNotInject wahlZulassungspruefungNotInject;

  // NOTE: final, weil stattdessen deserializeWahlvorschlaege implementiert werden soll
  @Override
  public List<Wahlvorschlag> deserialize(InputStream is, WahlvorschlagImportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    updateContext(context);
    return deserializeWahlvorschlaege(is, context, fileKonfiguration);
  }

  protected abstract List<Wahlvorschlag> deserializeWahlvorschlaege(InputStream is, WahlvorschlagImportContext context, FileKonfiguration fileKonfiguration) throws MappingException;

  // NOTE: final, weil stattdessen validateInputFormatInternal implementiert werden soll

  @Override
  public Violations validateInputFormat(InputStream is, WahlvorschlagImportContext context, FileKonfiguration fileKonfiguration) {
    updateContext(context);
    return validateInputFormatInternal(is, context, fileKonfiguration);
  }
  protected abstract Violations validateInputFormatInternal(InputStream is, WahlvorschlagImportContext context, FileKonfiguration fileKonfiguration);

  protected void updateContext(WahlvorschlagImportContext context) {
    context.setKandidaturenNachTkFunction(WahlvorschlagImportContext.DEFAULT_KANDIDATUREN_NACH_TK_FUNCTION);
  }

  protected List<Wahlvorschlag> mapToWahlvorschlaege(List<T> wahlvorschlaegeCsv, WahlvorschlagImportContext context) throws MappingException {

    // Listenkandidaturenen derselben ListeTK werden immer derselben Liste-Entität zugeordnet
    Map<ListeTK, Liste> listen = new HashMap<>();

    // DirektkandidatTKs müssen eindeutig sein
    Map<DirektkandidatTK, Direktkandidat> direktkandidaten = new HashMap<>();

    // für Einzelkandidaten wird derzeit keinerlei Eindeutigkeit überprüft, sie werden einfach alle importiert
    List<Direktkandidat> einzelkandidaten = new ArrayList<>();

    // Ersatzkandidaturen werden nur importiert, wenn sich eine andere Kandidatur zuordnen lässt
    Map<GruppeImGebietTK, List<Wahlvorschlag>> wahlvorschlaegeProGruppeImGebiet = new HashMap<>();

    for (T wahlvorschlagCsv : wahlvorschlaegeCsv) {
      if (wahlvorschlagCsv.isEinzelbewerber()) {
        Direktkandidat direktkandidat = mapToDirektkandidat(wahlvorschlagCsv, context);
        einzelkandidaten.add(direktkandidat);
        putIfNew(wahlvorschlaegeProGruppeImGebiet, direktkandidat);

      } else if (wahlvorschlagCsv.isErsatzkandidatur()) {
        mapToErsatzkandidatur(wahlvorschlagCsv, context, wahlvorschlaegeProGruppeImGebiet);

      } else if (wahlvorschlagCsv.isDirektkandidat()) {
        Direktkandidat direktkandidat = mapToDirektkandidat(wahlvorschlagCsv, context);
        putUnique(direktkandidaten, direktkandidat.getDirektkandidatTK(), direktkandidat);
        putIfNew(wahlvorschlaegeProGruppeImGebiet, direktkandidat);

      } else if (wahlvorschlagCsv.isListenkandidatur()) {
        Liste listeDerKandidatur = mapToListeOhneListenkandidatur(wahlvorschlagCsv, context);
        Liste liste = putIfNewOrReturnExisting(listen, listeDerKandidatur.getListeTK(), listeDerKandidatur);
        putIfNew(wahlvorschlaegeProGruppeImGebiet, liste);

        Listenkandidatur listenkandidatur = mapToListenkandidatur(wahlvorschlagCsv, context);
        liste.addListenkandidatur(listenkandidatur);

      } else if (wahlvorschlagCsv.isLeereListe()) {
        Liste liste = mapToListeOhneListenkandidatur(wahlvorschlagCsv, context);
        putUnique(listen, liste.getListeTK(), liste);
        putIfNew(wahlvorschlaegeProGruppeImGebiet, liste);

      } else {
        throw new MappingException("In folgendem Eintrag konnte keine Liste, Direkt-, Listen- oder Ersatzkandidatur erkannt werden: [" + wahlvorschlagCsv.toString() + "]!");
      }
    }

    List<Wahlvorschlag> result = new ArrayList<>();
    result.addAll(listen.values());
    result.addAll(direktkandidaten.values());
    result.addAll(einzelkandidaten);

    return result;
  }

  private void putIfNew(Map<GruppeImGebietTK, List<Wahlvorschlag>> wahlvorschlaegeProGruppeImGebiet, Wahlvorschlag wahlvorschlag) {
    List<Wahlvorschlag> wahlvorschlaege = wahlvorschlaegeProGruppeImGebiet.computeIfAbsent(wahlvorschlag.getGruppeImGebiet().getGruppeImGebietTK(), key -> new ArrayList<>());
    if (!wahlvorschlaege.contains(wahlvorschlag)) {
      wahlvorschlaege.add(wahlvorschlag);
    }
  }

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

    bestimmeDefaultZulassungsverhalten(context);
    addZulassungsVerhaltenProperty(formBuilder, context);
    addFindVorhandeneGruppeVerhaltenProperty(formBuilder, context); // ELECT-16291
  }

  protected void addZulassungsVerhaltenProperty(FormBuilder formBuilder, WahlvorschlagImportContext context) {
    if (isZulassungAktiviert(context)) {
      formBuilder.addProperty(new WahlvorschlagImporterZulassungsVerhaltenProperty(context));
    }
  }

  protected void addFindVorhandeneGruppeVerhaltenProperty(FormBuilder formBuilder, WahlvorschlagImportContext context) {
    formBuilder.addProperty(new WahlvorschlagImporterFindVorhandeneGruppeVerhaltenProperty(context));
  }

  private boolean isZulassungAktiviert(WahlvorschlagImportContext context) {
    return getWahlZulassungspruefungNotInject(context.getWahl()).isZulassungAktiviert();
  }

  private WahlZulassungspruefungNotInject getWahlZulassungspruefungNotInject(Wahl wahl) {
    if (wahlZulassungspruefungNotInject == null) {
      wahlZulassungspruefungNotInject = wahlZulassungspruefungFactory.create(wahl);
    }
    return wahlZulassungspruefungNotInject;
  }

  private void bestimmeDefaultZulassungsverhalten(WahlvorschlagImportContext context) {
    if (getWahlZulassungspruefungNotInject(context.getWahl()).getZulassungWahlvorschlagStatusInitial().isZugelassen()) {
      context.setZulassungsVerhalten(WahlvorschlagImporterZulassungsVerhalten.ALLE_WAHLVORSCHLAEGE_ZULASSEN);
    } else {
      context.setZulassungsVerhalten(WahlvorschlagImporterZulassungsVerhalten.KEINE_WAHLVORSCHLAEGE_ZULASSEN);
    }
  }

  protected abstract Listenkandidatur mapToListenkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException;

  protected abstract Liste mapToListeOhneListenkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException;

  protected abstract Direktkandidat mapToDirektkandidat(T wahlvorschlagCsv, WahlvorschlagImportContext context) throws MappingException;

  protected abstract Ersatzkandidatur mapToErsatzkandidatur(T wahlvorschlagCsv, WahlvorschlagImportContext context, Map<GruppeImGebietTK, List<Wahlvorschlag>> wahlvorschlaegeProGruppeImGebiet) throws MappingException;
}
