package com.airlock.iam.base.infrastructure.factory.guice;

import com.google.common.collect.ImmutableSet;
import com.google.inject.Binding;
import com.google.inject.Key;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

public final class GuiceKeySourceMap {
   private final Map<Key<?>, Set<String>> map;

   static GuiceKeySourceMap emptyMap() {
      return new GuiceKeySourceMap(new HashMap<>());
   }

   GuiceKeySourceMap addAll(Iterable<Binding<?>> bindings) {
      bindings.forEach(this::add);
      return this;
   }

   GuiceKeySourceMap add(Binding<?> binding) {
      this.map.merge(binding.getKey(), Set.of(this.source(binding)), this.appendSet());
      return this;
   }

   GuiceKeySourceMap removeKeysWithUniqueSource() {
      Map<Key<?>, Set<String>> mapWithNonUniqueSources = new HashMap<>(this.map);
      this.map.keySet().stream().filter(this::hasUniqueSource).forEach(mapWithNonUniqueSources::remove);
      return new GuiceKeySourceMap(mapWithNonUniqueSources);
   }

   private BiFunction<Set<String>, Set<String>, Set<String>> appendSet() {
      return (oldSet, newSet) -> ImmutableSet.builder().addAll(oldSet).addAll(newSet).build();
   }

   private String source(Binding<?> binding) {
      return binding.getSource().toString();
   }

   Set<Key<?>> keySet() {
      return Collections.unmodifiableSet(this.map.keySet());
   }

   boolean hasUniqueSource(Key<?> key) {
      return this.valueOrThrow(key).size() == 1;
   }

   public boolean isEmpty() {
      return this.map.isEmpty();
   }

   Set<String> valueOrThrow(Key<?> key) {
      return this.value(key).orElseThrow();
   }

   private Optional<Set<String>> value(Key<?> key) {
      return Optional.ofNullable(this.map.get(key));
   }

   @Override
   public String toString() {
      return this.map
         .keySet()
         .stream()
         .map(key -> key + " defined in:" + this.map.get(key).stream().collect(Collectors.joining(",\n\t", "\n\t", "")))
         .collect(Collectors.joining("\n"));
   }

   @SuppressFBWarnings(justification = "generated code")
   private GuiceKeySourceMap(Map<Key<?>, Set<String>> map) {
      this.map = map;
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public boolean equals(Object o) {
      if (o == this) {
         return true;
      } else if (!(o instanceof GuiceKeySourceMap other)) {
         return false;
      } else {
         Object this$map = this.map;
         Object other$map = other.map;
         return this$map == null ? other$map == null : this$map.equals(other$map);
      }
   }

   @SuppressFBWarnings(justification = "generated code")
   @Override
   public int hashCode() {
      int PRIME = 59;
      int result = 1;
      Object $map = this.map;
      return result * 59 + ($map == null ? 43 : $map.hashCode());
   }
}
