
package de.ivu.elect.business.gebietsbaum.boundary;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.ejb.Stateless;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;

import de.ivu.elect.business.datei.entity.DateiImportBerichtCustomData;
import de.ivu.elect.business.exchange.control.AbstractEntityImporter;
import de.ivu.elect.business.exchange.control.ImportExchange;
import de.ivu.elect.jee.exchange.Violations;
import de.ivu.elect.business.gebietsbaum.control.GebietsgruppierungImportContext;
import de.ivu.elect.business.gebietsbaum.control.GebietsgruppierungImportExchange;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gebietsbaum.entity.GebietsgruppierungMeta;
import de.ivu.elect.business.gebietsbaum.entity.GebietsgruppierungUK;
import de.ivu.elect.business.wahl.boundary.WahlDAO;
import de.ivu.elect.business.wahl.boundary.WahlterminDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.entity.Meta;
import de.ivu.elect.jee.exchange.ImportOptions;

@Stateless
public class GebietsgruppierungImporter
    extends
      AbstractEntityImporter<Gebietsgruppierung, GebietsgruppierungUK, GebietsgruppierungImportContext, DateiImportBerichtCustomData> {

  @Inject
  private GebietsgruppierungImportExchange importExchange;

  @Inject
  private GebietsgruppierungMeta meta;

  @Inject
  private GebietsgruppierungDAO gebietsgruppierungDAO;

  @Inject
  private GebietDAO gebietDAO;

  @Inject
  private WahlDAO wahlDAO;

  @Inject
  private WahlterminDAO wahlterminDAO;

  public GebietsgruppierungImporter() {
    super();
  }

  @Override
  protected ImportExchange<List<Gebietsgruppierung>, GebietsgruppierungImportContext> getExchange() {
    return importExchange;
  }

  @Override
  protected void validiere(Violations violations, List<Gebietsgruppierung> entities,
                           GebietsgruppierungImportContext context, ImportOptions importOptions) {
    for (Gebietsgruppierung entity : entities) {
      validateEntity(violations, entity, entity.getGebietsgruppierungTK());
    }

    // TODO  Klären: Hier noch mehr validieren?
    // TODO  Prüfungen aus DefaultGebietsgruppierungCsvImportMapper.validateInputFormat() hierhin verschieben
  }

  @Override
  public Meta getMetaForEntityInternal() {
    return meta;
  }

  @Override
  protected void persistiere(Collection<Gebietsgruppierung> nurInDateiNichtInDatenbank,
      GebietsgruppierungImportContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData,
      StringBuilder bericht) {
    Wahl wahl = wahlDAO.attach(context.getWahl());
    Wahltermin wahltermin = wahlterminDAO.attach(context.getWahltermin());

    Map<GebietKey, Gebiet> key2Gebiet = mappeGebieteNachKey(context);

    for (Gebietsgruppierung gebietsgruppierung : nurInDateiNichtInDatenbank) {
      gebietsgruppierung.setWahltermin(wahltermin);
      gebietsgruppierung.setWahl(wahl);

      List<Gebiet> persistenteGebiete = getPersistenteGebiete(key2Gebiet, gebietsgruppierung);
      gebietsgruppierung.removeAllGebiets();
      for (Gebiet persistentesGebiet : persistenteGebiete) {
        gebietsgruppierung.addGebiet(persistentesGebiet);
      }
      gebietsgruppierungDAO.persist(gebietsgruppierung);
    }
  }

  private List<Gebiet> getPersistenteGebiete(Map<GebietKey, Gebiet> key2Gebiet, Gebietsgruppierung gebietsgruppierung) {
    List<Gebiet> persistenteGebiete = new ArrayList<>();

    for (Gebiet gebiet : gebietsgruppierung.getGebiets()) {
      GebietKey gebietKey = getGebietKey(gebiet);
      Gebiet persistentesGebiet = key2Gebiet.get(gebietKey);
      if (persistentesGebiet != null) {
        persistenteGebiete.add(persistentesGebiet);
      }
    }
    return persistenteGebiete;
  }

  private GebietKey getGebietKey(Gebiet gebiet) {
    String gebietsartName = gebiet.getGebietsart() == null ? StringUtils.EMPTY : gebiet.getGebietsart().getName();
    return new GebietKey(gebietsartName, gebiet.getNummer());
  }

  private Map<GebietKey, Gebiet> mappeGebieteNachKey(GebietsgruppierungImportContext context) {
    Map<GebietKey, Gebiet> result = new HashMap<>();
    for (Gebiet gebiet : getGebiete(context)) {
      result.put(getGebietKey(gebiet), gebiet);
    }

    return result;
  }

  @Override
  protected void aktualisiere(Gebietsgruppierung gebietsgruppierungInDatei, Gebietsgruppierung gebietsgruppierungenInDB,
      GebietsgruppierungImportContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData,
      StringBuilder bericht) {
    aktualisiereGebietsgruppierung(gebietsgruppierungInDatei, gebietsgruppierungenInDB, context);
  }

  private void aktualisiereGebietsgruppierung(Gebietsgruppierung gebietsgruppierungInDatei,
      Gebietsgruppierung gebietsgruppierungenInDB, GebietsgruppierungImportContext context) {
    Map<GebietKey, Gebiet> key2Gebiet = mappeGebieteNachKey(context);
    List<Gebiet> persistenteGebiete = getPersistenteGebiete(key2Gebiet, gebietsgruppierungInDatei);
    gebietsgruppierungenInDB.removeAllGebiets();
    for (Gebiet gebiet : persistenteGebiete) {
      gebietsgruppierungenInDB.addGebiet(gebiet);
    }
  }

  @Override
  protected void loescheAusDatenbank(Collection<Gebietsgruppierung> entitiesInDb,
      GebietsgruppierungImportContext context, DateiImportBerichtCustomData dateiImportBerichtCustomData,
      StringBuilder bericht) {
    for (Gebietsgruppierung gebietsgruppierung : entitiesInDb) {
      gebietsgruppierungDAO.remove(gebietsgruppierung);
    }
  }

  @Override
  protected List<Gebietsgruppierung> getAllEntitiesInDB(GebietsgruppierungImportContext context) {
    if (context.getWahl() == null) {
      return gebietsgruppierungDAO.findByWahltermin(context.getWahltermin());
    }
    return gebietsgruppierungDAO.findByWahl(context.getWahl());
  }

  @Override
  protected Function<Gebietsgruppierung, GebietsgruppierungUK> getEntityToEntityTKFunction() {
    return gg -> new GebietsgruppierungUK(gg.getGebietsgruppierungArt(), gg.getName(), null, null);
  }

  @Override
  protected void erstelleBericht(List<Gebietsgruppierung> importObject, GebietsgruppierungImportContext context,
      ImportOptions importOptions, StringBuilder bericht, boolean vorab) {
    // nichts zu tun
  }

  private List<Gebiet> getGebiete(GebietsgruppierungImportContext context) {
    Wahl wahl = wahlDAO.attach(context.getWahl());
    if (wahl != null) {
      return gebietDAO.findByWahl(wahl);
    } else {
      Wahltermin wahltermin = wahlterminDAO.attach(context.getWahltermin());
      return gebietDAO.findByWahltermin(wahltermin);
    }
  }

  /**
   * Fachlicher Schlüssel für ein Gebiet
   */
  static class GebietKey {

    String gebietsartName;
    String nummer;

    public GebietKey(String gebietsartName, String nummer) {
      this.gebietsartName = gebietsartName;
      this.nummer = nummer;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      GebietKey gebietKey = (GebietKey) o;

      if (gebietsartName != null ? !gebietsartName.equals(gebietKey.gebietsartName) : gebietKey.gebietsartName != null)
        return false;
      return nummer != null ? nummer.equals(gebietKey.nummer) : gebietKey.nummer == null;

    }

    @Override
    public int hashCode() {
      int result = gebietsartName != null ? gebietsartName.hashCode() : 0;
      result = 31 * result + (nummer != null ? nummer.hashCode() : 0);
      return result;
    }
  }
}
