package com.github.paolorotolo.appintro;

import android.animation.ArgbEvaluator;
import android.os.Build;
import android.os.Bundle;
import android.os.Vibrator;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.Toast;

import com.github.paolorotolo.appintro.util.LogHelper;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

public abstract class AppIntroBase extends AppCompatActivity implements
        AppIntroViewPager.OnNextPageRequestedListener {

    public static final int DEFAULT_COLOR = 1;

    private static final String TAG = LogHelper.makeLogTag(AppIntroBase.class);

    private static final int DEFAULT_SCROLL_DURATION_FACTOR = 1;
    private static final int PERMISSIONS_REQUEST_ALL_PERMISSIONS = 1;
    private static final String INSTANCE_DATA_IMMERSIVE_MODE_ENABLED =
            "com.github.paolorotolo.appintro_immersive_mode_enabled";
    private static final String INSTANCE_DATA_IMMERSIVE_MODE_STICKY =
            "com.github.paolorotolo.appintro_immersive_mode_sticky";
    private static final String INSTANCE_DATA_COLOR_TRANSITIONS_ENABLED =
            "com.github.paolorotolo.appintro_color_transitions_enabled";
    protected final List<Fragment> fragments = new Vector<>();
    private final ArgbEvaluator argbEvaluator = new ArgbEvaluator();
    protected PagerAdapter mPagerAdapter;
    protected AppIntroViewPager pager;
    protected Vibrator mVibrator;
    protected IndicatorController mController;
    protected int slidesNumber;
    protected int vibrateIntensity = 20;
    protected int selectedIndicatorColor = DEFAULT_COLOR;
    protected int unselectedIndicatorColor = DEFAULT_COLOR;
    protected View nextButton;
    protected View doneButton;
    protected View skipButton;
    protected View backButton;
    protected int savedCurrentItem;
    protected ArrayList<PermissionObject> permissionsArray = new ArrayList<>();
    protected boolean isVibrateOn = false;
    protected boolean baseProgressButtonEnabled = true;
    protected boolean progressButtonEnabled = true;
    protected boolean skipButtonEnabled = true;
    protected boolean isWizardMode = false;
    protected boolean showBackButtonWithDone = false;
    private GestureDetectorCompat gestureDetector;
    private boolean isGoBackLockEnabled = false;
    private boolean isImmersiveModeEnabled = false;
    private boolean isImmersiveModeSticky = false;
    private boolean areColorTransitionsEnabled = false;
    private int currentlySelectedItem = -1;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);

        setContentView(getLayoutId());

        gestureDetector = new GestureDetectorCompat(this, new WindowGestureListener());

        nextButton = findViewById(R.id.next);
        doneButton = findViewById(R.id.done);
        skipButton = findViewById(R.id.skip);
        backButton = findViewById(R.id.back);

        checkButton(nextButton, "next");
        checkButton(doneButton, "done");
        checkButton(skipButton, "skip");
        checkButton(backButton, "back");

        FrameLayout frameLayout = (FrameLayout) findViewById(R.id.bottomContainer);
        if (frameLayout != null && isRtl()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                frameLayout.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
            }
        }
        if (isRtl()) {
            (nextButton).setScaleX(-1);
        }

        mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
        mPagerAdapter = new PagerAdapter(getSupportFragmentManager(), fragments);
        pager = (AppIntroViewPager) findViewById(R.id.view_pager);

        if (doneButton != null) {
            doneButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(@NonNull View v) {
                    if (isVibrateOn) {
                        mVibrator.vibrate(vibrateIntensity);
                    }

                    Fragment currentFragment = mPagerAdapter.getItem(pager.getCurrentItem());
                    boolean isSlideChangingAllowed = handleBeforeSlideChanged();

                    if (isSlideChangingAllowed) {
                        handleSlideChanged(currentFragment, null);
                        onDonePressed(currentFragment);
                    } else {
                        handleIllegalSlideChangeAttempt();
                    }
                }
            });
        }

        if (skipButton != null) {
            skipButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(@NonNull View v) {
                    if (isVibrateOn) {
                        mVibrator.vibrate(vibrateIntensity);
                    }
                    onSkipPressed(mPagerAdapter.getItem(pager.getCurrentItem()));
                }
            });
        }

        if (nextButton != null) {
            nextButton.setOnClickListener(new NextButtonOnClickListener());
        }

        if (backButton != null) {
            backButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (pager.getCurrentItem() > 0) {
                        pager.setCurrentItem(pager.getCurrentItem() - 1);
                    }
                }
            });
        }
        pager.setAdapter(this.mPagerAdapter);
        pager.addOnPageChangeListener(new PagerOnPageChangeListener());
        pager.setOnNextPageRequestedListener(this);

        setScrollDurationFactor(DEFAULT_SCROLL_DURATION_FACTOR);
    }

    /**
     * Check {@link View} to null pointer. Log error when view is {@code null}.
     *
     * @param view     view for check on {@code null}.
     * @param viewName text representation of {@link View}.
     */
    private void checkButton(@Nullable View view, @Nullable String viewName) {
        if (view == null) {
            Log.e(TAG, String.format("View not initialized, missing 'R.id.%1$s' in XML!", viewName));
        }
    }

    @Override
    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        // Call deprecated init method only if no fragments have been added through onCreate() or onStart()
        if (fragments.size() == 0) {
            init(null);
        }

        // required for triggering onPageSelected and onSlideChanged for first page
        if (isRtl()) {
            pager.setCurrentItem(fragments.size() - savedCurrentItem);
        } else {
            pager.setCurrentItem(savedCurrentItem);
        }
        pager.post(new Runnable() {
            @Override
            public void run() {
                handleSlideChanged(null, mPagerAdapter.getItem(pager.getCurrentItem()));
            }
        });

        slidesNumber = fragments.size();

        setProgressButtonEnabled(progressButtonEnabled);
        initController();
    }

    @Override
    public void onBackPressed() {
        // Do nothing if go back lock is enabled or slide has custom policy.
        if (!isGoBackLockEnabled) {
            if (!pager.isFirstSlide(fragments.size())) {
                pager.goToPreviousSlide();
            } else {
                super.onBackPressed();
            }
        }
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if (hasFocus && isImmersiveModeEnabled) {
            setImmersiveMode(true, isImmersiveModeSticky);
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (isImmersiveModeEnabled) {
            gestureDetector.onTouchEvent(event);
        }

        return super.dispatchTouchEvent(event);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("baseProgressButtonEnabled", baseProgressButtonEnabled);
        outState.putBoolean("progressButtonEnabled", progressButtonEnabled);
        outState.putBoolean("nextEnabled", pager.isPagingEnabled());
        outState.putBoolean("nextPagingEnabled", pager.isNextPagingEnabled());
        outState.putBoolean("skipButtonEnabled", skipButtonEnabled);
        outState.putInt("lockPage", pager.getLockPage());
        outState.putInt("currentItem", pager.getCurrentItem());

        outState.putBoolean(INSTANCE_DATA_IMMERSIVE_MODE_ENABLED, isImmersiveModeEnabled);
        outState.putBoolean(INSTANCE_DATA_IMMERSIVE_MODE_STICKY, isImmersiveModeSticky);
        outState.putBoolean(INSTANCE_DATA_COLOR_TRANSITIONS_ENABLED, areColorTransitionsEnabled);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        this.baseProgressButtonEnabled = savedInstanceState.getBoolean("baseProgressButtonEnabled");
        this.progressButtonEnabled = savedInstanceState.getBoolean("progressButtonEnabled");
        this.skipButtonEnabled = savedInstanceState.getBoolean("skipButtonEnabled");
        this.savedCurrentItem = savedInstanceState.getInt("currentItem");
        pager.setPagingEnabled(savedInstanceState.getBoolean("nextEnabled"));
        pager.setNextPagingEnabled(savedInstanceState.getBoolean("nextPagingEnabled"));
        pager.setLockPage(savedInstanceState.getInt("lockPage"));

        isImmersiveModeEnabled = savedInstanceState.getBoolean(INSTANCE_DATA_IMMERSIVE_MODE_ENABLED);
        isImmersiveModeSticky = savedInstanceState.getBoolean(INSTANCE_DATA_IMMERSIVE_MODE_STICKY);
        areColorTransitionsEnabled = savedInstanceState.getBoolean(
                INSTANCE_DATA_COLOR_TRANSITIONS_ENABLED);
    }

    @Override
    public boolean onCanRequestNextPage() {
        return handleBeforeSlideChanged();
    }

    @Override
    public void onIllegallyRequestedNextPage() {
        handleIllegalSlideChangeAttempt();
    }

    private void initController() {
        if (mController == null)
            mController = new DefaultIndicatorController();

        FrameLayout indicatorContainer = (FrameLayout) findViewById(R.id.indicator_container);
        indicatorContainer.addView(mController.newInstance(this));

        mController.initialize(slidesNumber);
        if (selectedIndicatorColor != DEFAULT_COLOR)
            mController.setSelectedIndicatorColor(selectedIndicatorColor);
        if (unselectedIndicatorColor != DEFAULT_COLOR)
            mController.setUnselectedIndicatorColor(unselectedIndicatorColor);

        mController.selectPosition(currentlySelectedItem);
    }

    private void handleIllegalSlideChangeAttempt() {
        Fragment currentFragment = mPagerAdapter.getItem(pager.getCurrentItem());

        if (currentFragment != null && currentFragment instanceof ISlidePolicy) {
            ISlidePolicy slide = (ISlidePolicy) currentFragment;

            if (!slide.isPolicyRespected()) {
                slide.onUserIllegallyRequestedNextPage();
            }
        }
    }

    /**
     * Called before a slide change happens. By returning false, one can disallow the slide change.
     *
     * @return true, if the slide change should be allowed, else false
     */
    private boolean handleBeforeSlideChanged() {
        Fragment currentFragment = mPagerAdapter.getItem(pager.getCurrentItem());

        LogHelper.d(TAG, String.format(
                "User wants to move away from slide: %s. Checking if this should be allowed...",
                currentFragment));

        // Check if the current fragment implements ISlidePolicy, else a change is always allowed
        if (currentFragment instanceof ISlidePolicy) {
            ISlidePolicy slide = (ISlidePolicy) currentFragment;

            LogHelper.d(TAG, "Current fragment implements ISlidePolicy.");

            // Check if policy is fulfilled
            if (!slide.isPolicyRespected()) {
                LogHelper.d(TAG, "Slide policy not respected, denying change request.");
                return false;
            }
        }

        LogHelper.d(TAG, "Change request will be allowed.");
        return true;
    }

    private void handleSlideChanged(Fragment oldFragment, Fragment newFragment) {
        // Check if oldFragment implements ISlideSelectionListener
        if (oldFragment != null && oldFragment instanceof ISlideSelectionListener) {
            ((ISlideSelectionListener) oldFragment).onSlideDeselected();
        }

        // Check if newFragment implements ISlideSelectionListener
        if (newFragment != null && newFragment instanceof ISlideSelectionListener) {
            ((ISlideSelectionListener) newFragment).onSlideSelected();
        }

        onSlideChanged(oldFragment, newFragment);
    }

    /**
     * Gets the layout id of the layout used by the current activity
     *
     * @return Layout to use
     */
    protected abstract int getLayoutId();

    /**
     * Called after a new slide has been selected
     *
     * @param position Position of the new selected slide
     */
    protected void onPageSelected(int position) {
        // Empty method
    }

    /**
     * Setting this to display or hide the Skip button. This is a static setting and
     * button state is maintained across slides until explicitly changed.
     *
     * @param showButton Set true to display. false to hide.
     */
    public void showSkipButton(boolean showButton) {
        this.skipButtonEnabled = showButton;
        setButtonState(skipButton, showButton);
    }

    public boolean isSkipButtonEnabled() {
        return skipButtonEnabled;
    }

    /**
     * Called when the user clicked the skip button
     *
     * @param currentFragment Instance of the currently displayed fragment
     */
    public void onSkipPressed(Fragment currentFragment) {
        onSkipPressed();
    }

    protected void setScrollDurationFactor(int factor) {
        pager.setScrollDurationFactor(factor);
    }

    /**
     * Helper method for displaying a view
     *
     * @param button View which visibility should be changed
     * @param show   Whether the view should be visible or not
     */
    protected void setButtonState(@Nullable View button, boolean show) {
        if (button == null) {
            return;
        }

        if (show) {
            button.setVisibility(View.VISIBLE);
        } else {
            button.setVisibility(View.INVISIBLE);
        }
    }

    /**
     * Returns the used ViewPager instance
     *
     * @return Instance of the used ViewPager
     */
    public AppIntroViewPager getPager() {
        return pager;
    }

    /**
     * Returns all current slides.
     *
     * @return List of the current slides
     */
    @NonNull
    public List<Fragment> getSlides() {
        return mPagerAdapter.getFragments();
    }

    /**
     * Adds a new slide
     *
     * @param fragment Instance of Fragment which should be added as slide
     */
    public void addSlide(@NonNull Fragment fragment) {
        if (isRtl())
            fragments.add(0, fragment);
        else
            fragments.add(fragment);
        if (isWizardMode) {
            setOffScreenPageLimit(fragments.size());
        }
        mPagerAdapter.notifyDataSetChanged();
    }

    public boolean isProgressButtonEnabled() {
        return progressButtonEnabled;
    }

    /**
     * Setting to to display or hide the Next or Done button. This is a static setting and
     * button state is maintained across slides until explicitly changed.
     *
     * @param progressButtonEnabled Set true to display. False to hide.
     */
    public void setProgressButtonEnabled(boolean progressButtonEnabled) {

        this.progressButtonEnabled = progressButtonEnabled;
        if (progressButtonEnabled) {

            if ((!isRtl() && pager.getCurrentItem() == slidesNumber - 1) || (isRtl() && pager.getCurrentItem() == 0)) {
                setButtonState(nextButton, false);
                setButtonState(doneButton, true);
                if (isWizardMode) {
                    setButtonState(backButton, showBackButtonWithDone);
                } else {
                    setButtonState(skipButton, false);
                }

            } else {
                setButtonState(nextButton, true);
                setButtonState(doneButton, false);
                if (isWizardMode) {
                    if (pager.getCurrentItem() == 0) {
                        setButtonState(backButton, false);
                    } else {
                        setButtonState(backButton, isWizardMode);
                    }
                } else {
                    setButtonState(skipButton, skipButtonEnabled);
                }
            }
        } else {
            setButtonState(nextButton, false);
            setButtonState(doneButton, false);
            setButtonState(backButton, false);
            setButtonState(skipButton, false);

        }
    }

    public void setOffScreenPageLimit(int limit) {
        pager.setOffscreenPageLimit(limit);
    }

    /**
     * @param savedInstanceState Null
     * @deprecated It is strongly recommended to use {@link #onCreate(Bundle)} instead. Be sure calling super.onCreate() in your method.
     * Please note that this method WILL NOT be called when the activity gets recreated i.e. the fragment instances get restored.
     * The method will only be called when there are no fragments registered to the intro at all.
     */
    public void init(@Nullable Bundle savedInstanceState) {

    }

    /**
     * Called when the user clicked the next button which triggered a fragment change
     *
     * @deprecated Obsolete, use {@link #onSlideChanged(Fragment, Fragment)} instead
     */
    public void onNextPressed() {

    }

    /**
     * Called when the user clicked the done button
     *
     * @deprecated Override {@link #onDonePressed(Fragment)} instead
     */
    public void onDonePressed() {

    }

    /**
     * Called when the user clicked the skip button
     *
     * @deprecated Override {@link #onSkipPressed(Fragment)} instead
     */
    public void onSkipPressed() {

    }

    /**
     * Called when the selected fragment changed
     *
     * @deprecated Override {@link #onSlideChanged(Fragment, Fragment)} instead
     */
    public void onSlideChanged() {

    }

    /**
     * Called when the user clicked the done button
     *
     * @param currentFragment Instance of the currently displayed fragment
     */
    public void onDonePressed(Fragment currentFragment) {
        onDonePressed();
    }

    /**
     * Called when the selected fragment changed. This will be called automatically if the into starts or is finished via the done button.
     *
     * @param oldFragment Instance of the fragment which was displayed before. This might be null if the the intro has just started.
     * @param newFragment Instance of the fragment which is displayed now. This might be null if the intro has finished
     */
    public void onSlideChanged(@Nullable Fragment oldFragment, @Nullable Fragment newFragment) {
        onSlideChanged();
    }

    @Override
    public boolean onKeyDown(int code, KeyEvent event) {
        if (code == KeyEvent.KEYCODE_ENTER || code == KeyEvent.KEYCODE_BUTTON_A ||
                code == KeyEvent.KEYCODE_DPAD_CENTER) {
            ViewPager vp = (ViewPager) this.findViewById(R.id.view_pager);
            if (vp.getCurrentItem() == vp.getAdapter().getCount() - 1) {
                onDonePressed(fragments.get(vp.getCurrentItem()));
            } else {
                vp.setCurrentItem(vp.getCurrentItem() + 1);
            }

            return false;
        }

        return super.onKeyDown(code, event);
    }

    /**
     * Allows the user to set the nav bar color of their app intro
     *
     * @param Color string form of color in 3 or 6 digit hex form (#ffffff)
     */
    public void setNavBarColor(String Color) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().setNavigationBarColor(android.graphics.Color.parseColor(Color));
        }
    }

    /**
     * Allows the user to set the nav bar color of their app intro
     *
     * @param color int form of color. pass your color resource to here (R.color.your_color)
     */
    public void setNavBarColor(@ColorRes int color) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().setNavigationBarColor(ContextCompat.getColor(this, color));
        }
    }

    /**
     * Allows for setting statusbar visibility (true by default)
     *
     * @param isVisible put true to show status bar, and false to hide it
     */
    public void showStatusBar(boolean isVisible) {
        if (!isVisible) {
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN);
        } else {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }
    }

    /**
     * sets vibration when buttons are pressed
     *
     * @param vibrationEnabled on/off
     */
    public void setVibrate(boolean vibrationEnabled) {
        this.isVibrateOn = vibrationEnabled;
    }

    /**
     * get the state of wizard mode
     */
    public boolean getWizardMode() {
        return isWizardMode;
    }

    /**
     * sets wizard mode
     *
     * @param wizardMode on/off
     */
    public void setWizardMode(boolean wizardMode) {
        this.isWizardMode = wizardMode;
        this.skipButtonEnabled = false;
        setButtonState(skipButton, !wizardMode);
        //setButtonState(backButton,wizardMode);
    }

    /**
     * get the state of wizard mode
     */
    public boolean getBackButtonVisibilityWithDone() {
        return isWizardMode;
    }

    /**
     * sets wizard mode
     *
     * @param show on/off
     */
    public void setBackButtonVisibilityWithDone(boolean show) {
        this.showBackButtonWithDone = show;
    }

    /**
     * sets vibration intensity
     *
     * @param intensity desired intensity
     */
    public void setVibrateIntensity(int intensity) {
        this.vibrateIntensity = intensity;
    }

    /**
     * Set a progress indicator instead of dots. This is recommended for a large amount of slides. In this case there
     * could not be enough space to display all dots on smaller device screens.
     */
    public void setProgressIndicator() {
        mController = new ProgressIndicatorController();
    }

    /**
     * Set a custom {@link IndicatorController} to use a custom indicator view for the {@link AppIntro} instead of the
     * default one.
     *
     * @param controller The controller to use
     */
    public void setCustomIndicator(@NonNull IndicatorController controller) {
        mController = controller;
    }

    /**
     * Sets whether color transitions between slides should be enabled.
     * Please note, that all slides have to implement ISlideBackgroundColorHolder.
     *
     * @param colorTransitionsEnabled Whether color transitions should be enabled
     */
    public void setColorTransitionsEnabled(boolean colorTransitionsEnabled) {
        areColorTransitionsEnabled = colorTransitionsEnabled;
    }

    /**
     * Sets the animation of the intro to a fade animation
     */
    public void setFadeAnimation() {
        pager.setPageTransformer(true, new ViewPageTransformer(ViewPageTransformer.TransformType.FADE));
    }

    /**
     * Sets the animation of the intro to a zoom animation
     */
    public void setZoomAnimation() {
        pager.setPageTransformer(true, new ViewPageTransformer(ViewPageTransformer.TransformType.ZOOM));
    }

    /**
     * Sets the animation of the intro to a flow animation
     */
    public void setFlowAnimation() {
        pager.setPageTransformer(true, new ViewPageTransformer(ViewPageTransformer.TransformType.FLOW));
    }

    /**
     * Sets the animation of the intro to a Slide Over animation
     */
    public void setSlideOverAnimation() {
        pager.setPageTransformer(true, new ViewPageTransformer(ViewPageTransformer.TransformType.SLIDE_OVER));
    }

    /**
     * Sets the animation of the intro to a Depth animation
     */
    public void setDepthAnimation() {
        pager.setPageTransformer(true, new ViewPageTransformer(ViewPageTransformer.TransformType.DEPTH));
    }

    /**
     * Overrides viewpager transformer
     *
     * @param transformer your custom transformer
     */
    public void setCustomTransformer(@Nullable ViewPager.PageTransformer transformer) {
        pager.setPageTransformer(true, transformer);
    }

    /**
     * Overrides color of selected and unselected indicator colors
     * <p>
     * Set DEFAULT_COLOR for color value if you don't want to change it
     *
     * @param selectedIndicatorColor   your selected color
     * @param unselectedIndicatorColor your unselected color
     */
    public void setIndicatorColor(int selectedIndicatorColor, int unselectedIndicatorColor) {
        this.selectedIndicatorColor = selectedIndicatorColor;
        this.unselectedIndicatorColor = unselectedIndicatorColor;

        if (mController != null) {
            if (selectedIndicatorColor != DEFAULT_COLOR)
                mController.setSelectedIndicatorColor(selectedIndicatorColor);
            if (unselectedIndicatorColor != DEFAULT_COLOR)
                mController.setUnselectedIndicatorColor(unselectedIndicatorColor);
        }
    }

    /**
     * Setting to disable forward swiping right on current page and allow swiping left. If a swipe
     * left occurs, the lock state is reset and swiping is re-enabled. (one shot disable) This also
     * hides/shows the Next and Done buttons accordingly.
     *
     * @param lockEnable Set true to disable forward swiping. False to enable.
     */
    public void setNextPageSwipeLock(boolean lockEnable) {
        if (lockEnable) {
            // if locking, save current progress button visibility
            baseProgressButtonEnabled = progressButtonEnabled;
            setProgressButtonEnabled(false);
        } else {
            // if unlocking, restore original button visibility
            setProgressButtonEnabled(baseProgressButtonEnabled);
        }
        pager.setNextPagingEnabled(!lockEnable);
    }

    /**
     * Setting to disable swiping left and right on current page. This also
     * hides/shows the Next and Done buttons accordingly.
     *
     * @param lockEnable Set true to disable forward swiping. False to enable.
     */
    public void setSwipeLock(boolean lockEnable) {
        if (lockEnable) {
            // if locking, save current progress button visibility
            baseProgressButtonEnabled = progressButtonEnabled;
            //setProgressButtonEnabled(!lockEnable);
        } else {
            // if unlocking, restore original button visibility
            setProgressButtonEnabled(baseProgressButtonEnabled);
        }
        pager.setPagingEnabled(!lockEnable);
    }

    /**
     * Enables/Disables leaving the intro via the device's software/hardware back button.
     * Note, that does does NOT lock swiping back through the slides.
     *
     * @param lockEnabled Whether leaving the intro via the phone's back button is locked.
     */
    public void setGoBackLock(boolean lockEnabled) {
        isGoBackLockEnabled = lockEnabled;
    }

    /**
     * Specifies whether to enable the non-sticky immersive mode.
     * This is a shorthand method for {@link #setImmersiveMode(boolean, boolean)} the second parameter set to false.
     * If you want to enable the sticky immersive mode (transcluent bars), use {@link #setImmersiveMode(boolean, boolean)} instead.
     * Note that immersive mode is only supported on Kitkat and newer.
     *
     * @param isEnabled Whether the immersive mode (non-sticky) should be enabled or not.
     */
    public void setImmersiveMode(boolean isEnabled) {
        setImmersiveMode(isEnabled, false);
    }

    /**
     * Specifies whether to enable the immersive mode.
     * Note that immersive mode is only supported on Kitkat and newer.
     *
     * @param isEnabled Whether the immersive mode should be enabled or not.
     * @param isSticky  Whether to use the sticky immersive mode or not
     */
    public void setImmersiveMode(boolean isEnabled, boolean isSticky) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (!isEnabled && isImmersiveModeEnabled) {
                getWindow().getDecorView().setSystemUiVisibility(
                        View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);

                isImmersiveModeEnabled = false;
            } else if (isEnabled) {

                int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN;

                if (isSticky) {
                    flags |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
                    isImmersiveModeSticky = true;
                } else {
                    flags |= View.SYSTEM_UI_FLAG_IMMERSIVE;
                    isImmersiveModeSticky = false;
                }

                getWindow().getDecorView().setSystemUiVisibility(flags);

                isImmersiveModeEnabled = true;
            }
        }
    }

    public void askForPermissions(String[] permissions, int slidesNumber) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (slidesNumber == 0) {
                Toast.makeText(getBaseContext(), "Invalid Slide Number", Toast.LENGTH_SHORT).show();
            } else {
                PermissionObject permission = new PermissionObject(permissions, slidesNumber);
                permissionsArray.add(permission);
                setSwipeLock(true);
            }
        }
    }

    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        switch (requestCode) {
            case PERMISSIONS_REQUEST_ALL_PERMISSIONS:
                if (isRtl()) {
                    pager.setCurrentItem(pager.getCurrentItem() - 1);
                } else {
                    pager.setCurrentItem(pager.getCurrentItem() + 1);
                }
                break;
            default:
                LogHelper.e(TAG, "Unexpected request code");
        }
    }

    protected boolean isRtl() {
        return AppIntroViewPager.isRtl(getResources());
    }

    private final class NextButtonOnClickListener implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            if (isVibrateOn) {
                mVibrator.vibrate(vibrateIntensity);
            }

            boolean isSlideChangingAllowed = handleBeforeSlideChanged();

            // Check if changing to the next slide is allowed
            if (isSlideChangingAllowed) {

                boolean requestPermission = false;
                int position = 0;

                for (int i = 0; i < permissionsArray.size(); i++) {
                    requestPermission =
                            pager.getCurrentItem() + 1 == permissionsArray.get(i).getPosition();
                    position = i;
                    break;
                }

                if (requestPermission) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        requestPermissions(permissionsArray.get(position).getPermission(),
                                PERMISSIONS_REQUEST_ALL_PERMISSIONS);
                        permissionsArray.remove(position);
                    } else {
                        pager.goToNextSlide();
                        onNextPressed();
                    }
                } else {
                    pager.goToNextSlide();
                    onNextPressed();
                }
            } else {
                handleIllegalSlideChangeAttempt();
            }
        }
    }

    public class PagerOnPageChangeListener implements ViewPager.OnPageChangeListener {

        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            if (areColorTransitionsEnabled) {
                if (position < mPagerAdapter.getCount() - 1) {
                    if (mPagerAdapter.getItem(position) instanceof ISlideBackgroundColorHolder &&
                            mPagerAdapter.getItem(position + 1) instanceof ISlideBackgroundColorHolder) {
                        Fragment currentSlide = mPagerAdapter.getItem(position);
                        Fragment nextSlide = mPagerAdapter.getItem(position + 1);

                        ISlideBackgroundColorHolder currentSlideCasted =
                                (ISlideBackgroundColorHolder) currentSlide;
                        ISlideBackgroundColorHolder nextSlideCasted =
                                (ISlideBackgroundColorHolder) nextSlide;

                        // Check if both fragments are attached to an activity,
                        // otherwise getDefaultBackgroundColor may fail.
                        if (currentSlide.isAdded() && nextSlide.isAdded()) {
                            int newColor = (int) argbEvaluator.evaluate(positionOffset,
                                    currentSlideCasted.getDefaultBackgroundColor(),
                                    nextSlideCasted.getDefaultBackgroundColor());

                            currentSlideCasted.setBackgroundColor(newColor);
                            nextSlideCasted.setBackgroundColor(newColor);
                        }
                    } else {
                        throw new IllegalStateException("Color transitions are only available if all slides implement ISlideBackgroundColorHolder.");
                    }
                }
            }
        }

        @Override
        public void onPageSelected(int position) {
            if (slidesNumber > 1)
                mController.selectPosition(position);

            // Allow the swipe to be re-enabled if a user swipes to a previous slide. Restore
            // state of progress button depending on global progress button setting
            if (!pager.isNextPagingEnabled()) {
                if (pager.getCurrentItem() != pager.getLockPage()) {
                    setProgressButtonEnabled(baseProgressButtonEnabled);
                    pager.setNextPagingEnabled(true);
                } else {
                    setProgressButtonEnabled(progressButtonEnabled);
                }
            } else {
                setProgressButtonEnabled(progressButtonEnabled);
            }

            AppIntroBase.this.onPageSelected(position);

            if (slidesNumber > 0) {
                if (currentlySelectedItem == -1) {
                    handleSlideChanged(null, mPagerAdapter.getItem(position));
                } else {
                    handleSlideChanged(mPagerAdapter.getItem(currentlySelectedItem),
                            mPagerAdapter.getItem(pager.getCurrentItem()));
                }
            }
            currentlySelectedItem = position;
        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    }

    private final class WindowGestureListener extends GestureDetector.SimpleOnGestureListener {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            if (isImmersiveModeEnabled && !isImmersiveModeSticky) {
                setImmersiveMode(true, false);
            }

            return false;
        }
    }
}
