// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.media.remote;

import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.AudioManager;

import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.media.remote.RemoteVideoInfo.PlayerState;
import org.chromium.chrome.browser.media.ui.MediaNotificationInfo;
import org.chromium.chrome.browser.media.ui.MediaNotificationListener;
import org.chromium.chrome.browser.media.ui.MediaNotificationManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.common.MediaMetadata;

import javax.annotation.Nullable;

/**
 * Notification control controls the cast notification and lock screen, using
 * {@link MediaNotificationManager}
 */
public class CastNotificationControl implements MediaRouteController.UiListener,
        MediaNotificationListener, AudioManager.OnAudioFocusChangeListener {

    private static CastNotificationControl sInstance;

    private Bitmap mPosterBitmap;
    protected MediaRouteController mMediaRouteController = null;
    private MediaNotificationInfo.Builder mNotificationBuilder;
    private Context mContext;
    private PlayerState mState;
    private String mTitle = "";
    private AudioManager mAudioManager;

    private boolean mIsShowing = false;

    private static final Object LOCK = new Object();


    private CastNotificationControl(Context context) {
        mContext = context;
        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    }
    /**
     * Get the unique NotificationControl, creating it if necessary.
     * @param context The context of the activity
     * @param mediaRouteController The current mediaRouteController, if any.
     * @return a {@code LockScreenTransportControl} based on the platform's SDK API or null if the
     *         current platform's SDK API is not supported.
     */
    public static CastNotificationControl getOrCreate(Context context,
            @Nullable MediaRouteController mediaRouteController) {
        synchronized (LOCK) {
            if (sInstance == null) {
                sInstance = new CastNotificationControl(context);
            }
            sInstance.setRouteController(mediaRouteController);
            return sInstance;
        }
    }

    /**
     * @return the poster bitmap previously assigned with {@link #setPosterBitmap(Bitmap)}, or
     * {@code null} if the poster bitmap has not yet been assigned.
     */
    public final Bitmap getPosterBitmap() {
        return mPosterBitmap;
    }

    /**
     * Sets the poster bitmap to display on the TransportControl.
     */
    public final void setPosterBitmap(Bitmap posterBitmap) {
        if (mPosterBitmap == posterBitmap
                || (mPosterBitmap != null && mPosterBitmap.sameAs(posterBitmap))) {
            return;
        }
        mPosterBitmap = posterBitmap;
        if (mNotificationBuilder == null || mMediaRouteController == null) return;
        mNotificationBuilder.setImage(mMediaRouteController.getPoster());
        updateNotification();
    }

    public void hide() {
        mIsShowing = false;
        MediaNotificationManager.hide(Tab.INVALID_TAB_ID, R.id.remote_notification);
        mAudioManager.abandonAudioFocus(this);
        mMediaRouteController.removeUiListener(this);
    }

    public void show(PlayerState initialState) {
        mMediaRouteController.addUiListener(this);
        // TODO(aberent): investigate why this is necessary, and whether we are handling
        // it correctly. Also add code to restore it when Chrome is resumed.
        mAudioManager.requestAudioFocus(this, AudioManager.USE_DEFAULT_STREAM_TYPE,
                AudioManager.AUDIOFOCUS_GAIN);
        Intent contentIntent = new Intent(mContext, ExpandedControllerActivity.class);
        mNotificationBuilder = new MediaNotificationInfo.Builder()
                .setPaused(false)
                .setPrivate(false)
                .setIcon(R.drawable.ic_notification_media_route)
                .setContentIntent(contentIntent)
                .setImage(mMediaRouteController.getPoster())
                .setId(R.id.remote_notification)
                .setListener(this);
        mState = initialState;
        updateNotification();
        mIsShowing = true;
    }

    public void setRouteController(MediaRouteController controller) {
        if (mMediaRouteController != null) mMediaRouteController.removeUiListener(this);
        mMediaRouteController = controller;
        if (controller != null) controller.addUiListener(this);
    }

    private void updateNotification() {
        // Nothing shown yet, nothing to update.
        if (mNotificationBuilder == null) return;

        mNotificationBuilder.setMetadata(new MediaMetadata(mTitle, "", ""));
        if (mState == PlayerState.PAUSED || mState == PlayerState.PLAYING) {
            mNotificationBuilder.setPaused(mState != PlayerState.PLAYING);
            mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP
                    | MediaNotificationInfo.ACTION_PLAY_PAUSE);
            MediaNotificationManager.show(mContext, mNotificationBuilder.build());
        } else if (mState == PlayerState.LOADING) {
            mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP);
            MediaNotificationManager.show(mContext, mNotificationBuilder.build());
        } else {
            hide();
        }
    }

    // TODO(aberent) at the moment this is only called from a test, but it should be called if the
    // poster changes.
    public void onPosterBitmapChanged() {
        if (mNotificationBuilder == null || mMediaRouteController == null) return;
        mNotificationBuilder.setImage(mMediaRouteController.getPoster());
        updateNotification();
    }

    // MediaRouteController.UiListener implementation.
    @Override
    public void onPlaybackStateChanged(PlayerState newState) {
        if (mState == newState) return;

        mState = newState;
        updateNotification();
    }

    @Override
    public void onRouteSelected(String name, MediaRouteController mediaRouteController) {
    }

    @Override
    public void onRouteUnselected(MediaRouteController mediaRouteController) {
        hide();
    }

    @Override
    public void onPrepared(MediaRouteController mediaRouteController) {
    }

    @Override
    public void onError(int errorType, String message) {
    }

    @Override
    public void onDurationUpdated(long durationMillis) {
    }

    @Override
    public void onPositionChanged(long positionMillis) {
    }

    @Override
    public void onTitleChanged(String title) {
        if (title == null || title.equals(mTitle)) return;

        mTitle = title;
        updateNotification();
    }

    // MediaNotificationListener methods
    @Override
    public void onPlay(int actionSource) {
        mMediaRouteController.resume();
    }

    @Override
    public void onPause(int actionSource) {
        mMediaRouteController.pause();
    }

    @Override
    public void onStop(int actionSource) {
        mMediaRouteController.release();
    }

    // AudioManager.OnAudioFocusChangeListener methods
    @Override
    public void onAudioFocusChange(int focusChange) {
        // Do nothing. The remote player should keep playing.
    }

    @VisibleForTesting
    static CastNotificationControl getForTests() {
        return sInstance;
    }

    @VisibleForTesting
    boolean isShowingForTests() {
        return mIsShowing;
    }
}
