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

#include "Transform.hpp"
#include "scene/SceneItem.hpp"

using namespace Dawn;

Transform::Transform(SceneItem &item) :
  item(item),
  transformLocal(1.0f),
  transformWorld(1.0f)
{
  this->updateLocalValuesFromLocalTransform();
}

void Transform::updateLocalValuesFromLocalTransform() {
  glm::vec3 skew;
  glm::vec4 perspective;
  glm::decompose(
    this->transformLocal,
    this->localScale,
    this->localRotation,
    this->localPosition,
    skew, perspective
  );
}

void Transform::updateLocalTransformFromLocalValues() {
  glm::mat4 translate = glm::translate(glm::mat4(1.0), this->localPosition);
  glm::mat4 rotate = glm::mat4_cast(this->localRotation);
  glm::mat4 scale = glm::scale(glm::mat4(1.0), this->localScale);
  this->transformLocal = translate * rotate * scale;
  this->updateWorldTransformFromLocalTransform();
}

void Transform::updateWorldTransformFromLocalTransform() {
  glm::mat4 newWorld(1.0f);
  auto parent = this->getParent();
  if(parent != nullptr) newWorld = parent->getWorldTransform();
  this->transformWorld = newWorld * transformLocal;
}

void Transform::updateLocalTransformFromWorldTransform() {
  glm::mat4 parentMat(1.0f);
  auto parent = this->getParent();
  if(parent != nullptr) parentMat = parent->getWorldTransform();
  this->transformLocal = parentMat / this->transformWorld;
  this->updateLocalValuesFromLocalTransform();
}

void Transform::updateChildrenTransforms() {
  auto it = this->children.begin();
  while(it != this->children.end()) {
    (*it)->updateWorldTransformFromLocalTransform();
    ++it;
  }
}

void Transform::lookAt(glm::vec3 pos, glm::vec3 look) {
  this->lookAt(pos, look, glm::vec3(0, 1, 0));
}

void Transform::lookAt(glm::vec3 pos, glm::vec3 look, glm::vec3 up) {
  this->setWorldTransform(glm::lookAt(pos, look, up));
}


glm::vec3 Transform::getLocalPosition() {
  return this->localPosition;
}

void Transform::setLocalPosition(glm::vec3 position) {
  this->localPosition = position;
  this->updateLocalTransformFromLocalValues();
  this->updateChildrenTransforms();
}

glm::vec3 Transform::getLocalScale() {
  return this->scale;
}

void Transform::setLocalScale(glm::vec3 scale) {
  this->localScale = scale;
  this->updateLocalTransformFromLocalValues();
  this->updateChildrenTransforms();
}

glm::quat Transform::getLocalRotation() {
  return this->localRotation;
}

void Transform::setLocalRotation(glm::quat rotation) {
  this->localRotation = rotation;
  this->updateLocalTransformFromLocalValues();
  this->updateChildrenTransforms();
}


glm::mat4 Transform::getLocalTransform() {
  return this->transformLocal;
}

void Transform::setLocalTransform(glm::mat4 transform) {
  this->transformLocal = transform;

  this->updateLocalValuesFromLocalTransform();
  this->updateChildrenTransforms();
}

glm::mat4 Transform::getWorldTransform() {
  return this->transformWorld;
}

void Transform::setWorldTransform(glm::mat4 transform) {
  this->transformWorld = transform;
  this->updateLocalTransformFromWorldTransform();
  this->updateChildrenTransforms();
}


void Transform::setParent(Transform *parent) {
  if(parent == this) throw "Cannot self reference";
  
  auto currentParent = this->getParent();
  if(currentParent == parent) return;

  if(currentParent != nullptr) {
    auto it = currentParent->children.begin();
    while(it != currentParent->children.end()) {
      if(*it == this) {
        currentParent->children.erase(it);
        break;
      }
      ++it;
    }
  }

  this->parent = parent;
  if(parent != nullptr) parent->children.push_back(this);
}

Transform * Transform::getParent() {
  return this->parent;
}

Transform::~Transform() {
  this->setParent(nullptr);
  auto it = this->parent->children.begin();
  while(it != this->parent->children.end()) {
    (*it)->setParent(nullptr);
    ++it;
  }
}