// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.browser_ui.widget.selectable_list;

import org.chromium.base.ObserverList;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * A generic delegate used to keep track of selected items.
 * @param <E> The type of the selectable items this delegate interacts with.
 */
public class SelectionDelegate<E> {
    // True if the SelectionDelegate should only support a single item being selected at a time.
    private boolean mIsSingleSelection;

    // If true, we can enter the selection mode even though zero items are currently selected.
    // When the number of items drops to zero again, this will automatically turn off.
    private boolean mEnableSelectionForZeroItems;

    /**
     * Observer interface to be notified of selection changes.
     * @param <E> The type of the selectable items this delegate interacts with.
     */
    public interface SelectionObserver<E> {
        /**
         * Called when the set of selected items has changed.
         * @param selectedItems The list of currently selected items. An empty list indicates there
         *                      is no selection.
         */
        void onSelectionStateChange(List<E> selectedItems);
    }

    private Set<E> mSelectedItems = new LinkedHashSet<>();
    private ObserverList<SelectionObserver<E>> mObservers = new ObserverList<>();

    /**
     * Sets the mode of this SelectionDelegate to single-selection.
     */
    public void setSingleSelectionMode() {
        mIsSingleSelection = true;
    }

    /**
     * Enables selection mode even though there are zero items selected.
     * @param enable True, for entering selection mode. False, to turn-off this mode.
     */
    public void setSelectionModeEnabledForZeroItems(boolean enable) {
        mEnableSelectionForZeroItems = enable;
        notifyObservers();
    }

    /**
     * Toggles the selected state for the given item.
     * @param item The item to toggle.
     * @return Whether the item is selected.
     */
    public boolean toggleSelectionForItem(E item) {
        if (mSelectedItems.contains(item)) {
            mSelectedItems.remove(item);
        } else {
            if (mIsSingleSelection) mSelectedItems.clear();
            mSelectedItems.add(item);
        }

        if (mSelectedItems.isEmpty()) mEnableSelectionForZeroItems = false;

        notifyObservers();

        return isItemSelected(item);
    }

    /**
     * Initializes the selected item list with a new set (clears previous selection).
     * @param items The items to set as selected.
     */
    public void setSelectedItems(Set<E> items) {
        mSelectedItems = items;
        notifyObservers();
    }

    /**
     * True if the item is selected. False otherwise.
     * @param item The item.
     * @return Whether the item is selected.
     */
    public boolean isItemSelected(E item) {
        return mSelectedItems.contains(item);
    }

    /**
     * @return Whether any items are selected.
     */
    public boolean isSelectionEnabled() {
        return !mSelectedItems.isEmpty() || mEnableSelectionForZeroItems;
    }

    /**
     * Clears all selected items.
     */
    public void clearSelection() {
        mEnableSelectionForZeroItems = false;
        mSelectedItems.clear();
        notifyObservers();
    }

    /**
     * @return The set of selected items.
     */
    public Set<E> getSelectedItems() {
        return mSelectedItems;
    }

    /**
     * @return The list of selected items.
     */
    public List<E> getSelectedItemsAsList() {
        return new ArrayList<E>(mSelectedItems);
    }

    /**
     * Adds a SelectionObserver to be notified of selection changes.
     * @param observer The SelectionObserver to add.
     */
    public void addObserver(SelectionObserver<E> observer) {
        mObservers.addObserver(observer);
    }

    /**
     * Removes a SelectionObserver.
     * @param observer The SelectionObserver to remove.
     */
    public void removeObserver(SelectionObserver<E> observer) {
        mObservers.removeObserver(observer);
    }

    private void notifyObservers() {
        List<E> selectedItems = getSelectedItemsAsList();
        for (SelectionObserver<E> observer : mObservers) {
            observer.onSelectionStateChange(selectedItems);
        }
    }
}
