-- Copies the table deeply, including metatables
function table.copy(t)
    local t_type = type(t)
    local copy
    if t_type == "table" then
        copy = {}
        for k, v in next, t, nil do
            copy[table.copy(k)] = table.copy(v)
        end
        setmetatable(copy, table.copy(getmetatable(t)))
    else
        copy = t
    end
    return copy
end

-- Copies the table shallowly, meaning that tables inside the table will not be created anew, and only be referenced in the copy
function table.shallow_copy(t)
    local copy = {}
    for k, v in pairs(t) do
        copy[k] = v
    end
    return copy
end

-- t = {}
-- table.array(t, 5) -> {1, 2, 3, 4, 5}
function table.array(t, n)
    for i = 1, n do
        table.push(t, i)
    end
    return t
end

-- t = {"a", "b", "c", "d"}
-- table.get(t, 1) -> "a"
-- table.get(t, 1, 3) -> {"a", "b", "c"}
-- table.get(t, 2, -1) -> {"b", "c", "d"}
function table.get(t, i, j)
    if i < 0 then i = #t + i + 1 end
    if not j then return t[i] end
    if j < 0 then j = #t + j + 1 end
    if i == j then return t[i] end
    local out = {}
    for k = i, j, math.sign(j - i) do
        table.push(out, t[k])
    end
    return out
end

-- t = {"a", "b", "c", "d"}
-- table.set(t, 1, 1) -> {1, "b", "c", "d"}
-- table.set(t, 1, 3, 2) -> {2, 2, 2, "d"}
-- table.set(t, 2, -1, 3) -> {"a", 3, 3, 3}
function table.set(t, i, j, v)
    if i < 0 then i = #t + i + 1 end
    if not v then
        t[i] = j; return t
    end
    if j < 0 then j = #t + j + 1 end
    if i == j then
        t[i] = v; return t
    end
    for k = i, j, math.sign(j - i) do
        t[k] = v
    end
    return t
end

-- Returns the index of the first instance of value v
-- t = {4, 3, 2, 4, "a", 1, "a"}
-- table.index(t, 4) -> 1
-- table.index(t, "a") -> 5
function table.index(t, v)
    for i, u in ipairs(t) do
        if u == v then
            return i
        end
    end
end

-- Returns the last value of the table
-- t = {1, 2, 3, 4}
-- table.back(t) -> 4
function table.back(t)
    return t[#t]
end

-- Returns the first n values
-- t = {4, 3, 2, 1}
-- table.head(t) -> 4
-- table.head(t, 2) -> {4, 3}
-- If n is defined then it always returns a table, even with only 1 value.
function table.head(t, n)
    local out = {}
    for i = 1, (n or 1) do
        table.push(out, t[i])
    end
    if n then
        return out
    else
        if #out == 1 then
            return out[1]
        else
            return out
        end
    end
end

-- Returns the last n values
-- t = {5, 4, 3, 2, 1}
-- table.tail(t) -> 1
-- table.tail(t, 2) -> {2, 1}
-- If n is defined then it always returns a table, even with only 1 value.
function table.tail(t, n)
    local out = {}
    for i = #t - (n or #t - 1) + 1, #t do
        table.push(out, t[i])
    end
    if n then
        return out
    else
        if #out == 1 then
            return out[1]
        else
            return out
        end
    end
end

-- Inserts value at the end of the table
-- t = {1, 2}
-- table.push(t, "a") -> {1, 2, "a"}
function table.push(t, v)
    table.insert(t, v)
    return t
end

-- Removes the first n values and returns them as well as the modified table
-- t = {4, 3, 2, 1}
-- table.shift(t) -> 4, {3, 2, 1}
-- table.shift(t, 3) -> {4, 3, 2}, {1}
function table.shift(t, n)
    local out = {}
    for i = 1, (n or 1) do
        table.insert(out, table.remove(t, 1))
    end
    if #out == 1 then
        return out[1], t
    else
        return out, t
    end
end

-- Inserts values at the start of the table
-- t = {3, 4}
-- table.unshift(t, 1, 2) -> {1, 2, 3, 4}
function table.unshift(t, ...)
    for j, v in ipairs({ ... }) do
        table.insert(t, 1 + j - 1, v)
    end
    return t
end

-- Removes the last value and returns it as well as the modified table
-- t = {1, 2, 3, 4}
-- table.pop(t) -> 4, {1, 2, 3}
function table.pop(t)
    return table.remove(t, #t), t
end

-- Deletes all instances of value v
-- t = {1, 1, 2, 3, 2, 3, 4, 4}
-- table.delete(t, 1) -> {2, 3, 2, 3, 4, 4}
-- t = {{id = 1}, {id = 1}, {id = 2}}
-- table.delete(t, function(v) return v.id == 1 end) -> {{id = 2}}
function table.delete(t, v)
    if type(v) == 'function' then
        for i = #t, 1, -1 do
            if v(t[i]) then
                table.remove(t, i)
            end
        end
    else
        for i = #t, 1, -1 do
            if v == t[i] then
                table.remove(t, i)
            end
        end
    end
    return t
end

-- Deletes the elements in the given range and returns them as well as the modified table
-- t = {1, 2, 3}
-- table.slice(t, 1) -> 1, {2, 3}
-- table.slice(t, 2, -1) -> {2, 3}, 1
function table.slice(t, i, j)
    if i < 0 then i = #t + i + 1 end
    if not j then return t[i] end
    if j < 0 then j = #t + j + 1 end
    if i == j then return t[i] end
    local out = {}
    for k = j, i, -math.sign(j - i) do
        table.insert(out, table.remove(t, k))
    end
    if #out == 1 then
        return out[1], t
    else
        return table.reverse(out), t
    end
end

-- Removes duplicates
-- t = {1, 1, 2, 2, 3, 3}
-- table.unify(t) -> {1, 2, 3}
-- t = {{id = 1}, {id = 1}, {id = 2}}
-- table.unify(t, function(v) return v.id end) -> {{id = 1}, {id = 2}}
function table.unify(t, f)
    if not f then f = function(v) return v end end
    local seen = {}
    for i = #t, 1, -1 do
        if not seen[f(t[i])] then
            seen[f(t[i])] = true
        else
            table.remove(t, i)
        end
    end
    return t
end

-- Counts the number of elements in the table
-- t = {1, 1, 5, 5, 4, 1, 3, 2, 0, 9, 8, 5, 1, 5, 5, 4, 6}
-- table.count(t, 1) -> 4
-- table.count(t, 5) -> 5
-- table.count(t, 6) -> 1
-- table.count(t, 4) -> 2
function table.count(t, v)
    local n = 0
    for i = 1, #t do
        if t[i] == v then
            n = n + 1
        end
    end
    return n
end

-- Applies function f to all table elements and replaces each element for the value returned by f
function table.map(t, f, ...)
    for k, v in ipairs(t) do
        t[k] = f(v, k, ...)
    end
    return t
end

-- Applies function f to all table elements resulting in a single output value
-- t = {1, 2, 3}
-- table.reduce(t, function(memo, v) return memo + v end) -> 6
-- t = {'a', 'b', 'c'}
-- table.reduce(t, function(memo, v) return memo .. v end) -> 'abc'
-- t = {{id = 1}, {id = 2}, {id = 3}}
-- table.reduce(t, function(memo, v) return memo + v.id end, 0) -> 6
-- The memo variable starts as the first argument in the array, but sometimes, as in the last example, that's not the desired functionality.
-- For those cases the third argument comes in handy and can be used to set the initial value of memo directly.
function table.reduce(t, f, dv, ...)
    local memo = dv or t[1]
    if dv then
        for i = 1, #t do
            memo = f(memo, t[i], i, ...)
        end
    else
        for i = 2, #t do
            memo = f(memo, t[i], i, ...)
        end
    end
    return memo
end

-- Applies function f to all array elements without changing the array
function table.foreach(t, f, ...)
    for k, v in ipairs(t) do
        f(v, k, ...)
    end
    return t
end

-- Applies function f to all array elements and adds the results to a new array
function table.foreachn(t, f, ...)
    local out = {}
    for k, v in ipairs(t) do
        table.insert(out, f(v, k, ...))
    end
    return out
end

-- Applies filter function f which removes all elements that pass the filter and returns them as well as the modified table
-- t = {1, 2, 3, 4}
-- table.reject(t, function(v) return v >= 3 end) -> {3, 4}, {1, 2}
-- table.reject(t, function(v) return v == 1 end) -> 1, {2, 3, 4}
function table.reject(t, f, ...)
    local out = {}
    for i = #t, 1, -1 do
        if f(t[i], i, ...) then
            table.insert(out, table.remove(t, i))
        end
    end
    if #out == 1 then
        return out[1], t
    else
        return table.reverse(out), t
    end
end

-- Applies filter function f which collects all elements that pass the filter and returns them; the original table is not modified
-- t = {1, 2, 3, 4}
-- table.select(t, function(v) return v >= 3 end) -> {3, 4}
-- table.select(t, function(v) return v == 1 end) -> 1
function table.select(t, f, ...)
    local out = {}
    for i = 1, #t do
        if f(t[i], i, ...) then
            table.insert(out, t[i])
        end
    end
    return out
end

-- Returns true if any values in the table pass the test
function table.any(t, f, ...)
    for i, v in ipairs(t) do
        if f(v, i, ...) then
            return true
        end
    end
end

-- Returns true if all values in the table pass the test
function table.all(t, f, ...)
    for i, v in ipairs(t) do
        if not f(v, i, ...) then
            return false
        end
    end
    return true
end

-- Check if table contains value v and return its index
-- If value v is a function instead then it checks according to the check performed by that function
-- t = {4, 3, 2, 1}
-- table.contains(t, 4) -> 1
-- t = {{id = 4}, {id = 3}, {id = 2}, {id = 1}}
-- table.contains(t, function(v) return v.id == 3 end) -> 2
function table.contains(t, v)
    if type(v) == "function" then
        for i, u in ipairs(t) do
            if v(u) then return i end
        end
    else
        for i, u in ipairs(t) do
            if u == v then return i end
        end
    end
end

-- t = {{1, 2}, {3, {4, 5}}, {6, 7}, 8}
-- table.flatten(t) -> {1, 2, 3, 4, 5, 6, 7, 8}
-- table.flatten(t, true) -> {1, 2, 3, {4, 5}, 6, 7, 8}
function table.flatten(t, shallow)
    local out = {}
    local u
    for k, v in ipairs(t) do
        if type(v) == "table" and getmetatable(t) == nil then
            u = shallow and v or table.flatten(v)
            for _, x in ipairs(u) do
                table.insert(out, x)
            end
        else
            table.insert(out, v)
        end
    end
    return out
end

-- t = {1, 2, 3, 4}
-- table.tostring(t) -> '{[1] = 1, [2] = 2, [3] = 3, [4] = 4}'
function table.tostring(t)
    if type(t) == "table" then
        local str = "{"
        for k, v in pairs(t) do
            if type(k) ~= "number" then k = '"' .. k .. '"' end
            str = str .. "[" .. k .. "] = " .. table.tostring(v) .. ", "
        end
        if str ~= "{" then
            return str:sub(1, -3) .. "}"
        else
            return str .. "}"
        end
    elseif type(t) == "string" then
        return '"' .. tostring(t) .. '"'
    else
        return tostring(t)
    end
end

-- Returns the first n values, same as head
-- t = {4, 3, 2, 1}
-- table.first(t) -> 4
-- table.first(t, 2) -> {4, 3}
function table.first(t, n)
    if n == 1 then return t[1] end
    local out = {}
    for i = 1, (n or 1) do
        table.push(out, t[i])
    end
    if #out == 1 then
        return out[1]
    else
        return out
    end
end

function table.first2(t, n)
    if n == 1 then return { t[1] } end
    local out = {}
    for i = 1, (n or 1) do
        table.push(out, t[i])
    end
    return out
end

-- Returns the last n values, same as tail
-- t = {4, 3, 2, 1}
-- table.last(t) -> 1
-- table.last(t, 2) -> {2, 1}
function table.last(t, n)
    if n == 1 then return t[#t] end
    local out = {}
    for i = #t - n + 1, #t do
        table.push(out, t[i])
    end
    if #out == 1 then
        return out[1]
    else
        return out
    end
end

-- t = {"a", "b", "c", "d"}
-- table.reverse(t) -> {"d", "c", "b", "a"}
-- table.reverse(t, 2, 3) -> {"a", "c", "b", "d"}
-- table.reverse(t, 2, -1) -> {"a", "d", "c", "b"}
function table.reverse(t, i, j)
    if not i then i = 1 end
    if i < 0 then i = #t + i + 1 end
    if not j then j = #t end
    if j < 0 then j = #t + j + 1 end
    if i == j then return t end
    for k = 0, (j - i + 1) / 2 - 1, math.sign(j - i) do
        t[i + k], t[j - k] = t[j - k], t[i + k]
    end
    return t
end

-- Shifts the table to the right n times, the last value is warped over to become the first value
-- t = {1, 2, 3, 4}
-- table.rotate(t) -> {4, 1, 2, 3}
-- table.rotate(t, 2) -> {3, 4, 1, 2}
function table.rotate(t, n)
    if not n then n = 1 end
    if n < 0 then n = #t + n end
    t = table.reverse(t, 1, #t)
    t = table.reverse(t, 1, #t - n)
    t = table.reverse(t, #t - n + 1, #t)
    return t
end

-- Returns a random value from the table
-- t = {1, 2, 3}
-- table.random(t) -> 1 or 2 or 3 randomly
function table.random(t)
    return t[love.math.random(1, #t)]
end

-- t = {1, 2, 3, 4, 5}
-- table.shuffle(t) -> {3, 4, 1, 2, 5}
-- table.shuttle(t) -> {2, 5, 1, 4, 3}
-- table.shuffle(t) -> {5, 4, 1, 3, 2}
function table.shuffle(t)
    for i = #t, 2, -1 do
        local j = love.math.random(i)
        t[i], t[j] = t[j], t[i]
    end
    return t
end

-- Merges both tables based on their indexes, if the second table has values in the same indexes as the first table then those will overwrite the first values.
-- t1 = {1, 2, ['a'] = 3, ['b'] = function() end}
-- t2 = {nil, 8, 4, 5, ['a'] = 8}
-- table.merge(t1, t2) -> {1, 2, 8, 4, 5, ['a'] = 8, ['b'] = function() end}
function table.merge(t1, t2)
    local out = {}
    for k, v in pairs(t1) do out[k] = v end
    for k, v in pairs(t2) do out[k] = v end
    return out
end