-- commas.lua
-- ----------------------------------------------------------
-- Rounding, duration, comma functions
-- See forum thread:
--  http://www.gammon.com.au/forum/?id=7805

--[[

This function rounds any number to the closest integer.
The "tricky" case is exactly half-way. 
That is, should 1.5 round to 1 or 2? How about -1.5?

This function rounds 1.5 "up" to 2, and -1.5 "down" to -2.

--]]

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

-- round normally, but when a number ends in exactly .5 round to the nearest even value.
function round_banker(x)
   if x == 0 then return 0 end -- prevent returning -0
   if (x + 0.5) % 2 == 0 then
      return math.floor(x + 0.5)
   else
      return math.ceil(x - 0.5)
   end
end

-- round "up" to absolute value, so we treat negative differently
--  that is, round (-1.5) will return -2
  
function round (x)
  if x >= 0 then
    return math.floor (x + 0.5)
  end  -- if positive

  return math.ceil (x - 0.5)
end -- function round

--[[

Duration

This function is designed to display a time interval in "short form". 
That is, rounded to the nearest major time interval. Some examples of intervals:


    * 3.6 days - displays "4 d"
    * 3.5 days - displays "4 d"
    * 3.4 days - displays "3 d"

    * 3.6 hours - displays "4 h"
    * 3.5 hours - displays "4 h"
    * 3.4 hours - displays "3 h"

    * 3.6 minutes - displays "4 m"
    * 3.5 minutes - displays "4 m"
    * 3.4 minutes - displays "3 m"

    * 59 seconds - displays "59 s"
    * 58 seconds - displays "58 s"
    * 57 seconds - displays "57 s" ... and so on to "0 s"


--]]

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

function convert_time (secs)

  -- handle negative numbers
  local sign = ""
  if secs < 0 then
    secs = math.abs (secs)
    sign = "-"
  end -- if negative seconds
  
  -- weeks
  if secs >= (60 * 60 * 24 * 6.5) then
    return sign .. round (secs / (60 * 60 * 24 * 7)) .. " w"
  end -- 6.5 or more days
  
  -- days
  if secs >= (60 * 60 * 23.5) then
    return sign .. round (secs / (60 * 60 * 24)) .. " d"
  end -- 23.5 or more hours
  
  -- hours
  if secs >= (60 * 59.5) then
   return sign .. round (secs / (60 * 60)) .. " h"
  end -- 59.5 or more minutes
  
  -- minutes
  if secs >= 59.5 then
   return sign .. round (secs / 60) .. " m"
  end -- 59.5 or more seconds
  
  -- seconds
  return sign .. round (secs) .. " s"    
end -- function convert_time 

--[[

Commas in numbers

This function adds commas to big numbers. 
For example 123456 becomes "123,456".

--]]

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

function commas (num)
  assert (type (num) == "number" or
          type (num) == "string")
  
  local result = ""

  -- split number into 3 parts, eg. -1234.545e22
  -- sign = + or -
  -- before = 1234
  -- after = .545e22

  local sign, before, after =
    string.match (tostring (num), "^([%+%-]?)(%d*)(%.?.*)$")

  -- pull out batches of 3 digits from the end, put a comma before them

  while string.len (before) > 3 do
    result = "," .. string.sub (before, -3, -1) .. result
    before = string.sub (before, 1, -4)  -- remove last 3 digits
  end -- while

  -- we want the original sign, any left-over digits, the comma part,
  -- and the stuff after the decimal point, if any
  return sign .. before .. result .. after

end -- function commas


-- trim leading and trailing spaces from a string
function trim (s)
  return (string.gsub (s, "^%s*(.-)%s*$", "%1"))
end -- trim


--[[

Shuffle a table

-- see: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle

Example:

cards = { "Ace", "King", "Queen", "Jack", 10, 9, 8, 7, 6, 5, 4, 3, 2 }

shuffle (cards)

--]]

function shuffle(t)
  local n = #t
 
  while n >= 2 do
    -- n is now the last pertinent index
    local k = math.random(n) -- 1 <= k <= n
    -- Quick swap
    t[n], t[k] = t[k], t[n]
    n = n - 1
  end
 
  return t
end


--[[

Directory scanner.

Calls function "f" for every file found in a path starting at "path".
Recurses to handle nested directories. Function "f" is called with two
arguments: (full) filename, statistics

See utils.readdir for the exact names of the entries supplied for each file.

See: http://www.gammon.com.au/forum/?id=9906


Example:


plugins = {}

-- this function is called for every found file
function load_file (name, stats)
 
  if stats.size > 0 and
     string.match (name:lower (), "%.xml$") and
     not stats.hidden then
       table.insert (plugins,  name)
  end -- if
  
end -- load_file

-- Scan plugins folder, passing each file found to "load_file" function.

scan_dir (GetInfo (60), load_file)

--]]


function scan_dir (path, f)

  -- find all files in that directory
  local t = assert (utils.readdir (path .. "\\*"))

  for k, v in pairs (t) do
      
    -- recurse to process subdirectory
    if v.directory then

      if k ~= "." and k ~= ".." then
        scan_dir (path .. "\\" .. k, f)
      end -- not current or owner directory

    else -- call supplied function
      f (path .. "\\" .. k, v)  
    end -- if 
 
  end -- for

end -- scan_dir 