package jp.sourceforge.nicoro;

import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

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

import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;

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

public class VideoCacheService extends Service {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG && false;
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;
	
	public static final String INTENT_NAME_VIDEO_URL = "VIDEO_URL";
	public static final String INTENT_NAME_VIDEO_NUMBER = "VIDEO_NUMBER";
//	public static final String INTENT_NAME_COOKIE_NICO_HISTORY = "COOKIE_NICO_HISTORY";
	public static final String INTENT_NAME_COOKIE_USER_SESSION = "COOKIE_USER_SESSION";
	
	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_OCCURRED_ERROR = 2;
	private static final int MSG_ID_VIDEO_LOADER_STOP_CACHE_CURRENT = 3;
	private static final int MSG_ID_VIDEO_LOADER_STOP_CACHE_QUEUE = 4;
	
	private static class CacheRequest {
		public String videoNumber;
		public String cookieUserSession;
		public int notificationIDStartVideoCache;
		public int notificationIDFinishVideoCache;
	}
	
	private CacheRequest createCacheRequest(String vn, String cus) {
		CacheRequest cacheRequest = new CacheRequest();
		cacheRequest.videoNumber = vn;
		cacheRequest.cookieUserSession = cus;
		cacheRequest.notificationIDStartVideoCache = mNotificationIDCounter;
    	++mNotificationIDCounter;
    	cacheRequest.notificationIDFinishVideoCache = mNotificationIDCounter;
    	++mNotificationIDCounter;
    	assert cacheRequest.notificationIDStartVideoCache % 2 == 0;
    	return cacheRequest;
	}
	
	private VideoLoader mVideoLoader;
	private CacheRequest mCacheRequestCurrent = null;
	private LinkedList<CacheRequest> mCacheRequestQueue = new LinkedList<CacheRequest>();
	
	private int mNotificationIDCounter = 0;
	
	private DefaultHttpClient mHttpClient;
	
	private int mSeekOffsetWrite = -1;
	private int mContentLength = -1;
	
	private Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "handleMessage msg.what=" + msg.what);
			}
			String videoNumber;
			switch (msg.what) {
			case MSG_ID_ADD_START_CACHE:
				CacheRequest cacheRequest = (CacheRequest) msg.obj;
				if (mCacheRequestCurrent == null) {
					mCacheRequestCurrent = cacheRequest;
					startCacheRequestCurrent();
				} else {
					mCacheRequestQueue.add(cacheRequest);
				}
				break;
			case MSG_ID_VIDEO_LOADER_FINISHED:
				mCacheRequestCurrent = mCacheRequestQueue.poll();
				if (mCacheRequestCurrent != null) {
					startCacheRequestCurrent();
				}
				break;
			case MSG_ID_VIDEO_LOADER_OCCURRED_ERROR:
				Util.showErrorToast(getApplicationContext(), (String) msg.obj);
				
				// エラー時も次のキャッシュ開始
				mCacheRequestCurrent = mCacheRequestQueue.poll();
				if (mCacheRequestCurrent != null) {
					startCacheRequestCurrent();
				}
				break;
			case MSG_ID_VIDEO_LOADER_STOP_CACHE_CURRENT:
				// 次のキャッシュ開始
				mCacheRequestCurrent = mCacheRequestQueue.poll();
				if (mCacheRequestCurrent != null) {
					startCacheRequestCurrent();
				}
				break;
			case MSG_ID_VIDEO_LOADER_STOP_CACHE_QUEUE:
				// 待ち行列にあれば削除
				videoNumber = (String) msg.obj;
				for (Iterator<CacheRequest> it = mCacheRequestQueue.iterator(); it.hasNext(); ) {
					CacheRequest cr = it.next();
					if (cr.videoNumber.equals(videoNumber)) {
						it.remove();
					}
				}
				break;
			default:
				assert false : msg.what;
				break;
			}
		}
	};
	
	private RemoteCallbackList<IVideoCacheServiceCallback> mCallbacks =
		new RemoteCallbackList<IVideoCacheServiceCallback>();
	private final IVideoCacheService.Stub mBinder = new IVideoCacheService.Stub() {
		@Override
		public int getContentLength(String videoNumber) throws RemoteException {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "getContentLength() mContentLength=" + mContentLength);
			}
			// TODO mCacheRequestCurrentのチェックも同一メインスレッドで行わないとまずい？
			if (mCacheRequestCurrent != null && mCacheRequestCurrent.videoNumber.equals(videoNumber)) {
				return mContentLength;
			} else {
				return -1;
			}
		}
		@Override
		public int getProgress(String videoNumber) throws RemoteException {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "getProgress() mSeekOffsetWrite=" + mSeekOffsetWrite);
			}
			if (mCacheRequestCurrent != null && mCacheRequestCurrent.videoNumber.equals(videoNumber)) {
				return mSeekOffsetWrite;
			} else {
				return -1;
			}
		}
		@Override
		public void stopCache(String videoNumber) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "stopCache()");
			}
			if (mCacheRequestCurrent != null && mCacheRequestCurrent.videoNumber.equals(videoNumber)) {
				mVideoLoader.finish();

		    	// 開始時のNotification削除
				NotificationManager notificationManager =
					(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
				notificationManager.cancel(mCacheRequestCurrent.notificationIDStartVideoCache);
				
				mCacheRequestCurrent = null;
				// 次のキャッシュがあれば開始
				mHandler.sendEmptyMessage(MSG_ID_VIDEO_LOADER_STOP_CACHE_CURRENT);
			} else {
				// 待ち行列にあれば削除
				Message message = mHandler.obtainMessage(
						MSG_ID_VIDEO_LOADER_STOP_CACHE_QUEUE, videoNumber);
				mHandler.sendMessage(message);
			}
		}
		@Override
		public int getCacheState(String videoNumber) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "getCacheState()");
			}
			if (mCacheRequestCurrent != null && mCacheRequestCurrent.videoNumber.equals(videoNumber)) {
				return CACHE_STATE_RUNNING;
			} else {
				for (CacheRequest cr : mCacheRequestQueue) {
					if (videoNumber.equals(cr.videoNumber)) {
						return CACHE_STATE_WAIT_START;
					}
				}
				return CACHE_STATE_NOT_RUN;
			}
		}
		@Override
		public void addStartCache(String videoNumber, String cookieUserSession) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "addStartCache() videoNumber=" + videoNumber
						+ " cookieUserSession=" + cookieUserSession);
			}
			CacheRequest cacheRequest = createCacheRequest(videoNumber, cookieUserSession);
			Message message = mHandler.obtainMessage(
					MSG_ID_ADD_START_CACHE, cacheRequest);
			mHandler.sendMessage(message);
		}
		
		@Override
		public void addListener(IVideoCacheServiceCallback callback) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "addListener() callback=" + callback);
			}
			mCallbacks.register(callback);
		}
		@Override
		public void removeListener(IVideoCacheServiceCallback callback) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "removeListener() callback=" + callback);
			}
			mCallbacks.unregister(callback);
		}
	};
	
	private final VideoLoader.EventListener mVideoLoaderEventListener =
		new VideoLoader.EventListener() {
		@Override
		public void onCached(VideoLoader streamLoader) {
			// 何もせず
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onCached");
			}
		}
		@Override
		public void onFinished(VideoLoader streamLoader) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onFinished");
			}
			assert mCacheRequestCurrent.videoNumber.equals(streamLoader.getVideoNumber());
			// キャッシュ完了のNotification表示
			setNotificationFinishCache(mCacheRequestCurrent);
			
			final int numCallbacks = mCallbacks.beginBroadcast();
			for (int i = 0; i < numCallbacks; ++i) {
				try {
					mCallbacks.getBroadcastItem(i).onFinished(
							mCacheRequestCurrent.videoNumber);
				} catch (RemoteException e) {
					Log.e(LOG_TAG, e.getMessage(), e);
				}
			}
			mCallbacks.finishBroadcast();
			
			mCacheRequestCurrent = null;
			mHandler.sendEmptyMessage(MSG_ID_VIDEO_LOADER_FINISHED);
			
//			// 自分自身の終了
//			stopSelf();
		}
		@Override
		public void onNotifyProgress(int seekOffsetWrite, int contentLength) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onNotifyProgress");
			}
			mSeekOffsetWrite = seekOffsetWrite;
			mContentLength = contentLength;
			
			final int numCallbacks = mCallbacks.beginBroadcast();
			for (int i = 0; i < numCallbacks; ++i) {
				try {
					mCallbacks.getBroadcastItem(i).onNotifyProgress(
							mCacheRequestCurrent.videoNumber, seekOffsetWrite, contentLength);
				} catch (RemoteException e) {
					Log.e(LOG_TAG, e.getMessage(), e);
				}
			}
			mCallbacks.finishBroadcast();
		}
		@Override
		public void onOccurredError(VideoLoader streamLoader,
				String errorMessage) {
			if (DEBUG_LOGV) {
				Log.v(LOG_TAG, "VideoCacheService onOccurredError");
			}
	    	// 開始時のNotification削除
			NotificationManager notificationManager =
				(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
			notificationManager.cancel(mCacheRequestCurrent.notificationIDStartVideoCache);
			
			mCacheRequestCurrent = null;
			Message message = mHandler.obtainMessage(
					MSG_ID_VIDEO_LOADER_OCCURRED_ERROR, errorMessage);
			mHandler.sendMessage(message);
		}
	};
	
	@Override
	public void onCreate() {
		super.onCreate();
		
		mHttpClient = Util.createHttpClient();
	}
	
	@Override
	public void onStart(Intent intent, int startId) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, "VideoCacheService onStart");
		}
		super.onStart(intent, startId);
		startFirst(intent);
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
    	if (mVideoLoader != null) {
    		mVideoLoader.finish();
    		mVideoLoader = null;
    	}
    	mHttpClient = null;
		NotificationManager notificationManager =
			(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//		notificationManager.cancel(NOTIFICATION_ID_START_VIDEO_CACHE);
		for (int i = 0; i < mNotificationIDCounter; i += 2) {
			notificationManager.cancel(i);
		}
	}
	
	@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);
		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 {
    		super.finalize();
    	} finally {
        	if (mVideoLoader != null) {
        		mVideoLoader.finish();
        	}
    	}
    }
    
    private void startFirst(Intent intent) {
    	assert mCacheRequestCurrent == null;
    	mCacheRequestCurrent = createCacheRequest(
    			intent.getStringExtra(
    	    			AbstractNicoroPlayer.INTENT_NAME_VIDEO_NUMBER),
    			intent.getStringExtra(
    	    			INTENT_NAME_COOKIE_USER_SESSION));
    	startCacheRequestCurrent();
    }
    
    private void startCacheRequestCurrent() {
		try {
			mVideoLoader = createVideoLoader(
					mCacheRequestCurrent, getApplicationContext());
			if (mVideoLoader == null) {
				// TODO エラー処理
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, "createVideoLoader failed");
				}
				Util.showErrorToast(getApplicationContext(),
						R.string.toast_cache_fail_start);
				mCacheRequestCurrent = null;
				return;
			}
			mVideoLoader.setEventListener(mVideoLoaderEventListener);
			mVideoLoader.startLoad();
			
			// Notificationでキャッシュ中の表示
			setNotificationStartCache(mCacheRequestCurrent);
			
		} catch (ClientProtocolException e) {
			// TODO エラー処理
			Log.e(LOG_TAG, e.getMessage(), e);
			Util.showErrorToast(getApplicationContext(),
					R.string.toast_cache_fail_start);
			mCacheRequestCurrent = null;
		} catch (IOException e) {
			// TODO エラー処理
			Log.e(LOG_TAG, e.getMessage(), e);
			Util.showErrorToast(getApplicationContext(),
					R.string.toast_cache_fail_start);
			mCacheRequestCurrent = null;
		}
    }
    
    private void setNotificationStartCache(CacheRequest cacheRequest) {
		// とりあえずブラウザ画面起動
		Intent intent = new Intent(
				Intent.ACTION_VIEW,
				Uri.parse("http://www.nicovideo.jp/watch/" + cacheRequest.videoNumber),
				getApplicationContext(), NicoroWebBrowser.class);
		intent.addCategory(Intent.CATEGORY_BROWSABLE);
		
		Notification notification = new Notification(
				android.R.drawable.stat_notify_sync,
//				R.drawable.icon,
//				0,
				"キャッシュ開始：" + cacheRequest.videoNumber,
				System.currentTimeMillis());
//		notification.flags = Notification.FLAG_ONGOING_EVENT;
//		Intent intentNotification = new Intent(getApplicationContext(), NicoroWebBrowser.class);
		PendingIntent pendingIntent = PendingIntent.getActivity(
				getApplicationContext(),
				0,
//				intentNotification,
				intent,
//				PendingIntent.FLAG_NO_CREATE);
				PendingIntent.FLAG_UPDATE_CURRENT);
		notification.setLatestEventInfo(getApplicationContext(),
				"キャッシュ開始：" + cacheRequest.videoNumber,
				cacheRequest.videoNumber + "のキャッシュを開始しました",
				pendingIntent/*null*/);
		NotificationManager notificationManager =
			(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.notify(
				cacheRequest.notificationIDStartVideoCache,
				notification);
    }

    private void setNotificationFinishCache(CacheRequest cacheRequest) {
    	// 開始時のNotification削除
		NotificationManager notificationManager =
			(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
		notificationManager.cancel(cacheRequest.notificationIDStartVideoCache);
    	
		// とりあえずブラウザ画面起動
		Intent intent = new Intent(
				Intent.ACTION_VIEW,
				Uri.parse("http://www.nicovideo.jp/watch/" + cacheRequest.videoNumber),
				getApplicationContext(), NicoroWebBrowser.class);
		intent.addCategory(Intent.CATEGORY_BROWSABLE);
		
		Notification notification = new Notification(
				R.drawable.icon,
				cacheRequest.videoNumber + " キャッシュ完了",
				System.currentTimeMillis());
		PendingIntent pendingIntent = PendingIntent.getActivity(
				getApplicationContext(),
				0,
				intent,
				PendingIntent.FLAG_UPDATE_CURRENT);
		notification.setLatestEventInfo(getApplicationContext(),
				cacheRequest.videoNumber + " キャッシュ完了",
				cacheRequest.videoNumber + "が出番を待っているようです",
				pendingIntent);
//		notification.flags = Notification.FLAG_NO_CLEAR;
		notification.flags = Notification.FLAG_AUTO_CANCEL;
		notificationManager.notify(
				cacheRequest.notificationIDFinishVideoCache,
				notification);
    }

	private VideoLoader createVideoLoader(Intent intent,
			Context context) throws ClientProtocolException, IOException {
//        String url = intent.getStringExtra(INTENT_NAME_VIDEO_URL);
        String videoNumber = intent.getStringExtra(AbstractNicoroPlayer.INTENT_NAME_VIDEO_NUMBER);
//        String cookieNicoHistory = intent.getStringExtra(INTENT_NAME_COOKIE_NICO_HISTORY);
        String cookieUserSession = intent.getStringExtra(INTENT_NAME_COOKIE_USER_SESSION);
        return createVideoLoader(videoNumber, cookieUserSession,
        		context);
	}

	private VideoLoader createVideoLoader(CacheRequest cacheRequest,
			Context context) throws ClientProtocolException, IOException {
        return createVideoLoader(cacheRequest.videoNumber,
        		cacheRequest.cookieUserSession,
        		context);
	}
	
	private VideoLoader createVideoLoader(String videoNumber, String cookieUserSession,
			Context context) throws ClientProtocolException, IOException {

//		try {
//			Thread.sleep(5000L);
//		} catch (InterruptedException e) {
//			Log.w(LOG_TAG, e.getMessage(), e);
//		}
		
		DefaultHttpClient httpClient = Util.createHttpClient();
		httpClient.getCookieStore().clear();
        String cookieNicoHistory = NicoroAPIManager.getCookieNicoHistory(
        		httpClient, videoNumber, cookieUserSession, null);
        
//		try {
//			Thread.sleep(5000L);
//		} catch (InterruptedException e) {
//			Log.w(LOG_TAG, e.getMessage(), e);
//		}
        
		httpClient.getCookieStore().clear();
		NicoroAPIManager.ParseGetFLV parseGetFLV = new NicoroAPIManager.ParseGetFLV();
		parseGetFLV.initialize(httpClient,
				videoNumber,
				cookieUserSession, null);
		final String getflvUrl = parseGetFLV.getUrl();
        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append(" url=").append(getflvUrl)
        			.append(" videoNumber=").append(videoNumber)
        			.append(" cookieNicoHistory=").append(cookieNicoHistory)
        			.append(" cookieUserSession=").append(cookieUserSession).toString());
        }
		VideoLoader videoLoader = null;
        if (getflvUrl != null && cookieNicoHistory != null) {
        	videoLoader = new VideoLoader(getflvUrl, cookieNicoHistory, videoNumber,
        			context, cookieUserSession);
        }
		return videoLoader;
	}
}
