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

#include "UIComponent.hpp"

using namespace Dawn;

void UIComponent::calculateDimensions(
  enum UIComponentAlign align,
  float_t *position,
  float_t *size,
  float_t outerSize,
  float_t innerSize,
  glm::vec2 alignment
) {
  assertNotNull(position);
  assertNotNull(size);

  switch(align) {
    case UI_COMPONENT_ALIGN_STRETCH:
      *position = alignment[0];
      *size = outerSize + (alignment[0] + alignment[1]);
      break;

    case UI_COMPONENT_ALIGN_START:
      *position = alignment[0];
      *size = alignment[1];
      break;

    case UI_COMPONENT_ALIGN_MIDDLE:
      *size = innerSize;
      *position = (outerSize / 2.0f) - (innerSize / 2.0f) + alignment[0];
      break;

    case UI_COMPONENT_ALIGN_END:
      *size = alignment[0];
      *position = outerSize - innerSize - alignment[1];
      break;
    
    default:
      assertUnreachable();
      break;
  }
}

UIComponent::UIComponent(UICanvas *canvas) {
  assertNotNull(canvas);
  this->canvas = canvas;
}

void UIComponent::updatePositions() {
  float_t outerWidth, outerHeight;

  if(this->parent == nullptr) {
    outerWidth = this->canvas->getWidth();
    outerHeight = this->canvas->getHeight();
  } else {
    outerWidth = this->parent->getWidth();
    outerHeight = this->parent->getHeight();
  }

  UIComponent::calculateDimensions(
    this->alignX,
    &this->relativeX,
    &this->width,
    outerWidth,
    this->getContentWidth(),
    glm::vec2(this->alignment[0], this->alignment[2])
  );
  UIComponent::calculateDimensions(
    this->alignY,
    &this->relativeY,
    &this->height,
    outerHeight,
    this->getContentHeight(),
    glm::vec2(this->alignment[1], this->alignment[3])
  );

  // Update children
  auto it = this->children.begin();
  while(it != this->children.end()) {
    (*it)->updatePositions();
    ++it;
  }

  // Fire event
  eventAlignmentUpdated.invoke(this);
}

float_t UIComponent::getWidth() {
  return this->width;
}

float_t UIComponent::getHeight() {
  return this->height;
}

float_t UIComponent::getContentWidth() {
  return this->width;
}

float_t UIComponent::getContentHeight() {
  return this->height;
}

float_t UIComponent::getRelativeX() {
  return this->relativeX;
}

float_t UIComponent::getRelativeY() {
  return this->relativeY;
}

DawnGame * UIComponent::getGame() {
  return this->canvas->getGame();
}

Scene * UIComponent::getScene() {
  return this->canvas->getScene();
}

void UIComponent::setTransform(
  UIComponentAlign xAlign,
  UIComponentAlign yAlign,
  glm::vec4 alignment,
  float_t z
) {
  this->alignX = xAlign;
  this->alignY = yAlign;
  this->alignment = alignment;
  this->z = z;
  this->updatePositions();
}

std::vector<struct ShaderPassItem> UIComponent::getPassItems(
  glm::mat4 projection,
  glm::mat4 view,
  glm::mat4 parent
) {

  // Calculate self transform matrix
  glm::mat4 selfTransform = parent * glm::translate(
    glm::mat4(1.0f),
    glm::vec3(this->relativeX, this->relativeY, this->z)
  );

  // Draw Self
  auto items = this->getSelfPassItems(projection, view, selfTransform);

  // Render children
  auto it = this->children.begin();
  while(it != this->children.end()) {
    vectorAppend(&items, (*it)->getPassItems(projection, view, selfTransform));
    ++it;
  }

  return items;
}

void UIComponent::addChild(UIComponent *child) {
  if(child->parent == this) return;
  if(child->parent != nullptr) child->parent->removeChild(child);
  this->children.push_back(child);
  child->parent = this;
  this->updatePositions();
}

void UIComponent::removeChild(UIComponent *child) {
  assertTrue(child->parent == this);
  auto it = this->children.begin();
  while(it != this->children.end()) {
    if(*it == child) {
      this->children.erase(it);
      break;
    }
    ++it;
  }
  child->parent = nullptr;
}

UIComponent::~UIComponent() {
  
}