// Copyright (c) 2022 Dominic Masters
// 
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

#include "VisualNovelTextbox.hpp"
#include "game/DawnGame.hpp"
#include "visualnovel/VisualNovelManager.hpp"

using namespace Dawn;

VisualNovelTextbox::VisualNovelTextbox(UICanvas *canvas) :
  UIComponent(canvas),
  selfParent(canvas),
  border(canvas),
  label(canvas)
{
  // Self Parent
  this->selfParent.setTransform(
    UI_COMPONENT_ALIGN_STRETCH, UI_COMPONENT_ALIGN_STRETCH, 
    glm::vec4(0, 0, 0, 0),
    0.0f
  );

  // Border
  this->selfParent.addChild(&this->border);
  this->border.setTransform(
    UI_COMPONENT_ALIGN_STRETCH, UI_COMPONENT_ALIGN_STRETCH, 
    glm::vec4(0, 0, 0, 0),
    0.0f
  );

  // Label
  this->selfParent.addChild(&this->label);
  this->label.startQuad = 0;
  this->label.quadCount = 0;
  
  this->canvas->getScene()->eventSceneUnpausedUpdate.addListener(
    this, &VisualNovelTextbox::textboxOnSceneUpdate
  );
}

void VisualNovelTextbox::show() {
  if(this->isVisible()) return;
  this->visible = true;
  this->addChild(&this->selfParent);

  if(this->talkSound != nullptr) {
    auto vnManager = this->getVisualNovelManager();
    assertNotNull(vnManager);
    assertNotNull(vnManager->audioCharacter);

    vnManager->audioCharacter->stop();
    vnManager->audioCharacter->setAudioData(this->talkSound);
    vnManager->audioCharacter->loop = true;
    vnManager->audioCharacter->play();
  }
}

void VisualNovelTextbox::hide() {
  if(!this->isVisible()) return;

  if(this->talkSound != nullptr) {
    auto vnManager = this->getVisualNovelManager();
    assertNotNull(vnManager);
    assertNotNull(vnManager->audioCharacter);
    vnManager->audioCharacter->stop();
  }

  this->visible = false;
  this->removeChild(&this->selfParent);
  this->eventHidden.invoke();
}

bool_t VisualNovelTextbox::isVisible() {
  return this->visible;
}

VisualNovelManager * VisualNovelTextbox::getVisualNovelManager() {
  return this->getScene()->findComponent<VisualNovelManager>();
}

void VisualNovelTextbox::updatePositions() {
  UIComponent::updatePositions();

  this->lineCurrent = 0;
  this->timeCharacter = 0;

  this->label.setTransform(
    UI_COMPONENT_ALIGN_STRETCH,
    UI_COMPONENT_ALIGN_STRETCH,
    glm::vec4(
      this->border.getBorderSize() + this->labelPadding,
      this->border.getBorderSize() + this->labelPadding
    ),
    1.0f
  );

  this->label.startQuad = 0;
  this->label.quadCount = 0;
}

void VisualNovelTextbox::textboxOnSceneUpdate() {
  DawnGame *game = this->canvas->getGame();

  if(this->hasRevealedAllCurrentCharacters()) {
    if(this->hasRevealedAllCharacters()) {
      if(game->inputManager.isPressed(INPUT_BIND_ACCEPT)) {
        this->hide();
      }
    } else {
      if(game->inputManager.isPressed(INPUT_BIND_ACCEPT)) {
        this->lineCurrent += this->getCountOfVisibleLines();
        this->label.startQuad = 0;
        for(int32_t i = 0; i < this->lineCurrent; i++) {
          this->label.startQuad += this->label.measure.getQuadsOnLine(i);
        }
        this->label.quadCount = 0;
        this->timeCharacter = 0.0f;

        this->label.setTransform(
          UI_COMPONENT_ALIGN_STRETCH,
          UI_COMPONENT_ALIGN_STRETCH,
          glm::vec4(
            glm::vec2(
              this->border.getBorderSize().x + this->labelPadding.x,
              this->border.getBorderSize().y + this->labelPadding.y -
              this->label.measure.getHeightOfLineCount(this->lineCurrent)
            ),
            this->border.getBorderSize() + this->labelPadding
          ),
          5.0f
        );
        this->eventNewPage.invoke();
      }
    }
    
    if(this->talkSound != nullptr) {
      auto vnManager = this->getVisualNovelManager();
      assertNotNull(vnManager);
      assertNotNull(vnManager->audioCharacter);
      vnManager->audioCharacter->stop();
    }

    return;
  }

  auto lastTimeCharacter = mathFloor<int32_t>(this->timeCharacter);
  if(game->inputManager.isDown(INPUT_BIND_ACCEPT)) {
    this->timeCharacter += game->timeManager.delta * VISUAL_NOVEL_TEXTBOX_SPEED_FASTER;
  } else {
    this->timeCharacter += game->timeManager.delta * VISUAL_NOVEL_TEXTBOX_SPEED;
  }
  auto newTimeCharacter = mathFloor<int32_t>(this->timeCharacter);
  if(newTimeCharacter == lastTimeCharacter) return;

  this->label.quadCount = newTimeCharacter;
  this->eventCharacterRevealed.invoke();
}

int32_t VisualNovelTextbox::getCountOfVisibleLines() {
  int32_t i = 1;
  glm::vec2 innerSize = this->border.getInnerSize() - this->labelPadding;

  for(i = this->label.measure.getLineCount(); i > 0; --i) {
    if(innerSize.y >= this->label.measure.getHeightOfLineCount(i)) {
      return i;
    }
  }
  return this->label.measure.getLineCount();
}

std::vector<struct ShaderPassItem> VisualNovelTextbox::getSelfPassItems(
  glm::mat4 projection,
  glm::mat4 view,
  glm::mat4 transform
) {
  return std::vector<struct ShaderPassItem>();
}

void VisualNovelTextbox::setFont(Font *font) {
  this->label.setFont(font);
  this->label.updateMesh();
}

void VisualNovelTextbox::setText(std::string key, float_t fontSize) {
  this->label.setText(key);
  this->label.setFontSize(fontSize);
  this->label.updateMesh();
}

void VisualNovelTextbox::setCharacter(VisualNovelCharacter *character) {
  this->character = character;
  this->talkSound = nullptr;
}

void VisualNovelTextbox::setTalkingSound(AudioAsset *asset) {
  this->talkSound = asset;
}

void VisualNovelTextbox::setText(std::string key) {
  this->label.setText(key);
  this->label.updateMesh();
}

void VisualNovelTextbox::setFontSize(float_t fontSize) {
  this->label.setFontSize(fontSize);
  this->label.updateMesh();
}

float_t VisualNovelTextbox::getFontSize() {
  return this->label.getFontSize();
}

void VisualNovelTextbox::setLabelPadding(glm::vec2 padding) {
  this->labelPadding = padding;
  this->updatePositions();
}

glm::vec2 VisualNovelTextbox::getLabelPadding() {
  return this->labelPadding;
}

bool_t VisualNovelTextbox::hasRevealedAllCurrentCharacters() {
  int32_t quadsTotal = 0;
  for(
    int32_t i = this->lineCurrent;
    i < mathMin<int32_t>(
      this->label.measure.getLineCount(),
      this->lineCurrent + this->getCountOfVisibleLines()
    );
    i++
  ) {
    quadsTotal += this->label.measure.getQuadsOnLine(i);
  }
  return mathFloor<int32_t>(this->timeCharacter) >= quadsTotal;
}

bool_t VisualNovelTextbox::hasRevealedAllCharacters() {
  return (
    this->lineCurrent + this->getCountOfVisibleLines() >=
    this->label.measure.getLineCount()
  );
}

VisualNovelTextbox::~VisualNovelTextbox() {
  this->canvas->getScene()->eventSceneUnpausedUpdate.removeListener(
    this, &VisualNovelTextbox::textboxOnSceneUpdate
  );
}