package com.getbase.android.autoprovider;

import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;

import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.ContentObservable;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.net.Uri;
import android.os.Build;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class MultiUriCursorWrapper extends CursorWrapper {

  public MultiUriCursorWrapper(Cursor cursor) {
    super(cursor);
  }

  protected boolean mClosed;
  protected ContentResolver mContentResolver;

  private final Set<Uri> mNotifyUris = new CopyOnWriteArraySet<>();

  private final Object mSelfObserverLock = new Object();
  private ContentObserver mSelfObserver;
  private boolean mSelfObserverRegistered;

  private final LinkedHashSet<Uri> mChangedByUris = new LinkedHashSet<>();

  private final ContentObservable mContentObservable = new ContentObservable();

  @Override
  public void deactivate() {
    onDeactivateOrClose();
    super.deactivate();
  }

  protected void onDeactivateOrClose() {
    if (mSelfObserver != null) {
      mContentResolver.unregisterContentObserver(mSelfObserver);
      mSelfObserverRegistered = false;
    }
  }

  @Override
  public boolean requery() {
    if (mSelfObserver != null && !mSelfObserverRegistered) {
      for (Uri notifyUri : mNotifyUris) {
        mContentResolver.registerContentObserver(notifyUri, true, mSelfObserver);
      }
      mSelfObserverRegistered = true;
    }

    boolean success = super.requery();
    if (success) {
      mChangedByUris.clear();
    }
    return success;
  }

  @Override
  public boolean isClosed() {
    return super.isClosed() && mClosed;
  }

  @Override
  public void close() {
    super.close();

    mClosed = true;
    mContentObservable.unregisterAll();
    onDeactivateOrClose();
  }

  @SuppressWarnings("deprecation")
  @Override
  public void registerContentObserver(ContentObserver observer) {
    mContentObservable.registerObserver(observer);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
      dispatchChangeForUris(observer);
    } else {
      if (!mChangedByUris.isEmpty()) {
        observer.dispatchChange(false);
      }
    }
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  private void dispatchChangeForUris(ContentObserver observer) {
    for (Uri changedByUri : new ArrayList<>(mChangedByUris)) {
      observer.dispatchChange(false, changedByUri);
    }
  }

  @Override
  public void unregisterContentObserver(ContentObserver observer) {
    // cursor will unregister all observers when it close
    if (!mClosed) {
      mContentObservable.unregisterObserver(observer);
    }
  }

  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
  @SuppressWarnings("deprecation")
  private void onChange(boolean selfChange, Uri uri) {
    synchronized (mSelfObserverLock) {

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        mContentObservable.dispatchChange(selfChange, uri);
      } else {
        mContentObservable.dispatchChange(selfChange);
      }

      if (selfChange) {
        for (Uri notifyUri : mNotifyUris) {
          mContentResolver.notifyChange(notifyUri, mSelfObserver);
        }
      }

      if (!selfChange) {
        mChangedByUris.add(uri);
      }
    }
  }

  public MultiUriCursorWrapper withNotificationUris(ContentResolver cr, Collection<Uri> uris) {
    synchronized (mSelfObserverLock) {
      mNotifyUris.addAll(uris);
      mContentResolver = cr;
      if (mSelfObserver == null) {
        mSelfObserver = new SelfContentObserver(this);
      }

      for (Uri uri : uris) {
        mContentResolver.registerContentObserver(uri, true, mSelfObserver);
      }

      mSelfObserverRegistered = true;
    }

    return this;
  }

  public MultiUriCursorWrapper withNotificationUri(ContentResolver cr, Uri uri) {
    return withNotificationUris(cr, Collections.singletonList(uri));
  }

  public List<Uri> getNotificationUris() {
    return Lists.newArrayList(mNotifyUris);
  }

  @Override
  public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
    withNotificationUri(cr, notifyUri);
  }

  @Override
  public Uri getNotificationUri() {
    synchronized (mSelfObserverLock) {
      return Iterables.getFirst(mNotifyUris, null);
    }
  }

  @Override
  protected void finalize() {
    try {
      super.finalize();

      if (mSelfObserver != null && mSelfObserverRegistered) {
        mContentResolver.unregisterContentObserver(mSelfObserver);
      }

      if (!mClosed) close();
    } catch (Throwable e) {
      // ignored
    }
  }

  private static class SelfContentObserver extends ContentObserver {
    WeakReference<MultiUriCursorWrapper> mCursor;

    public SelfContentObserver(MultiUriCursorWrapper cursor) {
      super(null);
      mCursor = new WeakReference<>(cursor);
    }

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

    @Override
    public void onChange(boolean selfChange, Uri uri) {
      MultiUriCursorWrapper cursor = mCursor.get();
      if (cursor != null) {
        cursor.onChange(false, uri);
      }
    }

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

@@ -530,40 +530,8 @@
 ns;%0A
-import java.util.LinkedHashSet;%0A
 impo
@@ -1056,26 +1056,16 @@
 e final 
-LinkedHash
 Set%3CUri%3E
@@ -1090,18 +1090,24 @@
 new 
-LinkedHash
+CopyOnWriteArray
 Set%3C
@@ -2636,24 +2636,8 @@
 i : 
-new ArrayList%3C%3E(
 mCha
@@ -2647,17 +2647,16 @@
 dByUris)
-)
  %7B%0A     

Fix ConcurrentModificationException