#include "CGuiObject.hpp"
#include "CGuiWidgetDrawParms.hpp"
#include "Graphics/CTexture.hpp"

namespace urde
{

CGuiObject::~CGuiObject()
{
    delete x74_child;
    delete x78_nextSibling;
}

void CGuiObject::Update(float dt)
{
    if (x74_child)
        x74_child->Update(dt);
    if (x78_nextSibling)
        x78_nextSibling->Update(dt);
}

void CGuiObject::Draw(const CGuiWidgetDrawParms& parms) const
{
    if (x74_child)
        x74_child->Draw(parms);
    if (x78_nextSibling)
        x78_nextSibling->Draw(parms);
}

void CGuiObject::MoveInWorld(const zeus::CVector3f& vec)
{
    if (x70_parent)
        x70_parent->RotateW2O(vec);
    x4_localXF.origin += vec;
    Reorthogonalize();
    RecalculateTransforms();
}

void CGuiObject::SetLocalPosition(const zeus::CVector3f& pos)
{
    MoveInWorld(pos - x4_localXF.origin);
}

void CGuiObject::RotateReset()
{
    x4_localXF.basis = zeus::CMatrix3f::skIdentityMatrix3f;
    Reorthogonalize();
    RecalculateTransforms();
}

zeus::CVector3f CGuiObject::RotateW2O(const zeus::CVector3f& vec) const
{
    return x34_worldXF.transposeRotate(vec);
}

zeus::CVector3f CGuiObject::RotateO2P(const zeus::CVector3f& vec) const
{
    return x4_localXF.rotate(vec);
}

zeus::CVector3f CGuiObject::RotateTranslateW2O(const zeus::CVector3f& vec) const
{
    return x34_worldXF.transposeRotate(vec - x34_worldXF.origin);
}

void CGuiObject::MultiplyO2P(const zeus::CTransform& xf)
{
    x4_localXF.origin += x64_rotationCenter;
    x4_localXF = xf * x4_localXF;
    x4_localXF.origin -= x64_rotationCenter;
    Reorthogonalize();
    RecalculateTransforms();
}

void CGuiObject::AddChildObject(CGuiObject* obj, bool makeWorldLocal, bool atEnd)
{
    obj->x70_parent = this;

    if (!x74_child)
    {
        x74_child = obj;
    }
    else if (atEnd)
    {
        CGuiObject* prev = nullptr;
        CGuiObject* cur = x74_child;
        for (; cur ; cur = cur->x78_nextSibling) {prev = cur;}
        if (prev)
            prev->x78_nextSibling = obj;
    }
    else
    {
        obj->x78_nextSibling = x74_child;
        x74_child = obj;
    }

    if (makeWorldLocal)
    {
        zeus::CVector3f negParentWorld = -x34_worldXF.origin;
        zeus::CMatrix3f basisMat(
            x34_worldXF.basis[0] / x34_worldXF.basis[0].magnitude(),
            x34_worldXF.basis[1] / x34_worldXF.basis[1].magnitude(),
            x34_worldXF.basis[2] / x34_worldXF.basis[2].magnitude());
        zeus::CVector3f xfWorld = basisMat * negParentWorld;
        obj->x4_localXF = zeus::CTransform(basisMat, xfWorld) * obj->x34_worldXF;
    }

    RecalculateTransforms();
}

CGuiObject* CGuiObject::RemoveChildObject(CGuiObject* obj, bool makeWorldLocal)
{
    CGuiObject* prev = nullptr;
    CGuiObject* cur = x74_child;
    for (; cur && cur != obj ; cur = cur->x78_nextSibling) {prev = cur;}
    if (!cur)
        return nullptr;
    if (prev)
        prev->x78_nextSibling = cur->x78_nextSibling;
    cur->x78_nextSibling = nullptr;
    cur->x70_parent = nullptr;

    if (makeWorldLocal)
        cur->x4_localXF = cur->x34_worldXF;
    cur->RecalculateTransforms();

    return cur;
}

void CGuiObject::RecalculateTransforms()
{
    if (x70_parent)
    {
        x4_localXF.origin += x64_rotationCenter;
        x34_worldXF = x70_parent->x34_worldXF * x4_localXF;
        x4_localXF.origin -= x64_rotationCenter;
        x34_worldXF.origin -= x64_rotationCenter;
    }
    else
    {
        x34_worldXF = x4_localXF;
    }

    if (x78_nextSibling)
        x78_nextSibling->RecalculateTransforms();
    if (x74_child)
        x74_child->RecalculateTransforms();
}

void CGuiObject::Reorthogonalize()
{
    static bool Global = false;
    if (Global)
    {
        x4_localXF.orthonormalize();
        RecalculateTransforms();
    }
}

void CGuiObject::SetO2WTransform(const zeus::CTransform& xf)
{
    x4_localXF = GetParent()->x34_worldXF.inverse() * xf;
    RecalculateTransforms();
}

}