/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.way.upgrade.utils.thread;

import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.way.upgrade.utils.Log;
/**
 * 
 * @author way
 *
 */
public class ThreadPool {
	private static final String TAG = "ThreadPool";
	private static final int CORE_POOL_SIZE = 4;
	private static final int MAX_POOL_SIZE = 8;
	private static final int KEEP_ALIVE_TIME = 20; // 10 seconds

	// Resource type
	public static final int MODE_NONE = 0;
	public static final int MODE_CPU = 1;
	public static final int MODE_NETWORK = 2;

	public static final JobContext JOB_CONTEXT_STUB = new JobContextStub();

	// M: in order to take full advantage of MT6589 CPU Core, change cpuCounter
	// to 4 from 2
	ResourceCounter mCpuCounter = new ResourceCounter(4);
	ResourceCounter mNetworkCounter = new ResourceCounter(6);

	// A Job is like a Callable, but it has an addition JobContext parameter.
	public interface Job<T> {
		public T run(JobContext jc);
	}

	public interface JobContext {
		boolean isCancelled();

		void setCancelListener(CancelListener listener);

		boolean setMode(int mode);
	}

	private static class JobContextStub implements JobContext {
		@Override
		public boolean isCancelled() {
			return false;
		}

		@Override
		public void setCancelListener(CancelListener listener) {
		}

		@Override
		public boolean setMode(int mode) {
			return true;
		}
	}

	public interface CancelListener {
		public void onCancel();
	}

	private static class ResourceCounter {
		public int value;

		public ResourceCounter(int v) {
			value = v;
		}
	}

	private final Executor mExecutor;

	public ThreadPool() {
		this(CORE_POOL_SIZE, MAX_POOL_SIZE);
	}

	public ThreadPool(int initPoolSize, int maxPoolSize) {
		mExecutor = new ThreadPoolExecutor(initPoolSize, maxPoolSize,
				KEEP_ALIVE_TIME, TimeUnit.SECONDS,
				new LinkedBlockingQueue<Runnable>(), new PriorityThreadFactory(
						"thread-pool",
						android.os.Process.THREAD_PRIORITY_BACKGROUND));
	}

	// Submit a job to the thread pool. The listener will be called when the
	// job is finished (or cancelled).
	public <T> Future<T> submit(Job<T> job, FutureListener<T> listener,
			int threadMode) {
		Log.i(TAG, "<submit> Job:" + job, Log.APP);
		Worker<T> w = new Worker<T>(job, listener, threadMode);
		mExecutor.execute(w);
		return w;
	}

	public <T> Future<T> submit(Job<T> job) {
		return submit(job, null, MODE_CPU);
	}

	private class Worker<T> implements Runnable, Future<T>, JobContext {
		private static final String TAG = "Worker";
		private Job<T> mJob;
		private FutureListener<T> mListener;
		private CancelListener mCancelListener;
		private ResourceCounter mWaitOnResource;
		private volatile boolean mIsCancelled;
		private boolean mIsDone;
		private T mResult;
		private int mMode;
		private int mTargetMode;

		public Worker(Job<T> job, FutureListener<T> listener, int threadMode) {
			mJob = job;
			mListener = listener;
			mTargetMode = threadMode;
		}

		// This is called by a thread in the thread pool.
		@Override
		public void run() {
			Log.i(TAG, "<run> start", Log.APP);
			T result = null;

			// A job is in CPU mode by default. setMode returns false
			// if the job is cancelled.
			if (setMode(mTargetMode)) {
				try {
					result = mJob.run(this);
				} catch (Throwable ex) {
				}
			}

			synchronized (this) {
				setMode(MODE_NONE);
				mResult = result;
				mIsDone = true;
				notifyAll();
			}
			if (mListener != null)
				mListener.onFutureDone(this);
			Log.i(TAG, "<run> end", Log.APP);
		}

		// Below are the methods for Future.
		@Override
		public synchronized void cancel() {
			if (mIsCancelled)
				return;
			mIsCancelled = true;
			if (mWaitOnResource != null) {
				synchronized (mWaitOnResource) {
					mWaitOnResource.notifyAll();
				}
			}
			if (mCancelListener != null) {
				mCancelListener.onCancel();
			}
		}

		@Override
		public boolean isCancelled() {
			return mIsCancelled;
		}

		@Override
		public synchronized boolean isDone() {
			return mIsDone;
		}

		@Override
		public synchronized T get() {
			while (!mIsDone) {
				try {
					wait();
				} catch (Exception ex) {
				}
			}
			return mResult;
		}

		@Override
		public void waitDone() {
			get();
		}

		// Below are the methods for JobContext (only called from the
		// thread running the job)
		@Override
		public synchronized void setCancelListener(CancelListener listener) {
			mCancelListener = listener;
			if (mIsCancelled && mCancelListener != null) {
				mCancelListener.onCancel();
			}
		}

		@Override
		public boolean setMode(int mode) {
			// Release old resource
			Log.i(TAG, "<setMode> start mode:" + mode, Log.APP);
			ResourceCounter rc = modeToCounter(mMode);
			if (rc != null)
				releaseResource(rc);
			mMode = MODE_NONE;

			// Acquire new resource
			rc = modeToCounter(mode);
			if (rc != null) {
				if (!acquireResource(rc)) {
					return false;
				}
				mMode = mode;
			}
			Log.i(TAG, "<setMode> end", Log.APP);
			return true;
		}

		private ResourceCounter modeToCounter(int mode) {
			if (mode == MODE_CPU) {
				Log.i(TAG, "<modeToCounter> MODE_CPU", Log.APP);
				return mCpuCounter;
			} else if (mode == MODE_NETWORK) {
				Log.i(TAG, "<modeToCounter> MODE_NETWORK", Log.APP);
				return mNetworkCounter;
			} else {
				return null;
			}
		}

		private boolean acquireResource(ResourceCounter counter) {
			Log.i(TAG, "<acquireResource> start", Log.APP);
			while (true) {
				synchronized (this) {
					if (mIsCancelled) {
						mWaitOnResource = null;
						return false;
					}
					mWaitOnResource = counter;
				}

				synchronized (counter) {
					if (counter.value > 0) {
						counter.value--;
						break;
					} else {
						try {
							counter.wait();
						} catch (InterruptedException ex) {
							// ignore.
						}
					}
				}
			}

			synchronized (this) {
				mWaitOnResource = null;
			}
			Log.i(TAG, "<acquireResource> end", Log.APP);
			return true;
		}

		private void releaseResource(ResourceCounter counter) {
			Log.i(TAG, "<releaseResource> start", Log.APP);
			synchronized (counter) {
				counter.value++;
				counter.notifyAll();
			}
			Log.i(TAG, "<releaseResource> end", Log.APP);
		}
	}
}
