package com.atlassian.stash.internal.web.soy.functions.nav;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;

import java.util.Map;

/**
 * Utility methods for working with nav options in the context of evaluating a nav soy function. Nav options are an
 * optional map of rendering options for a URLs generated by some soy functions, including {@link BaseNavFunction} and
 * {@link PluginResourceUrlFunction}, passed in as the final soy arg.
 * <p>
 * e.g. the following soy fragment:
 * <pre>
 *     {nav_password_reset()}
 * </pre>
 * <p>
 * Produces the url:
 * </p>
 * <pre>
 * /context/passwordreset
 * </pre>
 * <p>
 * Passing in a map of options as the the final soy arg will transform the result, e.g.:
 * </p>
 * <pre>
 *   {nav_password_reset([ 'queryParams' : [ 'token' : $token ], 'urlMode' : 'absolute' ])}{\n}
 * </pre>
 * <p>
 * Produces the url:
 * </p>
 * <pre>
 * http://host:port/context/passwordreset?token=babecafe1234
 * </pre>
 *
 */
class NavOptionsUtils {

    /**
     * A callback for processing the nav options map that is (optionally) passed as the last argument to some soy
     * functions responsible for generating URLs, for example {@link BaseNavFunction} and
     * {@link PluginResourceUrlFunction}.
     */
    static interface NavOptionsCallback {

        /**
         * Called once for each entry in the nested 'queryParam' map
         * @param key the query parameter key
         * @param value the query parameter value
         */
        void withQueryParam(String key, String value);

        /**
         * Called if the nav options map specifies a urlMode of 'absolute'
         * @return the absolute URL (respecting the context HTTP request, if there is one)
         */
        String buildAbsolute();

        /**
         * Called if the nav options map specifies a urlMode of 'absolute'
         * @return the absolute URL, using the server's configured base URL (ignoring any context HTTP request)
         */
        String buildConfigured();

        /**
         * Called if the nav options map specifies a urlMode of 'relative', or no urlMode is specified
         * @return the relative URL
         */
        String buildRelative();

    }

    /**
     * Parses a {@link Map} of options for the url from the final soy arg if the final soy arg is a map, and passes it
     * to the supplied {@link Function function}. If the final arg is not a map or there are no arguments, an empty map
     * is passed to the function. The map may contain any of the following elements:
     * <ul>
     *  <li><strong>queryParams:</strong> a nested {@link Map} of query parameter key value pairs</li>
     *  <li><strong>urlMode:</strong> a desired mode for the URL, i.e. 'absolute', 'configured' or 'relative'. Behaviour
     *      should default to 'relative' if no urlMode is specified.</li>
     * </ul>
     *
     * @param callback the callback to invoke when processing the nav options map, if present
     * @param soyArgs the soyArgs used to evaluate the context template.
     * @return the url supplied by the callback via {@link NavOptionsCallback#buildAbsolute() buildAbsolute()},
     *         {@link NavOptionsCallback#buildConfigured() buildConfigured()} or
     *         {@link NavOptionsCallback#buildRelative() buildRelative()}
     */
    static String withNavOptions(NavOptionsCallback callback, Object[] soyArgs) {
        Map<String, ?> options = null;
        if (soyArgs.length > 0) {
            Object maybeMap = soyArgs[soyArgs.length - 1];
            if (maybeMap instanceof Map) {
                options = (Map<String, ?>) maybeMap;
            }
        }

        if (options == null) {
            return callback.buildRelative();
        }


        Map<?, ?> queryParams = (Map<?, ?>) options.get("queryParams");
        if (queryParams != null) {
            for (Map.Entry entry : queryParams.entrySet()) {
                Preconditions.checkArgument(entry.getKey() instanceof String,
                        String.format("Parameter map must use all string keys (found %s)",
                                entry.getKey().getClass().getSimpleName()));
                Preconditions.checkArgument(entry.getValue() instanceof String,
                        String.format("Parameter map must use all string values (found %s)",
                                entry.getValue().getClass().getSimpleName()));
                callback.withQueryParam((String) entry.getKey(), (String) entry.getValue());
            }
        }

        // return an absolute URL if specified, or default to relative. Note that the NavBuilder will use the
        // base url of the context request if the the template is rendered on a request thread.
        String urlMode = (String) options.get("urlMode");
        if ("configured".equalsIgnoreCase(urlMode)) {
            return callback.buildConfigured();
        } else if ("absolute".equalsIgnoreCase(urlMode)) {
            return callback.buildAbsolute();
        } else {
            return callback.buildRelative();
        }
    }

}
