/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */

package com.android.tv.dvr.ui.playback;

import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaController.TransportControls;
import android.media.session.PlaybackState;
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import androidx.leanback.media.PlaybackControlGlue;
import androidx.leanback.widget.AbstractDetailsDescriptionPresenter;
import androidx.leanback.widget.Action;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.PlaybackControlsRow;
import androidx.leanback.widget.PlaybackControlsRow.ClosedCaptioningAction;
import androidx.leanback.widget.PlaybackControlsRow.MultiAction;
import androidx.leanback.widget.PlaybackControlsRowPresenter;
import androidx.leanback.widget.RowPresenter;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.util.TimeShiftUtils;
import java.util.ArrayList;

/**
 * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and send
 * command to the media controller. It also helps to update playback states displayed in the
 * fragment according to information the media session provides.
 */
class DvrPlaybackControlHelper extends PlaybackControlGlue {
    private static final String TAG = "DvrPlaybackControlHelpr";
    private static final boolean DEBUG = false;

    private static final int AUDIO_ACTION_ID = 1001;
    private static final long INVALID_TIME = -1;

    private int mPlaybackState = PlaybackState.STATE_NONE;
    private int mPlaybackSpeedLevel;
    private int mPlaybackSpeedId;
    private long mProgramStartTimeMs = INVALID_TIME;
    private boolean mEnableBuffering = false;
    private boolean mReadyToControl;

    private final DvrPlaybackOverlayFragment mFragment;
    private final MediaController mMediaController;
    private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback();
    private final TransportControls mTransportControls;
    private final int mExtraPaddingTopForNoDescription;
    private final MultiAction mClosedCaptioningAction;
    private final MultiAction mMultiAudioAction;
    private ArrayObjectAdapter mSecondaryActionsAdapter;
    private PlaybackControlsRow mPlaybackControlsRow;
    @Nullable private View mPlayPauseButton;

    DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) {
        super(activity, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]);
        mFragment = overlayFragment;
        mMediaController = activity.getMediaController();
        mMediaController.registerCallback(mMediaControllerCallback);
        mTransportControls = mMediaController.getTransportControls();
        mExtraPaddingTopForNoDescription =
                activity.getResources()
                        .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top);
        mClosedCaptioningAction = new ClosedCaptioningAction(activity);
        mMultiAudioAction = new MultiAudioAction(activity);
        mProgramStartTimeMs = overlayFragment.getProgramStartTimeMs();
        if (mProgramStartTimeMs != INVALID_TIME) {
            mEnableBuffering = true;
        }
        createControlsRowPresenter();
    }

    void createControlsRow() {
        mPlaybackControlsRow = new PlaybackControlsRow(this);
        setControlsRow(mPlaybackControlsRow);
        mSecondaryActionsAdapter =
                (ArrayObjectAdapter) mPlaybackControlsRow.getSecondaryActionsAdapter();
    }

    private void createControlsRowPresenter() {
        AbstractDetailsDescriptionPresenter detailsPresenter =
                new AbstractDetailsDescriptionPresenter() {
                    @Override
                    protected void onBindDescription(
                            AbstractDetailsDescriptionPresenter.ViewHolder viewHolder,
                            Object object) {
                        PlaybackControlGlue glue = (PlaybackControlGlue) object;
                        if (glue.hasValidMedia()) {
                            viewHolder.getTitle().setText(glue.getMediaTitle());
                            viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
                        } else {
                            viewHolder.getTitle().setText("");
                            viewHolder.getSubtitle().setText("");
                        }
                        if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) {
                            viewHolder.view.setPadding(
                                    viewHolder.view.getPaddingLeft(),
                                    mExtraPaddingTopForNoDescription,
                                    viewHolder.view.getPaddingRight(),
                                    viewHolder.view.getPaddingBottom());
                        }
                    }
                };
        PlaybackControlsRowPresenter presenter =
                new PlaybackControlsRowPresenter(detailsPresenter) {
                    @Override
                    protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
                        super.onBindRowViewHolder(vh, item);
                        vh.setOnKeyListener(DvrPlaybackControlHelper.this);
                        ViewGroup controlBar = (ViewGroup) vh.view.findViewById(R.id.control_bar);
                        mPlayPauseButton = controlBar.getChildAt(1);
                    }

                    @Override
                    protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
                        super.onUnbindRowViewHolder(vh);
                        vh.setOnKeyListener(null);
                    }
                };
        presenter.setProgressColor(
                getContext().getResources().getColor(R.color.play_controls_progress_bar_watched));
        presenter.setBackgroundColor(
                getContext()
                        .getResources()
                        .getColor(R.color.play_controls_body_background_enabled));
        setControlsRowPresenter(presenter);
    }

    @Override
    public void onActionClicked(Action action) {
        if (mReadyToControl) {
            int trackType;
            if (action.getId() == mClosedCaptioningAction.getId()) {
                trackType = TvTrackInfo.TYPE_SUBTITLE;
            } else if (action.getId() == AUDIO_ACTION_ID) {
                trackType = TvTrackInfo.TYPE_AUDIO;
            } else {
                super.onActionClicked(action);
                return;
            }
            ArrayList<TvTrackInfo> trackInfos = mFragment.getTracks(trackType);
            if (!trackInfos.isEmpty()) {
                showSideFragment(trackInfos, mFragment.getSelectedTrackId(trackType));
            }
        }
    }

    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        return mReadyToControl && super.onKey(v, keyCode, event);
    }

    @Override
    public boolean hasValidMedia() {
        PlaybackState playbackState = mMediaController.getPlaybackState();
        return playbackState != null;
    }

    @Override
    public boolean isMediaPlaying() {
        PlaybackState playbackState = mMediaController.getPlaybackState();
        if (playbackState == null) {
            return false;
        }
        int state = playbackState.getState();
        return state != PlaybackState.STATE_NONE
                && state != PlaybackState.STATE_CONNECTING
                && state != PlaybackState.STATE_PAUSED;
    }

    /** Returns the ID of the media under playback. */
    public String getMediaId() {
        MediaMetadata mediaMetadata = mMediaController.getMetadata();
        return mediaMetadata == null
                ? null
                : mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
    }

    @Override
    public CharSequence getMediaTitle() {
        MediaMetadata mediaMetadata = mMediaController.getMetadata();
        return mediaMetadata == null
                ? ""
                : mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
    }

    @Override
    public CharSequence getMediaSubtitle() {
        MediaMetadata mediaMetadata = mMediaController.getMetadata();
        return mediaMetadata == null
                ? ""
                : mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE);
    }

    @Override
    public int getMediaDuration() {
        MediaMetadata mediaMetadata = mMediaController.getMetadata();
        return mediaMetadata == null
                ? 0
                : (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
    }

    @Override
    public Drawable getMediaArt() {
        // Do not show the poster art on control row.
        return null;
    }

    @Override
    public long getSupportedActions() {
        return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND;
    }

    @Override
    public int getCurrentSpeedId() {
        return mPlaybackSpeedId;
    }

    @Override
    public int getCurrentPosition() {
        PlaybackState playbackState = mMediaController.getPlaybackState();
        if (playbackState == null) {
            return 0;
        }
        return (int) playbackState.getPosition();
    }

    /** Unregister media controller's callback. */
    void unregisterCallback() {
        mMediaController.unregisterCallback(mMediaControllerCallback);
    }

    /**
     * Update the secondary controls row.
     *
     * @param hasClosedCaption {@code true} to show the closed caption selection button, {@code
     *     false} to hide it.
     * @param hasMultiAudio {@code true} to show the audio track selection button, {@code false} to
     *     hide it.
     */
    void updateSecondaryRow(boolean hasClosedCaption, boolean hasMultiAudio) {
        if (hasClosedCaption) {
            if (mSecondaryActionsAdapter.indexOf(mClosedCaptioningAction) < 0) {
                mSecondaryActionsAdapter.add(0, mClosedCaptioningAction);
            }
        } else {
            mSecondaryActionsAdapter.remove(mClosedCaptioningAction);
        }
        if (hasMultiAudio) {
            if (mSecondaryActionsAdapter.indexOf(mMultiAudioAction) < 0) {
                mSecondaryActionsAdapter.add(mMultiAudioAction);
            }
        } else {
            mSecondaryActionsAdapter.remove(mMultiAudioAction);
        }
        getHost().notifyPlaybackRowChanged();
    }

    /** Update the focus to play pause button. */
    public void onPlaybackResume() {
        if (mPlayPauseButton != null) {
            mPlayPauseButton.requestFocus();
        }
    }

    @Nullable
    Boolean hasSecondaryRow() {
        if (mSecondaryActionsAdapter == null) {
            return null;
        }
        return mSecondaryActionsAdapter.size() != 0;
    }

    @Override
    public void play(int speedId) {
        if (getCurrentSpeedId() == speedId) {
            return;
        }
        if (speedId == PLAYBACK_SPEED_NORMAL) {
            mTransportControls.play();
        } else if (speedId <= -PLAYBACK_SPEED_FAST_L0) {
            mTransportControls.rewind();
        } else if (speedId >= PLAYBACK_SPEED_FAST_L0) {
            mTransportControls.fastForward();
        }
    }

    @Override
    public void pause() {
        mTransportControls.pause();
    }

    @Override
    public void updateProgress() {
        if (mEnableBuffering) {
            super.updateProgress();
            long bufferedTimeMs = System.currentTimeMillis() - mProgramStartTimeMs;
            mPlaybackControlsRow.setBufferedPosition(bufferedTimeMs);
        }
    }

    /** Notifies closed caption being enabled/disabled to update related UI. */
    void onSubtitleTrackStateChanged(boolean enabled) {
        mClosedCaptioningAction.setIndex(
                enabled ? ClosedCaptioningAction.ON : ClosedCaptioningAction.OFF);
    }

    private void onStateChanged(int state, long positionMs, int speedLevel) {
        if (DEBUG) Log.d(TAG, "onStateChanged");
        getControlsRow().setCurrentTime((int) positionMs);
        if (state == mPlaybackState && mPlaybackSpeedLevel == speedLevel) {
            // Only position is changed, no need to update controls row
            return;
        }
        // NOTICE: The below two variables should only be used in this method.
        // The only usage of them is to confirm if the state is changed or not.
        mPlaybackState = state;
        mPlaybackSpeedLevel = speedLevel;
        switch (state) {
            case PlaybackState.STATE_PLAYING:
                mPlaybackSpeedId = PLAYBACK_SPEED_NORMAL;
                setFadingEnabled(true);
                mReadyToControl = true;
                break;
            case PlaybackState.STATE_PAUSED:
                mPlaybackSpeedId = PLAYBACK_SPEED_PAUSED;
                setFadingEnabled(true);
                mReadyToControl = true;
                break;
            case PlaybackState.STATE_FAST_FORWARDING:
                mPlaybackSpeedId = PLAYBACK_SPEED_FAST_L0 + speedLevel;
                setFadingEnabled(false);
                mReadyToControl = true;
                break;
            case PlaybackState.STATE_REWINDING:
                mPlaybackSpeedId = -PLAYBACK_SPEED_FAST_L0 - speedLevel;
                setFadingEnabled(false);
                mReadyToControl = true;
                break;
            case PlaybackState.STATE_CONNECTING:
                setFadingEnabled(false);
                mReadyToControl = false;
                break;
            case PlaybackState.STATE_NONE:
                mReadyToControl = false;
                break;
            default:
                setFadingEnabled(true);
                break;
        }
        onStateChanged();
    }

    private void showSideFragment(ArrayList<TvTrackInfo> trackInfos, String selectedTrackId) {
        Bundle args = new Bundle();
        args.putParcelableArrayList(DvrPlaybackSideFragment.TRACK_INFOS, trackInfos);
        args.putString(DvrPlaybackSideFragment.SELECTED_TRACK_ID, selectedTrackId);
        DvrPlaybackSideFragment sideFragment = new DvrPlaybackSideFragment();
        sideFragment.setArguments(args);
        mFragment
                .getFragmentManager()
                .beginTransaction()
                .hide(mFragment)
                .replace(R.id.dvr_playback_side_fragment, sideFragment)
                .addToBackStack(null)
                .commit();
    }

    private class MediaControllerCallback extends MediaController.Callback {
        @Override
        public void onPlaybackStateChanged(PlaybackState state) {
            if (DEBUG) Log.d(TAG, "Playback state changed: " + state.getState());
            onStateChanged(state.getState(), state.getPosition(), (int) state.getPlaybackSpeed());
        }

        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            DvrPlaybackControlHelper.this.onMetadataChanged();
        }
    }

    private static class MultiAudioAction extends MultiAction {
        MultiAudioAction(Context context) {
            super(AUDIO_ACTION_ID);
            setDrawables(new Drawable[] {context.getDrawable(R.drawable.ic_tvoption_multi_track)});
        }
    }
}
