package jp.sourceforge.nicoro;

import static jp.sourceforge.nicoro.Log.LOG_TAG;
import jp.sourceforge.nicoro.R;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Canvas;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Message;
import android.util.DisplayMetrics;
import android.view.Gravity;

import static android.view.ViewGroup.LayoutParams.FILL_PARENT;

import android.widget.RelativeLayout;
import android.widget.Toast;
import android.widget.VideoView;

public class NicoroMediaPlayer extends AbstractNicoroPlayer
implements MessageView.Callback {
	private static final boolean DEBUG_LOGD = Release.IS_DEBUG && true;

	private static final int MSG_ID_VIDEO_DOWNLOAD_FINISHED = MSG_ID_SUB_OFFSET + 0;
	private static final int MSG_ID_MESSAGE_DRAW_MESSAGE = MSG_ID_SUB_OFFSET + 1;
	private static final int MSG_ID_WAIT_VIDEO_SIZE_GET = MSG_ID_SUB_OFFSET + 2;
	private static final int MSG_ID_VIDEO_DOWNLOAD_STARTED = MSG_ID_SUB_OFFSET + 3;
	private static final int MSG_ID_VIDEO_PROXY_PREPARED = MSG_ID_SUB_OFFSET + 4;
	private static final int MSG_ID_VIDEO_CACHED = MSG_ID_SUB_OFFSET + 5;
    private static final int MSG_ID_VIDEO_PROXY_RECONNECT = MSG_ID_SUB_OFFSET + 6;

	private boolean mIsVideoDownloadOk;
	private boolean mIsVideoProxyOk;
	private boolean mIsVideoCachedOk;
	private VideoProxyHttpServer mVideoProxyServer;
//	private VideoProxyRtspServer mVideoProxyServer;
	private boolean mMediaPlayerStreaming;
	private boolean mMediaPlayerShiftStreamToFile;
    private boolean mFullscreen16_9;
	private boolean mHadError;
	private int mVideoViewWidth = -1;
    private int mVideoViewHeight = -1;

	private VideoView mVideoView;
	private MessageView mMessageView;
	private MediaPlayer mMediaPlayer;

	private AlertDialog mErrorDialog;

	public NicoroMediaPlayer() {
		mHandler = new MessageHandler() {
			@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_VIDEO_DOWNLOAD_FINISHED:
				    assert mVideoLoader.isCacheCompleted();
					mProgressTextVideo.setText(
							getString(R.string.progress_video_finished));
					mProgressTextVideo.clearAnimation();
					mSeekBar.setSecondaryProgress(mSeekBar.getMax());
					mIsVideoDownloadOk = true;
					if (canStartPlay()) {
					    if (!mDidStartPlay) {
					        startPlay();
	                    } else if (mMediaPlayerShiftStreamToFile
                                && mMediaPlayerStreaming
                                && mVideoProxyServer != null) {
                            shiftPlayStreamToFile();
                        }
					}
					break;
				case MSG_ID_MESSAGE_DRAW_MESSAGE:
					if (!isFinishing()) {
						mMessageView.invalidate();

						Message message = mHandler.obtainMessage(
								MSG_ID_MESSAGE_DRAW_MESSAGE);
						mHandler.sendMessageDelayed(message, 33L);
					}
					break;
				case MSG_ID_WAIT_VIDEO_SIZE_GET:
					if (mMediaPlayer != null && !mHadError) {
					    // まだIllegalStateException出る場合がある
					    try {
    						if (mMediaPlayer.getVideoWidth() != 0
    								|| mMediaPlayer.getVideoHeight() != 0) {
    							mHandler.sendEmptyMessage(
    									MSG_ID_INFO_PLAY_DATA_UPDATE);
    						} else {
    							mHandler.sendEmptyMessageDelayed(
    									MSG_ID_WAIT_VIDEO_SIZE_GET, 1000L);
    						}
					    } catch (IllegalStateException e) {
					        Log.e(LOG_TAG, e.toString(), e);
					    }
					}
					break;
				case MSG_ID_VIDEO_DOWNLOAD_STARTED:
					if (mMediaPlayerStreaming) {
						if (!mVideoLoader.isCacheCompleted()) {
							// プロキシ開始
	//						mVideoProxyServer = new VideoProxyRtspServer(mVideoLoader);
							mVideoProxyServer = new VideoProxyHttpServer(mVideoLoader);
							mVideoProxyServer.registerOnPreparedServer(this,
									MSG_ID_VIDEO_PROXY_PREPARED);
							mVideoProxyServer.registerOnReconnectServer(this,
							        MSG_ID_VIDEO_PROXY_RECONNECT);
							mVideoProxyServer.startProxy();
						}
					}
					break;
				case MSG_ID_VIDEO_PROXY_PREPARED:
					if (mMediaPlayerStreaming) {
						mIsVideoProxyOk = true;
						if (canStartPlay() && !mDidStartPlay) {
							startPlay();
						}
					}
					break;
				case MSG_ID_VIDEO_CACHED:
					if (mMediaPlayerStreaming) {
						mIsVideoCachedOk = true;
						if (canStartPlay() && !mDidStartPlay) {
							startPlay();
						}
					}
					break;
				case MSG_ID_VIDEO_PROXY_RECONNECT:
				    if (/*mMediaPlayerShiftStreamToFile
				            &&*/ mMediaPlayerStreaming
				            && mVideoProxyServer != null
				            && mVideoLoader.isCacheCompleted()) {
				        shiftPlayStreamToFile();
				    }
				    break;
				case MSG_ID_INFO_PLAY_DATA_UPDATE:
				    if (mVideoViewWidth < 0) {
				        mVideoViewWidth = mVideoView.getWidth();
				    }
				    if (mVideoViewHeight < 0) {
				        mVideoViewHeight = mVideoView.getHeight();
				    }
                    super.handleMessage(msg);
				    break;
				default:
					super.handleMessage(msg);
					break;
				}
			}
		};
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

        if (DEBUG_LOGD) {
        	Log.d(LOG_TAG, "NicoroMediaPlayer onCreate");
        }

        mIsVideoDownloadOk = false;
        mIsVideoProxyOk = false;
        mIsVideoCachedOk = false;

        mHadError = false;

		setContentView(R.layout.nicoro_mediaplayer);
		mVideoView = (VideoView) findViewById(R.id.video);

		mFullscreen16_9 = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_16_9_fullscreen), false);
		setVideoViewLayout();

		mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
			@Override
			public void onPrepared(MediaPlayer mp) {
				mMediaPlayer = mp;
				mp.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
					@Override
					public void onSeekComplete(MediaPlayer mp) {
						// シークバー再有効化
						if (mHandler != null) {
							mHandler.sendEmptyMessage(MSG_ID_ENABLE_SEEK_BAR);
						}
					}
				});
				mp.setOnInfoListener(new MediaPlayer.OnInfoListener() {
					@Override
					public boolean onInfo(MediaPlayer mp, int what, int extra) {
						if (DEBUG_LOGD) {
							Log.d(LOG_TAG, Log.buf().append("MediaPlayer onInfo: what=")
									.append(what).append(" extra=")
									.append(extra).toString());
						}
						return false;
					}
				});
				if (mp.getVideoWidth() != 0 || mp.getVideoHeight() != 0) {
					mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
				} else {
					// OnVideoSizeChangedListenerの設定はVideoViewの内部リスナ上書きするのでNG
					// 仕方がないので一定時間待ってリトライ
					mHandler.sendEmptyMessageDelayed(
							MSG_ID_WAIT_VIDEO_SIZE_GET, 1000L);
				}
			}
		});
		mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
			@Override
			public boolean onError(MediaPlayer mp, int what, int extra) {
			    if (DEBUG_LOGD) {
			        Log.d(LOG_TAG, Log.buf().append("MediaPlayer onError: mp=")
			                .append(mp.toString())
			                .append(" what=").append(what)
			                .append(" extra=").append(extra)
			                .toString());
			    }

				mHadError = true;
				// エラー状態でサイズ取得しようとすると例外飛ぶことがあるっぽいのでMessageキャンセル
				mHandler.removeMessages(MSG_ID_WAIT_VIDEO_SIZE_GET);
				mHandler.removeMessages(MSG_ID_INFO_PLAY_DATA_UPDATE);
//				if (mInfoPlayData.getTextBuilder().length() == 0) {
//					mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
//				}

				// エラーダイアログの多重表示はしない
				if (mErrorDialog == null || !mErrorDialog.isShowing()) {
	                String errorMessage;
	                switch (what) {
	                case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
	                    errorMessage = getString(R.string.errormessage_mediaplayer_streaming);
	                    break;
	                case MediaPlayer.MEDIA_ERROR_UNKNOWN:
	                    errorMessage = getString(R.string.errormessage_mediaplayer_unknown);
	                    break;
	                default:
	                    Log.d(LOG_TAG, Log.buf()
	                            .append("MediaPlayer onError: unexpected argument=")
	                            .append(what).append(',').append(extra).toString());
	                    errorMessage = getString(R.string.errormessage_mediaplayer_unknown);
	                    break;
	                }

	                DialogInterface.OnClickListener listener =
	                    new DialogInterface.OnClickListener() {
	                    @Override
	                    public void onClick(DialogInterface dialog, int which) {
	                        assert mErrorDialog == dialog;
	                        dialog.dismiss();
	                        switch (which) {
	                        case DialogInterface.BUTTON_POSITIVE:
	                            Intent intent = new Intent(getIntent());
	                            intent.setClass(getApplicationContext(),
	                                    NicoroFFmpegPlayer.class);
	                            startActivityIfNeeded(intent, 0);
	                            finish();
	                            break;
	                        case DialogInterface.BUTTON_NEGATIVE:
	                            finish();
	                            break;
	                        default:
	                            if (DEBUG_LOGD) {
	                                Log.d(LOG_TAG, Log.buf()
	                                        .append("NicoroMediaPlayer ErrorDialog onClick: unexpected argument=")
	                                        .append(which).toString());
	                            }
	                            break;
	                        }
	                    }
	                };
	                mErrorDialog = new AlertDialog.Builder(NicoroMediaPlayer.this)
	                    .setTitle(
	                        R.string.dialog_title_mediaplayer_error)
	                    .setMessage(errorMessage)
	                    .setCancelable(false)
	                    .setPositiveButton(
	                            R.string.dialog_button_mediaplayer_error_start_ffmpeg,
	                            listener)
	                    .setNegativeButton(
	                            R.string.dialog_button_mediaplayer_error_close,
	                            listener)
	                    .show();
				}

				return true;
			}
		});

		mMessageView = (MessageView) findViewById(R.id.message_view);
		mMessageView.setCallback(this);
		initializeView();

        Intent intent = getIntent();
        mVideoLoader = createVideoLoader(intent, getApplicationContext());
        if (mVideoLoader != null) {
        	mVideoLoader.setEventListener(new VideoLoader.EventListener() {
        		@Override
        		public void onStarted(VideoLoader streamLoader) {
					if (mHandler != null) {
						mHandler.sendEmptyMessage(MSG_ID_VIDEO_DOWNLOAD_STARTED);
					}
        		}
				@Override
				public void onCached(VideoLoader videoLoader) {
					if (mHandler != null) {
						mHandler.sendEmptyMessage(MSG_ID_VIDEO_CACHED);
					}
				}
				@Override
				public void onFinished(VideoLoader videoLoader) {
					if (mHandler != null) {
						mHandler.sendEmptyMessage(MSG_ID_VIDEO_DOWNLOAD_FINISHED);
					}
				}
				@Override
				public void onOccurredError(VideoLoader videoLoader, String errorMessage) {
					if (mHandler != null) {
						mHandler.obtainMessage(MSG_ID_VIDEO_OCCURRED_ERROR,
								errorMessage).sendToTarget();
					}
				}
				@Override
				public void onNotifyProgress(int num, int den) {
					if (mHandler != null) {
//						try {
//							if (mVideoView == null || !mVideoView.isPlaying()) {
								mHandler.removeMessages(MSG_ID_VIDEO_NOTIFY_PROGRESS);
								mHandler.obtainMessage(MSG_ID_VIDEO_NOTIFY_PROGRESS,
										num, den).sendToTarget();
//							}
//						} catch (IllegalStateException e) {
//							// Error状態でisPlaying呼ぶと例外飛ぶ
//							Log.e(LOG_TAG, e.toString(), e);
//						}
					}
				}
        	});
        	// 最初にダウンロード要求
        	mVideoLoader.startLoad();
        }

        mMediaPlayerStreaming = mSharedPreferences.getBoolean(
				getString(R.string.pref_key_mediaplayer_streaming),
				true);
        mMediaPlayerShiftStreamToFile = mSharedPreferences.getBoolean(
                getString(R.string.pref_key_mediaplayer_shift_stream_to_file),
                true);
	}

	@Override
	protected void onDestroy() {
		mVideoView.stopPlayback();
		if (mVideoProxyServer != null) {
			mVideoProxyServer.stopProxy();
			mVideoProxyServer = null;
		}
		super.onDestroy();
		mMediaPlayer = null;
		mVideoView = null;
		mMessageView = null;
	}

	@Override
	protected boolean canStartPlay() {
		return (mOnResumed
				&& (mIsVideoDownloadOk || (mIsVideoProxyOk && mIsVideoCachedOk))
				&& mMessageData.mIsMessageOk && mMessageDataFork.mIsMessageOk);
	}

	@Override
	protected void startPlay() {
		super.startPlay();

		if (mMediaPlayerStreaming && !mVideoLoader.isCacheCompleted()) {
			assert mVideoProxyServer != null;
			mVideoView.setVideoURI(mVideoProxyServer.getUri());
		} else {
			String file = mVideoLoader.getFilePath();
	        mVideoView.setVideoPath(file);
		}

		mVideoView.start();
		if (DEBUG_LOGD) {
			Log.d(LOG_TAG, "VideoView start");
		}

		Message message = mHandler.obtainMessage(MSG_ID_MESSAGE_DRAW_MESSAGE);
		mHandler.sendMessageDelayed(message, 33L);

		postStartPlayIfIsRestored();
	}

	@Override
	protected StringBuilder appendCurrentPlayTime(StringBuilder builder) {
		getCurrentPositionVideoPlay(mRatinalCurrentPlayTime);
		final int posNum = mRatinalCurrentPlayTime.num;
		final int posDen = mRatinalCurrentPlayTime.den;
		return appendCurrentPlayTimeCommon(builder, posNum, posDen);
	}

	@Override
	protected void getCurrentPositionVideoPlay(Rational rational) {
		rational.num = mVideoView.getCurrentPosition();
		rational.den = 1000;	// ms
	}
	@Override
	protected void getCurrentPositionAudioPlay(Rational rational) {
		// Videoと同じ
		getCurrentPositionVideoPlay(rational);
	}
	@Override
	protected void getCurrentPositionVideoDecode(Rational rational) {
		// playの時間を代わりに使う
		getCurrentPositionVideoPlay(rational);
	}
	@Override
	protected void getCurrentPositionAudioDecode(Rational rational) {
		// playの時間を代わりに使う
		getCurrentPositionAudioPlay(rational);
	}

	@Override
	protected boolean switchPausePlay() {
		if (mVideoView.isPlaying()) {
			pausePlay();
		} else {
			restartPlay();
		}
		setButtonPauseImage();
		return true;
	}

	@Override
	protected void pausePlay() {
//		mIconPause.setVisibility(View.VISIBLE);
		mVideoView.pause();
	}

	@Override
	protected void restartPlay() {
//		mIconPause.setVisibility(View.INVISIBLE);
		mVideoView.start();
	}

	@Override
	protected boolean isPausePlay() {
		return !mVideoView.isPlaying();
	}

	@Override
	protected void seekBySecond(int second) {
		seekBySecondCommon(second);
		if (mVideoView == null) {
			// VideoViewの準備ができていない場合は、この場でシークバー有効化
			if (mHandler != null) {
				mHandler.sendEmptyMessage(MSG_ID_ENABLE_SEEK_BAR);
			}
		} else {
			mVideoView.seekTo(second * 1000);
		}
	}

	@Override
	protected StringBuilder appendVideoResolution(StringBuilder builder) {
		if (mMediaPlayer != null && !mHadError) {
            // まだIllegalStateException出る場合がある
            try {
    			int videoWidth = mMediaPlayer.getVideoWidth();
    			int videoHeight = mMediaPlayer.getVideoHeight();
    			if (videoWidth != 0 || videoHeight != 0) {
    				builder.append(videoWidth)
    					.append('×')
    					.append(videoHeight);
    			}
            } catch (IllegalStateException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
		}
		return builder;
	}

	@Override
	protected StringBuilder appendPlayerInfo(StringBuilder builder) {
		return builder.append(getString(R.string.info_play_data_mediaplayer));
	}

	@Override
	public void drawMessageView(MessageView messageView, Canvas canvas) {
		canvas.drawColor(0x00000000);
//		if (mVideoView.isPlaying()) {
		if (mMessageData.mIsMessageOk && mMessageDataFork.mIsMessageOk) {
			int vpos = mVideoView.getCurrentPosition() / 10;
			mMessageChatController.drawMessage(canvas,
					mMessageData,
	        		mMessageDataFork,
					vpos, messageView.getWidth(), messageView.getHeight(),
					mMessageDisable);
		}
	}

	private void setVideoViewLayout() {
        if (mFullscreen16_9) {
            mVideoView.getLayoutParams().width = FILL_PARENT;
            mVideoView.getLayoutParams().height = FILL_PARENT;
        } else {
            DisplayMetrics metrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(metrics);
            mVideoView.getLayoutParams().width =
//              (int) (metrics.heightPixels * 4 / 3 / metrics.density);
                (int) (metrics.heightPixels * 4 / 3);
            mVideoView.getLayoutParams().height =
//              (int) (metrics.heightPixels / metrics.density);
                (int) (metrics.heightPixels);
        }
        mVideoView.requestLayout();
	}

	/**
	 * ストリーム再生からファイル再生へ移行する
	 */
	private void shiftPlayStreamToFile() {
        if (mShowHintToast) {
            Toast toast = Toast.makeText(getApplicationContext(),
                    R.string.toast_shift_stream_to_file, Toast.LENGTH_SHORT);
            toast.setGravity(Gravity.BOTTOM | Gravity.RIGHT, 0, 0);
            toast.show();
        }

        int currentMs = mVideoView.getCurrentPosition();
        boolean isPlaying = mVideoView.isPlaying();

//        mVideoView.stopPlayback();
        String file = mVideoLoader.getFilePath();
        mVideoView.setVideoPath(file);
        seekBySecond(currentMs / 1000); // start前にseek実行
        mVideoView.start();
        mVideoProxyServer.stopProxy();
        mVideoProxyServer = null;
        if (!isPlaying) {
            mVideoView.pause();
        }
        // data再設定後はVideoViewのサイズが狂うときがある？ので
        // MessageViewを手動調整
        if (mVideoViewWidth > 0 && mVideoViewHeight > 0) {
            RelativeLayout.LayoutParams param =
                (RelativeLayout.LayoutParams)
                mMessageView.getLayoutParams();
            assert param != null;
            param.width = mVideoViewWidth;
            param.height = mVideoViewHeight;
            param.addRule(RelativeLayout.ALIGN_RIGHT, 0);
            mMessageView.requestLayout();
        }
	}
}
