local super = require "Object"

List = super:new()

function List:new()
    self = super.new(self)
    
    self.head = {
        item = nil,
    }
    self.head.prev = self.head
    self.head.next = self.head
    self._count = 0
    
    return self
end

function List:archive()
    local typeName, properties = self:class(), {}
    for item in self:iter() do
        properties[#properties + 1] = item
    end
    return self:class(), properties
end

function List:makerefs(dst, name)
    local node = self.head.next
    local index = 1
    while node ~= self.head do
        dst[node.item] = { name, { index = index } }
        node = node.next
        index = index + 1
    end
end

function List:iter()
    local node = self.head.next
    return function()
        local result = node.item
        node = node.next
        return result
    end
end

function List:reverseIter()
    local node = self.head.prev
    return function()
        local result = node.item
        node = node.prev
        return result
    end
end

local function _insertItem(self, item, nextNode)
    local node = {
        item = item,
        prev = nextNode.prev,
        next = nextNode,
    }
    nextNode.prev.next = node
    nextNode.prev = node
    if (type(item) == 'table' or type(item) == 'userdata') and type(item.addObserver) == 'function' then
        item:addObserver(self)
    end
    self._count = self._count + 1
end

local function _removeNode(self, node)
    local item = node.item
    if (type(item) == 'table' or type(item) == 'userdata') and type(item.removeObserver) == 'function' then
        item:removeObserver(self)
    end
    node.prev.next = node.next
    node.next.prev = node.prev
    self._count = self._count - 1
end

local function _nodeForIndex(self, index)
    local node = self.head.next
    local count = index
    while count > 1 and node ~= self.head do
        node = node.next
        count = count - 1
    end
    return node
end

local function _nodeForItem(self, item)
    local node = self.head.next
    local index = 1
    while node ~= self.head and node.item ~= item do
        node = node.next
        index = index + 1
    end
    if node == self.head then
        index = nil
    end
    return node, index
end

function List:count()
    return self._count
end

function List:add(item)
    if item then
        _insertItem(self, item, self.head)
        self:addUndo(function() self:remove(item) end)
        self:invalidate(self)
        self:event('add', item)
        self:event('insert', item, self._count)
    end
end

function List:join(list)
    if list then
        for item in list:iter() do
            self:add(item)
        end
    end
end

function List:get(index)
    local node = self.head
    while index > 0 do
        node = node.next
        if node == self.head then
            break
        end
        index = index - 1
    end
    return node.item -- self.head.item is nil
end

function List:index(item)
    local node = self.head.next
    local index = 1
    while node ~= self.head and item ~= node.item do
        node = node.next
        index = index + 1
    end
    if item == node.item then
        return index
    end
end

function List:vote(getter)
    local votes = {}
    local max = 0
    local result
    for item in self:iter() do
        local vote = getter(item)
        if vote then
            votes[vote] = (votes[vote] or 0) + 1
            if votes[vote] >= max then
                max = votes[vote]
                result = vote
            end
        end
    end
    return result
end

function List:insert(item, index)
    if item then
        local nextNode = _nodeForIndex(self, index)
        _insertItem(self, item, nextNode)
        self:addUndo(function() self:remove(item) end)
        self:invalidate(self)
        self:event('add', item)
        self:event('insert', item, index)
    end
end

function List:remove(item)
    if item then
        local node, index = _nodeForItem(self, item)
        if index then
            _removeNode(self, node)
            self:addUndo(function() self:insert(item, index) end)
            self:invalidate(self)
            self:event('remove', item, index)
        end
    end
end

function List:replace(item, newItem)
    if item and newItem then
        local node, index = _nodeForItem(self, item)
        if index then
            _insertItem(self, newItem, node)
            _removeNode(self, node)
            self:addUndo(function() self:replace(newItem, item) end)
            self:invalidate(self)
            self:event('add', newItem)
            self:event('replace', item, index, newItem)
        end
    end
end

function List:move(item, index)
    if item then
        local node, oldIndex = _nodeForItem(self, item)
        if oldIndex then
            _removeNode(self, node)
            local nextNode = _nodeForIndex(self, index)
            _insertItem(self, item, nextNode)
            self:addUndo(function() self:move(item, oldIndex) end)
            self:invalidate(self)
            self:event('move', item, oldIndex, index)
        end
    end
end

return List
