//  This software code is made available "AS IS" without warranties of any
//  kind.  You may copy, display, modify and redistribute the software
//  code either by itself or as incorporated into your code; provided that
//  you do not remove any proprietary notices.  Your use of this software
//  code is at your own risk and you waive any claim against Amazon
//  Digital Services, Inc. or its affiliates with respect to your use of
//  this software code. (c) 2006-2007 Amazon Digital Services, Inc. or its
//  affiliates.

package net.noderunner.amazon.s3;

import java.util.Map;

import org.apache.commons.httpclient.HttpURL;
import org.apache.commons.httpclient.HttpsURL;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.URIException;

/**
 * Calling formats.
 * 
 * @author Elias Ross
 */
public abstract class CallingFormat {

	/**
	 * Call by using a path.
	 */
	public final static CallingFormat PATH = new PathCallingFormat();
	
	/**
	 * Call by using a sub-domain of the bucket name.
	 */
	public final static CallingFormat SUBDOMAIN = new SubdomainCallingFormat();
	
	/**
	 * Call using a "vanity" or user-provided hostname.
	 * The bucket name is in fact the domain name.
	 */
	public final static CallingFormat VANITY = new VanityCallingFormat();

	public abstract boolean supportsLocatedBuckets();

	public abstract String getEndpoint(String server, int port, Bucket bucket);

	public abstract String getPathBase(Bucket bucket, String key);

	public abstract URI getURI(boolean isSecure, String server, int port,
			Bucket bucket, String key, Map<String, String> pathArgs)
			throws URIException;

	/**
	 * Adds query parameters to the URL.
	 */
	private static URI addQuery(HttpURL uri, Map<String, String> pathArgs) throws URIException {
		if (pathArgs == null || pathArgs.isEmpty())
			return uri;
		int size = pathArgs.size();
		String[] name = new String[size];
		String[] value = new String[size];
    	int i = 0;
        for (Map.Entry<String, String> me : pathArgs.entrySet()) {
        	name[i] = me.getKey();
        	value[i] = me.getValue();
        	if (value[i] == null)
        		throw new NullPointerException("query cannot contain null " + pathArgs);
        	i++;
	    }
		uri.setQuery(name, value);
	    return uri;
	}

	private static URI url(boolean isSecure, String host, int port, String path, Map<String, String> pathArgs)
			throws URIException
	{
		HttpURL url = isSecure ? new HttpsURL(host, port, "/") : new HttpURL(host, port, "/");
		url.setEscapedPath(path);
		return addQuery(url, pathArgs);
	}

	static private class PathCallingFormat extends CallingFormat {
		@Override
		public boolean supportsLocatedBuckets() {
			return false;
		}

		@Override
		public String getPathBase(Bucket bucket, String key) {
			return isBucketSpecified(bucket) ? "/" + bucket + "/" + UrlEncoder.encode(key) : "/";
		}

		@Override
		public String getEndpoint(String server, int port, Bucket bucket) {
			return server + ":" + port;
		}

		@Override
		public URI getURI(boolean isSecure, String server, int port, Bucket bucket, String key, Map<String, String> pathArgs)
				throws URIException {
			String pathBase = isBucketSpecified(bucket) ? "/" + bucket + "/" + UrlEncoder.encode(key) : "/";
			return url(isSecure, server, port, pathBase, pathArgs);
		}

		private boolean isBucketSpecified(Bucket bucket) {
			if (bucket == null)
				return false;
			if (bucket.getName().length() == 0)
				return false;
			return true;
		}
	}

	static private class SubdomainCallingFormat extends CallingFormat {
		@Override
		public boolean supportsLocatedBuckets() {
			return true;
		}

		public String getServer(String server, Bucket bucket) {
			return bucket + "." + server;
		}

		@Override
		public String getEndpoint(String server, int port, Bucket bucket) {
			return getServer(server, bucket) + ":" + port;
		}

		@Override
		public String getPathBase(Bucket bucket, String key) {
			return "/" + UrlEncoder.encode(key);
		}

		@Override
		public URI getURI(boolean isSecure, String server, int port,
				Bucket bucket, String key, Map<String, String> pathArgs)
				throws URIException {
			if (bucket == null || !bucket.specified()) {
    			return url(isSecure, server, port, "", pathArgs);
			} else {
				String serverToUse = getServer(server, bucket);
				String pathBase = getPathBase(bucket, key);
    			return url(isSecure, serverToUse, port, pathBase, pathArgs);
			}
		}
	}

	static private class VanityCallingFormat extends SubdomainCallingFormat {
		@Override
		public String getServer(String server, Bucket bucket) {
			return bucket.getName();
		}
	}
}
