package com.vaguehope.onosendai.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;

import com.vaguehope.onosendai.C;
import com.vaguehope.onosendai.model.TweetListCursorAdapter;

public final class ScrollIndicator {

	private static final double MAX_HEIGHT_RELATIVE = 0.95d;
	private static final double POSITION_MAX_HEIGHT = C.DATA_TW_MAX_COL_ENTRIES;
	private static final double ACCEL_BASE = 12d; // degree of non-linear effect.
	private static final double LOG_ACCEL_BASE = Math.log(ACCEL_BASE);
	/**
	 * Factored out for efficiency.  The JIT might do this, but might as well be sure.
	 */
	private static final double SCALE_CONSTANT_THING = 1d / (Math.exp(MAX_HEIGHT_RELATIVE * LOG_ACCEL_BASE) - 1);

	private static final int ACCEL_STOP_1 = 5;
	private static final int ACCEL_STOP_2 = 15;
	private static final int ACCEL_STOP_3 = 100;

	private static final int BAR_WIDTH_DIP = 7;
	private static final int COLOUR_BAR = Color.GRAY;
	private static final int COLOUR_UNREAD = Color.parseColor("#268bd2"); // Solarized Blue.

	private final BarMover barMover;

	private ScrollIndicator (final BarMover barMover) {
		this.barMover = barMover;
	}

	public void setUnreadTime (final long unreadTime) {
		this.barMover.setUnreadTime(unreadTime);
	}

	public long getUnreadTime () {
		return this.barMover.getUnreadTime();
	}

	public static ScrollIndicator attach (final Context context, final ViewGroup rootView, final ListView list, final OnScrollListener onScrollListener) {
		final AbsoluteShape bar = new AbsoluteShape(context, list);
		bar.layoutForce(0, 0, 0, 0);
		bar.setBackgroundColor(COLOUR_BAR);
		bar.bringToFront();
		rootView.addView(bar);

		final BarMover barMovingScrollListener = new BarMover(bar, list, (int) dipToPixels(context, BAR_WIDTH_DIP), onScrollListener);
		list.setOnScrollListener(barMovingScrollListener);

		return new ScrollIndicator(barMovingScrollListener);
	}

	private static float dipToPixels (final Context context, final int a) {
		return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, a, context.getResources().getDisplayMetrics());
	}

	/**
	 * @deprecated Replaced by barHeightRelative() which is relative to screen size.
	 */
	@Deprecated
	protected static int barHeightPx (final int position, final float pxPerUnit) {
		return (int) ((position
				+ Math.min(position, ACCEL_STOP_1)
				+ Math.min(position, ACCEL_STOP_2)
				+ Math.min(position, ACCEL_STOP_3)) * pxPerUnit);
	}

	protected static double barHeightRelative (final int position) {
		if (position == 0) return 0d;
		return Math.log(((position / POSITION_MAX_HEIGHT) / SCALE_CONSTANT_THING) + 1d) / LOG_ACCEL_BASE;
	}

	protected static int barHeightPx (final int position, final View view) {
		return (int) (barHeightRelative(position) * view.getHeight());
	}

	private static class AbsoluteShape extends View {

		private final ListView list;
		private final Rect unreadRect;
		private final Paint unreadPaint;
		private int unreadPosition;

		public AbsoluteShape (final Context context, final ListView list) {
			super(context);
			this.list = list;
			this.unreadRect = new Rect(0, 0, 1, 1);
			this.unreadPaint = new Paint(0);
			this.unreadPaint.setColor(COLOUR_UNREAD);
		}

		public void setUnreadPosition (final int unreadPosition) {
			this.unreadPosition = unreadPosition;
			invalidate();
		}

		@Override
		public void layout (final int l, final int t, final int r, final int b) {
			/* block calls so no layout manager can not mess with us. */
		}

		public void layoutForce (final int l, final int t, final int r, final int b) {
			super.layout(l, t, r, b);
		}

		@Override
		protected void onSizeChanged (final int w, final int h, final int oldw, final int oldh) {
			this.unreadRect.right = w;
		}

		@Override
		protected void onDraw (final Canvas canvas) {
			super.onDraw(canvas);
			this.unreadRect.bottom = barHeightPx(this.unreadPosition, this.list);
			canvas.drawRect(this.unreadRect, this.unreadPaint);
		}

	}

	private static class BarMover implements OnScrollListener {

		private final AbsoluteShape bar;
		private final ListView list;
		private final TweetListCursorAdapter listAdaptor;
		private final int barWidth;
		private final OnScrollListener delagate;

		private int lastPosition = -1;
		private long unreadTime = -1;

		public BarMover (final AbsoluteShape bar, final ListView list, final int barWidth, final OnScrollListener delagate) {
			this.bar = bar;
			this.list = list;
			this.listAdaptor = (TweetListCursorAdapter) list.getAdapter();
			this.barWidth = barWidth;
			this.delagate = delagate;
		}

		public void setUnreadTime (final long unreadTime) {
			this.unreadTime = unreadTime;
			for (int i = 0; i < this.listAdaptor.getCount(); i++) {
				if (this.listAdaptor.getItemTime(i) <= this.unreadTime) {
					this.bar.setUnreadPosition(i);
					return;
				}
			}
		}

		public long getUnreadTime () {
			return this.unreadTime;
		}

		private void updateBar () {
			final int position = this.list.getFirstVisiblePosition();
			if (position != this.lastPosition) {
				this.bar.layoutForce(this.list.getRight() - this.barWidth,
						this.list.getTop(),
						this.list.getRight(),
						this.list.getTop() + barHeightPx(position, this.list));
				this.lastPosition = position;

				final long time = this.listAdaptor.getItemTime(position);
				if (time > this.unreadTime) {
					this.unreadTime = time;
					this.bar.setUnreadPosition(position);
				}
			}
		}

		@Override
		public void onScrollStateChanged (final AbsListView view, final int scrollStateFlag) {
			updateBar();
			this.delagate.onScrollStateChanged(view, scrollStateFlag);
		}

		@Override
		public void onScroll (final AbsListView view, final int firstVisibleItem, final int visibleItemCount, final int totalItemCount) {
			updateBar();
			this.delagate.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
		}

	}

}

@@ -4932,24 +4932,114 @@
 readTime) %7B%0A
+%09%09%09if (unreadTime %3E 0L && unreadTime %3C= this.unreadTime) return; // Can only go forwards.%0A
 %09%09%09this.unre

Stop ScrollIndicator unread marker ever regressing. [issue #144]