//  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 Amazon Digital Services, Inc. or its
//  affiliates.

package net.noderunner.amazon.s3;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.httpclient.HttpMethod;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

/**
 * Returned by {@link Connection#list}.
 */
public class ListResponse extends Response {

    private String name = null;

    private String prefix = null;

    private String marker = null;

    private String delimiter = null;

    private int maxKeys = 0;

    private boolean isTruncated = false;

    private String nextMarker = null;

    private List<Entry> entries = null;

    private List<CommonPrefixEntry> commonPrefixEntries = null;

    ListResponse(HttpMethod method) throws IOException {
        super(method);
        if (isOk()) {
        	parse(new ListBucketHandler());
        }
    }
    
	/**
     * A List of CommonPrefixEntry objects representing the common prefixes of the
     * keys that matched up to the delimiter.  Null if the request fails.
     */
	public List<CommonPrefixEntry> getCommonPrefixEntries() {
		return commonPrefixEntries;
	}

	/**
	 * Returns delimiter.
	 */
	public String getDelimiter() {
		return delimiter;
	}

    /**
     * A List of ListEntry objects representing the objects in the given bucket.  
     */
	public List<Entry> getEntries() {
		return entries;
	}

    /**
     * Indicates if there are more results to the list.  True if the current
     * list results have been truncated.  false if request fails.
     */
	public boolean isTruncated() {
		return isTruncated;
	}

	/**
	 * Prefix entry.
	 */
	public static class CommonPrefixEntry {
		
        /**
         * The prefix common to the delimited keys it represents
         */
        private String prefix;

		CommonPrefixEntry() {
		}

		/**
		 * Returns prefix.
		 */
		public String getPrefix() {
			return prefix;
		}
    }

    class ListBucketHandler extends DefaultHandler {

        private boolean isEchoedPrefix = false;
        private Entry keyEntry = null;
        private CommonPrefixEntry commonPrefixEntry = null;
        private StringBuilder currText = null;
        private SimpleDateFormat iso8601Parser = null;

        public ListBucketHandler() {
            super();
            entries = new ArrayList<Entry>();
            commonPrefixEntries = new ArrayList<CommonPrefixEntry>();
            this.iso8601Parser = new ISO801DateFormat();
            this.currText = new StringBuilder();
        }

        @Override
        public void startDocument() {
            this.isEchoedPrefix = true;
        }

        @Override
        public void startElement(String uri, String name, String qName, Attributes attrs) {
            if (name.equals("Contents")) {
                this.keyEntry = new Entry();
            } else if (name.equals("Owner")) {
                this.keyEntry.setOwner(new Owner());
            } else if (name.equals("CommonPrefixes")){
                this.commonPrefixEntry = new CommonPrefixEntry();
            }
            this.currText = new StringBuilder();
        }

        @Override
        public void endElement(String uri, String name, String qName) {
            if (name.equals("Name")) {
                ListResponse.this.name = this.currText.toString();
            } 
            // this prefix is the one we echo back from the request
            else if (name.equals("Prefix") && this.isEchoedPrefix) {
                prefix = this.currText.toString();
                this.isEchoedPrefix = false;
            } else if (name.equals("Marker")) {
                marker = this.currText.toString();
            } else if (name.equals("MaxKeys")) {
                maxKeys = Integer.parseInt(this.currText.toString());
            } else if (name.equals("Delimiter")) {
                delimiter = this.currText.toString();
            } else if (name.equals("IsTruncated")) {
                isTruncated = Boolean.valueOf(this.currText.toString());
            } else if (name.equals("NextMarker")) {
                nextMarker = this.currText.toString();
            } else if (name.equals("Contents")) {
                entries.add(this.keyEntry);
            } else if (name.equals("Key")) {
                this.keyEntry.setKey(this.currText.toString());
            } else if (name.equals("LastModified")) {
                try {
                    this.keyEntry.setLastModified(this.iso8601Parser.parse(this.currText.toString()));
                } catch (ParseException e) {
                    throw new RuntimeException("Unexpected date format in list bucket output", e);
                }
            } else if (name.equals("ETag")) {
                this.keyEntry.setETag(this.currText.toString());
            } else if (name.equals("Size")) {
                this.keyEntry.setSize(Long.parseLong(this.currText.toString()));
            } else if (name.equals("StorageClass")) {
                this.keyEntry.setStorageClass(this.currText.toString());
            } else if (name.equals("ID")) {
                this.keyEntry.getOwner().setId(this.currText.toString());
            } else if (name.equals("DisplayName")) {
                this.keyEntry.getOwner().setDisplayName(this.currText.toString());
            } else if (name.equals("CommonPrefixes")) {
                commonPrefixEntries.add(this.commonPrefixEntry);
            }
            // this is the common prefix for keys that match up to the delimiter 
            else if (name.equals("Prefix")) {
                this.commonPrefixEntry.prefix = this.currText.toString();
            }
        }

        @Override
        public void characters(char ch[], int start, int length) {
            this.currText.append(ch, start, length);
        }

    }
    
    /**
     * The name of the bucket being listed.  Null if request fails.
     */
    public String getName() {
        return this.name;
    }

    /**
     * The prefix echoed back from the request.  Null if request fails.
     */
    public String getPrefix() {
        return this.prefix;
    }

    /**
     * The marker echoed back from the request.  Null if request fails.
     */
    public String getMarker() {
        return this.marker;
    }

    /**
     * The maxKeys echoed back from the request if specified.  0 if request fails.
     */
    public int getMaxKeys(){
        return this.maxKeys;
    }

    /**
     * The delimiter echoed back from the request.  Null if not specified in
     * the request, or if it fails.
     */
    public boolean getIsTruncated() {
        return this.isTruncated;
    }

    /**
     * Indicates what to use as a marker for subsequent list requests in the event
     * that the results are truncated.  Present only when a delimiter is specified.  
     * Null if request fails.
     */
    public String getNextMarker() {
        return this.nextMarker;
    }
    
    @Override
    public String toString() {
    	return super.toString() + 
    		" entries=" + entries +
    		" name=" + name +
    		" prefix=" + prefix +
    		" marker=" + marker +
    		" maxKeys=" + maxKeys +
    		" isTruncated=" + isTruncated +
    		" nextMarker=" + nextMarker +
    		" prefix=" + commonPrefixEntries;
    }

}
