package com.tomgibara.android.util;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Handler;
import android.os.Process;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ImageView;
 
/**
 * <p>
 * This excellent class was borrowed from Tom Gibara at:
 * http://blog.tomgibara.com/post/7665158012/android-adapter-view-rendering
 * Modified only to add this attribution. Thanks!
 * </p>
 * 
 * <p>
 * Concrete extensions of this class are capable of rendering a multiplicity of
 * Views on background threads. Interrupting the rendering of a View to start a
 * new rendering is handled by this class. So too is rendering a view in
 * multiple passes (initial passes are automatically prioritized over subsequent
 * passes). Support for caching renderings is also available.
 * <p>
 * 
 * <p>
 * This class is mainly intended to be used within
 * {@link Adapter#getView(int, View, android.view.ViewGroup)} to handle all
 * aspects of the asynchronous rendering of the views within an associated
 * {@link AdapterView}.
 * </p>
 * 
 * <p>
 * Example: A simple scenario would be a {@link GridView} containing
 * {@link ImageView}s which are rendered by an instance of this class where
 * <code>Param<code> is {@link Uri} and <code>Render</code> is {@link Bitmap}
 * and the implementation loads bitmaps over HTTP for display in the view. The
 * {@link GridView} would invoke the {@link ViewRenderer} via an {@link Adapter}
 * that called the {@link #renderView(View, Object)} method on each call to its
 * {@link Adapter#getView(int, View, android.view.ViewGroup)} method. Such an
 * implementation might support two pass rendering, loading a low-res image on
 * the first pass before loading a high-res image later.
 * </p>
 * 
 * <p>
 * <strong>Note that this class requires a well defined equals method on the
 * class that satisfies Param.</strong>
 * </p>
 * 
 * @author Tom Gibara
 * 
 * @param <Param>
 *            the type of parameters that define renders
 * @param <Render>
 *            the type of renders that are applied to views
 */
 
public abstract class ViewRenderer<Param, Render> {
 
	// statics
	
	// convenience method for testing equality
	private static final boolean equal(Object a, Object b) {
        if (a == b) return true;
        if (a == null) return false;
        if (b == null) return false;
        return a.equals(b);
	}
	
	// shared default executor, should be good enough in the common case
	// and avoids thread proliferation in casual use
	private static Executor sDefaultExecutor = null;
 
	// ThreadPoolExecutor is broken for priority queues (see 6539720) so we need to patch it.
	// Also newTaskFor() was only introduced in API level 9, so we can't use that either
	// instead we patch the submit method directly
	private static class Executor extends ThreadPoolExecutor {
		
		public Executor(int threadCount) {
			// ThreadPoolExecutor can't be configured to grow the thread count without risking queue rejections
			// so we take the easy way out and clamp it with a fixed number of threads.
			// TODO introduce our own executor (or equivalent) that doesn't share this weakness
			// TODO our own blocking queue which stitched-together lists of the same priority would be much more efficient
			super(threadCount, threadCount, 0L, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<Runnable>());
		}
		
		@Override
		public <T> Future<T> submit(Callable<T> task) {
			if (task == null) throw new NullPointerException();
			ComparableFuture<T> future = new ComparableFuture<T>(task);
			execute(future);
			return future;
		}
 
		@SuppressWarnings("unchecked")
		private static class ComparableFuture<T> extends FutureTask<T> implements Comparable<ComparableFuture<T>> {
 
			private final Comparable mComparable;
			
			public ComparableFuture(Callable<T> callable) {
				super(callable);
				mComparable = (Comparable) callable;
			}
			
			@Override
			public int compareTo(ComparableFuture<T> that) {
				return this.mComparable.compareTo(that.mComparable);
			}
			
		}
		
	}
	
	// wraps access to the default executor to allow for lazy creation
	private static synchronized Executor getDefaultExecutor() {
		return sDefaultExecutor == null ? sDefaultExecutor = new Executor(1) : sDefaultExecutor;
	}
 
	// fields
	
	private final Executor mExecutor;
	private final boolean mMayInterruptIfRunning;
	private final Handler mHandler;
	private final int mInheritedThreadPriority;
	private final int mPasses;
	
	private volatile long mNextOrder = 0L;
	private volatile boolean mStopping = false;
	
	private final Cache mCache;
	
	// constructors
 
	/**
	 * Constructs a new {@link ViewRenderer} object that coordinates concurrent
	 * background rendering for display to views. It is primarily designed for
	 * use with {@link AdapterView} based views. Supplying zero for the thread
	 * count will cause the {@link ViewRenderer} to use a single thread that
	 * can be shared among other instances.
	 * 
	 * @param maxThreadCount
	 *            the number of threads that will be created to support
	 *            background rendering, may be zero.
	 * @param passes
	 *            the number of rendering passes required to complete the
	 *            rendering required for a view
	 * @param mayInterruptIfRunning
	 *            whether background rendering tasks can be interrupted if they
	 *            become redundant
	 * @param cacheCapacity
	 *            the maximum number of render
	 * 
	 */
	
	public ViewRenderer(int threadCount, int passes, boolean mayInterruptIfRunning, int cacheCapacity) {
		if (threadCount < 0) throw new IllegalArgumentException("negative thread count");
		if (passes <= 0) throw new IllegalArgumentException("passes not positive");
		if (cacheCapacity < 0) throw new IllegalArgumentException("negative cache capacity");
		mExecutor = threadCount == 0 ? getDefaultExecutor() : new Executor(threadCount);
		mPasses = passes;
		mMayInterruptIfRunning = mayInterruptIfRunning;
		mCache = cacheCapacity == 0 ? null : new Cache(cacheCapacity);
		mHandler = new Handler();
		mInheritedThreadPriority = Process.getThreadPriority(Process.myTid());
	}
	
	// methods
	
	/**
	 * Causes a view to be rendered using the supplied parameters. The nature of
	 * the rendering performed is determined by the concrete subclass on which
	 * this method is called, but in all cases, the view is initialized (by a
	 * call to {@link #prepare(View, Object, int)} and a Render object created
	 * in {@link #render(Object, int)} (on a background thread) before being
	 * displayed (in a call to {@link #update(View, Object, int)}.
	 * 
	 * @param view
	 *            the view that will display the rendering
	 * @param param
	 *            the parameters that define the rendering
	 */
	
	public void renderView(View view, Param param) {
		if (mStopping) throw new IllegalStateException("stopped");
		//check the parameters
		if (view == null) throw new IllegalArgumentException("null view");
		
		// see if there's a tag which will tell us about past rendering on this view
		Task task = getTask(view);
		if (task != null) {
			if (equal(param, task.mParam)) {
				// a task has already done this (or will)
				// so there's nothing else to do
				return;
			} else {
				// try to cancel the existing task
				// this may save lots of work
				task.removeFromView(view);
			}
		}
		// try to obtain a task from the cache
		if (mCache == null) {
			task = null;
		} else {
			synchronized (mCache) {
				task = mCache.get(param);
			}
		}
		// create a task that will render and later update the view
		if (task == null) task = new Task(param);
		// associate the task with the view (and schedule it for rendering as necessary)
		task.assignToView(view);
	}
	
	/**
	 * Instructs this renderer that any cached renders should be purged. This
	 * method may be useful for dealing with low memory conditions or situations
	 * where parameter equality is no longer valid.
	 */
	
	public void clearCache() {
		mCache.clear();
	}
 
	/**
	 * Stops this renderer and makes it unusable for further rendering. A zero
	 * value for the timeout blocks indefinitely. A negative timeout value will
	 * cause the method to return immediately without waiting for background
	 * rendering operations to complete. Note that even if no timeout is set, or
	 * the timeout is exceeded, the renderer will not call
	 * {@link #update(View, Object, int)} or {@link #prepare(View, Object, int)}
	 * or otherwise modify a view at any time after the this method has been
	 * called.
	 * 
	 * @param timeout
	 *            the number of milliseconds for which to wait for rendering
	 *            operations to terminate
	 */
	
	public void stop(long timeout) throws InterruptedException {
		if (mStopping) return;
		mStopping = true;
		if (mExecutor != sDefaultExecutor) {
			mExecutor.shutdown();
			if (timeout == 0) {
				timeout = Long.MAX_VALUE;
			}
			if (timeout >= 0) {
				mExecutor.awaitTermination(timeout, TimeUnit.MILLISECONDS);
			}
		}
	}
 
	/**
	 * Stop the renderer without waiting for calls to
	 * {@link #render(Object, int)} to complete. This is a convenient way of
	 * calling {@link #stop(long)} without specifying a timeout.
	 */
	
	public void stop() {
		try {
			stop(-1L);
		} catch (InterruptedException e) {
			throw new IllegalStateException("Impossible: interrupted without waiting");
		}
	}
 
	/**
	 * The thread priority assigned to the specified rendering pass. The default
	 * implementation returns {@link Process#THREAD_PRIORITY_BACKGROUND} for
	 * every pass. 
	 * 
	 * @param pass the rendering pass for which the priority is being requested
	 * @return a thread priority
	 * @see Process
	 */
	
	protected int getThreadPriority(int pass) {
		return Process.THREAD_PRIORITY_BACKGROUND;
	}
	
	/**
	 * <p>
	 * Prepares a view for display in advance of being updated with its rendered
	 * content. A non-negative immediatePassHint indicates the update method
	 * will be called immediately after this call to prepare (this may enable
	 * some optimizations within the prepare method), otherwise the view will
	 * displayed to the visitor until the background rendering completes. This
	 * method should return quickly with all time-consuming operations being
	 * perfomed in one-or-more rendering passes.
	 * </p>
	 * 
	 * <p>
	 * Implementations will typically prepare the supplied view to display a
	 * blank placeholder, or a "loading" indicator. Note that a view may be
	 * prepared several times without receiving a render if it is part of an
	 * {@link AdapterView} that is recycling its views.
	 * </p>
	 * 
	 * @param view
	 *            a view will be displayed to the user presently
	 * @param param
	 *            defines the content that the view will display
	 * @param immediatePassHint
	 *            the pass to which rendering has already progressed
	 */
 
	protected abstract void prepare(View view, Param param, int immediatePassHint);
	
	/**
	 * Converts a Param into a Render for subsequent display in a view. The
	 * supplied pass parameter may be used to generate progressively more
	 * complete Renders. The first pass is zero. The number of passes is
	 * specified at construction time.
	 * 
	 * @param param
	 *            defines the rendering that needs to be produced
	 * @param pass
	 *            which rendering pass is being performed
	 * @return a rendering of the supplied parameters
	 */
	
	//TODO consider supplying the previous Render for multi-pass rendering
	protected abstract Render render(Param param, int pass);
 
	/**
	 * Applies previously rendered content to a view.
	 * 
	 * @param view
	 *            a view that needs to update with rendered content
	 * @param render
	 *            the content that is to be applied to the view
	 * @param pass
	 *            the rendering pass that generated the rendered content
	 */
 
	protected abstract void update(View view, Render render, int pass);
	
	/**
	 * Retrieves the last object that was associated with a view by the
	 * renderer. If no object has yet been associated with the view, null is
	 * returned.
	 * 
	 * @see ViewRenderer.setTag
	 * @param view
	 *            a rendered view
	 * @return the associated object or null
	 */
 
	protected Object getTag(View view) { return view.getTag(); }
 
	/**
	 * Associates an object with the view. The default implementation simply
	 * uses the {@link View.setTag(Object)} method. This may interfere with some
	 * layouts, so using the {@link View.setTag(int,Object)} method is
	 * preferable (API level 4 and above). A conservative implementation may be
	 * to use a {@link WeakHashMap}.
	 * 
	 * @param view
	 *            a rendered view
	 * @param tag
	 *            the object with which the view is to be tagged, may be null
	 */
	protected void setTag(View view, Object tag) { view.setTag(tag); }
	
	// private utility methods
	
	@SuppressWarnings("unchecked")
	private Task getTask(View view) {
		Object obj = getTag(view);
		return obj == null || (obj instanceof ViewRenderer.Task) ? (Task) obj : null;
	}
	
	private void setTask(View view, Task task) {
		setTag(view, task);
	}
	
	// inner classes
	
	private class Task implements Runnable, Callable<Void>, Comparable<Task> {
 
		// immutable fields for task
		private final long mOrder = mNextOrder++;
		private final Param mParam;
		
		// only set/mutated on UI thread
		// TODO look at strategy's for defraying cost of a hashset on each task
		// note that this set also incurs the cost of repeated iterator creation
		private final HashSet<View> mViews = new HashSet<View>();
		private Future<Void> mFuture;
		
		// only set on render thread
		private int mPass = 0; // from the UI thread, this is actually 'the next pass'
		private Render mRender = null;
		private boolean mFailed = false;
		
		public Task(Param param) {
			mParam = param;
		}
		
		void assignToView(View view) {
			// mPass is actually 'the next pass' at this point
			int pass = mPass - 1;
			// prepare the view for display while rendering
			prepare(view, mParam, pass);
			// update the view with whatever work we have already done
			if (pass >= 0) update(view, mRender, pass);
			//check if we have work left to do
			if (mPass < mPasses) {
				// add the view to our set
				mViews.add(view);
				// tag the view so that we can cancel the task later if we need to
				setTask(view, this);
				// all set, queue us up
				enqueue();
			}
		}
 
		void removeFromView(View view) {
			// mViews can't be null because keep tags and set in sync
			Task task = getTask(view);
			if (task == this) setTask(view, null);
			mViews.remove(view);
			if (mViews.isEmpty()) {
				cancel();
			}
		}
		
		void removeFromViews() {
			for (View view : mViews) {
				Task task = getTask(view);
				if (task == this) setTask(view, null);
			}
			mViews.clear();
			cancel();
		}
 
		// called to apply render to view
		@Override
		public void run() {
			if (mStopping) return;
			if (mFailed) {
				// render didn't complete (probably got cancelled)
				// remove from the view (but we may still get cached)
				removeFromViews();
			}
			// the views may be null/empty if we were removed from it while queued by the handler
			if (mViews != null && !mViews.isEmpty()) {
				try {
					for (View view : mViews) {
						// we are safe to update this view,
						// if another task had been started on the view
						// the view would have already have been removed from our set
						update(view, mRender, mPass - 1);
					}
				} finally {
					if (mPass < mPasses) {
						// reschedule again if we need to perform more passes
						enqueue();
					} else {
						// we're done, remove us from all our views
						// we lose the ability to avoid doing the same rendering for the same view (very rare)
						// but this is worth it, so that we can re-use the render from the cache (much more likely)
						removeFromViews();
					}
				}
			}
			// try putting us into the cache
			encache();
		}
		
		// called to produce render
		@Override
		public Void call() throws Exception {
			if (mStopping) return null;
			Process.setThreadPriority(getThreadPriority(mPass));
			try {
				Render render = render(mParam, mPass);
				if (render == null) {
					mFailed = true;
				} else {
					// record the render, we will apply to the view it cache it later 
					mRender = render;
					// increment the pass now - since later calls aren't guaranteed to occur
					mPass++;
				}
				mHandler.post(this);
				return null;
			} finally {
				Process.setThreadPriority(mInheritedThreadPriority);
			}
		}
		
		public int compareTo(Task that) {
			if (this == that) return 0;
			if (this.mPass != that.mPass) return this.mPass < that.mPass ? -1 : 1;
			if (this.mOrder != that.mOrder) return this.mOrder < that.mOrder ? -1 : 1;
			return 0;
		}
		
		private void enqueue() {
			mFailed = false;
			mFuture = mExecutor.submit((Callable<Void>) this);
		}
 
		private void encache() {
			if (mPass > 0 && mCache != null) {
				synchronized (mCache) {
					Task that = mCache.get(mParam);
					if (that == null || this.mPass > that.mPass) {
						mCache.put(mParam, this);
					}
				}
			}
		}
		
		// may only be called when all views have been removed
		private void cancel() {
			mFuture.cancel(mMayInterruptIfRunning);
		}
		
	}
 
	private class Cache extends LinkedHashMap<Param, Task> {
		
		// serialization boilerplate
		private static final long serialVersionUID = -5867267874566891476L;
		
		private final int mCapacity;
		
		public Cache(int capacity) {
			super(capacity, 0.75f, true);
			mCapacity = capacity;
		}
		
		@Override
		protected boolean removeEldestEntry(Map.Entry<Param, Task> eldest) {
			return size() >= mCapacity;
		}
		
	}
	
}