ZO_CategoryManager = ZO_Object:Subclass()

--list to hold node order for adding
local nodeList = {}

function ZO_CategoryManager:New()
    local newObj = ZO_Object.New(self)
    
    newObj.m_categoryCache = {}
    newObj.m_categoryData = {}

    return newObj
end

function ZO_CategoryManager:GetCategoryCache()
    return self.m_categoryCache
end

function ZO_CategoryManager:GetCategoryCacheData(catId)
    if(catId and self.m_categoryCache[catId])
    then
        return self.m_categoryCache[catId].data
    end
end

function ZO_CategoryManager:GetCategoryData()    
    return self.m_categoryData
end

function ZO_CategoryManager:ClearCategoryCache()
    self.m_categoryCache = {}
end

function ZO_CategoryManager:ClearCategoryData()
    self.m_categoryData = {}
    
    for k,v in pairs(self.m_categoryCache) do        
        v.children = nil
        v.inTree = nil
    end
end

function ZO_CategoryManager:AddCategory(catId, parentId, data)
    if(catId and not self:HasCategory(catId))
    then
		self.m_categoryCache[catId] = { id = catId, parentId = parentId, data = data}            
    end
end

function ZO_CategoryManager:HasCategory(catId)
    return self.m_categoryCache[catId] ~= nil
end

function ZO_CategoryManager:InsertData(parentCatId, data, comparator)
    
    --build the data node
    local dataNode = { parentId = parentCatId, data = data }
    table.insert(nodeList, dataNode)
    
    --collect nodes that lead to the data (ex. [grandParent, parent, data])
    local currCatId = parentCatId
    local currCat = nil

    -- Prevent cycles
    local visited = {}
    
    while(currCatId ~= nil) do
        if(visited[currCatId]) then break end

        currCat = self.m_categoryCache[currCatId]
        visited[currCatId] = true
        table.insert(nodeList, currCat)
        currCatId = currCat.parentId
    end

    self:InsertDataHelper(nodeList, comparator, self.m_categoryData)
end

function ZO_CategoryManager:InsertDataHelper(nodeList, comparator, level)
    
    --we've just processed the leaf node, quit
    if(#nodeList == 0) then
        return
    end
    
    --collect the next node and strike it from the list
    local currNode = nodeList[#nodeList]
    --node list is cleared out in the process of inserting the data
    nodeList[#nodeList] = nil
    
    --if we found the category is already in the tree, continue on...
    if(currNode.inTree) then
        self:InsertDataHelper(nodeList, comparator, currNode.children)
        return
    end
    
    --otherwise, add the category
    self:InsertAtLevel(currNode, comparator, level)
    self:InsertDataHelper(nodeList, comparator, currNode.children)
end

local function BinaryInsert(node, comparator, level)

    local low = 1
    local high = #level
    local mid

    --search out the insert position
    while(low <= high) do
        mid = low + math.floor((high-low) * 0.5)
        local compVal = comparator(node,level[mid])
        if(compVal > 0) then
            low = mid + 1
        else
            high = mid - 1
        end
    end
    
    table.insert(level, high+1, node)

end

function ZO_CategoryManager:InsertAtLevel(node, comparator, level)
    --leaf nodes have no id and no children
    if(node.id) then
        node.children= {}
    end
    
    node.inTree = true
    
    --ordered add
    if(comparator and #level > 0) then
        BinaryInsert(node, comparator, level) 
        return  
    end
    
    --unordered add, or it's at the end of the ordered list
    table.insert(level, node)
end

