package com.airlock.iam.common.infrastructure;

import com.airlock.iam.base.infrastructure.reflection.ReflectionHelper;
import com.airlock.iam.common.api.application.configuration.RestApplicationConfiguration;
import com.airlock.iam.common.api.infrastructure.ApplicationComponentProvider;
import com.airlock.iam.common.api.infrastructure.ApplicationExtension;
import java.util.Collection;
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public abstract class RestApplicationDefinition {
   public RestApplicationConfig getRestConfig() {
      Collection<Class<?>> extensions = this.loadExtensions();
      Collection<ApplicationComponentProvider> providers = this.flatMapToApplicationComponentsProviders(extensions);
      return new DefaultRestApplicationConfig(this.applicationName(), providers);
   }

   private Collection<Class<?>> loadExtensions() {
      ServiceLoader<? extends ApplicationExtension> loader = ServiceLoader.load(this.applicationExtensionSpi());
      return loader.stream().map(Provider::type).collect(Collectors.toList());
   }

   private Collection<ApplicationComponentProvider> flatMapToApplicationComponentsProviders(Collection<Class<?>> extensions) {
      return Stream.concat(this.mapToClasspathScanningApplicationComponentsProvider(extensions).stream(), this.mapToApplicationComponentsProvider(extensions))
         .collect(Collectors.toList());
   }

   private Optional<ClasspathScanningApplicationComponentProvider> mapToClasspathScanningApplicationComponentsProvider(Collection<Class<?>> extensions) {
      Collection<RestApplicationConfiguration> annotations = extensions.stream()
         .map(c -> c.getAnnotation(RestApplicationConfiguration.class))
         .flatMap(Stream::ofNullable)
         .collect(Collectors.toList());
      RestApplicationPackages packages = new RestApplicationPackages(annotations);
      return packages.all().length > 0 ? Optional.of(new ClasspathScanningApplicationComponentProvider(this.applicationName(), packages)) : Optional.empty();
   }

   private Stream<ApplicationComponentProvider> mapToApplicationComponentsProvider(Collection<Class<?>> configs) {
      return configs.stream()
         .filter(ApplicationComponentProvider.class::isAssignableFrom)
         .map(ReflectionHelper::newInstance)
         .map(ApplicationComponentProvider.class::cast);
   }

   protected abstract String applicationName();

   protected abstract Class<? extends ApplicationExtension> applicationExtensionSpi();
}
