/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.aliyun.oss.integrationtests;

import static com.aliyun.oss.integrationtests.TestUtils.batchPutObject;

import java.io.ByteArrayInputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.PutObjectRequest;
import junit.framework.Assert;

import org.junit.Test;

import com.aliyun.oss.OSSErrorCode;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;

public class ListObjectsTest extends TestBase {

    private static final int DEFAULT_MAX_RETURNED_KEYS = 100;
    private static final int MAX_RETURNED_KEYS_LIMIT = 1000;
    
    @Test
    public void testNormalListObjects() {
        final String bucketName = super.bucketName + "-normal-list-objects";
        
        try {
            ossClient.createBucket(bucketName);
            
            // List objects under empty bucket
            ObjectListing objectListing = ossClient.listObjects(bucketName);
            Assert.assertEquals(0, objectListing.getObjectSummaries().size());
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getMaxKeys());
            Assert.assertEquals(0, objectListing.getCommonPrefixes().size());
            Assert.assertEquals(bucketName, objectListing.getBucketName());
            Assert.assertNull(objectListing.getDelimiter());
            Assert.assertNull(objectListing.getPrefix());
            Assert.assertNull(objectListing.getMarker());
            Assert.assertNull(objectListing.getNextMarker());
            Assert.assertFalse(objectListing.isTruncated());
            Assert.assertEquals(objectListing.getRequestId().length(), REQUEST_ID_LEN);
            
            // Add MAX_RETURNED_KEYS_LIMIT + 1 + lv2KeyCount objects to bucket
            List<String> existingKeys = new ArrayList<String>();
            final int lv1KeyCount = 102;
            final int lv2KeyCount = 11;
            final int keyCount = MAX_RETURNED_KEYS_LIMIT + 1 + lv2KeyCount;
            final String lv0KeyPrefix = "normal-list-lv0-objects-";
            final String lv1KeyPrefix = "normal-list-lv0-objects/lv1-objects-";
            final String lv2KeyPrefix = "normal-list-lv0-objects/lv1-objects/lv2-objects-";
            for (int i = 0; i < keyCount; i++) {
                if (i % 10 != 0) {
                    existingKeys.add(lv0KeyPrefix + i);
                } else {
                    existingKeys.add(lv1KeyPrefix + i);
                    if (i % 100 == 0) {                        
                        existingKeys.add(lv2KeyPrefix + i);
                    }
                }
            }
            
            if (!batchPutObject(ossClient, bucketName, existingKeys)) {
                Assert.fail("batch put object failed");
            }
            
            // List objects under nonempty bucket
            objectListing = ossClient.listObjects(bucketName);
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getObjectSummaries().size());
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getMaxKeys());
            Assert.assertEquals(0, objectListing.getCommonPrefixes().size());
            Assert.assertEquals(bucketName, objectListing.getBucketName());
            Assert.assertNull(objectListing.getDelimiter());
            Assert.assertNull(objectListing.getPrefix());
            Assert.assertNull(objectListing.getMarker());
            Assert.assertNotNull(objectListing.getNextMarker());
            Assert.assertTrue(objectListing.isTruncated());
            Assert.assertEquals(objectListing.getRequestId().length(), REQUEST_ID_LEN);
            
            // List objects with lv1KeyPrefix under nonempty bucket
            objectListing = ossClient.listObjects(bucketName, lv1KeyPrefix);
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getObjectSummaries().size());
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getMaxKeys());
            Assert.assertEquals(0, objectListing.getCommonPrefixes().size());
            Assert.assertEquals(bucketName, objectListing.getBucketName());
            Assert.assertNull(objectListing.getDelimiter());
            Assert.assertEquals(lv1KeyPrefix, objectListing.getPrefix());
            Assert.assertNull(objectListing.getMarker());
            Assert.assertNotNull(objectListing.getNextMarker());
            Assert.assertTrue(objectListing.isTruncated());
            Assert.assertEquals(objectListing.getRequestId().length(), REQUEST_ID_LEN);
            
            // List objects with lv0KeyPrefix under nonempty bucket
            objectListing = ossClient.listObjects(bucketName, lv0KeyPrefix);
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getObjectSummaries().size());
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getMaxKeys());
            Assert.assertEquals(0, objectListing.getCommonPrefixes().size());
            Assert.assertEquals(bucketName, objectListing.getBucketName());
            Assert.assertNull(objectListing.getDelimiter());
            Assert.assertEquals(lv0KeyPrefix, objectListing.getPrefix());
            Assert.assertNull(objectListing.getMarker());
            Assert.assertNotNull(objectListing.getNextMarker());
            Assert.assertTrue(objectListing.isTruncated());
            Assert.assertEquals(objectListing.getRequestId().length(), REQUEST_ID_LEN);
            
            // List object with 'prefix' and 'marker' under nonempty bucket
            String marker = objectListing.getNextMarker();
            objectListing = ossClient.listObjects(new ListObjectsRequest(bucketName, lv0KeyPrefix, marker, null, null));
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getObjectSummaries().size());
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getMaxKeys());
            Assert.assertEquals(0, objectListing.getCommonPrefixes().size());
            Assert.assertEquals(bucketName, objectListing.getBucketName());
            Assert.assertNull(objectListing.getDelimiter());
            Assert.assertEquals(lv0KeyPrefix, objectListing.getPrefix());
            Assert.assertEquals(marker, objectListing.getMarker());
            Assert.assertNotNull(objectListing.getNextMarker());
            Assert.assertTrue(objectListing.isTruncated());
            Assert.assertEquals(objectListing.getRequestId().length(), REQUEST_ID_LEN);

            // List object with 'prefix' and 'delimiter' under nonempty bucket
            final String delimiter = "/";
            final String keyPrefix0 = "normal-list-lv0-objects/";
            final String keyPrefix1 = "normal-list-lv0-objects/lv1-objects/";
            objectListing = ossClient.listObjects(
                    new ListObjectsRequest(bucketName, keyPrefix0, null, delimiter, MAX_RETURNED_KEYS_LIMIT));
            Assert.assertEquals(lv1KeyCount, objectListing.getObjectSummaries().size());
            Assert.assertEquals(MAX_RETURNED_KEYS_LIMIT, objectListing.getMaxKeys());
            Assert.assertEquals(1, objectListing.getCommonPrefixes().size());
            Assert.assertEquals(keyPrefix1, objectListing.getCommonPrefixes().get(0));
            Assert.assertEquals(bucketName, objectListing.getBucketName());
            Assert.assertEquals(delimiter, objectListing.getDelimiter());
            Assert.assertEquals(keyPrefix0, objectListing.getPrefix());
            Assert.assertNull(objectListing.getMarker());
            Assert.assertNull(objectListing.getNextMarker());
            Assert.assertFalse(objectListing.isTruncated());
            Assert.assertEquals(objectListing.getRequestId().length(), REQUEST_ID_LEN);
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        } finally {
            deleteBucketWithObjects(ossClient, bucketName);
        }
    }

    @Test
    public void testUnormalListObjects() {
        final String bucketName = super.bucketName + "-unormal-list-objects";
        
        try {
            ossClient.createBucket(bucketName);
            
            // List objects under non-existent bucket
            final String nonexistentBucket = super.bucketName + "-unormal-list-objects-bucket";
            try {
                ossClient.listObjects(nonexistentBucket);
                Assert.fail("List objects should not be successful");
            } catch (OSSException e) {
                Assert.assertEquals(OSSErrorCode.NO_SUCH_BUCKET, e.getErrorCode());
            }
            
            // List objects under bucket without ownership
            final String bucketWithoutOwnership = "oss" + NOT_OWNERSHIP_BUCKET_SUFFIX;
            try {
                ossClient.listObjects(bucketWithoutOwnership);
                Assert.fail("List objects should not be successful");
            } catch (OSSException e) {
                Assert.assertEquals(OSSErrorCode.ACCESS_DENIED, e.getErrorCode());
            }
            
            // Add DEFAULT_MAX_RETURNED_KEYS - 1 objects to bucket
            List<String> existingKeys = new ArrayList<String>();
            final int keyCount = DEFAULT_MAX_RETURNED_KEYS;
            final String keyPrefix = "unormal-list-objects-";
            final int unluckyNumber = 13;
            for (int i = 0; i <= keyCount; i++) {
                if (i != unluckyNumber) {
                    existingKeys.add(keyPrefix + i);
                }
            }
            
            if (!batchPutObject(ossClient, bucketName, existingKeys)) {
                Assert.fail("batch put object failed");
            }
            
            // List object with nonexistent marker
            final String nonexistentMarker = keyPrefix + unluckyNumber;
            try {
                ListObjectsRequest request = new ListObjectsRequest(bucketName, null, nonexistentMarker, null, null);
                ObjectListing objectListing = ossClient.listObjects(request);
                Assert.assertTrue(objectListing.getObjectSummaries().size() < keyCount);
                Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getMaxKeys());
                Assert.assertEquals(0, objectListing.getCommonPrefixes().size());
                Assert.assertEquals(bucketName, objectListing.getBucketName());
                Assert.assertNull(objectListing.getDelimiter());
                Assert.assertNull(objectListing.getPrefix());
                Assert.assertEquals(nonexistentMarker, objectListing.getMarker());
                Assert.assertNull(objectListing.getNextMarker());
                Assert.assertFalse(objectListing.isTruncated());
            } catch (Exception e) {
                Assert.fail(e.getMessage());
            }
            
            // Set 'max-keys' less than zero
            final int maxKeysExceedLowerLimit = -1;
            try {
                ListObjectsRequest request = new ListObjectsRequest(bucketName);
                request.setMaxKeys(maxKeysExceedLowerLimit);
            } catch (Exception e) {
                Assert.assertTrue(e instanceof IllegalArgumentException);
            }
            
            // Set 'max-keys' exceed MAX_RETURNED_KEYS_LIMIT
            final int maxKeysExceedUpperLimit = MAX_RETURNED_KEYS_LIMIT + 1;
            try {
                ListObjectsRequest request = new ListObjectsRequest(bucketName);
                request.setMaxKeys(maxKeysExceedUpperLimit);
            } catch (Exception e) {
                Assert.assertTrue(e instanceof IllegalArgumentException);
            }
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        } finally {
            deleteBucketWithObjects(ossClient, bucketName);
        }
    }
    
    @Test
    public void testListObjectsWithEncodingType() {
        final String objectPrefix = "object-with-special-characters-";
        
        try {
            // Add several objects with special characters into bucket.
            List<String> existingKeys = new ArrayList<String>();
            existingKeys.add(objectPrefix + "\001\007");
            existingKeys.add(objectPrefix + "\002\007");
            
            if (!batchPutObject(ossClient, bucketName, existingKeys)) {
                Assert.fail("batch put object failed");
            }
            
            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
            try {
                ossClient.listObjects(listObjectsRequest);
            } catch (Exception e) {
                Assert.assertTrue(e instanceof OSSException);
                Assert.assertEquals(OSSErrorCode.INVALID_RESPONSE, ((OSSException)e).getErrorCode());
            }
            
            // List objects under nonempty bucket
            listObjectsRequest = new ListObjectsRequest(bucketName);
            listObjectsRequest.setEncodingType(DEFAULT_ENCODING_TYPE);
            ObjectListing objectListing = ossClient.listObjects(listObjectsRequest);
            for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
                String decodedKey = URLDecoder.decode(s.getKey(), "UTF-8");
                Assert.assertTrue(existingKeys.contains(decodedKey));
            }
            Assert.assertEquals(DEFAULT_ENCODING_TYPE, objectListing.getEncodingType());
            Assert.assertEquals(existingKeys.size(), objectListing.getObjectSummaries().size());
            Assert.assertEquals(DEFAULT_MAX_RETURNED_KEYS, objectListing.getMaxKeys());
            Assert.assertEquals(0, objectListing.getCommonPrefixes().size());
            Assert.assertEquals(bucketName, objectListing.getBucketName());
            Assert.assertNull(objectListing.getDelimiter());
            Assert.assertNull(objectListing.getPrefix());
            Assert.assertNull(objectListing.getMarker());
            Assert.assertNull(objectListing.getNextMarker());
            Assert.assertFalse(objectListing.isTruncated());
        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }
    }

    @Test
    public void testListObjectsWithRestoreInfo() {
        String objectPrefix = "object-with-special-restore";
        String content = "abcde";

        try {
            // First upload the archive file, and then unfreeze it to obtain the returned RestoreInfo
            Map<String, String> header = new HashMap<String, String>();
            header.put(OSSHeaders.STORAGE_CLASS, "Archive");

            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectPrefix, new ByteArrayInputStream(content.getBytes()));
            putObjectRequest.setHeaders(header);
            ossClient.putObject(putObjectRequest);

            ossClient.restoreObject(bucketName, objectPrefix);

            ListObjectsRequest listObjectsRequest = new ListObjectsRequest(bucketName);
            ObjectListing objectListing = ossClient.listObjects(listObjectsRequest);
            for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
                String restoreInfo = s.getRestoreInfo();
                Assert.assertEquals(restoreInfo, "ongoing-request=\"true\"");
            }

            boolean flag = true;
            long startTime = System.currentTimeMillis();
            while (flag){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                objectListing = ossClient.listObjects(listObjectsRequest);
                for (OSSObjectSummary s : objectListing.getObjectSummaries()) {
                    if(s.getRestoreInfo().contains("ongoing-request=\"false\"")){
                        flag = false;
                        Assert.assertTrue(true);
                        break;
                    }
                    long endTime = System.currentTimeMillis();
                    if(endTime - startTime > 1000 * 120){
                        Assert.assertFalse(true);
                    }
                }
            }

        } catch (Exception e) {
            Assert.fail(e.getMessage());
        }
    }
}
