/*
  This file is part of Subsonic.
	Subsonic is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	Subsonic is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.
	You should have received a copy of the GNU General Public License
	along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
	Copyright 2015 (C) Scott Jackson
*/

package net.nullsum.audinaut.view;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.AdapterDataObserver;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

import net.nullsum.audinaut.R;

import static android.support.v7.widget.RecyclerView.OnScrollListener;

public class FastScroller extends LinearLayout {
	private static final String TAG = FastScroller.class.getSimpleName();
	private static final int BUBBLE_ANIMATION_DURATION = 100;
	private static final int TRACK_SNAP_RANGE = 5;

	private TextView bubble;
	private View handle;
	private RecyclerView recyclerView;
	private final ScrollListener scrollListener = new ScrollListener();
	private int height;
	private int visibleRange = -1;
	private RecyclerView.Adapter adapter;
	private AdapterDataObserver adapterObserver;
	private boolean visibleBubble = true;
	private boolean hasScrolled = false;

	private ObjectAnimator currentAnimator = null;

	public FastScroller(final Context context,final AttributeSet attrs,final int defStyleAttr) {
		super(context,attrs,defStyleAttr);
		initialise(context);
	}

	public FastScroller(final Context context) {
		super(context);
		initialise(context);
	}

	public FastScroller(final Context context,final AttributeSet attrs) {
		super(context, attrs);
		initialise(context);
	}

	private void initialise(Context context) {
		setOrientation(HORIZONTAL);
		setClipChildren(false);
		LayoutInflater inflater = LayoutInflater.from(context);
		inflater.inflate(R.layout.fast_scroller,this,true);
		bubble = (TextView)findViewById(R.id.fastscroller_bubble);
		handle = findViewById(R.id.fastscroller_handle);
		bubble.setVisibility(INVISIBLE);
		setVisibility(GONE);
	}

	@Override
	protected void onSizeChanged(int w,int h,int oldw,int oldh) {
		super.onSizeChanged(w,h,oldw,oldh);
		height = h;
		visibleRange = -1;
	}

	@Override
	public boolean onTouchEvent(@NonNull MotionEvent event) {
		final int action = event.getAction();
		switch(action)
		{
			case MotionEvent.ACTION_DOWN:
				if(event.getX() < (handle.getX() - 30)) {
					return false;
				}

				if(currentAnimator != null)
					currentAnimator.cancel();
				if(bubble.getVisibility() == INVISIBLE) {
					if(visibleBubble) {
						showBubble();
					}
				} else if(!visibleBubble) {
					hideBubble();
				}
				handle.setSelected(true);
			case MotionEvent.ACTION_MOVE:
				setRecyclerViewPosition(event.getY());
				return true;
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_CANCEL:
				handle.setSelected(false);
				hideBubble();
				return true;
		}
		return super.onTouchEvent(event);
	}

	public void attachRecyclerView(RecyclerView recyclerView) {
		this.recyclerView = recyclerView;
		recyclerView.addOnScrollListener(scrollListener);
		registerAdapter();
		visibleRange = -1;
	}
	public void detachRecyclerView() {
		recyclerView.removeOnScrollListener(scrollListener);
		recyclerView.setVerticalScrollBarEnabled(true);
		unregisterAdapter();
		recyclerView = null;
		setVisibility(View.GONE);
	}
	public boolean isAttached() {
		return recyclerView != null;
	}

	private void setRecyclerViewPosition(float y) {
		if(recyclerView != null) {
			if(recyclerView.getChildCount() == 0) {
				return;
			}

			int itemCount = recyclerView.getAdapter().getItemCount();
			float proportion = getValueInRange(0, 1f, y / (float) height);

			float targetPosFloat = getValueInRange(0, itemCount - 1, proportion * (float)itemCount);
			int targetPos = (int) targetPosFloat;

			// Immediately make sure that the target is visible
			LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
			// layoutManager.scrollToPositionWithOffset(targetPos, 0);
			View firstVisibleView = recyclerView.getChildAt(0);

			// Calculate how far through this position we are
			int columns = Math.round(recyclerView.getWidth() / firstVisibleView.getWidth());
			int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView);
			int remainder = (targetPos - firstVisiblePosition) % columns;
			float offsetPercentage = (targetPosFloat - targetPos + remainder) / columns;
			if(offsetPercentage < 0) {
				offsetPercentage = 1 + offsetPercentage;
			}
			int firstVisibleHeight = firstVisibleView.getHeight();
			if(columns > 1) {
				firstVisibleHeight += (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, GridSpacingDecoration.SPACING, firstVisibleView.getResources().getDisplayMetrics());
			}
			int offset = (int) (offsetPercentage * firstVisibleHeight);

			layoutManager.scrollToPositionWithOffset(targetPos, -offset);
			onUpdateScroll(1, 1);

			try {
				String bubbleText = null;
				RecyclerView.Adapter adapter = recyclerView.getAdapter();
				if(adapter instanceof BubbleTextGetter) {
					bubbleText = ((BubbleTextGetter) adapter).getTextToShowInBubble(targetPos);
				}

				if(bubbleText == null) {
					visibleBubble = false;
					bubble.setVisibility(View.INVISIBLE);
				} else {
					bubble.setText(bubbleText);
					bubble.setVisibility(View.VISIBLE);
					visibleBubble = true;
				}
			} catch(Exception e) {
				Log.e(TAG, "Error getting text for bubble", e);
			}
		}
	}

	private float getValueInRange(float min, float max, float value) {
		float minimum = Math.max(min, value);
		return Math.min(minimum,max);
	}

	private void setBubbleAndHandlePosition(float y) {
		int bubbleHeight = bubble.getHeight();
		int handleHeight = handle.getHeight();
		handle.setY(getValueInRange(0,height-handleHeight,(int)(y-handleHeight/2)));
		bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
	}

	private void showBubble() {
		bubble.setVisibility(VISIBLE);
		if(currentAnimator != null)
			currentAnimator.cancel();
		currentAnimator = ObjectAnimator.ofFloat(bubble,"alpha",0f,1f).setDuration(BUBBLE_ANIMATION_DURATION);
		currentAnimator.start();
	}

	private void hideBubble() {
		if(currentAnimator != null)
			currentAnimator.cancel();
		currentAnimator = ObjectAnimator.ofFloat(bubble,"alpha",1f,0f).setDuration(BUBBLE_ANIMATION_DURATION);
		currentAnimator.addListener(new AnimatorListenerAdapter(){
			@Override
			public void onAnimationEnd(Animator animation) {
				super.onAnimationEnd(animation);
				bubble.setVisibility(INVISIBLE);
				currentAnimator = null;
			}

			@Override
			public void onAnimationCancel(Animator animation) {
				super.onAnimationCancel(animation);
				bubble.setVisibility(INVISIBLE);
				currentAnimator = null;
			}
		});
		currentAnimator.start();
	}

	private void registerAdapter() {
		RecyclerView.Adapter newAdapter = recyclerView.getAdapter();
		if(newAdapter != adapter) {
			unregisterAdapter();
		}

		if(newAdapter != null) {
			adapterObserver = new AdapterDataObserver() {
				@Override
				public void onChanged() {
					visibleRange = -1;
				}

				@Override
				public void onItemRangeChanged(int positionStart, int itemCount) {
					visibleRange = -1;
				}

				@Override
				public void onItemRangeInserted(int positionStart, int itemCount) {
					visibleRange = -1;
				}

				@Override
				public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
					visibleRange = -1;
				}

				@Override
				public void onItemRangeRemoved(int positionStart, int itemCount) {
					visibleRange = -1;
				}
			};
			newAdapter.registerAdapterDataObserver(adapterObserver);
			adapter = newAdapter;
		}
	}
	private void unregisterAdapter() {
		if(adapter != null) {
			adapter.unregisterAdapterDataObserver(adapterObserver);
			adapter = null;
			adapterObserver = null;
		}
	}

	private class ScrollListener extends OnScrollListener {
		@Override
		public void onScrolled(RecyclerView rv,int dx,int dy) {
			onUpdateScroll(dx, dy);
		}
	}

	private void onUpdateScroll(int dx, int dy) {
		if(recyclerView.getWidth() == 0) {
			return;
		}
		registerAdapter();

		View firstVisibleView = recyclerView.getChildAt(0);
		if(firstVisibleView == null) {
			return;
		}
		int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView);

		int itemCount = recyclerView.getAdapter().getItemCount();
		int columns = Math.round(recyclerView.getWidth() / firstVisibleView.getWidth());
		if(visibleRange == -1) {
			visibleRange = recyclerView.getChildCount();
		}

		// Add the percentage of the item the user has scrolled past already
		float pastFirst = -firstVisibleView.getY() / firstVisibleView.getHeight() * columns;
		float position = firstVisiblePosition + pastFirst;

		// Scale this so as we move down the visible range gets added to position from 0 -> visible range
		float scaledVisibleRange = position / (float) (itemCount - visibleRange) * visibleRange;
		position += scaledVisibleRange;

		float proportion = position / itemCount;
		setBubbleAndHandlePosition(height * proportion);

		if((visibleRange * 2) < itemCount) {
			if (!hasScrolled && (dx > 0 || dy > 0)) {
				setVisibility(View.VISIBLE);
				hasScrolled = true;
				recyclerView.setVerticalScrollBarEnabled(false);
			}
		} else if(hasScrolled) {
			setVisibility(View.GONE);
			hasScrolled = false;
			recyclerView.setVerticalScrollBarEnabled(true);
		}
	}

	public interface BubbleTextGetter {
		String getTextToShowInBubble(int position);
	}
}
