
package de.ivu.elect.exchange.gebietsbaum.csv;

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

import javax.inject.Inject;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

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.GebietFactory;
import de.ivu.elect.business.gebietsbaum.control.GebietsgruppierungImportContext;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsgruppierung;
import de.ivu.elect.business.gebietsbaum.entity.GebietsgruppierungUK;
import de.ivu.elect.business.gebietsbaum.entity.Verwaltungsart;
import de.ivu.elect.exchange.CsvImportMapper;
import de.ivu.elect.exchange.csv.CsvMarshaller;
import de.ivu.elect.exchange.gebietsbaum.GebietsgruppierungImportMapper;
import de.ivu.elect.jee.exchange.Violations;

/**
 * Hat die Aufgabe, die in der CSV-Datei enthaltenen Gebietsgruppierung in Entitäten umzuwandeln (s. #deserialize()). Diese sind dann noch nicht persistent und werden vor dem Import mit denen in der
 * Datenbank abgeglichen (das macht der Importer).
 */
public class DefaultGebietsgruppierungCsvImportMapper implements GebietsgruppierungImportMapper, CsvImportMapper<List<Gebietsgruppierung>, GebietsgruppierungImportContext> {

  @Inject
  private transient CsvMarshaller csvMarshaller;

  @Inject
  private transient GebietFactory gebietFactory;

  public DefaultGebietsgruppierungCsvImportMapper() {
    super();
  }

  @Override
  public List<String> getCsvColumnHeaders(GebietsgruppierungImportContext context) {
    return csvMarshaller.extractHeader(DefaultGebietsgruppierungCsvDTO.class);
  }

  /**
   * @return die in der CSV enthaltenen {@link Gebietsgruppierung}en.
   */
  @Override
  public List<Gebietsgruppierung> deserialize(InputStream is, GebietsgruppierungImportContext context, FileKonfiguration fileKonfiguration) throws MappingException {
    return mappeDtoToEntity(deserialize(is, fileKonfiguration));
  }

  private List<DefaultGebietsgruppierungCsvDTO> deserialize(InputStream is, FileKonfiguration fileKonfiguration) {
    return csvMarshaller.deserialize(DefaultGebietsgruppierungCsvDTO.class, is, fileKonfiguration);
  }

  private List<Gebietsgruppierung> mappeDtoToEntity(List<DefaultGebietsgruppierungCsvDTO> deserialize) {
    Map<GebietsgruppierungUK, Gebietsgruppierung> key2Gebietsgruppierung = new HashMap<>();

    for (DefaultGebietsgruppierungCsvDTO dto : deserialize) {
      if (StringUtils.isNotBlank(dto.getGebietsgruppierungArt()) && StringUtils.isNotBlank(dto.getGebietsgruppierungName()) && StringUtils.isNotBlank(
          dto.getGebietsartName())
          && StringUtils.isNotBlank(dto.getGebietNummer())) {
        GebietsgruppierungUK key = toGebietsgruppierungUK(dto);
        if (!key2Gebietsgruppierung.containsKey(key)) {
          Gebietsgruppierung gebietsgruppierung = new Gebietsgruppierung();
          gebietsgruppierung.setName(dto.getGebietsgruppierungName());
          gebietsgruppierung.setNummer(dto.getGebietsgruppierungNummer());
          gebietsgruppierung.setGebietsgruppierungArt(dto.getGebietsgruppierungArt());
          gebietsgruppierung.setPosition(dto.getPosition());
          if (StringUtils.isBlank(dto.getVerwaltungsart())) {
            gebietsgruppierung.setVerwaltungsart(Verwaltungsart.fromString(dto.getGebietsgruppierungArt()));
          } else {
            gebietsgruppierung.setVerwaltungsart(Verwaltungsart.fromString(dto.getVerwaltungsart()));
          }
          key2Gebietsgruppierung.put(key, gebietsgruppierung);
        }
        Gebiet gebiet = gebietFactory.create();
        Gebietsart gebietsart = new Gebietsart();
        gebietsart.setName(dto.getGebietsartName());
        gebiet.setGebietsart(gebietsart);
        gebiet.setNummer(dto.getGebietNummer());
        key2Gebietsgruppierung.get(key).addGebiet(gebiet);
      }
    }

    return new ArrayList<>(key2Gebietsgruppierung.values());
  }

  private GebietsgruppierungUK toGebietsgruppierungUK(DefaultGebietsgruppierungCsvDTO dto) {
    return new GebietsgruppierungUK(dto.getGebietsgruppierungArt(), dto.getGebietsgruppierungName(), null, null);
  }

  /**
   * TODO: Klären, was hier geprüft werden muss:
   * <ul>
   * <li>Alle Gebiete existieren</li>
   * <li>Gebietsart und Position sind eindeutig</li>
   * </ul>
   */
  @Override
  public Violations validateInputFormat(InputStream is, GebietsgruppierungImportContext context, FileKonfiguration fileKonfiguration) {
    Violations violations = new Violations();
    List<DefaultGebietsgruppierungCsvDTO> dtos = deserialize(is, fileKonfiguration);

    // TODO: Diese Prüfungen sollten wohl besser beim GebietsgruppierungImporter.validiere() implementiert werden
    // Prüft, dass zu jedem Paar (Art und Name) die Position immer gleich ist
    Map<GebietsgruppierungUK, Long> key2position = new HashMap<>();
    for (DefaultGebietsgruppierungCsvDTO dto : dtos) {
      GebietsgruppierungUK key = toGebietsgruppierungUK(dto);
      Long position = dto.getPosition();
      Long previousPosition = key2position.put(key, position);
      if (previousPosition != null && ObjectUtils.notEqual(position, previousPosition)) {
        violations.addError("gebietsgruppierungen.import.position.mehrdeutig", key.getGebietsgruppierungArt(), key.getName(), position, previousPosition);
      }
    }

    // Prüft, dass zu jedem Paar (Art und Position) der Name immer gleich ist
    Map<String, Map<Long, String>> art2Position2Name = new HashMap<>();
    for (DefaultGebietsgruppierungCsvDTO dto : dtos) {
      String art = dto.getGebietsgruppierungArt();
      Map<Long, String> position2Name = art2Position2Name.computeIfAbsent(art, egal -> new HashMap<>());
      String name = dto.getGebietsgruppierungName();
      String previousName = position2Name.put(dto.getPosition(), name);
      if (previousName != null && ObjectUtils.notEqual(name, previousName)) {
        violations.addError("gebietsgruppierungen.import.name.mehrdeutig", art, dto.getPosition(), name, previousName);
      }
    }

    return violations;
  }

  @Override
  public FileFormat getFileFormat() {
    return new FileFormat(DefaultGebietsgruppierungCsvExportMapper.FORMAT_NAME, FileType.CSV);
  }

  @Override
  public boolean isAcceptContext(GebietsgruppierungImportContext context) {
    return context.getWahl() != null || context.getWahltermin() != null;
  }
}
