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

import android.animation.AnimatorSetActivity;
import android.app.Activity;
import android.test.ActivityInstrumentationTestCase2;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;

import androidx.test.filters.SmallTest;

import com.android.frameworks.coretests.R;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class SlideTransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {

    Activity mActivity;

    public SlideTransitionTest() {
        super(AnimatorSetActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        mActivity = getActivity();
    }

    @SmallTest
    public void testShortSlide() throws Throwable {
        final float slideFraction = 0.5f;
        final View square1 = mActivity.findViewById(R.id.square1);
        final View sceneRoot = mActivity.findViewById(R.id.container);
        final SlideTranslationValueRatchet ratchet = new SlideTranslationValueRatchet(square1);
        square1.getViewTreeObserver().addOnPreDrawListener(ratchet);

        final Slide slideOut = new Slide(Gravity.BOTTOM);
        final float finalOffsetOut = sceneRoot.getHeight() * slideFraction;
        slideOut.setSlideFraction(slideFraction);
        TransitionLatch latch = setVisibilityInTransition(slideOut, R.id.square1, View.INVISIBLE);
        assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS));
        assertEquals(0f, square1.getTranslationY(), 0.1f);
        assertEquals(View.VISIBLE, square1.getVisibility());
        Thread.sleep(100);
        assertFalse(square1.getTranslationY() < 0.1
                || square1.getTranslationY() > finalOffsetOut - 0.1);
        assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS));
        // Give this 20% slop in case some frames get dropped.
        assertTrue(finalOffsetOut * 0.8 < ratchet.maxY);
        assertTrue(finalOffsetOut + 0.1 > ratchet.maxY);
        assertEquals(View.INVISIBLE, square1.getVisibility());

        ratchet.reset();
        final Slide slideIn = new Slide(Gravity.BOTTOM);
        final float initialOffsetIn = sceneRoot.getHeight() * slideFraction;
        slideIn.setSlideFraction(slideFraction);
        latch = setVisibilityInTransition(slideIn, R.id.square1, View.VISIBLE);
        assertTrue(latch.startLatch.await(200, TimeUnit.MILLISECONDS));
        assertEquals(initialOffsetIn, square1.getTranslationY(), 0.1f);
        assertEquals(View.VISIBLE, square1.getVisibility());
        Thread.sleep(100);
        assertFalse(square1.getTranslationY() < 0.1
                || square1.getTranslationY() > initialOffsetIn - 0.1);
        assertTrue(latch.endLatch.await(400, TimeUnit.MILLISECONDS));
        assertEquals(0f, ratchet.minY, 0.1);
        assertEquals(0f, square1.getTranslationY(), 0.1);
        assertEquals(View.VISIBLE, square1.getVisibility());

        square1.getViewTreeObserver().removeOnPreDrawListener(ratchet);
    }

    public TransitionLatch setVisibilityInTransition(final Transition transition, int viewId,
            final int visibility) throws Throwable {
        final ViewGroup sceneRoot = (ViewGroup) mActivity.findViewById(R.id.container);
        final View view = sceneRoot.findViewById(viewId);
        TransitionLatch latch = new TransitionLatch();
        transition.addListener(latch);
        runTestOnUiThread(new Runnable() {
            @Override
            public void run() {
                TransitionManager.beginDelayedTransition(sceneRoot, transition);
                view.setVisibility(visibility);
            }
        });
        return latch;
    }

    public static class TransitionLatch implements Transition.TransitionListener {
        public CountDownLatch startLatch = new CountDownLatch(1);
        public CountDownLatch endLatch = new CountDownLatch(1);
        public CountDownLatch cancelLatch = new CountDownLatch(1);
        public CountDownLatch pauseLatch = new CountDownLatch(1);
        public CountDownLatch resumeLatch = new CountDownLatch(1);

        @Override
        public void onTransitionStart(Transition transition) {
            startLatch.countDown();
        }

        @Override
        public void onTransitionEnd(Transition transition) {
            endLatch.countDown();
            transition.removeListener(this);
        }

        @Override
        public void onTransitionCancel(Transition transition) {
            cancelLatch.countDown();
        }

        @Override
        public void onTransitionPause(Transition transition) {
            pauseLatch.countDown();
        }

        @Override
        public void onTransitionResume(Transition transition) {
            resumeLatch.countDown();
        }
    }

    private static class SlideTranslationValueRatchet
            implements ViewTreeObserver.OnPreDrawListener {

        private final View mView;
        private boolean mInitialized;
        public float minX = Float.NaN;
        public float minY = Float.NaN;
        public float maxX = Float.NaN;
        public float maxY = Float.NaN;

        public SlideTranslationValueRatchet(View view) {
            mView = view;
        }

        public void reset() {
            minX = minY = maxX = maxY = Float.NaN;
            mInitialized = false;
        }

        @Override
        public boolean onPreDraw() {
            if (!mInitialized) {
                minX = maxX = mView.getTranslationX();
                minY = maxY = mView.getTranslationY();
                mInitialized = true;
            } else {
                minX = Math.min(minX, mView.getTranslationX());
                minY = Math.min(minY, mView.getTranslationY());
                maxX = Math.max(maxX, mView.getTranslationX());
                maxY = Math.max(maxY, mView.getTranslationY());
            }
            return true;
        }
    }
}
