#include <frostbite2D/2d/actor.h>
#include <SDL2/SDL.h>

namespace frostbite2D {

Actor::Actor()
  : parent_(nullptr)
  , scene_(nullptr)
  , rotation_(0.0f)
  , visible_(true)
  , zOrder_(0)
  , opacity_(1.0f)
  , anchor_(Vec2(0.0f, 0.0f)) {
}

void Actor::SetOpacity(float opacity) {
  opacity_ = std::clamp(opacity, 0.0f, 1.0f);
}

float Actor::GetWorldOpacity() const {
  float worldOpacity = opacity_;
  if (parent_) {
    worldOpacity *= parent_->GetWorldOpacity();
  }
  return worldOpacity;
}

void Actor::SetPosition(const Vec2& pos) {
  position_ = pos;
  markTransformDirty();
}

void Actor::SetPosition(float x, float y) {
  position_ = Vec2(x, y);
  markTransformDirty();
}

void Actor::SetRotation(float rotation) {
  rotation_ = rotation;
  markTransformDirty();
}

void Actor::SetScale(const Vec2& scale) {
  scale_ = scale;
  markTransformDirty();
}

void Actor::SetScale(float scale) {
  scale_ = Vec2(scale, scale);
  markTransformDirty();
}

void Actor::SetSize(const Vec2& size) {
  size_ = size;
  markTransformDirty();
}

void Actor::SetSize(float width, float height) {
  size_ = Vec2(width, height);
  markTransformDirty();
}

void Actor::SetAnchor(const Vec2& anchor) {
  anchor_ = anchor;
  markTransformDirty();
}

void Actor::SetAnchor(float x, float y) {
  anchor_ = Vec2(x, y);
  markTransformDirty();
}

const Transform2D& Actor::GetLocalTransform() const {
  if (transformDirty_) {
    updateLocalTransform();
  }
  return localTransform_;
}

const Transform2D& Actor::GetWorldTransform() const {
  if (transformDirty_) {
    updateLocalTransform();
    updateWorldTransform();
    transformDirty_ = false;
  }
  return worldTransform_;
}

void Actor::markTransformDirty() {
  transformDirty_ = true;
  OnTransformChanged();
  for (auto it = children_.begin(); it != children_.end(); ++it) {
    if (*it) {
      (*it)->markTransformDirty();
    }
  }
}

void Actor::updateLocalTransform() const {
  Vec2 anchorOffset = Vec2(anchor_.x * size_.x, anchor_.y * size_.y);
  localTransform_ = Transform2D::translation(position_.x, position_.y) *
                    Transform2D::rotation(rotation_) *
                    Transform2D::scaling(scale_.x, scale_.y) *
                    Transform2D::translation(-anchorOffset.x, -anchorOffset.y);
}

void Actor::updateWorldTransform() const {
  if (parent_) {
    worldTransform_ = parent_->GetWorldTransform() * localTransform_;
  } else {
    worldTransform_ = localTransform_;
  }
}

Actor::~Actor() {
  RemoveAllChildren();
}

void Actor::Update(float deltaTime) {
  UpdateChildren(deltaTime);
}

void Actor::Render() {
  RenderChildren();
}

void Actor::insertChildByZOrder(Ptr<Actor> child) {
  if (!child) {
    return;
  }

  if (children_.IsEmpty()) {
    children_.PushBack(child);
    return;
  }

  auto it = children_.begin();
  while (it != children_.end()) {
    if (*it && (*it)->GetZOrder() > child->GetZOrder()) {
      children_.InsertBefore(child, *it);
      return;
    }
    ++it;
  }

  children_.PushBack(child);
}

void Actor::AddChild(Ptr<Actor> child) {
  if (!child) {
    return;
  }

  if (child->GetParent() == this) {
    return;
  }

  if (child->GetParent()) {
    child->GetParent()->RemoveChild(child);
  }

  child->SetParent(this);
  child->SetScene(scene_);
  insertChildByZOrder(child);
}

void Actor::SetZOrder(int zOrder) {
  if (zOrder_ == zOrder) {
    return;
  }

  zOrder_ = zOrder;

  if (parent_) {
    parent_->RemoveChild(this);
    parent_->AddChild(this);
  }
}

void Actor::RemoveChild(Ptr<Actor> child) {
  if (!child || child->GetParent() != this) {
    return;
  }

  child->SetParent(nullptr);
  child->SetScene(nullptr);
  children_.Remove(child);
}

void Actor::RemoveAllChildren() {
  while (!children_.IsEmpty()) {
    Ptr<Actor> child = children_.GetFirst();
    RemoveChild(child);
  }
}

void Actor::UpdateChildren(float deltaTime) {
  for (auto it = children_.begin(); it != children_.end(); ++it) {
    if (*it && (*it)->IsVisible()) {
      (*it)->Update(deltaTime);
    }
  }
}

void Actor::RenderChildren() {
  for (auto it = children_.begin(); it != children_.end(); ++it) {
    if (*it && (*it)->IsVisible()) {
      (*it)->Render();
    }
  }
}

}

