-- Table helpers

function map_index(table, func)
    return setmetatable(table, {
        __index = function(table, key)
            return rawget(table, func(key))
        end,
        __newindex = function(table, key, value)
            rawset(table, func(key), value)
        end
    })
end

function set_case_insensitive_index(table)
    return map_index(table, string.lower)
end

-- Fisher-Yates
function shuffle(t)
    for i = 1, #t-1 do
        local j = math.random(i+1, #t)
        t[i], t[j] = t[j], t[i]
    end
    return t
end

function equals(t1, t2)
    local is_equal = t1 == t2
    if type(t1) ~= "table" or type(t2) ~= "table" then
        return is_equal
    end
    if is_equal then
        return true
    end
    if #t1 ~= #t2 then
        return false
    end
    local table_keys = {}
    for k1, v1 in pairs(t1) do
        local v2 = t2[k1]
        if not equals(v1, v2) then
            if type(k1) == "table" then
                table_keys[k1] = v1
            else
                return false
            end
        end
    end
    for k2, v2 in pairs(t2) do
        if type(k2) == "table" then
            local found
            for t, v in table_keys do
                if equals(k2, t) and equals(v2, v) then
                    table_keys[t] = nil
                    found=true
                    break
                end
            end
            if not found then
                return false
            end
        else
            if t1[k2] == nil then
                return false
            end
        end
    end
    return true
end

function tablecopy(t)
    return table.copy(t)
end

copy = tablecopy

function count(table)
    local count = 0
    for _ in pairs(table) do
        count = count + 1
    end
    return count
end

function is_empty(table)
    return next(table) == nil
end

function foreach(t, func)
    for k, v in pairs(t) do
        func(k, v)
    end
end

function foreach_value(t, func)
    for _, v in pairs(t) do
        func(v)
    end
end

function foreach_key(t, func)
    for k, _ in pairs(t) do
        func(k)
    end
end

function map(t, func)
    for k, v in pairs(t) do
        t[k]=func(v)
    end
    return t
end

function process(t, func)
    local r={}
    for k, v in pairs(t) do
        table.insert(r, func(k,v))
    end
    return r
end

function call(funcs, ...)
    for _, func in ipairs(funcs) do
        func(unpack(arg))
    end
end

function find(list, value)
    for index, other_value in pairs(list) do
        if value == other_value then
            return index
        end
    end
    return false
end

contains = find

function difference(table1, table2)
    local result={}
    for k, v in pairs(table2) do
        local v2=table1[v]
        if v2~=v then
            result[k]=v
        end
    end
    return result
end

function add_all(dst, new)
    for key, value in pairs(new) do
        dst[key] = value
    end
    return dst
end

function complete(dst, new)
    for key, value in pairs(new) do
        if  dst[key] == nil then
            dst[key] = value
        end
    end
    return dst
end

function merge_tables(table1, table2)
    return add_all(copy(table1), table2)
end

union = merge_tables

function intersection(t1, t2)
    local result = {}
    for key, value in pairs(t1) do
        if t2[key] then
            result[key] = value
        end
    end
    return result
end

function append(t1, t2)
    local l=#t1
    for k, v in ipairs(t2) do
        t1[l+k]=v
    end
    return t1
end

function keys(t)
    local keys = {}
    for key, _ in pairs(t) do
        table.insert(keys, key)
    end
    return keys
end

function values(t)
    local values = {}
    for key, _ in pairs(t) do
        table.insert(values, key)
    end
    return values
end

function flip(table)
    local flipped = {}
    for key, val in pairs(table) do
        flipped[val] = key
    end
    return flipped
end

function set(table)
    local flipped = {}
    for _, val in pairs(table) do
        flipped[val] = true
    end
    return flipped
end

function unique(table)
    local lookup = {}
    for val in ipairs(table) do
        lookup[val] = true
    end
    return keys(lookup)
end

function rpairs(t)
    local i = #t
    return function ()
        if i >= 1 then
            local v=t[i]
            i = i-1
            if v then
                return i+1, v
            end
        end
    end
end

function best_value(table, is_better_fnc)
    if not table or not is_better_fnc then
        return nil
    end
    local l=#table
    if l==0 then
        return nil
    end
    local m=table[1]
    for i=2, l do
        local v=table[i]
        if is_better_fnc(v, m) then
            m=v
        end
    end
    return m
end

function min(table)
    return best_value(table, function(v, m) return v < m end)
end

function max(table)
    return best_value(table, function(v, m) return v > m end)
end

function default_comparator(a, b)
    if a == b then
        return 0
    end
    if a > b then
        return 1
    end
    return -1
end

function binary_search_comparator(comparator)
    -- if found, returns index; if not found, returns -index for insertion
    function binary_search(list, value)
        local min, max = 1, #list
        while min <= max do
            local pivot = min + math.floor((max-min)/2)
            local element = list[pivot]
            local compared = comparator(value, element)
            if compared == 0 then
                return pivot
            elseif compared > 0 then
                min = pivot+1
            else
                max = pivot-1
            end
        end
        return -min
    end
end

binary_search = binary_search_comparator(default_comparator)

function reverse(list)
    local len = #list
    for i = 1, math.floor(#list/2) do
        list[len-i+1], list[i] = list[i], list[len-i+1]
    end
    return list
end