//  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.io.IOException;
import java.security.Key;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.httpclient.URI;

/**
 * Generates URL Query Strings that can be used to perform operations.
 * These parameters include an expiration date, so that
 * if you hand them off to someone else, they will only work for a limited amount of time.
 */
public class QueryGenerator {

    private String awsAccessKeyId;
    private Key awsSecretAccessKey;
    private boolean isSecure;
    private String server;
    private int port;
    private CallingFormat callingFormat;

    private Long expiresIn = null;
    private Long expires = null;

    // by default, expire in 1 minute.
    private static final Long DEFAULT_EXPIRES_IN = new Long(60 * 1000);

    public QueryGenerator(String awsAccessKeyId, String awsSecretAccessKey) {
        this(awsAccessKeyId, awsSecretAccessKey, true);
    }

    public QueryGenerator(String awsAccessKeyId, String awsSecretAccessKey, 
                                    boolean isSecure)
    {
        this(awsAccessKeyId, awsSecretAccessKey, isSecure, Connection.DEFAULT_HOST);
    }

    public QueryGenerator(String awsAccessKeyId, String awsSecretAccessKey, 
                                    boolean isSecure, String server)
    {
        this(awsAccessKeyId, awsSecretAccessKey, isSecure, server,
             isSecure ? Connection.SECURE_PORT : Connection.INSECURE_PORT);
    }

    public QueryGenerator(String awsAccessKeyId, String awsSecretAccessKey, 
                                    boolean isSecure, String server, int port)
    {
        this(awsAccessKeyId, awsSecretAccessKey, isSecure, server,
             port, CallingFormat.SUBDOMAIN);
    }
   
    public QueryGenerator(String awsAccessKeyId, String awsSecretAccessKey, 
                                    boolean isSecure, String server, CallingFormat callingFormat)
    {
        this(awsAccessKeyId, awsSecretAccessKey, isSecure, server, 
             isSecure ? Connection.SECURE_PORT : Connection.INSECURE_PORT, 
             callingFormat);
    }

    public QueryGenerator(String awsAccessKeyId, String awsSecretAccessKey,
                             boolean isSecure, String server, int port, CallingFormat callingFormat)
    {
        this.awsAccessKeyId = awsAccessKeyId;
        this.awsSecretAccessKey = CanonicalString.key(awsSecretAccessKey);
        this.isSecure = isSecure;
        this.server = server;
        this.port = port;
        this.callingFormat = callingFormat;

        this.expiresIn = DEFAULT_EXPIRES_IN;
        this.expires = null;
    }
    

    public void setCallingFormat(CallingFormat format) {
        this.callingFormat = format;
    }

    public void setExpires(long millisSinceEpoch) {
        expires = new Long(millisSinceEpoch);
        expiresIn = null;
    }

    public void setExpiresIn(long millis) {
        expiresIn = new Long(millis);
        expires = null;
    }

    public URI create(Bucket bucket, Headers headers)
    {
        // validate bucket name
        if (!bucket.validateName(callingFormat))
            throw new IllegalArgumentException("Invalid Bucket Name: "+bucket);

        return generateURI(Method.PUT, bucket, "", headers);
    }

    public URI list(Bucket bucket, String prefix, String marker,
                             Integer maxKeys, Headers headers){
        return list(bucket, prefix, marker, maxKeys, null, headers);
    }

    public URI list(Bucket bucket, String prefix, String marker,
                             Integer maxKeys, String delimiter, Headers headers)
    { 
        return generateURI(Method.GET, bucket, headers);
    }

	public URI delete(Bucket bucket, Headers headers)
    {
        return generateURI(Method.DELETE, bucket, headers);
    }

    public URI put(Bucket bucket, String key, S3Object object, Headers headers) {
        Headers metadata = null;
        if (object != null) {
            metadata = object.getMetadata();
        }
        if (headers == null) {
        	headers = new Headers();
        }

        return generateURI(Method.PUT, bucket, key, headers.mergeMetadata(metadata));
    }

    public URI get(Bucket bucket, String key, Headers headers)
    {
        return generateURI(Method.GET, bucket, key, headers);
    }

    public URI delete(Bucket bucket, String key, Headers headers)
    {
        return generateURI(Method.DELETE, bucket, key, headers);
    }
    
    private Map<String, String> map(String name) {
    	HashMap<String, String> map = new HashMap<String, String>();
    	map.put(name, "");
    	return map;
    }

    public URI getBucketLogging(Bucket bucket, Headers headers) {
        return generateURI(Method.GET, bucket, "", map("logging"), headers);
    }

    public URI putBucketLogging(Bucket bucket, Headers headers) {
        return generateURI(Method.PUT, bucket, "", map("logging"), headers);
    }

    public URI getACL(Bucket bucket, Headers headers) {
        return getACL(bucket, "", headers);
    }

    public URI getACL(Bucket bucket, String key, Headers headers)
    {
        return generateURI(Method.GET, bucket, key, map("acl"), headers);
    }

    public URI putACL(Bucket bucket, Headers headers) {
        return putACL(bucket, "", headers);
    }

    public URI putACL(Bucket bucket, String key, Headers headers)
    {
        return generateURI(Method.PUT, bucket, key, map("acl"), headers);
    }

    public URI listAllBuckets(Headers headers)
    {
        return generateURI(Method.GET, headers);
    }

	public URI listAllBuckets() {
		return listAllBuckets(null);
	}

	public String makeBareURI(Bucket bucket, String key) {
        StringBuilder buffer = new StringBuilder();
        if (this.isSecure) {
            buffer.append("https://");
        } else {
            buffer.append("http://");
        }
        buffer.append(this.server).append(":").append(this.port).append("/").append(bucket);
        buffer.append("/").append(UrlEncoder.encode(key));

        return buffer.toString();
    }

    @SuppressWarnings("unchecked")
	private URI generateURI(Method method, Bucket bucket, String key, Headers headers) {
		return generateURI(method, bucket, key, new HashMap(), headers);
	}

    private URI generateURI(Method method, Bucket bucket, Headers headers) {
		return generateURI(method, bucket, "", headers);
	}

	private URI generateURI(Method method, Headers headers) {
		return generateURI(method, null, headers);
	}

    private URI generateURI(Method method, Bucket bucket, String key, Map<String, String> pathArgs, Headers headers) 
    {
        long expires = 0L;
        if (this.expiresIn != null) {
            expires = System.currentTimeMillis() + this.expiresIn.longValue();
        } else if (this.expires != null) {
            expires = this.expires.longValue();
        } else {
            throw new RuntimeException("Illegal expires state");
        }

        // convert to seconds
        expires /= 1000;

        String enckey = UrlEncoder.encode(key);
        String canonicalString = CanonicalString.make(method, bucket, enckey, pathArgs, headers, ""+expires);
        String encodedCanonical = CanonicalString.encode(this.awsSecretAccessKey, canonicalString);

        pathArgs.put("Signature", encodedCanonical);
        pathArgs.put("Expires", Long.toString(expires));
        pathArgs.put("AWSAccessKeyId", this.awsAccessKeyId);
        
        try {
            return this.callingFormat.getURI(this.isSecure, server, port, bucket, key, pathArgs);
        } catch (IOException e) {
        	throw new IllegalStateException("Unable to generate URI " + e);
        }
    }

}
