--   This file is part of Reno desktop.
--
--  Reno desktop 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 3 of the License, or (at your option) any later version.
--
--  Reno desktop 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 Reno desktop. If not, see <https://www.gnu.org/licenses/>. 
-- JSON layout builder 
local json = require("dkjson")
local gears = require("gears")
local wibox = require("wibox")
local awful = require("awful")

local builtins = {
    h_spacer = function(o)
        return {
            forced_width = o.size or 3,
            widget = wibox.container.background
        }
    end,
    v_spacer = function(o)
        return {
            forced_height = o.size or 3,
            widget = wibox.container.background
        }
    end
    -- Insert your other builtins here
}
-- Load a bunch of builtins from awful.titlebar
for _,name in pairs({
    "titlewidget","iconwidget","floatingbutton",
    "maximizedbutton","minimizebutton","closebutton",
    "ontopbutton","stickybutton"
}) do
    builtins[name] = function(o)
        o.widget = awful.titlebar.widget[name](o.client)
        return o
    end
end

return function(description,opts)
    local style = opts.style or {}
    local c = opts.client
    local s = opts.screen
    local buttons = opts.buttons
    -- Build a layout given a JSON description, a style and client 
    -- (if applicable)
    local layout = {}
    local test,err = json.decode(description)
    if not test then
        error("Builder failure: "..err)
    end
    local function inner_builder(struct)
        if struct.widget then -- External widget descriptions
            return require(struct.widget)(gears.table.join({
                layout = (struct.layout and inner_builder(struct.layout)),                         client = (struct.client and c),
                screen = (struct.screen and s)
            },struct.options or {},opts.passthrough or {}))
        elseif struct.list then -- List descriptions
            local list = {
                layout = wibox.layout.fixed[(
                    (struct.vertical and "vertical") or
                    "horizontal"
                )],
                spacing = style.spacing
            }
            for k,v in pairs(struct.list) do
                if v.draggable then
                    list.buttons = buttons
                else
                    local new_obj = inner_builder(v)
                    if new_obj then
                        table.insert(list,new_obj)
                    end
                end
            end
            return list
        elseif struct.align then -- Align structure descriptions
            local orient = (
                    (struct.vertical and "vertical") or
                    "horizontal"
                )
            local list = {
                {
                    layout = wibox.layout.fixed[orient],
                    spacing = style.spacing
                },{
                    layout = wibox.layout.flex[orient],
                    spacing = style.spacing
                },{
                    -- Simulating "spacing" parameter
                    widget = builtins[(struct.vertical and "v_spacer") or
                        "h_spacer"]({size = style.spacing}),
                    layout = wibox.layout.fixed[orient],
                    spacing = style.spacing
                },
                layout = wibox.layout.align[orient]
            }
            for k,v in pairs({"left","center","right"}) do
                for _,obj in pairs(struct.align[v]) do
                    if obj.draggable then
                        list[k].buttons = buttons
                    else
                        local new_obj = inner_builder(obj)
                        if new_obj then
                            table.insert(list[k],new_obj)
                        end
                    end
                end
            end
            -- Part of simulating "spacing" parameter
            table.insert(list[1],builtins[(struct.vertical and "v_spacer") or
                        "h_spacer"]({size = style.spacing}))

            return list
        elseif struct.builtin then -- Builtin widget descriptions
            if not builtins[struct.builtin] then
                error("Builtin not defined: "..struct.builtin)
            end
            return builtins[struct.builtin](gears.table.join({
                client = (struct.client and c)
            },struct.options or {}))
        end 
        -- If this gets interpreted it's safe to say none of the constructions
        -- above got matched.
        print("Object where the error occured: ")
        gears.debug.dump(struct) 
        error("Builder error: invalid object description")
    end
    return inner_builder(test,layout),test.context_options
end
