package com.heinrichreimersoftware.materialintro.app;

import android.animation.ArgbEvaluator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.TypedValue;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;

import com.heinrichreimersoftware.materialintro.R;
import com.heinrichreimersoftware.materialintro.slide.Slide;
import com.heinrichreimersoftware.materialintro.slide.SlideAdapter;
import com.heinrichreimersoftware.materialintro.util.AnimUtils;
import com.heinrichreimersoftware.materialintro.util.CheatSheet;
import com.heinrichreimersoftware.materialintro.view.FadeableViewPager;
import com.heinrichreimersoftware.materialintro.view.InkPageIndicator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@SuppressLint("Registered")
public class IntroActivity extends AppCompatActivity {
    private static final String KEY_CURRENT_ITEM =
            "com.heinrichreimersoftware.materialintro.app.IntroActivity.KEY_CURRENT_ITEM";

    private static final String KEY_SLIDES =
            "com.heinrichreimersoftware.materialintro.app.IntroActivity.KEY_SLIDES";

    private final ArgbEvaluator evaluator = new ArgbEvaluator();
    private FrameLayout frame;
    private FadeableViewPager pager;
    private InkPageIndicator pagerIndicator;
    private ImageButton buttonNext;
    private ImageButton buttonSkip;
    private SlideAdapter adapter;
    private IntroPageChangeListener listener = new IntroPageChangeListener();
    private boolean fullscreen = false;
    private boolean skipEnabled = true;
    private boolean finishEnabled = true;
    private boolean allowFinish = true;
    private int position = 0;
    private float positionOffset = 0;
    private boolean canSwipe = true;

    private Runnable runWhenFinish;

    private SlideListener slideListener;

    private NavigationPolicy navigationPolicy = null;

    private List<OnNavigationBlockedListener> navigationBlockedListeners = new ArrayList<>();

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

        if (fullscreen && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            setSystemUiFlags(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN, true);
            setFullscreenFlags(fullscreen);
        }

        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_ITEM)) {
            position = savedInstanceState.getInt(KEY_CURRENT_ITEM, position);
        }

        setContentView(R.layout.activity_intro);
        findViews();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        updateTaskDescription();
        updateBackground();
        updateTaskDescription();
        updateButtonNextDrawable();
        updateButtonSkipDrawable();
        frame.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
            @Override
            public void onLayoutChange(View v, int left, int top, int right, int bottom,
                                       int oldLeft, int oldTop, int oldRight, int oldBottom) {
                updateViewPositions();
                v.removeOnLayoutChangeListener(this);
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        setFullscreenFlags(fullscreen);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(KEY_CURRENT_ITEM, pager.getCurrentItem());
    }

    @Override
    public void onBackPressed() {
        if (position > 0) {
            previousSlide();
            return;
        }
        setResult(RESULT_CANCELED);
        super.onBackPressed();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        for (Fragment fragment : fragmentManager.getFragments()) {
            if (fragment != null) {
                fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    }

    private void setSystemUiFlags(int flags, boolean value){
        int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
        if (value) {
            systemUiVisibility |= flags;
        } else {
            systemUiVisibility &= ~flags;
        }
        getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
    }

    private void setFullscreenFlags(boolean fullscreen){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            int fullscreenFlags = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
                    View.SYSTEM_UI_FLAG_FULLSCREEN;

            setSystemUiFlags(fullscreenFlags, fullscreen);
        }
    }

    public void setAllowFinish(boolean allowFinish) {
        this.allowFinish = allowFinish;
        return;
    }

    public void setRunWhenFinish(Runnable runWhenFinish) {
        this.runWhenFinish = runWhenFinish;
        return;
    }

    public void setSlideListener(SlideListener slideListener) {
        this.slideListener = slideListener;
        return;
    }

    private void findViews(){
        frame = (FrameLayout) findViewById(R.id.mi_frame);
        pager = (FadeableViewPager) findViewById(R.id.mi_pager);
        pagerIndicator = (InkPageIndicator) findViewById(R.id.mi_pager_indicator);
        buttonNext = (ImageButton) findViewById(R.id.mi_button_next);
        buttonSkip = (ImageButton) findViewById(R.id.mi_button_skip);

        FragmentManager fragmentManager = getSupportFragmentManager();
        adapter = new SlideAdapter(fragmentManager);

        pager.setAdapter(adapter);
        pager.addOnPageChangeListener(listener);
        pager.setCurrentItem(position);

        pagerIndicator.setViewPager(pager);

        buttonNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                nextSlide();
            }
        });
        buttonSkip.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                skipIfEnabled();
            }
        });
        CheatSheet.setup(buttonNext);
        CheatSheet.setup(buttonSkip);
    }

    public void nextSlide() {
        int currentItem = pager.getCurrentItem();
        if (currentItem == pager.getAdapter().getCount() - 1) {
            position += 1;
            finishIfNeeded();
            return;
        }
        if (canGoForward(currentItem, true)) {
            if (this.slideListener != null)
                slideListener.willLeaveSlide(currentItem);
            pager.setCurrentItem(++currentItem, true);
        }
        else {
            AnimUtils.applyShakeAnimation(this, buttonNext);

        }
    }

    public void previousSlide() {
        int currentItem = pager.getCurrentItem();

        if (canGoBackward(currentItem, true)) {
            if (this.slideListener != null)
                slideListener.willLeaveSlide(currentItem);
            pager.setCurrentItem(--currentItem, true);
        }
        else {
            AnimUtils.applyShakeAnimation(this, buttonSkip);

        }
    }

    private void skipIfEnabled() {
        if (skipEnabled) {
            int count = getCount();
            int endPosition = pager.getCurrentItem();
            while (endPosition < count && canGoForward(endPosition, true)) {
                endPosition++;
            }
            pager.setCurrentItem(endPosition);
        } else {
            previousSlide();
        }
    }

    private boolean canGoForward(int position, boolean notifyListeners) {
        boolean canGoForward = (navigationPolicy == null || navigationPolicy.canGoForward(position)) &&
                getSlide(position).canGoForward();
        if (!canGoForward && notifyListeners) {
            for (OnNavigationBlockedListener listener : navigationBlockedListeners) {
                listener.onNavigationBlocked(position, OnNavigationBlockedListener.DIRECTION_FORWARD);
            }
        }
        return canGoForward;
    }

    private boolean canGoBackward(int position, boolean notifyListeners) {
        boolean canGoBackward = (navigationPolicy == null || navigationPolicy.canGoBackward(position)) &&
                getSlide(position).canGoBackward();
        if (!canGoBackward && notifyListeners) {
            for (OnNavigationBlockedListener listener : navigationBlockedListeners) {
                listener.onNavigationBlocked(position, OnNavigationBlockedListener.DIRECTION_BACKWARD);
            }
        }
        return canGoBackward;
    }

    private void finishIfNeeded() {
        if (positionOffset == 0 && position == adapter.getCount()) {
            if (allowFinish) {
                setResult(RESULT_OK);
                finish();
                overridePendingTransition(0, 0);
            } else {
                if (runWhenFinish != null) {
                    runWhenFinish.run();
                }
            }

        }
    }

    private void updateFullscreen() {
        if (position + positionOffset > adapter.getCount() - 1) {
            setFullscreenFlags(false);
        } else {
            setFullscreenFlags(fullscreen);
        }
    }

    private void updateTaskDescription() {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            String title = getTitle().toString();
            Drawable iconDrawable = getApplicationInfo().loadIcon(getPackageManager());
            Bitmap icon = iconDrawable instanceof BitmapDrawable ? ((BitmapDrawable) iconDrawable).getBitmap() : null;
            int colorPrimary;
            if (position < getCount()) {
                try {
                    colorPrimary = ContextCompat.getColor(IntroActivity.this, getBackgroundDark(position));
                } catch (Resources.NotFoundException e) {
                    colorPrimary = ContextCompat.getColor(IntroActivity.this, getBackground(position));
                }
            }
            else {
                TypedValue typedValue = new TypedValue();
                TypedArray a = obtainStyledAttributes(typedValue.data, new int[]{R.attr.colorPrimary});
                colorPrimary = a.getColor(0, 0);
                a.recycle();
            }
            colorPrimary = ColorUtils.setAlphaComponent(colorPrimary, 0xFF);

            setTaskDescription(new ActivityManager.TaskDescription(title, icon, colorPrimary));
        }
    }

    private void updateBackground() {
        int background;
        int backgroundNext;
        int backgroundDark;
        int backgroundDarkNext;

        if (position == getCount()) {
            background = Color.TRANSPARENT;
            backgroundNext = Color.TRANSPARENT;
            backgroundDark = Color.TRANSPARENT;
            backgroundDarkNext = Color.TRANSPARENT;
        } else {
            background = ContextCompat.getColor(IntroActivity.this,
                    getBackground(position));
            backgroundNext = ContextCompat.getColor(IntroActivity.this,
                    getBackground(Math.min(position + 1, getCount() - 1)));

            background = ColorUtils.setAlphaComponent(background, 0xFF);
            backgroundNext = ColorUtils.setAlphaComponent(backgroundNext, 0xFF);

            try {
                backgroundDark = ContextCompat.getColor(IntroActivity.this,
                        getBackgroundDark(position));
            } catch (Resources.NotFoundException e) {
                backgroundDark = ContextCompat.getColor(IntroActivity.this,
                        R.color.mi_status_bar_background);
            }
            try {
                backgroundDarkNext = ContextCompat.getColor(IntroActivity.this,
                        getBackgroundDark(Math.min(position + 1, getCount() - 1)));
            } catch (Resources.NotFoundException e) {
                backgroundDarkNext = ContextCompat.getColor(IntroActivity.this,
                        R.color.mi_status_bar_background);
            }
        }

        if (position + positionOffset >= adapter.getCount() - 1) {
            backgroundNext = ColorUtils.setAlphaComponent(background, 0x00);
            backgroundDarkNext = Color.TRANSPARENT;
        }

        background = (Integer) evaluator.evaluate(positionOffset, background, backgroundNext);
        backgroundDark = (Integer) evaluator.evaluate(positionOffset, backgroundDark, backgroundDarkNext);

        frame.setBackgroundColor(background);

        float[] backgroundDarkHsv = new float[3];
        Color.colorToHSV(backgroundDark, backgroundDarkHsv);
        //Slightly darken the background color a bit for more contrast
        backgroundDarkHsv[2] *= 0.95;
        int backgroundDarker = Color.HSVToColor(backgroundDarkHsv);
        pagerIndicator.setPageIndicatorColor(backgroundDarker);
        ViewCompat.setBackgroundTintList(buttonNext, ColorStateList.valueOf(backgroundDarker));
        ViewCompat.setBackgroundTintList(buttonSkip, ColorStateList.valueOf(backgroundDarker));

        int iconColor;
        if (ColorUtils.calculateLuminance(backgroundDark) > 0.4) {
            //Light background
            iconColor = ContextCompat.getColor(this, R.color.mi_icon_color_light);
        } else {
            //Dark background
            iconColor = ContextCompat.getColor(this, R.color.mi_icon_color_dark);
        }
        pagerIndicator.setCurrentPageIndicatorColor(iconColor);
        DrawableCompat.setTint(buttonNext.getDrawable(), iconColor);
        DrawableCompat.setTint(buttonSkip.getDrawable(), iconColor);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().setStatusBarColor(backgroundDark);

            if (position == adapter.getCount()) {
                getWindow().setNavigationBarColor(Color.TRANSPARENT);
            } else if (position + positionOffset >= adapter.getCount() - 1) {
                TypedValue typedValue = new TypedValue();
                TypedArray a = obtainStyledAttributes(typedValue.data, new int[]{android.R.attr.navigationBarColor});

                int defaultNavigationBarColor = a.getColor(0, Color.BLACK);

                a.recycle();

                int navigationBarColor = (Integer) evaluator.evaluate(positionOffset, defaultNavigationBarColor, Color.TRANSPARENT);
                getWindow().setNavigationBarColor(navigationBarColor);
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                int systemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
                int flagLightStatusBar = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
                if (ColorUtils.calculateLuminance(backgroundDark) > 0.4) {
                    //Light background
                    systemUiVisibility |= flagLightStatusBar;
                } else {
                    //Dark background
                    systemUiVisibility &= ~flagLightStatusBar;
                }
                getWindow().getDecorView().setSystemUiVisibility(systemUiVisibility);
            }
        }
    }

    private void updateViewPositions() {
        if (position + positionOffset < 1) {
            //Between first and second item
            float offset = position + positionOffset;

            if (skipEnabled) {
                buttonSkip.setTranslationY(0);
            } else {
                buttonSkip.setTranslationY((1 - offset) * 2 * buttonNext.getHeight());
            }
            updateButtonNextDrawable();
        } else if (position + positionOffset >= 1 && position + positionOffset < adapter.getCount() - 2) {
            //Between second and second last item
            //Reset buttons
            buttonSkip.setTranslationY(0);
            buttonSkip.setTranslationX(0);
            buttonNext.setTranslationY(0);
            updateButtonNextDrawable();
        } else if (position + positionOffset >= adapter.getCount() - 2 && position + positionOffset < adapter.getCount() - 1) {
            //Between second last and last item
            float offset = position + positionOffset - adapter.getCount() + 2;

            if (skipEnabled) {
                boolean rtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && getResources().getConfiguration().getLayoutDirection() ==
                        View.LAYOUT_DIRECTION_RTL;
                buttonSkip.setTranslationX(offset * (rtl ? 1 : -1) * pager.getWidth());
            } else {
                buttonSkip.setTranslationX(0);
            }

            if (finishEnabled) {
                buttonNext.setTranslationY(0);
            } else {
                buttonNext.setTranslationY(offset * 2 * buttonNext.getHeight());
            }
            updateButtonNextDrawable();
        } else if (position + positionOffset >= adapter.getCount() - 1) {
            //Fade
            float offset = position + positionOffset - adapter.getCount() + 1;

            if (skipEnabled) {
                boolean rtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && getResources().getConfiguration().getLayoutDirection() ==
                        View.LAYOUT_DIRECTION_RTL;
                buttonSkip.setTranslationX((rtl ? 1 : -1) * pager.getWidth());
            } else {
                buttonSkip.setTranslationY(offset * 2 * buttonNext.getHeight());
            }

            if (finishEnabled) {
                buttonNext.setTranslationY(offset * 2 * buttonNext.getHeight());
            } else {
                buttonNext.setTranslationY(-2 * buttonNext.getHeight());
            }
            pagerIndicator.setTranslationY(offset * 2 * buttonNext.getWidth());
            updateButtonNextDrawable();
        }
    }

    private void updateButtonNextDrawable() {
        float offset = 0;
        if (finishEnabled && position + positionOffset >= adapter.getCount() - 2) {
            offset = Math.min(position + positionOffset - adapter.getCount() + 2, 1);
        }

        if (offset <= 0) {
            buttonNext.setImageResource(R.drawable.ic_next);
            buttonNext.getBackground().setAlpha(50);
        } else {
            buttonNext.setImageResource(offset > 0 ? R.drawable.ic_finish : R.drawable.ic_next);
            buttonNext.getBackground().mutate().setAlpha(50);

        }
    }

    private void updateButtonSkipDrawable() {
        if (skipEnabled) {
            buttonSkip.setImageResource(R.drawable.ic_skip);
        } else {
            buttonSkip.setImageResource(R.drawable.ic_previous);
            buttonSkip.getBackground().setAlpha(50);
        }
    }


    public boolean isFullscreen() {
        return fullscreen;
    }

    public void setFullscreen(boolean fullscreen) {
        this.fullscreen = fullscreen;
    }

    public boolean isSkipEnabled() {
        return skipEnabled;
    }

    public void setSkipEnabled(boolean skipEnabled) {
        this.skipEnabled = skipEnabled;
        updateButtonSkipDrawable();
        updateViewPositions();
    }

    public boolean isFinishEnabled() {
        return finishEnabled;
    }

    public void setFinishEnabled(boolean finishEnabled) {
        this.finishEnabled = finishEnabled;
        updateButtonNextDrawable();
        updateViewPositions();
    }

    @SuppressWarnings("deprecation")
    @Deprecated
    public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
        pager.setOnPageChangeListener(listener);
        pager.addOnPageChangeListener(this.listener);
    }

    public void addOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
        pager.addOnPageChangeListener(listener);
    }

    public void removeOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
        if (listener != this.listener)
            pager.removeOnPageChangeListener(listener);
    }


    protected void addSlide(int location, Slide object) {
        adapter.addSlide(location, object);
    }

    protected boolean addSlide(Slide object) {
        boolean ret = adapter.addSlide(object);
        adapter.notifyDataSetChanged();
        return ret;
    }

    protected boolean addSlides(int location, @NonNull Collection<? extends Slide> collection) {
        return adapter.addSlides(location, collection);
    }

    protected boolean addSlides(@NonNull Collection<? extends Slide> collection) {
        return adapter.addSlides(collection);
    }

    protected void clearSlides() {
        adapter.clearSlides();
    }

    protected boolean containsSlide(Object object) {
        return adapter.containsSlide(object);
    }

    protected boolean containsSlides(@NonNull Collection<?> collection) {
        return adapter.containsSlides(collection);
    }

    protected Slide getSlide(int location) {
        return adapter.getSlide(location);
    }

    protected Fragment getItem(int position) {
        return adapter.getItem(position);
    }

    @ColorRes
    protected int getBackground(int position) {
        return adapter.getBackground(position);
    }

    @ColorRes
    protected int getBackgroundDark(int position) {
        return adapter.getBackgroundDark(position);
    }

    protected List<Slide> getSlides() {
        return adapter.getSlides();
    }

    protected int indexOfSlide(Object object) {
        return adapter.indexOfSlide(object);
    }

    protected boolean isEmpty() {
        return adapter.isEmpty();
    }

    protected int getCount() {
        return adapter == null ? 0 : adapter.getCount();
    }

    protected int lastIndexOfSlide(Object object) {
        return adapter.lastIndexOfSlide(object);
    }

    protected Slide removeSlide(int location) {
        return adapter.removeSlide(location);
    }

    protected boolean removeSlide(Object object) {
        boolean ret =  adapter.removeSlide(object);
        adapter.notifyDataSetChanged();
        return ret;
    }

    protected boolean removeSlides(@NonNull Collection<?> collection) {
        return adapter.removeSlides(collection);
    }

    protected boolean retainSlides(@NonNull Collection<?> collection) {
        return adapter.retainSlides(collection);
    }

    protected Slide setSlide(int location, Slide object) {
        return adapter.setSlide(location, object);
    }

    protected List<Slide> setSlides(List<? extends Slide> list) {
        return adapter.setSlides(list);
    }


    private class IntroPageChangeListener extends FadeableViewPager.SimpleOnOverscrollPageChangeListener {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
            IntroActivity.this.position = position;
            IntroActivity.this.positionOffset = positionOffset;

            //Lock while scrolling a slide near its edges to lock (uncommon) multiple page swipes
            if (Math.abs(positionOffset) < 0.1f) {
                lockSwipeIfNeeded();
            }

            updateBackground();
            updateViewPositions();
            updateFullscreen();

            finishIfNeeded();
        }

        @Override
        public void onPageSelected(int position) {
            IntroActivity.this.position = position;
            updateTaskDescription();
            lockSwipeIfNeeded();
        }
    }

    public void setNavigationPolicy(NavigationPolicy navigationPolicy) {
        this.navigationPolicy = navigationPolicy;
    }

    public void addOnNavigationBlockedListener(OnNavigationBlockedListener listener) {
        navigationBlockedListeners.add(listener);
    }

    public void removeOnNavigationBlockedListener(OnNavigationBlockedListener listener) {
        navigationBlockedListeners.remove(listener);
    }

    public void clearOnNavigationBlockedListeners() {
        navigationBlockedListeners.clear();
    }

    public IntroActivity setCanSwipe(boolean canSwipe) {
        this.canSwipe = canSwipe;
        return this;
    }

    public void lockSwipeIfNeeded() {
        if (!this.canSwipe) {
            pager.setSwipeLeftEnabled(false);
            pager.setSwipeRightEnabled(false);
            return;
        }
        if (position < getCount()) {
            pager.setSwipeLeftEnabled(canGoForward(position, false));
            pager.setSwipeRightEnabled(canGoBackward(position, false));
        }
    }
}