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.util.DisplayMetrics;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;

public abstract class AbstractNicoroPlayer extends Activity {
	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_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";
	
	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_ENABLE_SEEK_BAR = 9;
//	protected static final int MSG_ID_INFO_CLOCK_UPDATE = 10;
	protected static final int MSG_ID_INFO_PLAY_DATA_UPDATE = 11;
	protected static final int MSG_ID_SUB_OFFSET = 0x100;
	
	public static final int REAL_PLAYER_WIDTH_PX = 512;
	public static final int REAL_PLAYER_HEIGHT_PX_4_3 = 384;
	public static final int REAL_PLAYER_HEIGHT_PX_16_9 = 288;
	
	private static final int ANIM_TIME_PLAYER_INFO_IN = 500;
	private static final int ANIM_TIME_PLAYER_INFO_OUT = 500;
	private static final int ANIM_TIME_PLAYER_CONTROLLER_IN = 400;
	private static final int ANIM_TIME_PLAYER_CONTROLLER_OUT = 400;
	
	protected static final String INFO_TIME_DEFAULT = "000:00";
	
	private static final long INTERVAL_TIME_UPDATE = 100L;
	
	protected VideoLoader mVideoLoader;
	protected MessageLoader mMessageLoader;
	protected MessageLoaderFork mMessageLoaderFork;
	protected RelativeLayout mProgressGroup;
	protected ProgressBar mProgressBar;
	protected VariableLabelView mProgressText;
//	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 Button mButtonCommentOnOff;
	private SeekBar mSeekBar;
	private ViewGroup mPlayerController;
	protected ThumbInfo mThumbInfo;
	
	private GestureDetector mGestureDetector;
	private class CustomGestureListener extends GestureDetector.SimpleOnGestureListener {
		private int mDisplayWidth;
		private int mDisplayHeight;
		
		public CustomGestureListener() {
			DisplayMetrics metrics = new DisplayMetrics();
			getWindowManager().getDefaultDisplay().getMetrics(metrics);
			mDisplayWidth = metrics.widthPixels;
			mDisplayHeight = metrics.heightPixels;
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2,
				float velocityX, float velocityY) {
			final float absVelocityX = Math.abs(velocityX);
			final float absVelocityY = Math.abs(velocityY);
			if (absVelocityY >= absVelocityX) {
				// Controller操作
				if (velocityY >= 0.0f) {
					if (isPlayerControllerShown()) {
						hidePlayerController();
					}
				} else {
					if (!isPlayerControllerShown()) {
						showPlayerController();
					}
				}
			} else {
				// Info操作
				if (velocityX >= 0.0f) {
					if (isPlayerInfoShown()) {
						hidePlayerInfo();
					}
				} else {
					if (!isPlayerInfoShown()) {
						showPlayerInfo();
					}
				}
			}
			return true;
		}

		@Override
		public boolean onSingleTapUp(MotionEvent e) {
			float x = e.getX();
			float y = e.getY();
			if (x >= ((float) (mDisplayWidth * 2)) / 3.0f
					&& (y < ((float) (mDisplayHeight * 2)) / 3.0f)) {
				switchPlayerInfoDisplay();
				return true;
			} else {
		    	if (isPlayerControllerShown()) {
		    		if (y < ((float) (mDisplayHeight * 2)) / 3.0f) {
		    			hidePlayerController();
						return true;
		    		} else {
		    			return false;
		    		}
		    	} else {
		    		showPlayerController();
					return true;
		    	}
			}
		}
	}
	
	private TranslateAnimation mAnimInfoIn;
	private TranslateAnimation mAnimInfoOut;
	private TranslateAnimation mAnimControllerIn;
	private TranslateAnimation mAnimControllerOut;

	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 Paint mPaintText = new Paint();
    
    protected boolean mMessageDisable;
    
    protected Rational mRatinalCurrentPlayTime = new Rational();
    
    protected volatile MessageHandler mHandler;
    
    private Matrix mMatrixMessage = new Matrix();
    private Random mRandom = new Random();
    
    private boolean mIsTrackingSeekBar;
    
    private SharedPreferences mSharedPreferences;
    
    private Calendar mClock = Calendar.getInstance();
    
    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:
				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:
				Util.showErrorDialog(AbstractNicoroPlayer.this,
						(String) msg.obj, true);
				break;
			case MSG_ID_THUMBINFO_FINISHED:
				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:
				Util.showErrorDialog(AbstractNicoroPlayer.this,
						(String) msg.obj, true);
				break;
			case MSG_ID_VIDEO_OCCURRED_ERROR:
				Util.showErrorDialog(AbstractNicoroPlayer.this,
						(String) msg.obj, true);
				break;
			case MSG_ID_VIDEO_NOTIFY_PROGRESS:
				StringBuilder textData = mProgressText.getTextBuilder();
				textData.delete(0, textData.length());
				textData.append(msg.arg1).append('/').append(msg.arg2);
				mProgressText.notifyUpdateText();
				break;
			case MSG_ID_INFO_TIME_UPDATE:
				if (!isFinishing()) {
					StringBuilder infoTimeData = mInfoTime.getTextBuilder();
					infoTimeData.delete(0, infoTimeData.length());
					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:
				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_ENABLE_SEEK_BAR:
				mSeekBar.setEnabled(true);
				mButtonFromBegin.setEnabled(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))
					.append(mThumbInfo.getMovieType()).append(' ');
				appendVideoResolution(builderInfoPlayData).append(' ');
				appendPlayerInfo(builderInfoPlayData);
				mInfoPlayData.notifyUpdateText();
				break;
			default:
				assert false : msg.what;
				break;
			}
		}
    }
    
	protected abstract boolean canStartPlay();
	protected abstract StringBuilder appendCurrentPlayTime(StringBuilder builder);
	
	protected abstract void getCurrentPositionVideoPlay(Rational rational);
	protected abstract void getCurrentPositionAudioPlay(Rational rational);
	protected abstract void getCurrentPositionVideoDecode(Rational rational);
	protected abstract void getCurrentPositionAudioDecode(Rational rational);
	
	protected abstract boolean switchPausePlay();
	protected abstract void pausePlay();
	protected abstract void restartPlay();
	protected abstract boolean isPausePlay();
	
	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 (!isPlayerInfoShown()) {
			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();

    	mIsTrackingSeekBar = false;
	}
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		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) {
						Message message = mHandler.obtainMessage(MSG_ID_THUMBINFO_FINISHED, thumbInfo.getLength());
						mHandler.sendMessage(message);
					}
				}
				@Override
				public void onOccurredError(ThumbInfo thumbInfo,
						String errorMessage) {
					if (mHandler != null) {
						Message message = mHandler.obtainMessage(MSG_ID_THUMBINFO_OCCURRED_ERROR, errorMessage);
						mHandler.sendMessage(message);
					}
				}
        	});
        	mThumbInfo.startLoad();
        }
        
		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) {
						Message message = mHandler.obtainMessage(MSG_ID_MESSAGE_OCCURRED_ERROR, errorMessage);
						mHandler.sendMessage(message);
					}
				}
        	});
//        	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;
				}
        	});
        }
        
        boolean messageAntialias = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_message_antialias), false);
        mPaintText.setAntiAlias(messageAntialias);
        
        mMessageDisable = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_message_disable), false);
        
        mGestureDetector = new GestureDetector(getApplicationContext(),
        		new CustomGestureListener());
        
		final boolean showHintToast = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_show_hint_toast), true);
		if (showHintToast) {
			Util.showInfoToast(getApplicationContext(),
					R.string.toast_explain_player_ctrl);
		}
    }
	
	@Override
	protected void onDestroy() {
    	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;
    	mProgressText = null;
    	mMessageData.mChatsWait = null;
    	mMessageData.mChatsRunningNaka = null;
    	mMessageData.mChatsRunningShita = null;
    	mMessageData.mChatsRunningUe = null;
    	mPaintText = null;
    	mHandler = null;
		super.onDestroy();
	}
	
	@Override
    public boolean onTouchEvent(MotionEvent event) {
		if (mGestureDetector.onTouchEvent(event)) {
			return true;
		}
		return super.onTouchEvent(event);
    }
	
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		// 1.6向けなのでonKeyDownでback key監視
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			if (isPlayerControllerShown()) {
				hidePlayerController();
				return true;
			}
		} else if (keyCode == KeyEvent.KEYCODE_MENU) {
			switchPlayerControllerDisplay();
			return true;
		}
		return super.onKeyDown(keyCode, event);
	}
	
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		return super.onKeyUp(keyCode, event);
	}
	
    @Override
    protected void finalize() throws Throwable {
    	try {
    		super.finalize();
    	} finally {
        	if (mVideoLoader != null) {
        		mVideoLoader.finish();
        	}
        	if (mMessageLoader != null) {
        		mMessageLoader.finish();
        	}
        	if (mMessageLoaderFork != null) {
        		mMessageLoaderFork.finish();
        	}
        	if (mThumbInfo != null) {
        		mThumbInfo.finish();
        	}
    	}
    }
	
	protected void initializeView() {
		mProgressGroup = (RelativeLayout) findViewById(R.id.progress_group);
        mProgressBar = (ProgressBar) findViewById(R.id.progress);
        mProgressText = (VariableLabelView) findViewById(R.id.progress_text);
//        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) {
				switchPausePlay();
			}
        });
        mButtonFromBegin = (ImageButton) findViewById(R.id.button_from_begin);
        mButtonFromBegin.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// いったんシークバー他無効化
				mSeekBar.setEnabled(false);
				mButtonFromBegin.setEnabled(false);
				seekBySecond(0);
				
				// TODO 最初に戻った後の再度再生するのはどうするか
			}
        });
        mButtonCommentOnOff = (Button) findViewById(R.id.button_comment_onoff);
        mButtonCommentOnOff.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				mMessageDisable = !mMessageDisable;
	        	setButtonCommentOnOffText();
	        	SharedPreferences.Editor editor = mSharedPreferences.edit();
	        	editor.putBoolean(
	        			getString(R.string.pref_key_message_disable),
	        			mMessageDisable);
	        	editor.commit();
			}
        });
        setButtonCommentOnOffText();
        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;
				// いったんシークバー他無効化
				seekBar.setEnabled(false);
				mButtonFromBegin.setEnabled(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);
	}
	
	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 drawMessage(Canvas canvas,
    		MessageData messageData,
    		MessageData messageDataFork,
    		int vpos, int videoWidth, int videoHeight) {
		List<MessageChat> chatsWait = messageData.mChatsWait;
		List<MessageChat> chatsRunningNaka = messageData.mChatsRunningNaka;
		List<MessageChat> chatsRunningShita = messageData.mChatsRunningShita;
		List<MessageChat> chatsRunningUe = messageData.mChatsRunningUe;
    	
    	mMatrixMessage.reset();
    	mMatrixMessage.setScale(
    			(float) videoWidth / (float) REAL_PLAYER_WIDTH_PX,
    			(float) videoHeight / (float) REAL_PLAYER_HEIGHT_PX_4_3);
    	canvas.setMatrix(mMatrixMessage);
//    	canvas.clipRect(0, 0, videoWidth, videoHeight);
    	
    	assert chatsWait != null;
    	assert chatsRunningNaka != null;
    	assert chatsRunningShita != null;
    	assert chatsRunningUe != null;
    	
    	if (DEBUG_LOGV) {
    		Log.v(LOG_TAG, Log.buf().append("vpos=").append(vpos).toString());
    	}
    	
    	for (Iterator<MessageChat> it = chatsRunningNaka.iterator(); it.hasNext(); ) {
    		MessageChat chat = it.next();
    		if (vpos > chat.vpos + MessageChat.DISPLAY_TIME_VPOS_NAKA) {
    			it.remove();
    			continue;
    		}
    	}
    	for (Iterator<MessageChat> it = chatsRunningShita.iterator(); it.hasNext(); ) {
    		MessageChat chat = it.next();
    		if (vpos > chat.vpos + MessageChat.DISPLAY_TIME_VPOS_SHITA) {
    			it.remove();
    			continue;
    		}
    	}
    	for (Iterator<MessageChat> it = chatsRunningUe.iterator(); it.hasNext(); ) {
    		MessageChat chat = it.next();
    		if (vpos > chat.vpos + MessageChat.DISPLAY_TIME_VPOS_UE) {
    			it.remove();
    			continue;
    		}
    	}
    	
    	for (Iterator<MessageChat> it = chatsWait.iterator(); it.hasNext(); ) {
    		MessageChat chat = it.next();
    		if (chat.vpos >= vpos) {
    			int vposOffset;
    			if (chat.getPos() == MessageChat.POS_NAKA) {
    				vposOffset = MessageChat.DISPLAY_TIME_VPOS_NAKA;
    			} else if (chat.getPos() == MessageChat.POS_SHITA) {
    				vposOffset = MessageChat.DISPLAY_TIME_VPOS_SHITA;
    			} else if (chat.getPos() == MessageChat.POS_UE) {
    				vposOffset = MessageChat.DISPLAY_TIME_VPOS_UE;
    			} else {
    				assert false;
    				vposOffset = MessageChat.DISPLAY_TIME_VPOS_NAKA;
    			}
    			if (chat.vpos <= (vpos + vposOffset)) {
	    			it.remove();
	    			
	    			chat.computeWidth(mPaintText);
	    			chat.computeSpeed();
	    			chat.computeLineHeight(mPaintText);
	    			if (chat.getPos() == MessageChat.POS_NAKA) {
	    				int nextY = chat.computeNakaNextY(vpos, chatsRunningNaka, mRandom);
	    				chat.setY(nextY, mPaintText);
	    				
	    				chat.addNakaOrder(chatsRunningNaka);
	    			} else if (chat.getPos() == MessageChat.POS_SHITA) {
	    				int nextY = chat.computeShitaNextY(chatsRunningShita, mRandom);
	    				chat.setY(nextY, mPaintText);
		
	    				chat.addShitaOrder(chatsRunningShita);
	    			} else if (chat.getPos() == MessageChat.POS_UE) {
						int nextY = chat.computeUeNextY(chatsRunningUe, mRandom);
	    				chat.setY(nextY, mPaintText);
		
	    				chat.addUeOrder(chatsRunningUe);
	    			} else {
	    				assert false;
	    			}
	    			
	        		if (DEBUG_LOGD) {
	        			Log.d(LOG_TAG, Log.buf().append("cur vpos=").append(vpos)
	        					.append(" vpos=").append(chat.vpos)
	        					.append(" chat=").append(chat.getText()).toString());
	        		}
    			}
    		}
    	}
    	
    	if (!mMessageDisable) {
	    	for (Iterator<MessageChat> it = chatsRunningNaka.iterator(); it.hasNext(); ) {
	    		MessageChat chat = it.next();
	    		chat.draw(vpos, canvas, mPaintText);
	    	}
			
	    	for (Iterator<MessageChat> it = chatsRunningShita.iterator(); it.hasNext(); ) {
	    		MessageChat chat = it.next();
	    		chat.draw(vpos, canvas, mPaintText);
	    	}
			
	    	for (Iterator<MessageChat> it = chatsRunningUe.iterator(); it.hasNext(); ) {
	    		MessageChat chat = it.next();
	    		chat.draw(vpos, canvas, mPaintText);
	    	}
    	}
    }
    
    void switchPlayerInfoDisplay() {
    	if (isPlayerInfoShown()) {
    		hidePlayerInfo();
    	} else {
    		showPlayerInfo();
    	}
    }
    
    private boolean isPlayerInfoShown() {
    	final int visibility = mPlayerInfo.getVisibility();
    	if (visibility == View.VISIBLE
    			&& (mAnimInfoOut == null
    					|| mPlayerInfo.getAnimation() != mAnimInfoOut)) {
    		return true;
    	} else {
    		return false;
    	}
    }
    private void showPlayerInfo() {
        if (mAnimInfoIn == null) {
        	int infoWidth = mPlayerInfo.getWidth();
        	mAnimInfoIn = new TranslateAnimation(
        			infoWidth, 0.0f, 0.0f, 0.0f);
        	mAnimInfoIn.setDuration(ANIM_TIME_PLAYER_INFO_IN);
        	mAnimInfoIn.setAnimationListener(new Animation.AnimationListener() {
				@Override
				public void onAnimationEnd(Animation animation) {
					mPlayerInfo.clearAnimation();
				}
				@Override
				public void onAnimationRepeat(Animation animation) {
				}
				@Override
				public void onAnimationStart(Animation animation) {
				}
        	});
        }
		mPlayerInfo.setVisibility(View.VISIBLE);
		mPlayerInfo.startAnimation(mAnimInfoIn);
    }
    private void hidePlayerInfo() {
        if (mAnimInfoOut == null) {
        	int infoWidth = mPlayerInfo.getWidth();
        	mAnimInfoOut = new TranslateAnimation(
        			0.0f, infoWidth, 0.0f, 0.0f);
        	mAnimInfoOut.setDuration(ANIM_TIME_PLAYER_INFO_OUT);
        	mAnimInfoOut.setAnimationListener(new Animation.AnimationListener() {
				@Override
				public void onAnimationEnd(Animation animation) {
					mPlayerInfo.setVisibility(View.INVISIBLE);
					mPlayerInfo.clearAnimation();
				}
				@Override
				public void onAnimationRepeat(Animation animation) {
				}
				@Override
				public void onAnimationStart(Animation animation) {
				}
        	});
        }
		mPlayerInfo.startAnimation(mAnimInfoOut);
    }

    void switchPlayerControllerDisplay() {
    	if (isPlayerControllerShown()) {
    		hidePlayerController();
    	} else {
    		showPlayerController();
    	}
    }
    
    private boolean isPlayerControllerShown() {
    	final int visibility = mPlayerController.getVisibility();
    	if (visibility == View.VISIBLE
    			&& (mAnimControllerOut == null
    					|| mPlayerController.getAnimation() != mAnimControllerOut)) {
    		return true;
    	} else {
    		return false;
    	}
    }
    private void showPlayerController() {
        if (mAnimControllerIn == null) {
        	int controllerHeight = mPlayerController.getHeight();
        	mAnimControllerIn = new TranslateAnimation(
        			0.0f, 0.0f, controllerHeight, 0.0f);
        	mAnimControllerIn.setDuration(ANIM_TIME_PLAYER_CONTROLLER_IN);
        	mAnimControllerIn.setAnimationListener(new Animation.AnimationListener() {
				@Override
				public void onAnimationEnd(Animation animation) {
					mPlayerController.clearAnimation();
				}
				@Override
				public void onAnimationRepeat(Animation animation) {
				}
				@Override
				public void onAnimationStart(Animation animation) {
				}
        	});
        }
		mPlayerController.setVisibility(View.VISIBLE);
		mPlayerController.startAnimation(mAnimControllerIn);
		
		// Infoの時間表示を隠す
		mInfoTime.setVisibility(View.INVISIBLE);
    }
    private void hidePlayerController() {
        if (mAnimControllerOut == null) {
        	int controllerHeight = mPlayerController.getHeight();
        	mAnimControllerOut = new TranslateAnimation(
        			0.0f, 0.0f, 0.0f, controllerHeight);
        	mAnimControllerOut.setDuration(ANIM_TIME_PLAYER_CONTROLLER_OUT);
        	mAnimControllerOut.setAnimationListener(new Animation.AnimationListener() {
				@Override
				public void onAnimationEnd(Animation animation) {
					mPlayerController.setVisibility(View.INVISIBLE);
					mPlayerController.clearAnimation();
				}
				@Override
				public void onAnimationRepeat(Animation animation) {
				}
				@Override
				public void onAnimationStart(Animation animation) {
				}
        	});
        }
		mPlayerController.startAnimation(mAnimControllerOut);
		
		// Infoの時間表示を再表示
		mInfoTime.setVisibility(View.VISIBLE);
    }
    
    protected void seekBySecondCommon(int second) {
    	// コメントデータ初期化
    	// 排他制御どうするか？
    	mMessageData.mChatsWait.clear();
    	mMessageData.mChatsWait.addAll(mMessageLoader.getChats());
		mMessageData.mChatsRunningNaka.clear(); 
		mMessageData.mChatsRunningShita.clear(); 
		mMessageData.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;
			if (minutes < 10) {
				builder.append("00");
			}
			else if (minutes < 100) {
				builder.append('0');
			}
			builder.append(minutes).append(':');
			if (seconds < 10) {
				builder.append('0');
			}
			return builder.append(seconds);
		} else {
			return builder.append(INFO_TIME_DEFAULT);
		}
	}
	
	private void setButtonCommentOnOffText() {
		if (mMessageDisable) {
			mButtonCommentOnOff.setText(R.string.button_comment_on);
		} else {
			mButtonCommentOnOff.setText(R.string.button_comment_off);
		}
	}
	
	protected void setButtonPauseImage() {
		if (isPausePlay()) {
			mButtonPause.setImageResource(android.R.drawable.ic_media_play);
		} else {
			mButtonPause.setImageResource(android.R.drawable.ic_media_pause);
		}
	}
}
