package com.blazebit.storage.rest.client;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.ext.MessageBodyReader;

import com.blazebit.storage.rest.model.convert.*;
import org.glassfish.jersey.client.proxy.WebResourceFactory;

import com.blazebit.storage.rest.api.AccountsResource;
import com.blazebit.storage.rest.api.AdminResource;
import com.blazebit.storage.rest.api.BucketsResource;
import com.blazebit.storage.rest.api.BucketsSubResource;
import com.blazebit.storage.rest.api.StorageQuotaModelsResource;
import com.blazebit.storage.rest.api.StorageTypesResource;

public class BlazeStorageClient implements BlazeStorage, Serializable {

	private static final long serialVersionUID = 1L;
	private static final List<MessageBodyReader<?>> responseObjectMessageReader = Arrays.asList(
			(MessageBodyReader<?>) new BucketRepresentationMessageBodyReader(), (MessageBodyReader<?>) new BucketObjectRepresentationMessageBodyReader()
	);
	
	private final String serverUrl;
	private final ClientRequestFilter[] requestFilters;
    private final transient Client client;
    
	private BlazeStorageClient(String serverUrl, ClientRequestFilter[] requestFilters) {
		this.serverUrl = serverUrl;
		this.requestFilters = requestFilters;
		
		this.client = ClientBuilder.newClient();
	}
	
	private WebTarget initTarget() {
		WebTarget clientTarget = client.target(serverUrl);
		
		// Register the non-response object providers
		clientTarget.register(BucketObjectUpdateRepresentationMessageBodyReader.class);
		clientTarget.register(BucketObjectUpdateRepresentationClientMessageBodyWriter.class);
		clientTarget.register(MultipartUploadRepresentationMessageBodyReader.class);
		clientTarget.register(MultipartUploadRepresentationMessageBodyWriter.class);
        
        for (ClientRequestFilter filter : requestFilters) {
        	clientTarget = clientTarget.register(filter);
        }
        
        return new ResponseObjectWebTarget(clientTarget, responseObjectMessageReader);
	}

	public static BlazeStorage getInstance(String serverUrl) {
		return new BlazeStorageClient(serverUrl, new ClientRequestFilter[0]);
	}

	public static BlazeStorage getInstance(String serverUrl, ClientRequestFilter... requestFilters) {
		return new BlazeStorageClient(serverUrl, requestFilters);
	}

	@Override
	public AdminResource admin() {
		return WebResourceFactory.newResource(AdminResource.class, initTarget());
	}

	@Override
	public AccountsResource accounts() {
		return WebResourceFactory.newResource(AccountsResource.class, initTarget());
	}
	
	@Override
	public BucketsSubResource buckets() {
		return WebResourceFactory.newResource(BucketsResource.class, initTarget()).get();
	}
	
	@Override
	public StorageQuotaModelsResource storageQuotaModels() {
		return WebResourceFactory.newResource(StorageQuotaModelsResource.class, initTarget());
	}
	
	@Override
	public StorageTypesResource storageTypes() {
		return WebResourceFactory.newResource(StorageTypesResource.class, initTarget());
	}

    @Override
	public void close() {
        client.close();
    }
    
    // SERIALIZATION
    
    private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.defaultWriteObject();
    }

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
        ois.defaultReadObject();
        set("client", ClientBuilder.newClient());
    }
    
    private void set(String fieldName, Object value) {
    	try {
	    	Field field = BlazeStorageClient.class.getDeclaredField(fieldName);
	    	field.setAccessible(true);
	    	field.set(this, value);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
    }
}

@@ -194,16 +194,49 @@
 .Field;%0A
+import java.lang.reflect.Method;%0A
 import j
@@ -252,16 +252,16 @@
 Arrays;%0A
-
 import j
@@ -1471,32 +1471,47 @@
 tring serverUrl,
+ Client client,
  ClientRequestFi
@@ -1605,19 +1605,16 @@
 ilters;%0A
-%09%09%0A
 %09%09this.c
@@ -1625,33 +1625,14 @@
 t = 
-ClientBuilder.newC
+c
 lient
-()
 ;%0A%09%7D
@@ -2436,16 +2436,29 @@
 rverUrl,
+ newClient(),
  new Cli
@@ -2624,16 +2624,29 @@
 rverUrl,
+ newClient(),
  request
@@ -2655,24 +2655,788 @@
 lters);%0A%09%7D%0A%0A
+%09public static BlazeStorage getInstance(String serverUrl, Client client, ClientRequestFilter... requestFilters) %7B%0A%09%09return new BlazeStorageClient(serverUrl, client, requestFilters);%0A%09%7D%0A%0A%09private static Client newClient() %7B%0A%09%09ClientBuilder clientBuilder = ClientBuilder.newBuilder();%0A%0A%09%09// Older RestEasy implementations didn't use connection pooling which is important for storage connections%0A%09%09try %7B%0A%09%09%09Method connectionPoolSize = clientBuilder.getClass().getMethod(%22connectionPoolSize%22, int.class);%0A%09%09%09connectionPoolSize.invoke(clientBuilder, 50);%0A%09%09%7D catch (NoSuchMethodException e) %7B%0A%09%09%09// Ignore%0A%09%09%7D catch (Exception e) %7B%0A%09%09%09throw new RuntimeException(%22Could not create RestEasy client with connection pool size!%22, e);%0A%09%09%7D%0A%09%09return clientBuilder.build();%0A%09%7D%0A%0A
 %09@Override%0A%09
@@ -4463,24 +4463,24 @@
 adObject();%0A
+
         set(
@@ -4489,30 +4489,16 @@
 lient%22, 
-ClientBuilder.
 newClien

Use http connection pooling by default for JAX-RS client and allow to override the client