/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * 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 android.app;

import android.app.DownloadManager.Query;
import android.app.DownloadManager.Request;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.os.StatFs;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

/**
 * Integration tests of the DownloadManager API.
 */
public class DownloadManagerStressTest extends DownloadManagerBaseTest {
    private static final String TAG = "DownloadManagerStressTest";
    private final static String CACHE_DIR =
            Environment.getDownloadCacheDirectory().getAbsolutePath();

    /**
     * {@inheritDoc}
     */
    @Override
    public void setUp() throws Exception {
        super.setUp();
        setWiFiStateOn(true);
        removeAllCurrentDownloads();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void tearDown() throws Exception {
        super.tearDown();
        setWiFiStateOn(true);
        removeAllCurrentDownloads();

        if (mReceiver != null) {
            mContext.unregisterReceiver(mReceiver);
            mReceiver = null;
        }
    }

    /**
     * Attempts to download several files simultaneously
     */
    @LargeTest
    public void testMultipleDownloads() throws Exception {
        // need to be sure all current downloads have stopped first
        removeAllCurrentDownloads();
        int NUM_FILES = 10;
        int MAX_FILE_SIZE = 10 * 1024; // 10 kb

        Random r = new LoggingRng();
        for (int i=0; i<NUM_FILES; ++i) {
            int size = r.nextInt(MAX_FILE_SIZE);
            byte[] blobData = generateData(size, DataType.TEXT);

            Uri uri = getServerUri(DEFAULT_FILENAME + i);
            Request request = new Request(uri);
            request.setTitle(String.format("%s--%d", DEFAULT_FILENAME + i, i));

            // Prepare the mock server with a standard response
            enqueueResponse(buildResponse(HTTP_OK, blobData));

            long requestID = mDownloadManager.enqueue(request);
        }

        waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME);
        Cursor cursor = mDownloadManager.query(new Query());
        try {
            assertEquals(NUM_FILES, cursor.getCount());

            if (cursor.moveToFirst()) {
                do {
                    int status = cursor.getInt(cursor.getColumnIndex(
                            DownloadManager.COLUMN_STATUS));
                    String filename = cursor.getString(cursor.getColumnIndex(
                            DownloadManager.COLUMN_URI));
                    String errorString = String.format(
                            "File %s failed to download successfully. Status code: %d",
                            filename, status);
                    assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status);
                } while (cursor.moveToNext());
            }

            assertEquals(NUM_FILES, mReceiver.numDownloadsCompleted());
        } finally {
            cursor.close();
        }
    }
    /**
     * Tests trying to download a large file (50M bytes).
     */
    @LargeTest
    public void testDownloadLargeFile() throws Exception {
        long fileSize = 50000000L;  // note: kept relatively small to not exceed /cache dir size
        Log.i(TAG, "creating a file of size: " + fileSize);
        File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null);
        Log.i(TAG, "DONE creating a file of size: " + fileSize);
        MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver();

        try {
            long dlRequest = doStandardEnqueue(largeFile);

            // wait for the download to complete
            waitForDownloadOrTimeout(dlRequest);

            ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest);
            verifyFileContents(pfd, largeFile);
            verifyFileSize(pfd, largeFile.length());

            assertEquals(1, receiver.numDownloadsCompleted());
            mContext.unregisterReceiver(receiver);
        } catch (Exception e) {
            throw e;
        } finally {
            largeFile.delete();
        }
    }


    /**
     * Tests downloading a file to system cache when there isn't enough space in the system cache 
     * to hold the entire file. DownloadManager deletes enough files to make space for the
     * new download.
     */
    @LargeTest
    public void testDownloadToCacheWithAlmostFullCache() throws Exception {
        int DOWNLOAD_FILE_SIZE = 1024 * 1024; // 1MB

        StatFs fs = new StatFs(CACHE_DIR);
        int blockSize = fs.getBlockSize();
        int availableBlocks = fs.getAvailableBlocks();
        int availableBytes = blockSize * availableBlocks;
        Log.i(TAG, "INITIAL stage, available space in /cache: " + availableBytes);
        File outFile = File.createTempFile("DM_TEST", null, new File(CACHE_DIR));
        byte[] buffer = new byte[blockSize];

        try {
            // fill cache to ensure we don't have enough space - take half the size of the
            // download size, and leave that much freespace left on the cache partition
            if (DOWNLOAD_FILE_SIZE <= availableBytes) {
                int writeSizeBytes = availableBytes - (DOWNLOAD_FILE_SIZE / 2);

                int writeSizeBlocks = writeSizeBytes / blockSize;
                int remainderSizeBlocks = availableBlocks - writeSizeBlocks;

                FileOutputStream fo = null;
                try {
                    fo = new FileOutputStream(outFile);
                    while (fs.getAvailableBlocks() >= remainderSizeBlocks) {
                        fo.write(buffer);
                        fs.restat(CACHE_DIR);
                    }
                } catch (IOException e) {
                    Log.e(LOG_TAG, "error filling file: ", e);
                    throw e;
                } finally {
                    if (fo != null) {
                        fo.close();
                    }
                }
            }

            // /cache should now be almost full. 
            long spaceAvailable = fs.getAvailableBlocks() * blockSize;
            Log.i(TAG, "BEFORE download, available space in /cache: " + spaceAvailable);
            assertTrue(DOWNLOAD_FILE_SIZE > spaceAvailable);

            // try to download 1MB file into /cache - and it should succeed
            byte[] blobData = generateData(DOWNLOAD_FILE_SIZE, DataType.TEXT);
            long dlRequest = doBasicDownload(blobData, DOWNLOAD_TO_SYSTEM_CACHE);
            verifyAndCleanupSingleFileDownload(dlRequest, blobData);
        } finally {
            if (outFile != null) {
                outFile.delete();
            }
        }
    }
}
