/*
 * 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 com.android.email.service;

import android.content.Context;
import android.content.Intent;

import com.android.email.AccountTestCase;
import com.android.email.EmailConnectivityManager;
import com.android.email.provider.ProviderTestUtils;
import com.android.email.service.AttachmentDownloadService.DownloadRequest;
import com.android.email.service.AttachmentDownloadService.DownloadSet;
import com.android.email.service.EmailServiceUtils.NullEmailService;
import com.android.emailcommon.provider.Account;
import com.android.emailcommon.provider.EmailContent.Attachment;
import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.EmailServiceStatus;

import java.io.File;
import java.util.Iterator;

/**
 * Tests of the AttachmentDownloadService
 *
 * You can run this entire test case with:
 *   runtest -c com.android.email.service.AttachmentDownloadServiceTests email
 */
public class AttachmentDownloadServiceTests extends AccountTestCase {
    private AttachmentDownloadService mService;
    private Context mMockContext;
    private Account mAccount;
    private Mailbox mMailbox;
    private long mAccountId;
    private long mMailboxId;
    private AttachmentDownloadService.AccountManagerStub mAccountManagerStub;
    private MockDirectory mMockDirectory;

    private DownloadSet mDownloadSet;

    @Override
    public void setUp() throws Exception {
        super.setUp();
        mMockContext = getMockContext();

        // Set up an account and mailbox
        mAccount = ProviderTestUtils.setupAccount("account", false, mMockContext);
        mAccount.mFlags |= Account.FLAGS_BACKGROUND_ATTACHMENTS;
        mAccount.save(mMockContext);
        mAccountId = mAccount.mId;

        mMailbox = ProviderTestUtils.setupMailbox("mailbox", mAccountId, true, mMockContext);
        mMailboxId = mMailbox.mId;

        // Set up our download service to simulate a running environment
        // Use the NullEmailService so that the loadAttachment calls become no-ops
        mService = new AttachmentDownloadService();
        mService.mContext = mMockContext;
        mService.addServiceIntentForTest(mAccountId, new Intent(mContext,
                NullEmailService.class));
        mAccountManagerStub = new AttachmentDownloadService.AccountManagerStub(null);
        mService.mAccountManagerStub = mAccountManagerStub;
        mService.mConnectivityManager = new MockConnectivityManager(mContext, "mock");
        mDownloadSet = mService.mDownloadSet;
        mMockDirectory =
            new MockDirectory(mService.mContext.getCacheDir().getAbsolutePath());
    }

    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    /**
     * This test creates attachments and places them in the DownloadSet; we then do various checks
     * that exercise its functionality.
     */
    public void testDownloadSet() {
        // TODO: Make sure that this doesn't interfere with the "real" ADS that might be running
        // on device
        Message message = ProviderTestUtils.setupMessage("message", mAccountId, mMailboxId, false,
                true, mMockContext);
        Attachment att1 = ProviderTestUtils.setupAttachment(message.mId, "filename1", 1000,
                Attachment.FLAG_DOWNLOAD_USER_REQUEST, true, mMockContext);
        Attachment att2 = ProviderTestUtils.setupAttachment(message.mId, "filename2", 1000,
                Attachment.FLAG_DOWNLOAD_FORWARD, true, mMockContext);
        Attachment att3 = ProviderTestUtils.setupAttachment(message.mId, "filename3", 1000,
                Attachment.FLAG_DOWNLOAD_FORWARD, true, mMockContext);
        Attachment att4 = ProviderTestUtils.setupAttachment(message.mId, "filename4", 1000,
                Attachment.FLAG_DOWNLOAD_USER_REQUEST, true, mMockContext);
        // Indicate that these attachments have changed; they will be added to the queue
        mDownloadSet.onChange(mMockContext, att1);
        mDownloadSet.onChange(mMockContext, att2);
        mDownloadSet.onChange(mMockContext, att3);
        mDownloadSet.onChange(mMockContext, att4);
        Iterator<DownloadRequest> iterator = mDownloadSet.descendingIterator();
        // Check the expected ordering; 1 & 4 are higher priority than 2 & 3
        // 1 and 3 were created earlier than their priority equals
        long[] expectedAttachmentIds = new long[] {att1.mId, att4.mId, att2.mId, att3.mId};
        for (int i = 0; i < expectedAttachmentIds.length; i++) {
            assertTrue(iterator.hasNext());
            DownloadRequest req = iterator.next();
            assertEquals(expectedAttachmentIds[i], req.attachmentId);
        }

        // Process the queue; attachment 1 should be marked "in progress", and should be in
        // the in-progress map
        mDownloadSet.processQueue();
        DownloadRequest req = mDownloadSet.findDownloadRequest(att1.mId);
        assertNotNull(req);
        assertTrue(req.inProgress);
        assertTrue(mDownloadSet.mDownloadsInProgress.containsKey(att1.mId));
        // There should also be only one download in progress (testing the per-account limitation)
        assertEquals(1, mDownloadSet.mDownloadsInProgress.size());
        // End the "download" with a connection error; we should still have this in the queue,
        // but it should no longer be in-progress
        mDownloadSet.endDownload(att1.mId, EmailServiceStatus.CONNECTION_ERROR);
        assertFalse(req.inProgress);
        assertEquals(0, mDownloadSet.mDownloadsInProgress.size());

        mDownloadSet.processQueue();
        // Things should be as they were earlier; att1 should be an in-progress download
        req = mDownloadSet.findDownloadRequest(att1.mId);
        assertNotNull(req);
        assertTrue(req.inProgress);
        assertTrue(mDownloadSet.mDownloadsInProgress.containsKey(att1.mId));
        // Successfully download the attachment; there should be no downloads in progress, and
        // att1 should no longer be in the queue
        mDownloadSet.endDownload(att1.mId, EmailServiceStatus.SUCCESS);
        assertEquals(0, mDownloadSet.mDownloadsInProgress.size());
        assertNull(mDownloadSet.findDownloadRequest(att1.mId));

        // Test dequeue and isQueued
        assertEquals(3, mDownloadSet.size());
        mService.dequeue(att2.mId);
        assertEquals(2, mDownloadSet.size());
        assertTrue(mService.isQueued(att4.mId));
        assertTrue(mService.isQueued(att3.mId));

        mDownloadSet.processQueue();
        // att4 should be the download in progress
        req = mDownloadSet.findDownloadRequest(att4.mId);
        assertNotNull(req);
        assertTrue(req.inProgress);
        assertTrue(mDownloadSet.mDownloadsInProgress.containsKey(att4.mId));
    }

    /**
     * A mock file directory containing a single (Mock)File.  The total space, usable space, and
     * length of the single file can be set
     */
    private static class MockDirectory extends File {
        private static final long serialVersionUID = 1L;
        private long mTotalSpace;
        private long mUsableSpace;
        private MockFile[] mFiles;
        private final MockFile mMockFile = new MockFile();


        public MockDirectory(String path) {
            super(path);
            mFiles = new MockFile[1];
            mFiles[0] = mMockFile;
        }

        private void setTotalAndUsableSpace(long total, long usable) {
            mTotalSpace = total;
            mUsableSpace = usable;
        }

        @Override
        public long getTotalSpace() {
            return mTotalSpace;
        }

        @Override
        public long getUsableSpace() {
            return mUsableSpace;
        }

        public void setFileLength(long length) {
            mMockFile.mLength = length;
        }

        @Override
        public File[] listFiles() {
            return mFiles;
        }
    }

    /**
     * A mock file that reports back a pre-set length
     */
    private static class MockFile extends File {
        private static final long serialVersionUID = 1L;
        private long mLength = 0;

        public MockFile() {
            super("_mock");
        }

        @Override
        public long length() {
            return mLength;
        }
    }

    private static class MockConnectivityManager extends EmailConnectivityManager {
        public MockConnectivityManager(Context context, String name) {
            super(context, name);
        }

        @Override
        public void waitForConnectivity() {
        }

        @Override
        public boolean isAutoSyncAllowed() {
            return true;
        }
    }

    public void testCanPrefetchForAccount() {
        // First, test our "global" limits (based on free storage)
        // Mock storage @ 100 total and 26 available
        // Note that all file lengths in this test are in arbitrary units
        mMockDirectory.setTotalAndUsableSpace(100L, 26L);
        // Mock 2 accounts in total
        mAccountManagerStub.setNumberOfAccounts(2);
        // With 26% available, we should be ok to prefetch
        assertTrue(mService.canPrefetchForAccount(mAccount, mMockDirectory));
        // Now change to 24 available
        mMockDirectory.setTotalAndUsableSpace(100L, 24L);
        // With 24% available, we should NOT be ok to prefetch
        assertFalse(mService.canPrefetchForAccount(mAccount, mMockDirectory));

        // Now, test per-account storage
        // Mock storage @ 100 total and 50 available
        mMockDirectory.setTotalAndUsableSpace(100L, 50L);
        // Mock a file of length 12, but need to uncache previous amount first
        mService.mAttachmentStorageMap.remove(mAccountId);
        mMockDirectory.setFileLength(11);
        // We can prefetch since 11 < 50/4
        assertTrue(mService.canPrefetchForAccount(mAccount, mMockDirectory));
        // Mock a file of length 13, but need to uncache previous amount first
        mService.mAttachmentStorageMap.remove(mAccountId);
        mMockDirectory.setFileLength(13);
        // We can't prefetch since 13 > 50/4
        assertFalse(mService.canPrefetchForAccount(mAccount, mMockDirectory));
    }

    public void testCanPrefetchForAccountNoBackgroundDownload() {
        Account account = ProviderTestUtils.setupAccount("account2", false, mMockContext);
        account.mFlags &= ~Account.FLAGS_BACKGROUND_ATTACHMENTS;
        account.save(mMockContext);

        // First, test our "global" limits (based on free storage)
        // Mock storage @ 100 total and 26 available
        // Note that all file lengths in this test are in arbitrary units
        mMockDirectory.setTotalAndUsableSpace(100L, 26L);
        // Mock 2 accounts in total
        mAccountManagerStub.setNumberOfAccounts(2);

        // With 26% available, we should be ok to prefetch,
        // *but* bg download is disabled on the account.
        assertFalse(mService.canPrefetchForAccount(account, mMockDirectory));
    }
}
