// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.suggestions.tile;

import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;

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

import static org.hamcrest.CoreMatchers.is;

import static org.chromium.chrome.test.util.browser.suggestions.mostvisited.FakeMostVisitedSites.createSiteSuggestion;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Build.VERSION_CODES;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.filters.MediumTest;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.chromium.base.FeatureList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.params.ParameterAnnotations;
import org.chromium.base.test.params.ParameterSet;
import org.chromium.base.test.params.ParameterizedRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.night_mode.ChromeNightModeTestUtils;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.offlinepages.OfflinePageItem;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.SiteSuggestion;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegate;
import org.chromium.chrome.browser.suggestions.SuggestionsUiDelegateImpl;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.ui.native_page.TouchEnabledDelegate;
import org.chromium.chrome.browser.util.BrowserUiUtils;
import org.chromium.chrome.test.ChromeJUnit4RunnerDelegate;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.util.ActivityTestUtils;
import org.chromium.chrome.test.util.ChromeRenderTestRule;
import org.chromium.chrome.test.util.NewTabPageTestUtils;
import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
import org.chromium.chrome.test.util.browser.offlinepages.FakeOfflinePageBridge;
import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
import org.chromium.chrome.test.util.browser.suggestions.mostvisited.FakeMostVisitedSites;
import org.chromium.components.embedder_support.util.UrlConstants;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.EmbeddedTestServerRule;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.test.util.NightModeTestUtils;
import org.chromium.url.GURL;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;

/**
 * Instrumentation tests for the {@link MostVisitedTilesGridLayout} and {@link
 * MostVisitedTilesCarouselLayout} on the New Tab Page.
 */
@RunWith(ParameterizedRunner.class)
@ParameterAnnotations.UseRunnerDelegate(ChromeJUnit4RunnerDelegate.class)
@CommandLineFlags.Add(ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE)
@Batch(Batch.PER_CLASS)
public class MostVisitedTilesLayoutTest {
    @ParameterAnnotations.ClassParameter
    private static List<ParameterSet> sClassParams =
            Arrays.asList(new ParameterSet().value(true).name("EnableScrollableMVTOnNTP"),
                    new ParameterSet().value(false).name("DisableScrollableMVTOnNTP"));

    public final int TILE_GRID_ROWS = 2;

    @Rule
    public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();

    @Rule
    public SuggestionsDependenciesRule mSuggestionsDeps = new SuggestionsDependenciesRule();

    @Rule
    public EmbeddedTestServerRule mTestServerRule = new EmbeddedTestServerRule();

    @Rule
    public ChromeRenderTestRule mRenderTestRule =
            ChromeRenderTestRule.Builder.withPublicCorpus()
                    .setBugComponent(
                            ChromeRenderTestRule.Component.UI_BROWSER_CONTENT_SUGGESTIONS_HISTORY)
                    .build();

    @Mock
    ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
    @Mock
    WindowAndroid mWindowAndroid;
    @Mock
    TouchEnabledDelegate mTouchEnabledDelegate;

    private static final String[] FAKE_MOST_VISITED_URLS = new String[] {
            "/chrome/test/data/android/navigate/one.html",
            "/chrome/test/data/android/navigate/two.html",
            "/chrome/test/data/android/navigate/three.html",
            "/chrome/test/data/android/navigate/four.html",
            "/chrome/test/data/android/navigate/five.html",
            "/chrome/test/data/android/navigate/six.html",
            "/chrome/test/data/android/navigate/seven.html",
            "/chrome/test/data/android/navigate/eight.html",
            "/chrome/test/data/android/navigate/nine.html",
    };

    private static final String[] FAKE_MOST_VISITED_TITLES =
            new String[] {"ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE"};

    private final CallbackHelper mLoadCompleteHelper = new CallbackHelper();
    private boolean mEnableScrollableMVT;

    public MostVisitedTilesLayoutTest(boolean enableScrollableMVT) {
        mEnableScrollableMVT = enableScrollableMVT;
    }

    @BeforeClass
    public static void setUpBeforeActivityLaunched() {
        ChromeNightModeTestUtils.setUpNightModeBeforeChromeActivityLaunched();
    }

    @ParameterAnnotations.UseMethodParameterBefore(NightModeTestUtils.NightModeParams.class)
    public void setupNightMode(boolean nightModeEnabled) {
        ChromeNightModeTestUtils.setUpNightModeForChromeActivity(nightModeEnabled);
        mRenderTestRule.setNightModeEnabled(nightModeEnabled);
    }

    @AfterClass
    public static void tearDownAfterActivityDestroyed() {
        ChromeNightModeTestUtils.tearDownNightModeAfterChromeActivityDestroyed();
    }

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        FeatureList.TestValues testValuesOverride = new FeatureList.TestValues();
        testValuesOverride.addFeatureFlagOverride(
                ChromeFeatureList.SHOW_SCROLLABLE_MVT_ON_NTP_ANDROID, mEnableScrollableMVT);
        FeatureList.setTestValues(testValuesOverride);
    }

    @Test
    @MediumTest
    @Feature({"NewTabPage", "RenderTest"})
    @ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
    @DisableFeatures(ChromeFeatureList.QUERY_TILES)
    public void testTilesLayoutAppearance(boolean nightModeEnabled) throws Exception {
        NewTabPage ntp = setUpFakeDataToShowOnNtp(FAKE_MOST_VISITED_URLS.length);
        mRenderTestRule.render(getTilesLayout(ntp),
                mEnableScrollableMVT ? "ntp_tile_carousel_layout" : "ntp_tile_grid_layout");
    }

    @Test
    @MediumTest
    @Feature({"NewTabPage", "RenderTest"})
    @DisableIf.Build(message = "Both variants are flaky on Nougat emulator, see crbug.com/1450693",
            supported_abis_includes = "x86", sdk_is_less_than = VERSION_CODES.O)
    public void
    testModernTilesLayoutAppearance_Full() throws IOException, InterruptedException {
        View tilesLayout = renderTiles(makeSuggestions(FAKE_MOST_VISITED_URLS.length));

        Activity activity = mActivityTestRule.getActivity();
        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        CriteriaHelper.pollUiThread(() -> {
            Criteria.checkThat(activity.getResources().getConfiguration().orientation,
                    is(ORIENTATION_PORTRAIT));
        });
        mRenderTestRule.render(tilesLayout,
                mEnableScrollableMVT ? "modern_full_carousel_portrait"
                                     : "modern_full_grid_portrait");

        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        CriteriaHelper.pollUiThread(() -> {
            Criteria.checkThat(activity.getResources().getConfiguration().orientation,
                    is(ORIENTATION_LANDSCAPE));
        });
        mRenderTestRule.render(tilesLayout,
                mEnableScrollableMVT ? "modern_full_carousel_landscape"
                                     : "modern_full_grid_landscape");

        // Reset device orientation.
        ActivityTestUtils.clearActivityOrientation(activity);
    }

    @Test
    @MediumTest
    @Feature({"NewTabPage", "RenderTest"})
    @DisableIf.Build(message = "Both variants are flaky on Nougat emulator, see crbug.com/1450693",
            supported_abis_includes = "x86", sdk_is_less_than = VERSION_CODES.O)
    public void
    testModernTilesLayoutAppearance_Two() throws IOException, InterruptedException {
        View tilesLayout = renderTiles(makeSuggestions(2));

        Activity activity = mActivityTestRule.getActivity();
        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        CriteriaHelper.pollUiThread(() -> {
            Criteria.checkThat(activity.getResources().getConfiguration().orientation,
                    is(ORIENTATION_PORTRAIT));
        });
        mRenderTestRule.render(tilesLayout,
                mEnableScrollableMVT ? "modern_two_tiles_carousel_portrait"
                                     : "modern_two_tiles_grid_portrait");

        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        CriteriaHelper.pollUiThread(() -> {
            Criteria.checkThat(activity.getResources().getConfiguration().orientation,
                    is(ORIENTATION_LANDSCAPE));
        });
        mRenderTestRule.render(tilesLayout,
                mEnableScrollableMVT ? "modern_two_tiles_carousel_landscape"
                                     : "modern_two_tiles_grid_landscape");

        // Reset device orientation.
        ActivityTestUtils.clearActivityOrientation(activity);
    }

    @Test
    @MediumTest
    @Feature({"NewTabPage", "RenderTest"})
    @ParameterAnnotations.UseMethodParameter(NightModeTestUtils.NightModeParams.class)
    public void testTileAppearanceModern(boolean nightModeEnabled)
            throws IOException, InterruptedException, TimeoutException {
        List<SiteSuggestion> suggestions = makeSuggestions(2);
        List<GURL> offlineAvailableUrls = Collections.singletonList(suggestions.get(0).url);
        ViewGroup tiles = renderTiles(suggestions, offlineAvailableUrls);

        mLoadCompleteHelper.waitForCallback(0);

        mRenderTestRule.render(tiles.getChildAt(0), "tile_modern_offline");
        mRenderTestRule.render(tiles.getChildAt(1), "tile_modern");
    }

    private List<SiteSuggestion> makeSuggestions(int count) {
        List<SiteSuggestion> siteSuggestions = new ArrayList<>(count);

        assertEquals(FAKE_MOST_VISITED_URLS.length, FAKE_MOST_VISITED_TITLES.length);
        assertTrue(count <= FAKE_MOST_VISITED_URLS.length);

        for (int i = 0; i < count; i++) {
            String url = mTestServerRule.getServer().getURL(FAKE_MOST_VISITED_URLS[i]);
            siteSuggestions.add(createSiteSuggestion(FAKE_MOST_VISITED_TITLES[i], url));
        }

        return siteSuggestions;
    }

    private NewTabPage setUpFakeDataToShowOnNtp(int suggestionCount) {
        List<SiteSuggestion> siteSuggestions = makeSuggestions(suggestionCount);

        FakeMostVisitedSites mMostVisitedSites = new FakeMostVisitedSites();
        mMostVisitedSites.setTileSuggestions(siteSuggestions);
        mSuggestionsDeps.getFactory().mostVisitedSites = mMostVisitedSites;

        mActivityTestRule.startMainActivityWithURL(UrlConstants.NTP_URL);

        Tab mTab = mActivityTestRule.getActivity().getActivityTab();
        NewTabPageTestUtils.waitForNtpLoaded(mTab);

        assertTrue(mTab.getNativePage() instanceof NewTabPage);
        NewTabPage ntp = (NewTabPage) mTab.getNativePage();

        org.chromium.ui.test.util.ViewUtils.waitForView(
                (ViewGroup) ntp.getView(), ViewMatchers.withId(R.id.mv_tiles_layout));

        return ntp;
    }

    private ViewGroup getTilesLayout(NewTabPage ntp) {
        ViewGroup mostVisitedTilesLayout = ntp.getView().findViewById(R.id.mv_tiles_layout);
        assertNotNull("Unable to retrieve the "
                        + (mEnableScrollableMVT ? "tile_carousel_layout." : "tile_grid_layout."),
                mostVisitedTilesLayout);
        return mostVisitedTilesLayout;
    }

    /**
     * Starts and sets up an activity to render the provided site suggestions in the activity.
     * @return the layout in which the suggestions are rendered.
     */
    private ViewGroup renderTiles(List<SiteSuggestion> siteSuggestions, List<GURL> offlineUrls)
            throws InterruptedException {
        // Launching the activity, that should now use the right UI.
        mActivityTestRule.startMainActivityOnBlankPage();
        ChromeActivity activity = mActivityTestRule.getActivity();

        // Setting up the dummy data.
        FakeMostVisitedSites mostVisitedSites = new FakeMostVisitedSites();
        mostVisitedSites.setTileSuggestions(siteSuggestions);
        mSuggestionsDeps.getFactory().mostVisitedSites = mostVisitedSites;

        ViewGroup contentView = new FrameLayout(activity);

        return TestThreadUtils.runOnUiThreadBlockingNoException(() -> {
            setOfflinePageBridge(offlineUrls);
            activity.setContentView(contentView);
            ViewGroup containerLayout =
                    (ViewGroup) LayoutInflater.from(contentView.getContext())
                            .inflate(R.layout.mv_tiles_container, contentView, false);
            containerLayout.setVisibility(View.VISIBLE);
            contentView.addView(containerLayout);
            initializeCoordinator(containerLayout);
            ViewGroup mostVisitedTilesLayout = containerLayout.findViewById(R.id.mv_tiles_layout);
            assertNotNull(mostVisitedTilesLayout);
            return mostVisitedTilesLayout;
        });
    }

    private ViewGroup renderTiles(List<SiteSuggestion> siteSuggestions)
            throws InterruptedException {
        return renderTiles(siteSuggestions, Collections.emptyList());
    }

    private void setOfflinePageBridge(List<GURL> offlineUrls) {
        FakeOfflinePageBridge offlinePageBridge = new FakeOfflinePageBridge();
        List<OfflinePageItem> offlinePageItems = new ArrayList<>();
        for (int i = 0; i < offlineUrls.size(); i++) {
            offlinePageItems.add(FakeOfflinePageBridge.createOfflinePageItem(
                    offlineUrls.get(i).getSpec(), i + 1L));
        }
        offlinePageBridge.setItems(offlinePageItems);
        offlinePageBridge.setIsOfflinePageModelLoaded(true);
        mSuggestionsDeps.getFactory().offlinePageBridge = offlinePageBridge;
    }

    private void initializeCoordinator(ViewGroup containerLayout) {
        ThreadUtils.assertOnUiThread();

        ChromeActivity activity = mActivityTestRule.getActivity();

        // TODO (https://crbug.com/1063807):  Add incognito mode tests.
        Profile profile = Profile.getLastUsedRegularProfile();
        SuggestionsUiDelegate uiDelegate =
                new SuggestionsUiDelegateImpl(null, profile, null, activity.getSnackbarManager());

        TileGroup.Delegate delegate = new TileGroupDelegateImpl(
                activity, profile, null, null, BrowserUiUtils.HostSurface.NOT_SET) {
            @Override
            public void onLoadingComplete(List<Tile> tiles) {
                super.onLoadingComplete(tiles);
                mLoadCompleteHelper.notifyCalled();
            }
        };

        MostVisitedTilesCoordinator coordinator = new MostVisitedTilesCoordinator(activity,
                mActivityLifecycleDispatcher, containerLayout, mWindowAndroid, false,
                mEnableScrollableMVT, TILE_GRID_ROWS, null, null);
        coordinator.initWithNative(uiDelegate, delegate, mTouchEnabledDelegate);
    }
}
