--[[
    The tree node
--]]

local ZO_TreeControlNode = ZO_Object:Subclass()

function ZO_TreeControlNode:New(myTree, controlData, myParent, childIndent)
    local node = ZO_Object.New(self)

    node.m_OwningTree       = myTree
    node.m_Control          = controlData
    node.m_Expanded         = true
    node.m_Showing          = true
    node.m_Child            = nil
    node.m_Sibling          = nil
    node.m_Parent           = myParent
    node.m_ExpandedCallback = nil
    node.m_ChildIndent      = childIndent
    
    return node
end

function ZO_TreeControlNode:SetExpandedCallback(callback)
    self.m_ExpandedCallback = callback
end

local function DetermineVisibility(node)
    if(node)
    then
        local parentShowing = true
        local parentExpanded = true
        
        if(node.m_Parent)
        then
            parentShowing = node.m_Parent:IsShowing()
            parentExpanded = node.m_Parent:IsExpanded()
        end
        
        node.m_Showing = parentShowing and parentExpanded

        if(node.m_Child)
        then
            DetermineVisibility(node.m_Child)
        end

        if(node.m_Sibling)
        then
            DetermineVisibility(node.m_Sibling)
        end
    end
end

function ZO_TreeControlNode:ToggleExpanded(expanded)
    if(expanded == nil) then
        expanded = not self.m_Expanded
    end
    
    self.m_Expanded = expanded
    
    if(self.m_ExpandedCallback) then
        self.m_ExpandedCallback(self, expanded)
    end
       
    if(expanded) then   
        --climb the tree opening un-expanded parents
        local lastChangedParent = self
        local curParent = self.m_Parent
        while(curParent ~= nil) do
            if(not curParent.m_Expanded) then
                curParent.m_Expanded = true
                if(curParent.m_ExpandedCallback) then
                    curParent.m_ExpandedCallback(curParent, true)
                end
                lastChangedParent = curParent
            end
            curParent = curParent.m_Parent
        end
        --update visibility from the highest changed node
        DetermineVisibility(lastChangedParent)
    else
        DetermineVisibility(self)
    end
    
    -- Update the whole tree.
    self.m_OwningTree:Update()
end

function ZO_TreeControlNode:IsExpanded()
    return self.m_Expanded
end

function ZO_TreeControlNode:IsShowing()
    return self.m_Showing
end

function ZO_TreeControlNode:HasChildren()
    return (self.m_Child ~= nil)
end

function ZO_TreeControlNode:GetNestingLevel()
    local nestingLevel = 0
    local ancestor = self.m_Parent
    
    while(ancestor ~= self.m_OwningTree.m_Root)
    do
        nestingLevel = nestingLevel + 1
        ancestor = ancestor.m_Parent
    end
    
    return nestingLevel
end

function ZO_TreeControlNode:GetControl()
    return self.m_Control
end

function ZO_TreeControlNode:GetOwningTree()
    return self.m_OwningTree
end

function ZO_TreeControlNode:GetNextSibling()
    return self.m_Sibling
end

function ZO_TreeControlNode:GetParent()
    return self.m_Parent
end

function ZO_TreeControlNode:GetChildIndent()
    return self.m_ChildIndent
end

function ZO_TreeControlNode:SetOffsetY(offsetY)
    self.m_OffsetY = offsetY
end
--[[
    The tree control
--]]

ZO_TreeControl = ZO_Object:Subclass()

local DEFAULT_INDENT_OFFSET = 10
local DEFAULT_LINE_SPACING  = 5


function ZO_TreeControl:New(initialAnchor, indentXOffset, verticalSpacing)
    local tree = ZO_Object.New(self)
    
    tree.m_Root             = ZO_TreeControlNode:New(tree)
    tree.m_OffsetX          = indentXOffset or DEFAULT_INDENT_OFFSET
    tree.m_OffsetY          = verticalSpacing or DEFAULT_LINE_SPACING
    tree.m_InitialAnchor    = initialAnchor

    return tree
end

function ZO_TreeControl:AddChild(atNode, insertedControl, childIndent)
    atNode = atNode or self.m_Root
    
    if(atNode == nil)
    then
        return nil
    end
    
    -- If root is the insertion point, and there is no child add the new node as Root's first child
    if((atNode == self.m_Root) and (self.m_Root.m_Child == nil))
    then
        local newNode = ZO_TreeControlNode:New(self, insertedControl, self.m_Root, childIndent)
        
        self.m_Root.m_Child = newNode        
        return newNode
    end
    
    -- Find the last sibling of atNode's first child and insert after that.
    if(atNode.m_Child == nil)
    then
        local newNode = ZO_TreeControlNode:New(self, insertedControl, atNode, childIndent)
        
        atNode.m_Child = newNode
        return newNode
    end

    return self:AddSibling(atNode.m_Child, insertedControl, childIndent)
end

local ADD_HERE = 1
local ADD_ON_END = 2
local function AddSiblingInternal(self, atNode, insertedControl, where, childIndent)
    atNode = atNode or self.m_Root.m_Child
    
    if((not atNode) or (atNode == self.m_Root))
    then
        return nil
    end
    
    local newNode = ZO_TreeControlNode:New(self, insertedControl, atNode.m_Parent, childIndent)
    if(where == ADD_ON_END) then
        while(atNode.m_Sibling)
        do
            atNode = atNode.m_Sibling
        end
    
        atNode.m_Sibling = newNode
    else     
        newNode.m_Sibling = atNode.m_Sibling
        atNode.m_Sibling = newNode   
    end
    
    return newNode
end

function ZO_TreeControl:AddSibling(atNode, insertedControl, childIndent)
    return AddSiblingInternal(self, atNode, insertedControl, ADD_ON_END, childIndent)
end

function ZO_TreeControl:AddSiblingAfterNode(atNode, insertedControl, childIndent)
    return AddSiblingInternal(self, atNode, insertedControl, ADD_HERE, childIndent)
end

-- NOTE: This removes entire subtrees, but does not inform the controls in those subtrees that they have been
-- removed.  That needs to be handled by whatever is calling RemoveNode.
function ZO_TreeControl:RemoveNode(node)
    if(node == self.m_Root) 
    then
        self:Clear()
        return
    end
    
    if(node.m_Parent)
    then
        if(node.m_Parent.m_Child == node)
        then
            node.m_Parent.m_Child = node.m_Sibling
            return
        end
        
        local previousSibling = nil
        local currentSibling = node.m_Parent.m_Child
        
        if(currentSibling == nil) 
        then 
            d("ZO_TreeControl: Corrupted tree.  Parent of: ", node, "...has no children.") 
            return 
        end
                
        while((currentSibling ~= node) and (currentSibling ~= nil))
        do
            previousSibling = currentSibling
            currentSibling = currentSibling.m_Sibling
        end
        
        if(currentSibling == node)
        then
            previousSibling.m_Sibling = node.m_Sibling
        end
    end
end

function ZO_TreeControl:Clear()
    self.m_Root = ZO_TreeControlNode:New(self)
end

function ZO_TreeControl:Update(updateFromNode, indent, anchor, firstControl)
    local node  = updateFromNode or self.m_Root
    indent      = indent or 0
    anchor      = anchor or ZO_Anchor:New(self.m_InitialAnchor)
    
    if(firstControl == nil) then
        firstControl = true
    end
        
    if(node)
    then
        local hidden = not node:IsShowing()
        
        if(node.m_Control)
        then
            node.m_Control:SetHidden(hidden)
                        
            if(not hidden)
            then
                if(firstControl) then
                    self.m_InitialAnchor:Set(node.m_Control)
                    firstControl = false
                else
                    anchor:SetOffsets(indent, node.m_OffsetY or self.m_OffsetY)                
                    anchor:Set(node.m_Control)           
                end     
                
                anchor:SetTarget(node.m_Control)
                anchor:SetRelativePoint(self.m_relativePoint or BOTTOMLEFT)
            end
        end
        
        local backOffIndent = false
        
        if(node.m_Child)
        then
            if(node == self.m_Root)
            then
                indent = 0
            else
                indent = node:GetChildIndent() or self.m_OffsetX
            end
                        
            anchor, indent = self:Update(node.m_Child, indent, anchor, firstControl)
            backOffIndent = node.m_Child:IsShowing()
        end
        
        if(backOffIndent)
        then
            indent = indent - (node:GetChildIndent() or self.m_OffsetX)
        else
            indent = 0
        end        
        
        node = node.m_Sibling
        
        if(node)
        then
            return self:Update(node, indent, anchor, firstControl)
        end
    end
    
    return anchor, indent
end

function ZO_TreeControl:SetRelativePoint(relativePoint)
    self.m_relativePoint = relativePoint
end

function ZO_TreeControl:SetIndent(indentX)
    self.m_OffsetX = indentX
end