package com.alexvasilkov.gestures.transition.internal;

import android.view.View;
import android.view.ViewGroup;

import com.alexvasilkov.gestures.animation.ViewPositionAnimator;
import com.alexvasilkov.gestures.animation.ViewPositionAnimator.PositionUpdateListener;
import com.alexvasilkov.gestures.commons.RecyclePagerAdapter;
import com.alexvasilkov.gestures.transition.ViewsTransitionAnimator;
import com.alexvasilkov.gestures.transition.tracker.IntoTracker;
import com.alexvasilkov.gestures.views.interfaces.AnimatorView;

import androidx.annotation.NonNull;
import androidx.viewpager.widget.ViewPager;

/**
 * Helper class to animate transitions into ViewPager with GestureView-driven pages.
 * <p>
 * It works best with {@link RecyclePagerAdapter} as ViewPager adapter.
 */
public class IntoViewPagerListener<ID> extends ViewsTransitionAnimator.RequestListener<ID> {

    private final ViewPager viewPager;
    private final IntoTracker<ID> tracker;

    private boolean preventExit;

    public IntoViewPagerListener(ViewPager viewPager, IntoTracker<ID> tracker) {
        this.viewPager = viewPager;
        this.tracker = tracker;

        viewPager.setVisibility(View.GONE); // We do not need to initialize ViewPager on startup
        viewPager.addOnPageChangeListener(new PagerListener());
        viewPager.setOnHierarchyChangeListener(new ChildStateListener());
    }

    @Override
    protected void initAnimator(ViewsTransitionAnimator<ID> animator) {
        super.initAnimator(animator);

        animator.addPositionUpdateListener(new PositionUpdateListener() {
            @Override
            public void onPositionUpdate(float pos, boolean isLeaving) {
                if (pos == 1f && isLeaving && getAnimator().getRequestedId() != null) {
                    if (preventExit) {
                        skipExit();
                    }
                    switchToCurrentPage();
                }

                viewPager.setVisibility(pos == 0f && isLeaving ? View.INVISIBLE : View.VISIBLE);
            }
        });
    }

    @Override
    public void onRequestView(@NonNull ID id) {
        // Requesting ViewPager layout if it was in 'gone' state
        if (viewPager.getVisibility() == View.GONE) {
            viewPager.setVisibility(View.INVISIBLE);
        }

        // Trying to find view for currently shown page.
        // If it is not a selected page then we should scroll to it at first.
        int position = tracker.getPositionById(id);

        if (position == IntoTracker.NO_POSITION) {
            return; // Nothing we can do
        }

        if (viewPager.getCurrentItem() == position) {
            applyCurrentPage();
        } else {
            viewPager.setCurrentItem(position, false);
        }
    }

    private void applyCurrentPage() {
        final ID id = getAnimator().getRequestedId();
        if (id == null) {
            return;
        }
        if (viewPager.getAdapter() == null || viewPager.getAdapter().getCount() == 0) {
            return;
        }

        final int position = tracker.getPositionById(id);

        if (position == IntoTracker.NO_POSITION) {
            switchToCurrentPage();
            return;
        }

        if (position != viewPager.getCurrentItem()) {
            return;
        }

        final View view = tracker.getViewById(id); // View may be null
        if (view instanceof AnimatorView) {
            getAnimator().setToView(id, (AnimatorView) view);
        } else if (view != null) {
            throw new IllegalArgumentException("View for " + id + " should be AnimatorView");
        }
    }

    private void switchToCurrentPage() {
        if (viewPager.getAdapter() == null || viewPager.getAdapter().getCount() == 0) {
            return;
        }

        final ID id = getAnimator().getRequestedId();
        final ID currentId = tracker.getIdByPosition(viewPager.getCurrentItem());

        // If user scrolled to new page we should silently switch views
        if (id != null && currentId != null && !id.equals(currentId)) {
            // Saving current state
            AnimatorView toView = getAnimator().getToView();
            ViewPositionAnimator toAnimator = toView == null ? null : toView.getPositionAnimator();
            boolean isLeaving = toAnimator != null && toAnimator.isLeaving();
            float position = toAnimator == null ? 0f : toAnimator.getPosition();
            boolean isAnimating = toAnimator != null && toAnimator.isAnimating();

            // Switching to new page, preventing exit of previous page
            skipExit();
            getAnimator().enter(currentId, false);

            // If exit animation was in place we should continue it
            if (isLeaving && position > 0f) {
                getAnimator().exit(isAnimating);
            }
        }
    }

    private void skipExit() {
        if (getAnimator().getToView() != null) {
            ViewPositionAnimator toAnimator = getAnimator().getToView().getPositionAnimator();
            if (toAnimator.isLeaving() && toAnimator.getPosition() == 1f) {
                toAnimator.setState(1f, false, false);
            }
        }
    }


    private class PagerListener implements ViewPager.OnPageChangeListener {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            // No-op
        }

        @Override
        public void onPageSelected(int position) {
            applyCurrentPage();
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            preventExit = state == ViewPager.SCROLL_STATE_DRAGGING && !getAnimator().isLeaving();

            if (state == ViewPager.SCROLL_STATE_IDLE && getAnimator().getRequestedId() != null) {
                switchToCurrentPage();
            }
        }
    }

    private class ChildStateListener implements ViewGroup.OnHierarchyChangeListener {
        @Override
        public void onChildViewAdded(View parent, View child) {
            applyCurrentPage();
        }

        @Override
        public void onChildViewRemoved(View parent, View child) {
            // No-op
        }
    }

}