// Aseprite UI Library
// Copyright (C) 2018-2025  Igara Studio S.A.
// Copyright (C) 2001-2017  David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

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

#include "gfx/size.h"
#include "ui/box.h"
#include "ui/message.h"
#include "ui/resize_event.h"
#include "ui/scale.h"
#include "ui/size_hint_event.h"
#include "ui/theme.h"

#include <algorithm>

namespace ui {

using namespace gfx;

Box::Box(int align) : Widget(kBoxWidget)
{
  enableFlags(IGNORE_MOUSE);
  setAlign(align);
  initTheme();
}

void Box::onSizeHint(SizeHintEvent& ev)
{
#define ADD_CHILD_SIZE(w, h)                                                                       \
  {                                                                                                \
    if (align() & HOMOGENEOUS)                                                                     \
      prefSize.w = std::max(prefSize.w, childSize.w);                                              \
    else                                                                                           \
      prefSize.w += childSize.w;                                                                   \
    prefSize.h = std::max(prefSize.h, childSize.h);                                                \
    fitIn.w = std::max(0, fitIn.w - prefSize.w);                                                   \
  }

#define FINAL_ADJUSTMENT(w)                                                                        \
  {                                                                                                \
    if (align() & HOMOGENEOUS)                                                                     \
      prefSize.w *= visibleChildren;                                                               \
    prefSize.w += childSpacing() * (visibleChildren - 1);                                          \
  }

  int visibleChildren = 0;
  for (auto* child : children()) {
    if (!child->hasFlags(HIDDEN))
      ++visibleChildren;
  }

  Size prefSize(0, 0);
  Size fitIn(0, 0);
  if (align() & HORIZONTAL)
    fitIn.w = ev.fitInWidth();
  else
    fitIn.h = ev.fitInHeight();

  for (auto child : children()) {
    if (child->hasFlags(HIDDEN))
      continue;

    Size childSize = child->sizeHint(fitIn);
    if (align() & HORIZONTAL) {
      ADD_CHILD_SIZE(w, h);
    }
    else {
      ADD_CHILD_SIZE(h, w);
    }
  }

  if (visibleChildren > 0) {
    if (align() & HORIZONTAL) {
      FINAL_ADJUSTMENT(w);
    }
    else {
      FINAL_ADJUSTMENT(h);
    }
  }

  prefSize.w += border().width();
  prefSize.h += border().height();

  ev.setSizeHint(prefSize);
}

void Box::onResize(ResizeEvent& ev)
{
#define LAYOUT_CHILDREN(x, y, w, h)                                                                \
  {                                                                                                \
    availExtraSize = availSize.w - prefSize.w;                                                     \
    availSize.w -= childSpacing() * (visibleChildren - 1);                                         \
    if (align() & HOMOGENEOUS)                                                                     \
      homogeneousSize = availSize.w / visibleChildren;                                             \
                                                                                                   \
    Rect defChildPos(childrenBounds());                                                            \
    int i = 0, j = 0;                                                                              \
    for (auto child : children()) {                                                                \
      if (child->hasFlags(HIDDEN))                                                                 \
        continue;                                                                                  \
                                                                                                   \
      int size = 0;                                                                                \
                                                                                                   \
      if (align() & HOMOGENEOUS) {                                                                 \
        if (i < visibleChildren - 1)                                                               \
          size = homogeneousSize;                                                                  \
        else                                                                                       \
          size = availSize.w;                                                                      \
      }                                                                                            \
      else {                                                                                       \
        size = child->sizeHint(availSize).w;                                                       \
                                                                                                   \
        if (child->isExpansive()) {                                                                \
          const int extraSize = guiscaled_div(availExtraSize, (expansiveChildren - j));            \
          size += extraSize;                                                                       \
          availExtraSize -= extraSize;                                                             \
          if (++j == expansiveChildren)                                                            \
            size += availExtraSize;                                                                \
        }                                                                                          \
        else {                                                                                     \
          availExtraSize -= (size - child->sizeHint().w);                                          \
        }                                                                                          \
      }                                                                                            \
                                                                                                   \
      Rect childPos = defChildPos;                                                                 \
      childPos.w = size = std::clamp(size, child->minSize().w, child->maxSize().w);                \
      childPos.h = std::clamp(childPos.h, child->minSize().h, child->maxSize().h);                 \
      child->setBounds(childPos);                                                                  \
                                                                                                   \
      defChildPos.x += size + childSpacing();                                                      \
      availSize.w -= size;                                                                         \
      ++i;                                                                                         \
    }                                                                                              \
  }

  setBoundsQuietly(ev.bounds());

  int visibleChildren = 0;
  int expansiveChildren = 0;
  for (auto child : children()) {
    if (!child->hasFlags(HIDDEN)) {
      ++visibleChildren;
      if (child->isExpansive())
        ++expansiveChildren;
    }
  }

  if (visibleChildren > 0) {
    Size availSize = childrenBounds().size();
    Size prefSize = sizeHint(availSize);
    int homogeneousSize = 0;
    int availExtraSize = 0;

    prefSize.w -= border().width();
    prefSize.h -= border().height();

    if (align() & HORIZONTAL) {
      LAYOUT_CHILDREN(x, y, w, h);
    }
    else {
      LAYOUT_CHILDREN(y, x, h, w);
    }
  }
}

} // namespace ui
