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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.ejb.Stateless;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietPruefkennzeichenGenerierenArt;
import de.ivu.elect.business.gebietsbaum.entity.GebietPruefkennzeichenGenerierenEindeutigkeit;
import de.ivu.elect.jee.exception.ElectCommonException;

@Stateless
public class PruefkennzeichenGenerieren implements Serializable {
  private static final long serialVersionUID = 1L;

  private static final Logger LOG = LoggerFactory.getLogger(PruefkennzeichenGenerieren.class);

  private static final int MAX_TRIES = 1_000_000;

  public Long generierePruefkennzeichen(Set<Gebiet> gebiete, GebietPruefkennzeichenGenerierenArt gebietPruefkennzeichenGenerierenArt,
                                        GebietPruefkennzeichenGenerierenEindeutigkeit gebietPruefkennzeichenGenerierenEindeutigkeit, boolean bestehendeKennzeichenUeberschreiben) {
    if (!gebiete.isEmpty()) {
      List<Set<Gebiet>> eindeutigSet = gebietPruefkennzeichenGenerierenArt
          .gruppiereNachEindeutigkeit(gebietPruefkennzeichenGenerierenEindeutigkeit
              .gruppiereNachEindeutigkeit(gebiete));

      for (Set<Gebiet> gebiets : eindeutigSet) {
        vergebePruefkennzeichenEindeutig(gebiets, gebietPruefkennzeichenGenerierenArt, bestehendeKennzeichenUeberschreiben);
      }

    }
    return (long) gebiete.size();
  }

  private void vergebePruefkennzeichenEindeutig(Set<Gebiet> gebiets, GebietPruefkennzeichenGenerierenArt gebietPruefkennzeichenGenerierenArt, boolean bestehendeKennzeichenUeberschreiben) {

    if (gebietPruefkennzeichenGenerierenArt.getAnzahlVerschiedenerKennzeichenMoeglich() < gebiets.size()) {
      throwKennzeichenReichenNicht(gebietPruefkennzeichenGenerierenArt.getAnzahlVerschiedenerKennzeichenMoeglich(), gebiets.size());
    }

    Set<String> neueKennzeichen = new HashSet<>(gebiets.size() * 2);
    Set<String> vorhandeneKennzeichen = new HashSet<>();

    if (!bestehendeKennzeichenUeberschreiben) {
      vorhandeneKennzeichen = gebiets.stream().filter(gebiet -> StringUtils.isNotBlank(gebiet.getExterneId())).map(Gebiet::getExterneId)
          .collect(Collectors.toCollection(() -> new HashSet<>(gebiets.size() * 2)));
    }

    int count = 0;
    while (neueKennzeichen.size() + vorhandeneKennzeichen.size() < gebiets.size() && count++ < MAX_TRIES) {
      neueKennzeichen.add(gebietPruefkennzeichenGenerierenArt.generiereKennzeichen());
    }

    if (neueKennzeichen.size() + vorhandeneKennzeichen.size() < gebiets.size()) {
      throwKennzeichenReichenNicht(neueKennzeichen.size() + vorhandeneKennzeichen.size(), gebiets.size());
    }

    List<String> kennzeichenAlsListe = new ArrayList<>(neueKennzeichen);
    Collections.shuffle(kennzeichenAlsListe);
    for (Gebiet gebiet : gebiets) {
      if (bestehendeKennzeichenUeberschreiben || StringUtils.isBlank(gebiet.getExterneId())) {
        gebiet.setExterneId(kennzeichenAlsListe.remove(0));
        gebiet.setExterneIdSalt(UUID.randomUUID().toString());
        LOG.info("Pruefkennzeichen vergeben: {}", gebiet);
      }
    }
  }

  private void throwKennzeichenReichenNicht(long kennzeichenSize, int gebietSize) {
    throw new ElectCommonException("gebiet.pruefkennzeichen.generieren.zu.wenige.eindetige.kennzeichen", kennzeichenSize, gebietSize);
  }

}
