/**
  ******************************************************************************
  * This file is part of the TouchGFX 4.16.1 distribution.
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

/**
 * @file touchgfx/containers/scrollers/DrawableList.hpp
 *
 * Declares the touchgfx::DrawableListItemsInterface (abstract), touchgfx::DrawableListItems
 * and touchgfx::DrawableList classes.
 */
#ifndef DRAWABLELIST_HPP
#define DRAWABLELIST_HPP

#include <touchgfx/containers/Container.hpp>

namespace touchgfx
{
/**
 * A drawable list items interface. Used to pass the allocated array of drawable elements to
 * ScrollList::setDrawables(), ScrollWheel::setDrawables() or
 * ScrollWheelWithSelectionStyle::setDrawables(). Provides easy access to each element
 * in the array as well as the size of the array.
 *
 * @see ScrollList::setDrawables, ScrollWheel::setDrawables,
 *      ScrollWheelWithSelectionStyle::setDrawables
 */
class DrawableListItemsInterface
{
public:
    /** Finalizes an instance of the DrawableListItemsInterface class. */
    virtual ~DrawableListItemsInterface()
    {
    }

    /**
     * Gets a drawable at a given index.
     *
     * @param  index Zero-based index of the drawable.
     *
     * @return Null if it fails, else the drawable.
     */
    virtual Drawable* getDrawable(int16_t index) = 0;

    /**
     * Gets number of drawables.
     *
     * @return The number of drawables.
     */
    virtual int16_t getNumberOfDrawables() = 0;
};

/**
 * An array of drawables used by DrawableList. This class is used to ease the setup of
 * a callback function to get access to a specific drawable in the array.
 *
 * Example usage:
 * @code
 *       static const int NUMBER_OF_DRAWABLES = 5;
 *       DrawableListItems<TextAreaWithOneWildcardContainer, NUMBER_OF_DRAWABLES> menuItems;
 * @endcode
 *
 * @tparam TYPE Type of the drawables. Can be a simple drawable, such as Image or a more
 *              complex container.
 * @tparam SIZE Size of the array. This is the number of drawables to allocate and should be
 *              all visible drawables on the screen at any given time.
 */
template <class TYPE, int SIZE>
class DrawableListItems : public DrawableListItemsInterface
{
public:
    virtual Drawable* getDrawable(int16_t index)
    {
        assert(index >= 0 && index < SIZE);
        return &element[index];
    }

    /**
     * Array indexer operator.
     *
     * @param  index Zero-based index of elements to access.
     *
     * @return The indexed value.
     */
    TYPE& operator[](int index)
    {
        assert(index >= 0 && index < SIZE);
        return element[index];
    }

    virtual int16_t getNumberOfDrawables()
    {
        return SIZE;
    }

    TYPE element[SIZE]; ///< The array of drawables
};

/**
 * A container able to display many items using only a few drawables. This is done by only
 * having drawables for visible items, and populating these drawables with new content
 * when each of these become visible.
 *
 * This means that all drawables must have an identical structure in some way, for
 * example an Image or a Container with a button and a text.
 */
class DrawableList : public Container
{
public:
    DrawableList();

    /**
     * @copydoc Container::setWidth
     *
     * @note If the list is vertical, the width is also propagated to all drawables in the list.
     */
    virtual void setWidth(int16_t width);

    /**
     * @copydoc Container::setHeight
     *
     * @note If the list is horizontal, the height is also propagated to all drawables in the list.
     */
    virtual void setHeight(int16_t height);

    /**
     * Sets a horizontal or vertical layout. If parameter horizontal is set true, all
     * drawables are arranged side by side. If horizontal is set false, the drawables are
     * arranged above and below each other (vertically).
     *
     * @param  horizontal True to align drawables horizontal, false to align drawables
     *                    vertically.
     *
     * @see ScrollBase::setHorizontal, getHorizontal
     *
     * @note Default value is false, i.e. vertical layout.
     */
    virtual void setHorizontal(bool horizontal);

    /**
     * Gets the orientation of the drawables, previously set using setHorizontal().
     *
     * @return True if it horizontal, false if it is vertical.
     *
     * @see ScrollBase::getHorizontal, setHorizontal
     */
    virtual bool getHorizontal() const;

    /**
     * Sets whether the list is circular (infinite) or not. A circular list is a list where
     * the first drawable re-appears after the last item in the list - and the last item in
     * the list appears before the first item in the list.
     *
     * @param  circular True if the list should be circular, false if the list should not be
     *                  circular.
     *
     * @see ScrollBase::setCircular, getCircular
     */
    virtual void setCircular(bool circular);

    /**
     * Gets the circular setting, previously set using setCircular().
     *
     * @return True if the list is circular (infinite), false if the list is not circular
     *         (finite).
     *
     * @see ScrollBase::getCircular, setCircular
     */
    virtual bool getCircular() const;

    /**
     * Sets drawables size. The drawable is is the size of each drawable in the list in the
     * set direction of the list (this is enforced by the DrawableList class). The specified
     * margin is added above and below each item for spacing. The entire size of an item is
     * thus size + 2 * spacing.
     *
     * For a horizontal list each element will be \a drawableSize high and have the same
     * width as set using setWidth(). For a vertical list each element will be \a
     * drawableSize wide and have the same height as set using setHeight().
     *
     * @param  drawableSize   The size of the drawable.
     * @param  drawableMargin The margin around drawables (margin before and margin after).
     *
     * @see setWidth, setHeight, setHorizontal
     */
    void setDrawableSize(int16_t drawableSize, int16_t drawableMargin);

    /**
     * Gets size of each item. This equals the drawable size plus the drawable margin as set
     * in setDrawables(). Equals getDrawableSize() + 2 * getDrawableMargin().
     *
     * @return The item size.
     *
     * @see setDrawables, setDrawableSize, getDrawableMargin
     *
     * @note Not the same as getDrawableSize().
     */
    virtual int16_t getItemSize() const;

    /**
     * Gets drawable size as set by setDrawables.
     *
     * @return The drawable size.
     *
     * @see setDrawables
     */
    virtual int16_t getDrawableSize() const;

    /**
     * Gets drawable margin.
     *
     * Gets drawable margin as set by setDrawables.
     *
     * @return The drawable margin.
     */
    virtual int16_t getDrawableMargin() const;

    /**
     * Sets the drawables parameters. These parameters are \li The access class to the array
     * of drawables \li The offset in the drawableListItems array to start using drawable
     * and \li Callback to update the contents of a drawable.
     *
     * @param [in] drawableListItems       Number of drawables allocated.
     * @param [in] drawableItemIndexOffset The offset of the drawable item.
     * @param [in] updateDrawableCallback  A callback to update the contents of a drawable.
     *
     * @see getRequiredNumberOfDrawables
     */
    virtual void setDrawables(DrawableListItemsInterface& drawableListItems,
                              int16_t drawableItemIndexOffset,
                              GenericCallback<DrawableListItemsInterface*, int16_t, int16_t>& updateDrawableCallback);

    /**
     * Gets number of drawables based on the size of each item and the size of the widget.
     *
     * @return The number of drawables.
     *
     * @see setDrawables
     */
    int16_t getNumberOfDrawables() const;

    /**
     * Sets number of items in the list. This forces all drawables to be updated to ensure
     * that the content is correct.
     *
     * @param  numberOfItems Number of items.
     *
     * @note The DrawableList is refreshed to reflect the change.
     */
    void setNumberOfItems(int16_t numberOfItems);

    /**
     * Gets number of items in the DrawableList, as previously set using setNumberOfItems().
     *
     * @return The number of items.
     *
     * @see setNumberOfItems
     */
    int16_t getNumberOfItems() const;

    /**
     * Gets required number of drawables. After setting up the DrawableList it is possible
     * to request how many drawables are needed to ensure that the list can always be drawn
     * properly. If the DrawableList has been setup with fewer Drawables than the required
     * number of drawables, part of the lower part of the DrawableList will look wrong.
     *
     * The number of required drawables depend on the size of the widget and the size of the
     * drawables and the margin around drawables. If there are fewer drawables than required,
     * the widget will not display correctly. If there are more drawables than required,
     * some will be left unused.
     *
     * @return The required number of drawables.
     *
     * @see setDrawables
     */
    int16_t getRequiredNumberOfDrawables() const;

    /**
     * Sets virtual coordinate. Does not move to the given coordinate, but places the
     * drawables and fill correct content into the drawables to give the impression that
     * everything has been scrolled to the given coordinate.
     *
     * Setting a value of 0 means that item 0 is at the start of the DrawableList. Setting a
     * value of "-getItemSize()" places item 0 outside the start of the DrawableList and
     * item 1 at the start of it.
     *
     * Items that are completely outside of view, will be updated with new content using the
     * provided callback from setDrawables(). Care is taken to not fill drawables more than
     * strictly required.
     *
     * @param  ofs The virtual coordinate.
     *
     * @see getOffset, setDrawables
     */
    void setOffset(int32_t ofs);

    /**
     * Gets offset, as previously set using setOffset().
     *
     * @return The virtual offset.
     *
     * @see setOffset
     */
    int32_t getOffset() const;

    /**
     * Gets item stored in a given Drawable.
     *
     * @param  drawableIndex Zero-based index of the drawable.
     *
     * @return The item index.
     */
    int16_t getItemIndex(int16_t drawableIndex) const;

    /**
     * Gets drawable indices. Useful when the number of items is smaller than the number of
     * drawables as the same item might be in more than one drawable on the screen (if the
     * DrawableList is circular). The passed array will be filled with the drawable indices
     * and the number of indices found is returned.
     *
     * @param       itemIndex          Zero-based index of the item.
     * @param [out] drawableIndexArray Array where the drawable indices are stored.
     * @param       arraySize          Size of drawable array.
     *
     * @return The number of drawable indices found.
     *
     * @see getItemIndex, setCircular, getDrawableIndex
     */
    int16_t getDrawableIndices(int16_t itemIndex, int16_t* drawableIndexArray, int16_t arraySize) const;

    /**
     * Gets the drawable index of an item. If the number of items is smaller than the number
     * of drawables and the DrawableList is circular, the same item can be in more than one
     * drawable. In that case, calling this function again with the previously returned
     * index as second parameter, the index of the next drawable containing the item will be
     * returned.
     *
     * @param  itemIndex         Index of the item.
     * @param  prevDrawableIndex (Optional) Index of the previous drawable. If given, search
     *                           starts after this index.
     *
     * @return The first drawable index with the given item. Returns -1 if the item is not
     *         in a drawable.
     *
     * @see getDrawableIndices
     */
    int16_t getDrawableIndex(int16_t itemIndex, int16_t prevDrawableIndex = -1) const;

    /**
     * Refresh drawables. Useful to call if the number or items, their size or other
     * properties have changed.
     */
    void refreshDrawables();

    /**
     * Item changed.
     *
     * Item changed and drawables containing this item must be updated. This function can be
     * called when an item has changed and needs to be updated on screen. If the given item
     * is displayed on screen, possible more than once for cyclic lists, each drawable is
     * request to refresh its content to reflect the new value.
     *
     * @param  itemIndex Zero-based index of the item.
     */
    void itemChanged(int16_t itemIndex);

private:
    bool isHorizontal;         ///< True if list is horizontal, false if not
    bool isCircular;           ///< True if list is circular, false if not
    int32_t offset;            ///< The offset of item 0
    int16_t itemSize;          ///< Size of each item (including margin)
    int16_t itemMargin;        ///< The margin around each item (included in itemSize)
    int16_t numItems;          ///< Number of items
    int16_t numDrawables;      ///< Number of drawables
    int16_t firstItem;         ///< The first visible item
    int16_t firstDrawable;     ///< The first visible drawable
    bool drawablesInitialized; ///< True if all drawables initialized

    int16_t firstDrawableIndex;                                                     ///< The offset when accessing DrawableListItems
    DrawableListItemsInterface* drawableItems;                                      ///< The drawable items
    GenericCallback<DrawableListItemsInterface*, int16_t, int16_t>* updateDrawable; ///< The update drawable callback
};

} // namespace touchgfx

#endif // DRAWABLELIST_HPP
