package io.virtualapp.widgets;

import android.os.Bundle;
import android.support.annotation.CallSuper;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;

/**
 * @author Aidan Follestad (afollestad)
 */
public abstract class DragSelectRecyclerViewAdapter<VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {

    private ArrayList<Integer> mSelectedIndices;
    private SelectionListener mSelectionListener;
    private int mLastCount = -1;
    private int mMaxSelectionCount = -1;
    protected DragSelectRecyclerViewAdapter() {
        mSelectedIndices = new ArrayList<>();
    }

    private void fireSelectionListener() {
        if (mLastCount == mSelectedIndices.size())
            return;
        mLastCount = mSelectedIndices.size();
        if (mSelectionListener != null)
            mSelectionListener.onDragSelectionChanged(mLastCount);
    }

    public void setMaxSelectionCount(int maxSelectionCount) {
        this.mMaxSelectionCount = maxSelectionCount;
    }

    public void setSelectionListener(SelectionListener selectionListener) {
        this.mSelectionListener = selectionListener;
    }

    public void saveInstanceState(Bundle out) {
        saveInstanceState("selected_indices", out);
    }

    public void saveInstanceState(String key, Bundle out) {
        out.putSerializable(key, mSelectedIndices);
    }

    public void restoreInstanceState(Bundle in) {
        restoreInstanceState("selected_indices", in);
    }

    public void restoreInstanceState(String key, Bundle in) {
        if (in != null && in.containsKey(key)) {
            //noinspection unchecked
            mSelectedIndices = (ArrayList<Integer>) in.getSerializable(key);
            if (mSelectedIndices == null) mSelectedIndices = new ArrayList<>();
            else fireSelectionListener();
        }
    }

    public final void setSelected(int index, boolean selected) {
        if (!isIndexSelectable(index))
            selected = false;
        if (selected) {
            if (!mSelectedIndices.contains(index) &&
                    (mMaxSelectionCount == -1 ||
                            mSelectedIndices.size() < mMaxSelectionCount)) {
                mSelectedIndices.add(index);
                notifyItemChanged(index);
            }
        } else if (mSelectedIndices.contains(index)) {
            mSelectedIndices.remove((Integer) index);
            notifyItemChanged(index);
        }
        fireSelectionListener();
    }

    public final boolean toggleSelected(int index) {
        boolean selectedNow = false;
        if (isIndexSelectable(index)) {
            if (mSelectedIndices.contains(index)) {
                mSelectedIndices.remove((Integer) index);
            } else if (mMaxSelectionCount == -1 ||
                    mSelectedIndices.size() < mMaxSelectionCount) {
                mSelectedIndices.add(index);
                selectedNow = true;
            }
            notifyItemChanged(index);
        }
        fireSelectionListener();
        return selectedNow;
    }

    protected boolean isIndexSelectable(int index) {
        return true;
    }

    @CallSuper
    @Override
    public void onBindViewHolder(VH holder, int position) {
        holder.itemView.setTag(holder);
    }

    public final void selectRange(int from, int to, int min, int max) {
        if (from == to) {
            // Finger is back on the initial item, unselect everything else
            for (int i = min; i <= max; i++) {
                if (i == from) continue;
                setSelected(i, false);
            }
            fireSelectionListener();
            return;
        }

        if (to < from) {
            // When selecting from one to previous items
            for (int i = to; i <= from; i++)
                setSelected(i, true);
            if (min > -1 && min < to) {
                // Unselect items that were selected during this drag but no longer are
                for (int i = min; i < to; i++) {
                    if (i == from) continue;
                    setSelected(i, false);
                }
            }
            if (max > -1) {
                for (int i = from + 1; i <= max; i++)
                    setSelected(i, false);
            }
        } else {
            // When selecting from one to next items
            for (int i = from; i <= to; i++)
                setSelected(i, true);
            if (max > -1 && max > to) {
                // Unselect items that were selected during this drag but no longer are
                for (int i = to + 1; i <= max; i++) {
                    if (i == from) continue;
                    setSelected(i, false);
                }
            }
            if (min > -1) {
                for (int i = min; i < from; i++)
                    setSelected(i, false);
            }
        }
        fireSelectionListener();
    }

    public final void selectAll() {
        int max = getItemCount();
        mSelectedIndices.clear();
        for (int i = 0; i < max; i++) {
            if (isIndexSelectable(i)) {
                mSelectedIndices.add(i);
            }
        }
        notifyDataSetChanged();
        fireSelectionListener();
    }

    public final void clearSelected() {
        mSelectedIndices.clear();
        notifyDataSetChanged();
        fireSelectionListener();
    }

    public final int getSelectedCount() {
        return mSelectedIndices.size();
    }

    public final Integer[] getSelectedIndices() {
        return mSelectedIndices.toArray(new Integer[mSelectedIndices.size()]);
    }

    public final boolean isIndexSelected(int index) {
        return mSelectedIndices.contains(index);
    }

    public interface SelectionListener {
        void onDragSelectionChanged(int count);
    }
}