
package org.xlcloud.rest.client;

import javax.inject.Inject;
import javax.ws.rs.core.MediaType;

import org.apache.log4j.Logger;
import org.xlcloud.logging.LoggingUtils;
import org.xlcloud.rest.client.cache.EntitiesCache;
import org.xlcloud.rest.client.cache.RequestCacheConfig;

import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;

/**
 * Base class for client-side implementations of Rest service API (package
 * org.xlcloud.service.api). It provides common methods adding headers
 * (Content-Type, Accept, Authorization) to requests represented by resources
 * {@link com.sun.jersey.api.client.WebResource}.
 * 
 * @author Tomek Adamczewski, AMG.net
 */
@WebResourceRestClient
public class WebResourceRequestBuilderDecorator {

    @Inject
    EntitiesCache cache;

    public static final String TEMPLATE_JSON_TYPE = "template/json";

    private static final MediaType MEDIA_TYPE = MediaType.APPLICATION_JSON_TYPE;

    private static final String AUTH_HEADER_NAME = "Authorization";

    private static Logger log = Logger.getLogger(WebResourceRequestBuilderDecorator.class);

    @Inject
    @OAuth2BearerToken
    private String oauth2BearerToken;

    @Inject
    private RequestCacheConfig cacheConfig;

    /**
     * Adds authorization and media type headers and executes GET request.
     * 
     * @param c
     *            expected type
     * @param webResource
     *            requested resource
     * @return
     */
    protected <T> T get(Class<T> c, WebResource webResource) {
        return get(c, webResource, MEDIA_TYPE.toString(), MEDIA_TYPE.toString());

    }

    protected <T> T get(Class<T> c, WebResource webResource, String inputMediaType, String outputMediaType) {
        if (!cacheConfig.isDirectRequest()) {
            T entity = cache.retrieveFromCache(webResource, oauth2BearerToken, c, "GET");
            if (entity != null) {
                log.info("GET " + LoggingUtils.maskResource(webResource.getURI().toASCIIString()) + ": retrieved from cache");
                return entity;
            }
        }

        ClientResponse response = decorate(webResource, inputMediaType, outputMediaType).get(ClientResponse.class);
        return cache.cacheResponse(webResource, oauth2BearerToken, response, c, "GET");
    }

    private Builder decorate(WebResource webResource) {
        return decorate(webResource, MEDIA_TYPE);
    }

    private Builder decorate(WebResource webResource, MediaType mediaType) {
        return webResource.header(AUTH_HEADER_NAME, "Bearer " + oauth2BearerToken).accept(MEDIA_TYPE).type(mediaType);
    }

    private Builder decorate(WebResource webResource, String inputMediaType, String outputMediaType) {
        return webResource.header(AUTH_HEADER_NAME, "Bearer " + oauth2BearerToken).accept(outputMediaType).type(inputMediaType);
    }

    /**
     * Adds authorization and media type headers and executes PUT request.
     * 
     * @param webResource
     *            requested resource
     * @return
     */
    public void put(WebResource webResource) {
        decorate(webResource).put();
    }

    /**
     * Adds authorization and media type headers and executes PUT request.
     * 
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public void put(Object requestEntity, WebResource webResource) {
        decorate(webResource).put(requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes PUT request.
     * 
     * @param c
     *            expected type
     * @param webResource
     *            requested resource
     * @return
     */
    public <T> T put(Class<T> c, WebResource webResource) {
        return decorate(webResource).put(c);
    }

    /**
     * Adds authorization and media type headers and executes PUT request.
     * 
     * @param c
     *            expected type
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public <T> T put(Class<T> c, Object requestEntity, WebResource webResource) {
        return decorate(webResource).put(c, requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes PUT request.
     * 
     * @param c
     *            expected type
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @param inputMediaType
     *            Content-Type header
     * @param outputMediaType
     *            Accept header
     * @return
     */
    public <T> T put(Class<T> c, Object requestEntity, WebResource webResource, String inputMediaType, String outputMediaType) {
        return decorate(webResource, inputMediaType, outputMediaType).put(c, requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes POST request.
     * 
     * @param webResource
     *            requested resource
     * @return
     */
    public void post(WebResource webResource) {
        decorate(webResource).post();
    }

    /**
     * Adds authorization and media type headers and executes POST request.
     * 
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public void post(Object requestEntity, WebResource webResource, MediaType mediaType) {
        decorate(webResource, mediaType).post(requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes POST request.
     * 
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public void post(Object requestEntity, WebResource webResource) {
        decorate(webResource).post(requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes POST request.
     * 
     * @param c
     *            expected type
     * @param webResource
     *            requested resource
     * @return
     */
    public <T> T post(Class<T> c, WebResource webResource) {
        return decorate(webResource).post(c);
    }

    /**
     * Adds authorization and media type headers and executes POST request.
     * 
     * @param c
     *            expected type
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public <T> T post(Class<T> c, Object requestEntity, WebResource webResource) {
        return decorate(webResource).post(c, requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes POST request.
     * 
     * @param c
     *            expected type
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public <T> T post(Class<T> c, Object requestEntity, WebResource webResource, MediaType mediaType) {
        return decorate(webResource, mediaType).post(c, requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes POST request.
     * 
     * @param c
     *            expected type
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @param inputMediaType
     *            Content-Type header
     * @param outputMediaType
     *            Accept header
     * @return
     */
    public <T> T post(Class<T> c, Object requestEntity, WebResource webResource, String inputMediaType, String outputMediaType) {
        return decorate(webResource, inputMediaType, outputMediaType).post(c, requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes DELETE request.
     * 
     * @param webResource
     *            requested resource
     * @return
     */
    public void delete(WebResource webResource) {
        decorate(webResource).delete();
    }

    /**
     * Adds authorization and media type headers and executes DELETE request.
     * 
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public void delete(Object requestEntity, WebResource webResource) {
        decorate(webResource).delete(requestEntity);
    }

    /**
     * Adds authorization and media type headers and executes DELETE request.
     * 
     * @param c
     *            expected type
     * @param webResource
     *            requested resource
     * @return
     */
    public <T> T delete(Class<T> c, WebResource webResource) {
        return decorate(webResource).delete(c);
    }

    /**
     * Adds authorization and media type headers and executes DELETE request.
     * 
     * @param c
     *            expected type
     * @param requestEntity
     *            object to be sent.
     * @param webResource
     *            requested resource
     * @return
     */
    public <T> T delete(Class<T> c, Object requestEntity, WebResource webResource) {
        return decorate(webResource).delete(c, requestEntity);
    }
}
