/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "GUIFixedListContainer.h"

#include "GUIListItemLayout.h"
#include "input/Key.h"

CGUIFixedListContainer::CGUIFixedListContainer(int parentID, int controlID, float posX, float posY, float width, float height, ORIENTATION orientation, const CScroller& scroller, int preloadItems, int fixedPosition, int cursorRange)
    : CGUIBaseContainer(parentID, controlID, posX, posY, width, height, orientation, scroller, preloadItems)
{
  ControlType = GUICONTAINER_FIXEDLIST;
  m_type = VIEW_TYPE_LIST;
  m_fixedCursor = fixedPosition;
  m_cursorRange = std::max(0, cursorRange);
  SetCursor(m_fixedCursor);
}

CGUIFixedListContainer::~CGUIFixedListContainer(void) = default;

bool CGUIFixedListContainer::OnAction(const CAction &action)
{
  switch (action.GetID())
  {
  case ACTION_PAGE_UP:
    {
      Scroll(-m_itemsPerPage);
      return true;
    }
    break;
  case ACTION_PAGE_DOWN:
    {
      Scroll(m_itemsPerPage);
      return true;
    }
    break;
    // smooth scrolling (for analog controls)
  case ACTION_SCROLL_UP:
    {
      m_analogScrollCount += action.GetAmount() * action.GetAmount();
      bool handled = false;
      while (m_analogScrollCount > 0.4f)
      {
        handled = true;
        m_analogScrollCount -= 0.4f;
        Scroll(-1);
      }
      return handled;
    }
    break;
  case ACTION_SCROLL_DOWN:
    {
      m_analogScrollCount += action.GetAmount() * action.GetAmount();
      bool handled = false;
      while (m_analogScrollCount > 0.4f)
      {
        handled = true;
        m_analogScrollCount -= 0.4f;
        Scroll(1);
      }
      return handled;
    }
    break;
  }
  return CGUIBaseContainer::OnAction(action);
}

bool CGUIFixedListContainer::MoveUp(bool wrapAround)
{
  int item = GetSelectedItem();
  if (item > 0)
    SelectItem(item - 1);
  else if (wrapAround)
  {
    SelectItem((int)m_items.size() - 1);
    SetContainerMoving(-1);
  }
  else
    return false;
  return true;
}

bool CGUIFixedListContainer::MoveDown(bool wrapAround)
{
  int item = GetSelectedItem();
  if (item < (int)m_items.size() - 1)
    SelectItem(item + 1);
  else if (wrapAround)
  { // move first item in list
    SelectItem(0);
    SetContainerMoving(1);
  }
  else
    return false;
  return true;
}

void CGUIFixedListContainer::Scroll(int amount)
{
  // increase or decrease the offset within [-minCursor, m_items.size() - maxCursor]
  int minCursor, maxCursor;
  GetCursorRange(minCursor, maxCursor);
  const int nextCursor = GetCursor() + amount;
  int offset = GetOffset() + amount;
  if (offset < -minCursor)
  {
    offset = -minCursor;
    SetCursor(nextCursor < minCursor ? minCursor : nextCursor);
  }
  if (offset > (int)m_items.size() - 1 - maxCursor)
  {
    offset = m_items.size() - 1 - maxCursor;
    SetCursor(nextCursor > maxCursor ? maxCursor : nextCursor);
  }
  ScrollToOffset(offset);
}

bool CGUIFixedListContainer::GetOffsetRange(int &minOffset, int &maxOffset) const
{
  GetCursorRange(minOffset, maxOffset);
  minOffset = -minOffset;
  maxOffset = m_items.size() - maxOffset - 1;
  return true;
}

void CGUIFixedListContainer::ValidateOffset()
{
  if (!m_layout) return;
  // ensure our fixed cursor position is valid
  if (m_fixedCursor >= m_itemsPerPage)
    m_fixedCursor = m_itemsPerPage - 1;
  if (m_fixedCursor < 0)
    m_fixedCursor = 0;
  // compute our minimum and maximum cursor positions
  int minCursor, maxCursor;
  GetCursorRange(minCursor, maxCursor);
  // assure our cursor is between these limits
  SetCursor(std::max(GetCursor(), minCursor));
  SetCursor(std::min(GetCursor(), maxCursor));
  int minOffset, maxOffset;
  GetOffsetRange(minOffset, maxOffset);
  // and finally ensure our offset is valid
  // don't validate offset if we are scrolling in case the tween image exceed <0, 1> range
  if (GetOffset() > maxOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() > maxOffset * m_layout->Size(m_orientation)))
  {
    SetOffset(std::max(-minCursor, maxOffset));
    m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
  }
  if (GetOffset() < minOffset || (!m_scroller.IsScrolling() && m_scroller.GetValue() < minOffset * m_layout->Size(m_orientation)))
  {
    SetOffset(minOffset);
    m_scroller.SetValue(GetOffset() * m_layout->Size(m_orientation));
  }
}

int CGUIFixedListContainer::GetCursorFromPoint(const CPoint &point, CPoint *itemPoint) const
{
  if (!m_focusedLayout || !m_layout)
    return -1;
  int minCursor, maxCursor;
  GetCursorRange(minCursor, maxCursor);
  // see if the point is either side of our focus range
  float start = (minCursor + 0.2f) * m_layout->Size(m_orientation);
  float end = (maxCursor - 0.2f) * m_layout->Size(m_orientation) + m_focusedLayout->Size(m_orientation);
  float pos = (m_orientation == VERTICAL) ? point.y : point.x;
  if (pos >= start && pos <= end)
  { // select the appropriate item
    pos -= minCursor * m_layout->Size(m_orientation);
    for (int row = minCursor; row <= maxCursor; row++)
    {
      const CGUIListItemLayout *layout = (row == GetCursor()) ? m_focusedLayout : m_layout;
      if (pos < layout->Size(m_orientation))
      {
        if (!InsideLayout(layout, point))
          return -1;
        return row;
      }
      pos -= layout->Size(m_orientation);
    }
  }
  return -1;
}

bool CGUIFixedListContainer::SelectItemFromPoint(const CPoint &point)
{
  if (!m_focusedLayout || !m_layout)
    return false;

  MarkDirtyRegion();

  const float mouse_scroll_speed = 0.25f;
  const float mouse_max_amount = 1.5f;
  float sizeOfItem = m_layout->Size(m_orientation);
  int minCursor, maxCursor;
  GetCursorRange(minCursor, maxCursor);
  // see if the point is either side of our focus range
  float start = (minCursor + 0.2f) * sizeOfItem;
  float end = (maxCursor - 0.2f) * sizeOfItem + m_focusedLayout->Size(m_orientation);
  float pos = (m_orientation == VERTICAL) ? point.y : point.x;
  if (pos < start && GetOffset() > -minCursor)
  { // scroll backward
    if (!InsideLayout(m_layout, point))
      return false;
    float amount = std::min((start - pos) / sizeOfItem, mouse_max_amount);
    m_analogScrollCount += amount * amount * mouse_scroll_speed;
    if (m_analogScrollCount > 1)
    {
      ScrollToOffset(GetOffset() - 1);
      m_analogScrollCount = 0;
    }
    return true;
  }
  else if (pos > end && GetOffset() + maxCursor < (int)m_items.size() - 1)
  {
    if (!InsideLayout(m_layout, point))
      return false;
    // scroll forward
    float amount = std::min((pos - end) / sizeOfItem, mouse_max_amount);
    m_analogScrollCount += amount * amount * mouse_scroll_speed;
    if (m_analogScrollCount > 1)
    {
      ScrollToOffset(GetOffset() + 1);
      m_analogScrollCount = 0;
    }
    return true;
  }
  else
  { // select the appropriate item
    int cursor = GetCursorFromPoint(point);
    if (cursor < 0)
      return false;
    // calling SelectItem() here will focus the item and scroll, which isn't really what we're after
    SetCursor(cursor);
    return true;
  }
}

void CGUIFixedListContainer::SelectItem(int item)
{
  // Check that GetOffset() is valid
  ValidateOffset();
  // only select an item if it's in a valid range
  if (item >= 0 && item < (int)m_items.size())
  {
    // Select the item requested - we first set the cursor position
    // which may be different at either end of the list, then the offset
    int minCursor, maxCursor;
    GetCursorRange(minCursor, maxCursor);

    int cursor;
    if ((int)m_items.size() - 1 - item <= maxCursor - m_fixedCursor)
      cursor = std::max(m_fixedCursor, maxCursor + item - (int)m_items.size() + 1);
    else if (item <= m_fixedCursor - minCursor)
      cursor = std::min(m_fixedCursor, minCursor + item);
    else
      cursor = m_fixedCursor;
    if (cursor != GetCursor())
      SetContainerMoving(cursor - GetCursor());
    SetCursor(cursor);
    ScrollToOffset(item - GetCursor());
    MarkDirtyRegion();
  }
}

bool CGUIFixedListContainer::HasPreviousPage() const
{
  return (GetOffset() > 0);
}

bool CGUIFixedListContainer::HasNextPage() const
{
  return (GetOffset() < (int)m_items.size() - m_itemsPerPage && (int)m_items.size() >= m_itemsPerPage);
}

int CGUIFixedListContainer::GetCurrentPage() const
{
  int offset = CorrectOffset(GetOffset(), GetCursor());
  if (offset + m_itemsPerPage - GetCursor() >= (int)GetRows())  // last page
    return (GetRows() + m_itemsPerPage - 1) / m_itemsPerPage;
  return offset / m_itemsPerPage + 1;
}

void CGUIFixedListContainer::GetCursorRange(int &minCursor, int &maxCursor) const
{
  minCursor = std::max(m_fixedCursor - m_cursorRange, 0);
  maxCursor = std::min(m_fixedCursor + m_cursorRange, m_itemsPerPage);

  if (!m_items.size())
  {
    minCursor = m_fixedCursor;
    maxCursor = m_fixedCursor;
    return;
  }

  while (maxCursor - minCursor > (int)m_items.size() - 1)
  {
    if (maxCursor - m_fixedCursor > m_fixedCursor - minCursor)
      maxCursor--;
    else
      minCursor++;
  }
}

