/*
 * Copyright (C) 2020 Google Inc.
 *
 * 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.car.carlauncher.homescreen.audio;

import static android.car.media.CarMediaManager.MEDIA_SOURCE_MODE_PLAYBACK;

import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.util.Size;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.Observer;

import com.android.car.apps.common.imaging.ImageBinder;
import com.android.car.carlauncher.Flags;
import com.android.car.carlauncher.homescreen.HomeCardInterface;
import com.android.car.carlauncher.homescreen.ui.CardContent;
import com.android.car.carlauncher.homescreen.ui.CardHeader;
import com.android.car.carlauncher.homescreen.ui.DescriptiveTextWithControlsView;
import com.android.car.carlauncher.homescreen.ui.SeekBarViewModel;
import com.android.car.media.common.MediaItemMetadata;
import com.android.car.media.common.R;
import com.android.car.media.common.playback.PlaybackProgress;
import com.android.car.media.common.playback.PlaybackViewModel;
import com.android.car.media.common.source.MediaModels;
import com.android.car.media.common.source.MediaSource;
import com.android.car.media.common.source.MediaSourceColors;
import com.android.car.media.common.source.MediaSourceViewModel;
import com.android.internal.annotations.VisibleForTesting;

/**
 * ViewModel for media. Uses both a {@link MediaSourceViewModel} and a {@link PlaybackViewModel}
 * for data on the audio source and audio metadata (such as song title), respectively.
 */
public class MediaViewModel extends AndroidViewModel implements AudioModel {

    private static final String TAG = "MediaViewModel";

    private static final String EMPTY_TIME = "";

    private HomeCardInterface.Presenter mAudioPresenter;
    // MediaSourceViewModel is for the current or last played media app
    private MediaSourceViewModel mSourceViewModel;
    // PlaybackViewModel has the media's metadata
    private PlaybackViewModel mPlaybackViewModel;
    private PlaybackViewModel.PlaybackController mPlaybackController;
    private Context mContext;

    private CardHeader mCardHeader;
    private CharSequence mAppName;
    private Drawable mAppIcon;
    private CharSequence mSongTitle;
    private CharSequence mArtistName;
    private CharSequence mTimes;
    private CharSequence mTimesSeparator;
    private boolean mIsSeekEnabled;
    private boolean mUseMediaSourceColor;
    private int mDefaultSeekBarColor;
    private int mSeekBarColor;

    /**
     * Use int value for progress and seekbar max value from config since {@link
     * android.widget.SeekBar} only works with int. Handling the long & int conversion in {@link
     * MediaViewModel}
     */
    private int mProgress;
    private int mSeekBarMax;
    private long mRealMaxProgress;

    private ImageBinder<MediaItemMetadata.ArtworkRef> mAlbumArtBinder;
    private Drawable mAlbumImageBitmap;
    private Drawable mMediaBackground;
    private OnModelUpdateListener mOnModelUpdateListener;
    private OnProgressUpdateListener mOnProgressUpdateListener;
    private Observer<Object> mMediaSourceColorObserver = x -> updateMediaSourceColor();
    private Observer<Object> mMetadataObserver = x -> updateModelMetadata();
    private Observer<Object> mPlaybackControllerObserver = controller -> updatePlaybackController();
    private PlaybackCallback mPlaybackCallback = new PlaybackCallback() {
        @Override
        public void seekTo(int pos) {
            if (mPlaybackController != null) {
                double fraction = (double) pos / (double) mSeekBarMax;
                Double realPos = mRealMaxProgress * fraction;
                mPlaybackController.seekTo(realPos.longValue());
            }
        }
    };
    private Observer<Object> mMediaSourceObserver = x -> updateModel();
    private Observer<Object> mProgressObserver = x -> updateProgress();

    private Observer<PlaybackViewModel.PlaybackStateWrapper> mPlaybackStateWrapperObserver =
            playbackStateWrapper -> {
                if (playbackStateWrapper != null
                        && mIsSeekEnabled != playbackStateWrapper.isSeekToEnabled()) {
                    mIsSeekEnabled = playbackStateWrapper.isSeekToEnabled();
                    if (mOnProgressUpdateListener != null) {
                        mOnProgressUpdateListener.onProgressUpdate(/* model = */
                                this, /* updateProgress = */
                                false);
                    }
                }
            };

    public MediaViewModel(Application application) {
        super(application);
    }

    @VisibleForTesting
    MediaViewModel(Application application, MediaSourceViewModel sourceViewModel,
            PlaybackViewModel playbackViewModel) {
        super(application);
        mSourceViewModel = sourceViewModel;
        mPlaybackViewModel = playbackViewModel;

    }

    @Override
    public void onCreate(@NonNull Context context) {
        // Initialize media data with media session sources or mbt sources
        if (Flags.mediaSessionCard()) {
            MediaModels mediaModels = new MediaModels(context);
            if (mSourceViewModel == null) {
                mSourceViewModel = mediaModels.getMediaSourceViewModel();
            }
            if (mPlaybackViewModel == null) {
                mPlaybackViewModel = mediaModels.getPlaybackViewModel();
            }
        } else {
            if (mSourceViewModel == null) {
                mSourceViewModel = MediaSourceViewModel.get(getApplication(),
                        MEDIA_SOURCE_MODE_PLAYBACK);
            }
            if (mPlaybackViewModel == null) {
                mPlaybackViewModel = PlaybackViewModel.get(getApplication(),
                        MEDIA_SOURCE_MODE_PLAYBACK);
            }
        }

        mContext = context;
        Resources resources = mContext.getResources();
        int max = resources.getInteger(R.integer.media_items_bitmap_max_size_px);
        mMediaBackground = resources
                .getDrawable(R.drawable.control_bar_image_background);
        Size maxArtSize = new Size(max, max);
        mAlbumArtBinder = new ImageBinder<>(ImageBinder.PlaceholderType.FOREGROUND, maxArtSize,
                drawable -> {
                    mAlbumImageBitmap = drawable;
                    mOnModelUpdateListener.onModelUpdate(/* model = */ this);
                });
        mSourceViewModel.getPrimaryMediaSource().observeForever(mMediaSourceObserver);
        mPlaybackViewModel.getMetadata().observeForever(mMetadataObserver);
        mPlaybackViewModel.getMediaSourceColors().observeForever(mMediaSourceColorObserver);
        mPlaybackViewModel.getProgress().observeForever(mProgressObserver);
        mPlaybackViewModel.getPlaybackController().observeForever(mPlaybackControllerObserver);
        mPlaybackViewModel.getPlaybackStateWrapper().observeForever(mPlaybackStateWrapperObserver);

        mSeekBarColor = mDefaultSeekBarColor = resources.getColor(
                com.android.car.carlauncher.R.color.seek_bar_color, null);
        mSeekBarMax = resources.getInteger(
                com.android.car.carlauncher.R.integer.optional_seekbar_max);
        mUseMediaSourceColor = resources.getBoolean(R.bool.use_media_source_color_for_seek_bar);
        mTimesSeparator = resources.getString(com.android.car.carlauncher.R.string.times_separator);
        mOnModelUpdateListener.onModelUpdate(/* model = */ this);

        updateModel(); // Make sure the name of the media source properly reflects the locale.
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if (mSourceViewModel != null) {
            mSourceViewModel.getPrimaryMediaSource().removeObserver(mMediaSourceObserver);
        }
        if (mPlaybackViewModel != null) {
            mPlaybackViewModel.getMetadata().removeObserver(mMetadataObserver);
            mPlaybackViewModel.getPlaybackStateWrapper().removeObserver(
                    mPlaybackStateWrapperObserver);
        }
    }

    @Override
    public Intent getIntent() {
        MediaSource mediaSource = getMediaSourceViewModel().getPrimaryMediaSource().getValue();

        return mediaSource != null ? mediaSource.getIntent() : null;
    }

    @Override
    public void setOnModelUpdateListener(OnModelUpdateListener onModelUpdateListener) {
        mOnModelUpdateListener = onModelUpdateListener;
    }

    @Override
    public void setOnProgressUpdateListener(OnProgressUpdateListener onProgressUpdateListener) {
        mOnProgressUpdateListener = onProgressUpdateListener;
    }

    @Override
    public CardHeader getCardHeader() {
        return mCardHeader;
    }

    @Override
    public CardContent getCardContent() {
        return new DescriptiveTextWithControlsView(
                new CardContent.CardBackgroundImage(mAlbumImageBitmap, mMediaBackground),
                mSongTitle,
                mArtistName,
                new SeekBarViewModel(
                        mTimes,
                        mIsSeekEnabled,
                        mSeekBarColor,
                        mProgress,
                        mPlaybackCallback)
        );

    }

    /**
     * Allows the {@link HomeAudioCardPresenter} to access the model to
     * initialize the {@link com.android.car.media.common.PlaybackControlsActionBar}
     */
    public PlaybackViewModel getPlaybackViewModel() {
        return mPlaybackViewModel;
    }

    protected MediaSourceViewModel getMediaSourceViewModel() {
        return mSourceViewModel;
    }

    /**
     * Callback for the observer of the MediaSourceViewModel
     */
    private void updateModel() {
        MediaSource mediaSource = mSourceViewModel.getPrimaryMediaSource().getValue();
        if (mediaSourceChanged()) {
            if (mediaSource != null) {
                if (Log.isLoggable(TAG, Log.INFO)) {
                    Log.i(TAG, "Setting Media view to source "
                            + mediaSource.getDisplayName(mContext));
                }
                mAppName = mediaSource.getDisplayName(mContext);
                mAppIcon = mediaSource.getIcon();
                mCardHeader = new CardHeader(mAppName, mAppIcon);
                updateMetadata();
                updateProgress();
                updateMediaSourceColor();

                mOnModelUpdateListener.onModelUpdate(/* model = */ this);
            } else {
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Not resetting media widget for apps "
                            + "that do not support media browse");
                }
            }
        }
    }

    /**
     * Callback for the observer of the PlaybackViewModel
     */
    private void updateModelMetadata() {
        if (metadataChanged()) {
            updateMetadata();
            if (mCardHeader != null) {
                mOnModelUpdateListener.onModelUpdate(/* model = */ this);
            }
        }
    }

    private void updateMediaSourceColor() {
        MediaSourceColors mediaSourceColors = mPlaybackViewModel.getMediaSourceColors().getValue();
        mSeekBarColor = (mediaSourceColors == null || !mUseMediaSourceColor)
                ? mDefaultSeekBarColor
                : mediaSourceColors.getAccentColor(mDefaultSeekBarColor);
        mOnProgressUpdateListener.onProgressUpdate(/* model = */ this, /* updateProgress = */
                false);
    }

    private void updateProgress() {
        PlaybackProgress playbackProgress = mPlaybackViewModel.getProgress().getValue();
        if (playbackProgress == null) {
            return;
        }
        mTimes = playbackProgress.hasTime() ? new StringBuilder(
                playbackProgress.getCurrentTimeText()).append(
                mTimesSeparator).append(playbackProgress.getMaxTimeText()).toString() : EMPTY_TIME;
        mRealMaxProgress = playbackProgress.getMaxProgress();
        int progress = playbackProgress.getProgressFraction() < 0 ? 0
                : (int) (mSeekBarMax * playbackProgress.getProgressFraction());
        if (mProgress != progress) {
            mProgress = progress;
            mOnProgressUpdateListener.onProgressUpdate(/* model = */ this, /* updateProgress = */
                    true);
        }
    }

    private void updateMetadata() {
        MediaItemMetadata metadata = mPlaybackViewModel.getMetadata().getValue();
        if (metadata == null) {
            clearMetadata();
        } else {
            mSongTitle = metadata.getTitle();
            mArtistName = metadata.getSubtitle();
            mAlbumArtBinder.setImage(mContext, metadata.getArtworkKey());
        }
    }

    private void updatePlaybackController() {
        mPlaybackController = mPlaybackViewModel.getPlaybackController().getValue();
    }

    private void clearMetadata() {
        mSongTitle = mContext.getString(R.string.default_media_song_title);
        mArtistName = null;
        mAlbumArtBinder.setImage(mContext, /* newArtRef = */ null);
    }

    /**
     * Helper method to check for a change in the media's metadata
     */
    private boolean metadataChanged() {
        MediaItemMetadata metadata = mPlaybackViewModel.getMetadata().getValue();
        if (metadata == null && (mSongTitle != null || mArtistName != null)) {
            return true;
        }
        if (metadata != null && (mSongTitle != metadata.getTitle()
                || mArtistName != metadata.getSubtitle())) {
            return true;
        }
        return false;
    }

    /**
     * Helper method to check for a change in the media source
     */
    private boolean mediaSourceChanged() {
        MediaSource mediaSource = mSourceViewModel.getPrimaryMediaSource().getValue();
        if (mediaSource == null && (mAppName != null || mAppIcon != null)) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "new media source is null...");
            }
            return true;
        }
        if (mediaSource != null && (mAppName != mediaSource.getDisplayName(mContext)
                || mAppIcon != mediaSource.getIcon())) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "new media source is " + mediaSource.toString());
            }
            return true;
        }
        return false;
    }

    /**
     * Callback for {@link com.android.car.carlauncher.homescreen.HomeCardFragment} pass the seekbar
     * info back.
     */
    public interface PlaybackCallback {
        /**
         * Moves to a new location in the media stream
         */
        void seekTo(int pos);
    }
}
