-- This template provides a timeline like display for tag changes.
-- Basically a taglist with some pretty dots on the side. Note that the way it
-- copy tags is fragile and probably not really supportable.

local file_path, image_path = ...
require("_common_template")(...)
local capi = {client = client, screen = screen}
local Pango = require("lgi").Pango
local PangoCairo = require("lgi").PangoCairo
require("awful.screen")
require("awful.tag")
local floating_l = require("awful.layout.suit.floating")
local taglist = require("awful.widget.taglist")
local gtable = require("gears.table")
local shape = require("gears.shape")
local color = require("gears.color")
local wibox = require("wibox")
local beautiful = require("beautiful")

local bar_size, radius = 18, 2
local screen_scale_factor = 5

local history = {}

local module = {}

-- Draw a mouse cursor at [x,y]
local function draw_mouse(cr, x, y)
    cr:set_source_rgb(1, 0, 0)
    cr:move_to(x, y)
    cr:rel_line_to( 0, 10)
    cr:rel_line_to( 3, -2)
    cr:rel_line_to( 3,  4)
    cr:rel_line_to( 2,  0)
    cr:rel_line_to(-3, -4)
    cr:rel_line_to( 4,  0)
    cr:close_path()
    cr:fill()
end

-- Instead of returning the maximum size, return the preferred one.
local function fixed_fit2(self, context, orig_width, orig_height)
    local width, height = orig_width, orig_height
    local used_in_dir, used_max = 0, 0

    for _, v in pairs(self._private.widgets) do
        local w, h = wibox.widget.base.fit_widget(self, context, v, width, height)

        -- Catch bugs before they crash Chrome (Firefox handles this better)
        assert(w < 5000)
        assert(h < 5000)

        local in_dir, max
        if self._private.dir == "y" then
            max, in_dir = w, h
            height = height - in_dir
        else
            in_dir, max = w, h
            width = width - in_dir
        end
        if max > used_max then
            used_max = max
        end
        used_in_dir = used_in_dir + in_dir
    end

    local spacing = self._private.spacing * (#self._private.widgets-1)

    -- Catch bugs before they crash Chrome (Firefox handles this better)
    assert(used_max    < 9000)
    assert(used_in_dir < 9000)

    if self._private.dir == "y" then
        return used_max, used_in_dir + spacing
    end
    return used_in_dir + spacing, used_max
end

-- Imported from the Collision module.
local function draw_lines()
    local ret = wibox.widget.base.make_widget()

    function ret:fit()
        local pager_w = math.max(root.size()/screen_scale_factor, 60)

        --FIXME support multiple screens.
        local w = (#screen[1].tags * pager_w)
            + ((#screen[1].tags - 1)*5)

        return w, self.widget_pos and #self.widget_pos*6 or 30
    end

    function ret:draw(_, cr, _, h)
        if (not self.widget_pos) or (not self.pager_pos) then return end

        cr:set_line_width(1)
        cr:set_source_rgba(0,0,0,0.3)

        local count = #self.widget_pos

        for k, t in ipairs(self.widget_pos) do
            local point1 = {x = t.widget_x, y = 0, width = t.widget_width, height = 1}
            local point2 = {x = t.pager_x, y = 0, width = t.pager_width, height = h}
            assert(point1.x and point1.width)
            assert(point2.x and point2.width)

            local dx = (point1.x == 0 and radius or 0) + (point1.width and point1.width/2 or 0)

            cr:move_to(bar_size+dx+point1.x, point1.y+2*radius)
            cr:line_to(bar_size+dx+point1.x, point2.y+(count-k)*((h-2*radius)/count)+2*radius)
            cr:line_to(point2.x+point2.width/2, point2.y+(count-k)*((h-2*radius)/count)+2*radius)
            cr:line_to(point2.x+point2.width/2, point2.y+point2.height)
            cr:stroke()

            cr:arc(bar_size+dx+point1.x, point1.y+2*radius, radius, 0, 2*math.pi)
            cr:fill()

            cr:arc(point2.x+point2.width/2, point2.y+point2.height-radius, radius, 0, 2*math.pi)
            cr:fill()
        end
    end

    return ret
end

local function gen_vertical_line(args)
    args = args or {}

    local w = wibox.widget.base.make_widget()

    function w:draw(_, cr, w2, h)
        cr:set_source_rgba(0,0,0,0.5)

        if args.begin then
            cr:rectangle(w2/2-0.5, h/2, 1, h/2)
        elseif args.finish then
            cr:rectangle(w2/2-0.5, 0, 1, h/2)
        else
            cr:rectangle(w2/2-0.5, 0, 1, h)
        end

        cr:fill()

        if args.dot then
            cr:arc(w2/2, args.center and h/2 or w2/2 ,bar_size/4, 0, 2*math.pi)
            cr:set_source_rgb(1,1,1)
            cr:fill_preserve()
            cr:set_source_rgba(0,0,0,0.5)
            cr:stroke()
        end
    end

    function w:fit()
        return bar_size, bar_size
    end

    return w
end

local function gen_taglist_layout_proxy(tags, w2, name)
    local l = wibox.layout.fixed.horizontal()
    l.fit = fixed_fit2

    local layout = l.layout

    l.layout = function(self,context, width, height)
        local ret = layout(self,context, width, height)

        for k, v in ipairs(ret) do
            tags[k][name.."_x"    ] = v._matrix.x0
            tags[k][name.."_width"] = v._width
        end

        if w2 then
            w2[name.."_pos"] = tags

            if not w2[name.."_configured"] then
                rawset(w2, name.."_configured", true)
                w2:emit_signal("widget::redraw_needed")
                w2:emit_signal("widget::layout_changed")
            end
        end

        return ret
    end

    return l
end

local function gen_fake_taglist_wibar(tags, w2)
    local layout = gen_taglist_layout_proxy(tags, w2, "widget")
    local w = wibox.widget {
        {
            {
                forced_height = bar_size,
                forced_width  = bar_size,
                image         = beautiful.awesome_icon,
                widget        = wibox.widget.imagebox,
            },
            taglist {
                forced_height = 14,
                forced_width  = 300,
                layout        = layout,
                screen        = screen[1],
                filter        = taglist.filter.all,
                source        = function() return tags end,
            },
            fit = fixed_fit2,
            layout = wibox.layout.fixed.horizontal,
        },
        bg     = beautiful.bg_normal,
        widget = wibox.container.background
    }

    -- Make sure it nevers goes unbounded by accident.
    local w3, h3 = w:fit({dpi=96}, 9999, 9999)
    assert(w3 < 5000 and h3 < 5000)

    return w
end

local function gen_cls(c,results)
    local ret = setmetatable({},{__index = function(_, i)
            local ret2 = c[i]
            if type(ret2) == "function" then
                if i == "geometry" then
                    return function(_, val)
                        if val then
                            c:geometry(gtable.crush(c:geometry(), val))
                            -- Make a copy as the original will be changed
                            results[c] = gtable.clone(c:geometry())
                        end
                        return c:geometry()
                    end
                else
                    return function(_,...) return ret2(c,...) end
                end
            end
            return ret2
        end})
    return ret
end

local function fake_arrange(tag)
    local cls,results,flt = {},setmetatable({},{__mode="k"}),{}
    local _, l = tag.screen, tag.layout
    local focus, focus_wrap = capi.client.focus, nil
    for _ ,c in ipairs (tag:clients()) do
        -- Handle floating client separately
        if not c.minimized then
            local floating = c.floating
            if (not floating) and (l ~=  floating_l) then
                cls[#cls+1] = gen_cls(c,results)
                if c == focus then
                    focus_wrap = cls[#cls]
                end
            else
                flt[#flt+1] = gtable.clone(c:geometry())
                flt[#flt].c = c
            end
        end
    end

    -- The magnifier layout require a focussed client
    -- there wont be any as that layout is not selected
    -- take one at random or (TODO) use stack data
    if not focus_wrap then
        focus_wrap = cls[1]
    end

    local param = {
        tag              = tag,
        screen           = 1,
        clients          = cls,
        focus            = focus_wrap,
        geometries       = setmetatable({}, {__mode = "k"}),
        workarea         = tag.screen.workarea,
        useless_gap      = tag.gaps or 4,
        apply_size_hints = false,
    }

    l.arrange(param)

    local ret = {}

    for _, geo_src in ipairs {param.geometries, flt } do
        for c, geo in pairs(geo_src) do
            geo.c = geo.c or c
            table.insert(ret, geo)
        end
    end

    return ret
end

local function gen_fake_clients(tag, args)
    local pager = wibox.widget.base.make_widget()

    function pager:fit()
        return 60, 48
    end

    if not tag then return end

    local sgeo = tag.screen.geometry

    local show_name = args.display_client_name or args.display_label

    function pager:draw(_, cr, w, h)
        if not tag.client_geo then return end

        for _, geom in ipairs(tag.client_geo) do
            local x      = (geom.x*w)/sgeo.width
            local y      = (geom.y*h)/sgeo.height
            local width  = (geom.width*w)/sgeo.width
            local height = (geom.height*h)/sgeo.height
            cr:set_source(color(geom.c.color or beautiful.bg_normal))
            cr:rectangle(x,y,width,height)
            cr:fill_preserve()
            cr:set_source(color(geom.c.border_color or beautiful.border_color))
            cr:stroke()

            if show_name and type(geom.c) == "table" and geom.c.name then
                cr:set_source_rgb(0, 0, 0)
                cr:move_to(x + 2, y + height - 2)
                cr:show_text(geom.c.name)
            end
        end

        -- Draw the screen outline.
        cr:set_source(color("#00000044"))
        cr:set_line_width(1.5)
        cr:set_dash({10,4},1)
        cr:rectangle(0, 0, w, h)
        cr:stroke()
    end

    return pager
end

local function gen_fake_pager_widget(tags, w2, args)
    local layout = gen_taglist_layout_proxy(tags, w2, "pager")
    layout.spacing = 10

    for _, t in ipairs(tags) do
        layout:add(wibox.widget {
            gen_fake_clients(t, args),
            widget        = wibox.container.background
        })
    end

    return layout
end

local function wrap_timeline(w, dot)
    return wibox.widget {
            gen_vertical_line { dot = dot or false},
            {
                w,
                top     = 0,
                bottom  = dot and 5 or 0,
                left    = 0,
                widget  = wibox.container.margin
            },
            fit    = fixed_fit2,
            layout = wibox.layout.fixed.horizontal
        }
end

local function gen_vertical_space(spacing)
    return wibox.widget {
        draw   = function() end,
        fit    = function() return 1, spacing end,
        widget = wibox.widget.base.make_widget()
    }
end

local function gen_label(text)
    return wibox.widget {
            gen_vertical_line {
                dot    = true,
                begin  = text == "Begin",
                finish = text == "End",
                center = true,
            },
        {
            {
                {
                    {
                        markup       = "<span size='smaller'><b>"..text.."</b> </span>",
                        forced_width = 50,
                        widget       = wibox.widget.textbox
                    },
                    top    = 2,
                    bottom = 2,
                    right  = 5,
                    left   = 10,
                    widget = wibox.container.margin
                },
                shape        = shape.rectangular_tag,
                border_width = 2,
                border_color = beautiful.border_color,
                bg           = beautiful.bg_normal,
                widget       = wibox.container.background
            },
            top    = 10,
            bottom = 10,
            widget = wibox.container.margin
        },
        fit    = fixed_fit2,
        layout = wibox.layout.fixed.horizontal
    }
end

local function draw_info(s, cr, factor)
    cr:set_source_rgba(0, 0, 0, 0.4)

    local pctx    = PangoCairo.font_map_get_default():create_context()
    local playout = Pango.Layout.new(pctx)
    local pdesc   = Pango.FontDescription()
    pdesc:set_absolute_size(11 * Pango.SCALE)
    playout:set_font_description(pdesc)

    local rows = {
        "primary", "index", "geometry", "dpi", "dpi range", "outputs:"
    }

    local dpi_range = s.minimum_dpi and s.preferred_dpi and s.maximum_dpi
        and (s.minimum_dpi.."-"..s.preferred_dpi.."-"..s.maximum_dpi)
        or s.dpi.."-"..s.dpi

    local values = {
        s.primary and "true" or "false",
        s.index,
        s.x..":"..s.y.." "..s.width.."x"..s.height,
        s.dpi,
        dpi_range,
        "",
    }

    for n, o in pairs(s.outputs) do
        table.insert(rows, "  "..n)
        table.insert(values,
            math.ceil(o.mm_width).."mm x "..math.ceil(o.mm_height).."mm"
        )
    end

    local col1_width, col2_width, height = 0, 0, 0

    -- Get the extents of the longest label.
    for k, label in ipairs(rows) do
        local attr, parsed = Pango.parse_markup(label..":", -1, 0)
        playout.attributes, playout.text = attr, parsed
        local _, logical = playout:get_pixel_extents()
        col1_width = math.max(col1_width, logical.width+10)

        attr, parsed = Pango.parse_markup(values[k], -1, 0)
        playout.attributes, playout.text = attr, parsed
        _, logical = playout:get_pixel_extents()
        col2_width = math.max(col2_width, logical.width+10)

        height = math.max(height, logical.height)
    end

    local dx = (s.width*factor - col1_width - col2_width - 5)/2
    local dy = (s.height*factor - #values*height)/2 - height

    -- Draw everything.
    for k, label in ipairs(rows) do
        local attr, parsed = Pango.parse_markup(label..":", -1, 0)
        playout.attributes, playout.text = attr, parsed
        playout:get_pixel_extents()
        cr:move_to(dx, dy)
        cr:show_layout(playout)

        attr, parsed = Pango.parse_markup(values[k], -1, 0)
        playout.attributes, playout.text = attr, parsed
        local _, logical = playout:get_pixel_extents()
        cr:move_to( dx+col1_width+5, dy)
        cr:show_layout(playout)

        dy = dy + 5 + logical.height
    end
end

local function gen_ruler(h_or_v, factor, margins)
    local ret = wibox.widget.base.make_widget()

    function ret:fit()
        local w, h
        local rw, rh = root.size()
        rw, rh = rw*factor, rh*factor

        if h_or_v == "vertical" then
            w = 1
            h = rh + margins.top/2 + margins.bottom/2
        else
            w = rw + margins.left/2 + margins.right/2
            h = 1
        end

        return w, h
    end

    function ret:draw(_, cr, w, h)
        cr:set_source(color("#77000033"))
        cr:set_line_width(2)
        cr:set_dash({1,1},1)
        cr:move_to(0, 0)
        cr:line_to(w == 1 and 0 or w, h == 1 and 0 or h)
        cr:stroke()
    end

    return ret
end

-- When multiple tags are present, only show the selected tag(s) for each screen.
local function gen_screens(l, screens, args)
    local margins = {left=50, right=50, top=30, bottom=30}

    local ret = wibox.layout.manual()

    local sreen_copies = {}

    -- Keep a copy because it can change.
    local rw, rh = root.size()

    -- Find the current origin.
    local x0, y0 = math.huge, math.huge

    for s in screen do
        x0, y0 = math.min(x0, s.geometry.x), math.min(y0, s.geometry.y)
        local scr_cpy = gtable.clone(s.geometry, false)
        scr_cpy.outputs = gtable.clone(s.outputs, false)
        scr_cpy.primary = screen.primary == s

        for _, prop in ipairs {
          "dpi", "index", "maximum_dpi", "minimum_dpi", "preferred_dpi" } do
            scr_cpy[prop] = s[prop]
        end

        table.insert(sreen_copies, scr_cpy)
    end

    function ret:fit()
        local w = margins.left+(x0+rw)/screen_scale_factor + 5 + margins.right
        local h = margins.top +(y0+rh)/screen_scale_factor + 5 + margins.bottom
        return w, h
    end

    -- Add the rulers.
    for _, s in ipairs(sreen_copies) do
        ret:add_at(
            gen_ruler("vertical"  , 1/screen_scale_factor, margins),
            {x=margins.left+s.x/screen_scale_factor, y =margins.top/2}
        )
        ret:add_at(
            gen_ruler("vertical"  , 1/screen_scale_factor, margins),
            {x=margins.left+s.x/screen_scale_factor+s.width/screen_scale_factor, y =margins.top/2}
        )
        ret:add_at(
            gen_ruler("horizontal", 1/screen_scale_factor, margins),
            {y=margins.top+s.y/screen_scale_factor, x =margins.left/2}
        )
        ret:add_at(
            gen_ruler("horizontal", 1/screen_scale_factor, margins),
            {y=margins.top+s.y/screen_scale_factor+s.height/screen_scale_factor, x =margins.left/2}
        )
    end

    -- Print an outline for the screens
    for k, s in ipairs(sreen_copies) do
        s.widget = wibox.widget.base.make_widget()

        local wb = gen_fake_taglist_wibar(screens[k].tags)
        wb.forced_width = s.width/screen_scale_factor

        -- The clients have an absolute geometry, transform to relative.
        if screens[k].tags[1] then
            for _, geo in ipairs(screens[k].tags[1].client_geo) do
                geo.x = geo.x - s.x
                geo.y = geo.y - s.y
            end
        end

        local clients_w = gen_fake_clients(screens[k].tags[1], args)

        local content = wibox.widget {
            wb,
            clients_w,
            nil,
            layout       = wibox.layout.align.vertical,
            forced_width = s.width/screen_scale_factor,
        }

        function s.widget:fit()
            return s.width/screen_scale_factor, s.height/screen_scale_factor
        end

        function s.widget:draw(_, cr, w, h)
            cr:set_source(color("#00000044"))
            cr:set_line_width(1.5)
            cr:set_dash({10,4},1)
            cr:rectangle(1,1,w-2,h-2)
            cr:stroke()

            if args.display_label ~= false then
                draw_info(s, cr, 1/screen_scale_factor)
            end
        end

        function s.widget:after_draw_children(_, cr)
            if args.display_mouse and mouse.screen.index == s.index then
                local rel_x = mouse.coords().x - s.x
                local rel_y = mouse.coords().y - s.y
                draw_mouse(cr, rel_x/screen_scale_factor+5, rel_y/screen_scale_factor+5)
            end
        end

        function s.widget:layout(_, width, height)
            return { wibox.widget.base.place_widget_at(
                content, 0, 0, width, height
            ) }
        end

        ret:add_at(s.widget, {x=margins.left+s.x/screen_scale_factor, y=margins.top+s.y/screen_scale_factor})
    end

    l:add(wrap_timeline(wibox.widget {
        markup  = "<i>Current tags:</i>",
        opacity = 0.5,
        widget  = wibox.widget.textbox
    }, true))
    l:add(wrap_timeline(ret,false))
end

-- When a single screen is present, show all tags.
local function gen_noscreen(l, tags, args)
    local w2 = draw_lines()
    l:add(wrap_timeline(wibox.widget {
        markup  = "<i>Current screens:</i>",
        opacity = 0.5,
        widget  = wibox.widget.textbox
    }, true))

    local wrapped_wibar = wibox.widget {
        gen_fake_taglist_wibar(tags, w2),
        fill_space = false,
        fit = fixed_fit2,
        layout = wibox.layout.fixed.horizontal
    }

    l:add(wrap_timeline(wrapped_wibar, false))

    if #capi.client.get() > 0 or args.show_empty then
        local w3, h3 = w2:fit({dpi=96}, 9999, 9999)
        assert(w3 < 5000 and h3 < 5000)

        l:add(wrap_timeline(w2, false))
        l:add(wrap_timeline(gen_fake_pager_widget(tags, w2, args), false))
    end
end

local function gen_timeline(args)
    local l = wibox.layout.fixed.vertical()
    l.fit = fixed_fit2

    l:add(gen_label("Begin"))

    for _, event in ipairs(history) do
        local ret = event.callback()
        if event.event == "event" then
            l:add(wrap_timeline(gen_vertical_space(5)))
            l:add(wrap_timeline(wibox.widget {
                markup = "<u><b>"..event.description.."</b></u>",
                widget = wibox.widget.textbox
            }, true))
        elseif event.event == "tags" and #ret == 1 and not args.display_screen then
            gen_noscreen(l, ret[1].tags, args)
        elseif event.event == "tags" and (#ret > 1 or args.display_screen) then
            gen_screens(l, ret, args)
        end
    end

    -- Spacing.
    l:add(wrap_timeline(gen_vertical_space(10)))

    l:add(gen_label("End"))

    return l
end

local function wrap_with_arrows(widget)
    local w = widget:fit({dpi=96}, 9999,9999)

    local arrows = wibox.widget.base.make_widget()

    function arrows:fit()
        return w, 10
    end

    function arrows:draw(_, cr, w2, h)

        cr:set_line_width(1)
        cr:set_source_rgba(1, 0, 0, 0.3)

        w2 = math.min(640, w2)

        local x = (w2 % 24) / 2

        while x + 15 < w2 do
            cr:move_to(x+2  , 0  )
            cr:line_to(x+10 , h-1)
            cr:line_to(x+20 , 0  )
            cr:stroke()
            x = x + 24
        end
    end

    assert(w < 5000)

    return wibox.widget {
        widget,
        {
            draw   = function() end,
            fit    = function() return 1, 10 end,
            widget = wibox.widget.base.make_widget()
        },
        {
            markup      = "<span color='#ff000055'>Code for this sequence</span>",
            align       = "center",
            foced_width = w,
            widget      = wibox.widget.textbox,
        },
        arrows,
        forced_width = w,
        fit          = fixed_fit2,
        layout       = wibox.layout.fixed.vertical
    }
end

function module.display_tags()
    local function do_it()
        local ret = {}
        for s in screen do
            local st = {}
            for _, t in ipairs(s.tags) do
                -- Copy just enough for the taglist to work.
                table.insert(st, {
                    name                = t.name,
                    selected            = t.selected,
                    icon                = t.icon,
                    screen              = t.screen,
                    _private            = t._private,
                    clients             = t.clients,
                    layout              = t.layout,
                    master_width_factor = t.master_width_factor,
                    client_geo          = fake_arrange(t),
                })
                assert(#st[#st].client_geo == #t:clients())
            end
            table.insert(ret, {tags=st})
        end
        return ret
    end

    table.insert(history, {event="tags", callback = do_it})
end

function module.add_event(description, callback)
    assert(description and callback)
    table.insert(history, {
        event       = "event",
        description = description,
        callback    = callback
    })
end

function module.execute(args)
    local widget = gen_timeline(args or {})
    require("gears.timer").run_delayed_calls_now()
    require("gears.timer").run_delayed_calls_now()
    require("gears.timer").run_delayed_calls_now()

    if (not args) or args.show_code_pointer ~= false then
        widget = wrap_with_arrows(widget)
    end

    local w, h = widget:fit({dpi=96}, 9999,9999)
    wibox.widget.draw_to_svg_file(widget, image_path..".svg", w, h)
end

loadfile(file_path)(module)