/*
 * Copyright (C) 2018 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.documentsui;

import static com.google.common.truth.Truth.assertThat;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;

import android.database.Cursor;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;

import androidx.test.filters.MediumTest;

import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.State;
import com.android.documentsui.sorting.SortDimension;
import com.android.documentsui.sorting.SortModel;
import com.android.documentsui.testing.ActivityManagers;
import com.android.documentsui.testing.TestEnv;
import com.android.documentsui.testing.TestFileTypeLookup;
import com.android.documentsui.testing.TestImmediateExecutor;
import com.android.documentsui.testing.TestProvidersAccess;
import com.android.modules.utils.build.SdkLevel;

import com.google.common.collect.Lists;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
@MediumTest
public class GlobalSearchLoaderTest {

    private static final String SEARCH_STRING = "freddy";
    private static int FILE_FLAG = Document.FLAG_SUPPORTS_MOVE | Document.FLAG_SUPPORTS_DELETE
            | Document.FLAG_SUPPORTS_REMOVE;

    private TestEnv mEnv;
    private TestActivity mActivity;
    private GlobalSearchLoader mLoader;
    private TestConfigStore mTestConfigStore;
    private Bundle mQueryArgs = new Bundle();

    @Parameter(0)
    public boolean isPrivateSpaceEnabled;

    /**
     * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with
     * private space flag enabled and once with it disabled.
     */
    @Parameters(name = "privateSpaceEnabled={0}")
    public static Iterable<?> data() {
        return Lists.newArrayList(true, false);
    }

    @Before
    public void setUp() {
        mEnv = TestEnv.create();
        mActivity = TestActivity.create(mEnv);
        mActivity.activityManager = ActivityManagers.create(false);
        mTestConfigStore = new TestConfigStore();
        mEnv.state.configStore = mTestConfigStore;

        isPrivateSpaceEnabled &= SdkLevel.isAtLeastS();
        if (isPrivateSpaceEnabled) {
            mTestConfigStore.enablePrivateSpaceInPhotoPicker();
            mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.USER_ID, true);
        }

        mEnv.state.action = State.ACTION_BROWSE;
        mEnv.state.acceptMimes = new String[]{"*/*"};

        mQueryArgs.putString(DocumentsContract.QUERY_ARG_DISPLAY_NAME, SEARCH_STRING);
        mLoader = new GlobalSearchLoader(mActivity, mEnv.providers, mEnv.state,
                TestImmediateExecutor.createLookup(), new TestFileTypeLookup(), mQueryArgs,
                TestProvidersAccess.USER_ID);

        final DocumentInfo doc = mEnv.model.createFile(SEARCH_STRING + ".jpg", FILE_FLAG);
        doc.lastModified = System.currentTimeMillis();
        mEnv.mockProviders.get(TestProvidersAccess.DOWNLOADS.authority)
                .setNextChildDocumentsReturns(doc);

        TestProvidersAccess.DOWNLOADS.flags |= DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
    }

    @After
    public void tearDown() {
        TestProvidersAccess.DOWNLOADS.flags &= ~DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
    }

    @Test
    public void testNotSearchableRoot_beIgnored() {
        TestProvidersAccess.PICKLES.flags |= DocumentsContract.Root.FLAG_LOCAL_ONLY;
        assertTrue(mLoader.shouldIgnoreRoot(TestProvidersAccess.PICKLES));
        TestProvidersAccess.PICKLES.flags &= ~DocumentsContract.Root.FLAG_LOCAL_ONLY;
    }

    @Test
    public void testNotLocalOnlyRoot_beIgnored() {
        if (isPrivateSpaceEnabled) return;
        TestProvidersAccess.PICKLES.flags |= DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
        assertTrue(mLoader.shouldIgnoreRoot(TestProvidersAccess.PICKLES));
        TestProvidersAccess.PICKLES.flags &= ~DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
    }

    @Test
    public void testShowAdvance_storageRoot_beIgnored() {
        TestProvidersAccess.HOME.flags |= DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
        assertTrue(mLoader.shouldIgnoreRoot(TestProvidersAccess.HOME));
        TestProvidersAccess.HOME.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH);
    }

    @Test
    public void testCrossProfileRoot_notInTextSearch_beIgnored() {
        mEnv.state.supportsCrossProfile = true;
        mQueryArgs.remove(DocumentsContract.QUERY_ARG_DISPLAY_NAME);
        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.OtherUser.USER_ID;
        assertThat(mLoader.shouldIgnoreRoot(TestProvidersAccess.DOWNLOADS)).isTrue();
        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
    }

    @Test
    public void testCrossProfileRoot_inTextSearch_beIncluded() {
        mEnv.state.supportsCrossProfile = true;
        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.OtherUser.USER_ID;
        assertThat(mLoader.shouldIgnoreRoot(TestProvidersAccess.DOWNLOADS)).isFalse();
        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
    }

    @Test
    public void testSearchResult_includeDirectory() {
        final DocumentInfo doc = mEnv.model.createFolder(SEARCH_STRING);
        doc.lastModified = System.currentTimeMillis();

        mEnv.mockProviders.get(TestProvidersAccess.DOWNLOADS.authority)
                .setNextChildDocumentsReturns(doc);

        final DirectoryResult result = mLoader.loadInBackground();

        final Cursor c = result.getCursor();
        assertEquals(1, c.getCount());

        c.moveToNext();
        final String mimeType = c.getString(c.getColumnIndex(Document.COLUMN_MIME_TYPE));
        assertEquals(Document.MIME_TYPE_DIR, mimeType);
    }

    @Test
    public void testShowOrHideHiddenFiles() {
        final DocumentInfo doc1 = mEnv.model.createFile(".test" + SEARCH_STRING);
        final DocumentInfo doc2 = mEnv.model.createFile("test" + SEARCH_STRING);
        doc1.documentId = ".test";
        doc2.documentId = "parent_folder/.hidden_folder/test";
        mEnv.mockProviders.get(TestProvidersAccess.DOWNLOADS.authority)
                .setNextChildDocumentsReturns(doc1, doc2);

        assertEquals(false, mLoader.mState.showHiddenFiles);
        DirectoryResult result = mLoader.loadInBackground();
        assertEquals(0, result.getCursor().getCount());

        mLoader.mState.showHiddenFiles = true;
        result = mLoader.loadInBackground();
        assertEquals(2, result.getCursor().getCount());
    }

    @Test
    public void testSearchResult_isNotMovable() {
        final DirectoryResult result = mLoader.loadInBackground();

        final Cursor c = result.getCursor();
        assertEquals(1, c.getCount());

        c.moveToNext();
        final int flags = c.getInt(c.getColumnIndex(Document.COLUMN_FLAGS));
        assertEquals(0, flags & Document.FLAG_SUPPORTS_DELETE);
        assertEquals(0, flags & Document.FLAG_SUPPORTS_REMOVE);
        assertEquals(0, flags & Document.FLAG_SUPPORTS_MOVE);
    }

    @Test
    public void testSearchResult_includeDirectory_excludedOtherUsers() {
        mEnv.state.canShareAcrossProfile = false;

        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;
        TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);

        final DocumentInfo currentUserDoc = mEnv.model.createFile(
                SEARCH_STRING + "_currentUser.pdf");
        currentUserDoc.lastModified = System.currentTimeMillis();
        mEnv.mockProviders.get(TestProvidersAccess.DOWNLOADS.authority)
                .setNextChildDocumentsReturns(currentUserDoc);

        final DocumentInfo otherUserDoc = mEnv.model.createFile(SEARCH_STRING + "_otherUser.png");
        otherUserDoc.lastModified = System.currentTimeMillis();
        mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
                .setNextChildDocumentsReturns(otherUserDoc);

        final DirectoryResult result = mLoader.loadInBackground();
        final Cursor c = result.getCursor();

        assertThat(c.getCount()).isEqualTo(1);
        c.moveToNext();
        final String docName = c.getString(c.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
        assertThat(docName).contains("currentUser");

        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
        TestProvidersAccess.PICKLES.flags &= ~DocumentsContract.Root.FLAG_SUPPORTS_SEARCH;
    }

    @Test
    public void testSearchResult_includeSearchString() {
        final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
        pdfDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo apkDoc = mEnv.model.createFile(SEARCH_STRING + ".apk");
        apkDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo testApkDoc = mEnv.model.createFile("test.apk");
        testApkDoc.lastModified = System.currentTimeMillis();

        mEnv.mockProviders.get(TestProvidersAccess.DOWNLOADS.authority)
                .setNextChildDocumentsReturns(pdfDoc, apkDoc, testApkDoc);

        mEnv.state.sortModel.sortByUser(
                SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);

        final DirectoryResult result = mLoader.loadInBackground();
        final Cursor c = result.getCursor();

        assertEquals(2, c.getCount());

        c.moveToNext();
        String displayName = c.getString(c.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
        assertTrue(displayName.contains(SEARCH_STRING));

        c.moveToNext();
        displayName = c.getString(c.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
        assertTrue(displayName.contains(SEARCH_STRING));
    }

    @Test
    public void testSearchResult_includeDifferentRoot() {
        final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
        pdfDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo apkDoc = mEnv.model.createFile(SEARCH_STRING + ".apk");
        apkDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo testApkDoc = mEnv.model.createFile("test.apk");
        testApkDoc.lastModified = System.currentTimeMillis();

        mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
                .setNextChildDocumentsReturns(pdfDoc, apkDoc, testApkDoc);

        TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);

        mEnv.state.sortModel.sortByUser(
                SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);

        final DirectoryResult result = mLoader.loadInBackground();
        final Cursor c = result.getCursor();

        assertEquals(3, c.getCount());

        TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);
    }

    @Test
    public void testSearchResult_includeCurrentUserRootOnly() {
        mEnv.state.canShareAcrossProfile = false;
        mEnv.state.supportsCrossProfile = true;

        final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
        pdfDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo apkDoc = mEnv.model.createFile(SEARCH_STRING + ".apk");
        apkDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo testApkDoc = mEnv.model.createFile("test.apk");
        testApkDoc.lastModified = System.currentTimeMillis();

        mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
                .setNextChildDocumentsReturns(pdfDoc, apkDoc, testApkDoc);
        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;

        TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);
        mEnv.state.sortModel.sortByUser(
                SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);

        final DirectoryResult result = mLoader.loadInBackground();
        final Cursor c = result.getCursor();

        assertEquals(1, c.getCount());

        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
        TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);
    }


    @Test
    public void testSearchResult_includeBothUsersRoots() {
        if (isPrivateSpaceEnabled) {
            mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.OtherUser.USER_ID, true);
        } else {
            mEnv.state.canShareAcrossProfile = true;
        }
        mEnv.state.supportsCrossProfile = true;

        final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
        pdfDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo apkDoc = mEnv.model.createFile(SEARCH_STRING + ".apk");
        apkDoc.lastModified = System.currentTimeMillis();

        final DocumentInfo testApkDoc = mEnv.model.createFile("test.apk");
        testApkDoc.lastModified = System.currentTimeMillis();

        mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
                .setNextChildDocumentsReturns(pdfDoc, apkDoc, testApkDoc);
        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;

        TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);
        mEnv.state.sortModel.sortByUser(
                SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);

        final DirectoryResult result = mLoader.loadInBackground();
        final Cursor c = result.getCursor();

        assertEquals(3, c.getCount());

        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
        TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);
    }


    @Test
    public void testSearchResult_emptyCurrentUsersRoot() {
        mEnv.state.canShareAcrossProfile = false;
        mEnv.state.supportsCrossProfile = true;

        final DocumentInfo pdfDoc = mEnv.model.createFile(SEARCH_STRING + ".pdf");
        pdfDoc.lastModified = System.currentTimeMillis();

        mEnv.mockProviders.get(TestProvidersAccess.PICKLES.authority)
                .setNextChildDocumentsReturns(pdfDoc);

        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.OtherUser.USER_ID;
        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.OtherUser.USER_ID;
        TestProvidersAccess.PICKLES.flags |= (DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);
        mEnv.state.sortModel.sortByUser(
                SortModel.SORT_DIMENSION_ID_TITLE, SortDimension.SORT_DIRECTION_ASCENDING);

        final DirectoryResult result = mLoader.loadInBackground();
        assertThat(result.getCursor().getCount()).isEqualTo(0);
        // We don't expect exception even if all roots are from other users.
        assertThat(result.exception).isNull();


        TestProvidersAccess.DOWNLOADS.userId = TestProvidersAccess.USER_ID;
        TestProvidersAccess.PICKLES.userId = TestProvidersAccess.USER_ID;
        TestProvidersAccess.PICKLES.flags &= ~(DocumentsContract.Root.FLAG_SUPPORTS_SEARCH
                | DocumentsContract.Root.FLAG_LOCAL_ONLY);
    }
}
