<commit_before>package solutions.alterego.androidbound.android.adapters;

import android.support.v4.util.Pair;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.SparseArray;
import android.view.ViewGroup;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import solutions.alterego.androidbound.interfaces.IViewBinder;

import static android.support.v7.util.DiffUtil.calculateDiff;

@Accessors(prefix = "m")
public class BindableRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private final IViewBinder mViewBinder;

    @Getter
    private int mItemTemplate;

    @Getter
    private Map<Class<?>, Integer> mTemplatesForObjects = new HashMap<>();

    @Getter
    private List mItemsSource = new ArrayList<>();

    private SparseArray<Class<?>> mObjectIndex;

    @Getter
    @Setter
    private RecyclerView.LayoutManager mLayoutManager;

    private Disposable mSetValuesDisposable = Disposables.disposed();

    private Disposable mRemoveItemsDisposable = Disposables.disposed();

    private Queue<List<?>> pendingUpdates =
            new ArrayDeque<>();

    private Disposable mAddValueDisposable = Disposables.disposed();

    public BindableRecyclerViewAdapter(IViewBinder vb, int itemTemplate) {
        mViewBinder = vb;
        mItemTemplate = itemTemplate;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Class<?> clazz = mObjectIndex.get(viewType);
        int layoutRes = mItemTemplate;

        if (clazz != null && mTemplatesForObjects.containsKey(clazz)) {
            layoutRes = mTemplatesForObjects.get(clazz);
            mViewBinder.getLogger().verbose(
                    "BindableRecyclerViewAdapter creating VH for viewType = " + viewType + " i.e. class = " + clazz
                            + " using layoutRes = "
                            + layoutRes);
        } else if (layoutRes != 0) {
            mViewBinder.getLogger().verbose("BindableRecyclerViewAdapter creating VH using layoutRes = " + layoutRes);
        } else {
            mViewBinder.getLogger().error("BindableRecyclerViewAdapter cannot find templates for class = " + clazz
                    + ": did you call setTemplatesForObjects or set itemTemplate in XML?");
        }
        return new BindableRecyclerViewItemViewHolder(
                mViewBinder.inflate(parent.getContext(), null, layoutRes, parent, false),
                mViewBinder, parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof BindableRecyclerViewItemViewHolder) {
            if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
                ((BindableRecyclerViewItemViewHolder) holder)
                        .onBindViewHolder(getItemsSource().get(position), getLayoutManager());
            } else {
                ((BindableRecyclerViewItemViewHolder) holder).onBindViewHolder(getItemsSource().get(position));
            }
        }
    }

    @Override
    public int getItemCount() {
        return getItemsSource() != null ? getItemsSource().size() : 0;
    }

    @Override
    public int getItemViewType(int position) {
        Object obj = getItemsSource().get(position);
        int viewType = mObjectIndex.indexOfValue(obj.getClass());
        mViewBinder.getLogger().verbose(
                "BindableRecyclerViewAdapter getItemViewType viewType = " + viewType + " i.e. class = " + obj.getClass()
                        .toString()
                        + " for position = " + position);
        return viewType;
    }

    public void setItemsSource(final List<?> value) {
        final List<?> oldItems = new ArrayList<>(mItemsSource);
        mSetValuesDisposable.dispose();
        mSetValuesDisposable = Observable.just(value)
                .subscribeOn(Schedulers.computation())
                .map(newList -> new Pair<List<?>, DiffUtil.DiffResult>(newList, calculateDiff(new ItemSourceDiffCallback(oldItems, value))))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::applyDiffResult, Throwable::printStackTrace);
    }

    public void addItemsSource(List<?> values) {
        if (values == null) {
            if (mItemsSource != null) {
                int size = mItemsSource.size();
                mItemsSource = null;
                postNotifyItemRangeRemoved(0, size);
            }
            return;
        }
        if (mItemsSource == null) {
            mItemsSource = new ArrayList<>();
        }

        mAddValueDisposable.dispose();
        mAddValueDisposable = Observable.fromIterable(values)
                .filter(value -> value != null)
                .subscribe(value -> {
                    boolean contains = mItemsSource.contains(value);
                    if (contains) {
                        int index = mItemsSource.indexOf(value);
                        mItemsSource.set(index, value);
                        notifyItemChanged(index);
                    } else if (mItemsSource.add(value)) {
                        notifyItemInserted(mItemsSource.size() - 1);
                    }
                });
    }

    private void applyDiffResult(Pair<List<?>, DiffUtil.DiffResult> resultPair) {
        if (!pendingUpdates.isEmpty()) {
            pendingUpdates.remove();
        }
        mItemsSource.clear();
        if (resultPair.first != null) {
            mItemsSource.addAll(new ArrayList<>(resultPair.first));
        }
        resultPair.second.dispatchUpdatesTo(this);
        if (pendingUpdates.size() > 0) {
            setItemsSource(pendingUpdates.peek());
        }
    }

    /* to prevent Cannot call this method in a scroll callback. Scroll callbacks might be run during a measure
    & layout pass where you cannot change the RecyclerView data. Any method call that might change the structure<commit_msg>not using DiffUtil for first RV source setting<commit_after>package solutions.alterego.androidbound.android.adapters;

import android.support.v4.util.Pair;
import android.support.v7.util.DiffUtil;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.SparseArray;
import android.view.ViewGroup;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.Disposables;
import io.reactivex.schedulers.Schedulers;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import solutions.alterego.androidbound.interfaces.IViewBinder;

import static android.support.v7.util.DiffUtil.calculateDiff;

@Accessors(prefix = "m")
public class BindableRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private final IViewBinder mViewBinder;

    @Getter
    private int mItemTemplate;

    @Getter
    private Map<Class<?>, Integer> mTemplatesForObjects = new HashMap<>();

    @Getter
    private List mItemsSource = new ArrayList<>();

    private SparseArray<Class<?>> mObjectIndex;

    @Getter
    @Setter
    private RecyclerView.LayoutManager mLayoutManager;

    private Disposable mSetValuesDisposable = Disposables.disposed();

    private Disposable mRemoveItemsDisposable = Disposables.disposed();

    private Queue<List<?>> pendingUpdates =
            new ArrayDeque<>();

    private Disposable mAddValueDisposable = Disposables.disposed();

    public BindableRecyclerViewAdapter(IViewBinder vb, int itemTemplate) {
        mViewBinder = vb;
        mItemTemplate = itemTemplate;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Class<?> clazz = mObjectIndex.get(viewType);
        int layoutRes = mItemTemplate;

        if (clazz != null && mTemplatesForObjects.containsKey(clazz)) {
            layoutRes = mTemplatesForObjects.get(clazz);
            mViewBinder.getLogger().verbose(
                    "BindableRecyclerViewAdapter creating VH for viewType = " + viewType + " i.e. class = " + clazz
                            + " using layoutRes = "
                            + layoutRes);
        } else if (layoutRes != 0) {
            mViewBinder.getLogger().verbose("BindableRecyclerViewAdapter creating VH using layoutRes = " + layoutRes);
        } else {
            mViewBinder.getLogger().error("BindableRecyclerViewAdapter cannot find templates for class = " + clazz
                    + ": did you call setTemplatesForObjects or set itemTemplate in XML?");
        }
        return new BindableRecyclerViewItemViewHolder(
                mViewBinder.inflate(parent.getContext(), null, layoutRes, parent, false),
                mViewBinder, parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof BindableRecyclerViewItemViewHolder) {
            if (getLayoutManager() instanceof StaggeredGridLayoutManager) {
                ((BindableRecyclerViewItemViewHolder) holder)
                        .onBindViewHolder(getItemsSource().get(position), getLayoutManager());
            } else {
                ((BindableRecyclerViewItemViewHolder) holder).onBindViewHolder(getItemsSource().get(position));
            }
        }
    }

    @Override
    public int getItemCount() {
        return getItemsSource() != null ? getItemsSource().size() : 0;
    }

    @Override
    public int getItemViewType(int position) {
        Object obj = getItemsSource().get(position);
        int viewType = mObjectIndex.indexOfValue(obj.getClass());
        mViewBinder.getLogger().verbose(
                "BindableRecyclerViewAdapter getItemViewType viewType = " + viewType + " i.e. class = " + obj.getClass()
                        .toString()
                        + " for position = " + position);
        return viewType;
    }

    public void setItemsSource(final List<?> value) {
        final List<?> oldItems = new ArrayList<>(mItemsSource);
        mSetValuesDisposable.dispose();
        mSetValuesDisposable = Observable.just(value)
                .subscribeOn(Schedulers.computation())
                .map(newList -> new Pair<List<?>, DiffUtil.DiffResult>(newList, calculateDiff(new ItemSourceDiffCallback(oldItems, value))))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(this::applyDiffResult, Throwable::printStackTrace);
    }

    private void applyDiffResult(Pair<List<?>, DiffUtil.DiffResult> resultPair) {
        boolean firstStart = true;

        if (!pendingUpdates.isEmpty()) {
            pendingUpdates.remove();
        }

        if (mItemsSource.size() > 0) {
            mItemsSource.clear();
            firstStart = false;
        }

        if (resultPair.first != null) {
            mItemsSource.addAll(new ArrayList<>(resultPair.first));
        }

        //if we call DiffUtil.DiffResult.dispatchUpdatesTo() on an empty adapter, it will crash - we have to call notifyDataSetChanged()!
        if (firstStart) {
            notifyDataSetChanged();
        } else {
            resultPair.second.dispatchUpdatesTo(this);
        }

        if (pendingUpdates.size() > 0) {
            setItemsSource(pendingUpdates.peek());
        }
    }

    public void addItemsSource(List<?> values) {
        if (values == null) {
            if (mItemsSource != null) {
                int size = mItemsSource.size();
                mItemsSource = null;
                postNotifyItemRangeRemoved(0, size);
            }
            return;
        }
        if (mItemsSource == null) {
            mItemsSource = new ArrayList<>();
        }

        mAddValueDisposable.dispose();
        mAddValueDisposable = Observable.fromIterable(values)
                .filter(value -> value != null)
                .subscribe(value -> {
                    boolean contains = mItemsSource.contains(value);
                    if (contains) {
                        int index = mItemsSource.indexOf(value);
                        mItemsSource.set(index, value);
                        notifyItemChanged(index);
                    } else if (mItemsSource.add(value)) {
                        notifyItemInserted(mItemsSource.size() - 1);
                    }
                });
    }

    /* to prevent Cannot call this method in a scroll callback. Scroll callbacks might be run during a measure
    & layout pass where you cannot change the RecyclerView data. Any method call that might change the structure
{'fraction_non_alphanumeric': 0.09634926608957471, 'fraction_numerical': 0.001580730146782085, 'mean_word_length': 2.5157449060598043, 'pattern_counts': {'":': 2, '<': 45, '<?xml version=': 0, '>': 54, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1799487', 'n_tokens_mistral': 3661, 'n_tokens_neox': 3501, 'n_words': 758}