ZO_ABOVE_SCENEGRAPH_DRAW_LEVEL = 100000

ZO_SceneGraph = ZO_Object:Subclass()

function ZO_SceneGraph:New(...)
    local obj = ZO_Object.New(self)
    obj:Initialize(...)
    return obj
end

function ZO_SceneGraph:Initialize(canvasControl, debugModeEnabled)
    self.nodes = {}
    self.canvasControl = canvasControl
    canvasControl:SetHandler("OnUpdate", function() self:OnUpdate() end)
    self.debugModeEnabled = debugModeEnabled
    self.debugTextureId = 1
    self.needsRender = false
    self.cameraNode = self:CreateNode("camera")
    self.cameraZ = -1
end

function ZO_SceneGraph:IsHidden()
    return self.canvasControl:IsHidden()
end

function ZO_SceneGraph:GetCameraX()
    return -self.cameraNode:GetX()
end

function ZO_SceneGraph:AddCameraX(dx)
    self.cameraNode:AddX(-dx)
end

function ZO_SceneGraph:SetCameraX(x)
    self.cameraNode:SetX(-x)
end

function ZO_SceneGraph:GetCameraY()
    return -self.cameraNode:GetY()
end

function ZO_SceneGraph:AddCameraY(dy)
    self.cameraNode:AddY(-dy)
end

function ZO_SceneGraph:SetCameraY(y)
    self.cameraNode:SetY(-y)
end

function ZO_SceneGraph:AddCameraRotation(radians)
    self.cameraNode:AddRotation(-radians)
end

function ZO_SceneGraph:SetCameraRotation(radians)
    self.cameraNode:SetRotation(-radians)
end

function ZO_SceneGraph:GetCameraZ()
    return self.cameraZ
end

function ZO_SceneGraph:AddCameraZ(z)
    self:SetCameraZ(self.cameraZ + z)
end

function ZO_SceneGraph:SetCameraZ(z)
    if self.cameraZ ~= z then
        self.cameraZ = z
        self.cameraNode:SetDirty(true)
    end
end

function ZO_SceneGraph:GetCameraNode()
    return self.cameraNode
end

function ZO_SceneGraph:GetCanvasControl()
    return self.canvasControl
end

function ZO_SceneGraph:GetNode(name)
    return self.nodes[name]
end

function ZO_SceneGraph:CreateNode(name)
    local node = ZO_SceneGraphNode:New(self, name)

    if self.debugModeEnabled then
        self:AddDebugTextureToNode(node)
    end

    self.nodes[name] = node

    return node
end

function ZO_SceneGraph:RemoveNode(node)
    -- after this point, this node will no longer participate in rendering, the node should eventually be garbage collected.
    -- it's important that you nil out references in application code too to achieve this.
    -- child controls do not hold references to their scene node, so they will
    -- also become "free-floating". Likewise, it's your responsibility to reuse
    -- those controls to not leak memory.
    node:SetParent(nil)
    self.nodes[node:GetName()] = nil
    local children = node:GetChildren()
    if children then
        for _, childNode in ipairs(children) do
            self:RemoveNode(childNode)
        end
    end
end

function ZO_SceneGraph:AddDebugTextureToNode(node)
    -- shortcut so you can see where a node is placed visually
    -- draws a purple box centered on the node's origin point
    local debugTexture = GetWindowManager():CreateControl(string.format("%sDebugTexture%d", self.canvasControl:GetName(), self.debugTextureId), self.canvasControl, CT_TEXTURE)
    self.debugTextureId = self.debugTextureId + 1
    debugTexture:SetDimensions(20, 20)
    debugTexture:SetColor(0.7, 0.7, 1, 1)
    debugTexture:SetPixelRoundingEnabled(false)
    node:AddTexture(debugTexture, 0, 0, 0)
end

function ZO_SceneGraph:OnSceneNodeDirty()
    self.needsRender = true
end

function ZO_SceneGraph:OnUpdate()
    if self.needsRender then
        self.needsRender = false
        self:Render()
    end
end

do
    local function RenderNode(node, dirtyUpstream)
        local dirty = dirtyUpstream or node:IsDirty()
        if dirty then
            node:BuildWorldViewMatrix()
            node:Render()
        end

        local children = node:GetChildren()
        if children ~= nil then
            for i = 1, #children do
                local child = children[i]
                RenderNode(child, dirty)
            end
        end
    end

    function ZO_SceneGraph:Render()
        local NOT_DIRTY_UPSTREAM = false
        RenderNode(self.cameraNode, NOT_DIRTY_UPSTREAM)
    end
end
