content: package com.github.aesteve.vertx.nubes.services;
  
  import com.github.aesteve.vertx.nubes.Config;
  import com.github.aesteve.vertx.nubes.annotations.services.Consumer;
  import com.github.aesteve.vertx.nubes.annotations.services.PeriodicTask;
  import com.github.aesteve.vertx.nubes.annotations.services.Proxify;
  import com.github.aesteve.vertx.nubes.annotations.services.ServiceProxy;
  import com.github.aesteve.vertx.nubes.utils.async.MultipleFutures;
  import io.vertx.codegen.annotations.ProxyGen;
  import io.vertx.core.Future;
  import io.vertx.core.Vertx;
  import io.vertx.core.VertxException;
  import io.vertx.core.eventbus.Message;
  import io.vertx.core.logging.Logger;
  import io.vertx.core.logging.LoggerFactory;
  import io.vertx.serviceproxy.ProxyHelper;
  import io.vertx.serviceproxy.ServiceBinder;
  
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.*;
  
  public class ServiceRegistry {
  
    private static final Logger LOG = LoggerFactory.getLogger(ServiceRegistry.class);
  
    private final Map<String, Object> services;
    private final Map<String, Object> serviceProxies;
    private final Set<Long> timerIds;
    private final Config config;
  
    private final Vertx vertx;
  
    public ServiceRegistry(Vertx vertx, Config config) {
      this.vertx = vertx;
      this.config = config;
      services = new HashMap<>();
      serviceProxies = new HashMap<>();
      timerIds = new HashSet<>();
    }
  
    public void registerService(String name, Object service) {
      services.put(name, service);
    }
  
    public Object get(String name) {
      return services.get(name);
    }
  
    public Object get(Field field) {
      com.github.aesteve.vertx.nubes.annotations.services.Service annot = field.getAnnotation(com.github.aesteve.vertx.nubes.annotations.services.Service.class);
      if (annot != null) {
        return get(annot.value());
      }
      ServiceProxy proxyAnnot = field.getAnnotation(ServiceProxy.class);
      if (proxyAnnot == null) {
        return null;
      }
      Class<?> serviceInterface = getInterface(field.getType());
      if (serviceInterface == null) {
        LOG.error("Could not inject service for : " + field.getName() + " could not find the matching proxified service using @ProxyGen");
        return null;
      }
      String address = proxyAnnot.value();
      if (serviceProxies.get(address) != null) {
        return serviceProxies.get(address);
      } else {
        Object service = createEbProxyClass(serviceInterface, address);
        serviceProxies.put(address, service);
        return service;
      }
    }
  
    public Collection<Object> services() {
      return services.values();
    }
  
    public boolean isEmpty() {
      return services.isEmpty();
    }
  
    public void startAll(Future<Void> future) {
      if (isEmpty()) {
        future.complete();
        return;
      }
      MultipleFutures<Void> futures = new MultipleFutures<>(future);
      futures.addAll(services(), obj -> {
        try {
          introspectService(obj);
        } catch (Exception e) {
          return res -> res.fail(e);
        }
        if (obj instanceof Service) {
          Service service = (Service) obj;
          service.init(vertx, config.json());
          return service::start;
        }
        return null;
      });
      futures.start();
    }
  
    public void stopAll(Future<Void> future) {
      if (isEmpty()) {
        future.complete();
        return;
      }
      timerIds.forEach(vertx::cancelTimer);
      MultipleFutures<Void> futures = new MultipleFutures<>(future);
      futures.addAll(services(), obj -> {
        if (obj instanceof Service) {
          Service service = (Service) obj;
          return service::stop;
        }
        return null;
      });
      futures.start();
    }
  
    private void introspectService(Object service) {
      Class<?> serviceClass = service.getClass();
      Proxify annot = serviceClass.getAnnotation(Proxify.class);
      if (annot != null) {
        createServiceProxy(annot.value(), service);
      }
      for (Method method : service.getClass().getMethods()) {
        PeriodicTask periodicTask = method.getAnnotation(PeriodicTask.class);
        if (periodicTask != null) {
          createPeriodicTask(service, periodicTask, method);
        }
        Consumer consumes = method.getAnnotation(Consumer.class);
        if (consumes != null) {
          createConsumer(service, consumes, method);
        }
      }
    }
  
    private void createConsumer(Object service, Consumer consumes, Method method) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      if (parameterTypes.length != 1 || !parameterTypes[0].equals(Message.class)) {
        String msg = "Cannot register consumer on method : " + getFullName(service, method);
        msg += " .Method should only declare one parameter of io.vertx.core.eventbus.Message type.";
        throw new VertxException(msg);
      }
      vertx.eventBus().consumer(consumes.value(), message -> {
        try {
          method.invoke(service, message);
        } catch (Exception e) {
          LOG.error("Exception happened during message handling on method : " + getFullName(service, method), e);
        }
      });
    }
  
    private void createPeriodicTask(Object service, PeriodicTask annotation, Method method) {
      if (method.getParameterTypes().length > 0) {
        throw new VertxException("Periodic tasks should not have parameters");
      }
      vertx.setPeriodic(annotation.value(), timerId -> {
        timerIds.add(timerId);
        try {
          method.invoke(service);
        } catch (Exception e) {
          LOG.error("Error while running periodic task", e);
        }
      });
    }
  
    private static String getFullName(Object service, Method method) {
      return service.getClass().getName() + "." + method.getName();
    }
  
    private <T> void createServiceProxy(String address, T service) {
      Class<T> serviceClass = getInterface(service.getClass());
      if (serviceClass == null) {
        LOG.error("Could not find a @ProxyGen super interface for class : " + service.getClass().getName() + " cannot proxy it ver the eventBus");
        return;
      }
      new ServiceBinder(vertx)
              .setAddress(address)
              .register(serviceClass, service);
    }
  
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getInterface(Class<?> serviceClass) {
      if (serviceClass.isAnnotationPresent(ProxyGen.class)) {
        return (Class<T>) serviceClass;
      }
      Class<?>[] interfaces = serviceClass.getInterfaces();
      for (Class<?> someInterface : interfaces) {
        if (someInterface.isAnnotationPresent(ProxyGen.class)) { // it must be it
          return (Class<T>) someInterface;
        }
      }
      return null;
    }
  
    public Object createEbProxyClass(Class<?> serviceInterface, String address) {
      String name = serviceInterface.getName() + "VertxEBProxy";
      try {
        return Class.forName(name).getConstructor(Vertx.class, String.class).newInstance(vertx, address);
      } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | ClassNotFoundException e) {
        throw new VertxException("Could not create your service proxy for class : " + serviceInterface, e);
      }
    }
  }
  