/****************************************************************************
 * apps/graphics/nxwidgets/include/cbuttonarray.cxx
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <stdint.h>
#include <stdbool.h>

#include <nuttx/nx/nxglib.h>

#include "graphics/nxwidgets/cbuttonarray.hxx"
#include "graphics/nxwidgets/cgraphicsport.hxx"
#include "graphics/nxwidgets/cwidgetstyle.hxx"

/****************************************************************************
 * Pre-Processor Definitions
 ****************************************************************************/

/****************************************************************************
 * CButtonArray Method Implementations
 ****************************************************************************/

using namespace NXWidgets;

/**
 * Constructor for an array of buttons.
 *
 * @param pWidgetControl The widget control for the display.
 * @param x The x coordinate of the button array, relative to its parent.
 * @param y The y coordinate of the button array, relative to its parent.
 * @param buttonColumns The number of buttons in one row of the button array
 * @param buttonRows The number of buttons in one column of the button array
 * @param buttonWidth The width of one button
 * @param buttonHeight The height of one button
 * @param style The style that the button should use.  If this is not
 *        specified, the button will use the global default widget
 *        style.
 */

CButtonArray::CButtonArray(CWidgetControl *pWidgetControl,
                           nxgl_coord_t x, nxgl_coord_t y,
                           uint8_t buttonColumns, uint8_t buttonRows,
                           nxgl_coord_t buttonWidth, nxgl_coord_t buttonHeight,
                           CWidgetStyle *style)
: CNxWidget(pWidgetControl, x, y,
            buttonColumns * buttonWidth + 2, buttonRows * buttonHeight + 2,
            0, style)
{
  // Save configuration

  m_buttonRows        = buttonRows;
  m_buttonColumns     = buttonColumns;
  m_buttonWidth       = buttonWidth;
  m_buttonHeight      = buttonHeight;

  // Make sure the the positional data is valid.

  m_clickX            = x;
  m_clickY            = y;
  m_cursorColumn      = 0;
  m_cursorRow         = 0;

  // Default behavior is to redraw the entire button array

  m_redrawButton      = false;

  // No highlighted buttons

  m_cursorOn          = false;
  m_cursorChange      = false;

  // At present, There is no border on the array itself

  m_flags.borderless  = true;

  // Make sure that the full size of the text array is allocated

  m_buttonText        = new CNxString[m_buttonRows * m_buttonColumns];
}

/**
 * CButtonArray Destructor.
 */

CButtonArray::~CButtonArray(void)
{
  // Delete the array of CNxString instances

  delete [] m_buttonText;
}

/**
 * Resize the array of buttons.  Button labels will be removed and will
 * have to be reapplied in the new geometry.
 *
 * @param buttonColumns The number of buttons in one row of the button array
 * @param buttonRows The number of buttons in one column of the button array
 * @param buttonWidth The width of one button
 * @param buttonHeight The height of one button
 */

bool CButtonArray::resizeArray(uint8_t buttonColumns, uint8_t buttonRows,
                               nxgl_coord_t buttonWidth, nxgl_coord_t buttonHeight)
{
  setRaisesEvents(false);
  disableDrawing();

  // Delete the current array of CNxString instances

  delete [] m_buttonText;

  // Update configuration

  m_buttonRows       = buttonRows;
  m_buttonColumns    = buttonColumns;
  m_buttonWidth      = buttonWidth;
  m_buttonHeight     = buttonHeight;

  // Reset the cursor position if necessary

  if (m_cursorColumn >= m_buttonColumns)
    {
      m_cursorColumn = m_buttonColumns - 1;
    }

  if (m_cursorRow >= m_buttonRows)
    {
      m_cursorRow    = m_buttonRows - 1;
    }

  // Allocate the new text array

  m_buttonText       = new CNxString[m_buttonRows * m_buttonColumns];

  // Resize the widget

  bool success       = resize(buttonColumns * buttonWidth + 2,
                              buttonRows * buttonHeight + 2);

  // And go active again with a resized button array (with no labels)

  enableDrawing();
  redraw();
  setRaisesEvents(true);
  return success;
}

/**
 * Returns the string shown in the label.
 *
 * @param column The column index of the button of interest
 * @param row The row index of the button of interest
 * @return The label's text.
 */

const CNxString &CButtonArray::getText(int column, int row) const
{
  return m_buttonText[row * m_buttonColumns + column];
}

/**
 * Set the text displayed in the label.
 *
 * @param column The column index of the button to set
 * @param row The row index of the button to set
 * @param text String to display.
 */

void CButtonArray::setText(int column, int row, const CNxString &text)
{
  m_buttonText[row * m_buttonColumns + column] = text;
  onTextChange();
}

/**
 * Return the position of the last clicked button (0,0 will be returned
 * the no button has every been clicked).  The button at this position
 * is currently clicked then, in addition, return true.
 *
 * @param column The location to return the column index of the button
 *    of interest
 * @param row The location to return the row index of the button of
 *    interest
 * @return True if a button in the array is clicked
 */

bool CButtonArray::isButtonClicked(int &column, int &row) const
{
  // Return the last clicked position

  column = (m_clickX - getX()) / m_buttonWidth;

  // Calculate and return the button row index

  row = (m_clickY - getY()) / m_buttonHeight;

  // Return true if the button is currently clicked

  return isClicked();
}

/**
 * Check if this specific button in the array is clicked
 *
 * @param column The column of the button to check.
 * @param row The row of the button to check.
 * @return True if this button is clicked
 */

bool CButtonArray::isThisButtonClicked(int column, int row) const
{
  // The upper left X/Y position of the button at these indices

  nxgl_coord_t x = getX() + column * m_buttonWidth;
  nxgl_coord_t y = getY() + row * m_buttonHeight;

  // Was the last click on this button?

  if ((m_clickX >= x && m_clickX < x + m_buttonWidth) &&
      (m_clickY >= y && m_clickY < y + m_buttonHeight))
    {
      // Yes.. return true if the button is clicked

      return isClicked();
    }
  else
    {
      // No.. then it can't be clicked

      return false;
    }
}

/**
 * Control the cursor state.
 *
 * @param cursorOn True(1), the current cursor position will be highlighted
 */

void CButtonArray::cursor(bool cursorOn)
{
  if (cursorOn != m_cursorOn)
    {
      // Set the state cursor state

      m_cursorOn     = cursorOn;

      // Update only the effected button

      m_cursorChange = true;
      redraw();
      m_cursorChange = false;
    }
}

/**
 * Return the current cursor position (button indices) and an indication
 * if the button at the cursor is currently highlighted.
 *
 * @param column The location to return the column index of the button
 *    of interest
 * @param row The location to return the row index of the button of
 *    interest
 * @return True if the cursor is enabled and the button is highlighted
 */

bool CButtonArray::getCursorPosition(int &column, int &row) const
{
  column = m_cursorColumn;
  row    = m_cursorRow;
  return m_cursorOn;
}

/**
 * Set the cursor position (button indices).  Note that the cursor
 * does not have to be enabled to set the position.
 *
 * @param column The column index of the button of interest
 * @param row The row index of the button of interest
 * @return True if the cursor position is valid
 */

bool CButtonArray::setCursorPosition(int column, int row)
{
  // Verify that the cursor position is within range

  if ((unsigned)column < m_buttonColumns && (unsigned)row < m_buttonRows)
    {
      // Verify that the now position is different from the old position

      if (column != m_cursorColumn || row != m_cursorRow)
        {
           m_cursorChange = true;

          // Is the cursor on now?

          bool redrawCursor = m_cursorOn;
          if (redrawCursor)
            {
              // Yes.. clear the old cursor highlight

              m_cursorOn = false;
              redraw();
            }

          // Set the new cursor position

          m_cursorColumn = column;
          m_cursorRow    = row;

          // Do we need to turn the cursor back on?

          if (redrawCursor)
            {
              // Yes.. Set the new cursor highlight

              m_cursorOn = true;
              redraw();
            }

          m_cursorChange = false;
        }

      return true;
    }

  return false;
}

/**
 * Check if this specific button in the array is at the cursor position
 * and highlighted.
 *
 * @param column The column of the button to check.
 * @param row The row of the button to check.
 * @return True if this button is at the cursor position and highlighted.
 */

bool CButtonArray::isCursorPosition(int column, int row) const
{
  if (column == m_cursorColumn && row == m_cursorRow)
    {
      return m_cursorOn;
    }

  return false;
}

/**
 * Insert the dimensions that this widget wants to have into the rect
 * passed in as a parameter.  All coordinates are relative to the
 * widget's parent.
 *
 * @param rect Reference to a rect to populate with data.
 */

void CButtonArray::getPreferredDimensions(CRect &rect) const
{
  return getRect(rect);
}

/**
 * Sets the font.
 *
 * @param font A pointer to the font to use.
 */

void CButtonArray::setFont(CNxFont *font)
{
  m_style.font = font;
  redraw();
}

/**
 * Draw the area of this widget that falls within the clipping region.
 * Called by the redraw() function to draw all visible regions.
 *
 * @param port The CGraphicsPort to draw to.
 * @see redraw()
 */

void CButtonArray::drawContents(CGraphicsPort *port)
{
  // Do we draw just the clicked/unclicked button?

  if (m_redrawButton)
    {
      int column;
      int row;

      // Just one.  Get the row/column indices from the last click

      posToButton(m_clickX, m_clickY, column, row);

      // And draw that button

      drawButton(port, column, row, isClicked());
    }

  // Do we just draw the highlighted button?

  else if (m_cursorChange)
    {
      // Do nothing if the highlighted button is also the clicked button

      if (!isThisButtonClicked(m_cursorColumn, m_cursorRow))
        {
          drawButton(port, m_cursorColumn, m_cursorRow, false);
        }
    }

  // Draw all buttons

  else
    {
      // Visit each column

      for (int column = 0; column < m_buttonColumns; column++)
        {
          // Visit each row

          for (int row = 0; row < m_buttonRows; row++)
            {
               // Draw each button

               drawButton(port, column, row, isThisButtonClicked(column, row));
            }
        }
    }
}

/**
 * Draw the area of this widget that falls within the clipping region.
 * Called by the redraw() function to draw all visible regions.
 *
 * @param port The CGraphicsPort to draw to.
 * @see redraw()
 */

void CButtonArray::drawBorder(CGraphicsPort *port)
{
  // This doesn't do anything anymore
}

/**
 * Redraw only one button
 *
 * @param port The CGraphicsPort to draw to.
 * @param column The button column index
 * @param row The button row index
 * @param useClicked Draw the button using the 'clicked' button style,
 *        regardless of the actual button state.
 * @see onClick() and onRelease()
 */

void CButtonArray::drawButton(CGraphicsPort *port, int column, int row, bool useClicked)
{
  // The X/Y position of the button

  nxgl_coord_t x = getX() + column * m_buttonWidth;
  nxgl_coord_t y = getY() + row * m_buttonHeight;

  // Determine which colors to use

  nxwidget_pixel_t borderColor1;
  nxwidget_pixel_t borderColor2;
  nxwidget_pixel_t backColor;
  nxwidget_pixel_t textColor;

  // Pick the background and test colors. Should we use the 'clicked' background style?

  if (useClicked || isCursorPosition(column, row))
    {
      // "Selected" text color on unique "Selected" background color

      backColor    = getSelectedBackgroundColor();
      textColor    = getSelectedTextColor();
    }
  else
    {
      // Normal background color and "Enabled" text color

      backColor    = getBackgroundColor();
      textColor    = getEnabledTextColor();
    }

  // Pick the border colors.  Should we use the 'clicked' button style?

  if (useClicked)
    {
      // Yes.. Bevelled into the screen

      borderColor1 = getShadowEdgeColor();
      borderColor2 = getShineEdgeColor();
    }
  else
    {
      // No.. Bevelled out of the screen

      borderColor1 = getShineEdgeColor();
      borderColor2 = getShadowEdgeColor();
    }

  // Use a special text color if the widget is not enabled

  if (!isEnabled())
    {
      textColor = getDisabledTextColor();
    }

  // Draw the background

  port->drawFilledRect(x, y, m_buttonWidth, m_buttonHeight, backColor);

  // Draw the button outline

  port->drawBevelledRect(x, y, m_buttonWidth, m_buttonHeight,  borderColor1, borderColor2);

  // Get the text for this button

  CNxString *text = &m_buttonText[row * m_buttonColumns + column];

  // Get the text drawing region

  CRect rect;
  rect.setX(x + 1);
  rect.setY(y + 1);
  rect.setWidth(m_buttonWidth - 2);
  rect.setHeight(m_buttonHeight - 2);

  // Get the centered text alignment

  nxgl_coord_t alignY = (m_buttonHeight - getFont()->getHeight() - 2) >> 1;
  nxgl_coord_t alignX = (m_buttonWidth - getFont()->getStringWidth(*text) - 2) >> 1;

  // Get the text display position

  struct nxgl_point_s pos;
  pos.x = x + alignX;
  pos.y = y + alignY;

  // Set the CGraphicsControl background to match the selected background color.
  // This is only necessary if we cannot read from the LCD.  If we cannot read
  // from then the font background is set to this background color.
  // REVISIT:  This begs for a more generalized solution.

#ifdef CONFIG_NX_WRITEONLY
  nxgl_mxpixel_t saveColor = port->getBackColor();
  port->setBackColor(backColor);
#endif

  // And draw the button text.

  port->drawText(&pos, &rect, getFont(), *text, 0, text->getLength(), textColor);

  // Restore the default background color

#ifdef CONFIG_NX_WRITEONLY
  port->setBackColor(saveColor);
#endif
}

/**
 * Redraws the button.
 *
 * @param x The x coordinate of the click.
 * @param y The y coordinate of the click.
 */

void CButtonArray::onClick(nxgl_coord_t x, nxgl_coord_t y)
{
  // Click X,Y is in raw window coordinates

  m_clickX = x;
  m_clickY = y;

  // Redraw only the button that was clicked

  m_redrawButton = true;
  redraw();
  m_redrawButton = false;
}

/**
 * Raises an action.
 *
 * @param x The x coordinate of the mouse.
 * @param y The y coordinate of the mouse.
 */

void CButtonArray::onPreRelease(nxgl_coord_t x, nxgl_coord_t y)
{
  m_widgetEventHandlers->raiseActionEvent();
}

/**
 * Redraws the button.
 *
 * @param x The x coordinate of the mouse.
 * @param y The y coordinate of the mouse.
 */

void CButtonArray::onRelease(nxgl_coord_t x, nxgl_coord_t y)
{
  // Redraw only the button that was released

  m_redrawButton = true;
  redraw();
  m_redrawButton = false;
}

/**
 * Redraws the button.
 *
 * @param x The x coordinate of the mouse.
 * @param y The y coordinate of the mouse.
 */

void CButtonArray::onReleaseOutside(nxgl_coord_t x, nxgl_coord_t y)
{
  redraw();
}

/**
 * Convert an X/Y position to a button column/row index
 *
 * @param x The x position
 * @param y The y position
 * @param column The location to return the column index of the button
 *    of interest
 * @param row The location to return the row index of the button of
 *    interest
 * @return false is the position is invalid
 */

bool CButtonArray::posToButton(nxgl_coord_t x, nxgl_coord_t y, int &column, int &row)
{
  // Get the row/column indices matching the x/y position

  column = (x - getX()) / m_buttonWidth;
  row    = (y - getY()) / m_buttonHeight;
  return ((unsigned)column < m_buttonColumns && (unsigned)row < m_buttonRows);
}

/**
 * Updates the GUI after the text has changed.
 */

void CButtonArray::onTextChange(void)
{
  redraw();
  m_widgetEventHandlers->raiseValueChangeEvent();
}