package com.peterjosling.scroball;

import android.content.ComponentName;
import android.content.Context;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.net.ConnectivityManager;
import android.service.notification.NotificationListenerService;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;

import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.peterjosling.scroball.db.ScroballDB;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

public class ListenerService extends NotificationListenerService
    implements MediaSessionManager.OnActiveSessionsChangedListener,
        SharedPreferences.OnSharedPreferenceChangeListener {

  private static final String TAG = ListenerService.class.getName();

  private List<MediaController> mediaControllers = new ArrayList<>();
  private Map<MediaController, MediaController.Callback> controllerCallbacks = new WeakHashMap<>();
  private PlaybackTracker playbackTracker;
  private SharedPreferences sharedPreferences;

  @Override
  public void onCreate() {
    ScroballApplication application = (ScroballApplication) getApplication();
    sharedPreferences = application.getSharedPreferences();

    ConnectivityManager connectivityManager =
        (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);

    ScroballDB scroballDB = application.getScroballDB();

    ScrobbleNotificationManager scrobbleNotificationManager =
        new ScrobbleNotificationManager(this, sharedPreferences);

    LastfmClient lastfmClient = application.getLastfmClient();

    Scrobbler scrobbler =
        new Scrobbler(lastfmClient, scrobbleNotificationManager, scroballDB, connectivityManager);

    playbackTracker = new PlaybackTracker(scrobbleNotificationManager, scrobbler);

    Log.i(TAG, "NotificationListenerService started");

    MediaSessionManager mediaSessionManager =
        (MediaSessionManager)
            getApplicationContext().getSystemService(Context.MEDIA_SESSION_SERVICE);

    ComponentName componentName = new ComponentName(this, this.getClass());
    mediaSessionManager.addOnActiveSessionsChangedListener(this, componentName);

    NetworkStateReceiver networkStateReceiver = new NetworkStateReceiver(scrobbler);
    IntentFilter filter = new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION);
    this.registerReceiver(networkStateReceiver, filter);

    // Trigger change event with existing set of sessions.
    List<MediaController> initialSessions = mediaSessionManager.getActiveSessions(componentName);
    onActiveSessionsChanged(initialSessions);

    sharedPreferences.registerOnSharedPreferenceChangeListener(this);
  }

  public static boolean isNotificationAccessEnabled(Context context) {
    return NotificationManagerCompat.getEnabledListenerPackages(context)
        .contains(context.getPackageName());
  }

  @Override
  public void onActiveSessionsChanged(List<MediaController> activeMediaControllers) {
    Log.i(TAG, "Active MediaSessions changed");

    Set<MediaController> existingControllers =
        ImmutableSet.copyOf(Iterables.filter(mediaControllers, controllerCallbacks::containsKey));
    Set<MediaController> newControllers = new HashSet<>(activeMediaControllers);

    Set<MediaController> toRemove = Sets.difference(existingControllers, newControllers);
    Set<MediaController> toAdd = Sets.difference(newControllers, existingControllers);

    for (MediaController controller : toRemove) {
      controller.unregisterCallback(controllerCallbacks.get(controller));
      playbackTracker.handleSessionTermination(controller.getPackageName());
      controllerCallbacks.remove(controller);
    }

    for (final MediaController controller : toAdd) {
      String packageName = controller.getPackageName();
      String prefKey = "player." + packageName;

      if (!sharedPreferences.contains(prefKey)) {
        boolean defaultVal = sharedPreferences.getBoolean("scrobble_new_players", true);

        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putBoolean(prefKey, defaultVal);
        editor.apply();
      }

      if (!sharedPreferences.getBoolean(prefKey, true)) {
        Log.i(TAG, String.format("Ignoring player %s", packageName));
        continue;
      }

      Log.i(TAG, String.format("Listening for events from %s", packageName));

      MediaController.Callback callback =
          new MediaController.Callback() {
            @Override
            public void onPlaybackStateChanged(@NonNull PlaybackState state) {
              controllerPlaybackStateChanged(controller, state);
            }

            @Override
            public void onMetadataChanged(MediaMetadata metadata) {
              controllerMetadataChanged(controller, metadata);
            }
          };

      controllerCallbacks.put(controller, callback);
      controller.registerCallback(callback);

      // Media may already be playing - update with current state.
      controllerPlaybackStateChanged(controller, controller.getPlaybackState());
      controllerMetadataChanged(controller, controller.getMetadata());
    }

    mediaControllers = activeMediaControllers;
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
    if (key.startsWith("player.")) {
      final String packageName = key.substring(7);

      if (sharedPreferences.getBoolean(key, true)) {
        Log.i(TAG, "Player enabled, re-registering callbacks");
        onActiveSessionsChanged(mediaControllers);
      } else {
        Log.i(TAG, "Player disabled, stopping any current tracking");
        final MediaController controller =
            Iterables.find(
                mediaControllers,
                new Predicate<MediaController>() {
                  @Override
                  public boolean apply(MediaController input) {
                    return input.getPackageName().equals(packageName);
                  }
                });

        if (controller != null && controllerCallbacks.containsKey(controller)) {
          controller.unregisterCallback(controllerCallbacks.get(controller));
          playbackTracker.handleSessionTermination(controller.getPackageName());
          controllerCallbacks.remove(controller);
        }
      }
    }
  }

  private void controllerPlaybackStateChanged(MediaController controller, PlaybackState state) {
    playbackTracker.handlePlaybackStateChange(controller.getPackageName(), state);
  }

  private void controllerMetadataChanged(MediaController controller, MediaMetadata metadata) {
    playbackTracker.handleMetadataChange(controller.getPackageName(), metadata);
  }
}

@@ -615,25 +615,24 @@
 on.base.
-Predicate
+Optional
 ;%0Aimport
@@ -6107,24 +6107,33 @@
       final 
+Optional%3C
 MediaControl
@@ -6127,34 +6127,43 @@
 %3CMediaController
- c
+%3E optionalC
 ontroller =%0A    
@@ -6180,17 +6180,20 @@
 erables.
-f
+tryF
 ind(%0A   
@@ -6226,344 +6226,247 @@
 ers,
-%0A                new Predicate%3CMediaController%3E() %7B%0A                  @Override%0A                  public boolean apply(MediaController input) %7B%0A                    return input.getPackageName().equals(packageName);%0A                  %7D%0A                %7D);%0A%0A        if (controller != null && controllerCallbacks.containsKey(controller)) %7B
+ input -%3E input.getPackageName().equals(packageName));%0A%0A        if (optionalController.isPresent()%0A            && controllerCallbacks.containsKey(optionalController.get())) %7B%0A          MediaController controller = optionalController.get();
 %0A   

Fix crash when disabling player without active session