-- ===================================================================
-- Overview.

-- This file is "top/modpol.ocutil.lua".

-- This file  provides a  collection of  largely portable  Lua utility
-- functions.  The collection is descended  from one assembled by Old-
-- Coder (Robert Kiraly).

-- ===================================================================

modpol.ocutil = {}

modpol.ocutil.log_console = true       -- Flag: Copy log to console
modpol.ocutil.logdir      = nil        -- Absolute path for  log-file  direc-
                                -- tory (or nil)

-- ===================================================================

-- Function: modpol.ocutil.fixnil
-- Params:   string s
-- Outputs:
--
-- If the string is nil   , returns "(nil)"
-- if the string is empty , returns "(empty)"
-- Otherwise, returns the string as it stands
--
-- This function can be used  to clarify -- and/or avoid crashes in --
-- debug messages.

-- ===================================================================

modpol.ocutil.fixnil = function (s)
    if     s == nil then s = "(nil)"
    elseif s == ""  then s = "(empty)"
    end
    return s
end

-- ===================================================================

-- Function: modpol.ocutil.setlogdir
-- Params:   string path
-- Outputs:
--
-- The input string should  be the absolute path for a writable direc-
-- tory that already exists.  This function tells  "modpol.ocutil.log" to put
-- log files in that directory.

-- ===================================================================

modpol.ocutil.setlogdir = function (path)
    if path ~= nil and path ~= "" then
        modpol.ocutil.logdir = path
    end
end

-- ===================================================================

-- Function: modpol.ocutil.setlogname
-- Params:   string name
-- Outputs:
--
-- The input string  should  be a filename  without a  path component.
-- This function tells  "modpol.ocutil.log" to use the specified filename for
-- the main log file.
--
-- "modpol.ocutil.setlogdir"  must be called  separately  to set the log-file
-- directory.

-- ===================================================================

modpol.ocutil.setlogname = function (name)
    if name ~= nil and name ~= "" then
        modpol.ocutil.logname = name
    end
end

-- ===================================================================

-- Function: modpol.ocutil.log
-- Params:   string s
-- Outputs:
--
-- Logs the specified string.  Appends a  newline if not  already pre-
-- sent.

-- ===================================================================

modpol.ocutil.log = function (s)
    s = modpol.ocutil.fixnil (s)
    s = s:gsub ("\n$", "", 1)   -- Remove trailing newline initially

    if modpol.ocutil.log_console then
        print (s)
    end

    if modpol.ocutil.logdir  ~= nil and
       modpol.ocutil.logname ~= nil then
       s = s .. "\n"           -- Add trailing newline

        local logpath = modpol.ocutil.logdir .. "/" .. modpol.ocutil.logname
        modpol.ocutil.file_append (logpath, s)
    end
end

-- ===================================================================

-- Function: modpol.ocutil.fatal_error
-- Params:   string s
-- Outputs:
--
-- Logs "Fatal error: " plus the specified string.  Appends a  newline
-- if not already present. Terminates the program.

-- ===================================================================

modpol.ocutil.fatal_error = function (s)
    modpol.ocutil.log ("Fatal Error: " .. s)
    os.exit (1)
    return nil                  -- Shouldn't be reached
end

-- ===================================================================

-- Function: modpol.ocutil.panic
-- Params:   string s
-- Outputs:
--
-- Logs "Internal error: " plus the specified string.  Appends a  new-
-- line if not already present. Terminates the program.

-- ===================================================================

modpol.ocutil.panic = function (s)
    modpol.ocutil.log ("Internal Error: " .. s)
    os.exit (1)
    return nil                  -- Shouldn't be reached
end

-- ===================================================================

-- Function: modpol.ocutil.str_empty
-- Params:   string x
-- Outputs:
--
-- Returns true if the string is nil or empty
-- Returns false otherwise

-- ===================================================================

modpol.ocutil.str_empty = function (x)
    if x == nil or x == "" then
        return true
    else
        return false
    end
end

-- ===================================================================

-- Function: modpol.ocutil.str_nonempty
-- Params:   string x
-- Outputs:
--
-- Returns true if the string is nil or empty
-- Returns false otherwise

-- ===================================================================

modpol.ocutil.str_nonempty = function (x)
    if x == nil or x == "" then
        return false
    else
        return true
    end
end

-- ===================================================================

-- Function: modpol.ocutil.table_empty
-- Params:   table tab
-- Outputs:
--
-- Returns true if the table is empty
-- Returns false otherwise

-- ===================================================================

modpol.ocutil.table_empty = function (tab)
    local next = next
    if next (tab) == nil then return true end
    return false
end

-- ===================================================================

-- Function: modpol.ocutil.table_nonempty
-- Params:   table tab
-- Outputs:
--
-- Returns false if the table is empty
-- Returns true otherwise

-- ===================================================================

modpol.ocutil.table_nonempty = function (tab)
    if modpol.ocutil.table_empty (tab) then return false end
    return true
end

-- ===================================================================

-- Function: modpol.ocutil.string_contains
-- Params:   strings a, b
-- Outputs:
--
-- Returns true if the 1st string contains the 2nd one
-- Returns false otherwise

-- ===================================================================

modpol.ocutil.str_contains = function (a, b)
    if string.match (a, b) then
        return true
    else
        return false
    end
end

-- ===================================================================

-- Function: modpol.ocutil.str_false
-- Params:   string x
-- Outputs:
--
-- This function checks the string for an  explicit false  (or equiva-
-- lent)  setting (as  opposed to empty or nil). If such a  setting is
-- found, true is returned. Otherwise, false is returned.

-- ===================================================================

modpol.ocutil.str_false = function (x)
    if x == "false" or x == "no"  or
       x == "off"   or x == "0"   or
       x == false   or x == 0 then
        return true
    else
        return false
    end
end

-- ===================================================================

-- Function: modpol.ocutil.str_true
-- Params:   string x
-- Outputs:
--
-- This function  checks the string for  an explicit true  (or equiva-
-- lent) setting. If such a setting is found, true is returned. Other-
-- wise, false is returned.

-- ===================================================================

modpol.ocutil.str_true  = function (x)
    if x == "true" or x == "yes" or
       x == "on"   or x == "1"   or
       x == true   or x == 1 then
        return true
    else
        return false
    end
end

-- ===================================================================

-- Function: modpol.ocutil.starts_with
-- Params:   strings String, Start
-- Outputs:
--
-- Returns true if the 1st string starts with the 2nd one
-- Returns false otherwise

-- ===================================================================

modpol.ocutil.starts_with = function (String, Start)
    if string.sub (String, 1, string.len (Start)) == Start then
        return true
    else
        return false
    end
end

-- ===================================================================

-- Function: modpol.ocutil.not_starts_with
-- Params:   strings String, Start
-- Outputs:
--
-- Returns false if the 1st string starts with the 2nd one
-- Returns true otherwise

-- ===================================================================

modpol.ocutil.not_starts_with = function (String, Start)
    if string.sub (String, 1, string.len (Start)) == Start then
        return false
    else
        return true
    end
end

-- ===================================================================

-- Function: modpol.ocutil.ends_with
-- Params:   strings String, End
-- Outputs:
--
-- Returns true if the 1st string ends with the 2nd one
-- Returns false otherwise
-- As a special case, returns true if the 2nd string is empty

-- ===================================================================

modpol.ocutil.ends_with = function (String, End)
    return End == '' or string.sub (String,
        -string.len (End)) == End
end

-- ===================================================================

-- Function: modpol.ocutil.not_ends_with
-- Params:   strings String, End
-- Outputs:
-- Returns false if the 1st string ends with the 2nd one
-- Returns true otherwise
-- As a special case, returns false if the 2nd string is empty

-- ===================================================================

modpol.ocutil.not_ends_with = function (String, End)
    if modpol.ocutil.ends_with (String, End) then
        return false
    else
        return true
    end
end

-- ===================================================================

-- Function: modpol.ocutil.firstch
-- Params:   string str
-- Outputs:
-- Returns the 1st character of the string

-- ===================================================================

modpol.ocutil.firstch = function (str)
    return string.sub (str, 1, 1)
end

-- ===================================================================

-- Function: modpol.ocutil.first_to_upper
-- Params:   string str
-- Outputs:
-- Returns the 1st character of the string in upper case

-- ===================================================================

modpol.ocutil.first_to_upper = function (str)
    return (str:gsub ("^%l", string.upper))
end

-- ===================================================================

-- Function: modpol.ocutil.all_first_to_upper
-- Params:   string str, flag cvtspace
-- Outputs:
--
-- Converts  the  1st character  of each word in  the string  to upper
-- case and  returns the result.  Words are delimited by spaces or un-
-- derscores.  If the flag is true,  also replaces  spaces with under-
-- scores.

-- ===================================================================

modpol.ocutil.all_first_to_upper = function (str, cvtspace)

    str = str:gsub ("^%l", string.upper)
    str = str:gsub ("[_ ]%l",
        function (a) return string.upper (a) end)

    if modpol.ocutil.str_true (cvtspace) then
        str = str:gsub ("_", " ")
    end

    return (str)
end

-- ===================================================================

-- Function: modpol.ocutil.strtok
-- Params:   strings source, delimitch
-- Outputs:
--
-- Breaks  the source string up into  a list of words and  returns the
-- list.
--
-- If "delimitch" is  omitted,  nil, or empty,  words are delimited by
-- spaces. Otherwise, words are  delimited by any of the characters in
-- "delimitch".

-- ===================================================================

modpol.ocutil.strtok = function (source, delimitch)
    if delimitch == nil or
       delimitch == ""  then delimitch = " " end

    local parts   = {}
    local pattern = '([^' .. delimitch .. ']+)'

    string.gsub (source, pattern,
        function (value)
            value = value:gsub ("^ *", "")
            value = value:gsub (" *$", "")
            parts [#parts + 1] = value
        end)

    return parts
end

-- ===================================================================

-- Function: modpol.ocutil.swap_key_value
-- Params:   table itable
-- Outputs:
-- Turns  keys into values and  vice versa and  returns  the resulting
-- table.

-- ===================================================================

modpol.ocutil.swap_key_value = function (itable)
    if itable == nil then return nil end
    local  otable = {}
    for key, value in pairs (itable) do otable [value] = key end
    return otable
end

-- ===================================================================

-- Function: modpol.ocutil.ktable_to_vtable
-- Params:   table ktable
-- Outputs:
--
-- "ktable_to_vtable" is short for "convert key table to value table".
--
-- The input table is assumed to be an arbitrary key-based list of the
-- general form:
-- { dog="woof", apple="red", book="read" }
--
-- This function takes the keys and returns them as an integer-indexed
-- array with the former keys stored now as values. The array is sort-
-- ed based on the former keys. The original values in the input table
-- are discarded. Sample output:
--
-- { "apple", "book", "dog", ... }

-- Here's  a  complete  code fragment which  demonstrates operation of
-- this function:

-- local tabby = { dog="woof", apple="red", book="read" }
--
-- print ("\nInput table:")
-- for ii, value in  pairs (tabby) do
--     print (ii .. ") " .. tabby [ii])
-- end
--
-- tabby = modpol.ocutil.ktable_to_vtable (tabby)
--
-- print ("\nOutput table:")
-- for ii, value in ipairs (tabby) do
--     print (ii .. ") " .. tabby [ii])
-- end

-- ===================================================================

modpol.ocutil.ktable_to_vtable = function (ktable)
    local vtable = {}
    if ktable == nil then return vtable end

    for key, _ in pairs (ktable) do
        table.insert (vtable, key)
    end

    table.sort (vtable)
    return vtable
end

-- ===================================================================

-- Function: modpol.ocutil.vtable_to_ktable
-- Params:   table vtable, scalar set_to
-- Outputs:
--
-- "vtable_to_ktable" is short for "convert value table to key table".
--
-- The input table is assumed to be an integer-indexed array of scalar
-- values.
--
-- This function creates a general table that holds the  scalar values
-- stored  now as keys and returns  the new table.  The new table maps
-- each of the keys to the value specified by "set_to".
--
-- If "set_to" is omitted or nil, it defaults to boolean true.

-- Here's  a  complete  code fragment which  demonstrates operation of
-- this function:
--
-- local tabby = { "apple", "book", "dog" }
--
-- print ("\nInput table:")
-- for ii, value in ipairs (tabby) do
--     print (ii .. ") " .. tabby [ii])
-- end
--
-- tabby = modpol.ocutil.vtable_to_ktable (tabby, 42)
--
-- print ("\nOutput table:")
-- for ii, value in  pairs (tabby) do
--     print (ii .. ") " .. tabby [ii])
-- end

-- ===================================================================

modpol.ocutil.vtable_to_ktable = function (vtable, set_to)

    local ktable = {}
    if vtable == nil then return ktable end
    if set_to == nil then set_to = true end

    for _, value in pairs (vtable) do
        ktable [value] = set_to
    end

    return ktable
end

-- ===================================================================

-- Function: modpol.ocutil.make_set
-- Params:   array harry
-- Outputs:
--
-- This function makes a set out of an array. Specifically, it returns
-- a new table with the  array's values as keys.  The  new table  maps
-- each key to boolean true.
--
-- Here's  a  complete  code fragment which  demonstrates operation of
-- this function:
--
-- local color_set = modpol.ocutil.make_set { "red", "green", "blue" }
-- if    color_set ["red"] ~= nil then print ("Supported color") end

-- ===================================================================

modpol.ocutil.make_set = function (list)
    local set = {}
    for _, l in ipairs (list) do set [l] = true end
    return set
end

-- ===================================================================

-- Function: modpol.ocutil.pos_to_str
-- Params:   position-table pos
-- Outputs:
--
-- This function takes a table of the following form as input:
-- { x=25.0, y=-135.5, z=2750.0 }
--
-- It returns the x-y-z values, separated by commas, as a string.

-- ===================================================================

modpol.ocutil.pos_to_str = function (pos)
    return pos.x .. "," .. pos.y .. "," .. pos.z
end

-- ===================================================================

-- Function: modpol.ocutil.table_length
-- Params:   table tabby
-- Outputs:  Returns number of entries in table
--
-- Note: This function works for tables in general as well as for int-
-- eger-indexed tables (i.e., arrays).

-- ===================================================================

modpol.ocutil.table_length = function (tabby)
    local count = 0
    for _ in pairs (tabby) do count = count+1 end
    return count
end

-- ===================================================================

-- Function: modpol.ocutil.clone_table
-- Params:   table tabby
-- Outputs:
--
-- This function returns  an independent clone of the input table.  It
-- should work correctly for most cases,  but special cases  that  re-
-- quire additional work may exist.
--
-- This function may also be called as "modpol.ocutil.table_clone".

-- ===================================================================

modpol.ocutil.clone_table = function (tabby)
    if tabby == nil then return nil end
    local copy = {}
    for k, v in pairs (tabby) do
        if type (v) == 'table' then v = modpol.ocutil.clone_table (v) end
        copy [k] = v
    end
    return copy
end

modpol.ocutil.table_clone = modpol.ocutil.clone_table

-- ===================================================================

-- Function: modpol.ocutil.file_exists
-- Params:   string path
-- Outputs:
--
-- The  input string should be the relative or absolute pathname for a
-- data file.  If the  file is read-accessible,  this function returns
-- true. Otherwise, it returns false.

-- ===================================================================

modpol.ocutil.file_exists = function (path)
    local file, err
    file, err = io.open (path, "rb")
    if err then return false end
    file:close()
    return true
end

-- ===================================================================

-- Function: modpol.ocutil.file_missing
-- Params:   string path
-- Outputs:
--
-- The  input string should be the relative or absolute pathname for a
-- data file.  If the  file is read-accessible,  this function returns
-- false. Otherwise, it returns true.

-- ===================================================================

modpol.ocutil.file_missing = function (path)
    if modpol.ocutil.file_exists (path) then return false end
    return true
end

-- ===================================================================

-- Function: modpol.ocutil.file_read
-- Params:   string path
-- Outputs:
--
-- The  input string should be the  absolute pathname for a data file.
-- If the file is read-accessible,  this function returns the contents
-- of the file. Otherwise, it returns nil.
--
-- Warning: There is presently no check for a maximum file size.

-- ===================================================================

modpol.ocutil.file_read = function (path)
    local file, err
    file, err = io.open (path, "rb")
    if err then return nil end
    local data = file:read ("*a")
    file:close()
    return data
end

-- ===================================================================

-- Function: modpol.ocutil.file_write
-- Params:   strings path, data
-- Outputs:
--
-- The 1st string should be the absolute pathname for a data file. The
-- file  doesn't need to  exist initially but the  directory that will
-- contain it does need to exist.
--
-- This function  writes the  2nd string to the file.  Existing stored
-- data is discarded.
--
-- This function returns true if successful and false otherwise.

-- ===================================================================

modpol.ocutil.file_write = function (path, data)
    local file, err
    file, err = io.open (path, "wb")
    if err then return false end
    io.output (file)
    io.write (data)
    io.close (file)
    return true
end

-- ===================================================================

-- Function: modpol.ocutil.file_append
-- Params:   strings path, data
-- Outputs:
--
-- This function is  identical to  "modpol.ocutil.file_write"  except for one
-- difference:  It  appends to existing files as opposed to overwrites
-- them.

-- ===================================================================

modpol.ocutil.file_append = function (path, data)
    local file, err
    file, err = io.open (path, "ab")
    if err then return false end
    io.output (file)
    io.write (data)
    io.close (file)
    return true
end

-- ===================================================================
-- End of file.