/*
 * Copyright (C) 2018 Yaroslav Mytkalyk
 *
 * 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 com.doctoror.rxcursorloader;

import android.content.ContentResolver;
import android.database.ContentObserver;
import android.database.Cursor;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.Scheduler;
import io.reactivex.functions.Action;

import static com.doctoror.rxcursorloader.RxCursorLoader.TAG;
import static com.doctoror.rxcursorloader.RxCursorLoader.isDebugLoggingEnabled;

final class RxCursorLoaderFlowableFactory {

    @NonNull
    static Flowable<Cursor> create(
            @NonNull final ContentResolver resolver,
            @NonNull final RxCursorLoader.Query query,
            @NonNull final Scheduler scheduler,
            @NonNull final BackpressureStrategy backpressureStrategy) {
        //noinspection ConstantConditions
        if (resolver == null) {
            throw new NullPointerException("ContentResolver must not be null");
        }
        //noinspection ConstantConditions
        if (query == null) {
            throw new NullPointerException("Query must not be null");
        }

        final CursorLoaderOnSubscribe onSubscribe = new CursorLoaderOnSubscribe(
                resolver, query, scheduler);

        return Flowable
                .create(onSubscribe, backpressureStrategy)
                .subscribeOn(scheduler)
                .doFinally(new Action() {
                    @Override
                    public void run() {
                        onSubscribe.release();
                    }
                });
    }

    private static final class CursorLoaderOnSubscribe
            implements FlowableOnSubscribe<Cursor> {

        private final Object mLock = new Object();

        @NonNull
        private final ContentResolver mContentResolver;

        @NonNull
        private final RxCursorLoader.Query mQuery;

        @NonNull
        private final Scheduler mScheduler;

        private Handler mHandler;

        private FlowableEmitter<Cursor> mEmitter;

        private ContentObserver mResolverObserver;

        CursorLoaderOnSubscribe(
                @NonNull final ContentResolver resolver,
                @NonNull final RxCursorLoader.Query query,
                @NonNull final Scheduler scheduler) {
            mContentResolver = resolver;
            mQuery = query;
            this.mScheduler = scheduler;
        }

        @Override
        public void subscribe(final FlowableEmitter<Cursor> emitter) {
            final HandlerThread handlerThread = new HandlerThread(TAG.concat(".HandlerThread"));
            handlerThread.start();
            synchronized (mLock) {
                mHandler = new Handler(handlerThread.getLooper());
                mEmitter = emitter;
                mContentResolver.registerContentObserver(mQuery.contentUri, true,
                        getResolverObserver());
            }
            reload();
        }

        private void release() {
            synchronized (mLock) {
                if (mResolverObserver != null) {
                    mContentResolver.unregisterContentObserver(mResolverObserver);
                    mResolverObserver = null;
                }

                mEmitter = null;

                if (mHandler != null) {
                    final Looper looper = mHandler.getLooper();
                    if (looper != null) {
                        looper.quit();
                    }
                }
                mHandler = null;
            }
        }

        /**
         * Loads new {@link Cursor}.
         * <p>
         * This must be called from {@link #subscribe(FlowableEmitter)} thread
         */
        private synchronized void reload() {
            synchronized (mLock) {
                if (isDebugLoggingEnabled()) {
                    Log.d(TAG, mQuery.toString());
                }

                final Cursor c = mContentResolver.query(
                        mQuery.contentUri,
                        mQuery.projection,
                        mQuery.selection,
                        mQuery.selectionArgs,
                        mQuery.sortOrder);

                if (mEmitter != null && !mEmitter.isCancelled()) {
                    if (c != null) {
                        mEmitter.onNext(c);
                    } else {
                        mEmitter.onError(new QueryReturnedNullException());
                    }
                }
            }
        }

        /**
         * Creates the {@link ContentObserver} to observe {@link Cursor} changes.
         * It must be initialized from thread in which {@link #subscribe(FlowableEmitter)} is
         * called.
         *
         * @return the {@link ContentObserver} to observe {@link Cursor} changes.
         */
        @NonNull
        private ContentObserver getResolverObserver() {
            if (mResolverObserver == null) {
                mResolverObserver = new ContentObserver(mHandler) {

                    @Override
                    public void onChange(final boolean selfChange) {
                        super.onChange(selfChange);
                        mScheduler.scheduleDirect(mReloadRunnable);
                    }
                };
            }
            return mResolverObserver;
        }

        private final Runnable mReloadRunnable = new Runnable() {
            @Override
            public void run() {
                reload();
            }
        };
    }
}

@@ -781,41 +781,8 @@
 er;%0A
-import android.os.HandlerThread;%0A
 impo
@@ -2693,24 +2693,41 @@
 Scheduler;%0A%0A
+        @NonNull%0A
         priv
@@ -2730,16 +2730,22 @@
 private 
+final 
 Handler 
@@ -2752,16 +2752,54 @@
 mHandler
+ = new Handler(Looper.getMainLooper())
 ;%0A%0A     
@@ -2848,60 +2848,8 @@
 r;%0A%0A
-        private ContentObserver mResolverObserver;%0A%0A
     
@@ -3132,13 +3132,8 @@
     
-this.
 mSch
@@ -3268,229 +3268,30 @@
     
-final HandlerThread handlerThread = new HandlerThread(TAG.concat(%22.HandlerThread%22));%0A            handlerThread.start();%0A            synchronized (mLock) %7B%0A                mHandler = new Handler(handlerThread.getLooper());
+synchronized (mLock) %7B
 %0A   
@@ -3429,35 +3429,32 @@
         
-getResolver
+mContent
 Observer
 ());%0A   
@@ -3445,18 +3445,16 @@
 Observer
-()
 );%0A     
@@ -3567,61 +3567,8 @@
 ) %7B%0A
-                if (mResolverObserver != null) %7B%0A    
     
@@ -3623,24 +3623,23 @@
 server(m
-Resolver
+Content
 Observer
@@ -3645,73 +3645,8 @@
 r);%0A
-                    mResolverObserver = null;%0A                %7D%0A%0A
     
@@ -3674,17 +3674,16 @@
 = null;%0A
-%0A
         
@@ -3694,20 +3694,16 @@
     
-if (
 mHandler
  != 
@@ -3702,236 +3702,41 @@
 dler
- != null) %7B%0A                    final Looper looper = mHandler.getLooper();%0A                    if (looper != null) %7B%0A                        looper.quit();%0A                    %7D%0A                %7D%0A                mHandler = null
+.removeCallbacksAndMessages(null)
 ;%0A  
@@ -4715,455 +4715,46 @@
     
-/**%0A         * Creates the %7B@link ContentObserver%7D to observe %7B@link Cursor%7D changes.%0A         * It must be initialized from thread in which %7B@link #subscribe(FlowableEmitter)%7D is%0A         * called.%0A         *%0A         * @return the %7B@link ContentObserver%7D to observe %7B@link Cursor%7D changes.%0A         */%0A        @NonNull%0A        private ContentObserver getResolverObserver() %7B%0A            if (mResolverObserver == null) %7B%0A                mResolver
+private final ContentObserver mContent
 Obse
@@ -4801,32 +4801,24 @@
             
-        
 @Override%0A  
@@ -4823,32 +4823,24 @@
             
-     
-   
 public void 
@@ -4880,68 +4880,8 @@
 ) %7B%0A
-                        super.onChange(selfChange);%0A        
     
@@ -4936,24 +4936,16 @@
 nable);%0A
-        
         
@@ -4962,80 +4962,10 @@
     
-        %7D;%0A            %7D%0A            return mResolverObserver;%0A        
 %7D
+;
 %0A%0A  

Remove HandlerThread usage