package net.squanchy.support.widget;

import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;
import android.support.annotation.Nullable;
import android.support.design.internal.BottomNavigationMenuView;
import android.support.design.widget.BottomNavigationView;
import android.util.AttributeSet;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.hadisatrio.optional.Optional;

import net.squanchy.support.graphics.CircularRevealDrawable;
import net.squanchy.support.view.Hotspot;

public class InterceptingBottomNavigationView extends BottomNavigationView {

    private int revealDurationMillis;

    private Optional<MotionEvent> lastUpEvent = Optional.absent();

    @Nullable
    private OnNavigationItemSelectedListener listener;

    @Nullable
    private ColorProvider colorProvider;

    public InterceptingBottomNavigationView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public InterceptingBottomNavigationView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        super.setOnNavigationItemSelectedListener(this::onItemSelected);
        revealDurationMillis = getResources().getInteger(android.R.integer.config_shortAnimTime);
    }

    private boolean onItemSelected(MenuItem menuItem) {
        boolean itemSelected = true;
        if (listener != null) {
            itemSelected = listener.onNavigationItemSelected(menuItem);
        }

        if (itemSelected && colorProvider != null) {
            startCircularReveal(colorProvider.getSelectedItemColor(), menuItem);
        }

        return itemSelected;
    }

    private void startCircularReveal(@ColorInt int color, MenuItem menuItem) {
        CircularRevealDrawable revealDrawable = (CircularRevealDrawable) getBackground();
        setBackgroundHotspot(revealDrawable, menuItem);
        revealDrawable.animateToColor(color, revealDurationMillis);
    }

    private void setBackgroundHotspot(CircularRevealDrawable revealDrawable, MenuItem menuItem) {
        lastUpEvent.ifPresentOrElse(
                motionEvent -> applyHotspot(revealDrawable, Hotspot.from(motionEvent)),
                () -> applyHotspot(revealDrawable, getHotspotFor(menuItem))
        );
    }

    private void applyHotspot(CircularRevealDrawable revealDrawable, Hotspot hotspot) {
        revealDrawable.setHotspot(hotspot.x(), hotspot.y());
    }

    private Hotspot getHotspotFor(MenuItem menuItem) {
        int selectedPosition = positionFor(menuItem);
        ViewGroup menuView = getBottomNavigationMenuView();
        View selectedItemView = menuView.getChildAt(selectedPosition);

        return Hotspot.fromCenterOf(selectedItemView)
                .offsetToParent(menuView);
    }

    @IntRange(from = 0)
    private int positionFor(MenuItem menuItem) {
        Menu menu = getMenu();
        int menuSize = menu.size();
        for (int i = 0; i < menuSize; i++) {
            int itemId = menu.getItem(i).getItemId();
            if (itemId == menuItem.getItemId()) {
                return i;
            }
        }
        throw new IllegalArgumentException("Menu item " + menuItem + " not found in the bottom bar items");
    }

    public void setRevealDurationMillis(@IntRange(from = 0) int revealDurationMillis) {
        this.revealDurationMillis = revealDurationMillis;
    }

    @Override
    public void setBackgroundColor(@ColorInt int color) {
        Drawable background = getBackground();
        if (background instanceof CircularRevealDrawable) {
            updateBackgroundColor(background, color);
        } else {
            setBackgroundColor(color);
        }
    }

    @Override
    public void setBackground(Drawable background) {
        if (background instanceof ColorDrawable) {
            Drawable currentBackground = getBackground();
            int newColor = ((ColorDrawable) background).getColor();
            updateBackgroundColor(currentBackground, newColor);
        } else {
            throw new IllegalArgumentException("Only ColorDrawables are supported");
        }
    }

    private void updateBackgroundColor(Drawable drawable, int newColor) {
        if (drawable instanceof CircularRevealDrawable) {
            ((CircularRevealDrawable) drawable).setColor(newColor);
        } else {
            super.setBackground(CircularRevealDrawable.from(newColor));
        }
    }

    public void cancelTransitions() {
        Drawable background = getBackground();
        if (background instanceof CircularRevealDrawable) {
            ((CircularRevealDrawable) background).cancelTransitions();
        }
    }

    @Override
    public void setOnNavigationItemSelectedListener(@Nullable OnNavigationItemSelectedListener listener) {
        this.listener = listener;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_UP) {
            MotionEvent motionEvent = MotionEvent.obtainNoHistory(ev);
            motionEvent.setLocation(ev.getX(), ev.getY());
            lastUpEvent = Optional.of(motionEvent);
        }
        return super.onInterceptTouchEvent(ev);
    }

    public void setColorProvider(@Nullable ColorProvider colorProvider) {
        this.colorProvider = colorProvider;
    }

    public void selectItemAt(@IntRange(from = 0) int position) {
        getMenu().getItem(position).setChecked(true);
        getBottomNavigationMenuView().updateMenuView();
    }

    private BottomNavigationMenuView getBottomNavigationMenuView() {
        return (BottomNavigationMenuView) getChildAt(0);
    }

    public interface ColorProvider {

        @ColorInt
        int getSelectedItemColor();
    }
}

@@ -819,47 +819,8 @@
  %7B%0A%0A
-    private int revealDurationMillis;%0A%0A
     
@@ -881,31 +881,16 @@
 bsent();
-%0A%0A    @Nullable
 %0A    pri
@@ -894,16 +894,25 @@
 private 
+Optional%3C
 OnNaviga
@@ -927,32 +927,33 @@
 SelectedListener
+%3E
  listener;%0A%0A    
@@ -949,24 +949,29 @@
 ener
-;%0A%0A    @Nullable
+ = Optional.absent();
 %0A   
@@ -979,16 +979,25 @@
 private 
+Optional%3C
 ColorPro
@@ -993,32 +993,33 @@
 al%3CColorProvider
+%3E
  colorProvider;%0A
@@ -1016,16 +1016,75 @@
 Provider
+ = Optional.absent();%0A%0A    private int revealDurationMillis
 ;%0A%0A    p
@@ -1639,24 +1639,28 @@
 listener
- != null
+.isPresent()
 ) %7B%0A    
@@ -1691,16 +1691,22 @@
 istener.
+get().
 onNaviga
@@ -1789,16 +1789,20 @@
 ider
- != null
+.isPresent()
 ) %7B%0A
@@ -1847,16 +1847,22 @@
 rovider.
+get().
 getSelec
@@ -5077,24 +5077,44 @@
 tener = 
+Optional.ofNullable(
 listener
 ;%0A    %7D%0A
@@ -5105,16 +5105,17 @@
 listener
+)
 ;%0A    %7D%0A
@@ -5593,16 +5593,36 @@
 vider = 
+Optional.ofNullable(
 colorPro
@@ -5626,16 +5626,17 @@
 Provider
+)
 ;%0A    %7D%0A

Use optionals instead of nullables