package jp.sourceforge.nicoro;

//import static jp.sourceforge.nicoro.Log.LOG_TAG;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

import android.os.Handler;
import android.os.Message;

public class ThumbInfoCacher extends LooperThread
		implements ThumbInfoInterface.EventListener {
	private static final int MSG_ID_LOAD_THUMBINFO = 0;
	private static final int MSG_ID_LOAD_FINISHED = 1;
	private static final int MSG_ID_LOAD_ERROR_OCCURRED = 2;
	private static final int MSG_ID_CLOSE = 3;

	private static class Params {
		public String videoNumber;
		public CallbackMessage<ThumbInfo, String> callbackMessage;

		public Params(String v, CallbackMessage<ThumbInfo, String> cm) {
			videoNumber = v;
			callbackMessage = cm;
		}
	}
	private static class ErrorObj {
		public ThumbInfo thumbInfo;
		public String errorMessage;

		public ErrorObj(ThumbInfo ti, String e) {
			thumbInfo = ti;
			errorMessage = e;
		}
	}
	private ConcurrentHashMap<String, SoftReference<ThumbInfo>> mThumbInfoCache =
		new ConcurrentHashMap<String, SoftReference<ThumbInfo>>();
	private HashMap<String, ThumbInfo> mDownloadingThumbInfos =
		new HashMap<String, ThumbInfo>();
	private ArrayList<Params> mRequests = new ArrayList<Params>();

	private ArrayList<LooperThread.WatchIdleWrapper> mMultiLoaders;
	private Random mRandom = new Random();

	public ThumbInfoCacher() {
		super("ThumbInfoCacher");
		mMultiLoaders = new ArrayList<LooperThread.WatchIdleWrapper>(3);
		for (int i = 0; i < 3; ++i) {
		    LooperThread.WatchIdleWrapper wiw = new LooperThread.WatchIdleWrapper(new LooperThread("ThumbInfoLoader-" + i) {
                @Override
                public boolean handleMessage(Message msg) {
                    return false;
                }
            });
		    mMultiLoaders.add(wiw);
		}
	}

	@Override
	public boolean handleMessage(Message msg) {
		switch (msg.what) {
		case MSG_ID_LOAD_THUMBINFO: {
			Params params = (Params) msg.obj;
			// ダウンロード中かチェック
			if (mDownloadingThumbInfos.get(params.videoNumber) == null) {
				// 新規作成してダウンロード開始
				startLoadThumbInfo(params.videoNumber);
			}
			mRequests.add(params);

		} break;
		case MSG_ID_LOAD_FINISHED: {
			ThumbInfo thumbInfo = (ThumbInfo) msg.obj;
			thumbInfo.finish();	// 念のため
			String videoNumber = thumbInfo.getVideoNumber();
			mDownloadingThumbInfos.remove(videoNumber);
			mThumbInfoCache.put(videoNumber, new SoftReference<ThumbInfo>(thumbInfo));
			for (Iterator<Params> it = mRequests.iterator(); it.hasNext(); ) {
				Params params = it.next();
				if (videoNumber.equals(params.videoNumber)
				        && params.callbackMessage != null) {
				    params.callbackMessage.sendMessageSuccess(
				            thumbInfo);
					it.remove();
				}
			}
		} break;
		case MSG_ID_LOAD_ERROR_OCCURRED: {
			ErrorObj errorObj = (ErrorObj) msg.obj;
			ThumbInfo thumbInfo = errorObj.thumbInfo;
			thumbInfo.finish();	// 念のため
			String videoNumber = thumbInfo.getVideoNumber();
			mDownloadingThumbInfos.remove(videoNumber);
//			// TODO 再ダウンロード
//			startLoadThumbInfo(videoNumber);
			for (Iterator<Params> it = mRequests.iterator(); it.hasNext(); ) {
				Params params = it.next();
				if (videoNumber.equals(params.videoNumber)
				        && params.callbackMessage != null) {
                    params.callbackMessage.sendMessageError(
                            errorObj.errorMessage);
					it.remove();
				}
			}
		} break;
		case MSG_ID_CLOSE: {
			for (ThumbInfo thumbInfo : mDownloadingThumbInfos.values()) {
				thumbInfo.finish();
			}
			mDownloadingThumbInfos.clear();
			Handler handler = getHandler();
			handler.removeMessages(MSG_ID_LOAD_THUMBINFO);
			handler.removeMessages(MSG_ID_LOAD_FINISHED);
			handler.removeMessages(MSG_ID_LOAD_ERROR_OCCURRED);
			for (LooperThread.WatchIdleWrapper wiw : mMultiLoaders) {
			    wiw.quit();
			}
		} break;
		default:
			assert false : msg.what;
			break;
		}
		return true;
	}

	@Override
	public boolean quit() {
		getHandler().sendEmptyMessage(MSG_ID_CLOSE);
		boolean ret = super.quit();
		return ret;
	}

	public void onLowMemory() {
	    for (Iterator<String> it = mThumbInfoCache.keySet().iterator(); it.hasNext(); ) {
	        String key = it.next();
	        SoftReference<ThumbInfo> ref = mThumbInfoCache.get(key);
            ThumbInfoInterface ti = (ref == null ? null : ref.get());
            if (ti == null) {
                it.remove();
            }
	    }
	}

	/**
	 *
	 * @param videoNumber
	 * @return キャッシュ上に存在しないときはnull
	 */
	public ThumbInfo getThumbInfo(String videoNumber) {
		SoftReference<ThumbInfo> ref = mThumbInfoCache.get(videoNumber);
		if (ref != null) {
			return ref.get();
		}
		return null;
	}

	public void loadThumbInfo(String videoNumber,
	        CallbackMessage<ThumbInfo, String> callback) {
		Params params = new Params(videoNumber, callback);
		getHandler().obtainMessage(MSG_ID_LOAD_THUMBINFO, params).sendToTarget();
	}

	private void startLoadThumbInfo(String videoNumber) {
	    LooperThread.WatchIdleWrapper watchIdleWrapper = null;
	    for (LooperThread.WatchIdleWrapper wiw : mMultiLoaders) {
	        if (wiw.isIdle()) {
	            watchIdleWrapper = wiw;
	            watchIdleWrapper.startIfNeeded();
                break;
	        }
	    }
	    if (watchIdleWrapper == null) {
	        watchIdleWrapper = mMultiLoaders.get(mRandom.nextInt(
	                mMultiLoaders.size()));
	    }
	    Handler handler = watchIdleWrapper.getHandler();
		ThumbInfo thumbInfo = new ThumbInfo(videoNumber, handler);
		thumbInfo.setEventListener(this);
		thumbInfo.startLoad();
		watchIdleWrapper.postIdleHandler();
		mDownloadingThumbInfos.put(videoNumber, thumbInfo);
	}

	// ThumbInfo.EventListener

	@Override
	public void onFinished(ThumbInfoInterface thumbInfo) {
		getHandler().obtainMessage(MSG_ID_LOAD_FINISHED, thumbInfo
				).sendToTarget();
	}
	@Override
	public void onOccurredError(ThumbInfo thumbInfo,
			String errorMessage) {
		getHandler().obtainMessage(MSG_ID_LOAD_ERROR_OCCURRED,
				new ErrorObj(thumbInfo, errorMessage)
				).sendToTarget();
	}
}
