
package de.ivu.elect.wus.exchange.stimmzettel;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.groupingBy;

import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

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

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.GebietDAO;
import de.ivu.elect.business.gebietsbaum.boundary.WahlverwaltungGebietInformation;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.stimmzettel.boundary.EntityAufStimmzettelDatenDAOFactory;
import de.ivu.elect.business.stimmzettel.control.StimmzettelExportContext;
import de.ivu.elect.business.stimmzettel.control.StimmzettelExportObject;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelDaten;
import de.ivu.elect.business.stimmzettel.entity.StimmzettelDatenFactory;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.core.exchange.gebietsbaum.WahlverwaltungGebietInformationCollector;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;

public abstract class AbstractStimmzettelZipExportMapper implements StimmzettelExportMapper {

  @Inject
  private transient GebietDAO gebietDAO;

  @Inject
  private transient WahlverwaltungGebietInformationCollector wahlverwaltungGebietInformationCollector;

  @Inject
  private transient EntityAufStimmzettelDatenDAOFactory entityAufStimmzettelDatenDAOFactory;

  @Inject
  private transient StimmzettelDatenFactory stimmzettelDatenFactory;

  protected AbstractStimmzettelZipExportMapper() {
    super();
  }

  @Override
  public boolean isAcceptContext(StimmzettelExportContext context) {
    return context.getWahl() != null && getSimpleExportMapper().isAcceptContext(context);
  }

  protected boolean isAcceptStimmzettelDaten(WahlverwaltungGebietInformation stimmzettelgebietInformation, StimmzettelDaten stimmzettelDaten) {
    return getSimpleExportMapper().isAcceptStimmzettelDaten(stimmzettelgebietInformation, stimmzettelDaten);
  }

  @Override
  public FileFormat getFileFormat() {
    return new FileFormat(getFileFormatName(), getFileType(), getSimpleExportMapper().getFileType());
  }

  protected abstract String getFileFormatName();

  public FileType getFileType() {
    return FileType.ZIP;
  }

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

    getSimpleExportMapper().addProperties(formBuilder, context);
  }

  @Override
  public void serialize(StimmzettelExportObject exportObject, OutputStream os, StimmzettelExportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    try {
      ZipOutputStream zos = new ZipOutputStream(os);

      Collection<Wahlvorschlag> wahlvorschlaege = exportObject.getWahlvorschlaege();

      SimpleStimmzettelForZipExportMapper simpleExportMapper = getSimpleExportMapper();

      FileType zipEntryFileType = getSimpleExportMapper().getTargetFileType(context);
      int zipEntryCount = 0;

      for (Entry<WahlverwaltungGebietInformation, List<Entry<WahlverwaltungGebietInformation, StimmzettelDaten>>> entryPerAuswertungsgebiet : collectStimmzettelDatenPerStimmzettelgebietPerAuswertungsgebiet(
          context, wahlvorschlaege).entrySet()) {
        zipEntryCount++;

        WahlverwaltungGebietInformation auswertungsgebietInformation = entryPerAuswertungsgebiet.getKey();

        // NOTE, dass bspw. bei der EUW ggf. weder Stimm- noch Wahlkreisnummer hinterlegt ist und deshalb ein generischer Dateiname erzeugt wird!
        String zipEntryFileName = getZipEntryFileNamePrefix() + auswertungsgebietInformation.createFileNameOrDefaultByIndex(fileKonfiguration, zipEntryFileType, zipEntryCount);
        zos.putNextEntry(new ZipEntry(zipEntryFileName));

        simpleExportMapper.serialize(zos, context, getFileFormatName(), auswertungsgebietInformation, entryPerAuswertungsgebiet.getValue(), false);

        zos.closeEntry();
      }

      simpleExportMapper.addResources(zos, context);

      zos.finish();
    } catch (Exception ex) {
      throw new MappingException(ex.getMessage(), ex);
    }
  }

  protected String getZipEntryFileNamePrefix() {
    return "";
  }

  private Map<WahlverwaltungGebietInformation, List<Entry<WahlverwaltungGebietInformation, StimmzettelDaten>>> collectStimmzettelDatenPerStimmzettelgebietPerAuswertungsgebiet(
      StimmzettelExportContext context, Collection<Wahlvorschlag> wahlvorschlaege) {
    Map<WahlverwaltungGebietInformation, List<Entry<WahlverwaltungGebietInformation, StimmzettelDaten>>> result = new HashMap<>();

    Map<Gebiet, List<Wahlvorschlag>> wahlvorschlaegePerGebiet = wahlvorschlaege.stream().collect(groupingBy(Wahlvorschlag::getGebiet));
    Map<WahlverwaltungGebietInformation, StimmzettelDaten> stimmzettelDatenPerStimmzettelgebiet = new HashMap<>();

    for (Entry<WahlverwaltungGebietInformation, SortedMap<WahlverwaltungGebietInformation, List<Gebiet>>> entryPerStimmzettelgebiet : collectInGebietshierarchieUntersteGebieteGroupedByDefault(context)
        .entrySet()) {
      WahlverwaltungGebietInformation stimmzettelgebietInformation = entryPerStimmzettelgebiet.getKey();

      Gebiet gebietBeliebigInStimmzettelgebiet = null;
      Set<Wahlvorschlag> wahlvorschlaegeInStimmzettelgebiet = new HashSet<>();

      for (Entry<WahlverwaltungGebietInformation, List<Gebiet>> entryPerGemeinde : entryPerStimmzettelgebiet.getValue().entrySet()) {
        List<Gebiet> gebiete = entryPerGemeinde.getValue();

        if (!gebiete.isEmpty()) {
          gebietBeliebigInStimmzettelgebiet = gebiete.get(0);

          for (Entry<Gebiet, List<Wahlvorschlag>> entryPerGebiet : wahlvorschlaegePerGebiet.entrySet()) {
            wahlvorschlaegeInStimmzettelgebiet.addAll(entryPerGebiet.getValue());
          }
        }
      }

      if (gebietBeliebigInStimmzettelgebiet != null && !wahlvorschlaegeInStimmzettelgebiet.isEmpty()) {
        StimmzettelDaten stimmzettelDaten = createStimmzettelDaten(gebietBeliebigInStimmzettelgebiet, wahlvorschlaegeInStimmzettelgebiet);

        if (isAcceptStimmzettelDaten(stimmzettelgebietInformation, stimmzettelDaten)) {
          stimmzettelDatenPerStimmzettelgebiet.put(stimmzettelgebietInformation, stimmzettelDaten);
        }
      }
    }

    if (context.isTargetCreatedPerWurzelgebiet()) { // Alles in eine Datei!
      result.put(wahlverwaltungGebietInformationCollector.getWurzelgebiet(context.getWahl()), new ArrayList<>(stimmzettelDatenPerStimmzettelgebiet.entrySet()));
    } else {
      for (Entry<WahlverwaltungGebietInformation, StimmzettelDaten> entry : stimmzettelDatenPerStimmzettelgebiet.entrySet()) {
        result.put(entry.getKey(), asList(entry));
      }
    }

    return result;
  }

  protected SortedMap<WahlverwaltungGebietInformation, SortedMap<WahlverwaltungGebietInformation, List<Gebiet>>> collectInGebietshierarchieUntersteGebieteGroupedByDefault(
      StimmzettelExportContext context) {
    Predicate<Gebiet> gebietPredicate = gebiet -> !gebiet.hasUntergeordneteGebiete() && !context.collectEntitiesExportierbar(gebietDAO, singletonList(gebiet)).isEmpty(); // WASDE-1953

    return wahlverwaltungGebietInformationCollector.collectGebieteGroupedByDefault(context.getWahl(), true, gebietPredicate,
        (stimmzettelgebietInformation, gemeindeInformation, gebietDescriptor) -> gebietDescriptor.getGebiet());
  }

  private StimmzettelDaten createStimmzettelDaten(Gebiet gebiet, Collection<Wahlvorschlag> wahlvorschlaege) {
    return stimmzettelDatenFactory.create(entityAufStimmzettelDatenDAOFactory.getEntityAufStimmzettelDaten(), gebiet, wahlvorschlaege);
  }

  protected abstract SimpleStimmzettelForZipExportMapper getSimpleExportMapper();
}
