/**
 * Copyright (C) 2013 Matija Mazi
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package si.mazi.rescu;

import si.mazi.rescu.serialization.PlainTextResponseReader;
import si.mazi.rescu.serialization.ToStringRequestWriter;
import si.mazi.rescu.serialization.jackson.JacksonMapper;
import si.mazi.rescu.serialization.jackson.JacksonRequestWriter;
import si.mazi.rescu.serialization.jackson.JacksonResponseReader;

import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Matija Mazi
 */
public class RestInvocationHandler implements InvocationHandler {

    private final ResponseReaderResolver responseReaderResolver;
    private final RequestWriterResolver requestWriterResolver;
    
    private final HttpTemplate httpTemplate;
    private final String intfacePath;
    private final String baseUrl;
    private final ClientConfig config;

    private final Map<Method, RestMethodMetadata> methodMetadataCache = new HashMap<Method, RestMethodMetadata>();

    public RestInvocationHandler(Class<?> restInterface, String url, ClientConfig config) {
        this.intfacePath = restInterface.getAnnotation(Path.class).value();
        this.baseUrl = url;
        
        if (config == null) {
            config = new ClientConfig(); //default config
        }
        
        this.config = config;
        
        //setup default readers/writers
        JacksonMapper jacksonMapper = new JacksonMapper(config.getJacksonConfigureListener());

        requestWriterResolver = new RequestWriterResolver();
        /*requestWriterResolver.addWriter(null,
                new NullRequestWriter());*/
        requestWriterResolver.addWriter(MediaType.APPLICATION_FORM_URLENCODED,
                new FormUrlEncodedRequestWriter());
        requestWriterResolver.addWriter(MediaType.APPLICATION_JSON,
                new JacksonRequestWriter(jacksonMapper));
        requestWriterResolver.addWriter(MediaType.TEXT_PLAIN,
                new ToStringRequestWriter());

        responseReaderResolver = new ResponseReaderResolver();
        responseReaderResolver.addReader(MediaType.APPLICATION_JSON,
                new JacksonResponseReader(jacksonMapper, this.config.isIgnoreHttpErrorCodes()));
        responseReaderResolver.addReader(MediaType.TEXT_PLAIN,
                new PlainTextResponseReader(this.config.isIgnoreHttpErrorCodes()));

                //setup http client
        this.httpTemplate = new HttpTemplate(
                this.config.getHttpConnTimeout(),
                this.config.getHttpReadTimeout(),
                this.config.getProxyHost(), this.config.getProxyPort(),
                this.config.getSslSocketFactory(), this.config.getHostnameVerifier());
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RestMethodMetadata methodMetadata = getMetadata(method);

        HttpURLConnection connection;
        if (hasValueGenerator(args)) {
            synchronized (this) {
                System.out.println("synchronized");
                connection = prepareAndInvoke(args, methodMetadata);
            }
        } else {
            connection = prepareAndInvoke(args, methodMetadata);
        }

        return receiveAndMap(methodMetadata, connection);
    }

    protected HttpURLConnection prepareAndInvoke(Object[] args, RestMethodMetadata _methodMetadata) throws IOException {
        RestInvocation invocation = RestInvocation.create(
                requestWriterResolver, _methodMetadata, args, config.getParamsMap());

        return invokeHttp(invocation);
    }

    protected HttpURLConnection invokeHttp(RestInvocation invocation) throws IOException {
        RestMethodMetadata methodMetadata = invocation.getMethodMetadata();

        RequestWriter requestWriter = requestWriterResolver.resolveWriter(invocation.getMethodMetadata());
        final String requestBody = requestWriter.writeBody(invocation);

        return httpTemplate.send(invocation.getInvocationUrl(), requestBody, invocation.getAllHttpHeaders(), methodMetadata.getHttpMethod());
    }

    protected Object receiveAndMap(RestMethodMetadata methodMetadata, HttpURLConnection connection) throws IOException {
        InvocationResult invocationResult = httpTemplate.receive(connection);
        return mapInvocationResult(invocationResult, methodMetadata);
    }

    private static boolean hasValueGenerator(Object[]args){
        if (args != null) for (Object arg : args)
            if (arg instanceof ValueFactory)
                return true;
        return false;
    }

    protected Object mapInvocationResult(InvocationResult invocationResult,
            RestMethodMetadata methodMetadata) throws IOException {
        return responseReaderResolver.resolveReader(methodMetadata).read(invocationResult, methodMetadata);
    }
    
    private RestMethodMetadata getMetadata(Method method) {
        RestMethodMetadata metadata = methodMetadataCache.get(method);
        if (metadata == null) {
            metadata = RestMethodMetadata.create(method, baseUrl, intfacePath);
            methodMetadataCache.put(method, metadata);
        }
        return metadata;
    }
}

@@ -4195,60 +4195,8 @@
 ) %7B%0A
-                System.out.println(%22synchronized%22);%0A
     

remove call on System.out