package io.doist.recyclerviewext.choice_modes;

import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.AbsListView;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 * {@link AbsListView}'s choice modes for {@link RecyclerView}.
 */
public abstract class Selector {
    public static final Object PAYLOAD_SELECT = new Object();

    private static final String KEY_SELECTOR_SELECTED_IDS = ":selector_selected_ids";

    protected RecyclerView mRecyclerView;
    protected RecyclerView.Adapter mAdapter;

    protected OnSelectionChangedListener mObserver;

    // Used internally to disable item change notifications.
    // All selection changes lead to these notifications and it can be undesirable or inefficient.
    private boolean mNotifyItemChanges = true;

    protected Selector(RecyclerView recyclerView, RecyclerView.Adapter adapter) {
        mRecyclerView = recyclerView;
        mAdapter = adapter;
        mAdapter.registerAdapterDataObserver(new SelectorAdapterDataObserver());
    }

    public abstract void setSelected(long id, boolean selected);

    public void toggleSelected(long id) {
        setSelected(id, !isSelected(id));
    }

    public abstract boolean isSelected(long id);

    public abstract long[] getSelectedIds();

    public abstract int getSelectedCount();

    public abstract void clearSelected();

    public void setOnSelectionChangedListener(OnSelectionChangedListener observer) {
        mObserver = observer;
    }

    public void bind(RecyclerView.ViewHolder holder) {
        bind(holder, null);
    }

    /**
     * Binds the {@code holder} according to its selected state using {@link View#setActivated(boolean)}.
     *
     * When {@code payload} is not {@link #PAYLOAD_SELECT}, the background jumps immediately to the final state.
     * {@link #PAYLOAD_SELECT} is the payload used when the selection changes, where it's likely that the
     * animation (if any) should run.
     */
    public void bind(RecyclerView.ViewHolder holder, Object payload) {
        holder.itemView.setActivated(isSelected(holder.getItemId()));

        if (payload != PAYLOAD_SELECT) {
            // Ensure background jumps immediately to the current state instead of animating.
            Drawable background = holder.itemView.getBackground();
            if (background != null) {
                if (background instanceof LayerDrawable) {
                    LayerDrawable backgroundLayerDrawable = (LayerDrawable) background;
                    for (int i = 0; i < backgroundLayerDrawable.getNumberOfLayers(); ++i) {
                        backgroundLayerDrawable.getDrawable(i).jumpToCurrentState();
                    }
                }
                background.jumpToCurrentState();
            }
        }
    }

    public void onSaveInstanceState(Bundle outState) {
        outState.putLongArray(KEY_SELECTOR_SELECTED_IDS, getSelectedIds());
    }

    public void onRestoreInstanceState(Bundle savedInstanceState) {
        long[] selectedIds = savedInstanceState.getLongArray(KEY_SELECTOR_SELECTED_IDS);
        if (selectedIds != null) {
            for (long selectedId : selectedIds) {
                mNotifyItemChanges = false;
                setSelected(selectedId, true);
                mNotifyItemChanges = true;
            }
        }
    }

    protected void notifyItemChanged(long id) {
        if (mNotifyItemChanges) {
            RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForItemId(id);
            if (holder != null) {
                int position = holder.getLayoutPosition();
                if (position != RecyclerView.NO_POSITION) {
                    mAdapter.notifyItemChanged(position, PAYLOAD_SELECT);
                }
            }
        }
    }

    public interface OnSelectionChangedListener {
        void onSelectionChanged(Selector selector);
    }

    private class SelectorAdapterDataObserver extends RecyclerView.AdapterDataObserver {
        private DeselectMissingIdsRunnable mDeselectMissingIdsRunnable = new DeselectMissingIdsRunnable();

        @Override
        public void onChanged() {
            mDeselectMissingIdsRunnable.run();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // Deselect missing ids after all changes go through.
            mRecyclerView.removeCallbacks(mDeselectMissingIdsRunnable);
            mRecyclerView.post(mDeselectMissingIdsRunnable);
        }

        private class DeselectMissingIdsRunnable implements Runnable {
            @Override
            public void run() {
                int selectedCount = getSelectedCount();
                if (selectedCount > 0) {
                    // Build list of all selected ids that are still present.
                    List<Long> selectedIds = new ArrayList<>(selectedCount);
                    for (int i = 0; i < mAdapter.getItemCount(); i++) {
                        long id = mAdapter.getItemId(i);
                        if (isSelected(id)) {
                            selectedIds.add(id);
                        }
                    }

                    // Build set of missing ids.
                    HashSet<Long> missingIds = new HashSet<>(selectedCount);
                    for (long previouslySelectedId : getSelectedIds()) {
                        missingIds.add(previouslySelectedId);
                    }
                    missingIds.removeAll(selectedIds);

                    // Unselect all missing ids.
                    mNotifyItemChanges = false;
                    for (Long missingId : missingIds) {
                        setSelected(missingId, false);
                    }
                    mNotifyItemChanges = true;
                }
            }
        }
    }
}

@@ -3272,32 +3272,72 @@
 dIds != null) %7B%0A
+            mNotifyItemChanges = false;%0A
             for 
@@ -3374,52 +3374,8 @@
 ) %7B%0A
-                mNotifyItemChanges = false;%0A
     
@@ -3413,32 +3413,42 @@
 dId, true);%0A    
+        %7D%0A
             mNot
@@ -3470,30 +3470,16 @@
 = true;%0A
-            %7D%0A
         
@@ -3565,24 +3565,214 @@
 mChanges) %7B%0A
+            int position = RecyclerView.NO_POSITION;%0A%0A            // Look up the item position using findViewHolderForItemId().%0A            // This is fast and will work in most scenarious.%0A
             
@@ -3889,36 +3889,32 @@
                 
-int 
 position = holde
@@ -3922,14 +3922,15 @@
 .get
-Layout
+Adapter
 Posi
@@ -3945,28 +3945,644 @@
             
+%7D%0A%0A
     
+        // RecyclerView can cache views offscreen that are not found by findViewHolderForItemId() et al,%0A            // but will be reattached without being rebound, so the adapter items must be iterated.%0A            // This is slower but prevents inconsistencies on the edges of the RecyclerView.%0A            if (position == RecyclerView.NO_POSITION) %7B%0A                for (int i = 0; i %3C mAdapter.getItemCount(); i++) %7B%0A                    if (mAdapter.getItemId(i) == id) %7B%0A                        position = i;%0A                        break;%0A                    %7D%0A                %7D%0A            %7D%0A%0A            
 if (position
@@ -4605,36 +4605,32 @@
 .NO_POSITION) %7B%0A
-    
                 
@@ -4683,34 +4683,16 @@
 ELECT);%0A
-                %7D%0A
         

Improve notification reliability for offscreen ViewHolders