package com.airlock.iam.common.domain.model.contextdata;

import com.airlock.iam.common.api.domain.model.contextdata.ContextData;
import com.airlock.iam.common.api.domain.model.contextdata.ContextDataItem;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Map.Entry;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public final class ContextDataImpl implements ContextData {
   private static final ELogger LOG = ELoggerFactory.getLogger(ContextDataImpl.class);
   private final Map<String, ContextDataItem<?>> items;

   public static ContextData emptyContextData() {
      return new ContextDataImpl(Collections.emptySet());
   }

   public ContextDataImpl(Set<ContextDataItem<?>> items) {
      this.items = items.stream().collect(Collectors.toMap(ContextDataItem::getKey, e -> e));
   }

   public ContextDataImpl(Map<String, Object> keyValueMap) {
      this.items = keyValueMap.entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> new ContextDataItemImpl<>(e.getKey(), e.getValue())));
   }

   @Override
   public <T> Optional<ContextDataItem<T>> find(String key) {
      return Optional.ofNullable((ContextDataItem<T>)this.items.get(key));
   }

   @Override
   public Set<ContextDataItem<?>> getItems() {
      return Set.copyOf(this.items.values());
   }

   @Override
   public int numberOfItems() {
      return this.items.size();
   }

   @Override
   public Map<String, Object> asKeyValueMap() {
      Map<String, Object> values = new HashMap<>();

      for (ContextDataItem<?> item : this.items.values()) {
         values.put(item.getKey(), item.getValue());
      }

      return values;
   }

   @Override
   public ContextData filter(Predicate<ContextDataItem<?>> predicate) {
      Set<ContextDataItem<?>> filtered = new HashSet<>();

      for (ContextDataItem<?> item : this.items.values()) {
         if (predicate.test(item)) {
            filtered.add(item);
         }
      }

      return new ContextDataImpl(filtered);
   }

   @Override
   public ContextData mergeWith(ContextData other) {
      Set<ContextDataItem<?>> mergedItems = new HashSet<>(this.items.values());

      for (ContextDataItem<?> item : other.getItems()) {
         Optional<ContextDataItem<Object>> currentItem = this.find(item.getKey());
         if (currentItem.isEmpty() || !item.equals(currentItem.get())) {
            LOG.debug(String.format("Updating context-data '%s'", item.getKey()));
            currentItem.ifPresent(mergedItems::remove);
            mergedItems.add(item);
         }
      }

      return new ContextDataImpl(mergedItems);
   }

   @Override
   public ContextData withItem(ContextDataItem<?> contextDataItem) {
      return this.mergeWith(new ContextDataImpl(Set.of(contextDataItem)));
   }

   @Override
   public ContextData withoutItem(String key) {
      return this.filter(item -> !item.getKey().equals(key));
   }

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

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