/*
 * Copyright (C) 2017 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.example.android.pictureinpicture;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;

import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.AllOf.allOf;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import android.content.pm.ActivityInfo;
import android.media.session.PlaybackState;
import android.support.v4.media.session.PlaybackStateCompat;
import android.view.View;

import androidx.test.InstrumentationRegistry;
import androidx.test.espresso.UiController;
import androidx.test.espresso.ViewAction;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

import com.example.android.pictureinpicture.widget.MovieView;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class MediaSessionPlaybackActivityTest {

    @Rule
    public ActivityTestRule<MediaSessionPlaybackActivity> rule =
            new ActivityTestRule<>(MediaSessionPlaybackActivity.class);

    @Test
    public void movie_playingOnPip() throws Throwable {
        // The movie should be playing on start
        onView(withId(R.id.movie))
                .check(matches(allOf(isDisplayed(), isPlaying())))
                .perform(showControls());
        // Click on the button to enter Picture-in-Picture mode
        onView(withId(R.id.minimize)).perform(click());
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
        // The Activity is paused. We cannot use Espresso to test paused activities.
        rule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // We are now in Picture-in-Picture mode
                assertTrue(rule.getActivity().isInPictureInPictureMode());
                final MovieView view = rule.getActivity().findViewById(R.id.movie);
                assertNotNull(view);
                // The video should still be playing
                assertTrue(view.isPlaying());

                // The media session state should be playing.
                assertMediaStateIs(PlaybackStateCompat.STATE_PLAYING);
            }
        });
    }

    @Test
    public void movie_pauseAndResume() throws Throwable {
        // The movie should be playing on start
        onView(withId(R.id.movie))
                .check(matches(allOf(isDisplayed(), isPlaying())))
                .perform(showControls());
        // Pause
        onView(withId(R.id.toggle)).perform(click());
        onView(withId(R.id.movie)).check(matches((not(isPlaying()))));
        // The media session state should be paused.
        assertMediaStateIs(PlaybackStateCompat.STATE_PAUSED);
        // Resume
        onView(withId(R.id.toggle)).perform(click());
        onView(withId(R.id.movie)).check(matches(isPlaying()));
        // The media session state should be playing.
        assertMediaStateIs(PlaybackStateCompat.STATE_PLAYING);
    }

    @Test
    public void fullscreen_enabledOnLandscape() throws Throwable {
        rule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                rule.getActivity()
                        .setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            }
        });
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
        rule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                final View decorView = rule.getActivity().getWindow().getDecorView();
                assertThat(decorView.getSystemUiVisibility(),
                        hasFlag(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN));
            }
        });
    }

    @Test
    public void fullscreen_disabledOnPortrait() throws Throwable {
        rule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                rule.getActivity()
                        .setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            }
        });
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
        rule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                final View decorView = rule.getActivity().getWindow().getDecorView();
                assertThat(decorView.getSystemUiVisibility(),
                        not(hasFlag(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)));
            }
        });
    }

    private void assertMediaStateIs(@PlaybackStateCompat.State int expectedState) {
        PlaybackState state = rule.getActivity().getMediaController().getPlaybackState();
        assertNotNull(state);
        assertThat(
                "MediaSession is not in the correct state",
                state.getState(),
                is(equalTo(expectedState)));
    }

    private static Matcher<? super View> isPlaying() {
        return new TypeSafeMatcher<View>() {
            @Override
            protected boolean matchesSafely(View view) {
                return ((MovieView) view).isPlaying();
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("MovieView is playing");
            }
        };
    }

    private static ViewAction showControls() {
        return new ViewAction() {
            @Override
            public Matcher<View> getConstraints() {
                return isAssignableFrom(MovieView.class);
            }

            @Override
            public String getDescription() {
                return "Show controls of MovieView";
            }

            @Override
            public void perform(UiController uiController, View view) {
                uiController.loopMainThreadUntilIdle();
                ((MovieView) view).showControls();
                uiController.loopMainThreadUntilIdle();
            }
        };
    }

    private static Matcher<? super Integer> hasFlag(final int flag) {
        return new TypeSafeMatcher<Integer>() {
            @Override
            protected boolean matchesSafely(Integer i) {
                return (i & flag) == flag;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("Flag integer contains " + flag);
            }
        };
    }

}
