/*
 * Copyright 2016–2020 Duolingo
 *
 * 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.duolingo.open.rtlviewpager;

import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

import java.util.HashMap;

/**
 * <code>RtlViewPager</code> is an API-compatible implementation of <code>ViewPager</code> which
 * orders paged views according to the layout direction of the view.  In left to right mode, the
 * first view is at the left side of the carousel, and in right to left mode it is at the right
 * side.
 * <p>
 * It accomplishes this by wrapping the provided <code>PagerAdapter</code> and any provided
 * <code>OnPageChangeListener</code>s so that clients can be agnostic to layout direction and
 * modifications are kept internal to <code>RtlViewPager</code>.
 */
public class RtlViewPager extends ViewPager {

    private final HashMap<OnPageChangeListener, ReversingOnPageChangeListener> mPageChangeListeners = new HashMap<>();
    private int mLayoutDirection = ViewCompat.LAYOUT_DIRECTION_LTR;

    public RtlViewPager(Context context) {
        super(context);
    }

    public RtlViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onRtlPropertiesChanged(int layoutDirection) {
        super.onRtlPropertiesChanged(layoutDirection);
        int viewCompatLayoutDirection = layoutDirection == View.LAYOUT_DIRECTION_RTL ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR;
        if (viewCompatLayoutDirection != mLayoutDirection) {
            PagerAdapter adapter = super.getAdapter();
            int position = 0;
            if (adapter != null) {
                position = getCurrentItem();
            }
            mLayoutDirection = viewCompatLayoutDirection;
            if (adapter != null) {
                adapter.notifyDataSetChanged();
                setCurrentItem(position);
            }
        }
    }

    @Override
    public void setAdapter(PagerAdapter adapter) {
        if (adapter != null) {
            adapter = new ReversingAdapter(adapter);
        }
        super.setAdapter(adapter);
        setCurrentItem(0);
    }

    @Override
    public PagerAdapter getAdapter() {
        ReversingAdapter adapter = (ReversingAdapter) super.getAdapter();
        return adapter == null ? null : adapter.getDelegate();
    }

    private boolean isRtl() {
        return mLayoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL;
    }

    @Override
    public int getCurrentItem() {
        int item = super.getCurrentItem();
        PagerAdapter adapter = super.getAdapter();
        if (adapter != null && isRtl()) {
            item = adapter.getCount() - item - 1;
        }
        return item;
    }

    @Override
    public void setCurrentItem(int position, boolean smoothScroll) {
        PagerAdapter adapter = super.getAdapter();
        if (adapter != null && isRtl()) {
            position = adapter.getCount() - position - 1;
        }
        super.setCurrentItem(position, smoothScroll);
    }

    @Override
    public void setCurrentItem(int position) {
        PagerAdapter adapter = super.getAdapter();
        if (adapter != null && isRtl()) {
            position = adapter.getCount() - position - 1;
        }
        super.setCurrentItem(position);
    }

    @Deprecated
    @Override
    public void setOnPageChangeListener(@NonNull ViewPager.OnPageChangeListener listener) {
        super.setOnPageChangeListener(new ReversingOnPageChangeListener(listener));
    }

    @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        return new SavedState(superState, mLayoutDirection);
    }

    @Override
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState ss = (SavedState) state;
        mLayoutDirection = ss.mLayoutDirection;
        super.onRestoreInstanceState(ss.mViewPagerSavedState);
    }

    public static class SavedState implements Parcelable {

        private final Parcelable mViewPagerSavedState;
        private final int mLayoutDirection;

        private SavedState(Parcelable viewPagerSavedState, int layoutDirection) {
            mViewPagerSavedState = viewPagerSavedState;
            mLayoutDirection = layoutDirection;
        }

        private SavedState(Parcel in, ClassLoader loader) {
            if (loader == null) {
                loader = getClass().getClassLoader();
            }
            mViewPagerSavedState = in.readParcelable(loader);
            mLayoutDirection = in.readInt();
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            out.writeParcelable(mViewPagerSavedState, flags);
            out.writeInt(mLayoutDirection);
        }

        // The `CREATOR` field is used to create the parcelable from a parcel, even though it is never referenced directly.
        public static final Parcelable.ClassLoaderCreator<SavedState> CREATOR
                = new Parcelable.ClassLoaderCreator<SavedState>() {

            @Override
            public SavedState createFromParcel(Parcel source) {
                return createFromParcel(source, null);
            }

            @Override
            public SavedState createFromParcel(Parcel source, ClassLoader loader) {
                return new SavedState(source, loader);
            }

            @Override
            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };

    }

    @Override
    public void addOnPageChangeListener(@NonNull OnPageChangeListener listener) {
        ReversingOnPageChangeListener reversingListener = new ReversingOnPageChangeListener(listener);
        mPageChangeListeners.put(listener, reversingListener);
        super.addOnPageChangeListener(reversingListener);
    }

    @Override
    public void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) {
        ReversingOnPageChangeListener reverseListener = mPageChangeListeners.remove(listener);
        if (reverseListener != null) {
            super.removeOnPageChangeListener(reverseListener);
        }
    }

    @Override
    public void clearOnPageChangeListeners() {
        super.clearOnPageChangeListeners();
        mPageChangeListeners.clear();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) {
            int height = 0;
            for (int i = 0; i < getChildCount(); i++) {
                View child = getChildAt(i);
                child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
                int h = child.getMeasuredHeight();
                if (h > height) {
                    height = h;
                }
            }
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private class ReversingOnPageChangeListener implements OnPageChangeListener {

        private final OnPageChangeListener mListener;

        ReversingOnPageChangeListener(OnPageChangeListener listener) {
            mListener = listener;
        }

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            // The documentation says that `getPageWidth(...)` returns the fraction of the _measured_ width that that page takes up.  However, the code seems to
            // use the width so we will here too.
            final int width = getWidth();
            PagerAdapter adapter = RtlViewPager.super.getAdapter();
            if (isRtl() && adapter != null) {
                int count = adapter.getCount();
                int remainingWidth = (int) (width * (1 - adapter.getPageWidth(position))) + positionOffsetPixels;
                while (position < count && remainingWidth > 0) {
                    position += 1;
                    remainingWidth -= (int) (width * adapter.getPageWidth(position));
                }
                position = count - position - 1;
                positionOffsetPixels = -remainingWidth;
                positionOffset = positionOffsetPixels / (width * adapter.getPageWidth(position));
            }
            mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
        }

        @Override
        public void onPageSelected(int position) {
            PagerAdapter adapter = RtlViewPager.super.getAdapter();
            if (isRtl() && adapter != null) {
                position = adapter.getCount() - position - 1;
            }
            mListener.onPageSelected(position);
        }

        @Override
        public void onPageScrollStateChanged(int state) {
            mListener.onPageScrollStateChanged(state);
        }
    }

    private class ReversingAdapter extends DelegatingPagerAdapter {

        ReversingAdapter(@NonNull PagerAdapter adapter) {
            super(adapter);
        }

        @Override
        public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            super.destroyItem(container, position, object);
        }

        @Deprecated
        @Override
        public void destroyItem(@NonNull View container, int position, @NonNull Object object) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            super.destroyItem(container, position, object);
        }

        @Override
        public int getItemPosition(@NonNull Object object) {
            int position = super.getItemPosition(object);
            if (isRtl()) {
                if (position == POSITION_UNCHANGED || position == POSITION_NONE) {
                    // We can't accept POSITION_UNCHANGED when in RTL mode because adding items to the end of the collection adds them to the beginning of the
                    // ViewPager.  Items whose positions do not change from the perspective of the wrapped adapter actually do change from the perspective of
                    // the ViewPager.
                    position = POSITION_NONE;
                } else {
                    position = getCount() - position - 1;
                }
            }
            return position;
        }

        @Override
        public CharSequence getPageTitle(int position) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            return super.getPageTitle(position);
        }

        @Override
        public float getPageWidth(int position) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            return super.getPageWidth(position);
        }

        @Override
        public @NonNull
        Object instantiateItem(@NonNull ViewGroup container, int position) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            return super.instantiateItem(container, position);
        }

        @Deprecated
        @Override
        public @NonNull
        Object instantiateItem(@NonNull View container, int position) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            return super.instantiateItem(container, position);
        }

        @Override
        public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            super.setPrimaryItem(container, position, object);
        }

        @Deprecated
        @Override
        public void setPrimaryItem(@NonNull View container, int position, @NonNull Object object) {
            if (isRtl()) {
                position = getCount() - position - 1;
            }
            super.setPrimaryItem(container, position, object);
        }

    }
}