package net.pms.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;

/**
 * Downloads URLs
 * 
 * @author Tim Cox (mail@tcox.org)
 */
public class UriRetriever {
	private static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
	private static final int BUFFER_SIZE = 1024;
	private HttpClient client = new HttpClient();

	public byte[] get(String uri) throws IOException {
		HttpMethod method = new GetMethod(uri);
		try {
			int statusCode = client.executeMethod(method);
			if (statusCode != HttpStatus.SC_OK) {
				throw new IOException("HTTP response not OK");
			}
			return method.getResponseBody();
		} catch (HttpException e) {
			throw new IOException("Unable to download by HTTP" + e.getMessage());
		} finally {
			method.releaseConnection();
		}
	}

	public byte[] getWithCallback(String uri, UriRetrieverCallback callback) throws IOException {
		HttpMethod getMethod = null;

		try {
			getMethod = startGetRequest(uri, callback);
			int totalBytes = getContentSize(uri, getMethod);
			byte[] data = pullData(uri, getMethod, callback, totalBytes);
			return data;
		} catch (HttpException e) {
			throw new IOException("Unable to download via HTTP: " + uri + ": " + e.getMessage());
		} catch (IOException e) {
			throw new IOException("Unable to download via HTTP: " + uri + ": " + e.getMessage());
		} finally {
			if (getMethod != null) {
				getMethod.releaseConnection();
			}
		}
	}

	private HttpMethod startGetRequest(String uri, UriRetrieverCallback callback) throws HttpException, IOException {
		int statusCode = -1;
		HttpMethod method = new GetMethod(uri);
		configureMethod(method);
		statusCode = client.executeMethod(method);
		if (statusCode != HttpStatus.SC_OK) {
			throw new IOException("HTTP result code was not OK");
		}
		return method;
	}

	private void configureMethod(HttpMethod method) {
		method.setRequestHeader("User-Agent", "PMS");
		method.setFollowRedirects(true);
	}

	private static byte[] pullData(String uri, HttpMethod method, UriRetrieverCallback callback, int totalBytes) throws IOException {
		int bytesWritten = 0;
		InputStream input = method.getResponseBodyAsStream();
		ByteArrayOutputStream output = new ByteArrayOutputStream(totalBytes);
		byte[] buffer = new byte[BUFFER_SIZE];
		int count = -1;
		while ((count = input.read(buffer)) != -1) {
			output.write(buffer, 0, count);
			bytesWritten += count;
			invokeCallback(uri, callback, totalBytes, bytesWritten);
		}
		output.flush();
		output.close();
		return output.toByteArray();
	}

	private static void invokeCallback(String uri, UriRetrieverCallback callback, int totalBytes, int bytesWritten) throws IOException {
		try {
			callback.progressMade(uri, bytesWritten, totalBytes);
		} catch (UriRetrieverCallback.CancelDownloadException e) {
			throw new IOException("Download was cancelled");
		}
	}

	private int getContentSize(String uri, HttpMethod method) {
		Header header = method.getResponseHeader(HTTP_HEADER_CONTENT_LENGTH);
		if (header != null) {
			String value = "" + header.getValue();
			int totalBytes = -1;
			try {
				totalBytes = Integer.parseInt(value);
			} catch (NumberFormatException e) {
				return 0;
			}
			return totalBytes;
		} else {
			return 0;
		}
	}
}
