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

/***********************************************************************

  Alert format:
  ------------

     "Title Text"
     "==Centre line of text"
     "--"
     "<<Left line of text"
     ">>Right line of text"
     "||My &Button||&Your Button||&He Button"

    .-----------------------------------------.
    | My Message                              |
    +=========================================+
    |           Centre line of text           |
    | --------------------------------------- |
    | Left line of text                       |
    |                      Right line of text |
    |                                         |
    | [My Button]  [Your Button] [Him Button] |
    `-----~---------~-------------~-----------'
 */

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

#include "ui/alert.h"

#include "base/string.h"
#include "fmt/format.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/grid.h"
#include "ui/label.h"
#include "ui/scale.h"
#include "ui/separator.h"
#include "ui/slider.h"
#include "ui/theme.h"

#include <algorithm>
#include <string>

namespace ui {

Alert::Alert()
  : Window(WithTitleBar)
  , m_progress(nullptr)
  , m_progressPlaceholder(nullptr)
{
  auto box1 = new Box(VERTICAL);
  auto box2 = new Box(VERTICAL);
  auto grid = new Grid(1, false);
  auto box3 = new Box(HORIZONTAL | HOMOGENEOUS);

  // To identify by the user
  box2->setId("labels");
  box3->setId("buttons");
  m_labelsPlaceholder = box2;
  m_buttonsPlaceholder = box3;

  // Pseudo separators (only to fill blank space)
  auto box4 = new Box(0);
  auto box5 = new Box(0);
  m_progressPlaceholder = new Box(0);

  box4->setExpansive(true);
  box5->setExpansive(true);
  box4->noBorderNoChildSpacing();
  box5->noBorderNoChildSpacing();
  m_progressPlaceholder->noBorderNoChildSpacing();

  // Setup parent <-> children relationship

  addChild(box1);

  box1->addChild(box4); // Filler
  box1->addChild(box2); // Labels
  box1->addChild(m_progressPlaceholder);
  box1->addChild(box5); // Filler
  box1->addChild(grid); // Buttons

  grid->addChildInCell(box3, 1, 1, CENTER | BOTTOM | HORIZONTAL);
}

void Alert::setTitle(const std::string& title)
{
  setText(title);
}

void Alert::addLabel(const std::string& text, const int align)
{
  auto label = new Label(text);
  label->setAlign(align);
  m_labelsPlaceholder->addChild(label);
}

void Alert::addSeparator()
{
  auto sep = new Separator("", HORIZONTAL);
  m_labelsPlaceholder->addChild(sep);
}

void Alert::addButton(const std::string& text)
{
  auto button = new Button(text);

  // Process the mnemonic next to & character and "false" means that
  // the mnemonic letter can be used without Alt or Command key
  // modifiers.
  button->processMnemonicFromText('&', false);

  button->setMinSize(gfx::Size(60*guiscale(), 0));
  m_buttons.push_back(button);

  button->setId(fmt::format("button-{}", m_buttons.size()).c_str());
  button->Click.connect([this, button]{ closeWindow(button); });

  m_buttonsPlaceholder->addChild(button);
}

void Alert::addProgress()
{
  ASSERT(!m_progress);
  m_progress = new Slider(0, 100, 0);
  m_progress->setReadOnly(true);
  m_progressPlaceholder->addChild(m_progress);
  m_progressPlaceholder->setVisible(true);
}

CheckBox* Alert::addCheckBox(const std::string& text)
{
  auto checkBox = new CheckBox(text);
  m_progressPlaceholder->addChild(checkBox);
  m_progressPlaceholder->setVisible(true);
  return checkBox;
}

void Alert::setProgress(double progress)
{
  ASSERT(m_progress);
  m_progress->setValue(int(std::clamp(progress * 100.0, 0.0, 100.0)));
}

// static
AlertPtr Alert::create(const std::string& _msg)
{
  std::string msg(_msg);

  // Create the alert window
  AlertPtr window(new Alert);
  window->processString(msg);
  return window;
}

// static
int Alert::show(const std::string& _msg)
{
  std::string msg(_msg);

  // Create the alert window
  AlertPtr window(new Alert);
  window->processString(msg);
  return window->show();
}

int Alert::show()
{
  // Default button is the first one (Enter default option, Esc should
  // act like the last button)
  if (!m_buttons.empty())
    m_buttons[0]->setFocusMagnet(true);

  // Open it
  openWindowInForeground();

  // Check the closer
  int ret = 0;
  if (Widget* closer = this->closer()) {
    for (int i=0; i<(int)m_buttons.size(); ++i) {
      if (closer == m_buttons[i]) {
        ret = i+1;
        break;
      }
    }
  }

  // And return it
  return ret;
}

void Alert::processString(std::string& buf)
{
  bool title = true;
  bool label = false;
  bool separator = false;
  bool button = false;
  int align = 0;

  // Process buffer
  int c = 0;
  int beg = 0;
  for (;;) {
    // Ignore characters
    if (buf[c] == '\n' ||
        buf[c] == '\r') {
      buf.erase(c, 1);
      continue;
    }

    if ((!buf[c]) ||
        ((buf[c] == buf[c+1]) &&
         ((buf[c] == '<') ||
          (buf[c] == '=') ||
          (buf[c] == '>') ||
          (buf[c] == '-') ||
          (buf[c] == '|')))) {
      if (title || label || separator || button) {
        std::string item = buf.substr(beg, c-beg);

        if (title)
          setTitle(item);
        else if (label)
          addLabel(item, align);
        else if (separator)
          addSeparator();
        else if (button)
          addButton(item);
      }

      // Done
      if (!buf[c])
        break;
      // Next widget
      else {
        title = label = separator = button = false;
        beg = c+2;
        align = 0;

        switch (buf[c]) {
          case '<': label=true; align=LEFT; break;
          case '=': label=true; align=CENTER; break;
          case '>': label=true; align=RIGHT; break;
          case '-': separator=true; break;
          case '|': button=true; break;
        }
        ++c;
      }
    }
    ++c;
  }
}

} // namespace ui
