/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.jooby;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.typesafe.config.ConfigValueFactory.fromAnyRef;
import static java.util.Objects.requireNonNull;
import static org.jooby.Route.CONNECT;
import static org.jooby.Route.DELETE;
import static org.jooby.Route.GET;
import static org.jooby.Route.HEAD;
import static org.jooby.Route.OPTIONS;
import static org.jooby.Route.PATCH;
import static org.jooby.Route.POST;
import static org.jooby.Route.PUT;
import static org.jooby.Route.TRACE;

import java.io.File;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.inject.Singleton;
import javax.net.ssl.SSLContext;

import org.jooby.Route.Definition;
import org.jooby.Route.Mapper;
import org.jooby.Session.Store;
import org.jooby.handlers.AssetHandler;
import org.jooby.internal.AppPrinter;
import org.jooby.internal.BuiltinParser;
import org.jooby.internal.BuiltinRenderer;
import org.jooby.internal.CookieSessionManager;
import org.jooby.internal.DefaulErrRenderer;
import org.jooby.internal.HttpHandlerImpl;
import org.jooby.internal.JvmInfo;
import org.jooby.internal.LocaleUtils;
import org.jooby.internal.ParameterNameProvider;
import org.jooby.internal.RequestScope;
import org.jooby.internal.RouteMetadata;
import org.jooby.internal.ServerExecutorProvider;
import org.jooby.internal.ServerLookup;
import org.jooby.internal.ServerSessionManager;
import org.jooby.internal.SessionManager;
import org.jooby.internal.TypeConverters;
import org.jooby.internal.handlers.HeadHandler;
import org.jooby.internal.handlers.OptionsHandler;
import org.jooby.internal.handlers.TraceHandler;
import org.jooby.internal.mvc.MvcRoutes;
import org.jooby.internal.mvc.MvcWebSocket;
import org.jooby.internal.parser.BeanParser;
import org.jooby.internal.parser.DateParser;
import org.jooby.internal.parser.LocalDateParser;
import org.jooby.internal.parser.LocaleParser;
import org.jooby.internal.parser.ParserExecutor;
import org.jooby.internal.parser.StaticMethodParser;
import org.jooby.internal.parser.StringConstructorParser;
import org.jooby.internal.ssl.SslContextProvider;
import org.jooby.mvc.Consumes;
import org.jooby.mvc.Produces;
import org.jooby.scope.Providers;
import org.jooby.scope.RequestScoped;
import org.jooby.spi.HttpHandler;
import org.jooby.spi.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.escape.Escaper;
import com.google.common.html.HtmlEscapers;
import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.inject.Binder;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Named;
import com.google.inject.name.Names;
import com.google.inject.util.Types;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
import com.typesafe.config.ConfigValueFactory;

import javaslang.Predicates;
import javaslang.control.Try;
import javaslang.control.Try.CheckedConsumer;
import javaslang.control.Try.CheckedRunnable;

/**
 * <h1>jooby</h1>
 * <h2>getting started</h2>
 *
 * <pre>
 * public class MyApp extends Jooby {
 *
 *   {
 *      use(new Jackson()); // 1. JSON serializer.
 *
 *      // 2. Define a route
 *      get("/", req {@literal ->} {
 *        Map{@literal <}String, Object{@literal >} model = ...;
 *        return model;
 *      }
 *   }
 *
 *  public static void main(String[] args) {
 *    run(MyApp::new, args); // 3. Done!
 *  }
 * }
 * </pre>
 *
 * <h2>application.conf</h2>
 * <p>
 * Jooby delegate configuration management to <a
 * href="https://github.com/typesafehub/config">TypeSafe Config</a>.
 * </p>
 *
 * <p>
 * By default Jooby looks for an <code>application.conf</code>. If
 * you want to specify a different file or location, you can do it with {@link #conf(String)}.
 * </p>
 *
 * <p>
 * <a href="https://github.com/typesafehub/config">TypeSafe Config</a> uses a hierarchical model to
 * define and override properties.
 * </p>
 * <p>
 * A {@link Jooby.Module} might provides his own set of properties through the
 * {@link Jooby.Module#config()} method. By default, this method returns an empty config object.
 * </p>
 * For example:
 *
 * <pre>
 *   use(new M1());
 *   use(new M2());
 *   use(new M3());
 * </pre>
 *
 * Previous example had the following order (first-listed are higher priority):
 * <ul>
 * <li>arguments properties</li>
 * <li>System properties</li>
 * <li>application.conf</li>
 * <li>M3 properties</li>
 * <li>M2 properties</li>
 * <li>M1 properties</li>
 * </ul>
 * <p>
 * Command line argmuents or system properties takes precedence over any application specific
 * property.
 * </p>
 *
 * <h2>env</h2>
 * <p>
 * Jooby defines one mode or environment: <strong>dev</strong>. In Jooby, <strong>dev</strong>
 * is special and some modules could apply special settings while running in <strong>dev</strong>.
 * Any other env is usually considered a <code>prod</code> like env. But that depends on module
 * implementor.
 * </p>
 * <p>
 * An <code>environment</code> can be defined in your <code>.conf</code> file using the
 * <code>application.env</code> property. If missing, Jooby set the <code>env</code> for you to
 * <strong>dev</strong>.
 * </p>
 * <p>
 * There is more at {@link Env} please read the {@link Env} javadoc.
 * </p>
 *
 * <h2>modules: the jump to full-stack framework</h2>
 * <p>
 * {@link Jooby.Module Modules} are quite similar to a Guice modules except that the configure
 * callback has been complementing with {@link Env} and {@link Config}.
 * </p>
 *
 * <pre>
 *   public class MyModule implements Jooby.Module {
 *     public void configure(Env env, Config config, Binder binder) {
 *     }
 *   }
 * </pre>
 *
 * From the configure callback you can bind your services as you usually do in a Guice app.
 * <p>
 * There is more at {@link Jooby.Module} so please read the {@link Jooby.Module} javadoc.
 * </p>
 *
 * <h2>path patterns</h2>
 * <p>
 * Jooby supports Ant-style path patterns:
 * </p>
 * <p>
 * Some examples:
 * </p>
 * <ul>
 * <li>{@code com/t?st.html} - matches {@code com/test.html} but also {@code com/tast.html} or
 * {@code com/txst.html}</li>
 * <li>{@code com/*.html} - matches all {@code .html} files in the {@code com} directory</li>
 * <li><code>com/{@literal **}/test.html</code> - matches all {@code test.html} files underneath the
 * {@code com} path</li>
 * <li>{@code **}/{@code *} - matches any path at any level.</li>
 * <li>{@code *} - matches any path at any level, shorthand for {@code **}/{@code *}.</li>
 * </ul>
 *
 * <h3>variables</h3>
 * <p>
 * Jooby supports path parameters too:
 * </p>
 * <p>
 * Some examples:
 * </p>
 * <ul>
 * <li><code> /user/{id}</code> - /user/* and give you access to the <code>id</code> var.</li>
 * <li><code> /user/:id</code> - /user/* and give you access to the <code>id</code> var.</li>
 * <li><code> /user/{id:\\d+}</code> - /user/[digits] and give you access to the numeric
 * <code>id</code> var.</li>
 * </ul>
 *
 * <h2>routes</h2>
 * <p>
 * Routes perform actions in response to a server HTTP request.
 * </p>
 * <p>
 * Routes are executed in the order they are defined, for example:
 * </p>
 *
 * <pre>
 *   get("/", (req, rsp) {@literal ->} {
 *     log.info("first"); // start here and go to second
 *   });
 *
 *   get("/", (req, rsp) {@literal ->} {
 *     log.info("second"); // execute after first and go to final
 *   });
 *
 *   get("/", (req, rsp) {@literal ->} {
 *     rsp.send("final"); // done!
 *   });
 * </pre>
 *
 * Previous example can be rewritten using {@link Route.Filter}:
 *
 * <pre>
 *   get("/", (req, rsp, chain) {@literal ->} {
 *     log.info("first"); // start here and go to second
 *     chain.next(req, rsp);
 *   });
 *
 *   get("/", (req, rsp, chain) {@literal ->} {
 *     log.info("second"); // execute after first and go to final
 *     chain.next(req, rsp);
 *   });
 *
 *   get("/", (req, rsp) {@literal ->} {
 *     rsp.send("final"); // done!
 *   });
 * </pre>
 *
 * Due to the use of lambdas a route is a singleton and you should NOT use global variables. For
 * example this is a bad practice:
 *
 * <pre>
 *  List{@literal <}String{@literal >} names = new ArrayList{@literal <}{@literal >}(); // names produces side effects
 *  get("/", (req, rsp) {@literal ->} {
 *     names.add(req.param("name").value();
 *     // response will be different between calls.
 *     rsp.send(names);
 *   });
 * </pre>
 *
 * <h3>mvc routes</h3>
 * <p>
 * A Mvc route use annotations to define routes:
 * </p>
 *
 * <pre>
 *   use(MyRoute.class);
 *   ...
 *
 *   // MyRoute.java
 *   {@literal @}Path("/")
 *   public class MyRoute {
 *
 *    {@literal @}GET
 *    public String hello() {
 *      return "Hello Jooby";
 *    }
 *   }
 * </pre>
 * <p>
 * Programming model is quite similar to JAX-RS/Jersey with some minor differences and/or
 * simplifications.
 * </p>
 *
 * <p>
 * To learn more about Mvc Routes, please check {@link org.jooby.mvc.Path},
 * {@link org.jooby.mvc.Produces} {@link org.jooby.mvc.Consumes} javadoc.
 * </p>
 *
 * <h2>static files</h2>
 * <p>
 * Static files, like: *.js, *.css, ..., etc... can be served with:
 * </p>
 *
 * <pre>
 *   assets("assets/**");
 * </pre>
 * <p>
 * Classpath resources under the <code>/assets</code> folder will be accessible from client/browser.
 * </p>
 *
 * <h2>lifecyle</h2>
 * <p>
 * We do provide {@link #onStart(CheckedConsumer)} and {@link #onStop(CheckedConsumer)} callbacks.
 * These callbacks are executed are application startup or shutdown time:
 * </p>
 *
 * <pre>{@code
 * {
 *   onStart(() -> {
 *     log.info("Welcome!");
 *   });
 *
 *   onStop(() -> {
 *     log.info("Bye!");
 *   });
 * }
 * }</pre>
 *
 * <p>
 * From life cycle callbacks you can access to application services:
 * </p>
 *
 * <pre>{@code
 * {
 *   onStart(registry -> {
 *     MyDatabase db = registry.require(MyDatabase.class);
 *     // do something with databse:
 *   });
 * }
 * }</pre>
 *
 * @author edgar
 * @see Jooby.Module
 * @since 0.1.0
 */
public class Jooby implements Router, LifeCycle, Registry {

  /**
   * <pre>{@code
   * {
   *   on("dev", () -> {
   *     // run something on dev
   *   }).orElse(() -> {
   *     // run something on prod
   *   });
   * }
   * }</pre>
   */
  public interface EnvPredicate {

    /**
     * <pre>{@code
     * {
     *   on("dev", () -> {
     *     // run something on dev
     *   }).orElse(() -> {
     *     // run something on prod
     *   });
     * }
     * }</pre>
     *
     * @param callback Env callback.
     */
    default void orElse(final Runnable callback) {
      orElse(conf -> callback.run());
    }

    /**
     * <pre>{@code
     * {
     *   on("dev", () -> {
     *     // run something on dev
     *   }).orElse(conf -> {
     *     // run something on prod
     *   });
     * }
     * }</pre>
     *
     * @param callback Env callback.
     */
    void orElse(Consumer<Config> callback);

  }

  /**
   * A module can publish or produces: {@link Route.Definition routes}, {@link Parser},
   * {@link Renderer}, and any other application specific service or contract of your choice.
   * <p>
   * It is similar to {@link com.google.inject.Module} except for the callback method receives a
   * {@link Env}, {@link Config} and {@link Binder}.
   * </p>
   *
   * <p>
   * A module can provide his own set of properties through the {@link #config()} method. By
   * default, this method returns an empty config object.
   * </p>
   * For example:
   *
   * <pre>
   *   use(new M1());
   *   use(new M2());
   *   use(new M3());
   * </pre>
   *
   * Previous example had the following order (first-listed are higher priority):
   * <ul>
   * <li>System properties</li>
   * <li>application.conf</li>
   * <li>M3 properties</li>
   * <li>M2 properties</li>
   * <li>M1 properties</li>
   * </ul>
   *
   * <p>
   * A module can provide start/stop methods in order to start or close resources.
   * </p>
   *
   * @author edgar
   * @see Jooby#use(Jooby.Module)
   * @since 0.1.0
   */
  public interface Module {

    /**
     * @return Produces a module config object (when need it). By default a module doesn't produce
     *         any configuration object.
     */
    default Config config() {
      return ConfigFactory.empty();
    }

    /**
     * Configure and produces bindings for the underlying application. A module can optimize or
     * customize a service by checking current the {@link Env application env} and/or the current
     * application properties available from {@link Config}.
     *
     * @param env The current application's env. Not null.
     * @param conf The current config object. Not null.
     * @param binder A guice binder. Not null.
     */
    void configure(Env env, Config conf, Binder binder) throws Throwable;

  }

  static class MvcClass implements Route.Props<MvcClass> {
    Class<?> routeClass;

    String path;

    ImmutableMap.Builder<String, Object> attrs = ImmutableMap.builder();

    private List<MediaType> consumes;

    private String name;

    private List<MediaType> produces;

    private List<String> excludes;

    private Mapper<?> mapper;

    private String prefix;

    private String renderer;

    public MvcClass(final Class<?> routeClass, final String path, final String prefix) {
      this.routeClass = routeClass;
      this.path = path;
      this.prefix = prefix;
    }

    @Override
    public MvcClass attr(final String name, final Object value) {
      attrs.put(name, value);
      return this;
    }

    @Override
    public MvcClass name(final String name) {
      this.name = name;
      return this;
    }

    @Override
    public MvcClass consumes(final List<MediaType> consumes) {
      this.consumes = consumes;
      return this;
    }

    @Override
    public MvcClass produces(final List<MediaType> produces) {
      this.produces = produces;
      return this;
    }

    @Override
    public MvcClass excludes(final List<String> excludes) {
      this.excludes = excludes;
      return this;
    }

    @Override
    public MvcClass map(final Mapper<?> mapper) {
      this.mapper = mapper;
      return this;
    }

    @Override
    public String renderer() {
      return renderer;
    }

    @Override
    public MvcClass renderer(final String name) {
      this.renderer = name;
      return this;
    }

    public Route.Definition apply(final Route.Definition route) {
      attrs.build().forEach(route::attr);
      if (name != null) {
        route.name(name);
      }
      if (prefix != null) {
        route.name(prefix + "/" + route.name());
      }
      if (consumes != null) {
        route.consumes(consumes);
      }
      if (produces != null) {
        route.produces(produces);
      }
      if (excludes != null) {
        route.excludes(excludes);
      }
      if (mapper != null) {
        route.map(mapper);
      }
      if (renderer != null) {
        route.renderer(renderer);
      }
      return route;
    }
  }

  private static class EnvDep {
    Predicate<String> predicate;

    Consumer<Config> callback;

    public EnvDep(final Predicate<String> predicate, final Consumer<Config> callback) {
      this.predicate = predicate;
      this.callback = callback;
    }
  }

  static {
    // set pid as system property
    String pid = System.getProperty("pid", JvmInfo.pid() + "");
    System.setProperty("pid", pid);
  }

  /**
   * Keep track of routes.
   */
  private transient Set<Object> bag = new LinkedHashSet<>();

  /**
   * The override config. Optional.
   */
  private transient Config srcconf;

  private final transient AtomicBoolean started = new AtomicBoolean(false);

  /** Keep the global injector instance. */
  private transient Injector injector;

  /** Session store. */
  private transient Session.Definition session = new Session.Definition(Session.Mem.class);

  /** Env builder. */
  private transient Env.Builder env = Env.DEFAULT;

  /** Route's prefix. */
  private transient String prefix;

  /** startup callback . */
  private transient List<CheckedConsumer<Registry>> onStart = new ArrayList<>();
  private transient List<CheckedConsumer<Registry>> onStarted = new ArrayList<>();

  /** stop callback . */
  private transient List<CheckedConsumer<Registry>> onStop = new ArrayList<>();

  /** Mappers . */
  @SuppressWarnings("rawtypes")
  private transient Mapper mapper;

  /** Don't add same mapper twice . */
  private transient Set<String> mappers = new HashSet<>();

  /** Bean parser . */
  private transient Optional<Parser> beanParser = Optional.empty();

  private transient ServerLookup server = new ServerLookup();

  private transient String dateFormat;

  private transient Charset charset;

  private transient String[] languages;

  private transient ZoneId zoneId;

  private transient Integer port;

  private transient Integer securePort;

  private transient String numberFormat;

  private transient boolean http2;

  private transient List<Consumer<Binder>> executors = new ArrayList<>();

  private transient boolean defaultExecSet;

  private boolean throwBootstrapException;

  /**
   * creates the injector
   */
  private transient BiFunction<Stage, com.google.inject.Module, Injector> injectorFactory = Guice::createInjector;

  private List<Jooby> apprefs;

  /**
   * Creates a new {@link Jooby} application.
   */
  public Jooby() {
    this(null);
  }

  /**
   * Creates a new application and prefix all the names of the routes with the given prefix. Useful,
   * for dynamic/advanced routing. See {@link Route.Chain#next(String, Request, Response)}.
   *
   * @param prefix Route name prefix.
   */
  public Jooby(final String prefix) {
    this.prefix = prefix;
    use(server);
  }

  @Override
  public Jooby use(final Jooby app) {
    return use(Optional.empty(), app);
  }

  @Override
  public Jooby use(final String path, final Jooby app) {
    return use(Optional.of(path), app);
  }

  /**
   * Use the provided HTTP server.
   *
   * @param server Server.
   * @return This jooby instance.
   */
  public Jooby server(final Class<? extends Server> server) {
    requireNonNull(server, "Server required.");
    // remove server lookup
    List<Object> tmp = bag.stream()
        .skip(1)
        .collect(Collectors.toList());
    tmp.add(0,
        (Module) (env, conf, binder) -> binder.bind(Server.class).to(server).asEagerSingleton());
    bag.clear();
    bag.addAll(tmp);
    return this;
  }

  private Jooby use(final Optional<String> path, final Jooby app) {
    requireNonNull(app, "App is required.");

    Function<Route.Definition, Route.Definition> rewrite = r -> {
      return path.map(p -> {
        Route.Definition result = new Route.Definition(r.method(), p + r.pattern(), r.filter());
        result.consumes(r.consumes());
        result.produces(r.produces());
        result.excludes(r.excludes());
        return result;
      }).orElse(r);
    };

    app.bag.forEach(it -> {
      if (it instanceof Route.Definition) {
        this.bag.add(rewrite.apply((Definition) it));
      } else if (it instanceof Route.Group) {
        ((Route.Group) it).routes().forEach(r -> this.bag.add(rewrite.apply(r)));
      } else if (it instanceof MvcClass) {
        Object routes = path.<Object> map(p -> new MvcClass(((MvcClass) it).routeClass, p, prefix))
            .orElse(it);
        this.bag.add(routes);
      } else {
        // everything else
        this.bag.add(it);
      }
    });
    // start/stop callback
    app.onStart.forEach(this.onStart::add);
    app.onStarted.forEach(this.onStarted::add);
    app.onStop.forEach(this.onStop::add);
    // mapper
    if (app.mapper != null) {
      this.map(app.mapper);
    }
    if (apprefs == null) {
      apprefs = new ArrayList<>();
    }
    apprefs.add(app);
    return this;
  }

  @Override
  public Route.Group use(final String pattern) {
    Route.Group group = new Route.Group(pattern, prefix);
    this.bag.add(group);
    return group;
  }

  /**
   * Set a custom {@link Env.Builder} to use.
   *
   * @param env A custom env builder.
   * @return This jooby instance.
   */
  public Jooby env(final Env.Builder env) {
    this.env = requireNonNull(env, "Env builder is required.");
    return this;
  }

  @Override
  public Jooby onStart(final CheckedRunnable callback) {
    LifeCycle.super.onStart(callback);
    return this;
  }

  @Override
  public Jooby onStart(final CheckedConsumer<Registry> callback) {
    requireNonNull(callback, "Callback is required.");
    onStart.add(callback);
    return this;
  }

  @Override
  public Jooby onStarted(final CheckedRunnable callback) {
    LifeCycle.super.onStarted(callback);
    return this;
  }

  @Override
  public Jooby onStarted(final CheckedConsumer<Registry> callback) {
    requireNonNull(callback, "Callback is required.");
    onStarted.add(callback);
    return this;
  }

  @Override
  public Jooby onStop(final CheckedRunnable callback) {
    LifeCycle.super.onStop(callback);
    return this;
  }

  @Override
  public Jooby onStop(final CheckedConsumer<Registry> callback) {
    requireNonNull(callback, "Callback is required.");
    onStop.add(callback);
    return this;
  }

  /**
   * Run the given callback if and only if, application runs in the given environment.
   *
   * <pre>
   * {
   *   on("dev", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * </pre>
   *
   * There is an else clause which is the opposite version of the env predicate:
   *
   * <pre>
   * {
   *   on("dev", () {@literal ->} {
   *     use(new DevModule());
   *   }).orElse(() {@literal ->} {
   *     use(new RealModule());
   *   });
   * }
   * </pre>
   *
   * @param env Environment where we want to run the callback.
   * @param callback An env callback.
   * @return This jooby instance.
   */
  public EnvPredicate on(final String env, final Runnable callback) {
    requireNonNull(env, "Env is required.");
    return on(envpredicate(env), callback);
  }

  /**
   * Run the given callback if and only if, application runs in the given environment.
   *
   * <pre>
   * {
   *   on("dev", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * </pre>
   *
   * There is an else clause which is the opposite version of the env predicate:
   *
   * <pre>
   * {
   *   on("dev", conf {@literal ->} {
   *     use(new DevModule());
   *   }).orElse(conf {@literal ->} {
   *     use(new RealModule());
   *   });
   * }
   * </pre>
   *
   * @param env Environment where we want to run the callback.
   * @param callback An env callback.
   * @return This jooby instance.
   */
  public EnvPredicate on(final String env, final Consumer<Config> callback) {
    requireNonNull(env, "Env is required.");
    return on(envpredicate(env), callback);
  }

  /**
   * Run the given callback if and only if, application runs in the given envirobment.
   *
   * <pre>
   * {
   *   on("dev", "test", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * </pre>
   *
   * There is an else clause which is the opposite version of the env predicate:
   *
   * <pre>
   * {
   *   on(env {@literal ->} env.equals("dev"), () {@literal ->} {
   *     use(new DevModule());
   *   }).orElse(() {@literal ->} {
   *     use(new RealModule());
   *   });
   * }
   * </pre>
   *
   * @param predicate Predicate to check the environment.
   * @param callback An env callback.
   * @return This jooby instance.
   */
  public EnvPredicate on(final Predicate<String> predicate, final Runnable callback) {
    requireNonNull(predicate, "Predicate is required.");
    requireNonNull(callback, "Callback is required.");

    return on(predicate, conf -> callback.run());
  }

  /**
   * Run the given callback if and only if, application runs in the given environment.
   *
   * <pre>
   * {
   *   on(env {@literal ->} env.equals("dev"), conf {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * </pre>
   *
   * @param predicate Predicate to check the environment.
   * @param callback An env callback.
   * @return This jooby instance.
   */
  public EnvPredicate on(final Predicate<String> predicate, final Consumer<Config> callback) {
    requireNonNull(predicate, "Predicate is required.");
    requireNonNull(callback, "Callback is required.");
    this.bag.add(new EnvDep(predicate, callback));

    return otherwise -> this.bag.add(new EnvDep(predicate.negate(), otherwise));
  }

  /**
   * Run the given callback if and only if, application runs in the given environment.
   *
   * <pre>
   * {
   *   on("dev", "test", "mock", () {@literal ->} {
   *     use(new DevModule());
   *   });
   * }
   * </pre>
   *
   * @param env1 Environment where we want to run the callback.
   * @param env2 Environment where we want to run the callback.
   * @param env3 Environment where we want to run the callback.
   * @param callback An env callback.
   * @return This jooby instance.
   */
  public Jooby on(final String env1, final String env2, final String env3,
      final Runnable callback) {
    on(envpredicate(env1).or(envpredicate(env2)).or(envpredicate(env3)), callback);
    return this;
  }

  @Override
  public <T> T require(final Key<T> type) {
    checkState(injector != null, "App didn't start yet");
    return injector.getInstance(type);
  }

  @Override
  public Route.OneArgHandler promise(final Deferred.Initializer initializer) {
    return req -> {
      return new Deferred(initializer);
    };
  }

  @Override
  public Route.OneArgHandler promise(final String executor,
      final Deferred.Initializer initializer) {
    return req -> new Deferred(executor, initializer);
  }

  @Override
  public Route.OneArgHandler promise(final Deferred.Initializer0 initializer) {
    return req -> {
      return new Deferred(initializer);
    };
  }

  @Override
  public Route.OneArgHandler promise(final String executor,
      final Deferred.Initializer0 initializer) {
    return req -> new Deferred(executor, initializer);
  }

  /**
   * Setup a session store to use. Useful if you want/need to persist sessions between shutdowns,
   * apply timeout policies, etc...
   *
   * Jooby comes with a dozen of {@link Session.Store}, checkout the
   * <a href="http://jooby.org/doc/session">session modules</a>.
   *
   * This method returns a {@link Session.Definition} objects that let you customize the session
   * cookie.
   *
   * @param store A session store.
   * @return A session store definition.
   */
  public Session.Definition session(final Class<? extends Session.Store> store) {
    this.session = new Session.Definition(requireNonNull(store, "A session store is required."));
    return this.session;
  }

  /**
   * Setup a session store that saves data in a the session cookie. It makes the application
   * stateless, which help to scale easily. Keep in mind that a cookie has a limited size (up to
   * 4kb) so you must pay attention to what you put in the session object (don't use as cache).
   *
   * Cookie session signed data using the <code>application.secret</code> property, so you must
   * provide an <code>application.secret</code> value. On dev environment you can set it in your
   * <code>.conf</code> file. In prod is probably better to provide as command line argument and/or
   * environment variable. Just make sure to keep it private.
   *
   * Please note {@link Session#id()}, {@link Session#accessedAt()}, etc.. make no sense for cookie
   * sessions, just the {@link Session#attributes()}.
   *
   * This method returns a {@link Session.Definition} objects that let you customize the session
   * cookie.
   *
   * @return A session definition/configuration object.
   */
  public Session.Definition cookieSession() {
    this.session = new Session.Definition();
    return this.session;
  }

  /**
   * Setup a session store to use. Useful if you want/need to persist sessions between shutdowns,
   * apply timeout policies, etc...
   *
   * Jooby comes with a dozen of {@link Session.Store}, checkout the
   * <a href="http://jooby.org/doc/session">session modules</a>.
   *
   * This method returns a {@link Session.Definition} objects that let you customize the session
   * cookie.
   *
   * @param store A session store.
   * @return A session store definition.
   */
  public Session.Definition session(final Session.Store store) {
    this.session = new Session.Definition(requireNonNull(store, "A session store is required."));
    return this.session;
  }

  /**
   * Register a new param/body converter. See {@link Parser} for more details.
   *
   * @param parser A parser.
   * @return This jooby instance.
   */
  public Jooby parser(final Parser parser) {
    if (parser instanceof BeanParser) {
      beanParser = Optional.of(parser);
    } else {
      bag.add(requireNonNull(parser, "A parser is required."));
    }
    return this;
  }

  /**
   * Append a response {@link Renderer} for write HTTP messages.
   *
   * @param renderer A renderer renderer.
   * @return This jooby instance.
   */
  public Jooby renderer(final Renderer renderer) {
    this.bag.add(requireNonNull(renderer, "A renderer is required."));
    return this;
  }

  @Override
  public Route.Collection before(final String method, final String pattern,
      final Route.Before handler, final Route.Before... chain) {
    Route.Definition[] routes = javaslang.collection.List.of(handler)
        .appendAll(Arrays.asList(chain))
        .map(before -> appendDefinition(new Route.Definition(method, pattern, before)))
        .toJavaArray(Route.Definition.class);
    return new Route.Collection(routes);
  }

  @Override
  public Route.Collection after(final String method, final String pattern,
      final Route.After handler, final Route.After... chain) {
    Route.Definition[] routes = javaslang.collection.List.of(handler)
        .appendAll(Arrays.asList(chain))
        .map(after -> appendDefinition(new Route.Definition(method, pattern, after)))
        .toJavaArray(Route.Definition.class);
    return new Route.Collection(routes);
  }

  @Override
  public Route.Collection complete(final String method, final String pattern,
      final Route.Complete handler, final Route.Complete... chain) {
    Route.Definition[] routes = javaslang.collection.List.of(handler)
        .appendAll(Arrays.asList(chain))
        .map(complete -> appendDefinition(new Route.Definition(method, pattern, complete)))
        .toJavaArray(Route.Definition.class);
    return new Route.Collection(routes);
  }

  @Override
  public Route.Definition use(final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition("*", path, filter));
  }

  @Override
  public Route.Definition use(final String verb, final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition(verb, path, filter));
  }

  @Override
  public Route.Definition use(final String verb, final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(verb, path, handler));
  }

  @Override
  public Route.Definition use(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition("*", path, handler));
  }

  @Override
  public Route.Definition use(final String path, final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition("*", path, handler));
  }

  @Override
  public Route.Definition get(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(GET, path, handler));
  }

  @Override
  public Route.Collection get(final String path1, final String path2, final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{get(path1, handler), get(path2, handler) });
  }

  @Override
  public Route.Collection get(final String path1, final String path2, final String path3,
      final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{get(path1, handler), get(path2, handler), get(path3, handler) });
  }

  @Override
  public Route.Definition get(final String path, final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(GET, path, handler));
  }

  @Override
  public Route.Collection get(final String path1, final String path2,
      final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{get(path1, handler), get(path2, handler) });
  }

  @Override
  public Route.Collection get(final String path1, final String path2,
      final String path3, final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{get(path1, handler), get(path2, handler), get(path3, handler) });
  }

  @Override
  public Route.Definition get(final String path, final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(GET, path, handler));
  }

  @Override
  public Route.Collection get(final String path1, final String path2,
      final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{get(path1, handler), get(path2, handler) });
  }

  @Override
  public Route.Collection get(final String path1, final String path2,
      final String path3, final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{get(path1, handler), get(path2, handler), get(path3, handler) });
  }

  @Override
  public Route.Definition get(final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition(GET, path, filter));
  }

  @Override
  public Route.Collection get(final String path1, final String path2, final Route.Filter filter) {
    return new Route.Collection(new Route.Definition[]{get(path1, filter), get(path2, filter) });
  }

  @Override
  public Route.Collection get(final String path1, final String path2,
      final String path3, final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{get(path1, filter), get(path2, filter), get(path3, filter) });
  }

  @Override
  public Route.Definition post(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(POST, path, handler));
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, handler), post(path2, handler) });
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final String path3, final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, handler), post(path2, handler), post(path3, handler) });
  }

  @Override
  public Route.Definition post(final String path, final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(POST, path, handler));
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, handler), post(path2, handler) });
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final String path3, final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, handler), post(path2, handler), post(path3, handler) });
  }

  @Override
  public Route.Definition post(final String path, final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(POST, path, handler));
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, handler), post(path2, handler) });
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final String path3, final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, handler), post(path2, handler), post(path3, handler) });
  }

  @Override
  public Route.Definition post(final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition(POST, path, filter));
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, filter), post(path2, filter) });
  }

  @Override
  public Route.Collection post(final String path1, final String path2,
      final String path3, final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{post(path1, filter), post(path2, filter), post(path3, filter) });
  }

  @Override
  public Route.Definition head(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(HEAD, path, handler));
  }

  @Override
  public Route.Definition head(final String path,
      final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(HEAD, path, handler));
  }

  @Override
  public Route.Definition head(final String path, final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(HEAD, path, handler));
  }

  @Override
  public Route.Definition head(final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition(HEAD, path, filter));
  }

  @Override
  public Route.Definition head() {
    return appendDefinition(new Route.Definition(HEAD, "*", filter(HeadHandler.class))
        .name("*.head"));
  }

  @Override
  public Route.Definition options(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(OPTIONS, path, handler));
  }

  @Override
  public Route.Definition options(final String path,
      final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(OPTIONS, path, handler));
  }

  @Override
  public Route.Definition options(final String path,
      final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(OPTIONS, path, handler));
  }

  @Override
  public Route.Definition options(final String path,
      final Route.Filter filter) {
    return appendDefinition(new Route.Definition(OPTIONS, path, filter));
  }

  @Override
  public Route.Definition options() {
    return appendDefinition(new Route.Definition(OPTIONS, "*", handler(OptionsHandler.class))
        .name("*.options"));
  }

  @Override
  public Route.Definition put(final String path,
      final Route.Handler handler) {
    return appendDefinition(new Route.Definition(PUT, path, handler));
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, handler), put(path2, handler) });
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final String path3, final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, handler), put(path2, handler), put(path3, handler) });
  }

  @Override
  public Route.Definition put(final String path,
      final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(PUT, path, handler));
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, handler), put(path2, handler) });
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final String path3, final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, handler), put(path2, handler), put(path3, handler) });
  }

  @Override
  public Route.Definition put(final String path,
      final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(PUT, path, handler));
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, handler), put(path2, handler) });
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final String path3, final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, handler), put(path2, handler), put(path3, handler) });
  }

  @Override
  public Route.Definition put(final String path,
      final Route.Filter filter) {
    return appendDefinition(new Route.Definition(PUT, path, filter));
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, filter), put(path2, filter) });
  }

  @Override
  public Route.Collection put(final String path1, final String path2,
      final String path3, final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{put(path1, filter), put(path2, filter), put(path3, filter) });
  }

  @Override
  public Route.Definition patch(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(PATCH, path, handler));
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, handler), patch(path2, handler) });
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final String path3, final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, handler), patch(path2, handler),
            patch(path3, handler) });
  }

  @Override
  public Route.Definition patch(final String path, final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(PATCH, path, handler));
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, handler), patch(path2, handler) });
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final String path3, final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, handler), patch(path2, handler),
            patch(path3, handler) });
  }

  @Override
  public Route.Definition patch(final String path, final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(PATCH, path, handler));
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, handler), patch(path2, handler) });
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final String path3, final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, handler), patch(path2, handler),
            patch(path3, handler) });
  }

  @Override
  public Route.Definition patch(final String path,
      final Route.Filter filter) {
    return appendDefinition(new Route.Definition(PATCH, path, filter));
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, filter), patch(path2, filter) });
  }

  @Override
  public Route.Collection patch(final String path1, final String path2,
      final String path3, final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{patch(path1, filter), patch(path2, filter),
            patch(path3, filter) });
  }

  @Override
  public Route.Definition delete(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(DELETE, path, handler));
  }

  @Override
  public Route.Collection delete(final String path1, final String path2,
      final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, handler), delete(path2, handler) });
  }

  @Override
  public Route.Collection delete(final String path1, final String path2, final String path3,
      final Route.Handler handler) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, handler), delete(path2, handler),
            delete(path3, handler) });
  }

  @Override
  public Route.Definition delete(final String path, final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(DELETE, path, handler));
  }

  @Override
  public Route.Collection delete(final String path1, final String path2,
      final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, handler), delete(path2, handler) });
  }

  @Override
  public Route.Collection delete(final String path1, final String path2, final String path3,
      final Route.OneArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, handler), delete(path2, handler),
            delete(path3, handler) });
  }

  @Override
  public Route.Definition delete(final String path,
      final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(DELETE, path, handler));
  }

  @Override
  public Route.Collection delete(final String path1,
      final String path2, final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, handler), delete(path2, handler) });
  }

  @Override
  public Route.Collection delete(final String path1,
      final String path2, final String path3,
      final Route.ZeroArgHandler handler) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, handler), delete(path2, handler),
            delete(path3, handler) });
  }

  @Override
  public Route.Definition delete(final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition(DELETE, path, filter));
  }

  @Override
  public Route.Collection delete(final String path1, final String path2,
      final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, filter), delete(path2, filter) });
  }

  @Override
  public Route.Collection delete(final String path1, final String path2, final String path3,
      final Route.Filter filter) {
    return new Route.Collection(
        new Route.Definition[]{delete(path1, filter), delete(path2, filter),
            delete(path3, filter) });
  }

  @Override
  public Route.Definition trace(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(TRACE, path, handler));
  }

  @Override
  public Route.Definition trace(final String path, final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(TRACE, path, handler));
  }

  @Override
  public Route.Definition trace(final String path, final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(TRACE, path, handler));
  }

  @Override
  public Route.Definition trace(final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition(TRACE, path, filter));
  }

  @Override
  public Route.Definition trace() {
    return appendDefinition(new Route.Definition(TRACE, "*", handler(TraceHandler.class))
        .name("*.trace"));
  }

  @Override
  public Route.Definition connect(final String path, final Route.Handler handler) {
    return appendDefinition(new Route.Definition(CONNECT, path, handler));
  }

  @Override
  public Route.Definition connect(final String path, final Route.OneArgHandler handler) {
    return appendDefinition(new Route.Definition(CONNECT, path, handler));
  }

  @Override
  public Route.Definition connect(final String path, final Route.ZeroArgHandler handler) {
    return appendDefinition(new Route.Definition(CONNECT, path, handler));
  }

  @Override
  public Route.Definition connect(final String path, final Route.Filter filter) {
    return appendDefinition(new Route.Definition(CONNECT, path, filter));
  }

  /**
   * Creates a new {@link Route.Handler} that delegate the execution to the given handler. This is
   * useful when the target handler requires some dependencies.
   *
   * <pre>
   *   public class MyHandler implements Route.Handler {
   *     &#64;Inject
   *     public MyHandler(Dependency d) {
   *     }
   *
   *     public void handle(Request req, Response rsp) throws Exception {
   *      // do something
   *     }
   *   }
   *   ...
   *   // external route
   *   get("/", handler(MyHandler.class));
   *
   *   // inline version route
   *   get("/", (req, rsp) {@literal ->} {
   *     Dependency d = req.getInstance(Dependency.class);
   *     // do something
   *   });
   * </pre>
   *
   * You can access to a dependency from a in-line route too, so the use of external route it is
   * more or less a matter of taste.
   *
   * @param handler The external handler class.
   * @return A new inline route handler.
   */
  private Route.Handler handler(final Class<? extends Route.Handler> handler) {
    requireNonNull(handler, "Route handler is required.");
    return (req, rsp) -> req.require(handler).handle(req, rsp);
  }

  /**
   * Creates a new {@link Route.Filter} that delegate the execution to the given filter. This is
   * useful when the target handler requires some dependencies.
   *
   * <pre>
   *   public class MyFilter implements Filter {
   *     &#64;Inject
   *     public MyFilter(Dependency d) {
   *     }
   *
   *     public void handle(Request req, Response rsp, Route.Chain chain) throws Exception {
   *      // do something
   *     }
   *   }
   *   ...
   *   // external filter
   *   get("/", filter(MyFilter.class));
   *
   *   // inline version route
   *   get("/", (req, rsp, chain) {@literal ->} {
   *     Dependency d = req.getInstance(Dependency.class);
   *     // do something
   *   });
   * </pre>
   *
   * You can access to a dependency from a in-line route too, so the use of external filter it is
   * more or less a matter of taste.
   *
   * @param filter The external filter class.
   * @return A new inline route.
   */
  private Route.Filter filter(final Class<? extends Route.Filter> filter) {
    requireNonNull(filter, "Filter is required.");
    return (req, rsp, chain) -> req.require(filter).handle(req, rsp, chain);
  }

  @Override
  public Definition assets(final String path, final Path basedir) {
    AssetHandler handler = new AssetHandler(basedir);
    configureAssetHandler(handler);
    return assets(path, handler);
  }

  @Override
  public Route.Definition assets(final String path, final String location) {
    AssetHandler handler = new AssetHandler(location);
    configureAssetHandler(handler);
    return assets(path, handler);
  }

  @Override
  public Route.Definition assets(final String path, final AssetHandler handler) {
    return appendDefinition(new Route.Definition(GET, path, handler));
  }

  @Override
  public Route.Collection use(final Class<?> routeClass) {
    requireNonNull(routeClass, "Route class is required.");
    MvcClass mvc = new MvcClass(routeClass, "", prefix);
    bag.add(mvc);
    return new Route.Collection(mvc);
  }

  /**
   * Keep track of routes in the order user define them.
   *
   * @param route A route definition to append.
   * @return The same route definition.
   */
  private Route.Definition appendDefinition(final Route.Definition route) {
    route.prefix = prefix;
    // reset name will update the name if prefix != null
    route.name(route.name());
    bag.add(route);
    return route;
  }

  /**
   * Import an application {@link Module}.
   *
   * @param module The module to import.
   * @return This jooby instance.
   * @see Jooby.Module
   */
  public Jooby use(final Jooby.Module module) {
    requireNonNull(module, "A module is required.");
    bag.add(module);
    return this;
  }

  /**
   * Set/specify a custom .conf file, useful when you don't want a <code>application.conf</code>
   * file.
   *
   * @param path Classpath location.
   * @return This jooby instance.
   */
  public Jooby conf(final String path) {
    use(ConfigFactory.parseResources(path));
    return this;
  }

  /**
   * Set/specify a custom .conf file, useful when you don't want a <code>application.conf</code>
   * file.
   *
   * @param path File system location.
   * @return This jooby instance.
   */
  public Jooby conf(final File path) {
    use(ConfigFactory.parseFile(path));
    return this;
  }

  /**
   * Set the application configuration object. You must call this method when the default file
   * name: <code>application.conf</code> doesn't work for you or when you need/want to register two
   * or more files.
   *
   * @param config The application configuration object.
   * @return This jooby instance.
   * @see Config
   */
  public Jooby use(final Config config) {
    this.srcconf = requireNonNull(config, "Config required.");
    return this;
  }

  @Override
  public Jooby err(final Err.Handler err) {
    this.bag.add(requireNonNull(err, "An err handler is required."));
    return this;
  }

  @Override
  public WebSocket.Definition ws(final String path, final WebSocket.OnOpen handler) {
    WebSocket.Definition ws = new WebSocket.Definition(path, handler);
    checkArgument(bag.add(ws), "Duplicated path: '%s'", path);
    return ws;
  }

  @Override
  public <T> WebSocket.Definition ws(final String path,
      final Class<? extends WebSocket.OnMessage<T>> handler) {
    String fpath = Optional.ofNullable(handler.getAnnotation(org.jooby.mvc.Path.class))
        .map(it -> path + "/" + it.value()[0])
        .orElse(path);

    WebSocket.Definition ws = ws(fpath, MvcWebSocket.newWebSocket(handler));

    Optional.ofNullable(handler.getAnnotation(Consumes.class))
        .ifPresent(consumes -> Arrays.asList(consumes.value()).forEach(ws::consumes));
    Optional.ofNullable(handler.getAnnotation(Produces.class))
        .ifPresent(produces -> Arrays.asList(produces.value()).forEach(ws::produces));
    return ws;
  }

  @Override
  public Route.Definition sse(final String path, final Sse.Handler handler) {
    return appendDefinition(new Route.Definition(GET, path, handler)).consumes(MediaType.sse);
  }

  @Override
  public Route.Definition sse(final String path, final Sse.Handler1 handler) {
    return appendDefinition(new Route.Definition(GET, path, handler)).consumes(MediaType.sse);
  }

  @SuppressWarnings("rawtypes")
  @Override
  public Route.Collection with(final Runnable callback) {
    // hacky way of doing what we want... but we do simplify developer life
    int size = this.bag.size();
    callback.run();
    // collect latest routes and apply route props
    List<Route.Props> local = this.bag.stream()
        .skip(size)
        .filter(Predicates.instanceOf(Route.Props.class))
        .map(r -> (Route.Props) r)
        .collect(Collectors.toList());
    return new Route.Collection(local.toArray(new Route.Props[local.size()]));
  }

  /**
   * Prepare and startup a {@link Jooby} application.
   *
   * @param app Application supplier.
   * @param args Application arguments.
   */
  public static void run(final Supplier<? extends Jooby> app, final String... args) {
    Config conf = ConfigFactory.systemProperties()
        .withFallback(args(args));
    System.setProperty("logback.configurationFile", logback(conf));
    app.get().start(args);
  }

  /**
   * Prepare and startup a {@link Jooby} application.
   *
   * @param app Application supplier.
   * @param args Application arguments.
   */
  public static void run(final Class<? extends Jooby> app, final String... args) {
    run(() -> Try.of(() -> app.newInstance()).get(), args);
  }

  /**
   * Export configuration from an application. Useful for tooling, testing, debugging, etc...
   *
   * @param app Application to extract/collect configuration.
   * @return Application conf or <code>empty</code> conf on error.
   */
  public static Config exportConf(final Jooby app) {
    AtomicReference<Config> conf = new AtomicReference<>(ConfigFactory.empty());
    app.on("*", c -> {
      conf.set(c);
    });
    exportRoutes(app);
    return conf.get();
  }

  /**
   * Export routes from an application. Useful for route analysis, testing, debugging, etc...
   *
   * @param app Application to extract/collect routes.
   * @return Application routes.
   */
  public static List<Definition> exportRoutes(final Jooby app) {
    @SuppressWarnings("serial")
    class Success extends RuntimeException {
      List<Definition> routes;

      Success(final List<Route.Definition> routes) {
        this.routes = routes;
      }
    }
    List<Definition> routes = Collections.emptyList();
    try {
      app.start(new String[0], r -> {
        throw new Success(r);
      });
    } catch (Success success) {
      routes = success.routes;
    } catch (Throwable x) {
      logger(app).debug("Failed bootstrap: {}", app, x);
    }
    return routes;
  }

  /**
   * Start an application.
   */
  public void start() {
    start(new String[0]);
  }

  /**
   * Start an application.
   *
   * @param args Application arguments.
   */
  public void start(final String... args) {
    try {
      start(args, null);
    } catch (Throwable x) {
      stop();
      String msg = "An error occurred while starting the application:";
      if (throwBootstrapException) {
        throw new Err(Status.SERVICE_UNAVAILABLE, msg, x);
      } else {
        logger(this).error(msg, x);
      }
    }
  }

  @SuppressWarnings("unchecked")
  private void start(final String[] args, final Consumer<List<Route.Definition>> routes)
      throws Throwable {
    long start = System.currentTimeMillis();

    started.set(true);

    this.injector = bootstrap(args(args), routes);

    // shutdown hook
    Runtime.getRuntime().addShutdownHook(new Thread(() -> stop()));

    Config conf = injector.getInstance(Config.class);

    Logger log = logger(this);

    // inject class
    injector.injectMembers(this);

    // onStart callbacks via .conf
    if (conf.hasPath("jooby.internal.onStart")) {
      ClassLoader loader = getClass().getClassLoader();
      Object internalOnStart = loader.loadClass(conf.getString("jooby.internal.onStart"))
          .newInstance();
      onStart.add((CheckedConsumer<Registry>) internalOnStart);
    }

    // start services
    for (CheckedConsumer<Registry> onStart : this.onStart) {
      onStart.accept(this);
    }

    // route mapper
    Set<Route.Definition> routeDefs = injector.getInstance(Route.KEY);
    Set<WebSocket.Definition> sockets = injector.getInstance(WebSocket.KEY);
    if (mapper != null) {
      routeDefs.forEach(it -> it.map(mapper));
    }

    AppPrinter printer = new AppPrinter(routeDefs, sockets, conf);
    printer.printConf(log, conf);

    // Start server
    Server server = injector.getInstance(Server.class);
    String serverName = server.getClass().getSimpleName().replace("Server", "").toLowerCase();

    server.start();
    long end = System.currentTimeMillis();

    log.info("[{}@{}]: Server started in {}ms\n\n{}\n",
        conf.getString("application.env"),
        serverName,
        end - start,
        printer);

    // started services
    for (CheckedConsumer<Registry> onStarted : this.onStarted) {
      onStarted.accept(this);
    }

    boolean join = conf.hasPath("server.join") ? conf.getBoolean("server.join") : true;
    if (join) {
      server.join();
    }
  }

  @Override
  @SuppressWarnings("unchecked")
  public Jooby map(final Mapper<?> mapper) {
    requireNonNull(mapper, "Mapper is required.");
    if (mappers.add(mapper.name())) {
      this.mapper = Optional.ofNullable(this.mapper)
          .map(next -> Route.Mapper.chain(mapper, next))
          .orElse((Mapper<Object>) mapper);
    }
    return this;
  }

  /**
   * Use the injection provider to create the Guice injector
   *
   * @param injectorFactory the injection provider
   * @return this instance.
   */

  public Jooby injector(
      final BiFunction<Stage, com.google.inject.Module, Injector> injectorFactory) {
    this.injectorFactory = injectorFactory;
    return this;
  }

  /**
   * Bind the provided abstract type to the given implementation:
   *
   * <pre>
   * {
   *   bind(MyInterface.class, MyImplementation.class);
   * }
   * </pre>
   *
   * @param type Service interface.
   * @param implementation Service implementation.
   * @param <T> Service type.
   * @return This instance.
   */
  public <T> Jooby bind(final Class<T> type, final Class<? extends T> implementation) {
    use((env, conf, binder) -> {
      binder.bind(type).to(implementation);
    });
    return this;
  }

  /**
   * Bind the provided abstract type to the given implementation:
   *
   * <pre>
   * {
   *   bind(MyInterface.class, MyImplementation::new);
   * }
   * </pre>
   *
   * @param type Service interface.
   * @param implementation Service implementation.
   * @param <T> Service type.
   * @return This instance.
   */
  public <T> Jooby bind(final Class<T> type, final Supplier<T> implementation) {
    use((env, conf, binder) -> {
      binder.bind(type).toInstance(implementation.get());
    });
    return this;
  }

  /**
   * Bind the provided type:
   *
   * <pre>
   * {
   *   bind(MyInterface.class);
   * }
   * </pre>
   *
   * @param type Service interface.
   * @param <T> Service type.
   * @return This instance.
   */
  public <T> Jooby bind(final Class<T> type) {
    use((env, conf, binder) -> {
      binder.bind(type);
    });
    return this;
  }

  /**
   * Bind the provided type:
   *
   * <pre>
   * {
   *   bind(new MyService());
   * }
   * </pre>
   *
   * @param service Service.
   * @return This instance.
   */
  @SuppressWarnings({"rawtypes", "unchecked" })
  public Jooby bind(final Object service) {
    use((env, conf, binder) -> {
      Class type = service.getClass();
      binder.bind(type).toInstance(service);
    });
    return this;
  }

  /**
   * Bind the provided type and object that requires some type of configuration:
   *
   * <pre>{@code
   * {
   *   bind(MyService.class, conf -> new MyService(conf.getString("service.url")));
   * }
   * }</pre>
   *
   * @param type Service type.
   * @param provider Service provider.
   * @param <T> Service type.
   * @return This instance.
   */
  public <T> Jooby bind(final Class<T> type, final Function<Config, ? extends T> provider) {
    use((env, conf, binder) -> {
      T service = provider.apply(conf);
      binder.bind(type).toInstance(service);
    });
    return this;
  }

  /**
   * Bind the provided type and object that requires some type of configuration:
   *
   * <pre>{@code
   * {
   *   bind(conf -> new MyService(conf.getString("service.url")));
   * }
   * }</pre>
   *
   * @param provider Service provider.
   * @param <T> Service type.
   * @return This instance.
   */
  @SuppressWarnings({"unchecked", "rawtypes" })
  public <T> Jooby bind(final Function<Config, T> provider) {
    use((env, conf, binder) -> {
      Object service = provider.apply(conf);
      Class type = service.getClass();
      binder.bind(type).toInstance(service);
    });
    return this;
  }

  /**
   * Set application date format.
   *
   * @param dateFormat A date format.
   * @return This instance.
   */
  public Jooby dateFormat(final String dateFormat) {
    this.dateFormat = requireNonNull(dateFormat, "DateFormat required.");
    return this;
  }

  /**
   * Set application number format.
   *
   * @param numberFormat A number format.
   * @return This instance.
   */
  public Jooby numberFormat(final String numberFormat) {
    this.numberFormat = requireNonNull(numberFormat, "NumberFormat required.");
    return this;
  }

  /**
   * Set application/default charset.
   *
   * @param charset A charset.
   * @return This instance.
   */
  public Jooby charset(final Charset charset) {
    this.charset = requireNonNull(charset, "Charset required.");
    return this;
  }

  /**
   * Set application locale (first listed are higher priority).
   *
   * @param languages List of locale using the language tag format.
   * @return This instance.
   */
  public Jooby lang(final String... languages) {
    this.languages = languages;
    return this;
  }

  /**
   * Set application time zone.
   *
   * @param zoneId ZoneId.
   * @return This instance.
   */
  public Jooby timezone(final ZoneId zoneId) {
    this.zoneId = requireNonNull(zoneId, "ZoneId required.");
    return this;
  }

  /**
   * Set the HTTP port.
   *
   * <p>
   * Keep in mind this work as a default port and can be reset via <code>application.port</code>
   * property.
   * </p>
   *
   * @param port HTTP port.
   * @return This instance.
   */
  public Jooby port(final int port) {
    this.port = port;
    return this;
  }

  /**
   * <p>
   * Set the HTTPS port to use.
   * </p>
   *
   * <p>
   * Keep in mind this work as a default port and can be reset via <code>application.port</code>
   * property.
   * </p>
   *
   * <h2>HTTPS</h2>
   * <p>
   * Jooby comes with a self-signed certificate, useful for development and test. But of course, you
   * should NEVER use it in the real world.
   * </p>
   *
   * <p>
   * In order to setup HTTPS with a secure certificate, you need to set these properties:
   * </p>
   *
   * <ul>
   * <li>
   * <code>ssl.keystore.cert</code>: An X.509 certificate chain file in PEM format. It can be an
   * absolute path or a classpath resource.
   * </li>
   * <li>
   * <code>ssl.keystore.key</code>: A PKCS#8 private key file in PEM format. It can be an absolute
   * path or a classpath resource.
   * </li>
   * </ul>
   *
   * <p>
   * Optionally, you can set these too:
   * </p>
   *
   * <ul>
   * <li>
   * <code>ssl.keystore.password</code>: Password of the keystore.key (if any). Default is:
   * null/empty.
   * </li>
   * <li>
   * <code>ssl.trust.cert</code>: Trusted certificates for verifying the remote endpoint’s
   * certificate. The file should contain an X.509 certificate chain in PEM format. Default uses the
   * system default.
   * </li>
   * <li>
   * <code>ssl.session.cacheSize</code>: Set the size of the cache used for storing SSL session
   * objects. 0 to use the default value.
   * </li>
   * <li>
   * <code>ssl.session.timeout</code>: Timeout for the cached SSL session objects, in seconds. 0 to
   * use the default value.
   * </li>
   * </ul>
   *
   * <p>
   * As you can see setup is very simple. All you need is your <code>.crt</code> and
   * <code>.key</code> files.
   * </p>
   *
   * @param port HTTPS port.
   * @return This instance.
   */
  public Jooby securePort(final int port) {
    this.securePort = port;
    return this;
  }

  /**
   * <p>
   * Enable <code>HTTP/2</code> protocol. Some servers require special configuration, others just
   * works. It is a good idea to check the server documentation about
   * <a href="http://jooby.org/doc/servers">HTTP/2</a>.
   * </p>
   *
   * <p>
   * In order to use HTTP/2 from a browser you must configure HTTPS, see {@link #securePort(int)}
   * documentation.
   * </p>
   *
   * <p>
   * If HTTP/2 clear text is supported then you may skip the HTTPS setup, but of course you won't be
   * able to use HTTP/2 with browsers.
   * </p>
   *
   * @return This instance.
   */
  public Jooby http2() {
    this.http2 = true;
    return this;
  }

  /**
   * Set the default executor to use from {@link Deferred Deferred API}.
   *
   * Default executor runs each task in the thread that invokes {@link Executor#execute execute},
   * that's a Jooby worker thread. A worker thread in Jooby can block.
   *
   * The {@link ExecutorService} will automatically shutdown.
   *
   * @param executor Executor to use.
   * @return This jooby instance.
   */
  public Jooby executor(final ExecutorService executor) {
    executor((Executor) executor);
    onStop(r -> executor.shutdown());
    return this;
  }

  /**
   * Set the default executor to use from {@link Deferred Deferred API}.
   *
   * Default executor runs each task in the thread that invokes {@link Executor#execute execute},
   * that's a Jooby worker thread. A worker thread in Jooby can block.
   *
   * The {@link ExecutorService} will automatically shutdown.
   *
   * @param executor Executor to use.
   * @return This jooby instance.
   */
  public Jooby executor(final Executor executor) {
    this.defaultExecSet = true;
    this.executors.add(binder -> {
      binder.bind(Key.get(String.class, Names.named("deferred"))).toInstance("deferred");
      binder.bind(Key.get(Executor.class, Names.named("deferred"))).toInstance(executor);
    });
    return this;
  }

  /**
   * Set a named executor to use from {@link Deferred Deferred API}. Useful for override the
   * default/global executor.
   *
   * Default executor runs each task in the thread that invokes {@link Executor#execute execute},
   * that's a Jooby worker thread. A worker thread in Jooby can block.
   *
   * The {@link ExecutorService} will automatically shutdown.
   *
   * @param name Name of the executor.
   * @param executor Executor to use.
   * @return This jooby instance.
   */
  public Jooby executor(final String name, final ExecutorService executor) {
    executor(name, (Executor) executor);
    onStop(r -> executor.shutdown());
    return this;
  }

  /**
   * Set a named executor to use from {@link Deferred Deferred API}. Useful for override the
   * default/global executor.
   *
   * Default executor runs each task in the thread that invokes {@link Executor#execute execute},
   * that's a Jooby worker thread. A worker thread in Jooby can block.
   *
   * The {@link ExecutorService} will automatically shutdown.
   *
   * @param name Name of the executor.
   * @param executor Executor to use.
   * @return This jooby instance.
   */
  public Jooby executor(final String name, final Executor executor) {
    this.executors.add(binder -> {
      binder.bind(Key.get(Executor.class, Names.named(name))).toInstance(executor);
    });
    return this;
  }

  /**
   * Set the default executor to use from {@link Deferred Deferred API}. This works as reference to
   * an executor, application directly or via module must provide an named executor.
   *
   * Default executor runs each task in the thread that invokes {@link Executor#execute execute},
   * that's a Jooby worker thread. A worker thread in Jooby can block.
   *
   * @param name Executor to use.
   * @return This jooby instance.
   */
  public Jooby executor(final String name) {
    defaultExecSet = true;
    this.executors.add(binder -> {
      binder.bind(Key.get(String.class, Names.named("deferred"))).toInstance(name);
    });
    return this;
  }

  /**
   * Set a named executor to use from {@link Deferred Deferred API}. Useful for override the
   * default/global executor.
   *
   * Default executor runs each task in the thread that invokes {@link Executor#execute execute},
   * that's a Jooby worker thread. A worker thread in Jooby can block.
   *
   * @param name Name of the executor.
   * @param provider Provider for the executor.
   * @return This jooby instance.
   */
  private Jooby executor(final String name, final Class<? extends Provider<Executor>> provider) {
    this.executors.add(binder -> {
      binder.bind(Key.get(Executor.class, Names.named(name))).toProvider(provider)
          .in(Singleton.class);
    });
    return this;
  }

  /**
   * If the application fails to start all the services are shutdown. Also, the exception is logged
   * and usually the application is going to exit.
   *
   * This options turn off logging and rethrow the exception as {@link Err}. Here is an example:
   *
   * <pre>
   * public class App extends Jooby {
   *   {
   *     throwBootstrapException();
   *     ...
   *   }
   * }
   *
   * App app = new App();
   *
   * try {
   *   app.start();
   * } catch (Err err) {
   *   Throwable cause = err.getCause();
   * }
   * </pre>
   *
   * @return
   */
  public Jooby throwBootstrapException() {
    this.throwBootstrapException = true;
    return this;
  }

  private static List<Object> normalize(final List<Object> services, final Env env,
      final RouteMetadata classInfo, final String prefix) {
    List<Object> result = new ArrayList<>();
    List<Object> snapshot = services;
    /** modules, routes, parsers, renderers and websockets */
    snapshot.forEach(candidate -> {
      if (candidate instanceof Route.Definition) {
        result.add(candidate);
      } else if (candidate instanceof Route.Group) {
        ((Route.Group) candidate).routes()
            .forEach(r -> result.add(r));
      } else if (candidate instanceof MvcClass) {
        MvcClass mvcRoute = ((MvcClass) candidate);
        Class<?> mvcClass = mvcRoute.routeClass;
        String path = ((MvcClass) candidate).path;
        MvcRoutes.routes(env, classInfo, path, mvcClass)
            .forEach(route -> result.add(mvcRoute.apply(route)));
      } else {
        result.add(candidate);
      }
    });
    return result;
  }

  private static List<Object> processEnvDep(final Set<Object> src, final Env env) {
    List<Object> result = new ArrayList<>();
    List<Object> bag = new ArrayList<>(src);
    bag.forEach(it -> {
      if (it instanceof EnvDep) {
        EnvDep envdep = (EnvDep) it;
        if (envdep.predicate.test(env.name())) {
          int from = src.size();
          envdep.callback.accept(env.config());
          int to = src.size();
          result.addAll(new ArrayList<>(src).subList(from, to));
        }
      } else {
        result.add(it);
      }
    });
    return result;
  }

  private Injector bootstrap(final Config args,
      final Consumer<List<Route.Definition>> rcallback) throws Throwable {
    Config appconf = ConfigFactory.parseResources("application.conf");
    Config initconf = srcconf == null ? appconf : srcconf.withFallback(appconf);
    List<Config> modconf = modconf(this.bag);
    Config conf = buildConfig(initconf, args, modconf);

    final List<Locale> locales = LocaleUtils.parse(conf.getString("application.lang"));

    Env env = this.env.build(conf, this, locales.get(0));
    String envname = env.name();

    final Charset charset = Charset.forName(conf.getString("application.charset"));

    String dateFormat = conf.getString("application.dateFormat");
    ZoneId zoneId = ZoneId.of(conf.getString("application.tz"));
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter
        .ofPattern(dateFormat, locales.get(0))
        .withZone(zoneId);

    DecimalFormat numberFormat = new DecimalFormat(conf.getString("application.numberFormat"));

    // Guice Stage
    Stage stage = "dev".equals(envname) ? Stage.DEVELOPMENT : Stage.PRODUCTION;

    // expand and normalize bag
    RouteMetadata rm = new RouteMetadata(env);
    List<Object> realbag = processEnvDep(this.bag, env);
    List<Config> realmodconf = modconf(realbag);
    List<Object> bag = normalize(realbag, env, rm, prefix);

    // collect routes and fire route callback
    if (rcallback != null) {
      List<Route.Definition> routes = bag.stream()
          .filter(it -> it instanceof Route.Definition)
          .map(it -> (Route.Definition) it)
          .collect(Collectors.<Route.Definition> toList());
      rcallback.accept(routes);
    }

    // final config ? if we add a mod that depends on env
    Config finalConfig;
    Env finalEnv;
    if (modconf.size() != realmodconf.size()) {
      finalConfig = buildConfig(initconf, args, realmodconf);
      finalEnv = this.env.build(finalConfig, this, locales.get(0));
    } else {
      finalConfig = conf;
      finalEnv = env;
    }

    boolean cookieSession = session.store() == null;
    if (cookieSession && !finalConfig.hasPath("application.secret")) {
      throw new IllegalStateException("Required property 'application.secret' is missing");
    }

    /** executors: */
    if (!defaultExecSet) {
      // default executor
      executor(MoreExecutors.directExecutor());
    }
    executor("direct", MoreExecutors.directExecutor());
    executor("server", ServerExecutorProvider.class);

    /** Some basic xss functions. */
    xss(finalEnv);

    /** dependency injection */
    @SuppressWarnings("unchecked")
    com.google.inject.Module joobyModule = binder -> {

      /** type converters */
      new TypeConverters().configure(binder);

      /** bind config */
      bindConfig(binder, finalConfig);

      /** bind env */
      binder.bind(Env.class).toInstance(finalEnv);

      /** bind charset */
      binder.bind(Charset.class).toInstance(charset);

      /** bind locale */
      binder.bind(Locale.class).toInstance(locales.get(0));
      TypeLiteral<List<Locale>> localeType = (TypeLiteral<List<Locale>>) TypeLiteral
          .get(Types.listOf(Locale.class));
      binder.bind(localeType).toInstance(locales);

      /** bind time zone */
      binder.bind(ZoneId.class).toInstance(zoneId);
      binder.bind(TimeZone.class).toInstance(TimeZone.getTimeZone(zoneId));

      /** bind date format */
      binder.bind(DateTimeFormatter.class).toInstance(dateTimeFormatter);

      /** bind number format */
      binder.bind(NumberFormat.class).toInstance(numberFormat);
      binder.bind(DecimalFormat.class).toInstance(numberFormat);

      /** bind ssl provider. */
      binder.bind(SSLContext.class).toProvider(SslContextProvider.class);

      /** routes */
      Multibinder<Definition> definitions = Multibinder
          .newSetBinder(binder, Definition.class);

      /** web sockets */
      Multibinder<WebSocket.Definition> sockets = Multibinder
          .newSetBinder(binder, WebSocket.Definition.class);

      /** tmp dir */
      File tmpdir = new File(finalConfig.getString("application.tmpdir"));
      tmpdir.mkdirs();
      binder.bind(File.class).annotatedWith(Names.named("application.tmpdir"))
          .toInstance(tmpdir);

      binder.bind(ParameterNameProvider.class).toInstance(rm);

      /** err handler */
      Multibinder<Err.Handler> ehandlers = Multibinder
          .newSetBinder(binder, Err.Handler.class);

      /** parsers & renderers */
      Multibinder<Parser> parsers = Multibinder
          .newSetBinder(binder, Parser.class);

      Multibinder<Renderer> renderers = Multibinder
          .newSetBinder(binder, Renderer.class);

      /** basic parser */
      parsers.addBinding().toInstance(BuiltinParser.Basic);
      parsers.addBinding().toInstance(BuiltinParser.Collection);
      parsers.addBinding().toInstance(BuiltinParser.Optional);
      parsers.addBinding().toInstance(BuiltinParser.Enum);
      parsers.addBinding().toInstance(BuiltinParser.Upload);
      parsers.addBinding().toInstance(BuiltinParser.Bytes);

      /** basic render */
      renderers.addBinding().toInstance(BuiltinRenderer.asset);
      renderers.addBinding().toInstance(BuiltinRenderer.bytes);
      renderers.addBinding().toInstance(BuiltinRenderer.byteBuffer);
      renderers.addBinding().toInstance(BuiltinRenderer.file);
      renderers.addBinding().toInstance(BuiltinRenderer.charBuffer);
      renderers.addBinding().toInstance(BuiltinRenderer.stream);
      renderers.addBinding().toInstance(BuiltinRenderer.reader);
      renderers.addBinding().toInstance(BuiltinRenderer.fileChannel);

      /** modules, routes, parsers, renderers and websockets */
      Set<Object> routeClasses = new HashSet<>();
      for (Object it : bag) {
        Try.run(() -> bindService(
            this.bag,
            finalConfig,
            finalEnv,
            rm,
            binder,
            definitions,
            sockets,
            ehandlers,
            parsers,
            renderers,
            routeClasses).accept(it))
            .getOrElseThrow(Throwables::propagate);
      }

      parsers.addBinding().toInstance(new DateParser(dateFormat));
      parsers.addBinding().toInstance(new LocalDateParser(dateTimeFormatter));
      parsers.addBinding().toInstance(new LocaleParser());
      parsers.addBinding().toInstance(new StaticMethodParser("valueOf"));
      parsers.addBinding().toInstance(new StaticMethodParser("fromString"));
      parsers.addBinding().toInstance(new StaticMethodParser("forName"));
      parsers.addBinding().toInstance(new StringConstructorParser());
      parsers.addBinding().toInstance(beanParser.orElseGet(() -> new BeanParser(false)));

      binder.bind(ParserExecutor.class).in(Singleton.class);

      /** override(able) renderer */
      boolean stacktrace = finalConfig.hasPath("err.stacktrace")
          ? finalConfig.getBoolean("err.stacktrace")
          : "dev".equals(envname);
      renderers.addBinding().toInstance(new DefaulErrRenderer(stacktrace));
      renderers.addBinding().toInstance(BuiltinRenderer.text);

      binder.bind(HttpHandler.class).to(HttpHandlerImpl.class).in(Singleton.class);

      RequestScope requestScope = new RequestScope();
      binder.bind(RequestScope.class).toInstance(requestScope);
      binder.bindScope(RequestScoped.class, requestScope);

      /** session manager */
      binder.bind(Session.Definition.class)
          .toProvider(session(finalConfig.getConfig("session"), session))
          .asEagerSingleton();
      Object sstore = session.store();
      if (cookieSession) {
        binder.bind(SessionManager.class).to(CookieSessionManager.class)
            .asEagerSingleton();
      } else {
        binder.bind(SessionManager.class).to(ServerSessionManager.class).asEagerSingleton();
        if (sstore instanceof Class) {
          binder.bind(Store.class).to((Class<? extends Store>) sstore)
              .asEagerSingleton();
        } else {
          binder.bind(Store.class).toInstance((Store) sstore);
        }
      }

      binder.bind(Request.class).toProvider(Providers.outOfScope(Request.class))
          .in(RequestScoped.class);
      binder.bind(Response.class).toProvider(Providers.outOfScope(Response.class))
          .in(RequestScoped.class);
      /** server sent event */
      binder.bind(Sse.class).toProvider(Providers.outOfScope(Sse.class))
          .in(RequestScoped.class);

      binder.bind(Session.class).toProvider(Providers.outOfScope(Session.class))
          .in(RequestScoped.class);

      /** def err */
      ehandlers.addBinding().toInstance(new Err.DefHandler());

      /** executors. */
      executors.forEach(it -> it.accept(binder));
    };

    Injector injector = injectorFactory.apply(stage, joobyModule);
    if (apprefs != null) {
      apprefs.forEach(app -> app.injector = injector);
      apprefs.clear();
      apprefs = null;
    }

    onStart.addAll(0, finalEnv.startTasks());
    onStarted.addAll(0, finalEnv.startedTasks());
    onStop.addAll(finalEnv.stopTasks());

    // clear bag and freeze it
    this.bag.clear();
    this.bag = ImmutableSet.of();
    this.executors.clear();
    this.executors = ImmutableList.of();

    return injector;
  }

  private void xss(final Env env) {
    Escaper ufe = UrlEscapers.urlFragmentEscaper();
    Escaper fpe = UrlEscapers.urlFormParameterEscaper();
    Escaper pse = UrlEscapers.urlPathSegmentEscaper();
    Escaper html = HtmlEscapers.htmlEscaper();

    env.xss("urlFragment", ufe::escape)
        .xss("formParam", fpe::escape)
        .xss("pathSegment", pse::escape)
        .xss("html", html::escape);
  }

  private static Provider<Session.Definition> session(final Config $session,
      final Session.Definition session) {
    return () -> {
      // save interval
      session.saveInterval(session.saveInterval()
          .orElse($session.getDuration("saveInterval", TimeUnit.MILLISECONDS)));

      // build cookie
      Cookie.Definition source = session.cookie();

      source.name(source.name()
          .orElse($session.getString("cookie.name")));

      if (!source.comment().isPresent() && $session.hasPath("cookie.comment")) {
        source.comment($session.getString("cookie.comment"));
      }
      if (!source.domain().isPresent() && $session.hasPath("cookie.domain")) {
        source.domain($session.getString("cookie.domain"));
      }
      source.httpOnly(source.httpOnly()
          .orElse($session.getBoolean("cookie.httpOnly")));

      Object maxAge = $session.getAnyRef("cookie.maxAge");
      if (maxAge instanceof String) {
        maxAge = $session.getDuration("cookie.maxAge", TimeUnit.SECONDS);
      }
      source.maxAge(source.maxAge()
          .orElse(((Number) maxAge).intValue()));

      source.path(source.path()
          .orElse($session.getString("cookie.path")));

      source.secure(source.secure()
          .orElse($session.getBoolean("cookie.secure")));

      return session;
    };
  }

  private static CheckedConsumer<? super Object> bindService(final Set<Object> src,
      final Config conf,
      final Env env,
      final RouteMetadata rm,
      final Binder binder,
      final Multibinder<Route.Definition> definitions,
      final Multibinder<WebSocket.Definition> sockets,
      final Multibinder<Err.Handler> ehandlers,
      final Multibinder<Parser> parsers,
      final Multibinder<Renderer> renderers,
      final Set<Object> routeClasses) {
    return it -> {
      if (it instanceof Jooby.Module) {
        int from = src.size();
        install((Jooby.Module) it, env, conf, binder);
        int to = src.size();
        // collect any route a module might add
        if (to > from) {
          List<Object> elements = normalize(new ArrayList<>(src).subList(from, to), env, rm, null);
          for (Object e : elements) {
            bindService(src,
                conf,
                env,
                rm,
                binder,
                definitions,
                sockets,
                ehandlers,
                parsers,
                renderers,
                routeClasses).accept(e);
          }
        }
      } else if (it instanceof Route.Definition) {
        Route.Definition rdef = (Definition) it;
        Route.Filter h = rdef.filter();
        if (h instanceof Route.MethodHandler) {
          Class<?> routeClass = ((Route.MethodHandler) h).method().getDeclaringClass();
          if (routeClasses.add(routeClass)) {
            binder.bind(routeClass);
          }
        }
        definitions.addBinding().toInstance(rdef);
      } else if (it instanceof WebSocket.Definition) {
        sockets.addBinding().toInstance((WebSocket.Definition) it);
      } else if (it instanceof Parser) {
        parsers.addBinding().toInstance((Parser) it);
      } else if (it instanceof Renderer) {
        renderers.addBinding().toInstance((Renderer) it);
      } else {
        ehandlers.addBinding().toInstance((Err.Handler) it);
      }
    };
  }

  private static List<Config> modconf(final Collection<Object> bag) {
    return bag.stream()
        .filter(it -> it instanceof Jooby.Module)
        .map(it -> ((Jooby.Module) it).config())
        .filter(c -> !c.isEmpty())
        .collect(Collectors.toList());
  }

  /**
   * Test if the application is up and running.
   *
   * @return True if the application is up and running.
   */
  public boolean isStarted() {
    return started.get();
  }

  /**
   * Stop the application, close all the modules and stop the web server.
   */
  public void stop() {
    if (started.compareAndSet(true, false)) {
      Logger log = logger(this);

      fireStop(this, log, onStop);
      if (injector != null) {
        try {
          injector.getInstance(Server.class).stop();
        } catch (Throwable ex) {
          log.debug("server.stop() resulted in exception", ex);
        }
      }
      injector = null;

      log.info("Stopped");
    }
  }

  private static void fireStop(final Jooby app, final Logger log,
      final List<CheckedConsumer<Registry>> onStop) {
    // stop services
    onStop.forEach(c -> Try.run(() -> c.accept(app))
        .onFailure(x -> log.error("shutdown of {} resulted in error", c, x)));
  }

  /**
   * Build configuration properties, it configure system, app and modules properties.
   *
   * @param source Source config to use.
   * @param args Args conf.
   * @param modules List of modules.
   * @return A configuration properties ready to use.
   */
  private Config buildConfig(final Config source, final Config args,
      final List<Config> modules) {
    // normalize tmpdir
    Config system = ConfigFactory.systemProperties();
    Config tmpdir = source.hasPath("java.io.tmpdir") ? source : system;

    // system properties
    system = system
        // file encoding got corrupted sometimes, override it.
        .withValue("file.encoding", fromAnyRef(System.getProperty("file.encoding")))
        .withValue("java.io.tmpdir",
            fromAnyRef(Paths.get(tmpdir.getString("java.io.tmpdir")).normalize().toString()));

    // set module config
    Config moduleStack = ConfigFactory.empty();
    for (Config module : ImmutableList.copyOf(modules).reverse()) {
      moduleStack = moduleStack.withFallback(module);
    }

    String env = Arrays.asList(system, args, source).stream()
        .filter(it -> it.hasPath("application.env"))
        .findFirst()
        .map(c -> c.getString("application.env"))
        .orElse("dev");

    String cpath = Arrays.asList(system, args, source).stream()
        .filter(it -> it.hasPath("application.path"))
        .findFirst()
        .map(c -> c.getString("application.path"))
        .orElse("/");

    Config envcof = envConf(source, env);

    // application.[env].conf -> application.conf
    Config conf = envcof.withFallback(source);

    return system
        .withFallback(args)
        .withFallback(conf)
        .withFallback(moduleStack)
        .withFallback(MediaType.types)
        .withFallback(defaultConfig(conf, Route.normalize(cpath)))
        .resolve();
  }

  /**
   * Build a conf from arguments.
   *
   * @param args Application arguments.
   * @return A conf.
   */
  static Config args(final String[] args) {
    if (args == null || args.length == 0) {
      return ConfigFactory.empty();
    }
    Map<String, String> conf = new HashMap<>();
    for (String arg : args) {
      String[] values = arg.split("=");
      String name;
      String value;
      if (values.length == 2) {
        name = values[0];
        value = values[1];
      } else {
        name = "application.env";
        value = values[0];
      }
      if (name.indexOf(".") == -1) {
        conf.put("application." + name, value);
      }
      conf.put(name, value);
    }
    return ConfigFactory.parseMap(conf, "args");
  }

  /**
   * Build a env config: <code>[application].[env].[conf]</code>.
   * Stack looks like
   *
   * <pre>
   *   (file://[origin].[env].[conf])?
   *   (cp://[origin].[env].[conf])?
   *   file://application.[env].[conf]
   *   /application.[env].[conf]
   * </pre>
   *
   * @param source App source to use.
   * @param env Application env.
   * @return A config env.
   */
  private Config envConf(final Config source, final String env) {
    String origin = source.origin().resource();
    Config result = ConfigFactory.empty();
    if (origin != null) {
      // load [resource].[env].[ext]
      int dot = origin.lastIndexOf('.');
      String originConf = origin.substring(0, dot) + "." + env + origin.substring(dot);

      result = fileConfig(originConf).withFallback(ConfigFactory.parseResources(originConf));
    }
    String appConfig = "application." + env + ".conf";
    return result
        .withFallback(fileConfig(appConfig))
        .withFallback(fileConfig("application.conf"))
        .withFallback(ConfigFactory.parseResources(appConfig));
  }

  /**
   * Config from file system.
   *
   * @param fname A file name.
   * @return A config for the file name.
   */
  static Config fileConfig(final String fname) {
    File dir = new File(System.getProperty("user.dir"));
    File froot = new File(dir, fname);
    if (froot.exists()) {
      return ConfigFactory.parseFile(froot);
    } else {
      File fconfig = new File(new File(dir, "conf"), fname);
      if (fconfig.exists()) {
        return ConfigFactory.parseFile(fconfig);
      }
    }
    return ConfigFactory.empty();
  }

  /**
   * Build default application.* properties.
   *
   * @param conf A source config.
   * @param cpath Application path.
   * @return default properties.
   */
  private Config defaultConfig(final Config conf, final String cpath) {
    String ns = getClass().getPackage().getName();
    String[] parts = ns.split("\\.");
    String appname = parts[parts.length - 1];

    // locale
    final List<Locale> locales;
    if (!conf.hasPath("application.lang")) {
      locales = Optional.ofNullable(this.languages)
          .map(langs -> LocaleUtils.parse(Joiner.on(",").join(langs)))
          .orElse(ImmutableList.of(Locale.getDefault()));
    } else {
      locales = LocaleUtils.parse(conf.getString("application.lang"));
    }
    Locale locale = locales.iterator().next();
    String lang = locale.toLanguageTag();

    // time zone
    final String tz;
    if (!conf.hasPath("application.tz")) {
      tz = Optional.ofNullable(zoneId).orElse(ZoneId.systemDefault()).getId();
    } else {
      tz = conf.getString("application.tz");
    }

    // number format
    final String nf;
    if (!conf.hasPath("application.numberFormat")) {
      nf = Optional.ofNullable(numberFormat)
          .orElseGet(() -> ((DecimalFormat) DecimalFormat.getInstance(locale)).toPattern());
    } else {
      nf = conf.getString("application.numberFormat");
    }

    int processors = Runtime.getRuntime().availableProcessors();
    String version = Optional.ofNullable(getClass().getPackage().getImplementationVersion())
        .orElse("0.0.0");
    Config defs = ConfigFactory.parseResources(Jooby.class, "jooby.conf")
        .withValue("contextPath", ConfigValueFactory.fromAnyRef(cpath.equals("/") ? "" : cpath))
        .withValue("application.name", ConfigValueFactory.fromAnyRef(appname))
        .withValue("application.version", ConfigValueFactory.fromAnyRef(version))
        .withValue("application.class", ConfigValueFactory.fromAnyRef(getClass().getName()))
        .withValue("application.ns", ConfigValueFactory.fromAnyRef(ns))
        .withValue("application.lang", ConfigValueFactory.fromAnyRef(lang))
        .withValue("application.tz", ConfigValueFactory.fromAnyRef(tz))
        .withValue("application.numberFormat", ConfigValueFactory.fromAnyRef(nf))
        .withValue("server.http2.enabled", ConfigValueFactory.fromAnyRef(http2))
        .withValue("runtime.processors", ConfigValueFactory.fromAnyRef(processors))
        .withValue("runtime.processors-plus1", ConfigValueFactory.fromAnyRef(processors + 1))
        .withValue("runtime.processors-plus2", ConfigValueFactory.fromAnyRef(processors + 2))
        .withValue("runtime.processors-x2", ConfigValueFactory.fromAnyRef(processors * 2))
        .withValue("runtime.processors-x4", ConfigValueFactory.fromAnyRef(processors * 4))
        .withValue("runtime.processors-x8", ConfigValueFactory.fromAnyRef(processors * 8))
        .withValue("runtime.concurrencyLevel", ConfigValueFactory
            .fromAnyRef(Math.max(4, processors)));

    if (charset != null) {
      defs = defs.withValue("application.charset", ConfigValueFactory.fromAnyRef(charset.name()));
    }
    if (port != null) {
      defs = defs.withValue("application.port", ConfigValueFactory.fromAnyRef(port.intValue()));
    }
    if (securePort != null) {
      defs = defs.withValue("application.securePort",
          ConfigValueFactory.fromAnyRef(securePort.intValue()));
    }
    if (dateFormat != null) {
      defs = defs.withValue("application.dateFormat", ConfigValueFactory.fromAnyRef(dateFormat));
    }
    return defs;
  }

  /**
   * Install a {@link JoobyModule}.
   *
   * @param module The module to install.
   * @param env Application env.
   * @param config The configuration object.
   * @param binder A Guice binder.
   * @throws Throwable If module bootstrap fails.
   */
  private static void install(final Jooby.Module module, final Env env, final Config config,
      final Binder binder) throws Throwable {
    module.configure(env, config, binder);
  }

  /**
   * Bind a {@link Config} and make it available for injection. Each property of the config is also
   * binded it and ready to be injected with {@link javax.inject.Named}.
   *
   * @param binder Guice binder.
   * @param config App config.
   */
  @SuppressWarnings("unchecked")
  private void bindConfig(final Binder binder, final Config config) {
    // root nodes
    traverse(binder, "", config.root());

    // terminal nodes
    for (Entry<String, ConfigValue> entry : config.entrySet()) {
      String name = entry.getKey();
      Named named = Names.named(name);
      Object value = entry.getValue().unwrapped();
      if (value instanceof List) {
        List<Object> values = (List<Object>) value;
        Type listType = values.size() == 0
            ? String.class
            : Types.listOf(values.iterator().next().getClass());
        Key<Object> key = (Key<Object>) Key.get(listType, Names.named(name));
        binder.bind(key).toInstance(values);
      } else {
        binder.bindConstant().annotatedWith(named).to(value.toString());
      }
    }
    // bind config
    binder.bind(Config.class).toInstance(config);
  }

  private static void traverse(final Binder binder, final String p, final ConfigObject root) {
    root.forEach((n, v) -> {
      if (v instanceof ConfigObject) {
        ConfigObject child = (ConfigObject) v;
        String path = p + n;
        Named named = Names.named(path);
        binder.bind(Config.class).annotatedWith(named).toInstance(child.toConfig());
        traverse(binder, path + ".", child);
      }
    });
  }

  private static Predicate<String> envpredicate(final String candidate) {
    return env -> env.equalsIgnoreCase(candidate) || candidate.equals("*");
  }

  static String logback(final Config conf) {
    // Avoid warning message from logback when multiples files are present
    String logback;
    if (conf.hasPath("logback.configurationFile")) {
      logback = conf.getString("logback.configurationFile");
    } else {
      String env = conf.hasPath("application.env") ? conf.getString("application.env") : null;
      ImmutableList.Builder<File> files = ImmutableList.builder();
      File userdir = new File(System.getProperty("user.dir"));
      File confdir = new File(userdir, "conf");
      if (env != null) {
        files.add(new File(userdir, "logback." + env + ".xml"));
        files.add(new File(confdir, "logback." + env + ".xml"));
      }
      files.add(new File(userdir, "logback.xml"));
      files.add(new File(confdir, "logback.xml"));
      logback = files.build()
          .stream()
          .filter(f -> f.exists())
          .map(f -> f.getAbsolutePath())
          .findFirst()
          .orElseGet(() -> {
            return Optional.ofNullable(Jooby.class.getResource("/logback." + env + ".xml"))
                .map(Objects::toString)
                .orElse("logback.xml");
          });
    }
    return logback;
  }

  private static Logger logger(final Jooby app) {
    return LoggerFactory.getLogger(app.getClass());
  }

  public void configureAssetHandler(final AssetHandler handler) {
    onStart(r -> {
      Config conf = r.require(Config.class);
      handler
          .cdn(conf.getString("assets.cdn"))
          .lastModified(conf.getBoolean("assets.lastModified"))
          .etag(conf.getBoolean("assets.etag"))
          .maxAge(conf.getString("assets.cache.maxAge"));
    });
  }

}
