package com.airlock.iam.flow.shared.domain.model.session;

import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.flow.shared.api.domain.model.session.UserSessionScopedFlowAttribute;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

public final class UserSessionAttributes {
   private static final ELogger LOG = ELoggerFactory.getLogger(UserSessionAttributes.class);
   private final Map<Class<? extends UserSessionScopedFlowAttribute<?>>, UserSessionScopedFlowAttribute<?>> attributes = new HashMap<>();

   public static UserSessionAttributes initUserSessionAttributes() {
      return new UserSessionAttributes(Collections.emptyList());
   }

   @VisibleForTesting
   static UserSessionAttributes initUserSessionAttributes(UserSessionScopedFlowAttribute<?>... attributes) {
      return new UserSessionAttributes(Arrays.asList(attributes));
   }

   UserSessionAttributes(Collection<UserSessionScopedFlowAttribute<?>> attributes) {
      attributes.forEach(
         a -> this.attributes.put((Class<? extends UserSessionScopedFlowAttribute<?>>)a.getRepositoryKey(), (UserSessionScopedFlowAttribute<?>)a)
      );
   }

   public <T extends UserSessionScopedFlowAttribute<?>> UserSessionAttributes addAttribute(T attribute) {
      Preconditions.checkArgument(attribute != null, "userSessionScopedFlowAttribute must not be null");
      Class<T> key = (Class<T>)attribute.getClass();
      this.attributes.merge(key, attribute, (oldValue, newValue) -> {
         LOG.debug("Overriding the session attribute [" + key + "].");
         return newValue;
      });
      return this;
   }

   public UserSessionAttributes removeAllAttributes() {
      this.attributes.clear();
      return this;
   }

   public boolean removeAttribute(Class<? extends UserSessionScopedFlowAttribute<?>> type) {
      return this.attributes.remove(type) != null;
   }

   public <T extends UserSessionScopedFlowAttribute<?>> Optional<T> findAttribute(Class<T> type) {
      return Optional.ofNullable((T)this.attributes.get(type));
   }

   public Collection<UserSessionScopedFlowAttribute<?>> attributes() {
      return this.attributes.values().stream().map(UserSessionScopedFlowAttribute::copyAttribute).collect(Collectors.toUnmodifiableList());
   }

   @Override
   public String toString() {
      return MoreObjects.toStringHelper(this).add("attributeKeys", this.attributes.keySet()).toString();
   }
}
