local awful = require("awful")
local gears = require("gears")
local menubar_utils = require("menubar.utils")
local wibox = require("wibox")
local beautiful = require("beautiful")
local awmtk = require("awmtk")
local function cascade_close(path,limit)
    for I = #path,limit or 1,-1 do
        if path[I] then
            path[I].visible = false
        end
        path[I] = nil
    end
end
local function merge_into_widget(widget,args)
    for k,v in ipairs(args) do
        table.insert(widget,v)
    end
end
local function get_button_container()
    --This function prevents mouse.current_widget_geometry from picking up textboxes
    local output = {}
    local geo = mouse.current_widgets
    for k,v in pairs(geo) do
        if tostring(v):match("^widget_background") then
            output = mouse.current_widget_geometries[k]
            break
        end
    end
    return output
end
local function loader(options)
    local style = awmtk.style(awmtk.defaults,options.style or {},"menu_")
    options = options or {}
    -- Define styles
    local button_bg_on = style.menu_button_bg_focus
    local button_bg_off = style.menu_button_bg_normal
    local userlinks = wibox.widget {
        layout = (
            options.vertical and 
            wibox.layout.fixed.vertical
        ) or wibox.layout.fixed.horizontal,
        spacing = style.menu_container_spacing or 4,
    }
    local button_template = function(v)
        return {
            layout = (
                options.vertical and
                wibox.layout.fixed.horizontal
            ) or wibox.layout.fixed.vertical,
            (v[3] and {
                widget = wibox.widget.imagebox,
                resize = true,
                image = v[3],
            }),
            {
                markup = v[1] or "",
                widget = wibox.widget.textbox,
                ellipsize = "end"
            },
            spacing = 2
        }
    end
    local button_style = {
        bg = button_bg_off,
        forced_height = style.menu_button_height or 
            style.menu_height or
            24,
        forced_width = style.menu_button_width or 
            style.menu_width or
            140
    }
    local menu_template = function(tree_layer) 
        return {
            layout = (
                options.vertical
                and wibox.layout.fixed.vertical
            ) or wibox.layout.fixed.horizontal,
            spacing = (
                options.vertical and
                style.menu_container_spacing_vertical
            ) or style.menu_container_spacing_horizontal,
            tree_layer = tree_layer,
        }
    end
    local menu_popup_template = {
        visible = false,
        ontop = true,
        preferred_positions = (options.vertical and {"right","left"}) or {"top","bottom"},
        preferred_anchors = (options.inverse and {"back","front"}) or {"front","back"},
    }
    --Create menu root
    local menu = menu_template(1)
    local tree_path = {"spacer"}
    --Insert widgets above/before the menu
    merge_into_widget(menu,options.before or {})
    local function generate_menu(leaf,menu)
        --Iterate over a table of widget defining tables
        for k,v in ipairs(leaf) do
            if type(v) ~= "table" then
                --Error if the structure is invalid
                error("Invalid leaf type "..type(v).." in menu tree")
            else
                local tree_layer = menu.tree_layer+1
                --Create a button widget
                local new_button = style.button(button_template(v),button_style)
                if type(v[2]) == "table" then
                    --Create a popup template for the new menu leaf
                    local new_popup = menu_template(tree_layer)
                    --Add buttons and widgets to the new leaf
                    merge_into_widget(new_popup,v[2].before or {})
                    generate_menu(v[2],new_popup)
                    merge_into_widget(new_popup,v[2].after or {})
                    new_popup = awful.popup(style.container(new_popup,menu_popup_template))
                    new_button:connect_signal("mouse::enter",function()
                        --Hide cascading leaves of a branch when leaf changes
                        if menu.current_selection and (menu.current_selection ~= new_popup) then
                                cascade_close(tree_path,tree_layer) 
                        end
                        --Move the new popup widget closer to mouse and align it precisely to the button
                        if mouse.current_widget_geometry then
                            local geo = get_button_container()
                            tree_path[tree_layer] = new_popup
                            --Apparently it's a thing that needed fixing.
                            --I don't question it, and neither should you.
                            if geo and geo.x and geo.y then
                                new_popup:move_next_to(geo)
                            end
                            --previous method call aligns the popup to the button, 
                            --NOT the buttons within the popup. So we do that ourselves (somewhat).
                            if new_popup.current_anchor == "front" then
                                new_popup.y = new_popup.y - style.menu_container_inner_margin
                            else
                                new_popup.y = new_popup.y + style.menu_container_inner_margin
                            end
                            new_popup.visible = true
                            menu.current_selection = new_popup
                        end
                    end)
                elseif type(v[2]) == "function" then
                    new_button:connect_signal("button::press",v[2])
                elseif type(v[2]) == "string" then
                    new_button:connect_signal("button::press",function()
                        awful.spawn(v[2])
                    end)
                end
                new_button:connect_signal("mouse::enter",function()
                    --Set button bg
                    if menu.current_button then
                        menu.current_button.bg = button_bg_off
                    end
                    menu.current_button = new_button
                    menu.current_button.bg = button_bg_on
                end)
                new_button:connect_signal("button::press",function()
                    cascade_close(tree_path,1)
                end)
                table.insert(menu,new_button)
            end
        end
    end
    generate_menu(options.items,menu)
    --Insert widgets below/after the menu
    merge_into_widget(menu,options.after or {})
    menu = awful.popup(style.container(menu,menu_popup_template))
    tree_path[1] = menu
    menu.toggle = function(x,y)
        if (not x) or (not y) then
            x = mouse.coords().x
            y = mouse.coords().y
        end
        menu.x = x
        menu.y = y
        if menu.x+menu.width > menu.screen.geometry.width then
            menu.x = menu.x-menu.width
        end
        if menu.y+menu.height > menu.screen.geometry.height then
            menu.y = menu.y-menu.height
        end
        if menu.visible then
            cascade_close(tree_path,2)
        end
        tree_path[1] = menu
        menu.visible = (not menu.visible)
    end 
    return menu
end

return loader
