content: package com.github.aesteve.vertx.nubes.routing;
  
  import com.github.aesteve.vertx.nubes.Config;
  import com.github.aesteve.vertx.nubes.annotations.Blocking;
  import com.github.aesteve.vertx.nubes.handlers.Processor;
  import com.github.aesteve.vertx.nubes.handlers.impl.DefaultMethodInvocationHandler;
  import com.github.aesteve.vertx.nubes.handlers.impl.PayloadTypeProcessor;
  import com.github.aesteve.vertx.nubes.reflections.Filter;
  import io.vertx.core.Handler;
  import io.vertx.core.Vertx;
  import io.vertx.core.http.HttpMethod;
  import io.vertx.ext.auth.AuthProvider;
  import io.vertx.ext.web.Router;
  import io.vertx.ext.web.RoutingContext;
  import io.vertx.ext.web.handler.*;
  import io.vertx.ext.web.sstore.LocalSessionStore;
  
  import java.lang.reflect.Method;
  import java.util.LinkedHashSet;
  import java.util.Set;
  import java.util.TreeSet;
  import java.util.function.BiConsumer;
  
  public class MVCRoute {
  
    private final String path;
    private final HttpMethod httpMethod;
    private final Object instance;
    private final Set<Filter> beforeFilters;
    private final Set<Filter> afterFilters;
    private Method mainHandler;
    private final Set<Handler<RoutingContext>> handlers;
    private Set<Processor> processors;
    private MVCRoute redirectRoute;
    private final Handler<RoutingContext> authHandler;
    private String loginRedirect;
    private Handler<RoutingContext> preInterceptor;
    private Handler<RoutingContext> postInterceptor;
    private final Config config;
    private final boolean disabled;
    private BiConsumer<RoutingContext, ?> returnHandler;
    private final boolean usesSession;
  
    public MVCRoute(Object instance, String path, HttpMethod method, Config config, Handler<RoutingContext> authHandler, boolean disabled, final boolean usesSession) {
      this.instance = instance;
      this.config = config;
      this.path = path;
      this.httpMethod = method;
      this.beforeFilters = new TreeSet<>();
      this.afterFilters = new TreeSet<>();
      this.handlers = new LinkedHashSet<>();
      this.processors = new LinkedHashSet<>();
      this.authHandler = authHandler;
      this.disabled = disabled;
      this.usesSession = usesSession;
    }
  
    public boolean isEnabled() {
      return !disabled;
    }
  
    public void redirectTo(MVCRoute anotherRoute) {
      redirectRoute = anotherRoute;
    }
  
    public void setLoginRedirect(String loginRedirect) {
      this.loginRedirect = loginRedirect;
    }
  
    public void addProcessor(Processor processor) {
      processors.add(processor);
    }
  
    public void addProcessors(Set<Processor> processors) {
      this.processors.addAll(processors);
    }
  
    public void addProcessorsFirst(Set<Processor> processors) {
      Set<Processor> oldProcessors = new LinkedHashSet<>(this.processors);
      this.processors = new LinkedHashSet<>(oldProcessors.size() + processors.size());
      this.processors.addAll(processors);
      this.processors.addAll(oldProcessors);
    }
  
    public void attachInterceptor(Handler<RoutingContext> handler, boolean before) {
      if (before) {
        this.preInterceptor = handler;
      } else {
        this.postInterceptor = handler;
      }
    }
  
    public void attachReturnHandler(BiConsumer<RoutingContext, ?> handler) {
      returnHandler = handler;
    }
  
    public void attachHandlers(Set<Handler<RoutingContext>> newHandlers) {
      handlers.addAll(newHandlers);
    }
  
    public void setMainHandler(Method mainHandler) {
      this.mainHandler = mainHandler;
    }
  
    public void addBeforeFilters(Set<Filter> beforeFilters) {
      this.beforeFilters.addAll(beforeFilters);
    }
  
    public void addAfterFilters(Set<Filter> afterFilters) {
      this.afterFilters.addAll(afterFilters);
    }
  
    public String path() {
      return path;
    }
  
    public HttpMethod method() {
      return httpMethod;
    }
  
    public void attachHandlersToRouter(Router router) {
      config.forEachGlobalHandler(handler -> router.route(httpMethod, path).handler(handler));
      final Vertx vertx = config.getVertx();
      if (authHandler != null) {
        attachAuthHandler(router, vertx);
      } else if (usesSession) {
        router.route(httpMethod, path).handler(SessionHandler.create(LocalSessionStore.create(vertx)));
      }
      handlers.forEach(handler ->
        router.route(httpMethod, path).handler(handler)
      );
      attachPreProcessingHandlers(router);
      boolean hasPostProcessors = redirectRoute != null || postInterceptor != null || !afterFilters.isEmpty()|| !processors.isEmpty();
      setHandler(router, mainHandler, hasPostProcessors);
      if (redirectRoute != null) {
        // intercepted -> redirected => do not call post processing handlers
        router.route(httpMethod, path).handler(ctx ->
          ctx.reroute(redirectRoute.method(), redirectRoute.path())
        );
      }
      attachPostProcessingHandlers(router);
    }
  
    private void attachPreProcessingHandlers(Router router) {
      processors.forEach(processor -> router.route(httpMethod, path).handler(processor::preHandle));
      int i = 0;
      boolean beforeFiltersHaveNext = mainHandler != null;
      for (Filter filter : beforeFilters) {
        boolean hasNext = beforeFiltersHaveNext || i < beforeFilters.size() - 1;
        setHandler(router, filter.method(), hasNext);
        i++;
      }
      if (preInterceptor != null) {
        router.route(httpMethod, path).handler(preInterceptor);
      }
    }
  
    private void attachPostProcessingHandlers(Router router) {
      if (postInterceptor != null) {
        router.route(httpMethod, path).handler(postInterceptor);
      }
      int i = 0;
      boolean afterFiltersHaveNext = !processors.isEmpty();
      for (Filter filter : afterFilters) {
        boolean hasNext = afterFiltersHaveNext || i < afterFilters.size() - 1;
        setHandler(router, filter.method(), hasNext);
        i++;
      }
      if (!mainHandler.getReturnType().equals(Void.TYPE) && returnHandler == null) { // try to set as payload
        processors.add(new PayloadTypeProcessor(config.getMarshallers()));
      }
      processors.forEach(processor -> router.route(httpMethod, path).handler(processor::postHandle));
      processors.forEach(processor -> router.route(httpMethod, path).handler(processor::afterAll));
  
    }
  
    private void attachAuthHandler(Router router, Vertx vertx) {
      final AuthProvider authProvider = config.getAuthProvider();
      router.route(httpMethod, path).handler(CookieHandler.create());
      router.route(httpMethod, path).handler(UserSessionHandler.create(authProvider));
      router.route(httpMethod, path).handler(SessionHandler.create(LocalSessionStore.create(vertx)));
      router.route(httpMethod, path).handler(authHandler);
      if (loginRedirect != null && !"".equals(loginRedirect)) {
        router.post(loginRedirect).handler(CookieHandler.create());
        router.post(loginRedirect).handler(BodyHandler.create());
        router.post(loginRedirect).handler(UserSessionHandler.create(authProvider));
        router.post(loginRedirect).handler(SessionHandler.create(LocalSessionStore.create(vertx)));
        router.post(loginRedirect).handler(FormLoginHandler.create(authProvider));
      }
    }
  
    private void setHandler(Router router, Method method, boolean hasNext) {
      Handler<RoutingContext> handler = new DefaultMethodInvocationHandler<>(instance, method, config, hasNext, returnHandler);
      if (method.isAnnotationPresent(Blocking.class)) {
        router.route(httpMethod, path).blockingHandler(handler);
      } else {
        router.route(httpMethod, path).handler(handler);
      }
    }
  
    @Override
    public String toString() {
      return "Route : " + httpMethod.toString() + " " + path();
    }
  }
  