package jp.sourceforge.nicoro;

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

import android.graphics.Canvas;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FFmpegPlayerFragment extends AbstractPlayerFragment
implements SurfaceVideoDrawer.DrawMessage, FFmpegVideoDecoder.SeekerBase {
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & false;
    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
    private static final boolean DEBUG_TRACE = Release.IS_DEBUG & false;

    /** Surfaceの準備完了 */
    private static final int MSG_ID_SURFACE_READY = MSG_ID_SUB_OFFSET + 1;
    /** Surfaceが破棄された */
    private static final int MSG_ID_SURFACE_DESTROYED = MSG_ID_SUB_OFFSET + 2;

    private SurfaceVideoDrawerInterface mSurfaceVideoDrawer =
        SurfaceVideoDrawerInterface.NullObject.getInstance();
    /*private*/ volatile boolean mIsFinish;
    /*private*/ volatile boolean mIsDecodedComplete;
    /*private*/ StreamAudioPlayerInterface mStreamAudioPlayer =
        StreamAudioPlayerInterface.NullObject.getInstance();

    private boolean mIsSurfaceOk;

    private FFmpegVideoDecoderInterface mFFmpegVideoDecoder =
        FFmpegVideoDecoderInterface.NullObject.getInstance();

    @SuppressWarnings("unused")
    private final Rational mRationalDebugLog = (DEBUG_LOGV || DEBUG_LOGD)
        ? new Rational() : null;

    private volatile boolean mIsPlaying = false;

    private volatile boolean mIsPause = false;

    private int mDisplayWidth = 0;
    private int mDisplayHeight = 0;

    private static class CreateDrawBufferListener implements FFmpegVideoDecoderInterface.CreateDrawBufferListener {
        private WeakReference<FFmpegPlayerFragment> mThis;

        public CreateDrawBufferListener(FFmpegPlayerFragment thiz) {
            mThis = new WeakReference<FFmpegPlayerFragment>(thiz);
        }

        @Override
        public void onCreateDrawBuffer(FFmpegVideoDecoderInterface decoder) {
            FFmpegPlayerFragment thiz = mThis.get();
            if (thiz == null) {
                return;
            }
            thiz.mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
        }
    }
    private CreateDrawBufferListener mCreateDrawBufferListener;

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
        case MSG_ID_VIDEO_CACHED:
            mIsVideoCachedOk = true;
            // 動画データを元にSurface準備
//                  try {
//                      if (!mFFmpegVideoDecoder.prepareFFmpeg(true)) {
//                          // 動画読み込み完了後にもう１回チャレンジ
//                          mIsVideoCachedOk = false;
//                      }
//                  } catch (IllegalArgumentException e) {
//                      String error = e.toString();
//                      Log.e(LOG_TAG, error, e);
//                        if (mHandler != null) {
//                            mHandler.obtainMessage(MSG_ID_PLAY_ERROR,
//                                    error).sendToTarget();
//                        }
//                  }
            new FFmpegVideoDecoderInterface.PrepareFFmpegTask(mFFmpegVideoDecoder) {
                @Override
                protected void onPostExecute(Boolean result) {
                    Handler handler = mHandler;
                    Exception e = getException();
                    if (e != null) {
                        handler.obtainMessage(MSG_ID_PLAY_ERROR,
                                e.toString()).sendToTarget();
                        return;
                    }
                    if (!result) {
                        if (!mIsVideoCachedOk) {
                            // 動画読み込み完了後にもう１回チャレンジ
                            mIsVideoCachedOk = false;
                        }
                    }
                }
            }.executeWrapper(true);
            break;
        case MSG_ID_SURFACE_READY:
            assert mIsVideoCachedOk;
            mIsSurfaceOk = true;
            if (!mIsPlaying && canStartPlay()) {
                startPlay();
            }
            onVideoViewSizeChanged(mSurfaceVideoDrawer.getViewWidth(),
                    mSurfaceVideoDrawer.getViewHeight());
            break;
        case MSG_ID_SURFACE_DESTROYED:
            mIsSurfaceOk = false;
            break;
        case MSG_ID_VIDEO_DOWNLOAD_FINISHED:
            super.handleMessage(msg);
            if (!mIsVideoCachedOk) {
                // 動画データを元にSurface準備
//                if (!mFFmpegVideoDecoder.prepareFFmpeg(true)) {
//                    // 読み込み完了後で駄目ならエラー
//                    if (mHandler != null) {
//                        mHandler.obtainMessage(MSG_ID_PLAY_ERROR,
//                                getString(R.string.errormessage_ffmpeg_prepare))
//                                .sendToTarget();
//                    }
//                }
                new FFmpegVideoDecoderInterface.PrepareFFmpegTask(mFFmpegVideoDecoder) {
                    @Override
                    protected void onPostExecute(Boolean result) {
                        Handler handler = mHandler;
                        Exception e = getException();
                        if (e != null) {
                            handler.obtainMessage(MSG_ID_PLAY_ERROR,
                                    e.toString()).sendToTarget();
                            return;
                        }
                        if (!result) {
                            // 読み込み完了後で駄目ならエラー
                            handler.obtainMessage(MSG_ID_PLAY_ERROR,
                                    getString(R.string.errormessage_ffmpeg_prepare))
                                    .sendToTarget();
                        }
                    }
                }.executeWrapper(true);
            }
            break;
        default:
            super.handleMessage(msg);
            break;
        }
        return true;
    }

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

//      VMRuntime.getRuntime().setMinimumHeapSize(Long.MAX_VALUE);

        initializeVideoLoader();

        mIsSurfaceOk = false;

        mIsPlaying = false;
        mIsPause = false;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_ffmpegplayer, container, false);

        mSurfaceVideoDrawer = new SurfaceVideoDrawer(
                (SurfaceView) view.findViewById(R.id.surface));
        mSurfaceVideoDrawer.registerEventHandler(mHandler,
                MSG_ID_SURFACE_READY, MSG_ID_SURFACE_DESTROYED,
                MSG_ID_PLAY_FINISHED);
        mSurfaceVideoDrawer.setDrawMessage(this);

        mStreamAudioPlayer = new StreamAudioPlayer(mContext);
        mFFmpegVideoDecoder = new FFmpegVideoDecoder(mSurfaceVideoDrawer,
                mStreamAudioPlayer, mVideoLoader,
                mContext, mLastOrientation,
                this);
        mFFmpegVideoDecoder.setCacheDecodeFirst(true);
        mCreateDrawBufferListener = new CreateDrawBufferListener(this);
        mFFmpegVideoDecoder.setCreateDrawBufferListener(mCreateDrawBufferListener);

        return view;
    }

    @Override
    public void onStart() {
        super.onStart();
        mFFmpegVideoDecoder.setIsVisible(true);
    }

    @Override
    public void onStop() {
        super.onStop();
        mFFmpegVideoDecoder.setIsVisible(false);
    }

    @Override
    public void onDestroy() {
        if (DEBUG_TRACE) {
            Debug.stopMethodTracing();
        }

        super.onDestroy();
    }

    @Override
    public void onDestroyImpl() {
        mIsFinish = true;
        releaseDecoderAndPlayer();
        // quitがタイムアウトで抜けた場合にsuper.onDestroyでメンバnullに設定するとぬるぽ発生するかも
        // →joinのタイムアウト無くしたのでおそらく発生しない
        super.onDestroyImpl();
    }

    @Override
    public void onDestroyImplPost() {
        super.onDestroyImplPost();
        mStreamAudioPlayer = StreamAudioPlayerInterface.NullObject.getInstance();
        mSurfaceVideoDrawer = SurfaceVideoDrawerInterface.NullObject.getInstance();
        mFFmpegVideoDecoder = FFmpegVideoDecoderInterface.NullObject.getInstance();

        Util.gcStrong();
    }

//    @Override
//    protected void finalize() throws Throwable {
//        try {
//            if (DEBUG_LOGV) {
//                Log.v(LOG_TAG, "NicoroFFmpegPlayer#finalize start");
//            }
//            super.finalize();
//        } finally {
//            mIsFinish = true;
//            releaseDecoderAndPlayer();
//            if (DEBUG_LOGV) {
//                Log.v(LOG_TAG, "NicoroFFmpegPlayer#finalize end");
//            }
//        }
//    }

    private void releaseDecoderAndPlayer() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        CountDownLatch latch = new CountDownLatch(2);
        mSurfaceVideoDrawer.quitAsync(executorService, latch);
        mFFmpegVideoDecoder.quitAsync(executorService, latch);

        mStreamAudioPlayer.finish();

        try {
            latch.await();
        } catch (InterruptedException e) {
            Log.e(LOG_TAG, e.toString(), e);
        }
        executorService.shutdown();
    }

    @Override
    protected void onOrientationChanged(int orientation) {
        // layout整ってから設定
//        mFFmpegVideoDecoder.updateDisplayMetrics(this);
        mFFmpegVideoDecoder.setOrientation(orientation);
//        mFFmpegVideoDecoder.updateSurfaceSize();
    }

    @Override
    public void onParentScreenLayout(int width, int height) {
        if (width != mDisplayWidth || height != mDisplayHeight) {
            mDisplayWidth = width;
            mDisplayHeight = height;
            mFFmpegVideoDecoder.updateDisplaySize(width, height);
            mFFmpegVideoDecoder.updateSurfaceSize();
        }
    }

    @Override
    protected boolean canStartPlay() {
        if (mStateManager.isStarting()) {
            return (mIsSurfaceOk
                    && (mIsVideoCachedOk || mIsVideoDownloadOk)
                    && mMessageChatController.isMessageDataOk());
        } else if (mStateManager.wasCreated() && !mStateManager.wasDestroyed()) {
            // バックグラウンド中
            return ((mIsVideoCachedOk || mIsVideoDownloadOk)
                    && mMessageChatController.isMessageDataOk());
        } else {
            return false;
        }
    }

    @Override
    protected void startPlay() {
        Util.gcStrong();
        SystemClock.sleep(500L);

        super.startPlay(mThumbInfo);

        mIsPlaying = true;
        mIsPause = false;
        mIsFinish = false;
        mStreamAudioPlayer.prepareStart();
        mIsDecodedComplete = false;
//        mFFmpegVideoDecoder.prepareFFmpeg();
        mFFmpegVideoDecoder.prepareDecode();
        mFFmpegVideoDecoder.start();

        // このタイミングでは MSG_ID_INFO_PLAY_DATA_UPDATE はちょっと早いので、CreateDrawBufferListener 経由でもう一回呼ぶ
        mHandler.sendEmptyMessage(MSG_ID_INFO_PLAY_DATA_UPDATE);
        postStartPlayIfIsRestored();

        if (DEBUG_TRACE) {
            Debug.startMethodTracing(getClass().getSimpleName());
        }
    }

    @Override
    public StringBuilder appendCurrentPlayTime(StringBuilder builder) {
        if (DEBUG_LOGV) {
            Rational r = mRationalDebugLog;
            getCurrentPositionVideoPlay(r);
            if (r.den == 0) { r.den = 1; }
            Log.v(LOG_TAG, Log.buf().append("VideoPlayTime: ")
                    .append(r.num).append("/").append(r.den).append(" ")
                    .append(r.getDivideFloat()).toString());
            getCurrentPositionAudioPlay(r);
            if (r.den == 0) { r.den = 1; }
            Log.v(LOG_TAG, Log.buf().append("AudioPlayTime: ")
                    .append(r.num).append("/").append(r.den).append(" ")
                    .append(r.getDivideFloat()).toString());
            getCurrentPositionVideoDecode(r);
            if (r.den == 0) { r.den = 1; }
            Log.v(LOG_TAG, Log.buf().append("VideoDecodeTime: ")
                    .append(r.num).append("/").append(r.den).append(" ")
                    .append(r.getDivideFloat()).toString());
            getCurrentPositionAudioDecode(r);
            if (r.den == 0) { r.den = 1; }
            Log.v(LOG_TAG, Log.buf().append("AudioDecodeTime: ")
                    .append(r.num).append("/").append(r.den).append(" ")
                    .append(r.getDivideFloat()).toString());
        }

        getCurrentPositionVideoPlay(mRationalCurrentPlayTime);
        final long posNum = mRationalCurrentPlayTime.num;
        final int posDen = mRationalCurrentPlayTime.den;
        return appendCurrentPlayTimeCommon(builder, posNum, posDen);
    }

    @Override
    public boolean hasTotalPlayTime() {
        return true;
    }

    @Override
    public void getCurrentPositionVideoPlay(Rational rational) {
        if (mIsFinish) {
            return;
        }
        mSurfaceVideoDrawer.getCurrentPosition(rational);
    }
    @Override
    protected void getCurrentPositionAudioPlay(Rational rational) {
        if (mIsFinish) {
            return;
        }
        // シークすると若干誤差が出る
        mStreamAudioPlayer.getCurrentPosition(rational);
    }
    @Override
    protected void getCurrentPositionVideoDecode(Rational rational) {
        if (mIsFinish) {
            return;
        }
        mFFmpegVideoDecoder.getCurrentPositionVideoDecode(rational);
    }
    @Override
    protected void getCurrentPositionAudioDecode(Rational rational) {
        if (mIsFinish) {
            return;
        }
//      // TODO シークすると破綻
        mFFmpegVideoDecoder.getCurrentPositionAudioDecode(rational);
    }

    @Override
    protected boolean switchPausePlay() {
        if (mIsFinish) {
            return false;
        }
        if (mStreamAudioPlayer.isInDummyDataAtStart()) {
            return false;
        }

        if (!mIsPause) {
            pausePlay();
        } else {
            restartPlay();
        }
        setButtonPauseImage();
        return true;
    }

    @Override
    protected void pausePlay() {
        if (mIsFinish) {
            return;
        }
//      mIconPause.setVisibility(View.VISIBLE);
        if (mStreamAudioPlayer.isInDummyDataAtStart()
                /*|| mStreamAudioPlayer.isInDummyDataAtSeek()*/) {
            mStreamAudioPlayer.reservePause();
        } else {
            mStreamAudioPlayer.pause();
        }
        mSurfaceVideoDrawer.pause();
        mFFmpegVideoDecoder.pause();
        mIsPause = true;
    }

    @Override
    protected void restartPlay() {
        if (mIsFinish) {
            return;
        }
//      mIconPause.setVisibility(View.INVISIBLE);
        mStreamAudioPlayer.restart();
        mSurfaceVideoDrawer.restart();
        mFFmpegVideoDecoder.restart();
        mIsPause = false;
    }

    @Override
    protected boolean isPausePlay() {
        if (mIsFinish) {
            // 都合上true返す
            return true;
        }
        if (mStreamAudioPlayer.isInDummyDataAtStart()) {
            // 都合上true返す
            return true;
        }
        return mIsPause;
    }

    @Override
    protected void seekBySecond(int second) {
        if (mIsFinish) {
            return;
        }
        mFFmpegVideoDecoder.seekBySecond(second);
    }

    @Override
    protected VideoLoaderInterface.EventListener createVideoLoaderEventListener() {
        return new VideoLoaderInterface.EventListener() {
            @Override
            public void onStarted(VideoLoaderInterface streamLoader) {
                // 何もしない
            }
            @Override
            public void onCached(VideoLoaderInterface videoLoader) {
                Handler handler = mHandler;
                handler.sendEmptyMessage(MSG_ID_VIDEO_CACHED);
            }
            @Override
            public void onFinished(VideoLoaderInterface videoLoader) {
                Handler handler = mHandler;
                handler.sendEmptyMessage(MSG_ID_VIDEO_DOWNLOAD_FINISHED);
            }
            @Override
            public void onOccurredError(VideoLoaderInterface videoLoader, String errorMessage) {
                Handler handler = mHandler;
                handler.obtainMessage(MSG_ID_VIDEO_OCCURRED_ERROR,
                        errorMessage).sendToTarget();
            }
            @Override
            public void onNotifyProgress(int num, int den) {
                Handler handler = mHandler;
//               if (!mIsPlaying) {
                    handler.removeMessages(MSG_ID_VIDEO_NOTIFY_PROGRESS);
                    handler.obtainMessage(MSG_ID_VIDEO_NOTIFY_PROGRESS,
                            num, den).sendToTarget();
//                }
            }
            @Override
            public void onRestarted(VideoLoaderInterface streamLoader) {
                // 何もしない
            }
        };
    }

    @Override
    public StringBuilder appendVideoResolution(StringBuilder builder) {
        if (mIsFinish) {
            return builder;
        }
        return builder.append(mFFmpegVideoDecoder.getVideoOriginalWidth())
            .append('×')
            .append(mFFmpegVideoDecoder.getVideoOriginalHeight());
    }

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

    @Override
    public void drawMessage(Canvas canvas, int vpos) {
        canvas.setMatrix(null);
        mMessageChatController.drawMessage(canvas,
                vpos,
                canvas.getWidth(), canvas.getHeight(),
                getMessageDisable());
    }

    @Override
    protected int getVpos() {
        return mSurfaceVideoDrawer.getVpos();
    }

    @Override
    public void enableSeekBar() {
        Handler handler = mHandler;
        handler.sendEmptyMessage(MSG_ID_ENABLE_SEEK_BAR);
    }

    @Override
    public void seekBySecondBase(int second) {
        seekBySecondCommon(second);
    }
}
