package jp.sourceforge.nicoro;

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

import android.content.Context;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;

import jp.gr.java_conf.shiseissi.commonlib.StackTracer;

public class MediaSurfaceView extends SurfaceView implements SurfaceHolder.Callback,
        MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener,
        MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener,
        MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener,
        MediaPlayer.OnVideoSizeChangedListener {

    private static final boolean DEBUG_LOGD = Release.IS_DEBUG & true;
    private static final boolean DEBUG_LOGV = Release.IS_DEBUG & false;

    private MediaPlayer mMediaPlayer;

    private MediaPlayer.OnPreparedListener mOnPreparedListener;
    private MediaPlayer.OnErrorListener mOnErrorListener;
    private MediaPlayer.OnCompletionListener mOnCompletionListener;
    private MediaPlayer.OnInfoListener mOnInfoListener;
    private MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener;
    private MediaPlayer.OnSeekCompleteListener mOnSeekCompleteListener;

    private boolean mSurfaceOk = false;
    private boolean mPrepared = false;
    private boolean mReserveStart = false;
    private boolean mReservePause = false;
    private boolean mPlayOrPause = false;
    private boolean mRestoreSurface = false;
    private boolean mPauseAfterRestoreSurface = false;
    private boolean mRestartAfterDestroySurface = false;
    private int mSeekAfterRestoreSurface = 0;
    private Uri mUri;
    private int mSurfaceWidth = 0;
    private int mSurfaceHeight = 0;
    private int mVideoWidth = 0;
    private int mVideoHeight = 0;
    private int mSeekWhenPrepared = 0;
    private int mPositionOnSurfaceDestroyed = 0;

    public MediaSurfaceView(Context context) {
        super(context);
        init();
    }

    public MediaSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MediaSurfaceView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    @SuppressWarnings("deprecation")
    private void init() {
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        setFocusable(true);
        setFocusableInTouchMode(true);
        requestFocus();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        int width = getDefaultSize(mVideoWidth, widthMeasureSpec);
//        int height = getDefaultSize(mVideoHeight, heightMeasureSpec);
//        if (mVideoWidth > 0 && mVideoHeight > 0) {
//            int vw_h = mVideoWidth * height;
//            int w_vh = width * mVideoHeight;
//            if (vw_h > w_vh) {
//                height = (w_vh + (mVideoWidth - 1)) / mVideoWidth;
//            } else if (vw_h < w_vh) {
//                width = (vw_h + (mVideoHeight - 1)) / mVideoHeight;
//            }
//        }
        int width = getDefaultSize(mSurfaceWidth, widthMeasureSpec);
        int height = getDefaultSize(mSurfaceHeight, heightMeasureSpec);
        if (mSurfaceWidth > 0 && mSurfaceHeight > 0) {
            int vw_h = mSurfaceWidth * height;
            int w_vh = width * mSurfaceHeight;
            if (vw_h > w_vh) {
                height = (w_vh + (mSurfaceWidth - 1)) / mSurfaceWidth;
            } else if (vw_h < w_vh) {
                width = (vw_h + (mSurfaceHeight - 1)) / mSurfaceHeight;
            }
        }
        setMeasuredDimension(width, height);
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, Log.buf().append("MediaSurfaceView onMeasure:")
                    .append(" widthMeasureSpec=").append(widthMeasureSpec)
                    .append(" heightMeasureSpec=").append(heightMeasureSpec)
                    .append(" width=").append(width)
                    .append(" height=").append(height)
                    .toString());
        }
    }

    @Override
    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("MediaPlayer onVideoSizeChanged: width=")
                    .append(width).append(" height=").append(height)
                    .toString());
        }

        mVideoWidth = width;
        mVideoHeight = height;

        if (mOnVideoSizeChangedListener != null) {
            mOnVideoSizeChangedListener.onVideoSizeChanged(mp, width, width);
        }

        if (mVideoWidth != 0 && mVideoHeight != 0) {
            updateValidVideoSize();
        }
    }

    @Override
    public void onSeekComplete(MediaPlayer mp) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, "MediaPlayer onSeekComplete");
        }

        if (mOnSeekCompleteListener != null) {
            mOnSeekCompleteListener.onSeekComplete(mp);
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, "MediaPlayer onPrepared");
        }

        mPrepared = true;
        mVideoWidth = mp.getVideoWidth();
        mVideoHeight = mp.getVideoHeight();

        if (mOnPreparedListener != null) {
            mOnPreparedListener.onPrepared(mp);
        }

        if (mVideoWidth != 0 && mVideoHeight != 0) {
            updateValidVideoSize();
        }

        if (mRestoreSurface) {
            mRestoreSurface = false;
            if (mSurfaceOk) {
                try {
                    int currentPos = mSeekAfterRestoreSurface;
                    mSeekAfterRestoreSurface = 0;
                    mp.seekTo(currentPos);
                    mp.start();
                    if (mPauseAfterRestoreSurface) {
                        mPauseAfterRestoreSurface = false;
                        mp.pause();
                    }
                    if (DEBUG_LOGD) {
                        Log.d(LOG_TAG, Log.buf().append("RestoreSurface: position=")
                                .append(currentPos).toString());
                    }
                } catch (IllegalStateException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                }
            }
        } else if (mRestartAfterDestroySurface) {
            mRestartAfterDestroySurface = false;
            int currentPos = mPositionOnSurfaceDestroyed;
            mPositionOnSurfaceDestroyed = 0;
            mp.seekTo(currentPos);
            mp.start();
            if (DEBUG_LOGD) {
                Log.d(LOG_TAG, Log.buf().append("RestartAfterDestroySurface: position=")
                        .append(currentPos).toString());
            }
        } else {
            if (mSeekWhenPrepared != 0) {
                seekTo(mSeekWhenPrepared);
            }

            if (mReserveStart) {
                if (mp == mMediaPlayer) {
                    mp.start();
                    mReserveStart = false;
                }
            }
            if (mReservePause) {
                if (mp == mMediaPlayer) {
                    if (mp.isPlaying()) {
                        mp.pause();
                    }
                    mReservePause = false;
                }
            }
        }
    }

    private void updateValidVideoSize() {
        int width;
        int height;
        if (mSurfaceWidth == 0 && mSurfaceHeight == 0) {
            width = mVideoWidth;
            height = mVideoHeight;
        } else {
            width = mSurfaceWidth;
            height = mSurfaceHeight;
            int vw_h = mVideoWidth * height;
            int w_vh = width * mVideoHeight;
            if (vw_h > w_vh) {
                height = (w_vh + (mVideoWidth - 1)) / mVideoWidth;
            } else if (vw_h < w_vh) {
                width = (vw_h + (mVideoHeight - 1)) / mVideoHeight;
            }
        }
        setSurfaceSize(width, height);
    }

    @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());
        }
        if (mOnInfoListener != null) {
            if (mOnInfoListener.onInfo(mp, what, extra)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("MediaPlayer onError: what=")
                    .append(what).append(" extra=").append(extra)
                    .toString());
        }

        mPrepared = false;

        if (!mSurfaceOk && mPositionOnSurfaceDestroyed != 0) {
            // バックグラウンドに回って強制的に再生が止まった
            post(new Runnable() {
                @Override
                public void run() {
                    MediaPlayer mp = mMediaPlayer;
                    if (mp != null) {
                        try {
                            mPrepared = false;
                            mp.reset();
                            mp.setDisplay(null);
                            mp.setDataSource(getContext(), mUri);
                            mp.prepareAsync();
                            mRestartAfterDestroySurface = true;
                        } catch (IllegalStateException e) {
                            Log.e(LOG_TAG, e.toString(), e);
                            onError(mp, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                        } catch (IOException e) {
                            Log.e(LOG_TAG, e.toString(), e);
                            onError(mp, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                        }
                    }
                }
            });
            return true;
        }

        if (mOnErrorListener != null) {
            if (mOnErrorListener.onError(mp, what, extra)) {
//                return true;
            }
        }
        // TODO NicoroMediaPlayerで処理することを期待して、ダイアログ表示等は省略

        // XXX 問答無用で全部解放
        release();

        return true;
    }

    @Override
    public void onCompletion(MediaPlayer mp) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, "MediaPlayer onCompletion");
        }

        if (mOnCompletionListener != null) {
            mOnCompletionListener.onCompletion(mp);
        }
    }

    @Override
    public void onBufferingUpdate(MediaPlayer mp, int percent) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("MediaPlayer onBufferingUpdate: percent=")
                    .append(percent).toString());
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, "surfaceCreated");
        }
        mSurfaceOk = true;
        MediaPlayer mp = mMediaPlayer;
        if (mp != null) {
            try {
                mp.setDisplay(holder);
            } catch (IllegalArgumentException e) {
                Log.e(LOG_TAG, "surface has been released by onPause?", e);
            }
            if (mPlayOrPause && !mRestoreSurface) {
                // Surface復帰のためには一度resetする必要がある
                try {
                    if (mp.isPlaying()) {
                        mp.pause();
                        mPauseAfterRestoreSurface = false;
                    } else if (mReserveStart) {
                        mPauseAfterRestoreSurface = false;
                        mReserveStart = false;
                    } else {
                        mPauseAfterRestoreSurface = true;
                    }
                    int currentPos = mp.getCurrentPosition();
                    mSeekAfterRestoreSurface = currentPos;
                    mPrepared = false;
                    mp.reset();
                    mp.setDataSource(getContext(), mUri);
                    mp.prepareAsync();
                    mRestoreSurface = true;
                } catch (IllegalStateException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    onError(mp, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                } catch (IOException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    onError(mp, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                }
            }
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("surfaceChanged: format=").append(format)
                    .append(" width=").append(width).append(" height=").append(height).toString());
        }
//        mSurfaceWidth = width;
//        mSurfaceHeight = height;
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, "surfaceDestroyed");
        }
        mSurfaceOk = false;
        mPositionOnSurfaceDestroyed = 0;
        MediaPlayer mp = mMediaPlayer;
        if (mp != null) {
            if (mp.isPlaying()) {
                mPositionOnSurfaceDestroyed = mp.getCurrentPosition();
                if (DEBUG_LOGD) {
                    Log.d(LOG_TAG, Log.buf().append("mPositionOnSurfaceDestroyed=")
                            .append(mPositionOnSurfaceDestroyed).toString());
                }
            }
            mp.setDisplay(null);
        }
    }

    public void setOnPreparedListener(MediaPlayer.OnPreparedListener onPreparedListener) {
        mOnPreparedListener = onPreparedListener;
    }
    public void setOnErrorListener(MediaPlayer.OnErrorListener onErrorListener) {
        mOnErrorListener = onErrorListener;
    }
    public void setOnCompletionListener(MediaPlayer.OnCompletionListener onCompletionListener) {
        mOnCompletionListener = onCompletionListener;
    }
    public void setOnInfoListener(MediaPlayer.OnInfoListener onInfoListener) {
        mOnInfoListener = onInfoListener;
    }
    public void setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener) {
        mOnVideoSizeChangedListener = onVideoSizeChangedListener;
    }
    public void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener onSeekCompleteListener) {
        mOnSeekCompleteListener = onSeekCompleteListener;
    }

    public void setVideoPath(String file) {
        setVideoURI(Uri.parse(file));
    }

    public void setVideoURI(Uri uri) {
        mUri = uri;
        openVideo();
        requestLayout();
        invalidate();
    }

    public void start() {
        if (mRestoreSurface) {
            mPauseAfterRestoreSurface = false;
        } else {
            mReserveStart = true;
            if (mPrepared) {
                try {
                    MediaPlayer mp = mMediaPlayer;
                    if (mp != null) {
                        mp.start();
                        mReserveStart = false;
                    }
                } catch (IllegalStateException e) {
                    Log.e(LOG_TAG, e.toString(), e);
                    onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                }
            }
        }
    }

    public void stopPlayback() {
        if (mRestoreSurface) {
            mRestoreSurface = false;
        }
        try {
            MediaPlayer mp = mMediaPlayer;
            if (mp != null) {
                mp.stop();
            }
        } catch (IllegalStateException e) {
            Log.e(LOG_TAG, e.toString(), e);
            onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        }
    }

    public void pause() {
        if (mRestoreSurface) {
            mPauseAfterRestoreSurface = true;
        } else {
            mReservePause = true;
            try {
                MediaPlayer mp = mMediaPlayer;
                if (mp != null && mp.isPlaying()) {
                    mp.pause();
                    mReservePause = false;
                }
            } catch (IllegalStateException e) {
                Log.e(LOG_TAG, e.toString(), e);
                onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
            }
        }
    }

    public boolean isPlaying() {
        if (mRestoreSurface) {
            return !mPauseAfterRestoreSurface;
        } else {
            try {
                MediaPlayer mp = mMediaPlayer;
                return (mp != null && mp.isPlaying());
            } catch (IllegalStateException e) {
                Log.e(LOG_TAG, e.toString(), e);
                onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                return false;
            }
        }
    }

    public int getCurrentPosition() {
        if (mRestoreSurface) {
            return mSeekAfterRestoreSurface;
        } else {
            try {
                MediaPlayer mp = mMediaPlayer;
                if (mp == null) {
                    return 0;
                } else {
                    return mp.getCurrentPosition();
                }
            } catch (IllegalStateException e) {
                Log.e(LOG_TAG, e.toString(), e);
                onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
                return 0;
            }
        }
    }

    public void seekTo(int msec) {
        if (mRestoreSurface) {
            mSeekAfterRestoreSurface = msec;
        } else {
            try {
                MediaPlayer mp = mMediaPlayer;
                if (mp == null || !mPrepared) {
                    mSeekWhenPrepared = msec;
                } else {
                    mp.seekTo(msec);
                    mSeekWhenPrepared = 0;
                }
            } catch (IllegalStateException e) {
                Log.e(LOG_TAG, e.toString(), e);
                onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
            }
        }
    }

    public void release() {
        MediaPlayer mp = mMediaPlayer;
        if (mp != null) {
            try {
                mp.stop();
            } catch (IllegalStateException e) {
                Log.e(LOG_TAG, e.toString(), e);
            }
            mp.release();
            mMediaPlayer = null;
        }
        clearState();
    }

    public int getVideoWidth() {
        return mVideoWidth;
    }
    public int getVideoHeight() {
        return mVideoHeight;
    }

    public void setSurfaceSize(int width, int height) {
        mSurfaceWidth = width;
        mSurfaceHeight = height;
        SurfaceHolder holder = getHolder();
        holder.setFixedSize(mSurfaceWidth, mSurfaceHeight);
        if (DEBUG_LOGD) {
            Log.d(LOG_TAG, Log.buf().append("MediaSurfaceView: setSurfaceSize")
                    .append(" width=").append(mSurfaceWidth)
                    .append(" height=").append(mSurfaceHeight).toString());
        }
        if (DEBUG_LOGV) {
            Log.v(LOG_TAG, new StackTracer().getStackTraceString());
        }
    }

    public boolean isPrepared() {
        return mPrepared;
    }

    private void clearState() {
        mSeekWhenPrepared = 0;
        mSurfaceWidth = 0;
        mSurfaceHeight = 0;
        mVideoWidth = 0;
        mVideoHeight = 0;
        mPrepared = false;
        mReserveStart = false;
        mPlayOrPause = false;
        mRestoreSurface = false;
        mPauseAfterRestoreSurface = false;
        mSeekAfterRestoreSurface = 0;
        mRestartAfterDestroySurface = false;
        mPositionOnSurfaceDestroyed = 0;
    }

    private void openVideo() {
        if (mMediaPlayer == null) {
            createMediaPlayer();
        } else {
            mMediaPlayer.reset();
        }

        clearState();
        mPlayOrPause = true;

        try {
            MediaPlayer mp = mMediaPlayer;
            mp.setDataSource(getContext(), mUri);
            mp.prepareAsync();
        } catch (IllegalArgumentException e) {
            Log.e(LOG_TAG, e.toString(), e);
            onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        } catch (SecurityException e) {
            Log.e(LOG_TAG, e.toString(), e);
            onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        } catch (IllegalStateException e) {
            Log.e(LOG_TAG, e.toString(), e);
            onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        } catch (IOException e) {
            Log.e(LOG_TAG, e.toString(), e);
            onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
        }
    }

    private void createMediaPlayer() {
        MediaPlayer mp = new MediaPlayer();
        mMediaPlayer = mp;
        mp.setOnBufferingUpdateListener(this);
        mp.setOnCompletionListener(this);
        mp.setOnErrorListener(this);
        mp.setOnInfoListener(this);
        mp.setOnPreparedListener(this);
        mp.setOnSeekCompleteListener(this);
        mp.setOnVideoSizeChangedListener(this);
        if (mSurfaceOk) {
            mp.setDisplay(getHolder());
        }
        mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mp.setScreenOnWhilePlaying(true);
    }
}
