/*
 * Copyright 2014 Ray Holder
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.rholder.esthree.command;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.github.rholder.esthree.util.RetryUtils;
import com.github.rholder.retry.RetryException;

import java.io.PrintStream;
import java.math.BigInteger;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;

public class Ls implements Callable<Integer> {

    // that's pretty close to s3cmd
    public static final String DEFAULT_LIST_FORMAT = "%1$tF %1$tR %2$9s   s3://%3$s/%4$s";

    public static final Integer AWS_MAX_KEYS = 1000;

    public static final BigInteger AWS_MAX_KEYS_BIG = new BigInteger(AWS_MAX_KEYS.toString());
    public static final BigInteger ONE = new BigInteger("1");

    public AmazonS3Client amazonS3Client;
    public String bucket;
    public String prefix;
    public BigInteger limit;

    public String listFormat = DEFAULT_LIST_FORMAT;
    public PrintStream printStream;

    public Ls(AmazonS3Client amazonS3Client, String bucket) {
        this.amazonS3Client = amazonS3Client;
        this.bucket = bucket;
    }

    public Ls withPrefix(String prefix) {
        this.prefix = prefix;
        return this;
    }

    public Ls withLimit(BigInteger limit) {
        this.limit = limit;
        return this;
    }

    public Ls withListFormat(String listFormat) {
        this.listFormat = listFormat;
        return this;
    }

    public Ls withPrintStream(PrintStream printStream) {
        this.printStream = printStream;
        return this;
    }

    @Override
    public Integer call() throws Exception {

        BigInteger count = new BigInteger("0");
        String nextMarker = null;
        int nextLimit;
        do {
            // no limit always requests the max
            nextLimit = AWS_MAX_KEYS;
            if(limit != null) {
                // if it is set, limit the next request to no more than the max that are still available
                nextLimit = limit.subtract(count).min(AWS_MAX_KEYS_BIG).intValue();
            }

            if(nextLimit > 0) {
                ObjectListing o = list(nextMarker, nextLimit);
                for (S3ObjectSummary os : o.getObjectSummaries()) {
                    if (limit == null || count.compareTo(limit) <= 0) {
                        printStream.println(String.format(listFormat,
                                os.getLastModified(),
                                os.getSize(),
                                os.getBucketName(),
                                os.getKey(),
                                os.getETag(),
                                os.getOwner(),
                                os.getStorageClass()));
                        nextMarker = o.getNextMarker();
                        count = count.add(ONE);
                    } else {
                        // bail out when we've printed all we can
                        nextMarker = null;
                    }
                }
            }
        } while (nextMarker != null && nextLimit > 0);

        return 0;
    }

    /**
     * Return the ObjectListing starting at the given marker. The number of
     * ObjectSummaries is bounded to be 1000 (this is the Amazon S3 default,
     * which we're also setting here explicitly to minimize future SDK update
     * implications).
     *
     * @param marker the last marker from a previous call or null
     * @param limit  return no more than this many
     */
    public ObjectListing list(final String marker, final int limit) throws ExecutionException, RetryException {
        return (ObjectListing) RetryUtils.AWS_RETRYER.call(new Callable<Object>() {
            public ObjectListing call() throws Exception {

                ListObjectsRequest lor = new ListObjectsRequest()
                        .withBucketName(bucket)
                        .withMarker(marker)
                        .withMaxKeys(limit);
                if (prefix != null) {
                    lor.withPrefix(prefix);
                }
                return amazonS3Client.listObjects(lor);
            }
        });
    }
}

@@ -1268,16 +1268,100 @@
 s/%254$s%22;
+%0A    public static final String DEFAULT_LIST_DIR_FORMAT = %22%251$26s   s3://%252$s/%253$s%22;
 %0A%0A    pu
@@ -1742,16 +1742,75 @@
 FORMAT;%0A
+    public String listDirFormat = DEFAULT_LIST_DIR_FORMAT;%0A
     publ
@@ -2291,32 +2291,160 @@
 rn this;%0A    %7D%0A%0A
+    public Ls withListDirFormat(String listDirFormat) %7B%0A        this.listDirFormat = listDirFormat;%0A        return this;%0A    %7D%0A%0A
     public Ls wi
@@ -3143,24 +3143,202 @@
 nextLimit);%0A
+                for(String dir : o.getCommonPrefixes()) %7B%0A                    printStream.println(String.format(listDirFormat, %22DIR%22, o.getBucketName(), dir));%0A                %7D%0A
             
@@ -5021,16 +5021,16 @@
 bucket)%0A
-
         
@@ -5065,16 +5065,60 @@
 marker)%0A
+                        .withDelimiter(%22/%22)%0A
         

add DIR listing to list output for feature parity with s3cmd output