package com.julienvey.trello.impl.http;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.julienvey.trello.TrelloHttpClient;
import com.julienvey.trello.exception.TrelloHttpException;
import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.Response;

import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class AsyncTrelloHttpClient implements TrelloHttpClient {

    private AsyncHttpClient asyncHttpClient;
    private ObjectMapper mapper;

    public AsyncTrelloHttpClient() {
        this(new AsyncHttpClient());
    }

    public AsyncTrelloHttpClient(AsyncHttpClient asyncHttpClient) {
        this.asyncHttpClient = asyncHttpClient;
        this.mapper = new ObjectMapper();
    }

    @Override
    public <T> T get(String url, final Class<T> responseType, String... params) {
        try {
            Future<T> f = asyncHttpClient.prepareGet(UrlExpander.expandUrl(url, params)).execute(
                    new AsyncCompletionHandler<T>() {

                        @Override
                        public T onCompleted(Response response) throws Exception {
                            return mapper.readValue(response.getResponseBody(), responseType);
                        }

                        @Override
                        public void onThrowable(Throwable t) {
                            throw new TrelloHttpException(t);
                        }
                    });
            return f.get();
        } catch (IOException | InterruptedException | ExecutionException e) {
            throw new TrelloHttpException(e);
        }
    }

    @Override
    public <T> T postForObject(String url, Object object, final Class<T> responseType, String... params) {
        try {
            byte[] body = this.mapper.writeValueAsBytes(object);
            Future<T> f = asyncHttpClient.preparePost(UrlExpander.expandUrl(url, params))
                    .setHeader("Content-Type", "application/json")
                    .setBody(body).execute(new AsyncCompletionHandler<T>() {
                        @Override
                        public T onCompleted(Response response) throws Exception {
                            return mapper.readValue(response.getResponseBody(), responseType);
                        }

                        @Override
                        public void onThrowable(Throwable t) {
                            throw new TrelloHttpException(t);
                        }
                    });
            return f.get();
        } catch (IOException | InterruptedException | ExecutionException e) {
            throw new TrelloHttpException(e);
        }
    }

    @Override
    public URI postForLocation(String url, Object object, String... params) {
        try {
            byte[] body = this.mapper.writeValueAsBytes(object);
            Future<URI> f = asyncHttpClient.preparePost(UrlExpander.expandUrl(url, params)).setBody(body).execute(
                    new AsyncCompletionHandler<URI>() {

                        @Override
                        public URI onCompleted(Response response) throws Exception {
                            String location = response.getHeader("Location");
                            if (location != null) {
                                return URI.create(location);
                            } else {
                                throw new TrelloHttpException("Location header not set");
                            }
                        }

                        @Override
                        public void onThrowable(Throwable t) {
                            throw new TrelloHttpException(t);
                        }
                    });
            return f.get();
        } catch (IOException | InterruptedException | ExecutionException e) {
            throw new TrelloHttpException(e);
        }
    }

    @Override
    public <T> T putForObject(String url, Object object, final Class<T> responseType, String... params) {
        try {
            byte[] body = this.mapper.writeValueAsBytes(object);
            Future<T> f = asyncHttpClient.preparePut(UrlExpander.expandUrl(url, params)).setBody(body).execute(
                    new AsyncCompletionHandler<T>() {

                        @Override
                        public T onCompleted(Response response) throws Exception {
                            return mapper.readValue(response.getResponseBody(), responseType);
                        }

                        @Override
                        public void onThrowable(Throwable t) {
                            throw new TrelloHttpException(t);
                        }
                    });
            return f.get();
        } catch (IOException | InterruptedException | ExecutionException e) {
            throw new TrelloHttpException(e);
        }
    }

    @Override
    public <T> T delete(String url, final Class<T> responseType, String... params) {
        try {
            Future<T> f = asyncHttpClient.prepareDelete(UrlExpander.expandUrl(url, params)).execute(
                    new AsyncCompletionHandler<T>() {

                        @Override
                        public T onCompleted(Response response) throws Exception {
                            return mapper.readValue(response.getResponseBody(), responseType);
                        }

                        @Override
                        public void onThrowable(Throwable t) {
                            throw new TrelloHttpException(t);
                        }
                    });
            return f.get();
        } catch (IOException | InterruptedException | ExecutionException e) {
            throw new TrelloHttpException(e);
        }
    }
}