// Aseprite
// Copyright (C) 2020-2025  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#include "app/ui/resources_listbox.h"

#include "app/i18n/strings.h"
#include "app/res/resource.h"
#include "app/res/resources_loader.h"
#include "app/ui/skin/skin_theme.h"
#include "ui/graphics.h"
#include "ui/message.h"
#include "ui/paint_event.h"
#include "ui/size_hint_event.h"
#include "ui/view.h"

namespace app {

using namespace ui;
using namespace skin;

//////////////////////////////////////////////////////////////////////
// ResourceListItem

ResourceListItem::ResourceListItem(Resource* resource)
  : ListItem(resource->id())
  , m_resource(resource)
{
}

bool ResourceListItem::onProcessMessage(ui::Message* msg)
{
  switch (msg->type()) {
    case kMouseLeaveMessage:
    case kMouseEnterMessage: invalidate(); break;
  }
  return ListItem::onProcessMessage(msg);
}

void ResourceListItem::onPaint(PaintEvent& ev)
{
  auto theme = SkinTheme::get(this);
  Graphics* g = ev.graphics();
  gfx::Rect bounds = clientBounds();
  gfx::Color bgcolor, fgcolor;

  if (isSelected()) {
    bgcolor = theme->colors.listitemSelectedFace();
    fgcolor = theme->colors.listitemSelectedText();
  }
  else {
    bgcolor = theme->colors.listitemNormalFace();
    fgcolor = theme->colors.listitemNormalText();
  }

  g->fillRect(bgcolor, bounds);

  static_cast<ResourcesListBox*>(parent())->paintResource(g, bounds, m_resource.get());

  g->drawText(text(),
              fgcolor,
              gfx::ColorNone,
              gfx::Point(bounds.x + 2 * guiscale(),
                         guiscaled_center(bounds.y, bounds.h, g->font()->lineHeight())));
}

void ResourceListItem::onSizeHint(SizeHintEvent& ev)
{
  ev.setSizeHint(static_cast<ResourcesListBox*>(parent())->resourceSizeHint(m_resource.get()));
}

//////////////////////////////////////////////////////////////////////
// ResourcesListBox::LoadingItem

class ResourcesListBox::LoadingItem : public ListItem {
public:
  LoadingItem() : ListItem(Strings::resource_listbox_loading()), m_state(0) {}

  void makeProgress()
  {
    constexpr char progress[] = { '/', '-', '\\', '|' };
    setText(fmt::format("{} {}", Strings::resource_listbox_loading(), progress[((++m_state) % 4)]));
  }

private:
  int m_state;
};

//////////////////////////////////////////////////////////////////////
// ResourcesListBox

ResourcesListBox::ResourcesListBox(ResourcesLoader* resourcesLoader)
  : m_resourcesLoader(resourcesLoader)
  , m_resourcesTimer(100)
{
  m_resourcesTimer.Tick.connect([this] { onTick(); });
}

Resource* ResourcesListBox::selectedResource()
{
  if (ResourceListItem* listItem = dynamic_cast<ResourceListItem*>(getSelectedChild()))
    return listItem->resource();
  else
    return nullptr;
}

void ResourcesListBox::markToReload()
{
  deleteAllChildren();
  m_reloadOnOpen = true;
}

void ResourcesListBox::reload()
{
  deleteAllChildren();

  ASSERT(m_resourcesLoader);
  if (m_resourcesLoader) {
    m_resourcesLoader->reload();
    m_resourcesTimer.start();
  }
}

void ResourcesListBox::deleteAllChildren()
{
  auto children = this->children(); // Create a copy because we'll
                                    // modify the list in the for()

  // Delete all ResourcesListItem. (PalettesListBox contains a tooltip
  // manager too, so we cannot remove just all children.)
  for (auto child : children) {
    if (dynamic_cast<ResourceListItem*>(child))
      delete child;
  }
}

void ResourcesListBox::paintResource(Graphics* g, gfx::Rect& bounds, Resource* resource)
{
  onPaintResource(g, bounds, resource);
}

gfx::Size ResourcesListBox::resourceSizeHint(Resource* resource)
{
  gfx::Size pref(0, 0);
  onResourceSizeHint(resource, pref);
  return pref;
}

bool ResourcesListBox::onProcessMessage(ui::Message* msg)
{
  switch (msg->type()) {
    case kOpenMessage: {
      if (m_reloadOnOpen) {
        m_reloadOnOpen = false;
        reload();
      }
      else {
        // Start timer to fill the list box with the current resource
        // loader state.
        m_resourcesTimer.start();
      }
      break;
    }
  }
  return ListBox::onProcessMessage(msg);
}

void ResourcesListBox::onChange()
{
  Resource* resource = selectedResource();
  if (resource)
    onResourceChange(resource);
}

ResourceListItem* ResourcesListBox::onCreateResourceItem(Resource* resource)
{
  return new ResourceListItem(resource);
}

void ResourcesListBox::onTick()
{
  if (m_resourcesLoader == nullptr) {
    stop();
    return;
  }

  if (!m_loadingItem) {
    m_loadingItem = new LoadingItem;
    addChild(m_loadingItem);
    layout();
  }
  m_loadingItem->makeProgress();

  std::unique_ptr<Resource> resource;

  while (m_resourcesLoader->next(resource)) {
    std::unique_ptr<ResourceListItem> listItem(onCreateResourceItem(resource.get()));
    insertChild(getItemsCount() - 1, listItem.get());
    resource.release();
    listItem.release();
  }

  if (m_resourcesLoader->isDone()) {
    // Delay sorting and layout until we're fully loaded, for perfomance with big lists.
    if (View* view = View::getView(this))
      view->updateView();
    sortItems();
    layout();

    FinishLoading();
    stop();
  }
}

void ResourcesListBox::stop()
{
  m_resourcesTimer.stop();

  if (m_loadingItem) {
    removeChild(m_loadingItem);
    layout();

    delete m_loadingItem;
    m_loadingItem = nullptr;

    if (View* view = View::getView(this))
      view->updateView();
  }
}

} // namespace app
