package com.google.sitebricks.client;

import com.google.common.collect.ImmutableMap;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.Realm;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.Response;
import net.jcip.annotations.ThreadSafe;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.ExecutionException;

/**
 * @author Jeanfrancois Arcand (jfarcand@apache.org)
 */
@ThreadSafe
        //@Concurrent
class AHCWebClient<T> implements WebClient<T> {
    private final Injector injector;
    private final String url;
    private final Map<String, String> headers;
    private final Class<T> transporting;
    private final Key<? extends Transport> transport;
    private final AsyncHttpClient httpClient;

    private final Web.Auth authType;
    private final String username;
    private final String password;

    public AHCWebClient(Injector injector, Web.Auth authType, String username,
                        String password, String url, Map<String, String> headers,
                        Class<T> transporting,
                        Key<? extends Transport> transport) {

        this.injector = injector;

        this.url = url;
        this.headers = (null == headers) ? null : ImmutableMap.copyOf(headers);


        this.authType = authType;
        this.username = username;
        this.password = password;
        this.transporting = transporting;
        this.transport = transport;

        // configure auth
        AsyncHttpClientConfig.Builder c = new AsyncHttpClientConfig.Builder();
        if (null != authType) {
            Realm.RealmBuilder b = new Realm.RealmBuilder();
            // TODO: Add support for Kerberos and SPNEGO
            Realm.AuthScheme scheme = authType.equals(Web.Auth.BASIC) ? Realm.AuthScheme.BASIC : Realm.AuthScheme.DIGEST;
            b.setPrincipal(username).setPassword(password).setScheme(scheme);
            c.setRealm(b.build());
        }

        this.httpClient = new AsyncHttpClient(c.build());

    }

    private static URI toUri(String url) {
        try {
            return new URI(url);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    private WebResponse simpleRequest(RequestBuilder requestBuilder) {

        //set request headers as necessary
        if (null != headers)
            for (Map.Entry<String, String> header : headers.entrySet())
                requestBuilder.addHeader(header.getKey(), header.getValue());

        try {

            Response r = httpClient.executeRequest(requestBuilder.build()).get();

            return new WebResponseImpl(injector, r);
        } catch (IOException e) {
            throw new TransportException(e);
        } catch (InterruptedException e) {
            throw new TransportException(e);
        } catch (ExecutionException e) {
            throw new TransportException(e);
        }
    }


    private WebResponse request(RequestBuilder requestBuilder, T t) {

        //set request headers as necessary
        if (null != headers)
            for (Map.Entry<String, String> header : headers.entrySet())
                requestBuilder.addHeader(header.getKey(), header.getValue());

        //fire method
        try {

            // Read the entity from the transport plugin.
            final ByteArrayOutputStream stream = new ByteArrayOutputStream();
            injector.getInstance(transport)
                    .out(stream, transporting, t);

            // TODO worry about endian issues? Or will Content-Encoding be sufficient?
            // OOM if the stream is too bug
            final byte[] outBuffer = stream.toByteArray();

            //set request body
            requestBuilder.setBody(outBuffer);

            Response r = httpClient.executeRequest(requestBuilder.build()).get();

            return new WebResponseImpl(injector, r);
        } catch (IOException e) {
            throw new TransportException(e);
        } catch (InterruptedException e) {
            throw new TransportException(e);
        } catch (ExecutionException e) {
            throw new TransportException(e);
        }
    }

    public WebResponse get() {
        return simpleRequest((new RequestBuilder("GET")).setUrl(url));
    }

    public WebResponse post(T t) {
        return request((new RequestBuilder("POST")).setUrl(url), t);
    }

    public WebResponse put(T t) {
        return request((new RequestBuilder("PUT")).setUrl(url), t);
    }

    public WebResponse delete() {
        return simpleRequest((new RequestBuilder("DELETE")).setUrl(url));
    }
}

Fix broken build by adding the missing class