/*
 * Copyright (C) 2016 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 BaseAsyncLoader<T> extends AsyncTaskLoader<T> {

  public static final long DEFAULT_UPDATE_THROTTLE = 1000;
  public static final long DEFAULT_THROTTLE = 1000;

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

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

  private T data;

  private long waitUntil;

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

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

  public BaseAsyncLoader(Context context) {
    super(context);
    setUpdateThrottle(DEFAULT_UPDATE_THROTTLE);
  }

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

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

  public void clearNotificationUris() {
    synchronized (notificationUris) {
      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.data = 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 (data != null) {
      deliverResult(data);
    }

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

    //if (takeContentChanged() || data == 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();
    data = 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();
    }
  }
}

@@ -1311,35 +1311,8 @@
 a;%0A%0A
-  private long waitUntil;%0A%0A
   pr
@@ -1372,17 +1372,16 @@
 per());%0A
-%0A
   privat
@@ -1396,107 +1396,49 @@
 le p
-ostResult = new Runnable() %7B%0A    @Override public void run() %7B%0A      deliverResult(data);%0A    %7D%0A  %7D
+endingOnChange;%0A  private long nextUpdate
 ;%0A%0A 
@@ -2133,24 +2133,24 @@
 r = new 
-ForceLoa
+Throttle
 dContent
@@ -2491,96 +2491,8 @@
  %7D%0A%0A
-  public void throttle(long ms) %7B%0A    waitUntil = System.currentTimeMillis() + ms;%0A  %7D%0A%0A
   @O
@@ -2606,208 +2606,8 @@
 sor;
-%0A    handler.removeCallbacks(postResult);%0A%0A    final long now = System.currentTimeMillis();%0A    if (now %3C waitUntil) %7B%0A      handler.postDelayed(postResult, waitUntil - now + 250);%0A      return;%0A    %7D
 %0A%0A  
@@ -3060,18 +3060,16 @@
  %7D%0A%0A    
-//
 if (take
@@ -3108,16 +3108,18 @@
 ) %7B%0A    
+  
 forceLoa
@@ -3127,18 +3127,16 @@
 ();%0A    
-//
 %7D%0A  %7D%0A%0A 
@@ -3502,28 +3502,795 @@
 lic 
-final class ForceLoa
+void throttle(long delayMillis) %7B%0A    if (pendingOnChange != null) %7B%0A      final long currentTime = System.currentTimeMillis();%0A      if (currentTime + delayMillis %3E nextUpdate) %7B%0A        handler.removeCallbacks(pendingOnChange);%0A        pendingOnChange = null;%0A      %7D%0A    %7D%0A    postOnChange(delayMillis);%0A  %7D%0A%0A  private void postOnChange(long delayMillis) %7B%0A    if (pendingOnChange == null) %7B%0A      final long currentTime = System.currentTimeMillis();%0A      nextUpdate = currentTime + delayMillis;%0A      pendingOnChange = new Runnable() %7B%0A        @Override public void run() %7B%0A          onContentChanged();%0A          nextUpdate = 0L;%0A          pendingOnChange = null;%0A        %7D%0A      %7D;%0A      handler.postDelayed(pendingOnChange, delayMillis);%0A    %7D%0A  %7D%0A%0A  private final class Throttle
 dCon
@@ -4332,27 +4332,21 @@
 r %7B%0A
+%0A
     
-public ForceLoa
+Throttle
 dCon
@@ -4378,43 +4378,15 @@
 per(
-new H
+h
 andler
-(Looper.getMainLooper())
 );%0A 
@@ -4538,33 +4538,52 @@
 %7B%0A      
-onContentChanged(
+postOnChange(DEFAULT_UPDATE_THROTTLE
 );%0A    %7D

Refactor throttle away from Loader logic.