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

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import java.util.stream.Collectors;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;

public class VerwandschaftZwischenGebietenBerechner {

  public Map<Gebiet, Map<Gebiet, Long>> berechneVerwandschaft (Set<Gebiet> alleGebiete) {
    Map<Gebiet, Map<Gebiet, Long>> verwandschaftsgrad = new HashMap<>();

    Map<Gebiet, Set<Gebiet>> untergeordneteGebiete = berechneUntergeordneteGebiete(alleGebiete.stream().filter(Objects::nonNull).collect(Collectors.toSet()));

    for (Gebiet gebiet: alleGebiete) {
      Map<Gebiet, Long> verwandschaftFuerGebiet = new HashMap<>();
      bfs(gebiet, untergeordneteGebiete, verwandschaftFuerGebiet);
      makeVerwandschaftZwischenGebietUndSichSelbstOderSeinenUebergeordnetenGebietenNull(gebiet, verwandschaftFuerGebiet);
      verwandschaftsgrad.put(gebiet, verwandschaftFuerGebiet);
    }

    return verwandschaftsgrad;
  }

  private Map<Gebiet, Set<Gebiet>> berechneUntergeordneteGebiete(Collection<Gebiet> alleGebiete) {
    Map<Gebiet, Set<Gebiet>> result = new HashMap<>();
    for (Gebiet gebiet: alleGebiete) {
      Gebiet uebergeordnetesGebiet = gebiet.getUebergeordnetesGebiet();
      if (uebergeordnetesGebiet == null) {
        continue;
      }
      result.putIfAbsent(uebergeordnetesGebiet, new HashSet<>());
      result.get(uebergeordnetesGebiet).add(gebiet);
    }
    alleGebiete.forEach(gebiet -> result.putIfAbsent(gebiet, new HashSet<>()));
    return result;
  }

  private void makeVerwandschaftZwischenGebietUndSichSelbstOderSeinenUebergeordnetenGebietenNull(Gebiet currentGebiet, Map<Gebiet, Long> verwandschaftFuerGebiet) {
    while (currentGebiet != null) {
      verwandschaftFuerGebiet.put(currentGebiet, null);
      currentGebiet = currentGebiet.getUebergeordnetesGebiet();
    }
  }

  private void bfs(Gebiet currentGebiet, Map<Gebiet, Set<Gebiet>> untergeordneteGebieteByGebiet, Map<Gebiet, Long> verwandschaftFuerGebiet) {
    Queue<Gebiet> queue = new LinkedList<>();
    queue.add(currentGebiet);
    verwandschaftFuerGebiet.put(currentGebiet, 0L);

    while (!queue.isEmpty()) {
      currentGebiet = queue.remove();
      long currentVerwandschaftsgrad = verwandschaftFuerGebiet.get(currentGebiet);

      Gebiet uebergeordnetesGebiet = currentGebiet.getUebergeordnetesGebiet();
      if (uebergeordnetesGebiet != null && !verwandschaftFuerGebiet.containsKey(uebergeordnetesGebiet)) {
        verwandschaftFuerGebiet.put(uebergeordnetesGebiet, currentVerwandschaftsgrad + 1);
        queue.add(uebergeordnetesGebiet);
      }

      Set<Gebiet> untergeordneteGebiete = untergeordneteGebieteByGebiet.get(currentGebiet);
      if (untergeordneteGebiete == null) {
        continue;
      }
      for (Gebiet untergeordnetesGebiet: untergeordneteGebiete) {
        if (!verwandschaftFuerGebiet.containsKey(untergeordnetesGebiet)) {
          verwandschaftFuerGebiet.put(untergeordnetesGebiet, currentVerwandschaftsgrad + 1);
          queue.add(untergeordnetesGebiet);
        }
      }

    }
  }
}
