package com.github.michaelbull.rs;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Optional;

/**
 * A {@link Client} that wraps a {@link org.apache.http.client.HttpClient} to interact with the RuneScape web-services API.
 */
public final class HttpClient implements Client {

	/**
	 * The URL to the RuneScape public web-services.
	 */
	public static final String WEB_SERVICES_URL = "http://services.runescape.com";

	/**
	 * The {@link Gson} instance.
	 */
	private final Gson gson = new Gson();

	/**
	 * Reads a {@link String} from a specified URL.
	 * @param url The URL to request from.
	 * @return The {@link String}.
	 * @throws IOException If an I/O error occurs.
	 */
	private static String stringFrom(String url) throws IOException {
		Preconditions.checkNotNull(url);
		HttpUriRequest request = new HttpGet(url);
		request.addHeader("accept", "application/json");
		request.addHeader("accept", "text/csv");

		try (CloseableHttpClient client = HttpClients.createDefault();
			 CloseableHttpResponse response = client.execute(request)) {
			return EntityUtils.toString(response.getEntity());
		}
	}

	/**
	 * Deserializes a JSON file from a specified URL into an object of the specified type.
	 * @param url The URL to deserialize from.
	 * @param typeOfT The specific genericized type of src.
	 * @param <T> The type of the desired object
	 * @return An {@link Optional} containing object of type T from the json, or {@link Optional#empty()} if the URL could not be deserialized.
	 * @throws IOException If an I/O error occurs.
	 */
	@Override
	public <T> Optional<T> fromJson(String url, Type typeOfT) throws IOException {
		Preconditions.checkNotNull(url);
		Preconditions.checkNotNull(typeOfT);

		try {
			return Optional.ofNullable(gson.fromJson(stringFrom(url), typeOfT));
		} catch (JsonSyntaxException | JsonIOException ignored) {
			return Optional.empty();
		}
	}

	/**
	 * Deserializes a JSON file from a specified URL into an object of the specified type.
	 * @param url The URL to deserialize from.
	 * @param classOfT The class of T.
	 * @param <T> The type of the desired object
	 * @return An optional of object of type T from the json, or {@link Optional#empty()} if the URL could not be deserialized.
	 * @throws IOException If an I/O error occurs.
	 */
	@Override
	public <T> Optional<T> fromJson(String url, Class<T> classOfT) throws IOException {
		Preconditions.checkNotNull(url);
		Preconditions.checkNotNull(classOfT);

		try {
			return Optional.ofNullable(gson.fromJson(stringFrom(url), classOfT));
		} catch (JsonSyntaxException | JsonIOException ignored) {
			return Optional.empty();
		}
	}

	/**
	 * Deserializes a CSV file from a specified URL into an {@link ImmutableList} of {@link CSVRecord}s.
	 * @param url The URL to deserialize from.
	 * @return An {@link ImmutableList} of {@link CSVRecord}s.
	 * @throws IOException If an I/O error occurs.
	 */
	@Override
	public ImmutableList<CSVRecord> fromCSV(String url) throws IOException {
		Preconditions.checkNotNull(url);

		try (CSVParser parser = CSVParser.parse(stringFrom(url), CSV_FORMAT)) {
			return ImmutableList.copyOf(parser.getRecords());
		}
	}
}