-- This module is loaded if a script calls require "gplus".

local glu = glu()
local millisecs = glu.millisecs
local floor = math.floor
local ceil = math.ceil
local abs = math.abs
local mmin = math.min
local mmax = math.max

local gp = {}

local timing = {}        -- timing information
local timingorder = {}   -- index to order timers
local timingstack = 0    -- used for grouping output into dependent timers

--------------------------------------------------------------------------------

function gp.timerstart(name)
    name = name or ""

    -- pause GC if running
    if collectgarbage("isrunning") then collectgarbage("stop") end

    if timing[name] == nil then
        timing[name] = { start=0, last=0, stack=timingstack }
        timingorder[#timingorder + 1] = name
    end
    timingstack = timingstack + 1

    -- start the timer
    timing[name].start = millisecs()
end

--------------------------------------------------------------------------------

function gp.timersave(name)
    name = name or ""
    -- return time in milliseconds since last request or timer start
    local timenow = millisecs()
    if timing[name] then
        timingstack = timingstack - 1
        timing[name].last = timenow - timing[name].start
        timing[name].start = timenow
        timing[name].stack = timingstack
        return timing[name].last
    else
        -- if timer never started return 0
        return 0
    end
end

--------------------------------------------------------------------------------

function gp.timervalue(name)
    name = name or ""
    -- return last measured value of the named timer in milliseconds
    if timing[name] then
        return timing[name].last
    else
        return 0
    end
end

--------------------------------------------------------------------------------

function gp.timerresetall()
    -- reset all timers
    timing = {}
    timingorder = {}
    timingstack = 0

    -- restart GC if paused
    if not collectgarbage("isrunning") then
        collectgarbage("restart")
        collectgarbage()
    end
end

--------------------------------------------------------------------------------

function gp.timervalueall(precision)
    precision = precision or 1
    if precision < 0 then precision = 0 end
    -- return a string containing all timer name value pairs in the order they were created
    local result = {}
    local laststack = 0
    local ntimers = #timingorder
    for i = 1, ntimers do
        local name = timingorder[i]
        local timer = timing[name]
        local output = ""
        if timer.stack > laststack then output = "{ "
        elseif timer.stack < laststack then output = "} " end
        laststack = timer.stack
        output = output..name.." "..string.format("%."..precision.."fms", timing[name].last)
        if i == ntimers then
            while laststack > 0 do
                output = output.." }"
                laststack = laststack - 1
            end
        end
        result[#result + 1] = output
    end
    gp.timerresetall()
    return table.concat(result, " ")
end

--------------------------------------------------------------------------------

function gp.int(x)
    -- return same result as Python's int(x)
    return x < 0 and ceil(x) or floor(x)
end

--------------------------------------------------------------------------------

function gp.round(x)
    -- return same result as Python's round(x)
    return x < 0 and ceil(x-0.5) or floor(x+0.5)
end

--------------------------------------------------------------------------------

function gp.min(a)
    -- return minimum value in given array
    local alen = #a
    if alen == 0 then return nil end
    if alen < 100000 then
        -- use faster math.min call
        return mmin( table.unpack(a) )
    else
        -- slower code but no danger of exceeding stack limit
        local n = a[1]
        for i = 2, alen do
            if a[i] < n then n = a[i] end
        end
        return n
    end
end

--------------------------------------------------------------------------------

function gp.max(a)
    -- return maximum value in given array
    local alen = #a
    if alen == 0 then return nil end
    if alen < 100000 then
        -- use faster math.max call
        return mmax( table.unpack(a) )
    else
        -- slower code but no danger of exceeding stack limit
        local n = a[1]
        for i = 2, alen do
            if a[i] > n then n = a[i] end
        end
        return n
    end
end

--------------------------------------------------------------------------------

function gp.equal(a1, a2)
    -- return true if given arrays have the same values
    if #a1 ~= #a2 then
        -- arrays are not the same length
        return false
    else
        -- both arrays are the same length (possibly 0)
        for i = 1, #a1 do
            if a1[i] ~= a2[i] then return false end
        end
        return true
    end
end

--------------------------------------------------------------------------------

function gp.validint(s)
    -- return true if given string represents a valid integer
    if #s == 0 then return false end
    s = s:gsub(",","")
    return s:match("^[+-]?%d+$") ~= nil
end

--------------------------------------------------------------------------------

function gp.split(s, sep)
    -- split given string into substrings that are separated by given sep
    -- (emulates Python's split function)
    sep = sep or " "
    local t = {}
    local start = 1
    while true do
        local i = s:find(sep, start, true)  -- find string, not pattern
        if i == nil then
            if start <= #s then
                t[#t+1] = s:sub(start, -1)
            end
            break
        end
        if i > start then
            t[#t+1] = s:sub(start, i-1)
        elseif i == start then
            t[#t+1] = ""
        end
        start = i + #sep
    end
    if #t == 0 then
        -- sep does not exist in s so return s
        return s
    else
        return table.unpack(t)
    end
end

--------------------------------------------------------------------------------

local pathsep = glu.getdir("app"):sub(-1)

-- avoid calling any glu.* function in gp.trace otherwise we'll get a Lua error
-- message saying "error in error handling" if user aborts the script

function gp.trace(msg)
    -- this function can be passed into xpcall so that a nice stack trace gets
    -- appended to a runtime error message
    if msg and #msg > 0 then
        -- append a stack trace starting with this function's caller (level 2)
        local result = msg.."\n\n"..debug.traceback(nil, 2)
        -- modify result to make it more readable
        result = result:gsub("\t", "")
        -- following is nicer if a local function is passed into xpcall
        result = result:gsub("in function <.+:(%d+)>", "in local on line %1")
        -- shorten lines starting with "..."
        result = result:gsub("\n%.%.%.[^\n]+"..pathsep, "\n")
        return result
    else
        return msg
    end
end

--------------------------------------------------------------------------------

return gp
