/**
 * Apache License
 * Version 2.0, January 2004
 * http://www.apache.org/licenses/
 *
 * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 *
 * 1. Definitions.
 *
 * "License" shall mean the terms and conditions for use, reproduction,
 * and distribution as defined by Sections 1 through 9 of this document.
 *
 * "Licensor" shall mean the copyright owner or entity authorized by
 * the copyright owner that is granting the License.
 *
 * "Legal Entity" shall mean the union of the acting entity and all
 * other entities that control, are controlled by, or are under common
 * control with that entity. For the purposes of this definition,
 * "control" means (i) the power, direct or indirect, to cause the
 * direction or management of such entity, whether by contract or
 * otherwise, or (ii) ownership of fifty percent (50%) or more of the
 * outstanding shares, or (iii) beneficial ownership of such entity.
 *
 * "You" (or "Your") shall mean an individual or Legal Entity
 * exercising permissions granted by this License.
 *
 * "Source" form shall mean the preferred form for making modifications,
 * including but not limited to software source code, documentation
 * source, and configuration files.
 *
 * "Object" form shall mean any form resulting from mechanical
 * transformation or translation of a Source form, including but
 * not limited to compiled object code, generated documentation,
 * and conversions to other media types.
 *
 * "Work" shall mean the work of authorship, whether in Source or
 * Object form, made available under the License, as indicated by a
 * copyright notice that is included in or attached to the work
 * (an example is provided in the Appendix below).
 *
 * "Derivative Works" shall mean any work, whether in Source or Object
 * form, that is based on (or derived from) the Work and for which the
 * editorial revisions, annotations, elaborations, or other modifications
 * represent, as a whole, an original work of authorship. For the purposes
 * of this License, Derivative Works shall not include works that remain
 * separable from, or merely link (or bind by name) to the interfaces of,
 * the Work and Derivative Works thereof.
 *
 * "Contribution" shall mean any work of authorship, including
 * the original version of the Work and any modifications or additions
 * to that Work or Derivative Works thereof, that is intentionally
 * submitted to Licensor for inclusion in the Work by the copyright owner
 * or by an individual or Legal Entity authorized to submit on behalf of
 * the copyright owner. For the purposes of this definition, "submitted"
 * means any form of electronic, verbal, or written communication sent
 * to the Licensor or its representatives, including but not limited to
 * communication on electronic mailing lists, source code control systems,
 * and issue tracking systems that are managed by, or on behalf of, the
 * Licensor for the purpose of discussing and improving the Work, but
 * excluding communication that is conspicuously marked or otherwise
 * designated in writing by the copyright owner as "Not a Contribution."
 *
 * "Contributor" shall mean Licensor and any individual or Legal Entity
 * on behalf of whom a Contribution has been received by Licensor and
 * subsequently incorporated within the Work.
 *
 * 2. Grant of Copyright License. Subject to the terms and conditions of
 * this License, each Contributor hereby grants to You a perpetual,
 * worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 * copyright license to reproduce, prepare Derivative Works of,
 * publicly display, publicly perform, sublicense, and distribute the
 * Work and such Derivative Works in Source or Object form.
 *
 * 3. Grant of Patent License. Subject to the terms and conditions of
 * this License, each Contributor hereby grants to You a perpetual,
 * worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 * (except as stated in this section) patent license to make, have made,
 * use, offer to sell, sell, import, and otherwise transfer the Work,
 * where such license applies only to those patent claims licensable
 * by such Contributor that are necessarily infringed by their
 * Contribution(s) alone or by combination of their Contribution(s)
 * with the Work to which such Contribution(s) was submitted. If You
 * institute patent litigation against any entity (including a
 * cross-claim or counterclaim in a lawsuit) alleging that the Work
 * or a Contribution incorporated within the Work constitutes direct
 * or contributory patent infringement, then any patent licenses
 * granted to You under this License for that Work shall terminate
 * as of the date such litigation is filed.
 *
 * 4. Redistribution. You may reproduce and distribute copies of the
 * Work or Derivative Works thereof in any medium, with or without
 * modifications, and in Source or Object form, provided that You
 * meet the following conditions:
 *
 * (a) You must give any other recipients of the Work or
 * Derivative Works a copy of this License; and
 *
 * (b) You must cause any modified files to carry prominent notices
 * stating that You changed the files; and
 *
 * (c) You must retain, in the Source form of any Derivative Works
 * that You distribute, all copyright, patent, trademark, and
 * attribution notices from the Source form of the Work,
 * excluding those notices that do not pertain to any part of
 * the Derivative Works; and
 *
 * (d) If the Work includes a "NOTICE" text file as part of its
 * distribution, then any Derivative Works that You distribute must
 * include a readable copy of the attribution notices contained
 * within such NOTICE file, excluding those notices that do not
 * pertain to any part of the Derivative Works, in at least one
 * of the following places: within a NOTICE text file distributed
 * as part of the Derivative Works; within the Source form or
 * documentation, if provided along with the Derivative Works; or,
 * within a display generated by the Derivative Works, if and
 * wherever such third-party notices normally appear. The contents
 * of the NOTICE file are for informational purposes only and
 * do not modify the License. You may add Your own attribution
 * notices within Derivative Works that You distribute, alongside
 * or as an addendum to the NOTICE text from the Work, provided
 * that such additional attribution notices cannot be construed
 * as modifying the License.
 *
 * You may add Your own copyright statement to Your modifications and
 * may provide additional or different license terms and conditions
 * for use, reproduction, or distribution of Your modifications, or
 * for any such Derivative Works as a whole, provided Your use,
 * reproduction, and distribution of the Work otherwise complies with
 * the conditions stated in this License.
 *
 * 5. Submission of Contributions. Unless You explicitly state otherwise,
 * any Contribution intentionally submitted for inclusion in the Work
 * by You to the Licensor shall be under the terms and conditions of
 * this License, without any additional terms or conditions.
 * Notwithstanding the above, nothing herein shall supersede or modify
 * the terms of any separate license agreement you may have executed
 * with Licensor regarding such Contributions.
 *
 * 6. Trademarks. This License does not grant permission to use the trade
 * names, trademarks, service marks, or product names of the Licensor,
 * except as required for reasonable and customary use in describing the
 * origin of the Work and reproducing the content of the NOTICE file.
 *
 * 7. Disclaimer of Warranty. Unless required by applicable law or
 * agreed to in writing, Licensor provides the Work (and each
 * Contributor provides its Contributions) on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied, including, without limitation, any warranties or conditions
 * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
 * PARTICULAR PURPOSE. You are solely responsible for determining the
 * appropriateness of using or redistributing the Work and assume any
 * risks associated with Your exercise of permissions under this License.
 *
 * 8. Limitation of Liability. In no event and under no legal theory,
 * whether in tort (including negligence), contract, or otherwise,
 * unless required by applicable law (such as deliberate and grossly
 * negligent acts) or agreed to in writing, shall any Contributor be
 * liable to You for damages, including any direct, indirect, special,
 * incidental, or consequential damages of any character arising as a
 * result of this License or out of the use or inability to use the
 * Work (including but not limited to damages for loss of goodwill,
 * work stoppage, computer failure or malfunction, or any and all
 * other commercial damages or losses), even if such Contributor
 * has been advised of the possibility of such damages.
 *
 * 9. Accepting Warranty or Additional Liability. While redistributing
 * the Work or Derivative Works thereof, You may choose to offer,
 * and charge a fee for, acceptance of support, warranty, indemnity,
 * or other liability obligations and/or rights consistent with this
 * License. However, in accepting such obligations, You may act only
 * on Your own behalf and on Your sole responsibility, not on behalf
 * of any other Contributor, and only if You agree to indemnify,
 * defend, and hold each Contributor harmless for any liability
 * incurred by, or claims asserted against, such Contributor by reason
 * of your accepting any such warranty or additional liability.
 *
 * END OF TERMS AND CONDITIONS
 *
 * APPENDIX: How to apply the Apache License to your work.
 *
 * To apply the Apache License to your work, attach the following
 * boilerplate notice, with the fields enclosed by brackets "{}"
 * replaced with your own identifying information. (Don't include
 * the brackets!)  The text should be enclosed in the appropriate
 * comment syntax for the file format. We also recommend that a
 * file or class name and description of purpose be included on the
 * same "printed page" as the copyright notice for easier
 * identification within third-party archives.
 *
 * Copyright 2014 Edgar Espina
 *
 * Licensed 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 com.google.common.base.Joiner;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
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.ProvisionException;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.ProviderMethodsModule;
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 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 org.jooby.Route.Definition;
import static org.jooby.Route.GET;
import static org.jooby.Route.HEAD;
import org.jooby.Route.Mapper;
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 org.jooby.Session.Store;
import org.jooby.funzy.Throwing;
import org.jooby.funzy.Try;
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.SourceProvider;
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.parser.ZonedDateTimeParser;
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 javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.inject.Singleton;
import javax.net.ssl.SSLContext;
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.LinkedList;
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;

/**
 * <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(Throwing.Consumer)} and {@link #onStop(Throwing.Consumer)} 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.
     */
    @Nonnull
    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.
     * @throws Throwable If something goes wrong.
     */
    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<Throwing.Consumer<Registry>> onStart = new ArrayList<>();
  private transient List<Throwing.Consumer<Registry>> onStarted = new ArrayList<>();

  /** stop callback . */
  private transient List<Throwing.Consumer<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 transient List<Jooby> apprefs;

  private transient LinkedList<String> path = new LinkedList<>();

  private transient String confname;

  private transient boolean caseSensitiveRouting = true;

  private transient String classname;

  /**
   * 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);
    this.classname = classname(getClass().getName());
  }

  @Override
  public Route.Collection path(String path, Runnable action) {
    this.path.addLast(Route.normalize(path));
    Route.Collection collection = with(action);
    this.path.removeLast();
    return collection;
  }

  @Override
  public Jooby use(final Jooby app) {
    return use(prefixPath(null), app);
  }

  private Optional<String> prefixPath(@Nullable String tail) {
    return path.size() == 0
        ? tail == null ? Optional.empty() : Optional.of(Route.normalize(tail))
        : Optional.of(path.stream()
        .collect(Collectors.joining("", "", tail == null
            ? "" : Route.normalize(tail))));
  }

  @Override
  public Jooby use(final String path, final Jooby app) {
    return use(prefixPath(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 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;
  }

  /**
   * 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 Throwing.Runnable callback) {
    LifeCycle.super.onStart(callback);
    return this;
  }

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

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

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

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

  @Override
  public Jooby onStop(final Throwing.Consumer<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,
        "Registry is not ready. Require calls are available at application startup time, see http://jooby.org/doc/#application-life-cycle");
    try {
      return injector.getInstance(type);
    } catch (ProvisionException x) {
      Throwable cause = x.getCause();
      if (cause instanceof Err) {
        throw (Err) cause;
      }
      throw x;
    }
  }

  @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.Definition before(final String method, final String pattern,
      final Route.Before handler) {
    return appendDefinition(method, pattern, handler);
  }

  @Override
  public Route.Definition after(final String method, final String pattern,
      final Route.After handler) {
    return appendDefinition(method, pattern, handler);
  }

  @Override
  public Route.Definition complete(final String method, final String pattern,
      final Route.Complete handler) {
    return appendDefinition(method, pattern, handler);
  }

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

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

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

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

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

  @Override
  public Route.Definition get(final String path, final Route.Handler handler) {
    if (handler instanceof AssetHandler) {
      return assets(path, (AssetHandler) handler);
    } else {
      return appendDefinition(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(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(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(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(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(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(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(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(HEAD, path, handler);
  }

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

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

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

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

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

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

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

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

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

  @Override
  public Route.Definition put(final String path,
      final Route.Handler handler) {
    return appendDefinition(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(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(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(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(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(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(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(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(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(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(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(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(TRACE, path, handler);
  }

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

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

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

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

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

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

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

  @Override
  public Route.Definition connect(final String path, final Route.Filter filter) {
    return appendDefinition(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 Route.AssetDefinition assets(final String path, final Path basedir) {
    return assets(path, new AssetHandler(basedir));
  }

  @Override
  public Route.AssetDefinition assets(final String path, final String location) {
    return assets(path, new AssetHandler(location));
  }

  @Override
  public Route.AssetDefinition assets(final String path, final AssetHandler handler) {
    Route.AssetDefinition route = appendDefinition(GET, path, handler, Route.AssetDefinition::new);
    return configureAssetHandler(route);
  }

  @Override
  public Route.Collection use(final Class<?> routeClass) {
    return use("", routeClass);
  }

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

  /**
   * Keep track of routes in the order user define them.
   *
   * @param method Route method.
   * @param pattern Route pattern.
   * @param filter Route filter.
   * @return The same route definition.
   */
  private Route.Definition appendDefinition(String method, String pattern, Route.Filter filter) {
    return appendDefinition(method, pattern, filter, Route.Definition::new);
  }

  /**
   * Keep track of routes in the order user define them.
   *
   * @param method Route method.
   * @param pattern Route pattern.
   * @param filter Route filter.
   * @param creator Route creator.
   * @return The same route definition.
   */
  private <T extends Route.Definition> T appendDefinition(String method, String pattern,
      Route.Filter filter, Throwing.Function4<String, String, Route.Filter, Boolean, T> creator) {
    String pathPattern = prefixPath(pattern).orElse(pattern);
    T route = creator.apply(method, pathPattern, filter, caseSensitiveRouting);
    if (prefix != null) {
      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) {
    this.confname = 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) {
    this.confname = path.getName();
    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(GET, path, handler).consumes(MediaType.sse);
  }

  @Override
  public Route.Definition sse(final String path, final Sse.Handler1 handler) {
    return appendDefinition(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(Route.Props.class::isInstance)
        .map(Route.Props.class::cast)
        .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.apply(() -> 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. Fire the {@link #onStart(Throwing.Runnable)} event and the
   * {@link #onStarted(Throwing.Runnable)} events.
   */
  public void start() {
    start(new String[0]);
  }

  /**
   * Start an application. Fire the {@link #onStart(Throwing.Runnable)} event and the
   * {@link #onStarted(Throwing.Runnable)} events.
   *
   * @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(this::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((Throwing.Consumer<Registry>) internalOnStart);
    }

    // start services
    for (Throwing.Consumer<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 (Throwing.Consumer<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 This instance.
   */
  public Jooby throwBootstrapException() {
    this.throwBootstrapException = true;
    return this;
  }

  /**
   * Configure case for routing algorithm. Default is <code>case sensitive</code>.
   *
   * @param enabled True for case sensitive, false otherwise.
   * @return This instance.
   */
  public Jooby caseSensitiveRouting(boolean enabled) {
    this.caseSensitiveRouting = enabled;
    return this;
  }

  private static List<Object> normalize(final List<Object> services, final Env env,
      final RouteMetadata classInfo, final boolean caseSensitiveRouting) {
    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 MvcClass) {
        MvcClass mvcRoute = ((MvcClass) candidate);
        Class<?> mvcClass = mvcRoute.routeClass;
        String path = ((MvcClass) candidate).path;
        MvcRoutes.routes(env, classInfo, path, caseSensitiveRouting, 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 initconf = Optional.ofNullable(srcconf)
        .orElseGet(() -> ConfigFactory.parseResources("application.conf"));
    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);
    DateTimeFormatter zonedDateTimeFormat = DateTimeFormatter
        .ofPattern(conf.getString("application.zonedDateTimeFormat"));

    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, caseSensitiveRouting);

    // 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.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(
            logger(this),
            this.bag,
            finalConfig,
            finalEnv,
            rm,
            binder,
            definitions,
            sockets,
            ehandlers,
            parsers,
            renderers,
            routeClasses,
            caseSensitiveRouting)
            .accept(it))
            .throwException();
      }

      parsers.addBinding().toInstance(new DateParser(dateFormat));
      parsers.addBinding().toInstance(new LocalDateParser(dateTimeFormatter));
      parsers.addBinding().toInstance(new ZonedDateTimeParser(zonedDateTimeFormat));
      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 */
      renderers.addBinding().toInstance(new DefaulErrRenderer());
      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(Route.Chain.class).toProvider(Providers.outOfScope(Route.Chain.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 Throwing.Consumer<? super Object> bindService(Logger log,
      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,
      final boolean caseSensitiveRouting) {
    return it -> {
      if (it instanceof Jooby.Module) {
        int from = src.size();
        install(log, (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,
              caseSensitiveRouting);
          for (Object e : elements) {
            bindService(log, src,
                conf,
                env,
                rm,
                binder,
                definitions,
                sockets,
                ehandlers,
                parsers,
                renderers,
                routeClasses, caseSensitiveRouting).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).implementingClass();
          if (routeClasses.add(routeClass)) {
            binder.bind(routeClass);
          }
          definitions.addBinding().toInstance(rdef);
        } else {
          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, fire the {@link #onStop(Throwing.Runnable)} event and shutdown the
   * web server.
   *
   * Stop listeners run in the order they were added:
   *
   * <pre>{@code
   * {
   *
   *   onStop(() -> System.out.println("first"));
   *
   *   onStop(() -> System.out.println("second"));
   *
   *   ...
   * }
   * }</pre>
   *
   *
   */
  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<Throwing.Consumer<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 envconf = envConf(source, env);

    // application.[env].conf -> application.conf
    Config conf = envconf.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 name = Optional.ofNullable(this.confname).orElse(source.origin().resource());
    Config result = ConfigFactory.empty();
    if (name != null) {
      // load [resource].[env].[ext]
      int dot = name.lastIndexOf('.');
      name = name.substring(0, dot);
    } else {
      name = "application";
    }
    String envconfname = name + "." + env + ".conf";
    Config envconf = fileConfig(envconfname);
    Config appconf = fileConfig(name + ".conf");
    return result
        // file system:
        .withFallback(envconf)
        .withFallback(appconf)
        // classpath:
        .withFallback(ConfigFactory.parseResources(envconfname));
  }

  /**
   * Config from file system.
   *
   * @param fname A file name.
   * @return A config for the file name.
   */
  static Config fileConfig(final String fname) {
    // TODO: sanitization of arguments
    File dir = new File(System.getProperty("user.dir"));
    // TODO: sanitization of arguments
    File froot = new File(dir, fname);
    if (froot.exists()) {
      return ConfigFactory.parseFile(froot);
    } else {
      // TODO: sanitization of arguments
      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 = Optional.ofNullable(getClass().getPackage())
        .map(Package::getName)
        .orElse("default." + getClass().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())
        .map(Package::getImplementationVersion)
        .filter(Objects::nonNull)
        .orElse("0.0.0");
    Config defs = ConfigFactory.parseResources(Jooby.class, "jooby.conf")
        .withValue("contextPath", fromAnyRef(cpath.equals("/") ? "" : cpath))
        .withValue("application.name", fromAnyRef(appname))
        .withValue("application.version", fromAnyRef(version))
        .withValue("application.class", fromAnyRef(classname))
        .withValue("application.ns", fromAnyRef(ns))
        .withValue("application.lang", fromAnyRef(lang))
        .withValue("application.tz", fromAnyRef(tz))
        .withValue("application.numberFormat", fromAnyRef(nf))
        .withValue("server.http2.enabled", fromAnyRef(http2))
        .withValue("runtime.processors", fromAnyRef(processors))
        .withValue("runtime.processors-plus1", fromAnyRef(processors + 1))
        .withValue("runtime.processors-plus2", fromAnyRef(processors + 2))
        .withValue("runtime.processors-x2", fromAnyRef(processors * 2))
        .withValue("runtime.processors-x4", fromAnyRef(processors * 4))
        .withValue("runtime.processors-x8", fromAnyRef(processors * 8))
        .withValue("runtime.concurrencyLevel", fromAnyRef(Math.max(4, processors)))
        .withValue("server.threads.Min", fromAnyRef(Math.max(4, processors)))
        .withValue("server.threads.Max", fromAnyRef(Math.max(32, processors * 8)));

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

  /**
   * Install a {@link Jooby.Module}.
   *
   * @param log Logger.
   * @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 Logger log, final Jooby.Module module, final Env env, final Config config,
      final Binder binder) throws Throwable {
    module.configure(env, config, binder);
    try {
      binder.install(ProviderMethodsModule.forObject(module));
    } catch (NoClassDefFoundError x) {
      // Allow dynamic linking of optional dependencies (required by micrometer module), we ignore
      // missing classes here, if there is a missing class Jooby is going to fails early (not here)
      log.debug("ignoring class not found from guice provider method", x);
    }
  }

  /**
   * 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();
      // TODO: sanitization of arguments
      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(File::exists)
          .map(File::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());
  }

  private Route.AssetDefinition configureAssetHandler(final Route.AssetDefinition 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"));
    });
    return handler;
  }

  /**
   * Class name is this, except for script bootstrap.
   *
   * @param name Default classname.
   * @return Classname.
   */
  private String classname(String name) {
    if (name.equals(Jooby.class.getName()) || name.equals("org.jooby.Kooby")) {
      return SourceProvider.INSTANCE.get()
          .map(StackTraceElement::getClassName)
          .orElse(name);
    }
    return name;
  }
}
