package com.airlock.iam.plugin.framework.misc.config;

import com.airlock.iam.plugin.api.config.ConfigurationContext;
import com.airlock.iam.plugin.api.config.ConfigurationEnvironment;
import com.airlock.iam.plugin.api.exception.InitializeException;
import com.airlock.iam.plugin.api.property.PluginConfigurationProperties;
import com.airlock.iam.plugin.api.property.PropertyConfiguration;
import com.airlock.iam.plugin.framework.application.configuration.MutablePluginConfiguration;
import com.airlock.iam.plugin.impl.cache.PluginClassCache;
import java.lang.reflect.Field;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class EffectivePropertiesWalkerScope implements SubtreeWalkerScope {
   private final ConfigurationEnvironment env;
   private final ConfigurationContext ctx;
   private final boolean excludeInexistentProperties;

   public EffectivePropertiesWalkerScope(ConfigurationEnvironment env, ConfigurationContext ctx) {
      this.env = env;
      this.ctx = ctx;
      this.excludeInexistentProperties = true;
   }

   public EffectivePropertiesWalkerScope(ConfigurationEnvironment env, ConfigurationContext ctx, boolean excludeInexistentProperties) {
      this.env = env;
      this.ctx = ctx;
      this.excludeInexistentProperties = excludeInexistentProperties;
   }

   @Override
   public Stream<MutablePluginConfiguration> getNexts(MutablePluginConfiguration current) {
      PluginConfigurationProperties properties = new PluginConfigurationPropertiesView(current, this.env)
         .inheritFromParent()
         .inheritFromCommonEnvironment()
         .getProperties();
      Set<String> contextSpecificPropertyNames = propertiesForContext(properties, this.ctx)
         .<String>map(PropertyConfiguration::getName)
         .collect(Collectors.toSet());
      Stream<PropertyConfiguration> defaultContextProperties = propertiesForContext(properties, ConfigurationContext.defaultContext())
         .filter(propertyConfiguration -> !contextSpecificPropertyNames.contains(propertyConfiguration.getName()));
      return Stream.concat(propertiesForContext(properties, this.ctx), defaultContextProperties)
         .filter(this.includeProperty(current))
         .flatMap(prop -> prop.getDirectlyReferenced().stream().map(MutablePluginConfiguration.class::cast));
   }

   private static Stream<PropertyConfiguration> propertiesForContext(PluginConfigurationProperties properties, ConfigurationContext ctx) {
      return properties.getContextSpecificProperties(ctx).stream();
   }

   private Predicate<PropertyConfiguration> includeProperty(MutablePluginConfiguration current) {
      return this.excludeInexistentProperties ? prop -> getFieldNames(current).anyMatch(field -> field.equals(prop.getName())) : prop -> true;
   }

   private static Stream<String> getFieldNames(MutablePluginConfiguration current) {
      try {
         return PluginClassCache.collectAllFields(current.getPluginClass()).stream().map(Field::getName);
      } catch (InitializeException e) {
         return Stream.empty();
      }
   }
}
