#include "scene.h"
#include "scene_node_entity.h"
#include "transformable.h"
#include "utils/debug.h"
#include "utils/drop.h"
#include "utils/mirror.h"

//! Tick a node and all children recursively
static
void Internal_SceneNodeTick(SceneNode *self, double delta) {
    // tick self first
    self->entity.tc->tick(self->entity.data, delta);
    // fix the number of elements to update now,
    // so that if/when new children are added, they don't get ticked until next frame
    size_t const len = self->children.len;
    for(size_t i = 0; i < len; ++i)
        Internal_SceneNodeTick(*list_at_as(SceneNode*, &self->children, i), delta);
}

//! Update global transform of a node
static
void Internal_SceneNodeUpdateTransform(SceneNode *self) {
    if(TC_MIRRORS(self->entity, Transformable)) {
        Transformable transformable = TC_CAST(self->entity, Transformable);
        if(transformable.tc->get_dirty_bit(transformable.data)) {
            transformable.tc->force_update(transformable.data);
        }
    }
}

//! Update global transforms of all children recursively
static
void Internal_SceneNodeUpdateTransformRecursive(SceneNode *self, bool is_dirty) {
    if(TC_MIRRORS(self->entity, Transformable)) {
        Transformable transformable = TC_CAST(self->entity, Transformable);
        is_dirty = is_dirty || transformable.tc->get_dirty_bit(transformable.data);
        if(is_dirty) {
            transformable.tc->force_update(transformable.data);
            transformable.tc->clear_dirty_bit(transformable.data);
        }
    } else {
        is_dirty = false;
    }
    list_foreach(SceneNode **,child, &self->children)
        Internal_SceneNodeUpdateTransformRecursive(*child, is_dirty);
}

//! Remove a single node (does NOT recurse to children children) from this scene.
static
void Internal_SceneRemoveNode(Scene *self, SceneNode *node) {
    // notify the node entity that it has exited the tree
    node->entity.tc->exit_tree(node->entity.data);
    // sever relationship
    node->scene = NULL;
    size_t idx = list_find(&self->nodes, &node);
    list_erase(&self->nodes, idx);
}

//! Remove a node and all children from this scene recursively starting at the bottom of the tree.
static
void Internal_SceneRemoveNodeRecursive(Scene *self, SceneNode *node) {
    // recurse to node's children first
    list_foreach(SceneNode **,child, &node->children)
        Internal_SceneRemoveNodeRecursive(self, *child);
    // remove node
    Internal_SceneRemoveNode(self, node);
}

//! Set the scene on a node (does NOT recurse to children), calls enter_tree
static
void Internal_SceneNodeSetScene(SceneNode *self, Scene *scene) {
    ASSERT_RETURN(scene != NULL,, "Internal_SceneNodeSetScene: Attempting to set node scene to NULL. Use Internal_SceneRemoveNodeRercursive instead.");
    self->scene = scene;
    self->entity.tc->enter_tree(self->entity.data);
}

//! Register a node and it's children as part of a scene recursively.
//! Will call enter_tree on all node entities
static
void Internal_SceneAddNode(Scene *self, SceneNode *node) {
    ASSERT_RETURN(node->scene == NULL,, "Attempting to add node that is already in a scene, remove it from that scene first");
    ASSERT_RETURN(list_find(&self->nodes, &node) == self->nodes.len,, "Attempting to add node that is already in this scene");
    // add to internal list
    list_add(&self->nodes, &node);
    // establish two-way relationship
    Internal_SceneNodeSetScene(node, self);
    Internal_SceneNodeUpdateTransform(node);
    // do the same for all children (and their children) recursively
    list_foreach(SceneNode **,child, &node->children)
        Internal_SceneAddNode(self, *child);
}

SceneNode *CreateSceneNode(SceneNodeEntity entity) {
    // initialize memory
    SceneNode *self = new(SceneNode);
    ASSERT_RETURN(self != NULL, NULL, "CreateSceneNode: Failed to allocate new SceneNode");
    self->children = list_from_type(SceneNode*);
    self->entity = tc_null(SceneNodeEntity);
    self->parent = NULL;
    self->scene = NULL;
    // attach initial entity
    SceneNodeAttachEntity(self, entity);
    return self;
}

void DestroySceneNode(SceneNode *self) {
    // destroy children *first*, then delete self
    list_foreach(SceneNode **, child, &self->children)
        DestroySceneNode(*child);
    // remove node from scene if it is part of one
    if(self->scene != NULL)
        Internal_SceneRemoveNode(self->scene, self);
    // destroy entity as well
    if(!tc_is_null(self->entity))
        self->entity.drop->drop(self->entity.data);
    free(self);
}

SceneNode *SceneNodeGetChild(SceneNode *self, size_t idx) {
    return *list_at_as(SceneNode*, &self->children, idx);
}

void SceneNodeAddChild(SceneNode *self, SceneNode *child) {
    // catch logic error of trying to attach a child that already has a parent
    ASSERT_RETURN(child->parent == NULL,, "SceneNodeAddChild: New child node already has a parent");
    // register parent-child relationship
    list_add(&self->children, &child);
    child->parent = self;
    // add child to same scene as self
    if(self->scene != NULL)
        Internal_SceneAddNode(self->scene, child);
}

void SceneNodeRemoveChild(SceneNode *self, SceneNode *child) {
    // catch error of attempting to remove non-child node
    ASSERT_RETURN(child->parent == self,, "SceneNodeRemoveChild: Cannot remove node that is not a child of this node.");
    // get the index and use it to ensure that the child is actually registered with the parent
    size_t const idx = list_find(&self->children, child);
    ASSERT_RETURN(idx == self->children.len,, "IMPORTANT: SceneNodeRemoveChild: child is not registered with parent.");
    if(self->scene != NULL) {
        Internal_SceneRemoveNodeRecursive(child->scene, child);
    }
    // use the index to erase the object from the list
    list_erase(&self->children, idx);
}

void SceneNodeAttachEntity(SceneNode *self, SceneNodeEntity entity) {
    ASSERT_RETURN_WARN(!tc_is_null(entity),, "SceneNodeAttachEntity: Cannot attach null entity, use SceneNodeDetachEntity to remove entity instead.");
    // establish two-way relationship
    entity.tc->set_node(entity.data, self);
    self->entity = entity;
    // if this node is in a tree, notify the entity that it has entered one.
    if(self->scene != NULL)
        self->entity.tc->enter_tree(self->entity.data);
}

SceneNodeEntity SceneNodeDetachEntity(SceneNode *self) {
    ASSERT_RETURN_WARN(!tc_is_null(self->entity), tc_null(SceneNodeEntity), "SceneNodeDetachEntity: Cannot detach entity from node with no entity.");
    // store the entity to return it.
    SceneNodeEntity e = self->entity;
    // null entity <-> node relationship
    e.tc->set_node(e.data, NULL);
    self->entity = tc_null(SceneNodeEntity);
    return e;
}

Scene *CreateScene(SceneNode *root) {
    Scene *scene = new(Scene);
    ASSERT_RETURN(scene != NULL, NULL, "CreateScene: Failed to allocate scene");
    scene->main_camera = NULL;
    scene->nodes = list_from_type(SceneNode*);
    scene->root = root;
    Internal_SceneAddNode(scene, root);
    return scene;
}

void DestroyScene(Scene *self) {
    DestroySceneNode(self->root); // recurses to the rest of the scene
    list_empty(&self->nodes);
    free(self);
}

void SceneTick(Scene* self, double delta_time) {
    Internal_SceneNodeTick(self->root, delta_time);
    Internal_SceneNodeUpdateTransformRecursive(self->root, false);
}
