package com.mobify.astro.plugins.webviewplugin;

import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.widget.FrameLayout;
import android.widget.ProgressBar;

import com.mobify.astro.ActivityTestBase;
import com.mobify.astro.AstroActivity;
import com.mobify.astro.animations.FadeInAnimation;
import com.mobify.astro.animations.FadeOutAnimation;
import com.mobify.astro.plugins.loaders.LoaderPlugin;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;

public class LoadingContainerViewTest extends ActivityTestBase {
    LoadingContainerView loadingContainerView;

    @Before
    public void setup() throws Throwable {
        final AstroActivity testActivity = getActivity();

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView = new LoadingContainerView(testActivity);
                testActivity.setContentView(loadingContainerView);
                loadingContainerView = spy(loadingContainerView);
            }
        });
    }

    @Test
    public void testGetCenteredLayoutParams() {
        FrameLayout.LayoutParams params = loadingContainerView.getCenteredLayoutParams();
        assertNotNull(params);
        assertEquals(params.gravity, Gravity.CENTER);
    }

    @Test
    public void testGetChildContainer() {
        View childContainer = loadingContainerView.getChildContainer();
        assertNotNull(childContainer);
    }

    @Test
    public void testGetChildContainerAnimation() {
        Animation animation = loadingContainerView.getChildContainerAnimation();
        assertTrue(animation instanceof FadeOutAnimation);
    }

    @Test
    public void testGetLoadingSpinnerAnimation() {
        Animation animation = loadingContainerView.getLoadingSpinnerAnimation();
        assertTrue(animation instanceof FadeInAnimation);
    }

    @Test
    public void testInitialState() {
        assertEquals(2, loadingContainerView.getChildCount());
        assertEquals(loadingContainerView.getLoaderView(), loadingContainerView.getChildAt(0));
        assertEquals(loadingContainerView.getChildContainer(), loadingContainerView.getChildAt(1));

        assertEquals(LoadingContainerView.State.SHOWING, loadingContainerView.state);
        assertEquals(View.INVISIBLE, loadingContainerView.getLoaderView().getVisibility());
        assertEquals(View.VISIBLE, loadingContainerView.getChildContainer().getVisibility());
    }

    @Test
    public void testEnterChildContainerFadingOutState() throws Throwable {
        FrameLayout childContainerMock = mock(FrameLayout.class);
        doReturn(childContainerMock).when(loadingContainerView).getChildContainer();

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.enterChildContainerFadingOutState();
            }
        });

        assertEquals(LoadingContainerView.State.CHILD_CONTAINER_FADING_OUT, loadingContainerView.state);
        verify(childContainerMock).startAnimation(any(Animation.class));
    }

    @Test
    public void testEnterLoadingSpinnerFadingInState() throws Throwable {
        LoaderPlugin loaderPluginMock = mock(LoadingContainerView.PlaceholderLoader.class);
        loadingContainerView._loaderPlugin = loaderPluginMock;

        ProgressBar loadingSpinnerMock = mock(ProgressBar.class);
        doReturn(loadingSpinnerMock).when(loaderPluginMock).getView();

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.enterLoadingSpinnerFadingInState();
            }
        });

        assertEquals(LoadingContainerView.State.LOADING_SPINNER_FADING_IN, loadingContainerView.state);
        assertEquals(View.INVISIBLE, loadingContainerView.getChildContainer().getVisibility());
        verify(loadingSpinnerMock).startAnimation(any(Animation.class));
        verify(loadingContainerView).setNextChildView();
    }

    @Test
    public void testEnterLoadingState() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.enterLoadingState();
            }
        });

        assertEquals(LoadingContainerView.State.LOADING, loadingContainerView.state);
        assertEquals(View.VISIBLE, loadingContainerView.getLoaderView().getVisibility());
    }

    @Test
    public void testDisablingLoader() throws Throwable {
        // loader view should be invisible when entering loading state and loader is disabled
        loadingContainerView.setLoaderEnabled(false);
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.enterLoadingState();
            }
        });

        assertEquals(LoadingContainerView.State.LOADING, loadingContainerView.state);
        assertEquals(View.INVISIBLE, loadingContainerView.getLoaderView().getVisibility());
    }

    @Test
    public void testEnterShowingState() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.enterShowingState();
            }
        });

        assertEquals(View.INVISIBLE, loadingContainerView.getLoaderView().getVisibility());
        assertEquals(View.VISIBLE, loadingContainerView.getChildContainer().getVisibility());
    }

    @Test
    public void testSetNextChildView() throws Throwable {
        loadingContainerView.nextView = null;
        loadingContainerView.setNextChildView();
        verify(loadingContainerView, never()).setChildView(any(View.class));

        View view = new View(getActivity());
        loadingContainerView.nextView = view;

        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.setNextChildView();
            }
        });

        verify(loadingContainerView).setChildView(view);
    }

    @Test
    public void testClearAnimations() throws Throwable {
        FrameLayout childContainerMock = mock(FrameLayout.class);
        doReturn(childContainerMock).when(loadingContainerView).getChildContainer();

        LoaderPlugin loaderPluginMock = mock(LoadingContainerView.PlaceholderLoader.class);
        loadingContainerView._loaderPlugin = loaderPluginMock;

        ProgressBar loadingSpinnerMock = mock(ProgressBar.class);
        doReturn(loadingSpinnerMock).when(loaderPluginMock).getView();

        Animation animationMock = mock(Animation.class);
        doReturn(animationMock).when(loadingContainerView).getChildContainerAnimation();

        // !hasStarted(), !hasEnded()
        doReturn(false).when(animationMock).hasStarted();
        doReturn(false).when(animationMock).hasEnded();
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.clearAnimations();
            }
        });
        assertFalse(loadingContainerView.cancelChildContainerAnimationListenerTick);
        verify(childContainerMock).clearAnimation();
        verify(loadingSpinnerMock).clearAnimation();

        // hasStarted(), hasEnded()
        doReturn(true).when(animationMock).hasStarted();
        doReturn(true).when(animationMock).hasEnded();
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.clearAnimations();
            }
        });
        assertFalse(loadingContainerView.cancelChildContainerAnimationListenerTick);

        // hasStarted(), !hasEnded()
        doReturn(true).when(animationMock).hasStarted();
        doReturn(false).when(animationMock).hasEnded();
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.clearAnimations();
            }
        });
        assertTrue(loadingContainerView.cancelChildContainerAnimationListenerTick);
    }

    @Test
    public void testClearChildView() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.clearChildView();
            }
        });

        verify(loadingContainerView).setChildView(null);
    }

    @Test
    public void testSetChildView() throws Throwable {
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.clearChildView();
            }
        });
        assertEquals(0, loadingContainerView.getChildContainer().getChildCount());

        final View view = new View(getActivity());
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.setChildView(view);
            }
        });
        assertEquals(1, loadingContainerView.getChildContainer().getChildCount());
        assertEquals(view, loadingContainerView.getChildContainer().getChildAt(0));
    }

    @Test
    public void testTransitionToLoadingWithViewFromChildContainerFadingOut() throws Throwable {
        final View view = new View(getActivity());

        loadingContainerView.state = LoadingContainerView.State.CHILD_CONTAINER_FADING_OUT;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToLoadingWithView(view, "");
            }
        });
        assertEquals(view, loadingContainerView.nextView);
    }

    @Test
    public void testTransitionToLoadingWithViewFromLoadingSpinnerFadingIn() throws Throwable {
        final View view = new View(getActivity());

        loadingContainerView.state = LoadingContainerView.State.LOADING_SPINNER_FADING_IN;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToLoadingWithView(view, "");
            }
        });

        verify(loadingContainerView).setChildView(view);
    }

    @Test
    public void testTransitionToLoadingWithViewFromLoading() throws Throwable {
        final View view = new View(getActivity());

        loadingContainerView.state = LoadingContainerView.State.LOADING;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToLoadingWithView(view, "");
            }
        });
        verify(loadingContainerView).setChildView(view);
    }

    @Test
    public void testTransitionToLoadingWithViewFromShowing() throws Throwable {
        final View view = new View(getActivity());

        loadingContainerView.state = LoadingContainerView.State.SHOWING;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToLoadingWithView(view, "");
            }
        });
        assertEquals(view, loadingContainerView.nextView);
        verify(loadingContainerView).transitionToLoading();
    }

    @Test
    public void testTransitionToLoading() throws Throwable {
        loadingContainerView.state = LoadingContainerView.State.SHOWING;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToLoading();
            }
        });
        verify(loadingContainerView).enterChildContainerFadingOutState();
    }

    @Test
    public void testTransitionToShowingWithViewFromShowing() throws Throwable {
        final View view = new View(getActivity());

        loadingContainerView.state = LoadingContainerView.State.SHOWING;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToShowingWithView(view);
            }
        });

        verify(loadingContainerView).clearAnimations();
        verify(loadingContainerView).setChildView(view);
        verify(loadingContainerView, never()).enterShowingState();
    }

    @Test
    public void testTransitionToShowingWithViewFromLoading() throws Throwable {
        final View view = new View(getActivity());

        loadingContainerView.state = LoadingContainerView.State.LOADING;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToShowingWithView(view);
            }
        });

        verify(loadingContainerView).clearAnimations();
        verify(loadingContainerView).setChildView(view);
        verify(loadingContainerView).enterShowingState();
    }

    @Test
    public void testTransitionToShowingWhenStateFromShowing() throws Throwable {
        loadingContainerView.state = LoadingContainerView.State.SHOWING;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToShowing();
            }
        });

        verify(loadingContainerView, never()).setNextChildView();
        verify(loadingContainerView, never()).enterShowingState();
    }

    @Test
    public void testTransitionToShowingFromChildContainerFadingOut() throws Throwable {
        loadingContainerView.state = LoadingContainerView.State.CHILD_CONTAINER_FADING_OUT;
        mActivityRule.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                loadingContainerView.transitionToShowing();
            }
        });

        verify(loadingContainerView).setNextChildView();
        verify(loadingContainerView).enterShowingState();
    }
}
