#include <Redacted3D/engine/world.h>
#include <Redacted3D/types/entity/moby.h>


int DataModel::getEntityCount() const {
    return this->children.size();
}

int DataModel::getMobyCount() const {
    int count;
    for (auto& e : this->children)
        if (auto* m = dynamic_cast<Moby*>(e))
            count++;
    return count;
}

void Entity::SetParent(Entity *parent) {
    // hold a reference to this so it doesn't get collected as we're working with it
    Entity *oldParent = this->parent;
    if (parent == oldParent)
        return;
    // Don't allow for an instance to be parented to itself
    if (this == parent)
        throw std::runtime_error("Cannot parent a widget to itself");
    if (this->IsAncestorOf(parent))
        throw std::runtime_error("Cannot create circular object reference");

    for (Entity *ancestor: this->GetAncestors()) {
        if (oldParent && !ancestor->IsAncestorOf(parent) && parent != ancestor) {
            ancestor->DescendantRemoved(this);
            for (Entity *descendant: this->GetDescendants()) {
                ancestor->DescendantRemoved(descendant);
            }
        }
    }

    // Remove ourselves from our parent (if we have one)
    if (this->parent) {
        parent->children.erase(std::remove(parent->children.begin(), parent->children.end(), this),
                               parent->children.end());
    }
    // Update our old parent to the new one
    this->parent = parent;
    // If our parent is set to nullptr, we can't update it's vector of children
    if (!parent) { return; }
    parent->children.emplace_back(this);
    for (Entity* ancestor: this->GetAncestors()) {
        if (!oldParent || !(oldParent->IsDescendantOf(parent) && oldParent != ancestor)) {
            ancestor->DescendantAdded(this);
            for (auto* descendant : this->GetDescendants()) {
                ancestor->DescendantAdded(descendant);
            }
        }
    }
}

bool Entity::IsDescendantOf(Entity *ancestor) {
    if (ancestor == nullptr)
        return false;
    Entity *instance = this;
    while (instance->GetParent()) {
        instance = instance->GetParent();
        if (instance == ancestor)
            return true;
    }
    return false;
}

bool Entity::IsAncestorOf(Entity *descendant) {
    if (descendant == nullptr)
        return false;
    Entity *instance = descendant;
    while (instance->GetParent()) {
        instance = instance->GetParent();
        if (instance == this)
            return true;
    }
    return false;
}

std::vector<Entity *> Entity::GetChildren() {
    return this->children;
}

std::vector<Entity *> Entity::GetAncestors() {
    std::vector<Entity *> ancestors;
    for (Entity *ancestor = this->parent; ancestor; ancestor = ancestor->parent) {
        ancestors.push_back(ancestor);
    }
    return ancestors;
}

std::vector<Entity *> Entity::GetDescendants() {
    std::vector<Entity *> descendants;
    for (auto& child: this->children) {
        descendants.push_back(child);
        std::vector<Entity *> recursiveDescendants = child->GetDescendants();
        descendants.insert(descendants.end(), recursiveDescendants.begin(), recursiveDescendants.end());
    }
    return descendants;
}

Entity *Entity::Add(Entity *newChild) {
    newChild->SetParent(this);
    return newChild;
}

Entity *Entity::GetFamilyTreeRoot() const {

    //if (JUI::Scene* scene = dynamic_cast<JUI::Scene*>(parent); scene != nullptr)

    // This is retarded, Fix ASAP!
    auto parent = this->GetParent();
    if (parent->GetParent() == nullptr)
        return parent;
    return parent->GetFamilyTreeRoot();
}

void World::setAmbientLightColor(GLfloat red, GLfloat green, GLfloat blue) {
    GLfloat ambient[4] = {red, green, blue, 0.0f};
    globalLightColor.x = red; globalLightColor.y = green; globalLightColor.z = blue;
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);
}

Vector3 World::getAmbientLightColor() {
    return globalLightColor;
}

void World::setGlobalFogColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) {
    GLfloat fog[4] = {red, green, blue, alpha};
    glFogfv(GL_FOG_COLOR, fog);
    globalFogColor.x = red; globalFogColor.y = green; globalFogColor.z = blue; globalFogColor.w = alpha;
}

Vector4 World::getGlobalFogColor() {
    return globalFogColor;
}

void World::setGlobalFogRange(GLfloat start, GLfloat end) {
    glFogf(GL_FOG_START, start);
    glFogf(GL_FOG_END, end);
    globalFogRange = {start, end};

}

Vector2 World::getGlobalFogRange() {
    return globalFogRange;
}

void World::setGlobalFogMode(GLenum mode) {
    glFogi(GL_FOG_MODE, mode);
    globalFogMode = mode;
}

GLenum World::getGlobalFogMode() const {
    return globalFogMode;
}

GLfloat World::getGlobalFogDensity() const {
    return globalFogDensity;
}

void World::setGlobalFogDensity(GLfloat density) {
    glFogf(GL_FOG_DENSITY, density);
    globalFogDensity = density;
}

Camera *World::getActiveCamera() {
    return activeCamera;
}

void World::setActiveCamera(Camera *camera) {
    activeCamera = camera;
}
