/*
 * Copyright (C) 2020 Stefan Schüller <sschueller@techdroid.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package net.schueller.peertube.service;

import static android.media.session.PlaybackState.ACTION_PAUSE;
import static android.media.session.PlaybackState.ACTION_PLAY;
import static com.google.android.exoplayer2.ui.PlayerNotificationManager.ACTION_STOP;
import static net.schueller.peertube.activity.VideoListActivity.EXTRA_VIDEOID;
import static net.schueller.peertube.network.UnsafeOkHttpClient.getUnsafeOkHttpClientBuilder;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.util.Log;
import android.webkit.URLUtil;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioAttributes;
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator;
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.ui.PlayerNotificationManager;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.util.Util;
import net.schueller.peertube.R;
import net.schueller.peertube.activity.VideoPlayActivity;
import net.schueller.peertube.helper.APIUrlHelper;
import net.schueller.peertube.helper.MetaDataHelper;
import net.schueller.peertube.model.Video;
import okhttp3.OkHttpClient;

public class VideoPlayerService extends Service {

    private static final String TAG = "VideoPlayerService";

    private static final String MEDIA_SESSION_TAG = "peertube_player";

    private final IBinder mBinder = new LocalBinder();

    private static final String PLAYBACK_CHANNEL_ID = "playback_channel";

    private static final Integer PLAYBACK_NOTIFICATION_ID = 1;

    public SimpleExoPlayer player;

    private Video currentVideo;

    private String currentStreamUrl;

    private boolean currentStreamUrlIsHLS;

    private PlayerNotificationManager playerNotificationManager;

    private IntentFilter becomeNoisyIntentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);

    private BecomingNoisyReceiver myNoisyAudioStreamReceiver = new BecomingNoisyReceiver();

    @Override
    public void onCreate() {
        Log.v(TAG, "onCreate...");

        super.onCreate();

        player = new SimpleExoPlayer.Builder(getApplicationContext())
                .setTrackSelector(new DefaultTrackSelector(getApplicationContext()))
                .build();

        // Stop player if audio device changes, e.g. headphones unplugged
        player.addListener(new Player.EventListener() {
            @Override
            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {

                if (playbackState == ACTION_PAUSE) { // this means that pause is available, hence the audio is playing
                    Log.v(TAG, "ACTION_PLAY: " + playbackState);
                    registerReceiver(myNoisyAudioStreamReceiver, becomeNoisyIntentFilter);
                }

                if (playbackState
                        == ACTION_PLAY) { // this means that play is available, hence the audio is paused or stopped
                    Log.v(TAG, "ACTION_PAUSE: " + playbackState);
                    safeUnregisterReceiver();
                }
            }
        });

    }

    public class LocalBinder extends Binder {

        public VideoPlayerService getService() {
            // Return this instance of VideoPlayerService so clients can call public methods
            return VideoPlayerService.this;
        }
    }


    @Override
    public void onDestroy() {

        Log.v(TAG, "onDestroy...");
        if (playerNotificationManager != null) {
            playerNotificationManager.setPlayer(null);
        }
        //Was seeing an error when exiting the program about not unregistering the receiver.
        safeUnregisterReceiver();

        if (player != null) {
            player.release();
            player = null;
        }
        super.onDestroy();
    }

    private void safeUnregisterReceiver()
    {
        try {
            unregisterReceiver(myNoisyAudioStreamReceiver);
        } catch (Exception e) {
            Log.e("VideoPlayerService", "attempted to unregister a nonregistered service");
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Context context = this;
        Log.v(TAG, "onStartCommand...");

        if (!URLUtil.isValidUrl(currentStreamUrl)) {
            Toast.makeText(context, "Invalid URL provided. Unable to play video.", Toast.LENGTH_SHORT).show();
            return START_NOT_STICKY;
        } else {
            playVideo();
            return START_STICKY;
        }
    }


    public void setCurrentVideo(Video video) {
        Log.v(TAG, "setCurrentVideo...");
        currentVideo = video;
    }

    public void setCurrentStreamUrl(String streamUrl, boolean isHLS) {
        Log.v(TAG, "setCurrentStreamUrl..." + streamUrl);
        currentStreamUrlIsHLS = isHLS;
        currentStreamUrl = streamUrl;
    }

    //Playback speed control
    public void setPlayBackSpeed(float speed) {
        Log.v(TAG, "setPlayBackSpeed...");
        player.setPlaybackParameters(new PlaybackParameters(speed));
    }

    /**
     * Returns the current playback speed of the player.
     *
     * @return the current playback speed of the player.
     */
    public float getPlayBackSpeed() {
        return player.getPlaybackParameters().speed;
    }

    public void playVideo() {
        Context context = this;

        // We need a valid URL

        Log.v(TAG, "playVideo...");

        // Produces DataSource instances through which media data is loaded.
//        DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(getApplicationContext(),
//                Util.getUserAgent(getApplicationContext(), "PeerTube"), null);

        OkHttpClient.Builder okhttpClientBuilder;

        if (!APIUrlHelper.useInsecureConnection(this)) {
            okhttpClientBuilder = new OkHttpClient.Builder();
        } else {
            okhttpClientBuilder = getUnsafeOkHttpClientBuilder();
        }

        // Create a data source factory.
        DataSource.Factory dataSourceFactory =  new OkHttpDataSourceFactory(okhttpClientBuilder.build(), Util.getUserAgent(getApplicationContext(), "PeerTube"));

        // Create a progressive media source pointing to a stream uri.
        MediaSource mediaSource;
        if (currentStreamUrlIsHLS) {
            mediaSource = new HlsMediaSource.Factory(dataSourceFactory)
                    .createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)));
        } else {
            mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory)
                    .createMediaSource(MediaItem.fromUri(Uri.parse(currentStreamUrl)));
        }

        // Set the media source to be played.
        player.setMediaSource(mediaSource);

        // Prepare the player.
        player.prepare();

        // Auto play
        player.setPlayWhenReady(true);

        //set playback speed to global default
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        float speed = Float.parseFloat(sharedPref.getString(getString(R.string.pref_video_speed_key), "1.0"));

        this.setPlayBackSpeed(speed);

        playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
                context, PLAYBACK_CHANNEL_ID, R.string.playback_channel_name,
                PLAYBACK_NOTIFICATION_ID,
                new PlayerNotificationManager.MediaDescriptionAdapter() {

                    @Override
                    public String getCurrentContentTitle(Player player) {
                        return currentVideo.getName();
                    }

                    @Nullable
                    @Override
                    public PendingIntent createCurrentContentIntent(Player player) {
                        Intent intent = new Intent(context, VideoPlayActivity.class);
                        intent.putExtra(EXTRA_VIDEOID, currentVideo.getUuid());
                        return PendingIntent.getActivity(context, 0, intent,
                                PendingIntent.FLAG_UPDATE_CURRENT);
                    }

                    @Override
                    public String getCurrentContentText(Player player) {
                        return MetaDataHelper.getMetaString(
                                currentVideo.getCreatedAt(),
                                currentVideo.getViews(),
                                getBaseContext()
                        );
                    }

                    @Nullable
                    @Override
                    public Bitmap getCurrentLargeIcon(Player player,
                            PlayerNotificationManager.BitmapCallback callback) {
                        return null;
                    }
                }
        );

        playerNotificationManager.setSmallIcon(R.drawable.ic_logo_bw);

        // don't show skip buttons in notification
        playerNotificationManager.setUseNavigationActions(false);
        playerNotificationManager.setUseStopAction(true);

        playerNotificationManager.setNotificationListener(
                new PlayerNotificationManager.NotificationListener() {
                    @Override
                    public void onNotificationStarted(int notificationId, Notification notification) {
                        startForeground(notificationId, notification);
                    }

                    @Override
                    public void onNotificationCancelled(int notificationId) {
                        Log.v(TAG, "onNotificationCancelled...");
                        stopForeground(true);
                        Intent killFloat = new Intent(ACTION_STOP);
                        sendBroadcast(killFloat);
                   /*
                        Intent killFloat = new Intent(BROADCAST_ACTION);
                        Intent killFloatingWindow = new Intent(getApplicationContext(),VideoPlayActivity.class);
                        killFloatingWindow.putExtra("killFloat",true);

                        startActivity(killFloatingWindow);
                        // TODO: only kill the notification if we no longer have a bound activity
                        stopForeground(true);
                    */
                    }
                }
        );

        playerNotificationManager.setPlayer(player);

        // external Media control, Android Wear / Google Home etc.
        MediaSessionCompat mediaSession = new MediaSessionCompat(context, MEDIA_SESSION_TAG);
        mediaSession.setActive(true);
        playerNotificationManager.setMediaSessionToken(mediaSession.getSessionToken());
        MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mediaSession);
        mediaSessionConnector.setQueueNavigator(new TimelineQueueNavigator(mediaSession) {
            @Override
            public MediaDescriptionCompat getMediaDescription(Player player, int windowIndex) {
                return Video.getMediaDescription(context, currentVideo);
            }
        });
        mediaSessionConnector.setPlayer(player);

        // Audio Focus
        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setUsage(C.USAGE_MEDIA)
                .setContentType(C.CONTENT_TYPE_MOVIE)
                .build();
        player.setAudioAttributes(audioAttributes, true);

    }

    // pause playback on audio output change
    private class BecomingNoisyReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
                player.setPlayWhenReady(false);
            }
        }
    }


}
