package jp.sourceforge.nicoro;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicReference;

import jp.gr.java_conf.shiseissi.commonlib.HandlerThreadCompat;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import static jp.sourceforge.nicoro.NicoroAPIManager.ECO_TYPE_HIGH;
import static jp.sourceforge.nicoro.NicoroAPIManager.ECO_TYPE_MID;
import static jp.sourceforge.nicoro.NicoroAPIManager.ECO_TYPE_LOW;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.impl.client.DefaultHttpClient;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;

// TODO 複数動画のキャッシュを順番に実行できるように

public class VideoCacheService extends Service implements Handler.Callback {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG & true;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;

	public static final int CACHE_STATE_INVALID = -1;
	public static final int CACHE_STATE_NOT_RUN = 0;
	public static final int CACHE_STATE_WAIT_START = 1;
	public static final int CACHE_STATE_RUNNING = 2;

	private static final int MSG_ID_ADD_START_CACHE = 0;
	private static final int MSG_ID_VIDEO_LOADER_FINISHED = 1;
    private static final int MSG_ID_VIDEO_LOADER_NOTIFY_PROGRESS = 2;
	private static final int MSG_ID_VIDEO_LOADER_OCCURRED_ERROR = 3;
	private static final int MSG_ID_VIDEO_LOADER_STOP_CACHE_CURRENT = 4;
	private static final int MSG_ID_VIDEO_LOADER_STOP_CACHE_QUEUE = 5;
//	private static final int MSG_ID_START_FIRST = 6;
    private static final int MSG_ID_DELETE_CACHE_FINISHED = 6;
    private static final int MSG_ID_DELETE_ALL_CACHE_FINISHED = 7;

	private static final long NOTIFY_PROGRESS_INTERVAL_MS = 1000;

    /**
	 * 動画キャッシュのリクエスト
	 */
	static class CacheRequest {
		public String videoV;
		public String cookieUserSession;
		public int forceEco;
		public int notificationIDFinishVideoCache;
	}

	/**
	 * 現在実行中の動画キャッシュのデータ
	 */
	static class NotifyProgressData {
	    String videoV;
	    int ecoType;
	    int seekOffsetWrite;
	    int contentLength;
	}

	private VideoLoaderInterface mVideoLoader = VideoLoaderInterface.NullObject.getInstance();
	private CacheRequest mCacheRequestCurrent = null;
	private LinkedList<CacheRequest> mCacheRequestQueue = new LinkedList<CacheRequest>();

//	private DefaultHttpClient mHttpClient;

	private volatile int mSeekOffsetWrite = -1;
	private volatile int mContentLength = -1;

	private final AtomicReference<NotifyProgressData> mRefNotifyProgressData =
	    new AtomicReference<NotifyProgressData>(new NotifyProgressData());
	private long mLastNotifyProgressTime = 0;

	NotificationController mNotificationController;

	private boolean mRequestNotificationStartVideoCacheRealert = false;

    private SharedPreferences mSharedPreferences;

    private final HandlerThreadCompat mHandlerThread;
	private final HandlerWrapper mHandler;

	@Override
	public boolean handleMessage(Message msg) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, "handleMessage msg.what=" + msg.what);
		}
		String videoV;
		CacheRequest cacheRequest;
		switch (msg.what) {
		case MSG_ID_ADD_START_CACHE:
			cacheRequest = (CacheRequest) msg.obj;
			if (cacheRequest != null) {
				if (mCacheRequestCurrent == null) {
					startCacheRequestCurrent(cacheRequest);
				} else {
				    addCacheRequest(cacheRequest);
				}
			}
			break;
		case MSG_ID_VIDEO_LOADER_FINISHED: {
		    videoV = (String) msg.obj;
		    int ecoType = msg.arg1;

            final int numCallbacks = mCallbacks.beginBroadcast();
            for (int i = 0; i < numCallbacks; ++i) {
                try {
                    mCallbacks.getBroadcastItem(i).onFinished(
                            videoV, ecoType);
                } catch (RemoteException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                }
            }
            mCallbacks.finishBroadcast();

            startNextCacheOrEnd();
		} break;
		case MSG_ID_VIDEO_LOADER_NOTIFY_PROGRESS: {
		    NotifyProgressData notifyProgressData =
		        (NotifyProgressData) msg.obj;
		    assert notifyProgressData != null;
		    assert mRefNotifyProgressData.get() == null;

            final int numCallbacks = mCallbacks.beginBroadcast();
            for (int i = 0; i < numCallbacks; ++i) {
                try {
                    mCallbacks.getBroadcastItem(i).onNotifyProgress(
                            notifyProgressData.videoV,
                            notifyProgressData.ecoType,
                            notifyProgressData.seekOffsetWrite,
                            notifyProgressData.contentLength);
                } catch (RemoteException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                }
            }
            mCallbacks.finishBroadcast();

            boolean realert;
            if (mRequestNotificationStartVideoCacheRealert) {
                realert = true;
                mRequestNotificationStartVideoCacheRealert = false;
            } else {
                realert = false;
            }
            mNotificationController.updateRunningCache(
                    notifyProgressData, mCacheRequestQueue.size(), realert);

		    mRefNotifyProgressData.set(notifyProgressData);
		} break;
		case MSG_ID_VIDEO_LOADER_OCCURRED_ERROR:
			Util.showErrorToast(getApplicationContext(), (String) msg.obj);

			// エラー時も次のキャッシュ開始
            startNextCacheOrEnd();
			break;
		case MSG_ID_VIDEO_LOADER_STOP_CACHE_CURRENT:
			// 次のキャッシュ開始
            startNextCacheOrEnd();
			break;
		case MSG_ID_VIDEO_LOADER_STOP_CACHE_QUEUE:
			// 待ち行列にあれば削除
			videoV = (String) msg.obj;
			for (Iterator<CacheRequest> it = mCacheRequestQueue.iterator(); it.hasNext(); ) {
				CacheRequest cr = it.next();
				if (cr.videoV.equals(videoV)) {
					it.remove();
				}
			}
			break;
//		case MSG_ID_START_FIRST:
//			// 一番最初のキャッシュ開始
//			startFirst((Intent) msg.obj);
//			break;
		case MSG_ID_DELETE_CACHE_FINISHED: {
            videoV = (String) msg.obj;

            final int numCallbacks = mCallbacks.beginBroadcast();
            for (int i = 0; i < numCallbacks; ++i) {
                try {
                    mCallbacks.getBroadcastItem(i).onDeleted(videoV);
                } catch (RemoteException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                }
            }
            mCallbacks.finishBroadcast();

		} break;
		case MSG_ID_DELETE_ALL_CACHE_FINISHED: {
            final int numCallbacks = mCallbacks.beginBroadcast();
            for (int i = 0; i < numCallbacks; ++i) {
                try {
                    mCallbacks.getBroadcastItem(i).onAllDeleted();
                } catch (RemoteException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                }
            }
            mCallbacks.finishBroadcast();

		} break;
		default:
			assert false : msg.what;
			break;
		}
		return true;
	}

	private RemoteCallbackList<IVideoCacheServiceCallback> mCallbacks =
		new RemoteCallbackList<IVideoCacheServiceCallback>();

	private static class VideoCacheServiceStub extends IVideoCacheService.Stub {
	    private WeakReference<VideoCacheService> mVideoCacheService;

	    VideoCacheServiceStub(VideoCacheService s) {
	        mVideoCacheService = new WeakReference<VideoCacheService>(s);
	    }

	    @Override
		public int getContentLength(String videoV, int ecoType)
		throws RemoteException {
	        VideoCacheService s = mVideoCacheService.get();
	        if (s == null) {
	            return -1;
	        }

			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, Log.buf().append("getContentLength() mContentLength=")
						.append(s.mContentLength).toString());
			}
			// TODO mCacheRequestCurrentのチェックも同一メインスレッドで行わないとまずい？
			CacheRequest cacheRequestCurrent = s.mCacheRequestCurrent;
			VideoLoaderInterface videoLoader = s.mVideoLoader;
			if (cacheRequestCurrent != null && !videoLoader.isNull()
					&& cacheRequestCurrent.videoV.equals(videoV)
					&& videoLoader.getEcoType() == ecoType) {
				return s.mContentLength;
			} else {
				return -1;
			}
		}
		@Override
		public int getProgress(String videoV, int ecoType)
		throws RemoteException {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return -1;
            }

			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, Log.buf().append("getProgress() mSeekOffsetWrite=")
						.append(s.mSeekOffsetWrite).toString());
			}
			CacheRequest cacheRequestCurrent = s.mCacheRequestCurrent;
			VideoLoaderInterface videoLoader = s.mVideoLoader;
			if (cacheRequestCurrent != null && !videoLoader.isNull()
					&& cacheRequestCurrent.videoV.equals(videoV)
					&& videoLoader.getEcoType() == ecoType) {
				return s.mSeekOffsetWrite;
			} else {
				return -1;
			}
		}
		@Override
		public void stopCache(String videoV) {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return;
            }

			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, Log.buf().append("stopCache() videoV=")
						.append(videoV).toString());
			}
			// 通常と低画質の二つのキャッシュが同時に動作している可能性がある

			// 待ち行列にあれば削除
			s.mHandler.obtainMessage(
					MSG_ID_VIDEO_LOADER_STOP_CACHE_QUEUE, videoV)
					.sendToTarget();

			CacheRequest cacheRequestCurrent = s.mCacheRequestCurrent;
			if (cacheRequestCurrent != null
					&& cacheRequestCurrent.videoV.equals(videoV)) {
				VideoLoaderInterface videoLoader = s.mVideoLoader;
				videoLoader.finish();

		    	// 開始時のNotification削除
				s.mNotificationController.cancelRunningCache(cacheRequestCurrent);

				s.mCacheRequestCurrent = null;
				// 次のキャッシュがあれば開始
				s.mHandler.sendEmptyMessage(MSG_ID_VIDEO_LOADER_STOP_CACHE_CURRENT);
			}
		}
		@Override
		public int getCacheState(String videoV, int ecoType) {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return CACHE_STATE_NOT_RUN;
            }

            if (DEBUG_LOGV) {
				Log.v(LOG_TAG, Log.buf().append("getCacheState() videoV=")
						.append(videoV).append(" ecoType=").append(ecoType)
						.toString());
			}
			CacheRequest cacheRequestCurrent = s.mCacheRequestCurrent;
			if (cacheRequestCurrent != null
					&& cacheRequestCurrent.videoV.equals(videoV)) {
				VideoLoaderInterface videoLoader = s.mVideoLoader;
				if (videoLoader.isNull()) {
					if (cacheRequestCurrent.forceEco == ecoType) {
						if (DEBUG_LOGV) {
							Log.v(LOG_TAG, "CACHE_STATE_RUNNING: mCacheRequestCurrent.forceEco == ecoType");
						}
						return CACHE_STATE_RUNNING;
					}
				} else {
					int ecoTypeVideoLoader = videoLoader.getEcoType();
					if (ecoTypeVideoLoader == ecoType) {
						if (DEBUG_LOGV) {
							Log.v(LOG_TAG, "CACHE_STATE_RUNNING: mVideoLoader.getEcoType() == ecoType");
						}
						return CACHE_STATE_RUNNING;
					} else {
						if (cacheRequestCurrent.forceEco != ECO_TYPE_LOW
								&& ecoTypeVideoLoader == ECO_TYPE_LOW) {
							// 中・高画質要求で低画質動作中
							if (DEBUG_LOGV) {
								Log.v(LOG_TAG, "CACHE_STATE_RUNNING: mCacheRequestCurrent.forceEco != ECO_TYPE_LOW && mVideoLoader.getEcoType() == ECO_TYPE_LOW");
							}
							return CACHE_STATE_RUNNING;
						}
						if (cacheRequestCurrent.forceEco == ECO_TYPE_MID
                                && ecoTypeVideoLoader != ECO_TYPE_MID) {
						    // 中画質要求でその他画質動作中
                            if (DEBUG_LOGV) {
                                Log.v(LOG_TAG, "CACHE_STATE_RUNNING: mCacheRequestCurrent.forceEco == ECO_TYPE_MID && mVideoLoader.getEcoType() != ECO_TYPE_MID");
                            }
                            return CACHE_STATE_RUNNING;
						}
					}
				}
			}

			for (CacheRequest cr : s.mCacheRequestQueue) {
				// キャッシュ待ちは本当に別画質になるか分からないので
				// forceEcoで判断
				if (videoV.equals(cr.videoV)
						&& ecoType == cr.forceEco) {
					if (DEBUG_LOGV) {
						Log.v(LOG_TAG, "CACHE_STATE_WAIT_START");
					}
					return CACHE_STATE_WAIT_START;
				}
			}
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "CACHE_STATE_NOT_RUN");
			}
			return CACHE_STATE_NOT_RUN;
		}
		@Override
		public void addStartCache(String videoV,
				int forceEco) {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return;
            }

            String cookieUserSession = NicoroConfig.getCookieUserSession(
                    s.mSharedPreferences);
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, Log.buf().append("addStartCache() videoV=")
						.append(videoV).append(" forceEco=")
						.append(forceEco).append(" cookieUserSession=")
						.append(cookieUserSession).toString());
			}
			CacheRequest cacheRequest = s.mNotificationController.createCacheRequest(
			        videoV, cookieUserSession, forceEco);
			s.mHandler.obtainMessage(
					MSG_ID_ADD_START_CACHE, cacheRequest)
					.sendToTarget();
		}
		@Override
		public int getWaitRequestSize() {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return 0;
            }

		    return s.mCacheRequestQueue.size();
		}
		@Override
		public void clearNotification(String videoV) {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return;
            }

            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, Log.buf().append("clearNotification() videoV=")
                        .append(videoV)
                        .toString());
            }

            s.mNotificationController.cancelFinishedCache(videoV);
		}

        @Override
        public void deleteCache(String videoV) throws RemoteException {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return;
            }

            // キャッシュ処理が動作していれば停止
            stopCache(videoV);
            clearNotification(videoV);

            VideoLoader.deleteCacheFile(s, videoV);

            s.mHandler.obtainMessage(MSG_ID_DELETE_CACHE_FINISHED, videoV).sendToTarget();
        }

        @Override
        public void deleteAllCache() throws RemoteException {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return;
            }

            VideoLoader.deleteAllCacheFile(s);

            s.mHandler.sendEmptyMessage(MSG_ID_DELETE_ALL_CACHE_FINISHED);
        }

		@Override
		public void addListener(IVideoCacheServiceCallback callback) {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return;
            }

			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, Log.buf().append("addListener() callback=")
						.append(callback).toString());
			}
			s.mCallbacks.register(callback);
		}
		@Override
		public void removeListener(IVideoCacheServiceCallback callback) {
            VideoCacheService s = mVideoCacheService.get();
            if (s == null) {
                return;
            }

			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, Log.buf().append("removeListener() callback=")
						.append(callback).toString());
			}
			s.mCallbacks.unregister(callback);
		}
	};
    private final VideoCacheServiceStub mBinder = new VideoCacheServiceStub(this);

	private final VideoLoaderInterface.EventListener mVideoLoaderEventListener =
		new VideoLoaderInterface.EventListener() {
		@Override
		public void onStarted(VideoLoaderInterface streamLoader) {
			// 何もしない
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onStarted");
			}
		}
		@Override
		public void onCached(VideoLoaderInterface streamLoader) {
			// 何もせず
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onCached");
			}
		}
		@Override
		public void onFinished(VideoLoaderInterface streamLoader) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onFinished");
			}
            CacheRequest cacheRequestCurrent = mCacheRequestCurrent;
            if (cacheRequestCurrent == null) {
                Log.e(LOG_TAG, "VideoCacheService#onFinished: mCacheRequestCurrent is null!");
                return;
            }
            assert cacheRequestCurrent.videoV.equals(
                    streamLoader.getVideoV());

            // 開始時のNotification削除
            mNotificationController.cancelRunningCache(cacheRequestCurrent);

            // キャッシュ完了のNotification表示
            Notification notification = mNotificationController.createNotificationFinishedCache(
                    cacheRequestCurrent);
            mNotificationController.addFinishedCache(cacheRequestCurrent,
                    notification);

            mCacheRequestCurrent = null;
			mHandler.obtainMessage(MSG_ID_VIDEO_LOADER_FINISHED,
			        streamLoader.getEcoType(), 0,
			        cacheRequestCurrent.videoV).sendToTarget();

//			// 自分自身の終了
//			stopSelf();
		}
		@Override
		public void onNotifyProgress(int seekOffsetWrite, int contentLength) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onNotifyProgress");
			}
			mSeekOffsetWrite = seekOffsetWrite;
			mContentLength = contentLength;

			CacheRequest cacheRequestCurrent = mCacheRequestCurrent;
			VideoLoaderInterface videoLoader = mVideoLoader;
			if (cacheRequestCurrent == null || videoLoader.isNull()) {
			    return;
			}
            long currentTime = SystemClock.elapsedRealtime();
            if (currentTime - mLastNotifyProgressTime < NOTIFY_PROGRESS_INTERVAL_MS) {
                // 一定期間内に更新通知が来たら飛ばす
                return;
            }
		    NotifyProgressData notifyProgressData =
		        mRefNotifyProgressData.getAndSet(null);
		    if (notifyProgressData == null) {
		        // 前の値で更新処理中：今回は飛ばす
		        return;
		    }
		    mLastNotifyProgressTime = currentTime;
		    notifyProgressData.videoV = cacheRequestCurrent.videoV;
		    notifyProgressData.ecoType = videoLoader.getEcoType();
		    notifyProgressData.seekOffsetWrite = seekOffsetWrite;
		    notifyProgressData.contentLength = contentLength;
		    mHandler.obtainMessage(MSG_ID_VIDEO_LOADER_NOTIFY_PROGRESS,
		            notifyProgressData).sendToTarget();
		}
		@Override
		public void onOccurredError(VideoLoaderInterface streamLoader,
				String errorMessage) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onOccurredError");
			}
	    	// 開始時のNotification削除
			CacheRequest cacheRequestCurrent = mCacheRequestCurrent;
//			assert cacheRequestCurrent != null;
			if (cacheRequestCurrent != null) {
			    mNotificationController.cancelRunningCache(cacheRequestCurrent);

				mCacheRequestCurrent = null;
			}
			mHandler.obtainMessage(
					MSG_ID_VIDEO_LOADER_OCCURRED_ERROR, errorMessage)
					.sendToTarget();
		}
        @Override
        public void onRestarted(VideoLoaderInterface streamLoader) {
            // 何もしない
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, "VideoCacheService onRestarted");
            }
        }
	};

	public VideoCacheService() {
	    mHandlerThread = new HandlerThreadCompat("VideoCacheServiceHandler");
	    mHandlerThread.start();
	    mHandler = new HandlerWrapper(this, mHandlerThread.getLooper());
	}

	@Override
	public void onCreate() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "VideoCacheService onCreate");
        }
		super.onCreate();

        mSharedPreferences = Util.getDefaultSharedPreferencesMultiProcess(this);

        mNotificationController = NotificationController.getInstance(this);
//		mHttpClient = Util.createHttpClient();
	}

	@Override
	public void onStart(Intent intent, int startId) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, "VideoCacheService onStart");
		}
        handleStart(intent);
	}

	// API Level 5からのためoverrideは行わない
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "VideoCacheService onStartCommand");
        }
        handleStart(intent);
        return START_REDELIVER_INTENT;
    }

    private void handleStart(Intent intent) {
        if (intent == null) {
            // 無視
            return;
        }
//        startFirst(intent);
        // 作成済みbinderの処理が必要なため処理遅らせる
//        mHandler.obtainMessage(MSG_ID_START_FIRST, intent).sendToTarget();
        mHandler.obtainMessage(MSG_ID_ADD_START_CACHE,
                createCacheRequestFromIntent(intent)).sendToTarget();
    }

    @SuppressLint("NewApi")
	@Override
	public void onDestroy() {
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, "VideoCacheService onDestroy");
        }
		super.onDestroy();
		VideoLoaderInterface videoLoader = mVideoLoader;
		videoLoader.finish();
		mVideoLoader = VideoLoaderInterface.NullObject.getInstance();
//    	mHttpClient = null;

		mNotificationController.cancelAllRunningCache();

		mHandlerThread.quit();
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig) {
		super.onConfigurationChanged(newConfig);
	}

	@Override
	public IBinder onBind(Intent intent) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, "VideoCacheService onBind");
		}
//		startFirst(intent);
		// 作成済みbinderの処理が必要なため処理遅らせる
//		mHandler.obtainMessage(MSG_ID_START_FIRST, intent).sendToTarget();
        mHandler.obtainMessage(MSG_ID_ADD_START_CACHE,
                createCacheRequestFromIntent(intent)).sendToTarget();
		return mBinder;
	}

	@Override
	public void onRebind(Intent intent) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, "VideoCacheService onRebind");
		}
		super.onRebind(intent);
	}

	@Override
	public boolean onUnbind(Intent intent) {
		return super.onUnbind(intent);
//		return true;
	}

    @Override
    protected void finalize() throws Throwable {
    	try {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, "VideoCacheService#finalize start");
            }
    		super.finalize();
    	} finally {
    		VideoLoaderInterface videoLoader = mVideoLoader;
    		videoLoader.finish();
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, "VideoCacheService#finalize end");
            }
    	}
    }

//    private void startFirst(Intent intent) {
//    	assert mCacheRequestCurrent == null;
//    	CacheRequest cacheRequest = createCacheRequestFromIntent(intent);
//    	startCacheRequestCurrent(cacheRequest);
//    }

    private void startCacheRequestCurrent(CacheRequest cacheRequest) {
		try {
	    	assert cacheRequest != null;
			mCacheRequestCurrent = cacheRequest;
			VideoLoaderInterface videoLoader = createVideoLoader(
					cacheRequest, getApplicationContext());
			mVideoLoader = videoLoader;
			if (videoLoader.isNull()) {
				// TODO エラー処理
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, "createVideoLoader failed");
				}
				Context context = getApplicationContext();
				String message = context.getString(R.string.toast_cache_fail_start);
				if (cacheRequest.forceEco == ECO_TYPE_MID) {
                    // 中画質のない動画かもしれないのでメッセージ追加
                    message += " : " + context.getString(R.string.errormessage_mid_may_none);
				}
				Util.showErrorToast(context, message);
				mCacheRequestCurrent = null;
				return;
			}
			videoLoader.setEventListener(mVideoLoaderEventListener);
			videoLoader.startLoad();

			// Notificationでキャッシュ中の表示
			Notification notification = mNotificationController.createNotificationRunningCache(
			        cacheRequest, mCacheRequestQueue.size());
            mNotificationController.addRunningCache(cacheRequest, notification);

			final int numCallbacks = mCallbacks.beginBroadcast();
			for (int i = 0; i < numCallbacks; ++i) {
				try {
					mCallbacks.getBroadcastItem(i).onStart(
							cacheRequest.videoV,
							videoLoader.getEcoType());
				} catch (RemoteException e) {
					Log.e(LOG_TAG, e.toString(), e);
				}
			}
			mCallbacks.finishBroadcast();
		} catch (ClientProtocolException e) {
			// TODO エラー処理
			Log.e(LOG_TAG, e.toString(), e);
			Util.showErrorToast(getApplicationContext(),
					R.string.toast_cache_fail_start);
			mCacheRequestCurrent = null;
		} catch (IOException e) {
			// TODO エラー処理
			Log.e(LOG_TAG, e.toString(), e);
			Util.showErrorToast(getApplicationContext(),
					R.string.toast_cache_fail_start);
			mCacheRequestCurrent = null;
		}
    }

    private void addCacheRequest(CacheRequest cacheRequest) {
        mCacheRequestQueue.add(cacheRequest);

        // Notification更新
        mRequestNotificationStartVideoCacheRealert = true;

        // 表示分かりづらいのでToastでも表示
        String text = createTextQueueRunningCache(cacheRequest.forceEco,
                cacheRequest.videoV);
        Util.showInfoToast(this, text);
    }

    private String createTextQueueRunningCache(int forceEco, String videoV) {
        int resTitle;
        if (forceEco == ECO_TYPE_LOW) {
            resTitle = R.string.toast_add_start_cache_force_low;
        } else if (forceEco == ECO_TYPE_MID) {
            resTitle = R.string.toast_add_start_cache_force_mid;
        } else {
            resTitle = R.string.toast_add_start_cache;
        }
        return getString(resTitle, videoV);
    }

    private VideoLoaderInterface createVideoLoader(CacheRequest cacheRequest,
			Context context) throws ClientProtocolException, IOException {
        return createVideoLoader(cacheRequest.videoV,
        		cacheRequest.cookieUserSession,
        		cacheRequest.forceEco,
        		context);
	}

	private VideoLoaderInterface createVideoLoader(String videoV,
			String cookieUserSession, int forceEco,
			Context context) throws ClientProtocolException, IOException {

		DefaultHttpClient httpClient = Util.createHttpClient();
		final String cookieNicoHistory;
		final String getflvUrl;
		try {
    		httpClient.getCookieStore().clear();
            cookieNicoHistory = NicoroAPIManager.getCookieNicoHistory(
            		httpClient, videoV, cookieUserSession, forceEco, null);

    		httpClient.getCookieStore().clear();
    		NicoroAPIManager.ParseGetFLV parseGetFLV;
    		if (forceEco == ECO_TYPE_LOW) {
    			parseGetFLV = new NicoroAPIManager.ParseGetFLVLow();
    		} else if (forceEco == ECO_TYPE_MID) {
                parseGetFLV = new NicoroAPIManager.ParseGetFLVMid();
    		} else {
    			parseGetFLV = new NicoroAPIManager.ParseGetFLV();
    		}
    		parseGetFLV.initialize(httpClient,
    				videoV,
    				cookieUserSession, null);
    		getflvUrl = parseGetFLV.getUrl();
		} finally {
		    httpClient.getConnectionManager().shutdown();
		}
        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append(" url=").append(getflvUrl)
        			.append(" videoV=").append(videoV)
        			.append(" cookieNicoHistory=").append(cookieNicoHistory)
        			.append(" cookieUserSession=").append(cookieUserSession).toString());
        }
		VideoLoaderInterface videoLoader = VideoLoaderInterface.NullObject.getInstance();
        if (getflvUrl != null && cookieNicoHistory != null) {
        	videoLoader = new VideoLoader(getflvUrl, cookieNicoHistory, videoV,
        			context, cookieUserSession,
        			android.os.Process.THREAD_PRIORITY_DEFAULT);
        }
		return videoLoader;
	}

    private CacheRequest createCacheRequestFromIntent(Intent intent) {
        String cookieUserSession = NicoroConfig.getCookieUserSession(mSharedPreferences);
        return mNotificationController.createCacheRequest(
                intent.getStringExtra(PlayerConstants.INTENT_NAME_VIDEO_NUMBER),
                cookieUserSession,
                intent.getIntExtra(PlayerConstants.INTENT_NAME_FORCE_ECO, ECO_TYPE_HIGH));
    }

	void startNextCacheOrEnd() {
        CacheRequest cacheRequest = mCacheRequestQueue.poll();
        if (cacheRequest == null) {
            stopSelf();
        } else {
            startCacheRequestCurrent(cacheRequest);
        }
	}

    public static boolean isAnyCacheRunning(String videoNumber, IVideoCacheService videoCacheService) {
        try {
            int state = videoCacheService.getCacheState(videoNumber, ECO_TYPE_HIGH);
            if (state == CACHE_STATE_RUNNING
                    || state == CACHE_STATE_WAIT_START) {
                return true;
            }
            state = videoCacheService.getCacheState(videoNumber, ECO_TYPE_MID);
            if (state == CACHE_STATE_RUNNING
                    || state == CACHE_STATE_WAIT_START) {
                return true;
            }
            state = videoCacheService.getCacheState(videoNumber, ECO_TYPE_LOW);
            if (state == CACHE_STATE_RUNNING
                    || state == CACHE_STATE_WAIT_START) {
                return true;
            }
        } catch (RemoteException e) {
            Log.e(LOG_TAG, e.toString(), e);
        }
        return false;
    }
}
