/*
 *  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.apache.isis.viewer.restfulobjects.applib.client;

import java.util.Map;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;

import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.LinkRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.RestfulHttpMethod;
import org.apache.isis.viewer.restfulobjects.applib.client.RestfulRequest.RequestParameter;
import org.apache.isis.viewer.restfulobjects.applib.util.UrlEncodingUtils;
import org.jboss.resteasy.client.ClientExecutor;
import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.specimpl.ResteasyUriBuilder;

/**
 * Configures the body, query string etc of a {@link ClientRequest}.
 * 
 * <p>
 * Needed because, unfortunately, {@link ClientRequest} does not seem to allow
 * the query string to be set directly (only
 * {@link ClientRequest#getQueryParameters() query parameters}). Instead, it is
 * necessary to {@link ResteasyUriBuilder#replaceQuery(String) use} its underlying
 * {@link ResteasyUriBuilder}.
 */
public class ClientRequestConfigurer {

    public static ClientRequestConfigurer create(final ClientExecutor executor, final String uriTemplate) {
        final UriBuilder uriBuilder = new ResteasyUriBuilder().uriTemplate(uriTemplate);
        final ClientRequest clientRequest = executor.createRequest(uriBuilder);
        return new ClientRequestConfigurer(clientRequest, uriBuilder);
    }

    private final ClientRequest clientRequest;
    private final UriBuilder uriBuilder;

    ClientRequestConfigurer(final ClientRequest clientRequest, final UriBuilder uriBuilder) {
        this.clientRequest = clientRequest;
        this.uriBuilder = uriBuilder;
    }

    public ClientRequestConfigurer accept(final MediaType mediaType) {
        clientRequest.accept(mediaType);
        return this;
    }

    public ClientRequestConfigurer header(final String name, final String value) {
        clientRequest.header(name, value);
        return this;
    }

    /**
     * Prerequisite to {@link #configureArgs(JsonRepresentation)} or
     * {@link #configureArgs(Map)}.
     */
    public ClientRequestConfigurer setHttpMethod(final RestfulHttpMethod httpMethod) {
        clientRequest.setHttpMethod(httpMethod.getJavaxRsMethod());
        return this;
    }

    /**
     * Used when creating a request with arguments to execute.
     * 
     * <p>
     * Typical flow is:
     * <ul>
     * <li> {@link RestfulClient#createRequest(RestfulHttpMethod, String)}
     * <li> {@link RestfulRequest#withArg(RequestParameter, Object)} for each arg
     * <li> {@link RestfulRequest#execute()} - which calls this method.
     * </ul>
     */
    public ClientRequestConfigurer configureArgs(final Map<RequestParameter<?>, Object> args) {
        if (clientRequest.getHttpMethod() == null) {
            throw new IllegalStateException("Must set up http method first");
        }

        final JsonRepresentation argsAsMap = JsonRepresentation.newMap();
        for (final RequestParameter<?> requestParam : args.keySet()) {
            put(args, requestParam, argsAsMap);
        }
        getHttpMethod().setUpArgs(this, argsAsMap);
        return this;
    }

    private <P> void put(final Map<RequestParameter<?>, Object> args, final RequestParameter<P> requestParam, final JsonRepresentation argsAsMap) {
        @SuppressWarnings("unchecked")
        final P value = (P) args.get(requestParam);
        final String valueStr = requestParam.getParser().asString(value);
        argsAsMap.mapPut(requestParam.getName(), valueStr);
    }

    /**
     * Used when following links (
     * {@link RestfulClient#follow(LinkRepresentation)}).
     */
    public ClientRequestConfigurer configureArgs(final JsonRepresentation requestArgs) {
        if (clientRequest.getHttpMethod() == null) {
            throw new IllegalStateException("Must set up http method first");
        }

        getHttpMethod().setUpArgs(this, requestArgs);
        return this;
    }

    /**
     * Called back from
     * {@link RestfulHttpMethod#setUpArgs(ClientRequestConfigurer, JsonRepresentation)}
     */
    public ClientRequestConfigurer body(final JsonRepresentation requestArgs) {
        clientRequest.body(MediaType.APPLICATION_JSON_TYPE, requestArgs.toString());
        return this;
    }

    /**
     * Called back from
     * {@link RestfulHttpMethod#setUpArgs(ClientRequestConfigurer, JsonRepresentation)}
     */
    public ClientRequestConfigurer queryString(final JsonRepresentation requestArgs) {
        if (requestArgs.size() == 0) {
            return this;
        }
        final String queryString = UrlEncodingUtils.urlEncode(requestArgs.toString());
        uriBuilder.replaceQuery(queryString);
        return this;
    }

    /**
     * Called back from
     * {@link RestfulHttpMethod#setUpArgs(ClientRequestConfigurer, JsonRepresentation)}
     */
    public ClientRequestConfigurer queryArgs(final JsonRepresentation requestArgs) {
        final MultivaluedMap<String, String> queryParameters = clientRequest.getQueryParameters();
        for (final Map.Entry<String, JsonRepresentation> entry : requestArgs.mapIterable()) {
            final String param = entry.getKey();
            final JsonRepresentation argRepr = entry.getValue();
            final String arg = UrlEncodingUtils.urlEncode(argRepr.asArg());
            queryParameters.add(param, arg);
        }
        return this;
    }

    /**
     * For testing.
     */
    ClientRequest getClientRequest() {
        return clientRequest;
    }

    RestfulHttpMethod getHttpMethod() {
        final String httpMethod = clientRequest.getHttpMethod();
        return RestfulHttpMethod.valueOf(httpMethod);
    }

}
