//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#ifndef _GuiGameListMenuCtrl_H_
#define _GuiGameListMenuCtrl_H_

#include "gui/core/guiControl.h"
#include "gui/controls/guiBitmapCtrl.h"

class GuiGameListMenuProfile;

/// \class GuiGameListMenuCtrl
/// A base class for cross platform menu controls that are gamepad friendly.
class GuiGameListMenuCtrl : public GuiControl
{
public:
   typedef GuiControl Parent;
   typedef GuiGameListMenuProfile Profile;

protected:
   /// \struct Row
   /// Internal data representation of a single row in the control.
   struct Row
   {
      StringTableEntry mLabel;            ///< Text to display in the row as a label
      StringTableEntry mScriptCallback;   ///< Script callback when row is activated
      StringTableEntry mTooltip;          ///< A descriptive tooltip message for what the row is
      S32 mIconIndex;                     ///< Index of the icon to display on the row (-1 = no icon)
      S32 mHeightPad;                     ///< Extra amount to pad above this row
      bool mUseHighlightIcon;             ///< Toggle the use of the highlight icon
      bool mEnabled;                      ///< If this row is enabled or not (grayed out)

      enum Mode
      {
         Default = 0,
         OptionList,
         Slider,
         Keybind
      };

      Mode mMode;

      //List options
      Vector<StringTableEntry>   mOptions;         ///< Collection of options available to display
      S32                        mSelectedOption;  ///< Index into mOptions pointing at the selected option
      bool                       mWrapOptions;     ///< Determines if options should "wrap around" at the ends

      //Slider option
      F32                        mValue;           ///< When working as a slider, this contains the value
      F32                        mStepSize;        ///< When working as a slider, this is the increment levels in the range
      Point2F                    mRange;           ///< When working as a slider, this sets our min/max range

      //Keybind option
      StringTableEntry           mBitmap;
      GFXTexHandle               mBitmapTex;

      Row() : mLabel(StringTable->EmptyString()), mScriptCallback(StringTable->EmptyString()), mTooltip(StringTable->EmptyString()), mIconIndex(-1), mHeightPad(0), mUseHighlightIcon(false), mEnabled(true),
         mSelectedOption(0), mWrapOptions(false), mMode(Mode::Default), mValue(0), mStepSize(1), mRange(Point2F(0, 1)), mBitmap(StringTable->EmptyString()), mBitmapTex(nullptr)
      {
         VECTOR_SET_ASSOCIATION(mOptions);
      }

      virtual ~Row() {}
   };

public:
   /// \return The index of the highlighted row or NO_ROW if none of the rows
   /// are currently highlighted.
   virtual S32 getHighlighted() const { return mHighlighted; }

   /// \return The index of the selected row or NO_ROW if none of the rows are
   /// currently selected.
   virtual S32 getSelected() const { return mSelected; }

   /// Sets the selected row. Only rows that are enabled can be selected. Input is
   /// clamped to [0, mRows.size())
   ///
   /// \param index The index to set as selected.
   virtual void setSelected(S32 index);

   /// Determines if the specified row is enabled or disabled.
   ///
   /// \param index Index of the row to check.
   /// \return True if the specified row is enabled. False if the row is not
   /// enabled or the given index was not valid.
   virtual bool isRowEnabled(S32 index) const;

   /// Sets a row's enabled status according to the given parameters.
   ///
   /// \param index The row to set the enabled status of.
   /// \param enabled Indicate true to enable the row or false to disable it.
   virtual void setRowEnabled(S32 index, bool enabled);

   /// Gets the label displayed on the specified row.
   ///
   /// \param rowIndex Index of the row to get the label of.
   /// \return The label for the row.
   virtual StringTableEntry getRowLabel(S32 rowIndex) const;

   /// Sets the label on the given row.
   ///
   /// \param rowIndex Index of the row to set the label on.
   /// \param label Text to set as the label of the row.
   virtual void setRowLabel(S32 rowIndex, const char * label);

   /// Adds a row to the control.
   ///
   /// \param label The text to display on the row as a label.
   /// \param callback Name of a script function to use as a callback when this
   /// row is activated.
   /// \param icon [optional] Index of the icon to use as a marker. Default -1
   /// means no icon will be shown on this row.
   /// \param yPad [optional] An extra amount of height padding before the row.
   /// \param enabled [optional] If this row is initially enabled. Default true.
   virtual void addRow(const char* label, const char* callback, S32 icon = -1, S32 yPad = 0, bool useHighlightIcon = true, bool enabled = true, S32 mode = 0, const char* tooltip = "");

   /// Adds a row to the control.
   ///
   /// \param label The text to display on the row as a label.
   /// \param optionsList A tab separated list of options for the control.
   /// \param wrapOptions Specify true to allow options to wrap at the ends or
   /// false to prevent wrapping.
   /// \param callback [optional] Name of a script function to use as a callback
   /// when this row is activated. Default NULL means no callback.
   /// \param icon [optional] Index of the icon to use as a marker. Default -1
   /// means no icon will be shown on this row.
   /// \param yPad [optional] An extra amount of height padding before the row.
   /// \param enabled [optional] If this row is initially enabled. Default true.
   void addRow(const char* label, const char* optionsList, bool wrapOptions, const char* callback, S32 icon, S32 yPad, bool enabled, const char* tooltip = "", const char* defaultValue = "");

   /// Adds a row to the control.
   ///
   /// \param label The text to display on the row as a label.
   /// \param defaultValue A float indicating the slider's default value
   /// \param increments A float indicating the incremental values the slider snaps along between it's range
   /// \param range A Point2F that indicates the minimum and maximum value range
   /// \param callback [optional] Name of a script function to use as a callback
   /// when this row is activated. Default NULL means no callback.
   /// \param icon [optional] Index of the icon to use as a marker. Default -1
   /// means no icon will be shown on this row.
   /// \param yPad [optional] An extra amount of height padding before the row.
   /// \param enabled [optional] If this row is initially enabled. Default true.
   void addRow(const char* label, F32 defaultValue, F32 increments, Point2F range, const char* callback, S32 icon, S32 yPad, bool enabled, const char* tooltip = "");

   void addRow(const char* label, const char* bitmapName, const char* callback, S32 icon, S32 yPad, bool enabled, const char* tooltip);

   //Removes row at the provided index
   void removeRow(const S32& row);

   /// Gets the text for the currently selected option of the given row.
   ///
   /// \param rowIndex Index of the row to get the option from.
   /// \return A string representing the text currently displayed as the selected
   /// option on the given row. If there is no such displayed text then the empty
   /// string is returned.
   StringTableEntry getCurrentOption(S32 rowIndex) const;

   /// Attempts to set the given row to the specified selected option. The option
   /// will only be set if the option exists in the control.
   ///
   /// \param rowIndex Index of the row to set an option on.
   /// \param option The option to be made active.
   /// \return True if the row contained the option and was set, false otherwise.
   bool selectOption(S32 rowIndex, StringTableEntry option);

   /// Sets the list of options on the given row.
   ///
   /// \param rowIndex Index of the row to set options on.
   /// \param optionsList A tab separated list of options for the control.
   void setOptions(S32 rowIndex, const char* optionsList);

   /// Activates the current row. The script callback of  the current row will
   /// be called (if it has one).
   virtual void activateRow();

   /// Gets the number of rows in the control.
   ///
   /// \return The number of rows in this control.
   virtual S32 getRowCount() const { return mRows.size(); }

   /// Gets the value of a row
   ///
   /// \param rowIndex Index of the row to get the value of.
   F32 getValue(S32 rowIndex);

   /// Sets the value of a row
   ///
   /// \param rowIndex Index of the row to set the value of.
   /// \param value The new value to be set.
   void setValue(S32 rowIndex, F32 value);

   /// Gets the tooltip of a row
   ///
   /// \param rowIndex Index of the row to get the tooltip of.
   const char* getTooltip(S32 rowIndex);

   GuiGameListMenuCtrl();
   ~GuiGameListMenuCtrl();

   void onRender(Point2I offset, const RectI &updateRect);

   void onRenderListOption(Row* row, Point2I currentOffset);
   void onRenderSliderOption(Row* row, Point2I currentOffset);

   void onRenderKeybindOption(Row* row, Point2I currentOffset);

   /// Callback when the object is registered with the sim.
   ///
   /// \return True if the profile was successfully added, false otherwise.
   bool onAdd();

   /// Callback when the control wakes up.
   bool onWake();

   /// Callback when a key is pressed.
   ///
   /// \param event The event that triggered this callback.
   bool onKeyDown(const GuiEvent &event);

   /// Callback when a key is repeating.
   ///
   /// \param event The event that triggered this callback.
   bool onKeyRepeat(const GuiEvent &event){ return onKeyDown(event); }

   /// Callback when the mouse button is clicked on the control.
   ///
   /// \param event A reference to the event that triggered the callback.
   void onMouseDown(const GuiEvent &event);

   /// Callback when the mouse is dragged on the control.
   ///
   /// \param event A reference to the event that triggered the callback.
   void onMouseDragged(const GuiEvent &event){ onMouseDown(event); }

   /// Callback when the mouse leaves the control.
   ///
   /// \param event A reference to the event that triggered the callback.
   void onMouseLeave(const GuiEvent &event);

   /// Callback when the mouse is moving over this control
   ///
   /// \param event A reference to the event that triggered the callback.
   void onMouseMove(const GuiEvent &event);

   /// Callback when the mouse button is released.
   ///
   /// \param event A reference to the event that triggered the callback.
   void onMouseUp(const GuiEvent &event);

   virtual bool onInputEvent(const InputEventInfo& event);

   /// Callback when the gamepad axis is activated.
   ///
   /// \param event A reference to the event that triggered the callback.
   virtual bool onGamepadAxisUp(const GuiEvent & event);

   /// Callback when the gamepad axis is activated.
   ///
   /// \param event A reference to the event that triggered the callback.
   virtual bool onGamepadAxisDown(const GuiEvent & event);

   /// Callback when the gamepad axis is activated.
   ///
   /// \param event A reference to the event that triggered the callback.
   virtual bool onGamepadAxisLeft(const GuiEvent& event);

   /// Callback when the gamepad axis is activated.
   ///
   /// \param event A reference to the event that triggered the callback.
   virtual bool onGamepadAxisRight(const GuiEvent& event);

   void clearRows();

   void refresh();

   RectI getRowBounds(S32 rowIndex);

   DECLARE_CONOBJECT(GuiGameListMenuCtrl);
   DECLARE_CATEGORY( "Gui Game" );
   DECLARE_DESCRIPTION( "Base class for cross platform menu controls that are gamepad friendly." );

   /// Initializes fields accessible through the console.
   static void initPersistFields();

   static const S32 NO_ROW          = -1; ///< Indicates a query result of no row found.
   static const S32 NO_ICON         = -1; ///< Indicates a row has no extra icon available
   static const S32 NO_OPTION = -1; ///< Indicates there is no option

protected:
   /// Adds a row to the control.
   ///
   /// \param row A reference to the row object to fill.
   /// \param label The text to display on the row as a label.
   /// \param callback Name of a script function to use as a callback when this
   /// row is activated.
   /// \param icon [optional] Index of the icon to use as a marker. Default -1
   /// means no icon will be shown on this row.
   /// \param yPad [optional] An extra amount of height padding before the row.
   /// \param enabled [optional] If this row is initially enabled. Default true.
   virtual void addRow(Row * row, const char* label, const char* callback, S32 icon, S32 yPad, bool useHighlightIcon, bool enabled, S32 mode = 0, const char* tooltip = "");

   /// Determines if the given index is a valid row index. Any index pointing at
   /// an existing row is valid.
   ///
   /// \param index The index to check for validity.
   /// \return True if the index points at a valid row, false otherwise.
   virtual bool isValidRowIndex(S32 index) const;

   /// Sets the script variable $ThisControl to reflect this control.
   virtual void setThisControl();

   /// Called to implement debug rendering which displays colored lines to
   /// provide visual feedback on extents and hit zones.
   virtual void onDebugRender(Point2I offset);

   /// Looks up the row having a hit area at the given global point.
   ///
   /// \param globalPoint The point we want to check for hitting a row.
   /// \return The index of the hit row or NO_ROW if no row was hit.
   virtual S32 getRow(Point2I globalPoint);

   /// Checks to make sure our control has a profile of the correct type.
   ///
   /// \return True if the profile is of type GuiGameListMenuProfile or false if
   /// the profile is of any other type.
   virtual bool hasValidProfile() const;

   /// Enforces the validity of the fields on this control and its profile (if
   /// the profile is valid, see: hasValidProfile).
   virtual void enforceConstraints();

   /// @name Callbacks
   /// @{
   DECLARE_CALLBACK( void, onChange, () );

   DECLARE_CALLBACK(void, onInputEvent,  (const char* device, const char* action, bool state));

   DECLARE_CALLBACK(void, onAxisEvent, (const char* device, const char* action, F32 axisValue));
   /// @}

   /// Evaluates some script. If the command is empty then nothing is evaluated.
   ///
   /// \param command The script to evaluate.
   void doScriptCommand(StringTableEntry command);

   StringTableEntry  mCallbackOnA;  ///< Script callback when the 'A' button is pressed
   StringTableEntry  mCallbackOnB;  ///< Script callback when the 'B' button is pressed
   StringTableEntry  mCallbackOnX;  ///< Script callback when the 'X' button is pressed
   StringTableEntry  mCallbackOnY;  ///< Script callback when the 'Y' button is pressed

   bool              mDebugRender;  ///< Determines when to show debug render lines
   Vector<Row *>     mRows;         ///< Holds data wrappers on all the rows we have

private:
   /// Performs a click on the current option row. The x position is used to
   /// determine if the left or right arrow were clicked. If one was clicked, the
   /// option will be changed. If neither was clicked, the option is unaffected.
   /// This method should only be called when there is an actively selected row.
   ///
   /// \param row The row to perform the click on.
   /// \param xPos The x position of the the click, relative to the control.
   void clickOption(Row* row, S32 xPos);

   /// Changes the option on the currently selected row. If there is no row
   /// selected, this method does nothing.
   ///
   /// \param delta The amount to change the option selection by. Typically this
   /// will be 1 or -1.
   void changeOption(S32 delta);

   /// Changes the option on the given row.
   ///
   /// \param row The row to change the option on.
   /// \param delta The amount to change the option selection by. Typically this
   /// will be 1 or -1.
   void changeOption(Row* row, S32 delta);

   /// Performs a click on the current slider row. The x position is used to
   /// determine if the left or right arrow were clicked, or if it landed somewhere on the sliderbar.
   /// If one was clicked, the option will be changed. If neither was clicked, the option is unaffected.
   /// This method should only be called when there is an actively selected row.
   ///
   /// \param row The row to perform the click on.
   /// \param xPos The x position of the the click, relative to the control.
   void clickSlider(Row* row, S32 xPos);

   void clickKeybind(Row* row, S32 xPos);

private:
   /// Recalculates the height of this control based on the stored row height and
   /// and padding on the rows.
   virtual Point2I getMinExtent() const;

   /// Makes sure the height will allow all rows to be displayed without being
   /// truncated.
   void updateHeight();

   /// Sets the first enabled row as selected. If there are no enabled rows then
   /// selected will be set to NO_ROW.
   void selectFirstEnabledRow();

   /// Changes the currently selected row.
   ///
   /// \param delta The amount to change the row selection by. Typically this will
   /// be 1 or -1.
   void changeRow(S32 delta);

   S32      mSelected;     ///< index of the currently selected row
   S32      mHighlighted;  ///< index of the currently highlighted row

   bool     mCallbackOnInputs;
};

/// \class GuiGameListMenuProfile
/// A gui profile with additional fields specific to GuiGameListMenuCtrl.
class GuiGameListMenuProfile : public GuiControlProfile
{
   typedef GuiControlProfile Parent;

public:
   /// Enforces range constraints on all required fields.
   virtual void enforceConstraints();

   /// Get the height of rows in this profile. All rows are considered to be the
   /// same base height. Rows can have an extra amount of y padding defined when
   /// they are added to the control.
   ///
   /// \return The height of rows in this profile.
   S32 getRowHeight() { return (mRowSize.y) ? mRowSize.y : getBitmapArrayRect(TEX_NORMAL).extent.y; }

   /// Get the width of rows in this profile. All rows are considered to be the
   /// same width.
   ///
   /// \return The width of rows in this profile.
   S32 getRowWidth() { return (mRowSize.x) ? mRowSize.x : getBitmapArrayRect(TEX_NORMAL).extent.x; }

   /// Row scale is the ratio between the defined row size and the raw size of
   /// the bitmap.
   ///
   /// \return The row scale.
   const Point2F & getRowScale() const { return mRowScale; }

   /// Gets the extent of icons for this profile. If there are no icons you will
   /// get a point of (0, 0);
   ///
   /// \return The extent of icons or (0, 0) if there aren't any.
   Point2I getIconExtent();

   /// Gets the extent of arrows for this profile. If there are no arrows you
   /// will get a point of (0, 0).
   ///
   /// \return The extent of icons or (0, 0) if there aren't any.
   Point2I getArrowExtent();

   /// Gets the extent of the defined hit area for this profile. If the hit area
   /// is not defined then it defaults to the full size of a row.
   ///
   /// \return The extents of the defined hit area or the full size of the row.
   Point2I getHitAreaExtent();

   /// Determines if this profile has textures for the left and right arrows.
   ///
   /// \return True if the profile's bitmap has textures for the arrows, false
   /// otherwise.
   bool hasArrows(){ return (! getBitmapArrayRect(TEX_FIRST_ARROW).extent.isZero()); }

   /// Callback when the object is registered with the sim.
   ///
   /// \return True if the profile was successfully added, false otherwise.
   bool onAdd();

   Point2I  mHitAreaUpperLeft;   ///< Offset for the upper left corner of the hit area
   Point2I  mHitAreaLowerRight;  ///< Offset for the lower right corner of the hit area
   Point2I  mIconOffset;         ///< Offset for a row's extra icon
   Point2I  mRowSize;            ///< The base size of a row

   S32   mColumnSplit;  ///< Absolute position of the split between columns
   S32   mRightPad;     ///< Extra padding between the right arrow and the hit area

   GuiGameListMenuProfile();

   DECLARE_CONOBJECT(GuiGameListMenuProfile);

   /// Initializes fields accessible through the console.
   static void initPersistFields();
   
   enum 
   {
      TEX_NORMAL      = 0,  ///< texture index for a normal, unselected row
      TEX_SELECTED    = 1,  ///< texture index for a selected row
      TEX_HIGHLIGHT   = 2,  ///< texture index for a highlighted row (moused over, not selected)
      TEX_DISABLED    = 3,  ///< texture index for a disabled row
      TEX_L_ARROW_OFF = 4,  ///< texture index for the left arrow of an unselected row
      TEX_L_ARROW_ON  = 5,  ///< texture index for the left arrow of a selected row
      TEX_R_ARROW_OFF = 6,  ///< texture index for the right arrow of an unselected row
      TEX_R_ARROW_ON  = 7,  ///< texture index for the right arrow of a selected row

      TEX_FIRST_ARROW = 4,  ///< texture index for the first arrow
      TEX_FIRST_ICON  = 8,  ///< texture index for the first row marker icon
   };

private:
   Point2F  mRowScale;           ///< Ratio of row size to actual bitmap size
};

#endif
