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

import static java.util.Objects.requireNonNull;

import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.jooby.Env;
import org.jooby.Jooby;
import org.jooby.Renderer;
import org.jooby.View;
import org.thymeleaf.ITemplateEngine;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

import com.google.inject.Binder;
import com.google.inject.multibindings.Multibinder;
import com.typesafe.config.Config;

/**
 * <h1>thymeleaf</h1>
 * <p>
 * <a href="http://www.thymeleaf.org">Thymeleaf</a> is a modern server-side Java template engine for
 * both web and standalone environments.
 * </p>
 *
 * <h2>exports</h2>
 * <ul>
 * <li>{@link TemplateEngine}</li>
 * <li>{@link View.Engine}</li>
 * </ul>
 *
 * <h2>usage</h2>
 *
 * <pre>{@code
 * {
 *  use(new Thl());
 *
 *  get("/", () -> {
 *    return Results.html("index")
 *        .put("model", new MyModel());
 *  });
 *
 *  // Or Thymeleaf API:
 *  get("/thymeleaf-api", () -> {
 *    TemplateEngine engine = require(TemplateEngine.class);
 *    engine.processs("template", ...);
 *  });
 * }
 * }</pre>
 *
 * <p>
 * Templates are loaded from root of classpath: <code>/</code> and must end with: <code>.html</code>
 * file extension. Example:
 * </p>
 *
 * <p>
 * public/index.html
 * </p>
 * <pre>{@code
 * <!DOCTYPE html>
 * <html xmlns:th="http://www.thymeleaf.org">
 * <body>
 * <p>
 *     Hello <span th:text="${model.name}">World</span>!!!
 * </p>
 * </body>
 * </html>
 * }</pre>
 *
 * <h2>template loader</h2>
 * <p>
 * Templates are loaded from the <code>root</code> of classpath and must end with
 * <code>.html</code>. You can change the default template location and/or extensions:
 * </p>
 *
 * <pre>{@code
 * {
 *   use(new Thl("templates", ".thl"));
 * }
 * }</pre>
 *
 * <h2>request locals</h2>
 * <p>
 * A template engine has access to request locals (a.k.a attributes). Here is an example:
 * </p>
 *
 * <pre>{@code
 * {
 *   use(new Thl());
 *
 *   get("*", req -> {
 *     req.set("foo", bar);
 *   });
 * }
 * }</pre>
 *
 * <p>
 * Then from template:
 * </p>
 *
 * <pre>{@code
 *   <span th:text="${who}">World</span>
 * }</pre>
 *
 * <h2>template cache</h2>
 * <p>
 * Cache is OFF when <code>env=dev</code> (useful for template reloading), otherwise is ON.
 * </p>
 *
 * <h2>advanced configuration</h2>
 * <p>
 * Advanced configuration if provided by callback:
 * </p>
 *
 * <pre>{@code
 * {
 *   use(new Thl().doWith(engine -> {
 *     engine.addDialect(...);
 *   }));
 * }
 * }</pre>
 *
 * @author edgar
 */
public class Thl implements Jooby.Module {

  private final String prefix;

  private final String suffix;

  private BiConsumer<TemplateEngine, Config> callback;

  /**
   * Creates a new thymeleaf module.
   *
   * @param prefix Template prefix.
   * @param suffix Template suffix.
   */
  public Thl(final String prefix, final String suffix) {
    this.prefix = Objects.requireNonNull(prefix, "Prefix required.");
    this.suffix = Objects.requireNonNull(suffix, "Suffix required.");
  }

  /**
   * Creates a new thymeleaf module.
   */
  public Thl() {
    this("/", ".html");
  }

  /**
   * Set a configuration callback.
   *
   * <pre>{@code
   * {
   *   use(new Thl().doWith(engine -> {
   *     ...
   *   }));
   * }
   * }</pre>
   *
   * @param callback Callback.
   * @return This module.
   */
  public Thl doWith(final Consumer<TemplateEngine> callback) {
    requireNonNull(callback, "Callback required.");
    return doWith((e, c) -> callback.accept(e));
  }

  /**
   * Set a configuration callback.
   *
   * <pre>{@code
   * {
   *   use(new Thl().doWith(engine -> {
   *     ...
   *   }));
   * }
   * }</pre>
   *
   * @param callback Callback.
   * @return This module.
   */
  public Thl doWith(final BiConsumer<TemplateEngine, Config> callback) {
    this.callback = requireNonNull(callback, "Callback required.");
    return this;
  }

  @Override
  public void configure(final Env env, final Config conf, final Binder binder) throws Throwable {
    ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
    boolean cacheable = !env.name().equals("dev");
    /** Defaults: */
    resolver.setCacheable(cacheable);
    resolver.setPrefix(prefix);
    resolver.setSuffix(suffix);
    resolver.setTemplateMode(TemplateMode.HTML);

    TemplateEngine engine = new TemplateEngine();
    engine.setTemplateResolver(resolver);

    if (callback != null) {
      callback.accept(engine, conf);
    }

    binder.bind(TemplateEngine.class).toInstance(engine);
    binder.bind(ITemplateEngine.class).toInstance(engine);

    Multibinder.newSetBinder(binder, Renderer.class)
        .addBinding()
        .toInstance(new ThlEngine(engine, env));
  }
}
