package org.jmx4perl.client.request;

import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.*;
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.json.simple.*;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 * Class doing the hard work of conversion between HTTP request/responses and
 * J4p request/responses.
 *
 * @author roland
 * @since Apr 25, 2010
 */
public class J4pRequestHandler {

    // j4p agent URL for the agent server
    private String j4pServerUrl;

    // Escape patterns
    private static final Pattern SLASH_PATTERN = Pattern.compile("/+");
    private static final Pattern ESCAPED_SLASH_PATTERN = Pattern.compile("%2F");

    public J4pRequestHandler(String pJ4pServerUrl) {
        j4pServerUrl = pJ4pServerUrl;
    }

    /**
     * Get the HttpRequest for executing the given single request
     *
     * @param pRequest request to convert
     * @param pPreferredMethod HTTP method preferred
     * @return the request used with HttpClient to obtain the result.
     */
    public HttpUriRequest getHttpRequest(J4pRequest pRequest,String pPreferredMethod) throws UnsupportedEncodingException {
        String method = pPreferredMethod;
        if (method == null) {
            method = pRequest.getPreferredHttpMethod();
        }
        if (method == null) {
            method = HttpGet.METHOD_NAME;
        }
        if (method.equals(HttpGet.METHOD_NAME)) {
            List<String> parts = pRequest.getRequestParts();
            // If parts == null the request decides, that POST *must* be used
            if (parts != null) {
                StringBuilder requestPath = new StringBuilder();
                requestPath.append(pRequest.getType().getValue());
                for (String p : parts) {
                    requestPath.append("/");
                    requestPath.append(escape(p));
                }
                // TODO: Option handling, special escaping
                return new HttpGet(j4pServerUrl + "/" + requestPath.toString());
            }
        }


        // We are using a post method as fallback
        // TODO: Option handling
        JSONObject requestContent = pRequest.toJson();
        HttpPost postReq = new HttpPost(j4pServerUrl);
        postReq.setEntity(new StringEntity(requestContent.toJSONString(),"utf-8"));
        return postReq;
    }

    /**
     * Get an HTTP Request for requesting multips requests at once
     *
     * @param pRequests requests to put into a HTTP request
     * @return HTTP request to send to the server
     */
    public <T extends J4pRequest> HttpUriRequest getHttpRequest(List<T> pRequests) throws UnsupportedEncodingException {
        JSONArray bulkRequest = new JSONArray();
        HttpPost postReq = new HttpPost(j4pServerUrl);
        for (T request : pRequests) {
            JSONObject requestContent = request.toJson();
            bulkRequest.add(requestContent);
        }
        postReq.setEntity(new StringEntity(bulkRequest.toJSONString(),"utf-8"));
        return postReq;
    }


    /**
     * Extract the complete JSON response out of a HTTP response
     *
     * @param pRequest the original J4p request
     * @param pHttpResponse the resulting http response
     * @param <T> J4p Request
     * @return JSON content of the answer
     * @throws J4pException when parsing of the answer fails
     */
    @SuppressWarnings("PMD.PreserveStackTrace")
    public JSONAware extractJsonResponse(HttpResponse pHttpResponse) throws IOException, ParseException {
        HttpEntity entity = pHttpResponse.getEntity();
        JSONParser parser = new JSONParser();
        Header contentEncoding = entity.getContentEncoding();
        if (contentEncoding != null) {
            return (JSONAware) parser.parse(new InputStreamReader(entity.getContent(), Charset.forName(contentEncoding.getValue())));
        } else {
            return (JSONAware) parser.parse(new InputStreamReader(entity.getContent()));
        }
    }

    /**
     * Extract a {@link J4pResponse} out of a JSON object
     *
     * @param pRequest request which lead to the response
     * @param pJsonResponse JSON response
     * @param <T> request type.
     * @param <R> response type
     * @return the J4p response
     */
    public <R extends J4pResponse<T>,T extends J4pRequest> R extractResponse(T pRequest,JSONObject pJsonResponse) {
        return pRequest.<R>createResponse(pJsonResponse);
    }

    // Escape a part for usage as part of URI path
    private String escape(String pPart) throws UnsupportedEncodingException {
        Matcher matcher = SLASH_PATTERN.matcher(pPart);
        int index = 0;
        StringBuilder ret = new StringBuilder();
        while (matcher.find()) {
            String part = pPart.subSequence(index, matcher.start()).toString();
            ret.append(part).append("/");
            ret.append(escapeSlash(pPart, matcher));
            ret.append("/");
            index = matcher.end();
        }
        if (index != pPart.length()) {
            ret.append(pPart.substring(index,pPart.length()));
        }
        return uriEscape(ret);
    }

    private String escapeSlash(String pPart, Matcher pMatcher) {
        StringBuilder ret = new StringBuilder();
        String separator = pPart.substring(pMatcher.start(), pMatcher.end());
        int len = separator.length();
        for (int i = 0;i<len;i++) {
            if (i == 0 && pMatcher.start() == 0) {
                ret.append("^");
            } else if (i == len - 1 && pMatcher.end() == pPart.length()) {
                ret.append("+");
            } else {
                ret.append("-");
            }
        }
        return ret.toString();
    }

    private String uriEscape(StringBuilder pRet) throws UnsupportedEncodingException {
        // URI Escape unsafe chars
        String encodedRet = URLEncoder.encode(pRet.toString(),"utf-8");
        // Translate all "/" back...
        return ESCAPED_SLASH_PATTERN.matcher(encodedRet).replaceAll("/");
    }

}
