package jp.sourceforge.nicoro;

import java.text.FieldPosition;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import jp.sourceforge.nicoro.R;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.ToggleButton;

public abstract class AbstractNicoroPlayer extends Activity {
	private static final boolean DEBUG_LOGV = Release.IS_DEBUG && true;
	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_COOKIE = "COOKIE";
	public static final String INTENT_NAME_VIDEO_NUMBER = "VIDEO_NUMBER";
	public static final String INTENT_NAME_MESSAGE_URL = "MESSAGE_URL";
	public static final String INTENT_NAME_THREAD_ID = "THREAD_ID";
	public static final String INTENT_NAME_USER_ID = "USER_ID";
	public static final String INTENT_NAME_THREAD_KEY = "THREAD_KEY";
	public static final String INTENT_NAME_FORCE_184 = "FORCE_184";
	public static final String INTENT_NAME_COOKIE_USER_SESSION = "COOKIE_USER_SESSION";
	public static final String INTENT_NAME_FORCE_LOW = "FORCE_LOW";

	protected static final int MSG_ID_MESSAGE_FINISHED = 0;
	protected static final int MSG_ID_MESSAGE_OCCURRED_ERROR = 1;
	protected static final int MSG_ID_THUMBINFO_FINISHED = 2;
	protected static final int MSG_ID_THUMBINFO_OCCURRED_ERROR = 3;
	protected static final int MSG_ID_VIDEO_OCCURRED_ERROR = 4;
	protected static final int MSG_ID_VIDEO_NOTIFY_PROGRESS = 5;
	protected static final int MSG_ID_INFO_TIME_UPDATE = 6;
	protected static final int MSG_ID_PLAY_ERROR = 7;
	protected static final int MSG_ID_MESSAGE_FORK_FINISHED = 8;
	protected static final int MSG_ID_MESSAGE_FORK_OCCURRED_ERROR = 9;
	protected static final int MSG_ID_ENABLE_SEEK_BAR = 10;
//	protected static final int MSG_ID_INFO_CLOCK_UPDATE = 11;
	protected static final int MSG_ID_INFO_PLAY_DATA_UPDATE = 12;
	protected static final int MSG_ID_THUMBINFO_FINISHED_NEW = 13;
	protected static final int MSG_ID_THUMBINFO_OCCURRED_ERROR_NEW = 14;
	protected static final int MSG_ID_SUB_OFFSET = 0x100;

	public static final int REAL_PLAYER_WIDTH_PX_4_3 = 512;
	public static final int REAL_PLAYER_HEIGHT_PX_4_3 = 384;
//	public static final int REAL_PLAYER_HEIGHT_PX_16_9 = 288;
	public static final int REAL_PLAYER_WIDTH_PX_15_9 = 640;
	public static final int REAL_PLAYER_WIDTH_PX_16_9 = 684; // 682.66

	protected static final String INFO_TIME_DEFAULT = "000:00";

	private static final long INTERVAL_TIME_UPDATE = 100L;

	private static final String KEY_SAVE_CURRENT_PLAY_TIME = "KEY_SAVE_CURRENT_PLAY_TIME";
	private static final String KEY_SAVE_IS_PAUSE_PLAY = "KEY_SAVE_IS_PAUSE_PLAY";

	protected VideoLoader mVideoLoader;
	protected MessageLoader mMessageLoader;
	protected MessageLoaderFork mMessageLoaderFork;
	protected RelativeLayout mProgressGroup;
	protected ProgressBar mProgressBar;
	protected VariableLabelView mProgressTextVideo;
	protected VariableLabelView mProgressTextThumbinfo;
	protected VariableLabelView mProgressTextMessage;
	protected VariableLabelView mProgressTextMessageFork;
	protected VariableLabelView mProgressTextInner;
//	protected VariableLabelView mInfoClock;
	protected VariableLabelView mInfoCountPlay;
	protected VariableLabelView mInfoCountComment;
	protected VariableLabelView mInfoCountMylist;
	protected VariableLabelView mInfoTitle;
	protected VariableLabelView mInfoDescription;
	protected VariableLabelView mInfoPlayData;
	protected VariableLabelView mInfoTime;
	protected VariableLabelView mControllerTime;
	protected ViewGroup mPlayerInfo;
	private ImageButton mButtonPause;
	private ImageButton mButtonFromBegin;
	private ToggleButton mButtonCommentOnOff;
	protected SeekBar mSeekBar;
	private ViewGroup mPlayerController;
	protected ThumbInfo mThumbInfo;

	private PlayerInfoControllerManager mPlayerInfoControllerManager;

	protected static class MessageData {
		public boolean mIsMessageOk;
		public List<MessageChat> mChatsWait = null;
		public List<MessageChat> mChatsRunningNaka = null;
		public List<MessageChat> mChatsRunningShita = null;
		public List<MessageChat> mChatsRunningUe = null;
	}
	protected MessageData mMessageData = new MessageData();
	protected MessageData mMessageDataFork = new MessageData();

    protected Rational mRatinalCurrentPlayTime = new Rational();

    protected volatile MessageHandler mHandler;

//    protected Matrix mMatrixMessage = new Matrix();
//    protected Random mRandom = new Random();
//    protected Paint mPaintText = new Paint();
    protected boolean mMessageDisable;

    protected MessageChatController mMessageChatController = new MessageChatController();

    private boolean mIsTrackingSeekBar;

    protected SharedPreferences mSharedPreferences;

    private Calendar mClock = Calendar.getInstance();

    protected boolean mOnResumed;
    private boolean mFromOnPause;
	protected boolean mDidStartPlay = false;

    private boolean mIsRestored;
    private Rational mSaveStateCurrentPlayTime;
    private boolean mSaveIsPausePlay;

    protected boolean mShowHintToast;

    protected abstract class MessageHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			if (mHandler == null) {
				if (DEBUG_LOGD) {
					Log.d(LOG_TAG, Log.buf().append("Activity was destroyed. ignore message=")
							.append(msg.toString()).toString());
				}
				return;
			}
			switch (msg.what) {
			case MSG_ID_MESSAGE_FINISHED:
				mProgressTextMessage.setText(
						getString(R.string.progress_message_finished));
				mProgressTextMessage.clearAnimation();
				mMessageData.mIsMessageOk = true;
				mMessageData.mChatsWait =
						new LinkedList<MessageChat>(mMessageLoader.getChats());
				mMessageData.mChatsRunningNaka =
						new LinkedList<MessageChat>();
				mMessageData.mChatsRunningShita =
					new LinkedList<MessageChat>();
				mMessageData.mChatsRunningUe =
					new LinkedList<MessageChat>();
				if (canStartPlay()) {
					startPlay();
				}
				break;
			case MSG_ID_MESSAGE_OCCURRED_ERROR:
				mProgressTextMessageFork.setText(
						getString(R.string.progress_message_error));
				mProgressTextMessageFork.clearAnimation();
				Util.showErrorDialog(AbstractNicoroPlayer.this,
						(String) msg.obj, true);
				break;
			case MSG_ID_THUMBINFO_FINISHED:
				mProgressTextThumbinfo.setText(
						getString(R.string.progress_thumbinfo_finished));
				mProgressTextThumbinfo.clearAnimation();
				mMessageLoader.setVideoLength((String) msg.obj);
				mMessageLoader.startLoad();
				mMessageLoaderFork.setVideoLength((String) msg.obj);
				mMessageLoaderFork.startLoad();

				mInfoTitle.setText(mThumbInfo.getTitle());
				mInfoDescription.setText(mThumbInfo.getDescription());
				break;
			case MSG_ID_THUMBINFO_OCCURRED_ERROR:
				mProgressTextThumbinfo.setText(
						getString(R.string.progress_thumbinfo_error));
				mProgressTextThumbinfo.clearAnimation();
				Util.showErrorDialog(AbstractNicoroPlayer.this,
						(String) msg.obj, true);
				break;
			case MSG_ID_VIDEO_OCCURRED_ERROR:
				mProgressTextVideo.setText(
						getString(R.string.progress_video_error));
				mProgressTextVideo.clearAnimation();
				Util.showErrorDialog(AbstractNicoroPlayer.this,
						(String) msg.obj, true);
				break;
			case MSG_ID_VIDEO_NOTIFY_PROGRESS:
				StringBuilder textData = mProgressTextVideo.getTextBuilder();
				textData.setLength(0);
				textData.append(msg.arg1).append('/').append(msg.arg2);
				mProgressTextVideo.notifyUpdateText();
				mProgressTextVideo.clearAnimation();
				mSeekBar.setSecondaryProgress(
						(int) ((long) mSeekBar.getMax() * msg.arg1 / msg.arg2));
				break;
			case MSG_ID_INFO_TIME_UPDATE:
				if (!isFinishing()) {
					StringBuilder infoTimeData = mInfoTime.getTextBuilder();
					infoTimeData.setLength(0);
					appendCurrentPlayTime(infoTimeData).append('/')
						.append(mThumbInfo.getFormattedLengthForPlayer());
					mInfoTime.notifyUpdateText();

					StringBuilder controllerTimeData = mControllerTime.getTextBuilder();
					controllerTimeData.replace(0, controllerTimeData.length(),
							infoTimeData.toString());
					mControllerTime.notifyUpdateText();

					mSeekBar.setMax(mThumbInfo.getLengthBySecond());
					if (!mIsTrackingSeekBar && mSeekBar.isEnabled()) {
						// TODO: 二重呼び出しでちょっと無駄
						getCurrentPositionVideoPlay(mRatinalCurrentPlayTime);
						mSeekBar.setProgress(
								mRatinalCurrentPlayTime.num / mRatinalCurrentPlayTime.den);
					}

					// 繰り返し
					mHandler.sendEmptyMessageDelayed(
							MSG_ID_INFO_TIME_UPDATE, INTERVAL_TIME_UPDATE);
				}
				break;
			case MSG_ID_PLAY_ERROR:
				Util.showErrorDialog(AbstractNicoroPlayer.this,
						(String) msg.obj, true);
				break;
			case MSG_ID_MESSAGE_FORK_FINISHED:
				mProgressTextMessageFork.setText(
						getString(R.string.progress_message_fork_finished));
				mProgressTextMessageFork.clearAnimation();
				mMessageDataFork.mIsMessageOk = true;
				mMessageDataFork.mChatsWait =
						new LinkedList<MessageChat>(mMessageLoaderFork.getChats());
				mMessageDataFork.mChatsRunningNaka =
						new LinkedList<MessageChat>();
				mMessageDataFork.mChatsRunningShita =
					new LinkedList<MessageChat>();
				mMessageDataFork.mChatsRunningUe =
					new LinkedList<MessageChat>();
				if (canStartPlay()) {
					startPlay();
				}
				break;
			case MSG_ID_MESSAGE_FORK_OCCURRED_ERROR:
				mProgressTextMessageFork.setText(
						getString(R.string.progress_message_fork_error));
				mProgressTextMessageFork.clearAnimation();
				// TODO 投稿者コメントのエラーはとりあえず無視？
				Log.w(LOG_TAG, Log.buf().append("fork=\"1\" message load failed:")
						.append((String) msg.obj).toString());
				mMessageDataFork.mIsMessageOk = true;
				break;
			case MSG_ID_ENABLE_SEEK_BAR:
				setEnabledSeekController(true);
				break;
//			case MSG_ID_INFO_CLOCK_UPDATE:
//				StringBuilder infoClockData = mInfoClock.getTextBuilder();
//				// TODO 微妙に処理無駄
//				infoClockData.delete(0, infoClockData.length());
////				infoClockData.append(getString(R.string.info_clock));
//				infoClockData.append("[ ");
//				mClock.setTimeInMillis(System.currentTimeMillis());
//				final int hour = mClock.get(Calendar.HOUR);
//				final int minute = mClock.get(Calendar.MINUTE);
//				final int second = mClock.get(Calendar.SECOND);
//				if (hour < 10) {
//					infoClockData.append('0');
//				}
//				infoClockData.append(hour).append(':');
//				if (minute < 10) {
//					infoClockData.append('0');
//				}
//				infoClockData.append(minute).append(':');
//				if (second < 10) {
//					infoClockData.append('0');
//				}
//				infoClockData.append(second);
//				infoClockData.append(" ]");
//				mInfoClock.notifyUpdateText();
//
//				// 繰り返し
//				{
//					long now = SystemClock.uptimeMillis();
//					long next = now + (1000 - now % 1000);
//					mHandler.sendEmptyMessageAtTime(
//							MSG_ID_INFO_CLOCK_UPDATE, next);
//				}
//				break;
			case MSG_ID_INFO_PLAY_DATA_UPDATE:
				StringBuilder builderInfoPlayData = mInfoPlayData.getTextBuilder();
				builderInfoPlayData.append(getString(R.string.info_play_data_pre));
				if (mVideoLoader.isLow()) {
					builderInfoPlayData
						.append("low-")
						.append(mVideoLoader.getMovieType());
				} else {
					builderInfoPlayData
						.append(mThumbInfo.getMovieType());
				}
				builderInfoPlayData.append(' ');
				appendVideoResolution(builderInfoPlayData).append(' ');
				appendPlayerInfo(builderInfoPlayData);
				mInfoPlayData.notifyUpdateText();
				break;
			case MSG_ID_THUMBINFO_FINISHED_NEW:
				mThumbInfo = (ThumbInfo) msg.obj;
				mHandler.obtainMessage(MSG_ID_THUMBINFO_FINISHED,
						mThumbInfo.getLength()).sendToTarget();
				break;
			case MSG_ID_THUMBINFO_OCCURRED_ERROR_NEW:
				String errorMessage = (String) msg.obj;
				mHandler.obtainMessage(MSG_ID_THUMBINFO_OCCURRED_ERROR,
						errorMessage).sendToTarget();
				break;
			default:
				assert false : msg.what;
				break;
			}
		}
    }

    /**
     * すべての再生準備が整ったか確認する（動画ファイルのダウンロード、コメントファイルのダウンロード等）
     * @return
     */
	protected abstract boolean canStartPlay();
	protected abstract StringBuilder appendCurrentPlayTime(StringBuilder builder);

	/**
	 * 動画の再生位置を取得する
	 * @param rational 位置情報（秒単位の分数）
	 */
	protected abstract void getCurrentPositionVideoPlay(Rational rational);
	/**
	 * 音声の再生位置を取得する
	 * @param rational 位置情報（秒単位の分数）
	 */
	protected abstract void getCurrentPositionAudioPlay(Rational rational);
	/**
	 * 動画のデコード位置を取得する
	 * @param rational 位置情報（秒単位の分数）
	 */
	protected abstract void getCurrentPositionVideoDecode(Rational rational);
	/**
	 * 音声のデコード位置を取得する
	 * @param rational 位置情報（秒単位の分数）
	 */
	protected abstract void getCurrentPositionAudioDecode(Rational rational);

	/**
	 * 再生状態と一時停止状態を切り替える
	 * @return 成否
	 */
	protected abstract boolean switchPausePlay();
	/**
	 * 再生を一時停止する
	 */
	protected abstract void pausePlay();
	/**
	 * 一時停止していた再生を再開する
	 */
	protected abstract void restartPlay();
	/**
	 * 再生が一時停止中か確認する
	 * @return
	 */
	protected abstract boolean isPausePlay();

	/**
	 * 秒単位でシークする
	 * @param second
	 */
	protected abstract void seekBySecond(int second);

	protected abstract StringBuilder appendVideoResolution(StringBuilder builder);
	protected abstract StringBuilder appendPlayerInfo(StringBuilder builder);

	protected void startPlay() {
    	mProgressGroup.setVisibility(View.GONE);
    	if (!mPlayerInfoControllerManager.isPlayerInfoShown()) {
    	    mPlayerInfoControllerManager.showPlayerInfo();
    	}

		mHandler.sendEmptyMessage(MSG_ID_INFO_TIME_UPDATE);

//		mHandler.sendEmptyMessage(MSG_ID_INFO_CLOCK_UPDATE);

		mInfoCountPlay.getTextBuilder()
			.append(getString(R.string.info_count_play))
			.append(mThumbInfo.getViewCounter());
		mInfoCountPlay.notifyUpdateText();
		mInfoCountComment.getTextBuilder()
			.append(getString(R.string.info_count_comment))
			.append(mThumbInfo.getCommentNum());
		mInfoCountComment.notifyUpdateText();
		mInfoCountMylist.getTextBuilder()
			.append(getString(R.string.info_count_mylist))
			.append(mThumbInfo.getMylistCounter());
		mInfoCountMylist.notifyUpdateText();

		mMessageChatController.setDuration(mThumbInfo.getLengthByVpos());

    	mIsTrackingSeekBar = false;
		mDidStartPlay = true;
	}

	protected void postStartPlayIfIsRestored() {
    	if (mIsRestored) {
    		// 再開時は一時停止にしておく
			pausePlay();
			setButtonPauseImage();
//			if (mSaveIsPausePlay) {
    			// 再開できるようにコントローラ表示
			    mPlayerInfoControllerManager.showPlayerController();
//			}
        	if (mSaveStateCurrentPlayTime != null) {
				int second = mSaveStateCurrentPlayTime.num / mSaveStateCurrentPlayTime.den;
				if (second != 0) {	// 0のときはシーク不要
					// いったんシークバー他無効化
	        		setEnabledSeekController(false);
					mSeekBar.setMax(mThumbInfo.getLengthBySecond());
					mSeekBar.setProgress(second);
		    		seekBySecond(second);
				}
        	}
    	}
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onCreate: savedInstanceState=")
					.append(savedInstanceState).toString());
		}
		super.onCreate(savedInstanceState);

		if (savedInstanceState == null) {
			mIsRestored = false;
		} else {
			mIsRestored = true;
			mSaveStateCurrentPlayTime = savedInstanceState.getParcelable(
					KEY_SAVE_CURRENT_PLAY_TIME);
			mSaveIsPausePlay = savedInstanceState.getBoolean(
					KEY_SAVE_IS_PAUSE_PLAY);
		}

		mOnResumed = false;
		mFromOnPause = false;
        mDidStartPlay = false;

		mMessageData.mIsMessageOk = false;
		mMessageDataFork.mIsMessageOk = false;

        Intent intent = getIntent();

//        mThumbInfo = createThumbInfo(intent);
//        if (mThumbInfo != null) {
//        	mThumbInfo.setEventListener(new ThumbInfo.EventListener() {
//				@Override
//				public void onFinished(ThumbInfo thumbInfo) {
//					if (mHandler != null) {
//						mHandler.obtainMessage(MSG_ID_THUMBINFO_FINISHED,
//								thumbInfo.getLength()).sendToTarget();
//					}
//				}
//				@Override
//				public void onOccurredError(ThumbInfo thumbInfo,
//						String errorMessage) {
//					if (mHandler != null) {
//						mHandler.obtainMessage(MSG_ID_THUMBINFO_OCCURRED_ERROR,
//								errorMessage).sendToTarget();
//					}
//				}
//        	});
//        	mThumbInfo.startLoad();
//        }
        getThumbInfo(intent);

		mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(
				getApplicationContext());
		String userSession = mSharedPreferences.getString(NicoroConfig.COOKIE_USER_SESSION, null);
        mMessageLoader = createMessageLoader(intent, getApplicationContext(), userSession);
        if (mMessageLoader != null) {
        	mMessageLoader.setEventListener(new MessageLoader.EventListener() {
				@Override
				public void onFinished(MessageLoader messageLoader) {
					if (mHandler != null) {
						mHandler.sendEmptyMessage(MSG_ID_MESSAGE_FINISHED);
					}
				}
				@Override
				public void onOccurredError(MessageLoader messageLoader,
						String errorMessage) {
					if (mHandler != null) {
						mHandler.obtainMessage(MSG_ID_MESSAGE_OCCURRED_ERROR,
								errorMessage).sendToTarget();
					}
				}
        	});
//        	mMessageLoader.startLoad();
        }
        mMessageLoaderFork = createMessageLoaderFork(intent, getApplicationContext(), userSession);
        if (mMessageLoaderFork != null) {
        	mMessageLoaderFork.setEventListener(new MessageLoader.EventListener() {
				@Override
				public void onFinished(MessageLoader messageLoader) {
					if (mHandler != null) {
						mHandler.sendEmptyMessage(MSG_ID_MESSAGE_FORK_FINISHED);
					}
				}
				@Override
				public void onOccurredError(MessageLoader messageLoader,
						String errorMessage) {
//					// TODO 投稿者コメントのエラーはとりあえず無視？
//					Log.w(LOG_TAG, Log.buf().append("fork=\"1\" message load failed:")
//							.append(errorMessage).toString());
//					mMessageDataFork.mIsMessageOk = true;
					if (mHandler != null) {
						mHandler.obtainMessage(MSG_ID_MESSAGE_FORK_OCCURRED_ERROR,
								errorMessage).sendToTarget();
					}
				}
        	});
        }

        boolean messageAntialias = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_message_antialias), false);
        mMessageChatController.setAntiAlias(messageAntialias);
//        mPaintText.setAntiAlias(messageAntialias);

        mMessageDisable = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_message_disable), false);

        mShowHintToast = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_show_hint_toast), true);
		if (mShowHintToast) {
			Util.showInfoToast(getApplicationContext(),
					R.string.toast_explain_player_ctrl);
		}
    }

	@Override
	protected void onResume() {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onResume").toString());
		}
		super.onResume();
		mOnResumed = true;
		if (mFromOnPause) {
            if (canStartPlay()) {
                // 再開できるようにコントローラ表示
                mPlayerInfoControllerManager.showPlayerController();
            }

		    mFromOnPause = false;
		} else {
    		if (canStartPlay()) {
    	        startPlay();
    		}
		}
	}

	private void savePlayState() {
        if (mDidStartPlay) {
            if (mSaveStateCurrentPlayTime == null) {
                mSaveStateCurrentPlayTime = new Rational();
            }
            getCurrentPositionVideoPlay(mSaveStateCurrentPlayTime);
            mSaveIsPausePlay = isPausePlay();
        }
	}

	@Override
	protected void onSaveInstanceState(Bundle outState) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onSaveInstanceState: outState=")
					.append(outState).toString());
		}
		super.onSaveInstanceState(outState);

		savePlayState();
		outState.putParcelable(KEY_SAVE_CURRENT_PLAY_TIME, mSaveStateCurrentPlayTime);
		outState.putBoolean(KEY_SAVE_IS_PAUSE_PLAY, mSaveIsPausePlay);
	}

	@Override
	protected void onPause() {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onPause").toString());
		}
		super.onPause();
		mOnResumed = false;
		mFromOnPause = true;
		// TODO とりあえず必ず一時停止
		pausePlay();
        setButtonPauseImage();
	}

	@Override
	protected void onDestroy() {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onDestroy").toString());
		}
		// TODO: ANR対策の為に***Loader類のabort対応が必要
    	if (mVideoLoader != null) {
    		mVideoLoader.finish();
    		mVideoLoader = null;
    	}
    	if (mMessageLoader != null) {
    		mMessageLoader.finish();
    		mMessageLoader = null;
    	}
    	if (mMessageLoaderFork != null) {
    		mMessageLoaderFork.finish();
    		mMessageLoaderFork = null;
    	}
    	if (mThumbInfo != null) {
    		mThumbInfo.finish();
    		mThumbInfo = null;
    	}
    	mProgressGroup = null;
    	mProgressBar = null;
    	mProgressTextVideo = null;
    	mMessageData.mChatsWait = null;
    	mMessageData.mChatsRunningNaka = null;
    	mMessageData.mChatsRunningShita = null;
    	mMessageData.mChatsRunningUe = null;
//    	mPaintText = null;
    	mMessageChatController = null;
    	mHandler = null;
		super.onDestroy();
	}

	@Override
    public boolean onTouchEvent(MotionEvent event) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onTouchEvent: event=").append(event)
					.toString());
		}
		if (mPlayerInfoControllerManager.onTouchEvent(event)) {
			return true;
		}
		return super.onTouchEvent(event);
    }

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onKeyDown: keyCode=").append(keyCode)
					.append(" event=").append(event)
					.toString());
		}
		if (mPlayerInfoControllerManager.onKeyDown(keyCode, event)) {
		    return true;
		}
		return super.onKeyDown(keyCode, event);
	}

	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		if (DEBUG_LOGV) {
			Log.v(LOG_TAG, Log.buf().append(getClass().getName())
					.append("#onKeyUp: keyCode=").append(keyCode)
					.append(" event=").append(event)
					.toString());
		}
		return super.onKeyUp(keyCode, event);
	}

    @Override
    protected void finalize() throws Throwable {
    	try {
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, "AbstractNicoroPlayer#finalize start");
            }
    		super.finalize();
    	} finally {
        	if (mVideoLoader != null) {
        		mVideoLoader.finish();
        	}
        	if (mMessageLoader != null) {
        		mMessageLoader.finish();
        	}
        	if (mMessageLoaderFork != null) {
        		mMessageLoaderFork.finish();
        	}
        	if (mThumbInfo != null) {
        		mThumbInfo.finish();
        	}
            if (DEBUG_LOGV) {
                Log.v(LOG_TAG, "AbstractNicoroPlayer#finalize end");
            }
    	}
    }

	protected void initializeView() {
		mProgressGroup = (RelativeLayout) findViewById(R.id.progress_group);
        mProgressBar = (ProgressBar) findViewById(R.id.progress);
        mProgressTextVideo = (VariableLabelView) findViewById(R.id.progress_text_video);
        mProgressTextVideo.setText(getString(R.string.progress_video_wait_connect));
        mProgressTextVideo.startAnimation(
        		AnimationUtils.loadAnimation(getApplicationContext(),
        				R.anim.blink));
        mProgressTextThumbinfo = (VariableLabelView) findViewById(R.id.progress_text_thumbinfo);
        mProgressTextThumbinfo.setText(getString(R.string.progress_thumbinfo_wait));
        mProgressTextThumbinfo.startAnimation(
        		AnimationUtils.loadAnimation(getApplicationContext(),
        				R.anim.blink));
        mProgressTextMessage = (VariableLabelView) findViewById(R.id.progress_text_message);
        mProgressTextMessage.setText(getString(R.string.progress_message_wait));
        mProgressTextMessage.startAnimation(
        		AnimationUtils.loadAnimation(getApplicationContext(),
        				R.anim.blink));
        mProgressTextMessageFork = (VariableLabelView) findViewById(R.id.progress_text_message_fork);
        mProgressTextMessageFork.setText(getString(R.string.progress_message_fork_wait));
        mProgressTextMessageFork.startAnimation(
        		AnimationUtils.loadAnimation(getApplicationContext(),
        				R.anim.blink));
        mProgressTextInner = (VariableLabelView) findViewById(R.id.progress_text_inner);
//        mInfoClock = (VariableLabelView) findViewById(R.id.info_clock);
        mInfoCountPlay = (VariableLabelView) findViewById(R.id.info_count_play);
        mInfoCountComment = (VariableLabelView) findViewById(R.id.info_count_comment);
        mInfoCountMylist = (VariableLabelView) findViewById(R.id.info_count_mylist);
        mInfoTime = (VariableLabelView) findViewById(R.id.info_time);
        mInfoTitle = (VariableLabelView) findViewById(R.id.info_title);
        mInfoDescription = (VariableLabelView) findViewById(R.id.info_description);
        mInfoPlayData = (VariableLabelView) findViewById(R.id.info_play_data);
        mControllerTime = (VariableLabelView) findViewById(R.id.controller_time);
        mPlayerInfo = (ViewGroup) findViewById(R.id.player_info);
        mButtonPause = (ImageButton) findViewById(R.id.button_pause);
        mButtonPause.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
			    if (mDidStartPlay) {
			        switchPausePlay();
			    }
			}
        });
        mButtonFromBegin = (ImageButton) findViewById(R.id.button_from_begin);
        mButtonFromBegin.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// いったんシークバー他無効化
				setEnabledSeekController(false);
				mSeekBar.setProgress(0);
				seekBySecond(0);

				// TODO 最初に戻った後の再度再生するのはどうするか
			}
        });
        mButtonCommentOnOff = (ToggleButton) findViewById(R.id.button_comment_onoff);
        mButtonCommentOnOff.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
			@Override
			public void onCheckedChanged(CompoundButton buttonView,
					boolean isChecked) {
				boolean messageDisable = !isChecked;
				if (mMessageDisable != messageDisable) {
					mMessageDisable = messageDisable;
		        	SharedPreferences.Editor editor = mSharedPreferences.edit();
		        	editor.putBoolean(
		        			getString(R.string.pref_key_message_disable),
		        			mMessageDisable);
		        	editor.commit();
				}
			}
        });
        mButtonCommentOnOff.setChecked(!mMessageDisable);
        mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
			@Override
			public void onProgressChanged(SeekBar seekBar, int progress,
					boolean fromUser) {
			}
			@Override
			public void onStartTrackingTouch(SeekBar seekBar) {
				mIsTrackingSeekBar = true;
			}
			@Override
			public void onStopTrackingTouch(SeekBar seekBar) {
				mIsTrackingSeekBar = false;
				// いったんシークバー他無効化
				assert seekBar == mSeekBar;
				setEnabledSeekController(false);

//				// いったんポーズ
//				final boolean isPause = isPausePlay();
//				if (!isPause) {
//					pausePlay();
//				}

				int second = seekBar.getProgress();
//				seekBySecondCommon(second);
				seekBySecond(second);

//				if (!isPause) {
//					restartPlay();
//				}
			}
        });
        mPlayerController = (ViewGroup) findViewById(R.id.player_controller);

        // Viewが必要なためここで初期化
        mPlayerInfoControllerManager = new PlayerInfoControllerManager(
                getApplicationContext(), mPlayerInfo, mPlayerController, mInfoTime);
	}

	protected static VideoLoader createVideoLoader(Intent intent, Context context) {
		VideoLoader videoLoader = null;
        String url = intent.getStringExtra(AbstractNicoroPlayer.INTENT_NAME_VIDEO_URL);
        String cookie = intent.getStringExtra(AbstractNicoroPlayer.INTENT_NAME_COOKIE);
        String videoNumber = intent.getStringExtra(AbstractNicoroPlayer.INTENT_NAME_VIDEO_NUMBER);
        String cookieUserSession = intent.getStringExtra(AbstractNicoroPlayer.INTENT_NAME_COOKIE_USER_SESSION);

        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append(" url=").append(url).append(" cookie=")
        			.append(cookie).append(" videoNumber=").append(videoNumber)
        			.append(" cookieUserSession=").append(cookieUserSession)
        			.toString());
        }
        if (url != null && cookie != null) {
        	videoLoader = new VideoLoader(url, cookie, videoNumber,
        			context, cookieUserSession);
        }
		return videoLoader;
	}

	protected static MessageLoader createMessageLoader(Intent intent, Context context,
			String userSession) {
		MessageLoader messageLoader = null;
        String cookie = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_COOKIE);
        String messageUrl = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_MESSAGE_URL);
        String threadId = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_THREAD_ID);
        String userId = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_USER_ID);
        String threadKey = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_THREAD_KEY);
        String force184 = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_FORCE_184);
        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append(" cookie=").append(cookie)
        			.append(" messageUrl=").append(messageUrl).append(" threadId=")
        			.append(threadId).append(" userId=").append(userId).append(" threadKey=")
        			.append(threadKey).append(" force184=").append(force184).toString());
        }
        if (messageUrl != null && threadId != null && cookie != null) {
        	messageLoader = new MessageLoader(messageUrl, threadId, userSession,
        			userId, threadKey, force184,
        			context);
        }
		return messageLoader;
	}

	protected static MessageLoaderFork createMessageLoaderFork(Intent intent, Context context,
			String userSession) {
		MessageLoaderFork messageLoader = null;
        String cookie = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_COOKIE);
        String messageUrl = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_MESSAGE_URL);
        String threadId = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_THREAD_ID);
        String userId = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_USER_ID);
        String threadKey = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_THREAD_KEY);
        String force184 = intent.getStringExtra(
        		AbstractNicoroPlayer.INTENT_NAME_FORCE_184);
        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append(" cookie=").append(cookie)
        			.append(" messageUrl=").append(messageUrl).append(" threadId=")
        			.append(threadId).append(" userId=").append(userId).append(" threadKey=")
        			.append(threadKey).append(" force184=").append(force184).toString());
        }
        if (messageUrl != null && threadId != null && cookie != null) {
        	messageLoader = new MessageLoaderFork(messageUrl, threadId, userSession,
        			userId, threadKey, force184,
        			context);
        }
		return messageLoader;
	}

	protected static ThumbInfo createThumbInfo(Intent intent) {
		ThumbInfo thumbInfo = null;
        String videoNumber = intent.getStringExtra(AbstractNicoroPlayer.INTENT_NAME_VIDEO_NUMBER);
        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append(" videoNumber=").append(videoNumber).toString());
        }
        if (videoNumber != null) {
        	thumbInfo = new ThumbInfo(videoNumber);
        }
        return thumbInfo;
	}

	protected void getThumbInfo(Intent intent) {
        String videoNumber = intent.getStringExtra(AbstractNicoroPlayer.INTENT_NAME_VIDEO_NUMBER);
        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, Log.buf().append(" videoNumber=").append(videoNumber).toString());
        }
		ThumbInfoCacher cacher = NicoroApplication.getInstance(this
		        ).getThumbInfoCacher();
        if (videoNumber == null) {
        	return;
        }
    	ThumbInfo thumbInfo = cacher.getThumbInfo(videoNumber);
    	if (thumbInfo == null) {
			if (mHandler != null) {
				cacher.loadThumbInfo(videoNumber,
				        new CallbackMessage<ThumbInfo, String>(
				                mHandler,
				                MSG_ID_THUMBINFO_FINISHED_NEW,
				                MSG_ID_THUMBINFO_OCCURRED_ERROR_NEW));
			}
    	} else {
    		mThumbInfo = thumbInfo;
			if (mHandler != null) {
				mHandler.obtainMessage(MSG_ID_THUMBINFO_FINISHED,
						thumbInfo.getLength()).sendToTarget();
			}
    	}
	}

    protected void seekBySecondCommon(int second) {
    	// コメントデータ初期化
    	// 排他制御どうするか？
    	if (mMessageData.mChatsWait != null) {
	    	mMessageData.mChatsWait.clear();
	    	mMessageData.mChatsWait.addAll(mMessageLoader.getChats());
    	}
    	if (mMessageData.mChatsRunningNaka != null) {
    		mMessageData.mChatsRunningNaka.clear();
    	}
    	if (mMessageData.mChatsRunningShita != null) {
    		mMessageData.mChatsRunningShita.clear();
    	}
    	if (mMessageData.mChatsRunningUe != null) {
    		mMessageData.mChatsRunningUe.clear();
    	}

        if (mMessageDataFork.mChatsWait != null) {
            mMessageDataFork.mChatsWait.clear();
            mMessageDataFork.mChatsWait.addAll(mMessageLoaderFork.getChats());
        }
        if (mMessageDataFork.mChatsRunningNaka != null) {
            mMessageDataFork.mChatsRunningNaka.clear();
        }
        if (mMessageDataFork.mChatsRunningShita != null) {
            mMessageDataFork.mChatsRunningShita.clear();
        }
        if (mMessageDataFork.mChatsRunningUe != null) {
            mMessageDataFork.mChatsRunningUe.clear();
        }
    }

	protected StringBuilder appendCurrentPlayTimeCommon(StringBuilder builder,
			final int posNum, final int posDen) {
		if (posDen != 0) {
			int minutes = posNum / (posDen * 60);
			int seconds = posNum / posDen % 60;
			return Util.appendPlayTime(builder, minutes, seconds);
		} else {
			return builder.append(INFO_TIME_DEFAULT);
		}
	}

	protected void setButtonPauseImage() {
		if (isPausePlay()) {
			mButtonPause.setImageResource(android.R.drawable.ic_media_play);
		} else {
			mButtonPause.setImageResource(android.R.drawable.ic_media_pause);
		}
	}

	protected void setEnabledSeekController(boolean enabled) {
		mSeekBar.setEnabled(enabled);
		mButtonFromBegin.setEnabled(enabled);
	}
}
