package net.noderunner.amazon.s3;
//  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.

import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.List;

import net.noderunner.amazon.s3.Bucket;
import net.noderunner.amazon.s3.CallingFormat;
import net.noderunner.amazon.s3.Connection;
import net.noderunner.amazon.s3.Entry;
import net.noderunner.amazon.s3.GetResponse;
import net.noderunner.amazon.s3.GetStreamResponse;
import net.noderunner.amazon.s3.Headers;
import net.noderunner.amazon.s3.ListResponse;
import net.noderunner.amazon.s3.Response;
import net.noderunner.amazon.s3.S3Object;
import net.noderunner.amazon.s3.emulator.Server;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;


public class S3EmulatorTest {
	
    private static final int UnspecifiedMaxKeys = -1;

	private Server s;

	private Bucket bucket;
	
	private int port;
	
	private Connection conn;

    @Before
    public void setUp() throws Exception {
        s = new Server();
        s.start();
        bucket = new Bucket("localhost");
        port = s.getPort();
        String awsAccessKeyId = "512341324124";
        String awsSecretAccessKey = "512351234123";
		CallingFormat format = CallingFormat.VANITY;
		conn = new Connection(awsAccessKeyId, awsSecretAccessKey, false, "localhost", port, format);
		conn.toString();
    }
    
    @After
    public void tearDown() throws Exception {
    	s.close();
    	conn.shutdown();
    }
    
    @Test
    public void testMe() throws Exception {
		String location = null;
        
        Response response = conn.create(bucket, location, null);
        response.assertOk();

        ListResponse listBucketResponse = conn.list(bucket);
        listBucketResponse.assertOk();
        assertEquals("list wasn't empty " + listBucketResponse, 0, listBucketResponse.getEntries().size());
        verifyBucketResponseParameters(listBucketResponse, bucket, "", "", UnspecifiedMaxKeys, null, false, null);

        // start delimiter tests

        final String text = "this is a test";
        final String key = "example.txt";
        final String innerKey = "test/inner.txt";
        final String lastKey = "z-last-key.txt";

        response = conn.put(bucket, key, new S3Object(text));
        response.assertOk();

        response = conn.put(bucket, innerKey, new S3Object(text));
        response.assertOk();

        response = conn.put(bucket, lastKey, new S3Object(text));
        response.assertOk();

        // plain list
        listBucketResponse = conn.list(bucket);
        listBucketResponse.assertOk();
        assertEquals("Unexpected list size", 3, listBucketResponse.getEntries().size());
        assertEquals("Unexpected common prefix size", 0, listBucketResponse.getCommonPrefixEntries().size());
        verifyBucketResponseParameters(listBucketResponse, bucket, "", "", UnspecifiedMaxKeys, null, false, null);
        
        System.out.println("LIST " + listBucketResponse.getEntries());

        // root "directory"
        listBucketResponse = conn.list(bucket, null, null, null, "/", null);
        listBucketResponse.assertOk();
        assertEquals("Unexpected list size " + listBucketResponse, 2, listBucketResponse.getEntries().size());
        // TODO
        // assertEquals("Unexpected common prefix size", 1, listBucketResponse.getCommonPrefixEntries().size());
        verifyBucketResponseParameters(listBucketResponse, bucket, "", "", UnspecifiedMaxKeys, "/", false, null);

        // root "directory" with a max-keys of "1"
        listBucketResponse = conn.list(bucket, null, null, 1, "/", null);
        listBucketResponse.assertOk();
        assertEquals("Unexpected list size", 1, listBucketResponse.getEntries().size());
        assertEquals("Unexpected common prefix size", 0, listBucketResponse.getCommonPrefixEntries().size());
        // TODO
        verifyBucketResponseParameters(listBucketResponse, bucket, "", "", 1, "/", true, "example.txt");

        // root "directory" with a max-keys of "2"
        listBucketResponse = conn.list(bucket, null, null, 2, "/", null);
        listBucketResponse.assertOk();
        assertEquals("Unexpected list size", 1, listBucketResponse.getEntries().size());
        // TODO
        // assertEquals("Unexpected common prefix size", 1, listBucketResponse.getCommonPrefixEntries().size());
        // TODO
        // verifyBucketResponseParameters(listBucketResponse, bucket, "", "", 2, "/", true, "test/");
        String marker = listBucketResponse.getNextMarker();
        listBucketResponse = conn.list(bucket, null, marker, 2, "/", null);
        listBucketResponse.assertOk();
        assertEquals("Unexpected list size", 1, listBucketResponse.getEntries().size());
        assertEquals("Unexpected common prefix size", 0, listBucketResponse.getCommonPrefixEntries().size());
        // TODO
        // verifyBucketResponseParameters(listBucketResponse, bucket, "", marker, 2, "/", false, null);

        // test "directory"
        listBucketResponse = conn.list(bucket, "test/", null, null, "/", null);
        listBucketResponse.assertOk();
        assertEquals("Unexpected list size", 1, listBucketResponse.getEntries().size());
        assertEquals("Unexpected common prefix size", 0, listBucketResponse.getCommonPrefixEntries().size());
        verifyBucketResponseParameters(listBucketResponse, bucket, "test/", "", UnspecifiedMaxKeys, "/", false, null);

        // remove innerkey
        response = conn.delete(bucket, innerKey, null);
        assertEquals(
                "couldn't delete entry",
                HttpURLConnection.HTTP_NO_CONTENT,
                response.getResponseCode());

        // remove last key
        response = conn.delete(bucket, lastKey, null);
        assertEquals(
                "couldn't delete entry",
                HttpURLConnection.HTTP_NO_CONTENT,
                response.getResponseCode());


        // end delimiter tests

        response = conn.put(bucket, key, new S3Object(text.getBytes(), null), null);
        response.assertOk();

        Headers metadata = new Headers();
        metadata.put("title", "title");
        response = conn.put(bucket, key, new S3Object(text.getBytes(), metadata), null);
        response.assertOk();

        GetResponse getResponse = conn.get(bucket, key, null);
        getResponse.assertOk();
        assertEquals("didn't get the right data back", text.getBytes(), getResponse.getObject().getData());
        assertEquals("didn't get the right metadata back", 1, getResponse.getObject().getMetadata().size());
        assertEquals(
                "didn't get the right metadata back",
                "title",
                getResponse.getObject().getMetadata().getValue("title"));
        assertEquals(
                "didn't get the right content-length",
                ""+text.length(),
                getResponse.getHeaderField("Content-Length"));
        
        GetStreamResponse streamResponse = conn.getStream(bucket, key);
        InputStream is = streamResponse.getInputStream();
        byte b[] = new byte[text.length()];
        int len = is.read(b);
        assertEquals("didn't get the right data back " + len, text.getBytes(), b);
        streamResponse.release();

        String titleWithSpaces = " \t  title with leading and trailing spaces    ";
        Headers h = new Headers();
        h.put("title", titleWithSpaces);
        response = conn.put(bucket, key, new S3Object(text.getBytes(), h), null);
        assertEquals(
                "couldn't put metadata with leading and trailing spaces",
                HttpURLConnection.HTTP_OK,
                response.getResponseCode());

        getResponse = conn.get(bucket, key, null);
        assertEquals(
                "couldn't get object",
                HttpURLConnection.HTTP_OK,
                getResponse.getResponseCode());
        assertEquals("didn't get the right metadata back", getResponse.getObject().getMetadata().size(), 1);
        assertEquals(
                "didn't get the right metadata back",
                titleWithSpaces.trim(),
                getResponse.getObject().getMetadata().getValue("title"));

        String weirdKey = "&weird+%";
        response = conn.put(bucket, weirdKey, new S3Object(text.getBytes()));
        assertEquals(
                "couldn't put weird key",
                HttpURLConnection.HTTP_OK,
                response.getResponseCode());

        getResponse = conn.get(bucket, weirdKey, null);
        assertEquals(
                "couldn't get weird key",
                HttpURLConnection.HTTP_OK,
                getResponse.getResponseCode());

        // start acl test

        getResponse = conn.getACL(bucket, key, null);
        assertEquals(
                "couldn't get acl",
                HttpURLConnection.HTTP_OK,
                getResponse.getResponseCode());

        byte[] acl = getResponse.getObject().getData();

        response = conn.putACL(bucket, key, new String(acl), null);
        assertEquals(
                "couldn't put acl",
                HttpURLConnection.HTTP_OK,
                response.getResponseCode());

        getResponse = conn.getACL(bucket, null);
        assertEquals(
                "couldn't get bucket acl",
                HttpURLConnection.HTTP_OK,
                getResponse.getResponseCode());

        byte[] bucketACL = getResponse.getObject().getData();

        response = conn.putACL(bucket, new String(bucketACL), null);
        assertEquals(
                "couldn't put bucket acl",
                HttpURLConnection.HTTP_OK,
                response.getResponseCode());

        // end acl test

        // bucket logging tests
        getResponse = conn.getBucketLogging(bucket, null);
        assertEquals(
                "couldn't get bucket logging config",
                HttpURLConnection.HTTP_OK, 
                getResponse.getResponseCode());

        byte[] bucketLogging = getResponse.getObject().getData();

        response = conn.putBucketLogging(bucket, new String(bucketLogging), null);
        assertEquals(
                "couldn't put bucket logging config",
                HttpURLConnection.HTTP_OK,
                response.getResponseCode());

        // end bucket logging tests

        listBucketResponse = conn.list(bucket, null, null, null, null);
        assertEquals(
                "couldn't list bucket",
                HttpURLConnection.HTTP_OK,
                listBucketResponse.getResponseCode());
        List<Entry> entries = listBucketResponse.getEntries();
        assertEquals("didn't get back the right number of entries", 2, entries.size());
        // depends on weirdKey < $key
        assertEquals("first key isn't right", weirdKey, ((Entry)entries.get(0)).getKey());
        assertEquals("second key isn't right", key, ((Entry)entries.get(1)).getKey());
        verifyBucketResponseParameters(listBucketResponse, bucket, "", "", UnspecifiedMaxKeys, null, false, null);

        listBucketResponse = conn.list(bucket, null, null, 1, null);
        assertEquals(
                "couldn't list bucket",
                HttpURLConnection.HTTP_OK,
                listBucketResponse.getResponseCode());
        assertEquals(
                "didn't get back the right number of entries",
                1,
                listBucketResponse.getEntries().size());
        verifyBucketResponseParameters(listBucketResponse, bucket, "", "", 1, null, true, null);

        for (Entry entry : entries) {
            response = conn.delete(bucket, entry.getKey(), null);
            assertEquals(
                    "couldn't delete entry",
                    HttpURLConnection.HTTP_NO_CONTENT,
                    response.getResponseCode());
        }

        /* TODO
        ListAllBucketsResponse listAllMyBucketsResponse = conn.listAllBuckets();
        assertEquals(
                "couldn't list all my buckets",
                HttpURLConnection.HTTP_OK,
                listAllMyBucketsResponse.getResponseCode());
        List<Bucket> buckets = listAllMyBucketsResponse.getEntries();

        response = conn.delete(bucket);
        assertEquals(
                "couldn't delete bucket",
                HttpURLConnection.HTTP_NO_CONTENT,
                response.getResponseCode());

        listAllMyBucketsResponse = conn.listAllBuckets();
        assertEquals(
                "couldn't list all my buckets",
                HttpURLConnection.HTTP_OK,
                listAllMyBucketsResponse.getResponseCode());
        assertEquals(
                "bucket count is incorrect",
                buckets.size() - 1,
                listAllMyBucketsResponse.getEntries().size());
        */

    }

    private static void verifyBucketResponseParameters( ListResponse listBucketResponse,
                                                           Bucket bucket, String prefix, String marker,
                                                           int maxKeys, String delimiter, boolean isTruncated,
                                                           String nextMarker ) {
        assertEquals("Bucket name should match.", bucket.getName(), listBucketResponse.getName());
        assertEquals("Bucket prefix should match.", prefix, listBucketResponse.getPrefix());
        assertEquals("Bucket marker should match.", marker, listBucketResponse.getMarker());
        assertEquals("Bucket delimiter should match.", delimiter, listBucketResponse.getDelimiter());
        if ( UnspecifiedMaxKeys != maxKeys ) {
            assertEquals("Bucket max-keys should match.", maxKeys, listBucketResponse.getMaxKeys());
        }
        assertEquals("Bucket should not be truncated.", isTruncated, listBucketResponse.isTruncated());
        assertEquals("Bucket nextMarker should match.", nextMarker, listBucketResponse.getNextMarker());
    }


    private static void assertEquals(String message, int expected, int actual) {
        if (expected != actual) {
            throw new RuntimeException(message + ": expected " + expected + " but got " + actual);
        }
    }
    
    private static void assertEquals(String message, byte[] expected, byte[] actual) {
        if (! Arrays.equals(expected, actual)) {
            throw new RuntimeException(
                    message +
                    ": expected " +
                    new String(expected) +
                    " but got " +
                    new String(actual));
        }
    }

    private static void assertEquals(String message, Object expected, Object actual) {
        if (expected != actual && (actual == null || ! actual.equals(expected))) {
            throw new RuntimeException(message + ": expected " + expected + " but got " + actual);
        }
    }

    private static void assertEquals(String message, boolean expected, boolean actual) {
        if (expected != actual) {
            throw new RuntimeException(message + ": expected " + expected + " but got " + actual);
        }
    }

}
