package net.callumtaylor.asynchttp;

import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;

import net.callumtaylor.asynchttp.obj.ConnectionInfo;
import net.callumtaylor.asynchttp.obj.NameValuePair;
import net.callumtaylor.asynchttp.obj.RequestMode;
import net.callumtaylor.asynchttp.obj.RequestUtil;
import net.callumtaylor.asynchttp.response.ByteArrayResponseHandler;
import net.callumtaylor.asynchttp.response.ResponseHandler;

import java.util.List;

import okhttp3.Cache;
import okhttp3.Headers;
import okhttp3.RequestBody;

/**
 * This class is a synchronous class which runs on any thread that the code was created
 * on. This will throw a {@link android.os.NetworkOnMainThreadException} if ran on the UI thread.
 *
 * <b>Note:</b> Because of the way SyncHttpClient works, only one instance can be created
 * at a time. If one client makes 2 requests, the first request is canceled for the new
 * request. You can either wait for the first to finish before making the second, or you
 * can create two separate instances.
 *
 * <b>Depends on</b>
 * <ul>
 * <li>{@link net.callumtaylor.asynchttp.response.ResponseHandler}</li>
 * <li>{@link RequestBody}</li>
 * <li>{@link NameValuePair}</li>
 * <li>{@link ConnectionInfo}</li>
 * <li>{@link net.callumtaylor.asynchttp.obj.Packet}</li>
 * <li>{@link RequestMode}</li>
 * <li>{@link RequestUtil}</li>
 * </ul>
 * <h1>Example GET</h1>
 *
 * SyncHttpClient is a paramitized class which means the type you infer to it, is the type
 * that gets returned when calling the method. When supplying a {@link net.callumtaylor.asynchttp.response.ResponseHandler}, that
 * processor must also paramitized with the same type as the SyncHttpClient instance.
 *
 * <pre>
 * SyncHttpClient<JsonElement> client = new SyncHttpClient<JsonElement>(&quot;http://example.com&quot;);
 * List&lt;NameValuePair&gt; params = new ArrayList&lt;NameValuePair&gt;();
 * params.add(new NameValuePair(&quot;key&quot;, &quot;value&quot;));
 *
 * JsonElement response = client.get(&quot;api/v1/&quot;, params, new JsonResponseHandler());
 * </pre>
 *
 * <h1>Example DELETE</h1>
 *
 * <pre>
 * SyncHttpClient<JsonElement> client = new SyncHttpClient<JsonElement>(&quot;http://example.com&quot;);
 * List&lt;NameValuePair&gt; params = new ArrayList&lt;NameValuePair&gt;();
 * params.add(new NameValuePair(&quot;key&quot;, &quot;value&quot;));
 *
 * JsonElement response = client.delete(&quot;api/v1/&quot;, params, new JsonResponseHandler());
 * </pre>
 *
 * Because of the nature of REST, GET and DELETE requests behave in the same
 * way, POST and PUT requests also behave in the same way.
 *
 * @author Callum Taylor &lt;callumtaylor.net&gt; &#064;scruffyfox
 */
public class SyncHttpClient<E>
{
	/**
	 * User agent to send with every request. Defaults to {@link RequestUtil#getDefaultUserAgent()}
	 */
	public static String userAgent = RequestUtil.getDefaultUserAgent();

	/**
	 * Instance for where to store cached requests. Defaults to null (off). You should set this to {@link Context#getCacheDir()}
	 * if you want auto cache-control handling
	 */
	public static Cache cache = null;

	private Uri requestUri;
	private long requestTimeout = 0L;
	private boolean allowAllSsl = false;
	private boolean allowRedirect = false;
	private ClientExecutorTask<E> executor;

	/**
	 * Creates a new client using a base Url without a timeout
	 * @param baseUrl The base connection url
	 */
	public SyncHttpClient(String baseUrl)
	{
		this(baseUrl, 0);
	}

	/**
	 * Creates a new client using a base Uri without a timeout
	 * @param baseUri The base connection uri
	 */
	public SyncHttpClient(Uri baseUri)
	{
		this(baseUri, 0);
	}

	/**
	 * Creates a new client using a base Url with a timeout in MS
	 * @param baseUrl The base connection url
	 * @param timeout The timeout in MS
	 */
	public SyncHttpClient(String baseUrl, long timeout)
	{
		this(Uri.parse(baseUrl), timeout);
	}

	/**
	 * Creates a new client using a base Uri with a timeout in MS
	 * @param baseUri The base connection uri
	 * @param timeout The timeout in MS
	 */
	public SyncHttpClient(Uri baseUri, long timeout)
	{
		requestUri = baseUri;
		requestTimeout = timeout;
	}

	/**
	 * Cancels the current executor task
	 */
	public void cancel()
	{
		if (executor != null)
		{
			executor.cancel();
		}
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(@NonNull ResponseHandler<?> response)
	{
		return get("", null, null, response);
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param path The path extended from the baseUri
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(String path)
	{
		return get(path, null, null, new ByteArrayResponseHandler());
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(String path, @NonNull ResponseHandler<?> response)
	{
		return get(path, null, null, response);
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(@Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return get("", null, headers, response);
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(@Nullable List<NameValuePair> params,  @NonNull ResponseHandler<?> response)
	{
		return get("", params, null, response);
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(@Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return get("", params, headers, response);
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The request params for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(String path, @Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return get(path, params, null, response);
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(String path, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return get(path, null, headers, response);
	}

	/**
	 * Performs a GET request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E get(String path, @Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.GET, requestUri, headers, null, response);
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E options(@NonNull ResponseHandler<?> response)
	{
		return options("", null, null, response);
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param path The path extended from the baseUri
	 */
	public E options(String path)
	{
		return options(path, null, null, new ByteArrayResponseHandler());
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param response The response handler for the request
	 */
	public E options(String path, @NonNull ResponseHandler<?> response)
	{
		return options(path, null, null, response);
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E options(@Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return options("", null, headers, response);
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E options(@Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return options("", params, null, response);
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E options(@Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return options("", params, headers, response);
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The request params for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E options(String path, @Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return options(path, params, null, response);
	}

	/**
	 * Performs a OPTIONS request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E options(String path, @Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.OPTIONS, requestUri, headers, null, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(@NonNull ResponseHandler<?> response)
	{
		return head("", null, null, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param path The path extended from the baseUri
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(String path)
	{
		return head(path, null, null, new ByteArrayResponseHandler());
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(String path, @NonNull ResponseHandler<?> response)
	{
		return head(path, null, null, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(@Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return head("", params, null, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(@Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return head("", null, headers, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(@Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return head("", params, headers, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The request params for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(String path, @Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return head(path, params, null, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(String path, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return head(path, null, headers, response);
	}

	/**
	 * Performs a HEAD request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E head(String path, @Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.HEAD, requestUri, headers, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(@NonNull ResponseHandler<?> response)
	{
		return delete("", null, null, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 */
	public E delete(String path)
	{
		return delete(path, null, null, null, new ByteArrayResponseHandler());
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @NonNull ResponseHandler<?> response)
	{
		return delete(path, null, null, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(@Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return delete("", null, null, headers, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(@Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return delete("", params, null, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(@Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return delete("", params, headers, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The request params for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return delete(path, params, null, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return delete(path, null, null, headers, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.DELETE, requestUri, headers, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param deleteData The delete data entity to delete to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(@Nullable List<NameValuePair> params, @Nullable RequestBody deleteData, @NonNull ResponseHandler<?> response)
	{
		return delete("", params, deleteData, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param deleteData The delete data entity to delete to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(RequestBody deleteData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return delete("", null, deleteData, headers, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param deleteData The delete data entity to delete to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @Nullable RequestBody deleteData, @NonNull ResponseHandler<?> response)
	{
		return delete(path, null, deleteData, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param deleteData The delete data entity to delete to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @Nullable RequestBody deleteData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return delete(path, null, deleteData, headers, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param deleteData The delete data entity to delete to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody deleteData, @NonNull ResponseHandler<?> response)
	{
		return delete(path, params, deleteData, null, response);
	}

	/**
	 * Performs a DELETE request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param deleteData The delete data entity to delete to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E delete(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody deleteData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.DELETE, requestUri, headers, deleteData, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(@NonNull ResponseHandler<?> response)
	{
		return post("", null, null, null, response);
	}

	/**
	 * Performs a POST request on the baseUr
	 * @param path The path extended from the baseUri
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path)
	{
		return post(path, null, null, null, new ByteArrayResponseHandler());
	}

	/**
	 * Performs a POST request on the baseUr
	 * @param path The path extended from the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @NonNull ResponseHandler<?> response)
	{
		return post(path, null, null, null, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(@Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return post("", params, null, null, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(@Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return post("", null, null, headers, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(@Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return post("", params, null, headers, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return post("", null, postData, null, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return post("", null, postData, headers, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(@Nullable List<NameValuePair> params, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return post("", params, postData, null, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(@Nullable List<NameValuePair> params, @Nullable RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return post("", params, postData, headers, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return post(path, params, null, null, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return post(path, null, null, headers, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return post(path, params, null, headers, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return post(path, null, postData, null, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @Nullable RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return post(path, null, postData, headers, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return post(path, params, postData, null, response);
	}

	/**
	 * Performs a POST request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E post(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.POST, requestUri, headers, postData, response);
	}

	/**
	 * Performs a PUT request on the baseUr
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(@NonNull ResponseHandler<?> response)
	{
		return put("", null, null, null, response);
	}

	/**
	 * Performs a PUT request on the baseUr
	 * @param path The path extended from the baseUri
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path)
	{
		return put(path, null, null, null, new ByteArrayResponseHandler());
	}

	/**
	 * Performs a PUT request on the baseUr
	 * @param path The path extended from the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @NonNull ResponseHandler<?> response)
	{
		return put(path, null, null, null, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(@Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return put("", params, null, null, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(@Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return put("", null, null, headers, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(@Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return put("", params, null, headers, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return put("", null, postData, null, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return put("", null, postData, headers, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(@Nullable List<NameValuePair> params, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return put("", params, postData, null, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return put(path, params, null, null, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return put(path, null, null, headers, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return put(path, params, null, headers, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return put(path, null, postData, null, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @Nullable RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return put(path, null, postData, headers, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return put(path, params, postData, null, response);
	}

	/**
	 * Performs a PUT request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E put(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.PUT, requestUri, headers, postData, response);
	}

	/**
	 * Performs a PATCH request on the baseUr
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(@NonNull ResponseHandler<?> response)
	{
		return patch("", null, null, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUr
	 * @param path The path extended from the baseUri
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path)
	{
		return patch(path, null, null, null, new ByteArrayResponseHandler());
	}

	/**
	 * Performs a PATCH request on the baseUr
	 * @param path The path extended from the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @NonNull ResponseHandler<?> response)
	{
		return patch(path, null, null, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(@Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return patch("", params, null, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(@Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return patch("", null, null, headers, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(@Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return patch("", params, null, headers, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return patch("", null, postData, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return patch("", null, postData, headers, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(@Nullable List<NameValuePair> params, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return patch("", params, postData, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @Nullable List<NameValuePair> params, @NonNull ResponseHandler<?> response)
	{
		return patch(path, params, null, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return patch(path, null, null, headers, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @Nullable List<NameValuePair> params, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return patch(path, params, null, headers, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return patch(path, null, postData, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @Nullable RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		return patch(path, null, postData, headers, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody postData, @NonNull ResponseHandler<?> response)
	{
		return patch(path, params, postData, null, response);
	}

	/**
	 * Performs a PATCH request on the baseUri
	 * @param path The path extended from the baseUri
	 * @param params The Query params to append to the baseUri
	 * @param postData The post data entity to post to the server
	 * @param headers The request headers for the connection
	 * @param response The response handler for the request
	 *
	 * @return The response object, or null
	 */
	@Nullable
	public E patch(String path, @Nullable List<NameValuePair> params, @Nullable RequestBody postData, @Nullable Headers headers, @NonNull ResponseHandler<?> response)
	{
		if (!TextUtils.isEmpty(path))
		{
			requestUri = Uri.withAppendedPath(requestUri, path);
		}

		requestUri = RequestUtil.appendParams(requestUri, params);
		return executeTask(RequestMode.PATCH, requestUri, headers, postData, response);
	}

	private E executeTask(RequestMode mode, Uri uri, @Nullable Headers headers, @Nullable RequestBody sendData, ResponseHandler<?> requestProcessor)
	{
		if (headers == null)
		{
			headers = new Headers.Builder().build();
		}

		headers = headers.newBuilder().add("User-Agent", userAgent).build();

		executor = new ClientExecutorTask<E>(mode, uri, headers, sendData, requestProcessor, allowRedirect, allowAllSsl, requestTimeout, cache);
		executor.preExecute();
		E response = executor.executeTask();
		executor.postExecute();

		return response;
	}

	/**
	 * Sets to allow all SSL. This is insecure, avoid using this method.
	 * @param allow Allow all SSL true/false
	 */
	public void setAllowAllSsl(boolean allow)
	{
		this.allowAllSsl = allow;
	}

	/**
	 * Sets to auto redirect on 302 responses
	 * @param allow Allow redirect true/false
	 */
	public void setAllowRedirect(boolean allow)
	{
		this.allowRedirect = allow;
	}

	/**
	 * Gets the connection info <b>after</b> a connection request has been made
	 * @return The connection info, or null
	 */
	public ConnectionInfo getConnectionInfo()
	{
		if (executor != null && executor.response != null)
		{
			return executor.response.getConnectionInfo();
		}

		return null;
	}
}
