--[[ Lua rb settings reader
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2019 William Wilgus
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
]]

rb.settings = rb.settings or {}

local var = {offset = 1, size = 2, type = 3, fields = 3}

local function bytesLE_n(str)
    str = str or ""
    local tbyte={str:byte(1, -1)}
    local bpos, num = 1, 0
    for k = 1,#tbyte do -- (k = #t, 1, -1 for BE)
        num = num + tbyte[k] * bpos
        bpos = bpos * 256 --1<<8
    end
    return num
end

local function get_var_fields(s_var)
    -- converts member string into table
    -- var = {offset, size, "type"}
    s_var = s_var or ""
    local o, s, t = string.match(s_var, "(0x%x+),%s*(%d+),%s*(.+)")
    local tvar = {o, s, t}

    return #tvar == var.fields and tvar or nil
end

local function format_val(val, var_type)
    local ret, num
    if var_type == nil then
        return nil
    elseif var_type == "str" then
        -- stop at first null byte, return nil if str doesn't exist
        return val and string.match(val, "^%Z+") or nil
    end

    num = bytesLE_n(val)
    if string.find(var_type, "^b") then
        if(num <= 0) then
            ret = false
        else
            ret = true
        end
    elseif string.find(var_type, "^u_[cil]") then
        -- Lua integers are signed so we need to do a bit of extra processing
        ret = (string.format("%u", num))
    else
        ret = num
    end

    return ret
end

local function dump_struct(t_settings, t_struct, n_elems, t_var)
    --Internal function dumps structs
    local tdata = {}

    local function struct_get_elem(v, elem_offset)
        local val, offset, tvar1
        tvar1 = get_var_fields(v)
        offset = t_var[var.offset] + tvar1[var.offset] + elem_offset
        val = t_settings(offset, tvar1[var.size])
        return format_val(val, tvar1[var.type])
    end

    if n_elems > 0 then
        -- Array of structs, struct[elems];
        local elemsize = (t_var[var.size] / n_elems)
        for i = 0, n_elems - 1 do
            tdata[i] = tdata[i] or {}
            for k1, v1 in pairs(t_struct) do
                tdata[i][k1] = struct_get_elem(v1, (elemsize * i))
            end
        end
    else
        -- single struct, struct;
        for k1, v1 in pairs(t_struct) do
            tdata[k1] = struct_get_elem(v1, 0)
        end
    end
    return tdata
end

local function get_array_elems(var_type)
    --extract the number of elements, returns 0 if not found
    local elems = string.match(var_type,".*%[(%d+)%]")
    return tonumber(elems) or 0
end

local function get_struct_name(var_type)
    --extract the name of a struct, returns nil if not found
    return string.match(var_type,"^s_([^%[%]%s]+)")
end

function rb.settings.read(s_settings, s_var, s_groupname)
    local data, val
    local tvar = get_var_fields(s_var)
    if tvar == nil then return nil end

    local elems = get_array_elems(tvar[var.type])
    local structname = get_struct_name(tvar[var.type])

    local tsettings = rb[s_settings]
    if not tsettings then error(s_settings .. " does not exist") end

    if structname and rb[s_groupname] then
        return dump_struct(tsettings, rb[s_groupname][structname], elems, tvar)
    end

    local voffset, vsize, vtype = tvar[var.offset], tvar[var.size], tvar[var.type]
    if elems > 0 then
        -- Arrays of values, val[elems];
        data = {}
        local elemsize = (vsize / elems)

        for i = 0, elems - 1 do
            val = tsettings(voffset + (elemsize * i), elemsize)
            data[i] = format_val(val, vtype)
        end
    else
        -- Single value, val;
        if vtype == "ptr_char" then -- (**char)
            vtype = "str"
            val = tsettings(voffset, vsize, nil, true)
        else
            val = tsettings(voffset, vsize)
        end
        data = format_val(val, vtype)
    end
    return data
end

function rb.settings.dump(s_settings, s_groupname, s_structname, t_output, fn_filter)
    t_output = t_output or {}
    fn_filter = fn_filter or function(s,k) return true end
    local tgroup = rb[s_groupname]
    s_structname = s_structname or s_settings
    for k, v in pairs(tgroup[s_structname]) do
        if fn_filter(s_structname, k) then
            t_output[k] = rb.settings.read(s_settings, v, s_groupname)
        end
    end
    return t_output
end

return true
