/*
 * *
 *  * This file is part of QuickLyric
 *  * Created by geecko
 *  *
 *  * QuickLyric is free software: you can redistribute it and/or modify
 *  * it under the terms of the GNU General Public License as published by
 *  * the Free Software Foundation, either version 3 of the License, or
 *  * (at your option) any later version.
 *  *
 *  * QuickLyric 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 General Public License for more details.
 *  * You should have received a copy of the GNU General Public License
 *  * along with QuickLyric.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package com.geecko.QuickLyric.utils;

import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.RequiresApi;
import android.support.v4.util.ArraySet;
import android.util.Log;

import com.geecko.QuickLyric.broadcastReceiver.MusicBroadcastReceiver;
import com.geecko.QuickLyric.services.NotificationListenerService;
import com.geecko.QuickLyric.services.ScrobblerService;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

public class MediaControllerCallback {

    private static MediaSessionManager.OnActiveSessionsChangedListener sessionListener;
    private final MetadataUpdateListener metadataListener;
    private MediaController controller;
    private static WeakReference<MediaController> sController = new WeakReference<>(null);
    private MediaController.Callback controllerCallback;
    private Handler handler = new Handler();
    private Bitmap lastBitmap;

    public MediaControllerCallback(MetadataUpdateListener metadataListener) {
        this.metadataListener = metadataListener;
    }


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public static void registerFallbackControllerCallback(Context context, MediaControllerCallback controllerCallback) {
        MediaSessionManager mediaSessionManager = ((MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE));
        ComponentName className = new ComponentName(context.getApplicationContext(), NotificationListenerService.class);
        if (sessionListener != null)
            mediaSessionManager.removeOnActiveSessionsChangedListener(sessionListener);
        sessionListener = list -> controllerCallback.registerActiveSessionCallback(context, list);
        mediaSessionManager.addOnActiveSessionsChangedListener(sessionListener, className);
        controllerCallback.registerActiveSessionCallback(context, mediaSessionManager.getActiveSessions(className));
    }

    public void registerActiveSessionCallback(Context context, List<MediaController> controllers) {
        if (controllers.size() > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            controller = controllers.get(0);
            sController = new WeakReference<>(controller);
            if (controllerCallback != null) {
                for (MediaController ctlr : controllers)
                    ctlr.unregisterCallback(controllerCallback);
            } else {
                controllerCallback = new MediaController.Callback() {
                    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                    @Override
                    public void onPlaybackStateChanged(PlaybackState state) {
                        super.onPlaybackStateChanged(state);
                        if (state == null)
                            return;
                        if (isInvalidPackage(controller))
                            return;
                        boolean isPlaying = state.getState() == PlaybackState.STATE_PLAYING;
                        if (!isPlaying) {
                            NotificationManager notificationManager =
                                    ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE));
                            notificationManager.cancel(0);
                            notificationManager.cancel(8);
                        }
                        savePlayerName(controller.getPackageName(), context);
                        if (controller != controller)
                            return; //ignore inactive sessions
                        broadcastControllerState(context, controller, isPlaying);
                    }

                    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                    @Override
                    public void onMetadataChanged(MediaMetadata metadata) {
                        super.onMetadataChanged(metadata);
                        if (controller != controller)
                            return;
                        if (metadata == null)
                            return;
                        if (isInvalidPackage(controller))
                            return;
                        savePlayerName(controller.getPackageName(), context);
                        broadcastControllerState(context, controller, null);
                    }
                };
            }
            controller.registerCallback(controllerCallback);
            if (isInvalidPackage(controller))
                return;
            broadcastControllerState(context, controller, null);
        }
    }

    public static long getActiveControllerPosition(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && sController.get() != null) {
            PlaybackState state = sController.get().getPlaybackState();
            if (state != null)
                return state.getPosition();
        } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
            long kitkatPosition = NotificationListenerService.getRemotePlayerPosition();
            if (kitkatPosition >= 0)
                return kitkatPosition;
        }
        SharedPreferences preferences = context.getSharedPreferences("current_music", Context.MODE_PRIVATE);
        long startTime = preferences.getLong("startTime", System.currentTimeMillis());
        long distance = System.currentTimeMillis() - startTime;
        long position = preferences.getLong("position", -1L);
        if (preferences.getBoolean("playing", true) && position >= 0L)
            position += distance;
        return position;
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void removeControllerCallback() {
        if (controllerCallback != null && controller != null) {
            controller.unregisterCallback(controllerCallback);
        }
        controllerCallback = null;
    }

    @TargetApi(21)
    public void broadcastControllerState(Context context, MediaController controller, Boolean isPlaying) {
        final MediaController[] controllers = new MediaController[]{controller};
        final Boolean[] playing = new Boolean[]{isPlaying};
        handler.postDelayed(() -> {
            MediaMetadata metadata = controllers[0].getMetadata();
            PlaybackState playbackState = controllers[0].getPlaybackState();
            if (metadata == null)
                return;
            String artist = null;
            try {
                artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
                if (artist == null)
                    artist = metadata.getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST);
            } catch (Exception ignored) {
            }
            String track = null;
            try {
                track = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
            } catch (Exception ignored) {
            }
            Bitmap artwork = null;
            try {
                artwork = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
                if (artwork == null)
                    artwork = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
            } catch (Exception ignored) {
            }

            double duration;
            try {
                duration =(double) metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
            } catch (RuntimeException ignored) {
                duration = 0;
            }
            long position = duration == 0 || playbackState == null ? -1 : playbackState.getPosition();

            if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("pref_filter_20min", true) && duration > 1200000)
                return;
            if (playing[0] == null)
                playing[0] = playbackState != null && playbackState.getState() == PlaybackState.STATE_PLAYING;

            saveArtwork(context, artwork, artist, track);

            String player = controllers[0].getPackageName();

            if ("com.aimp.player".equals(player)) // Aimp is awful
                position = -1;
            broadcast(context, artist, track, playing[0], duration, position, player);
        }, 100);
    }

    public void broadcast(Context context, String artist, String track, boolean playing, int duration, long position, String player) {
        Intent localIntent = new Intent("com.android.music.metachanged");
        localIntent.putExtra("artist", artist);
        localIntent.putExtra("track", track);
        localIntent.putExtra("playing", playing);
        localIntent.putExtra("duration", duration);
        localIntent.putExtra("player", player);
        Log.d("title", track);
        if (position != -1)
            localIntent.putExtra("position", position);

        String notifPref = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).getString("pref_notifications", "0");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            new MusicBroadcastReceiver().onReceive(context, localIntent);
        } else {
            if (!(context instanceof ScrobblerService) || notifPref.equals("0"))
                new MusicBroadcastReceiver().onReceive(context, localIntent);
            else if (metadataListener != null && playing)
                metadataListener.onMetadataUpdated(localIntent.getExtras());
        }
    }

    public void broadcast(Context context, String artist, String track, boolean playing, double duration, long position, String player) {
        Intent localIntent = new Intent("com.android.music.metachanged");
        localIntent.putExtra("artist", artist);
        localIntent.putExtra("track", track);
        localIntent.putExtra("playing", playing);
        localIntent.putExtra("duration", duration);
        localIntent.putExtra("player", player);
        if (position != -1)
            localIntent.putExtra("position", position);

        String notifPref = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).getString("pref_notifications", "0");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            new MusicBroadcastReceiver().onReceive(context, localIntent);
        } else {
            if (!(context instanceof ScrobblerService) || notifPref.equals("0"))
                new MusicBroadcastReceiver().onReceive(context, localIntent);
            else if (metadataListener != null)
                metadataListener.onMetadataUpdated(localIntent.getExtras());
        }
    }

    public void saveArtwork(Context context, Bitmap artwork, String artist, String track) {
        File artworksDir = new File(context.getCacheDir(), "artworks");
        if (artwork != null && (artworksDir.exists() || artworksDir.mkdir())) {
            File artworkFile = new File(artworksDir, artist + track + ".png");
            if (!artworkFile.exists())
                try {
                    //noinspection ResultOfMethodCallIgnored
                    artworkFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            if (artworkFile.length() == 0 || !artwork.sameAs(lastBitmap)) { //prevent many writes of the same Bitmap
                FileOutputStream fos = null;
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                artwork.compress(Bitmap.CompressFormat.PNG, 100, stream);
                try {
                    fos = new FileOutputStream(artworkFile);
                    stream.writeTo(fos);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (fos != null)
                        {
                            fos.flush();
                            fos.getFD().sync();
                            fos.close();
                        }
                        stream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (lastBitmap != null) {
                lastBitmap.recycle();
            }
            lastBitmap = artwork;
        }
    }

    /* Lollipop Stuff */
    private static void savePlayerName(String packageName, Context context) {
        if (packageName != null) {
            Set<String> usedPlayers = getUsedPlayersList(context);
            usedPlayers.add(packageName);
            context.getSharedPreferences("NotificationListenerService", Context.MODE_PRIVATE)
                    .edit().putStringSet("players_used", usedPlayers).apply();
        }
    }

    public static Set<String> getUsedPlayersList(Context context) {
        return context.getSharedPreferences("NotificationListenerService", Context.MODE_PRIVATE)
                    .getStringSet("players_used", new ArraySet<>());
    }

    @TargetApi(21)
    private boolean isInvalidPackage(MediaController controller) {
        String playerPackageName = controller.getPackageName();
        return playerPackageName != null && (playerPackageName.contains(".chrome") || playerPackageName.contains("firefox") ||
                playerBlacklist.contains(playerPackageName));
    }

    private static final List<String> playerBlacklist = Arrays.asList(
            "au.com.shiftyjelly.pocketcasts", "com.bambuna.podcastaddict", "tunein.player, sanity.freeaudiobooks",
            "com.audible.application", "sanity.podcast.freak", "com.samsung.android.video", "tv.twitch.android.app",
            "tv.molotov.app", "com.netflix.mediaclient", "com.android.server.telecom", "tunein.player", "radiotime.player");

    public interface MetadataUpdateListener {
        void onMetadataUpdated(Bundle metadata);
    }
}