#include "StarButtonWidget.hpp"
#include "StarRoot.hpp"
#include "StarJsonExtra.hpp"
#include "StarRandom.hpp"
#include "StarAssets.hpp"

namespace Star {

ButtonWidget::ButtonWidget() {
  m_hovered = false;
  m_pressed = false;
  m_checkable = false;
  m_checked = false;
  m_disabled = false;
  m_highlighted = false;
  m_hasCheckedImages = false;
  m_sustain = false;
  m_invisible = false;
  m_hTextAnchor = HorizontalAnchor::HMidAnchor;
  m_fontColor = Color::White;
  m_fontColorDisabled = Color::Gray;

  auto assets = Root::singleton().assets();

  auto interfaceConfig = assets->json("/interface.config");
  m_pressedOffset = jsonToVec2I(interfaceConfig.get("buttonPressedOffset"));
  m_fontSize = interfaceConfig.query("font.buttonSize").toInt();
  m_fontDirectives = interfaceConfig.queryString("font.defaultDirectives", "");
  m_font = interfaceConfig.query("font.defaultFont").toString();

  m_clickSounds = jsonToStringList(assets->json("/interface.config:buttonClickSound"));
  m_releaseSounds = jsonToStringList(assets->json("/interface.config:buttonReleaseSound"));
  m_hoverSounds = jsonToStringList(assets->json("/interface.config:buttonHoverSound"));
  m_hoverOffSounds = jsonToStringList(assets->json("/interface.config:buttonHoverOffSound"));
}

ButtonWidget::ButtonWidget(WidgetCallbackFunc callback,
    String const& baseImage,
    String const& hoverImage,
    String const& pressedImage,
    String const& disabledImage)
  : ButtonWidget() {
  setCallback(callback);
  setImages(baseImage, hoverImage, pressedImage, disabledImage);
}

ButtonWidget::~ButtonWidget() {
  if (m_buttonGroup)
    m_buttonGroup->removeButton(this);
}

void ButtonWidget::renderImpl() {
  if (isPressed() && sustainCallbackOnDownHold())
    if (m_callback)
      m_callback(this);

  Vec2F position = Vec2F(screenPosition());
  Vec2F textPosition = position + Vec2F(m_textOffset);
  if (m_hTextAnchor == HorizontalAnchor::HMidAnchor)
    textPosition += Vec2F(size()) / 2;
  else if (m_hTextAnchor == HorizontalAnchor::RightAnchor)
    textPosition += Vec2F(size()[0], size()[1] / 2);
  else if (m_hTextAnchor == HorizontalAnchor::LeftAnchor)
    textPosition += Vec2F(0, size()[1] / 2);
  // We need to show the down button offset if we're pressing the button or
  // don't have checked images and thus need some way to show that the button
  // is checked (there's probably some better default behavior in that case)
  if (m_pressed || (m_checked && !m_hasCheckedImages)) {
    position += Vec2F(m_pressedOffset);
    textPosition += Vec2F(m_pressedOffset);
  }

  if (m_hasCheckedImages && m_checked) {
    if (m_disabled)
      drawButtonPart(m_disabledImageChecked, position);
    else if ((m_pressed || m_highlighted) && !m_pressedImageChecked.empty())
      drawButtonPart(m_pressedImageChecked, position);
    else if ((m_pressed || m_hovered || m_highlighted) && !m_hoverImageChecked.empty())
      drawButtonPart(m_hoverImageChecked, position);
    else
      drawButtonPart(m_baseImageChecked, position);
  } else {
    if (m_disabled)
      drawButtonPart(m_disabledImage, position);
    else if ((m_pressed || m_highlighted) && !m_pressedImage.empty())
      drawButtonPart(m_pressedImage, position);
    else if ((m_pressed || m_hovered || m_highlighted) && !m_hoverImage.empty())
      drawButtonPart(m_hoverImage, position);
    else if (!m_invisible)
      drawButtonPart(m_baseImage, position);
  }

  if (!m_overlayImage.empty())
    drawButtonPart(m_overlayImage, position);

  if (!m_text.empty()) {
    auto& guiContext = GuiContext::singleton();
    guiContext.setFontProcessingDirectives(m_fontDirectives);
    guiContext.setFontSize(m_fontSize);
    guiContext.setFont(m_font);
    if (m_disabled)
      guiContext.setFontColor(m_fontColorDisabled.toRgba());
    else if (m_fontColorChecked && m_checked)
      guiContext.setFontColor(m_fontColorChecked.value().toRgba());
    else
      guiContext.setFontColor(m_fontColor.toRgba());
    guiContext.setFontMode(FontMode::Shadow);
    guiContext.renderInterfaceText(m_text, {textPosition, m_hTextAnchor, VerticalAnchor::VMidAnchor});
    guiContext.setFontMode(FontMode::Normal);
    guiContext.setFontProcessingDirectives("");
  }
}

bool ButtonWidget::sendEvent(InputEvent const& event) {
  if (m_visible && !m_disabled) {
    if (event.is<MouseButtonDownEvent>() && event.get<MouseButtonDownEvent>().mouseButton == MouseButton::Left) {
      if (inMember(*context()->mousePosition(event))) {
        if (!isPressed()) {
          auto assets = Root::singleton().assets();
          auto sound = Random::randValueFrom(m_clickSounds, "");
          if (!sound.empty())
            context()->playAudio(sound);
        }
        setPressed(true);
        if (m_callback) {
          focus();
          return true;
        }
      } else {
        blur();
        return false;
      }
    } else if (event.is<MouseButtonUpEvent>()) {
      if (isPressed()) {
        auto assets = Root::singleton().assets();
        auto sound = Random::randValueFrom(m_releaseSounds, "");
        if (!sound.empty())
          context()->playAudio(sound);
      }
      setPressed(false);
      return false;
    }
  }

  return false;
}

void ButtonWidget::mouseOver() {
  Widget::mouseOver();
  if (!m_disabled) {
    if (!m_hovered) {
      auto assets = Root::singleton().assets();
      auto sound = Random::randValueFrom(m_hoverSounds);
      if (!sound.empty())
        context()->playAudio(sound);
    }
    m_hovered = true;
  }
}

void ButtonWidget::mouseOut() {
  Widget::mouseOut();
  if (!m_disabled && m_hovered) {
    auto assets = Root::singleton().assets();
    auto sound = Random::randValueFrom(m_hoverOffSounds);
    if (!sound.empty())
      context()->playAudio(sound);
  }
  m_hovered = false;
  m_pressed = false;
}

void ButtonWidget::mouseReturnStillDown() {
  Widget::mouseReturnStillDown();
  m_hovered = true;
  m_pressed = true;
}

void ButtonWidget::hide() {
  Widget::hide();
  m_pressed = false;
  m_hovered = false;
}

void ButtonWidget::setCallback(WidgetCallbackFunc callback) {
  m_callback = callback;
}

ButtonGroupPtr ButtonWidget::buttonGroup() const {
  return m_buttonGroup;
}

void ButtonWidget::setButtonGroup(ButtonGroupPtr newGroup, int id) {
  if (m_buttonGroup != newGroup) {
    if (m_buttonGroup)
      m_buttonGroup->removeButton(this);

    m_buttonGroup = std::move(newGroup);

    if (m_buttonGroup) {
      setCheckable(true);
      m_buttonGroup->addButton(this, id);
    }
  }
}

int ButtonWidget::buttonGroupId() {
  if (m_buttonGroup)
    return m_buttonGroup->id(this);
  else
    return ButtonGroup::NoButton;
}

bool ButtonWidget::isHovered() const {
  return m_hovered;
}

bool ButtonWidget::isPressed() const {
  return m_pressed;
}

void ButtonWidget::setPressed(bool pressed) {
  if (m_pressed != pressed) {
    // Button action is triggered when the button is released after being pressed
    if (m_pressed) {
      check();
      if (m_callback) {
        m_callback(this);
      }
    }
    m_pressed = pressed;
  }
}

bool ButtonWidget::isCheckable() const {
  return m_checkable;
}

void ButtonWidget::setCheckable(bool checkable) {
  m_checkable = checkable;
}

bool ButtonWidget::isHighlighted() const {
  return m_highlighted;
}

void ButtonWidget::setHighlighted(bool highlighted) {
  m_highlighted = highlighted;
}

bool ButtonWidget::isChecked() const {
  return m_checked;
}

void ButtonWidget::setChecked(bool checked) {
  // might cause button groups to have multiple selected against its rules, be careful with direct poking, use check()
  // instead.
  m_checked = checked;
}

void ButtonWidget::check() {
  if (m_checkable) {
    // If we are part of an exclusive button group, then don't uncheck if
    // we are already checked and pressed again.
    if (m_buttonGroup) {
      if (m_buttonGroup->toggle() || !isChecked()) {
        setChecked(!m_buttonGroup->toggle() || !isChecked());
        m_buttonGroup->wasChecked(this);
      }
    } else {
      setChecked(!isChecked());
    }
  }
}

bool ButtonWidget::sustainCallbackOnDownHold() {
  return m_sustain;
}

void ButtonWidget::setSustainCallbackOnDownHold(bool sustain) {
  m_sustain = sustain;
}

void ButtonWidget::setImages(String const& baseImage, String const& hoverImage, String const& pressedImage, String const& disabledImage) {
  m_baseImage = baseImage;
  m_hoverImage = hoverImage;
  m_pressedImage = pressedImage;
  m_disabledImage = disabledImage;
  if (m_disabledImage.empty() && !m_baseImage.empty())
    m_disabledImage = m_baseImage + Root::singleton().assets()->json("/interface.config:disabledButton").toString();
  updateSize();
}

void ButtonWidget::setCheckedImages(String const& baseImage, String const& hoverImage, String const& pressedImage, String const& disabledImage) {
  m_hasCheckedImages = !baseImage.empty();
  m_baseImageChecked = baseImage;
  m_hoverImageChecked = hoverImage;
  m_pressedImageChecked = pressedImage;
  m_disabledImageChecked = disabledImage;
  if (m_hasCheckedImages && m_disabledImageChecked.empty())
    m_disabledImageChecked = m_baseImageChecked + Root::singleton().assets()->json("/interface.config:disabledButton").toString();
  updateSize();
}

void ButtonWidget::setOverlayImage(String const& overlayImage) {
  m_overlayImage = overlayImage;
}

Vec2I const& ButtonWidget::pressedOffset() const {
  return m_pressedOffset;
}

void ButtonWidget::setPressedOffset(Vec2I const& offset) {
  m_pressedOffset = offset;
}

void ButtonWidget::setText(String const& text) {
  m_text = text;
}

void ButtonWidget::setFontSize(int size) {
  m_fontSize = size;
}

void ButtonWidget::setFontDirectives(String directives) {
  m_fontDirectives = directives;
}

void ButtonWidget::setTextOffset(Vec2I textOffset) {
  m_textOffset = textOffset;
}

void ButtonWidget::setTextAlign(HorizontalAnchor hAnchor) {
  m_hTextAnchor = hAnchor;
}

void ButtonWidget::setFontColor(Color color) {
  m_fontColor = color;
}

void ButtonWidget::setFontColorDisabled(Color color) {
  m_fontColorDisabled = color;
}

void ButtonWidget::setFontColorChecked(Color color) {
  m_fontColorChecked = color;
}

// Although ButtonWidget wraps other widgets from time to time.  These should never be "accessible"
WidgetPtr ButtonWidget::getChildAt(Vec2I const&) {
  return {};
}

void ButtonWidget::disable() {
  m_disabled = true;
  m_pressed = false;
}

void ButtonWidget::enable() {
  m_disabled = false;
}

void ButtonWidget::setEnabled(bool enabled) {
  m_disabled = !enabled;
  m_pressed &= enabled; // and off the pressed flag if the button is no longer enabled.
}

void ButtonWidget::setInvisible(bool invisible) {
  m_invisible = invisible;
}

RectI ButtonWidget::getScissorRect() const {
  if (m_pressed) {
    return RectI::withSize(screenPosition(), size() + m_pressedOffset);
  }
  return RectI::withSize(screenPosition(), size());
}

void ButtonWidget::drawButtonPart(String const& image, Vec2F const& position) {
  auto& guiContext = GuiContext::singleton();
  auto imageSize = guiContext.textureSize(image);
  guiContext.drawInterfaceQuad(image, position + Vec2F(m_buttonBoundSize - imageSize) / 2);
}

void ButtonWidget::updateSize() {
  if (m_invisible || m_baseImage.empty())
    return;
  auto& guiContext = GuiContext::singleton();
  m_buttonBoundSize = guiContext.textureSize(m_baseImage);
  if (!m_hoverImage.empty())
    m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_hoverImage));
  if (!m_pressedImage.empty())
    m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_pressedImage));
  if (!m_baseImageChecked.empty())
    m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_baseImageChecked));
  if (!m_hoverImageChecked.empty())
    m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_hoverImageChecked));
  if (!m_pressedImageChecked.empty())
    m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_pressedImageChecked));
  if (!m_disabledImageChecked.empty())
    m_buttonBoundSize = m_buttonBoundSize.piecewiseMax(guiContext.textureSize(m_disabledImageChecked));

  setSize(Vec2I(m_buttonBoundSize));
}

}