# Module:Road data/util

local util = {}  

local insert = table.insert  
local concat = table.concat  
local format = mw.ustring.format  

---
-- Add all entries in `arr` into `target`.  
-- An error is raised if `overwrite` is not true  
-- and any key in `arr` is already in `target`.  
function util.addAll(target, arr, overwrite)  
if type(target) ~= "table" then  
error("target is not a table")  
end  
for key,value in pairs(arr) do  
if overwrite or target[key] == nil then  
target[key] = value  
else  
error("Duplicate key: " .. tostring(key))  
end  
end  
end  

local function comp(e1, e2)  
local t1 = type(e1)  
local t2 = type(e2)  
if t1 ~= t2 then return t1 < t2 end  
if t1 == "function" then  
error("Unexpected function type")  
end  
return e1 < e2  
end  

local arrayToStringAux  
arrayToStringAux = function(arr, indent)  
if type(arr) ~= "table" then  
error("arr is not a table")  
end  
if type(indent) ~= "number" then  
error("indent is not a number")  
end  
local result = {}  
local keys = {}  
for key in pairs(arr) do insert(keys, key) end  
table.sort(keys, comp)  
for _,key in ipairs(keys) do  
local value = arr[key]  
local keyPrint  
if type(key) == "string" then  
keyPrint = format("\"%s\"", key)  
else  
keyPrint = tostring(key)  
end  
local valuePrint  
if type(value) == "table" then  
valuePrint = format("{\n%s\n%s}",  
arrayToStringAux(value, indent + 4),  
string.rep(" ", indent))  
elseif type(value) == "string" then  
valuePrint = format("\"%s\"", value)  
else  
valuePrint = tostring(value)  
end  
insert(result, format("%s[%s] = %s",  
string.rep(" ", indent),  
keyPrint,  
valuePrint))  
end  
return concat(result, ", \n")  
end  

--- Return a string representation of `arr`.  
function util.arrayToString(arr, indent)  
return arrayToStringAux(arr, indent or 0)  
end  

local function convert(distance, multiplier, desiredPrec)  
if type(distance) ~= "string" then  
error("distance is not a string")  
end  
if type(multiplier) ~= "number" then  
error("multiplier is not a number")  
end  
-- Import math functions.  
local math = require "Module:Math"  
-- This function returns the precision of a given string representing a number.  
local precision = math._precision  
-- This function returns the order of magnitude of a given string representing a number.  
local order = math._order  
-- This function rounds a given number to the given number of digits.  
local round = math._precision_format  

local prec = desiredPrec or precision(distance)  
if not desiredPrec then  
local ord = order(distance)  
-- Adjust precision based on multiplier, as done in .  
prec = prec - order(multiplier / 0.2)  
end  

local converted = distance * multiplier  
local magnitude = order(converted)  
if prec <= -magnitude then  
-- Ensure the result has at least two significant digits.  
prec = -magnitude + 1  
end  
return round(converted, prec)  
end  

---
Convert length specified in one unit (mi or km) to length in the other unit.  
@param #map<#string, #string> lengths  
a map from unit to distance (as a string) in that unit;  
may contain entry `prec` indicating desired conversion precision  
@param #string blank text to be used if length is unspecified  
@return #table a table containing the conversion result:  
orig = source unit;  
comp = target unit;  
mi = length in miles;  
ft = converted length in feet;  
km = length in kilometers;  
m = converted length in meters;  
error = error message, if any  

function util.convertLengths(lengths, blank)  
-- Import math functions.  
local math = require "Module:Math"  
-- In Lua, storing functions locally results in more efficient execution.  
-- This function rounds a given number to the given number of digits.  
local round = math._precision_format  
-- This function returns the precision of a given string representing a number.  
local precision = math._precision  

local kmPerMile = 1.609344  
local ftPerMile = 5280  
-- The length in kilometers as passed to the function.  
local km = lengths.km  
-- The length in miles as passed to the function.  
local mi = lengths.mi  
-- Precision for the converted length.  
local prec = lengths.prec  
local errMsg = {}  
-- Sanitize inputs.  
local km_ = tonumber(km)  
if km and not km_ then  
insert(errMsg, util.err("km is not a number"))  
end  
local mi_ = tonumber(mi)  
if mi and not mi_ then  
insert(errMsg, util.err("mi is not a number"))  
end  
local prec_ = tonumber(prec)  
if prec and not prec_ then  
insert(errMsg, util.err("prec is not a number"))  
end  
prec = prec_  

local ft  
local m  
local orig = "mi"  
local comp = "km"  
if mi and km then  
insert(errMsg, util.err("Both mi and km are specified"))  
elseif mi then  
-- Length in miles was passed.  
if mi_ then  
-- If `mi` is indeed a number, compute and round the length in kilometers.  
km = convert(mi, kmPerMile, prec)  
m = convert(mi, kmPerMile * 1000, prec)  
-- format mi (insert separators as in 1,000)  
mi = round(mi_, precision(mi))  
else  
-- `mi` is not a number.  
km = blank  
m = blank  
end  
elseif km then  
-- Length in kilometers was passed.  
-- Swap units.  
orig, comp = comp, orig  
if km_ then  
-- If `km` is indeed a number, compute and round the length in miles.  
mi = convert(km, 1 / kmPerMile, prec)  
ft = convert(km, ftPerMile / kmPerMile, prec)  
-- format km (insert separators as in 1,000)  
km = round(km_, precision(km))  
else  
-- `km` is not a number.  
mi = blank  
ft = blank  
end  
else  
mi = blank  
ft = blank  
km = blank  
m = blank  
end  
local error = concat(errMsg)  
if error == "" then error = nil end  
return {mi = mi, ft = ft, km = km, m = m, orig = orig, comp = comp,  
error = error}  
end  

--- Generates wikitext error messages.  
function util.err(msg)  
if msg == nil then  
error("Unspecified error message")  
end  
return format('<strong>Error: %s</strong>', msg)  
end  

return util