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

import static java.util.Objects.requireNonNull;

import java.util.function.Supplier;

/**
 * A {@link Result} builder with some utility static methods.
 *
 * @author edgar
 * @since 0.5.0
 */
public class Results {

  /**
   * Set the result
   *
   * @param entity A result value.
   * @return A new result.
   */
  public static Result with(final Object entity) {
    return new Result().set(entity);
  }

  /**
   * Set the result
   *
   * @param entity A result value.
   * @param status A HTTP status.
   * @return A new result.
   */
  public static Result with(final Object entity, final Status status) {
    return new Result().status(status).set(entity);
  }

  /**
   * Set the result
   *
   * @param entity A result value.
   * @param status A HTTP status.
   * @return A new result.
   */
  public static Result with(final Object entity, final int status) {
    return with(entity, Status.valueOf(status));
  }

  /**
   * Set the response status.
   *
   * @param status A status!
   * @return A new result.
   */
  public static Result with(final Status status) {
    requireNonNull(status, "A HTTP status is required.");
    return new Result().status(status);
  }

  /**
   * Set the response status.
   *
   * @param status A status!
   * @return A new result.
   */
  public static Result with(final int status) {
    requireNonNull(status, "A HTTP status is required.");
    return new Result().status(status);
  }

  /**
   * @return A new result with {@link Status#OK}.
   */
  public static Result ok() {
    return with(Status.OK);
  }

  /**
   * @param view View to render.
   * @return A new view.
   */
  public static View html(final String view) {
    return new View(view);
  }

  /**
   * @param entity A result content!
   * @return A new json result.
   */
  public static Result json(final Object entity) {
    return with(entity, 200).type(MediaType.json);
  }

  /**
   * @param entity A result content!
   * @return A new json result.
   */
  public static Result xml(final Object entity) {
    return with(entity, 200).type(MediaType.xml);
  }

  /**
   * @param entity A result content!
   * @return A new result with {@link Status#OK} and given content.
   */
  public static Result ok(final Object entity) {
    return ok().set(entity);
  }

  /**
   * @return A new result with {@link Status#ACCEPTED}.
   */
  public static Result accepted() {
    return with(Status.ACCEPTED);
  }

  /**
   * @param content A result content!
   * @return A new result with {@link Status#ACCEPTED}.
   */
  public static Result accepted(final Object content) {
    return accepted().set(content);
  }

  /**
   * @return A new result with {@link Status#NO_CONTENT}.
   */
  public static Result noContent() {
    return with(Status.NO_CONTENT);
  }

  /**
   * Redirect to the given url with status code defaulting to {@link Status#FOUND}.
   *
   * <pre>
   *  rsp.redirect("/foo/bar");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("../login");
   * </pre>
   *
   * Redirects can be a fully qualified URI for redirecting to a different site:
   *
   * <pre>
   *   rsp.redirect("http://google.com");
   * </pre>
   *
   * Redirects can be relative to the root of the host name. For example, if you were
   * on <code>http://example.com/admin/post/new</code>, the following redirect to /admin would
   * land you at <code>http://example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("/admin");
   * </pre>
   *
   * Redirects can be relative to the current URL. A redirection of post/new, from
   * <code>http://example.com/blog/admin/</code> (notice the trailing slash), would give you
   * <code>http://example.com/blog/admin/post/new.</code>
   *
   * <pre>
   *   rsp.redirect("post/new");
   * </pre>
   *
   * Redirecting to post/new from <code>http://example.com/blog/admin</code> (no trailing slash),
   * will take you to <code>http://example.com/blog/post/new</code>.
   *
   * <p>
   * If you found the above behavior confusing, think of path segments as directories (have trailing
   * slashes) and files, it will start to make sense.
   * </p>
   *
   * Pathname relative redirects are also possible. If you were on
   * <code>http://example.com/admin/post/new</code>, the following redirect would land you at
   * <code>http//example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("..");
   * </pre>
   *
   * A back redirection will redirect the request back to the <code>Referer</code>, defaulting to
   * <code>/</code> when missing.
   *
   * <pre>
   *   rsp.redirect("back");
   * </pre>
   *
   * @param location A location.
   * @return A new result.
   */
  public static Result redirect(final String location) {
    return redirect(Status.FOUND, location);
  }

  /**
   * Redirect to the given url with status code defaulting to {@link Status#FOUND}.
   *
   * <pre>
   *  rsp.redirect("/foo/bar");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("../login");
   * </pre>
   *
   * Redirects can be a fully qualified URI for redirecting to a different site:
   *
   * <pre>
   *   rsp.redirect("http://google.com");
   * </pre>
   *
   * Redirects can be relative to the root of the host name. For example, if you were
   * on <code>http://example.com/admin/post/new</code>, the following redirect to /admin would
   * land you at <code>http://example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("/admin");
   * </pre>
   *
   * Redirects can be relative to the current URL. A redirection of post/new, from
   * <code>http://example.com/blog/admin/</code> (notice the trailing slash), would give you
   * <code>http://example.com/blog/admin/post/new.</code>
   *
   * <pre>
   *   rsp.redirect("post/new");
   * </pre>
   *
   * Redirecting to post/new from <code>http://example.com/blog/admin</code> (no trailing slash),
   * will take you to <code>http://example.com/blog/post/new</code>.
   *
   * <p>
   * If you found the above behavior confusing, think of path segments as directories (have trailing
   * slashes) and files, it will start to make sense.
   * </p>
   *
   * Pathname relative redirects are also possible. If you were on
   * <code>http://example.com/admin/post/new</code>, the following redirect would land you at
   * <code>http//example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("..");
   * </pre>
   *
   * A back redirection will redirect the request back to the <code>Referer</code>, defaulting to
   * <code>/</code> when missing.
   *
   * <pre>
   *   rsp.redirect("back");
   * </pre>
   *
   * @param location A location.
   * @return A new result.
   */
  public static Result tempRedirect(final String location) {
    return redirect(Status.TEMPORARY_REDIRECT, location);
  }

  /**
   * Redirect to the given url with status code defaulting to {@link Status#FOUND}.
   *
   * <pre>
   *  rsp.redirect("/foo/bar");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("../login");
   * </pre>
   *
   * Redirects can be a fully qualified URI for redirecting to a different site:
   *
   * <pre>
   *   rsp.redirect("http://google.com");
   * </pre>
   *
   * Redirects can be relative to the root of the host name. For example, if you were
   * on <code>http://example.com/admin/post/new</code>, the following redirect to /admin would
   * land you at <code>http://example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("/admin");
   * </pre>
   *
   * Redirects can be relative to the current URL. A redirection of post/new, from
   * <code>http://example.com/blog/admin/</code> (notice the trailing slash), would give you
   * <code>http://example.com/blog/admin/post/new.</code>
   *
   * <pre>
   *   rsp.redirect("post/new");
   * </pre>
   *
   * Redirecting to post/new from <code>http://example.com/blog/admin</code> (no trailing slash),
   * will take you to <code>http://example.com/blog/post/new</code>.
   *
   * <p>
   * If you found the above behavior confusing, think of path segments as directories (have trailing
   * slashes) and files, it will start to make sense.
   * </p>
   *
   * Pathname relative redirects are also possible. If you were on
   * <code>http://example.com/admin/post/new</code>, the following redirect would land you at
   * <code>http//example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("..");
   * </pre>
   *
   * A back redirection will redirect the request back to the <code>Referer</code>, defaulting to
   * <code>/</code> when missing.
   *
   * <pre>
   *   rsp.redirect("back");
   * </pre>
   *
   * @param location A location.
   * @return A new result.
   */
  public static Result moved(final String location) {
    return redirect(Status.MOVED_PERMANENTLY, location);
  }

  /**
   * Redirect to the given url with status code defaulting to {@link Status#FOUND}.
   *
   * <pre>
   *  rsp.redirect("/foo/bar");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("http://example.com");
   *  rsp.redirect("../login");
   * </pre>
   *
   * Redirects can be a fully qualified URI for redirecting to a different site:
   *
   * <pre>
   *   rsp.redirect("http://google.com");
   * </pre>
   *
   * Redirects can be relative to the root of the host name. For example, if you were
   * on <code>http://example.com/admin/post/new</code>, the following redirect to /admin would
   * land you at <code>http://example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("/admin");
   * </pre>
   *
   * Redirects can be relative to the current URL. A redirection of post/new, from
   * <code>http://example.com/blog/admin/</code> (notice the trailing slash), would give you
   * <code>http://example.com/blog/admin/post/new.</code>
   *
   * <pre>
   *   rsp.redirect("post/new");
   * </pre>
   *
   * Redirecting to post/new from <code>http://example.com/blog/admin</code> (no trailing slash),
   * will take you to <code>http://example.com/blog/post/new</code>.
   *
   * <p>
   * If you found the above behavior confusing, think of path segments as directories (have trailing
   * slashes) and files, it will start to make sense.
   * </p>
   *
   * Pathname relative redirects are also possible. If you were on
   * <code>http://example.com/admin/post/new</code>, the following redirect would land you at
   * <code>http//example.com/admin</code>:
   *
   * <pre>
   *   rsp.redirect("..");
   * </pre>
   *
   * A back redirection will redirect the request back to the <code>Referer</code>, defaulting to
   * <code>/</code> when missing.
   *
   * <pre>
   *   rsp.redirect("back");
   * </pre>
   *
   * @param location A location.
   * @return A new result.
   */
  public static Result seeOther(final String location) {
    return redirect(Status.SEE_OTHER, location);
  }

  /**
   * Performs content-negotiation on the Accept HTTP header on the request object. It select a
   * handler for the request, based on the acceptable types ordered by their quality values.
   * If the header is not specified, the first callback is invoked. When no match is found,
   * the server responds with 406 "Not Acceptable", or invokes the default callback: {@code ** / *}.
   *
   * <pre>
   *   get("/jsonOrHtml", () {@literal ->}
   *     Results
   *         .when("text/html", () {@literal ->} Results.html("view").put("model", model)))
   *         .when("application/json", () {@literal ->} model)
   *         .when("*", () {@literal ->} {throw new Err(Status.NOT_ACCEPTABLE);})
   *   );
   * </pre>
   *
   * @param type A media type.
   * @param supplier A result supplier.
   * @return A new result.
   */
  public static Result when(final String type, final Supplier<Object> supplier) {
    return new Result().when(type, supplier);
  }

  /**
   * Performs content-negotiation on the Accept HTTP header on the request object. It select a
   * handler for the request, based on the acceptable types ordered by their quality values.
   * If the header is not specified, the first callback is invoked. When no match is found,
   * the server responds with 406 "Not Acceptable", or invokes the default callback: {@code ** / *}.
   *
   * <pre>
   *   get("/jsonOrHtml", () {@literal ->}
   *     Results
   *         .when("text/html", () {@literal ->} Results.html("view").put("model", model)))
   *         .when("application/json", () {@literal ->} model)
   *         .when("*", () {@literal ->} {throw new Err(Status.NOT_ACCEPTABLE);})
   *   );
   * </pre>
   *
   * @param type A media type.
   * @param supplier A result supplier.
   * @return A new result.
   */
  public static Result when(final MediaType type, final Supplier<Object> supplier) {
    return new Result().when(type, supplier);
  }

  /**
   * Produces a redirect (302) status code and set the <code>Location</code> header too.
   *
   * @param status A HTTP redirect status.
   * @param location A location.
   * @return A new result.
   */
  private static Result redirect(final Status status, final String location) {
    requireNonNull(location, "A location is required.");
    return with(status).header("location", location);
  }

}
