-- General purpose function only

-- Debug
-- Prints everything in a table (first level only)
function print_table(table)
    for i, j in pairs(table) do
        print(i, j)
    end
end

function max(vector)
    if vector == nil or #vector == 0 then return nil end
    local maxi = vector[1]
    for i = 2, #vector do
        if vector[i] > maxi then maxi = vector[i] end
    end
    return maxi
end

function print_matrix(table, len, deci)
    for i, j in pairs(table) do
        io.write(i, ": ")
        for _, l in ipairs(j) do
            io.write(string.format("%"..len.."."..deci.."f ", l))
        end
        io.write("\n")
    end
end

-- Converts a matrix into a vector, line after line
function matrix_to_vect(matrix)
    local vector = {}
    for i = 1, #matrix do
        for j = 1, #matrix[i] do
            vector[#vector + 1] = matrix[i][j]
        end
    end
    return vector
end

-- Type checking, returns the values, stops execution if fails
function assert_type(x, xType, msg)
    if msg == nil then msg = "" end
    local s1 = "\nError : \n    Value is not a(n) "
    assert(is_type(x, xType), s1..xType.." ("..type(x).." found). \n    "..msg)
end

-- Check type withouth exit (true/false)
-- Integers are ok
function is_type(x, xType)
    if xType == 'integer' and type(x) == 'number' then
        return x%1 == 0
    else
        return type(x) == xType
    end
end

-- delete?
function assert_integer(x)
    assert(type(x) == 'number', "Value is not an integer ("..type(x)..").")
    assert(x%1==0,  "Value is not an integer ("..type(x)..").")
    return x
end
-- delete?
function assert_number(x)
    assert(type(x) == 'number', "Value is not a number ("..type(x)..").")
    return x
end
-- delete?
function assert_string(x)
    assert(type(x) == 'string', "Value is not a string ("..type(x)..").")
    return x
end
-- delete?
function assert_table(x)
    assert(type(x) == 'table', "Value  is not a table ("..type(x)..").")
    return x
end

--Find if a value is in a table
function is_in_table(value, table)
    for i = 1, #table, 1 do 
        if value == table[i] then return true end
    end
    return false
end

-- Repeats values n times (in a table)
function rep(value, times)
    local table = {}
    for i = 1, times do 
        table[i] = value
    end
    return table
end

-- Function to create and initialize a 2D matrix to value 
function make_matrix(value, nLines, nCols)    
    local myMat = {}
    for i = 1, nLines do
        myMat[i] = rep(value, nCols)  
    end
    return myMat
end

-- Function to create and initialize a 2D matrix according to a profile (horizontal or vertical)
function make_profile_matrix(listVal, nLines, nCols, isVertical)
    if (isVertical==false and #listVal ~= nCols) or (isVertical and #listVal ~= nLines) then
        print("Wrong profile dimensions") end
    local myMat = {}
    if isVertical then
        for i = 1, nLines do
            myMat[i] = rep(listVal[i], nCols)
        end
    else
        for j = 1, nLines do
            myMat[j] = {}
            for i = 1, nCols do
                myMat[j][i] = listVal[i]
            end
        end
    end
    return myMat
end

-- Real copy of a table
-- NOT A DEEP COPY
function copy_table(table, range_min, range_max)
    local new_table = {}
    local count = 1
    for i = range_min, range_max, 1 do
        new_table[count] = table[i]
        count = count + 1
    end
    return new_table
end

-- superficial copy of a table of consecutives values
function copy_itable(table)
    local new = {}
    for i = 1, #table, 1 do new[i] = table[i] end
    return new
end

-- Deep copy
function deep_copy(table)
    local new = {}
    -- If not a table, value returned
    if not is_type(table, 'table') then return table end
    -- Iteration
    for i, j in pairs(table) do 
        if is_type(j, 'table') then new[i] = deep_copy(j)
        else new[i] = j end
    end
    return new
end

-- Appends table2 at the end of table1
-- table1 IS modified, table 2 untouched
function append_table(table1, table2)
    local t1 = {}; local t2 = {}
    if type(table1) == 'table' then t1 = table1 end 
    if type(table2) == 'table' then t2 = table2 end
    
    for i = 1, #t2 do
        t1[#t1 + 1] = t2[i]
    end
end


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

-- Basic check for vector of an given type, size check optional
function is_type_vector(table, tType, size)
    if not is_type(table, 'table') then return false end
    if non_seq_size(table) ~= #table then return false end
    if size == nil then size = #table
    elseif size ~= #table then return false end
    if size == 0 then return true end
    for i = 1, size do
        if not is_type(table[i], tType) then return false end
    end
    return true
end

-- Check if a table if of the given dimentions & type (rectangular)
-- nLines or nCol at nil just checks equality with the first encoutered size
function is_type_matrix(table, tType, nLines, nCol)
    if not is_type(table, 'table') then return false end
    if non_seq_size(table) ~= #table then return false end
    if nLines == nil then nLines = #table 
    elseif nLines ~= #table then return false end
    if nLines == 0 then return true end
    if nCol == nil then nCol = #table[1] end
    for i = 1, nLines do
        if not is_type_vector(table[i], tType, nCol) then return false end
    end
    return true
end

-- Is in range [min, max[
function is_range_vector(table, min, max)
    for i = 1, #table do
        if table[i] < min and table[i] >= max  then return false end
    end
    return true
end

-- Reverse all elements in place
function reverse_vector(vector)
    for i = 1, (#vector / 2), 1 do
        vector[i], vector[#vector - (i-1)] = vector[#vector - (i-1)], vector[i]
    end
end 


-- Quicksort (Rosetta code implementation)
-- use table.sort(myTable) instead?
function quicksort(t, start, endi)
    start = start or 1 -- equivalent to: if start == nil then start = 1 end
    endi =  endi or #t
    --partition w.r.t. first element
    if(endi - start < 1) then return t end
    local pivot = start
    for i = start + 1, endi do
        if t[i] <= t[pivot] then
            local temp = t[pivot + 1]
            t[pivot + 1] = t[pivot]
            if(i == pivot + 1) then
                t[pivot] = temp
                else
                t[pivot] = t[i]
                t[i] = temp
            end
            pivot = pivot + 1
        end
    end
    t = quicksort(t, start, pivot - 1)
    return quicksort(t, pivot + 1, endi)
end

-- Binary search 
-- Exact = 1 return false if value not found
-- Exact = 2 return the upper bound (false if does not exists)
-- Exact = 3 return the lower bound (false if does not exists)
function binarySearch (list, value, exact)
    local low = 1
    local high = #list
    local mid = 0
    while low <= high do
        mid = math.floor((low+high)/2)
        if list[mid] > value then high = mid - 1
        else if list[mid] < value then low = mid + 1
             else return mid
             end
        end
    end

    -- Exact = 1 return false if value not found
    if exact == 1 then return false
    -- Exact = 2 return the upper bound (false if does not exists)
    elseif exact == 2 then
        if low > #list then return false end
        return low
    -- Exact = 3 return the lower bound (false if does not exists)
    elseif exact == 3 then 
        if high < 1 then return false end
        return high
    else print("The \"exact\" parameter must take value 1, 2, or 3") end
end


-- Checks wether or not numbers of a SORTED vestor are unique 
function is_unique(table)
    if #table < 2 then return true end
    for i = 2, #table do
        if table[i-1] == table[i] then return false end
    end
    return true
end

