package com.oath.micro.server.rest.client.nio;


import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;

import lombok.Getter;

import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.client.AsyncRequestCallback;
import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.ResponseExtractor;
import org.springframework.web.client.RestClientException;

import com.oath.micro.server.rest.jackson.JacksonUtil;

public class NIORestClient {

	@Getter
	private final AsyncRestTemplate template;

	// GET
	public <T> CompletableFuture<ResponseEntity<T>> getForEntity(String url,
			Class<T> responseType, Object... uriVariables)
			throws RestClientException {

		return toCompletableFuture(template.getForEntity(url, responseType,
				uriVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> getForEntity(String url,
			Class<T> responseType, Map<String, ?> urlVariables)
			throws RestClientException {

		return toCompletableFuture(template.getForEntity(url, responseType,
				urlVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> getForEntity(URI url,
			Class<T> responseType) throws RestClientException {

		return toCompletableFuture(template.getForEntity(url, responseType));
	}

	// HEAD

	public CompletableFuture<HttpHeaders> headForHeaders(String url,
			Object... uriVariables) throws RestClientException {

		return toCompletableFuture(template.headForHeaders(url, uriVariables));
	}

	public CompletableFuture<HttpHeaders> headForHeaders(String url,
			Map<String, ?> uriVariables) throws RestClientException {
		return toCompletableFuture(template.headForHeaders(url, uriVariables));
	}

	public CompletableFuture<HttpHeaders> headForHeaders(URI url)
			throws RestClientException {
		return toCompletableFuture(template.headForHeaders(url));
	}

	public CompletableFuture<URI> postForLocation(String url,
			HttpEntity<?> request, Object... uriVariables)
			throws RestClientException {
		return toCompletableFuture(template.postForLocation(url, request,
				uriVariables));
	}

	public CompletableFuture<URI> postForLocation(String url,
			HttpEntity<?> request, Map<String, ?> uriVariables)
			throws RestClientException {
		return toCompletableFuture(template.postForLocation(url, request,
				uriVariables));
	}

	public CompletableFuture<URI> postForLocation(URI url, HttpEntity<?> request)
			throws RestClientException {
		return toCompletableFuture(template.postForLocation(url, request));
	}

	public <T> CompletableFuture<ResponseEntity<T>> postForEntity(String url,
			HttpEntity<?> request, Class<T> responseType,
			Object... uriVariables) throws RestClientException {
		return toCompletableFuture(template.postForEntity(url, request,
				responseType, uriVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> postForEntity(String url,
			HttpEntity<?> request, Class<T> responseType,
			Map<String, ?> uriVariables) throws RestClientException {
		return toCompletableFuture(template.postForEntity(url, request,
				responseType, uriVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> postForEntity(URI url,
			HttpEntity<?> request, Class<T> responseType)
			throws RestClientException {
		return toCompletableFuture(template.postForEntity(url, request,
				responseType));
	}

	public CompletableFuture<?> put(String url, HttpEntity<?> request,
			Object... uriVariables) throws RestClientException {
		return toCompletableFuture(template.put(url, request, uriVariables));
	}

	public CompletableFuture<?> put(String url, HttpEntity<?> request,
			Map<String, ?> uriVariables) throws RestClientException {
		return toCompletableFuture(template.put(url, request, uriVariables));
	}

	public CompletableFuture<?> put(URI url, HttpEntity<?> request)
			throws RestClientException {
		return toCompletableFuture(template.put(url, request));
	}

	public CompletableFuture<?> delete(String url, Object... urlVariables)
			throws RestClientException {
		return toCompletableFuture(template.delete(url, urlVariables));
	}

	public CompletableFuture<?> delete(String url, Map<String, ?> urlVariables)
			throws RestClientException {
		return toCompletableFuture(template.delete(url, urlVariables));
	}

	public CompletableFuture<?> delete(URI url) throws RestClientException {
		return toCompletableFuture(template.delete(url));
	}

	public CompletableFuture<Set<HttpMethod>> optionsForAllow(String url,
			Object... uriVariables) throws RestClientException {
		return toCompletableFuture(template.optionsForAllow(url, uriVariables));
	}

	public CompletableFuture<Set<HttpMethod>> optionsForAllow(String url,
			Map<String, ?> uriVariables) throws RestClientException {
		return toCompletableFuture(template.optionsForAllow(url, uriVariables));
	}

	public CompletableFuture<Set<HttpMethod>> optionsForAllow(URI url)
			throws RestClientException {
		return toCompletableFuture(template.optionsForAllow(url));
	}

	public <T> CompletableFuture<ResponseEntity<T>> exchange(String url,
			HttpMethod method, HttpEntity<?> requestEntity,
			Class<T> responseType, Object... uriVariables)
			throws RestClientException {
		return toCompletableFuture(template.exchange(url, method,
				requestEntity, responseType, uriVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> exchange(String url,
			HttpMethod method, HttpEntity<?> requestEntity,
			Class<T> responseType, Map<String, ?> uriVariables)
			throws RestClientException {
		return toCompletableFuture(template.exchange(url, method,
				requestEntity, responseType, uriVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> exchange(URI url,
			HttpMethod method, HttpEntity<?> requestEntity,
			Class<T> responseType) throws RestClientException {
		return toCompletableFuture(template.exchange(url, method,
				requestEntity, responseType));
	}

	public <T> CompletableFuture<ResponseEntity<T>> exchange(String url,
			HttpMethod method, HttpEntity<?> requestEntity,
			ParameterizedTypeReference<T> responseType, Object... uriVariables)
			throws RestClientException {
		return toCompletableFuture(template.exchange(url, method,
				requestEntity, responseType, uriVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> exchange(String url,
			HttpMethod method, HttpEntity<?> requestEntity,
			ParameterizedTypeReference<T> responseType,
			Map<String, ?> uriVariables) throws RestClientException {
		return toCompletableFuture(template.exchange(url, method,
				requestEntity, responseType, uriVariables));
	}

	public <T> CompletableFuture<ResponseEntity<T>> exchange(URI url,
			HttpMethod method, HttpEntity<?> requestEntity,
			ParameterizedTypeReference<T> responseType)
			throws RestClientException {
		return toCompletableFuture(template.exchange(url, method,
				requestEntity, responseType));
	}

	public <T> CompletableFuture<T> execute(String url, HttpMethod method,
			AsyncRequestCallback requestCallback,
			ResponseExtractor<T> responseExtractor, Object... urlVariables)
			throws RestClientException {
		return toCompletableFuture(template.execute(url, method,
				requestCallback, responseExtractor, urlVariables));
	}

	public <T> CompletableFuture<T> execute(String url, HttpMethod method,
			AsyncRequestCallback requestCallback,
			ResponseExtractor<T> responseExtractor, Map<String, ?> urlVariables)
			throws RestClientException {
		return toCompletableFuture(template.execute(url, method,
				requestCallback, responseExtractor, urlVariables));
	}

	public <T> CompletableFuture<T> execute(URI url, HttpMethod method,
			AsyncRequestCallback requestCallback,
			ResponseExtractor<T> responseExtractor) throws RestClientException {
		return toCompletableFuture(template.execute(url, method,
				requestCallback, responseExtractor));
	}

	public NIORestClient(AsyncRestTemplate template) {
		super();

		this.template = template;
		MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
		converter.setObjectMapper(JacksonUtil.getMapper());
		template.getMessageConverters().add(converter);
	}

	<T> CompletableFuture<T> toCompletableFuture(
			final ListenableFuture<T> listenableFuture) {
		// create an instance of CompletableFuture
		CompletableFuture<T> completable = new CompletableFuture<T>() {
			@Override
			public boolean cancel(boolean mayInterruptIfRunning) {
				// propagate cancel to the listenable future
				boolean result = listenableFuture.cancel(mayInterruptIfRunning);
				super.cancel(mayInterruptIfRunning);
				return result;
			}
		};

		// add callback
		listenableFuture.addCallback(new ListenableFutureCallback<T>() {
			@Override
			public void onSuccess(T result) {
				completable.complete(result);
			}

			@Override
			public void onFailure(Throwable t) {
				completable.completeExceptionally(t);
			}
		});
		return completable;
	}


}
