--[[
Copyright (C) 2013-2014 Draios inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.


This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
--]]

--[[
This file contains a bunch of functions that are helpful in multiple scripts
]]--

--[[
Serialize the content of a table into a tring
]]--
function st(val, name, skipnewlines, depth)
	skipnewlines = skipnewlines or false
	depth = depth or 0

	local tmp = string.rep(" ", depth)

	if name then tmp = tmp .. name .. " = " end

	if type(val) == "table" then
		tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")

		for k, v in pairs(val) do
			tmp =  tmp .. st(v, k, skipnewlines, depth + 1) .. "," .. (not skipnewlines and "\n" or "")
		end

		tmp = tmp .. string.rep(" ", depth) .. "}"
	elseif type(val) == "number" then
		tmp = tmp .. tostring(val)
	elseif type(val) == "string" then
		tmp = tmp .. string.format("%q", val)
	elseif type(val) == "boolean" then
		tmp = tmp .. (val and "true" or "false")
	else
		tmp = tmp .. "\"[inserializeable datatype:" .. type(val) .. "]\""
	end

	return tmp
end

--[[
Extends a string to newlen with spaces
]]--
function extend_string(s, newlen)
	if #s < newlen then
        local ccs = "                                                                                                                                                                       "
		s = s .. string.sub(ccs, 0, newlen - #s)
		return s
	else
		if newlen > 0 then
			return (string.sub(s, 0, newlen - 1) .. " ")
		else
			return ""
		end
	end
end

--[[
Basic string split.
]]--
function split(s, delimiter)
	local result = {}
	
	for match in (s..delimiter):gmatch("(.-)"..delimiter) do
		table.insert(result, match)
	end
	return result
end

--[[
convert a number into a byte representation.
E.g. 1230 becomes 1.23K
]]--
function format_bytes(val)
	if val > (1024 * 1024 * 1024 * 1024 * 1024) then
		return string.format("%.2fP", val / (1024 * 1024 * 1024 * 1024 * 1024))
	elseif val > (1024 * 1024 * 1024 * 1024) then
		return string.format("%.2fT", val / (1024 * 1024 * 1024 * 1024))
	elseif val > (1024 * 1024 * 1024) then
		return string.format("%.2fG", val / (1024 * 1024 * 1024))
	elseif val > (1024 * 1024) then
		return string.format("%.2fM", val / (1024 * 1024))
	elseif val > 1024 then
		return string.format("%.2fKB", val / (1024))
	else
		return string.format("%dB", val)
	end
end

--[[
convert a nanosecond time interval into a s.ns representation.
E.g. 1100000000 becomes 1.1s
]]--
ONE_S_IN_NS=1000000000
ONE_MS_IN_NS=1000000
ONE_US_IN_NS=1000

function format_time_interval(val)
	if val >= (ONE_S_IN_NS) then
		return string.format("%u.%02us", math.floor(val / ONE_S_IN_NS), (val % ONE_S_IN_NS) / 10000000)
	elseif val >= (ONE_S_IN_NS / 100) then
		return string.format("%ums", math.floor(val / (ONE_S_IN_NS / 1000)))
	elseif val >= (ONE_S_IN_NS / 1000) then
		return string.format("%u.%02ums", math.floor(val / (ONE_S_IN_NS / 1000)), (val % ONE_MS_IN_NS) / 10000)
	elseif val >= (ONE_S_IN_NS / 100000) then
		return string.format("%uus", math.floor(val / (ONE_S_IN_NS / 1000000)))
	elseif val >= (ONE_S_IN_NS / 1000000) then
		return string.format("%u.%02uus", math.floor(val / (ONE_S_IN_NS / 1000000)), (val % ONE_US_IN_NS) / 10)
	else
		return string.format("%uns", val)
	end
end

--[[
extract the top num entries from the table t, after sorting them based on the entry value using the function order()
]]--
function pairs_top_by_val(t, num, order)
	local keys = {}

	for k in pairs(t) do keys[#keys+1] = k end

	table.sort(keys, function(a,b) return order(t, a, b) end)

	local i = 0
	return function()
		i = i + 1
		if (num == 0 or i <= num) and keys[i] then
			return keys[i], t[keys[i]]
		end
	end
end

--[[
Timestamp <-> string conversion
]]--
function ts_to_str(tshi, tslo)
	return string.format("%u%.9u", tshi, tslo)
end

--[[
Pick a key-value table and render it to the console in sorted top format
]]--
json = require ("dkjson")

function print_sorted_table(stable, ts_s, ts_ns, timedelta, viz_info)
	local sorted_grtable = pairs_top_by_val(stable, viz_info.top_number, function(t,a,b) return t[b] < t[a] end)

	if viz_info.output_format == "json" then
		local jdata = {}
		local j = 1
		
		for k,v in sorted_grtable do
			local vals = split(k, "\001\001")
			vals[#vals + 1] = v
			jdata[j] = vals
			j = j + 1
		end
			
		local jinfo = {}
		
		for i, keyname in ipairs(viz_info.key_fld) do
			jinfo[i] = {name = keyname, desc = viz_info.key_desc[i], is_key = true}
		end
		jinfo[3] = {name = viz_info.value_fld, desc = viz_info.value_desc, is_key = false}

		local res = {ts = sysdig.make_ts(ts_s, ts_ns), data = jdata, info = jinfo}
			
		local str = json.encode(res, { indent = true })
		print(str)
	else
		-- Same size to extend each string
		local EXTEND_STRING_SIZE = 20
		local header = extend_string(viz_info.value_desc, EXTEND_STRING_SIZE)
		
		for i, fldname in ipairs(viz_info.key_desc) do
			header = header .. extend_string(fldname, EXTEND_STRING_SIZE)
		end
		
		print(header)
		print("--------------------------------------------------------------------------------")

		for k,v in sorted_grtable do
			local keystr = ""
			
			local singlekeys = split(k, "\001\001")

			for i, singlekey in ipairs(singlekeys) do
				if i < #singlekeys then
					keystr = keystr .. extend_string(string.sub(singlekey, 0, EXTEND_STRING_SIZE), EXTEND_STRING_SIZE)
				else
					keystr = keystr .. singlekey
				end
			end

			if viz_info.value_units == "none" then
				print(extend_string(tostring(v), EXTEND_STRING_SIZE) .. keystr)
			elseif viz_info.value_units == "bytes" then
				print(extend_string(format_bytes(v), EXTEND_STRING_SIZE) .. keystr)
			elseif viz_info.value_units == "time" then
				print(extend_string(format_time_interval(v), EXTEND_STRING_SIZE) .. keystr)
			elseif viz_info.value_units == "timepct" then
				if timedelta > 0 then
					pctstr = string.format("%.2f%%", v / timedelta * 100)
				else
					pctstr = "0.00%"
				end

				print(extend_string(pctstr, EXTEND_STRING_SIZE) .. keystr)	
			end
		end
	end
end

--[[
Try to convert user input to a number using tonumber(). If tonumber() returns
'nil', print an error message to the user and exit, otherwise return
tonumber(value).
]]--
function parse_numeric_input(value, name)
	val = tonumber(value)
	if val == nil then
		print(string.format("Input %s must be a number.", name))
		require ("os")
		os.exit()
	end
	return val
end

--[[
Perform a deep copy of a table.
]]--
function copytable(orig)
    local orig_type = type(orig)
    local copy
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[copytable(orig_key)] = copytable(orig_value)
        end
        setmetatable(copy, copytable(getmetatable(orig)))
    else -- number, string, boolean, etc
        copy = orig
    end
    return copy
end

--[[
Add the content of a table at the end of another one.
]]--
function concattable(dst, src)
    for i=1,#src do
        dst[#dst + 1] = src[i]
    end
    
    return dst
end
