/**
 * @file llflatlistview.h
 * @brief LLFlatListView base class and extension to support messages for several cases of an empty list.
 *
 * $LicenseInfo:firstyear=2009&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#ifndef LL_LLFLATLISTVIEW_H
#define LL_LLFLATLISTVIEW_H

#include "llpanel.h"
#include "llscrollcontainer.h"
#include "lltextbox.h"


/**
 * LLFlatListView represents a flat list ui control that operates on items in a form of LLPanel's.
 * LLSD can be associated with each added item, it can keep data from an item in digested form.
 * Associated LLSD's can be of any type (singular, a map etc.).
 * Items (LLPanel's subclasses) can be of different height.
 * The list is LLPanel created in itself and grows in height while new items are added.
 *
 * The control can manage selection of its items when the flag "allow_select" is set. Also ability to select
 * multiple items (by using CTRL) is enabled through setting the flag "multi_select" - if selection is not allowed that flag
 * is ignored. The option "keep_one_selected" forces at least one item to be selected at any time (only for mouse events on items)
 * since any item of the list was selected.
 *
 * Examples of using this control are presented in Picks panel (My Profile and Profile View), where this control is used to
 * manage the list of pick items.
 *
 * ASSUMPTIONS AND STUFF
 * - NULL pointers and undefined LLSD's are not accepted by any method of this class unless specified otherwise
 * - Order of returned selected items are not guaranteed
 * - The control assumes that all items being added are unique.
 */
class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler
{
    LOG_CLASS(LLFlatListView);
public:

    /**
     * Abstract comparator for comparing flat list items in a form of LLPanel
     */
    class ItemComparator
    {
    public:
        ItemComparator() {};
        virtual ~ItemComparator() {};

        /** Returns true if item1 < item2, false otherwise */
        virtual bool compare(const LLPanel* item1, const LLPanel* item2) const = 0;
    };

    /**
     * Represents reverse comparator which acts as a decorator for a comparator that need to be reversed
     */
    class ItemReverseComparator : public ItemComparator
    {
    public:
        ItemReverseComparator(const ItemComparator& comparator) : mComparator(comparator) {};
        virtual ~ItemReverseComparator() {};

        virtual bool compare(const LLPanel* item1, const LLPanel* item2) const
        {
            return mComparator.compare(item2, item1);
        }

    private:
        const ItemComparator& mComparator;
    };


    struct Params : public LLInitParam::Block<Params, LLScrollContainer::Params>
    {
        /** turning on/off selection support */
        Optional<bool> allow_select;

        /** turning on/off multiple selection (works while clicking and holding CTRL)*/
        Optional<bool> multi_select;

        /** don't allow to deselect all selected items (for mouse events on items only) */
        Optional<bool> keep_one_selected;

        /** try to keep selection visible after reshape */
        Optional<bool> keep_selection_visible_on_reshape;

        /** padding between items */
        Optional<U32> item_pad;

        /** textbox with info message when list is empty*/
        Optional<LLTextBox::Params> no_items_text;

        Params();
    };

    // disable traversal when finding widget to hand focus off to
    /*virtual*/ bool canFocusChildren() const { return false; }

    /**
     * Connects callback to signal called when Return key is pressed.
     */
    boost::signals2::connection setReturnCallback( const commit_signal_t::slot_type& cb ) { return mOnReturnSignal.connect(cb); }

    /** Overridden LLPanel's reshape, height is ignored, the list sets its height to accommodate all items */
    virtual void reshape(S32 width, S32 height, bool called_from_parent  = true);

    /** Returns full rect of child panel */
    const LLRect& getItemsRect() const;

    LLRect getRequiredRect() { return getItemsRect(); }

    /** Returns distance between items */
    const S32 getItemsPad() { return mItemPad; }

    /**
     * Adds and item and LLSD value associated with it to the list at specified position
     * @return true if the item was added, false otherwise
     */
    virtual bool addItem(LLPanel * item, const LLSD& value = LLUUID::null, EAddPosition pos = ADD_BOTTOM, bool rearrange = true);

    /**
     * Insert item_to_add along with associated value to the list right after the after_item.
     * @return true if the item was successfully added, false otherwise
     */
    virtual bool insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, const LLSD& value = LLUUID::null);

    /**
     * Remove specified item
     * @return true if the item was removed, false otherwise
     */
    virtual bool removeItem(LLPanel* item, bool rearrange = true);

    /**
     * Remove an item specified by value
     * @return true if the item was removed, false otherwise
     */
    virtual bool removeItemByValue(const LLSD& value, bool rearrange = true);

    /**
     * Remove an item specified by uuid
     * @return true if the item was removed, false otherwise
     */
    virtual bool removeItemByUUID(const LLUUID& uuid, bool rearrange = true);

    /**
     * Get an item by value
     * @return the item as LLPanel if associated with value, NULL otherwise
     */
    virtual LLPanel* getItemByValue(const LLSD& value) const;

    template<class T>
    T* getTypedItemByValue(const LLSD& value) const
    {
        return dynamic_cast<T*>(getItemByValue(value));
    }

    /**
     * Select or deselect specified item based on select
     * @return true if succeed, false otherwise
     */
    virtual bool selectItem(LLPanel* item, bool select = true);

    /**
     * Select or deselect an item by associated value based on select
     * @return true if succeed, false otherwise
     */
    virtual bool selectItemByValue(const LLSD& value, bool select = true);

    /**
     * Select or deselect an item by associated uuid based on select
     * @return true if succeed, false otherwise
     */
    virtual bool selectItemByUUID(const LLUUID& uuid, bool select = true);

    /**
     * Get all panels stored in the list.
     */
    virtual void getItems(std::vector<LLPanel*>& items) const;

    /**
     * Get all items values.
     */
    virtual void getValues(std::vector<LLSD>& values) const;

    /**
     * Get LLSD associated with the first selected item
     */
    virtual LLSD getSelectedValue() const;

    /**
     * Get LLSD's associated with selected items.
     * @param selected_values std::vector being populated with LLSD associated with selected items
     */
    virtual void getSelectedValues(std::vector<LLSD>& selected_values) const;


    /**
     * Get LLUUID associated with selected item
     * @return LLUUID if such was associated with selected item
     */
    virtual LLUUID getSelectedUUID() const;

    /**
     * Get LLUUIDs associated with selected items
     * @param selected_uuids An std::vector being populated with LLUUIDs associated with selected items
     */
    virtual void getSelectedUUIDs(uuid_vec_t& selected_uuids) const;

    /** Get the top selected item */
    virtual LLPanel* getSelectedItem() const;

    /**
     * Get selected items
     * @param selected_items An std::vector being populated with pointers to selected items
     */
    virtual void getSelectedItems(std::vector<LLPanel*>& selected_items) const;


    /**
     * Resets selection of items.
     *
     * It calls onCommit callback if setCommitOnSelectionChange(bool b) was called with "true"
     * argument for current Flat List.
     * @param no_commit_on_deselection - if true onCommit callback will not be called
     */
    virtual void resetSelection(bool no_commit_on_deselection = false);

    /**
     * Sets comment text which will be shown in the list is it is empty.
     *
     * Textbox to hold passed text is created while this method is called at the first time.
     *
     * @param comment_text - string to be shown as a comment.
     */
    void setNoItemsCommentText( const std::string& comment_text);

    /** Turn on/off multiple selection support */
    void setAllowMultipleSelection(bool allow) { mMultipleSelection = allow; }

    /** Turn on/off selection support */
    void setAllowSelection(bool can_select) { mAllowSelection = can_select; }

    /** Sets flag whether onCommit should be fired if selection was changed */
    // FIXME: this should really be a separate signal, since "Commit" implies explicit user action, and selection changes can happen more indirectly.
    void setCommitOnSelectionChange(bool b)     { mCommitOnSelectionChange = b; }

    /** Get number of selected items in the list */
    U32 numSelected() const {return mSelectedItemPairs.size(); }

    /** Get number of (visible) items in the list */
    U32 size(const bool only_visible_items = true) const;

    /** Removes all items from the list */
    virtual void clear();

    /**
     * Removes all items that can be detached from the list but doesn't destroy
     * them, caller responsible to manage items after they are detached.
     * Detachable item should accept "detach" action via notify() method,
     * where it disconnect all callbacks, does other valuable routines and
     * return 1.
     */
    void detachItems(std::vector<LLPanel*>& detached_items);

    /**
     * Set comparator to use for future sorts.
     *
     * This class does NOT manage lifetime of the comparator
     * but assumes that the comparator is always alive.
     */
    void setComparator(const ItemComparator* comp) { mItemComparator = comp; }
    void sort();

    bool updateValue(const LLSD& old_value, const LLSD& new_value);

    void scrollToShowFirstSelectedItem();

    void selectFirstItem    ();
    void selectLastItem     ();

    virtual S32 notify(const LLSD& info) ;

    virtual ~LLFlatListView();

protected:

    /** Pairs LLpanel representing a single item LLPanel and LLSD associated with it */
    typedef std::pair<LLPanel*, LLSD> item_pair_t;

    typedef std::list<item_pair_t*> pairs_list_t;
    typedef pairs_list_t::iterator pairs_iterator_t;
    typedef pairs_list_t::const_iterator pairs_const_iterator_t;

    /** An adapter for a ItemComparator */
    struct ComparatorAdaptor
    {
        ComparatorAdaptor(const ItemComparator& comparator) : mComparator(comparator) {};

        bool operator()(const item_pair_t* item_pair1, const item_pair_t* item_pair2)
        {
            return mComparator.compare(item_pair1->first, item_pair2->first);
        }

        const ItemComparator& mComparator;
    };


    friend class LLUICtrlFactory;
    LLFlatListView(const LLFlatListView::Params& p);

    /** Manage selection on mouse events */
    void onItemMouseClick(item_pair_t* item_pair, MASK mask);

    void onItemRightMouseClick(item_pair_t* item_pair, MASK mask);

    /**
     *  Updates position of items.
     *  It does not take into account invisible items.
     */
    virtual void rearrangeItems();

    virtual item_pair_t* getItemPair(LLPanel* item) const;

    virtual item_pair_t* getItemPair(const LLSD& value) const;

    virtual bool selectItemPair(item_pair_t* item_pair, bool select);

    virtual bool selectNextItemPair(bool is_up_direction, bool reset_selection);

    virtual bool canSelectAll() const;
    virtual void selectAll();

    virtual bool isSelected(item_pair_t* item_pair) const;

    virtual bool removeItemPair(item_pair_t* item_pair, bool rearrange);

    bool addItemPairs(pairs_list_t panel_list, bool rearrange = true);

    /**
     * Notify parent about changed size of internal controls with "size_changes" action
     *
     * Size includes Items Rect width and either Items Rect height or comment text height.
     * Comment text height is included if comment text is set and visible.
     * List border size is also included into notified size.
     */
    void notifyParentItemsRectChanged();

    virtual bool handleKeyHere(KEY key, MASK mask);

    virtual bool postBuild();

    virtual void onFocusReceived();

    virtual void onFocusLost();

    virtual void draw();

    LLRect getLastSelectedItemRect();

    void ensureSelectedVisible();

    const pairs_list_t& getItemPairs() { return mItemPairs; }

private:

    void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;}

    void setNoItemsCommentVisible(bool visible) const;

protected:

    /** Comparator to use when sorting the list. */
    const ItemComparator* mItemComparator;


private:

    LLPanel* mItemsPanel;

    S32 mItemsNoScrollWidth;

    S32 mBorderThickness;

    /** Items padding */
    S32 mItemPad;

    /** Selection support flag */
    bool mAllowSelection;

    /** Multiselection support flag, ignored if selection is not supported */
    bool mMultipleSelection;

    /**
     * Flag specified whether onCommit be called if selection is changed in the list.
     *
     * Can be ignored in the resetSelection() method.
     * @see resetSelection()
     */
    bool mCommitOnSelectionChange;

    bool mKeepOneItemSelected;

    bool mIsConsecutiveSelection;

    bool mKeepSelectionVisibleOnReshape;

    /** All pairs of the list */
    pairs_list_t mItemPairs;

    /** Selected pairs for faster access */
    pairs_list_t mSelectedItemPairs;

    /**
     * Rectangle contained previous size of items parent notified last time.
     * Is used to reduce amount of parentNotify() calls if size was not changed.
     */
    LLRect mPrevNotifyParentRect;

    LLTextBox* mNoItemsCommentTextbox;

    LLViewBorder* mSelectedItemsBorder;

    commit_signal_t mOnReturnSignal;
};

/**
 * Extends LLFlatListView functionality to show different messages when there are no items in the
 * list depend on whether they are filtered or not.
 *
 * Class provides one message per case of empty list.
 * It also provides protected updateNoItemsMessage() method to be called each time when derived list
 * is changed to update base mNoItemsCommentTextbox value.
 *
 * It is implemented to avoid duplication of this functionality in concrete implementations of the
 * lists. It is intended to be used as a base class for lists which should support two different
 * messages for empty state. Can be improved to support more than two messages via state-to-message map.
 */
class LLFlatListViewEx : public LLFlatListView
{
public:
    LOG_CLASS(LLFlatListViewEx);

    struct Params : public LLInitParam::Block<Params, LLFlatListView::Params>
    {
        /**
         * Contains a message for empty list when it does not contain any items at all.
         */
        Optional<std::string>   no_items_msg;

        /**
         * Contains a message for empty list when its items are removed by filtering.
         */
        Optional<std::string>   no_filtered_items_msg;
        Params();
    };

    // *WORKAROUND: two methods to overload appropriate Params due to localization issue:
    // no_items_msg & no_filtered_items_msg attributes are not defined as translatable in VLT. See EXT-5931
    void setNoItemsMsg(const std::string& msg) { mNoItemsMsg = msg; }
    void setNoFilteredItemsMsg(const std::string& msg) { mNoFilteredItemsMsg = msg; }

    bool getForceShowingUnmatchedItems();

    void setForceShowingUnmatchedItems(bool show);

    /**
     * Sets up new filter string and filters the list.
     */
    void setFilterSubString(const std::string& filter_str, bool notify_parent);
    std::string getFilterSubString() { return mFilterSubString; }

    /**
     * Filters the list, rearranges and notifies parent about shape changes.
     * Derived classes may want to overload rearrangeItems() to exclude repeated separators after filtration.
     */
    void filterItems(bool re_sort, bool notify_parent);

    /**
     * Returns true if last call of filterItems() found at least one matching item
     */
    bool hasMatchedItems();

protected:
    LLFlatListViewEx(const Params& p);

    /**
     * Applies a message for empty list depend on passed argument.
     *
     * @param filter_string - if is not empty, message for filtered items will be set, otherwise for
     * completely empty list. Value of filter string will be passed as search_term in SLURL.
     */
    void updateNoItemsMessage(const std::string& filter_string);

    /**
    * Applies visibility acording to action and LLFlatListView settings.
    *
    * @param item - item we are changing
    * @param item - action - parameters to determin visibility from
    */
    bool updateItemVisibility(LLPanel* item, const LLSD &action);

private:
    std::string mNoFilteredItemsMsg;
    std::string mNoItemsMsg;
    std::string mFilterSubString;
    /**
     * Show list items that don't match current filter
     */
    bool mForceShowingUnmatchedItems;
    /**
     * True if last call of filterItems() found at least one matching item
     */
    bool mHasMatchedItems;
};

#endif