/*
 * Copyright (C) 2015 Simon Vig Therkildsen
 *
 * 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 net.simonvt.cathode.database;

import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.content.AsyncTaskLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import timber.log.Timber;

public abstract class SimpleLoaderBase<T extends AbsSimpleCursor> extends AsyncTaskLoader<T> {

  private final List<Uri> notificationUris = new ArrayList<>();

  private final Map<Uri, ContentObserver> observers = new HashMap<>();

  private T cursor;

  private long waitUntil;

  private Handler handler = new Handler(Looper.getMainLooper());

  private Runnable postResult = new Runnable() {
    @Override public void run() {
      deliverResult(cursor);
    }
  };

  public SimpleLoaderBase(Context context) {
    super(context);
  }

  public void addNotificationUri(Uri uri) {
    notificationUris.add(uri);
    registerUri(uri);
  }

  public void removeNotificationUri(Uri uri) {
    unregisterUri(uri);
    notificationUris.remove(uri);
  }

  public void clearNotificationUris() {
    for (Uri uri : notificationUris) {
      unregisterUri(uri);
    }

    notificationUris.clear();
  }

  private void registerUri(Uri uri) {
    ContentObserver observer = new ForceLoadContentObserver();
    observers.put(uri, observer);

    if (isStarted()) {
      getContext().getContentResolver().registerContentObserver(uri, true, observer);
    }
  }

  private void unregisterUri(Uri uri) {
    ContentObserver observer = observers.get(uri);
    getContext().getContentResolver().unregisterContentObserver(observer);
  }

  public void throttle(long ms) {
    waitUntil = System.currentTimeMillis() + ms;
  }

  @Override public void deliverResult(T cursor) {
    if (isReset()) {
      return;
    }

    this.cursor = cursor;
    handler.removeCallbacks(postResult);

    final long now = System.currentTimeMillis();
    if (now < waitUntil) {
      handler.postDelayed(postResult, waitUntil - now + 250);
      return;
    }

    if (isStarted()) {
      super.deliverResult(cursor);
    }
  }

  @Override protected void onStartLoading() {
    if (cursor != null) {
      deliverResult(cursor);
    }

    for (Uri uri : notificationUris) {
      ContentObserver observer = observers.get(uri);
      getContext().getContentResolver().registerContentObserver(uri, true, observer);
      Timber.d("Registering observer");
    }

    //if (takeContentChanged() || cursor == null) {
    forceLoad();
    //}
  }

  @Override protected void onStopLoading() {
    cancelLoad();

    for (ContentObserver observer : observers.values()) {
      getContext().getContentResolver().unregisterContentObserver(observer);
      Timber.d("unregistering observer");
    }
  }

  @Override protected void onReset() {
    super.onReset();
    onStopLoading();
    cursor = null;
  }

  public final class ForceLoadContentObserver extends ContentObserver {
    public ForceLoadContentObserver() {
      super(new Handler(Looper.getMainLooper()));
    }

    @Override
    public boolean deliverSelfNotifications() {
      return true;
    }

    @Override
    public void onChange(boolean selfChange) {
      onContentChanged();
    }
  }
}

@@ -1549,24 +1549,64 @@
 (Uri uri) %7B%0A
+    synchronized (notificationUris) %7B%0A  
     notifica
@@ -1620,24 +1620,26 @@
 s.add(uri);%0A
+  
     register
@@ -1644,24 +1644,30 @@
 erUri(uri);%0A
+    %7D%0A
   %7D%0A%0A  publi
@@ -1702,24 +1702,64 @@
 (Uri uri) %7B%0A
+    synchronized (notificationUris) %7B%0A  
     unregist
@@ -1766,24 +1766,26 @@
 erUri(uri);%0A
+  
     notifica
@@ -1806,16 +1806,22 @@
 e(uri);%0A
+    %7D%0A
   %7D%0A%0A  p
@@ -1853,24 +1853,64 @@
 ionUris() %7B%0A
+    synchronized (notificationUris) %7B%0A  
     for (Uri
@@ -1934,32 +1934,34 @@
 onUris) %7B%0A      
+  
 unregisterUri(ur
@@ -1964,27 +1964,31 @@
 i(uri);%0A    
+ 
+ 
 %7D%0A%0A
+  
     notifica
@@ -2005,16 +2005,22 @@
 lear();%0A
+    %7D%0A
   %7D%0A%0A  p
@@ -3023,24 +3023,64 @@
 or);%0A    %7D%0A%0A
+    synchronized (notificationUris) %7B%0A  
     for (Uri
@@ -3108,24 +3108,26 @@
 is) %7B%0A      
+ 
+ 
 ContentObser
@@ -3157,24 +3157,26 @@
 s.get(uri);%0A
+  
       getCon
@@ -3247,32 +3247,34 @@
 bserver);%0A      
+  
 Timber.d(%22Regist
@@ -3287,24 +3287,32 @@
 observer%22);%0A
+      %7D%0A
     %7D%0A%0A    /
@@ -3919,36 +3919,32 @@
 %7D%0A%0A    @Override
-%0A   
  public boolean 
@@ -4011,20 +4011,16 @@
 Override
-%0A   
  public 

Synchronize access to notificationUris.