--- Module for a data container that does not allow nil values.
-- @classmod lqc.helpers.vector
-- @alias Vector
local deep_equals = require 'lqc.helpers.deep_equals'

local Vector = {}
local Vector_mt = {
    __index = Vector,
}

--- Constructs a new vector, possibly filled with data (a table value)
-- @param data[opt={}] the data to be stored in the vector initially
-- @return a new vector filled with the initial data if provided.
function Vector.new(data)
    local begin_data = data or {}
    local vector = {
        data = begin_data,
    }
    return setmetatable(vector, Vector_mt)
end

--- Adds an element to the back of the vector.
-- @param obj a non-nil value
-- @return self (for method chaining); raises an error if trying to add nil to the vector
function Vector:push_back(obj)
    if obj == nil then
        error 'nil is not allowed in vector datastructure!'
    end
    table.insert(self.data, obj)
    return self
end

--- Replaces an element in the vector.
-- @param idx Index of the element in the vector to be replaced
-- @param obj Object that the previous object should be replaced with
-- @return self (for method chaining); raises an error if idx is an index
--         not present in the vector
function Vector:replace(idx, obj)
    local vec_size = self:size()
    if idx < 1 or idx > vec_size then
        error('Invalid index! Index should be between 1 and ' .. vec_size)
    end
    self.data[idx] = obj
    return self
end

--- Appends another vector to this vector
-- @param other_vec Another vector object
-- @return a vector containing the data of both vectors
function Vector:append(other_vec)
    for i = 1, other_vec:size() do
        self:push_back(other_vec:get(i))
    end
    return self
end

--- Gets the element at position 'index' in the vector
-- @param index position of the value in the vector
-- @return element at position 'index
function Vector:get(index)
    return self.data[index]
end

--- Checks if an element is contained in the vector
-- @param element element to be checked if it is in the vector
-- @return true if the element is already in the vector; otherwise false.
function Vector:contains(element)
    for i = 1, #self.data do
        if self.data[i] == element then
            return true
        end
    end
    return false
end

--- Returns the size of the vector.
-- @return length of the vector (0 if empty)
function Vector:size()
    return #self.data
end

--- Removes an element from the vector by value
-- @param obj object to remove
function Vector:remove(obj)
    -- Find element, then remove by index
    local pos = -1
    for i = 1, #self.data do
        if deep_equals(self.data[i], obj) then
            pos = i
            break
        end
    end
    if pos == -1 then
        return
    end
    table.remove(self.data, pos)
end

--- Removes an element from the vector by index
-- @param idx Position of the element you want to remove
function Vector:remove_index(idx)
    if idx > self:size() then
        return
    end
    table.remove(self.data, idx)
end

--- Returns the vector, with the contents represented as a flat table
-- @return Table with the contents of the vector
function Vector:to_table()
    return self.data
end

return Vector

