--[[

     Licensed under GNU General Public License v2
      * (c) 2018, Luca CPZ

--]]

local helpers  = require("lain.helpers")
local markup   = require("lain.util.markup")
local awful    = require("awful")
local naughty  = require("naughty")
local floor    = math.floor
local os       = os
local pairs    = pairs
local string   = string
local tconcat  = table.concat
local type     = type
local tonumber = tonumber
local tostring = tostring

-- Calendar notification
-- lain.widget.cal

local function factory(args)
    args = args or {}
    local cal = {
        attach_to           = args.attach_to or {},
        week_start          = args.week_start or 2,
        three               = args.three or false,
        followtag           = args.followtag or false,
        week_number         = args.week_number or "none",
        week_number_format  = args.week_number_format or args.week_number == "left" and "%3d | " or "| %-3d",
        icons               = args.icons or helpers.icons_dir .. "cal/white/",
        notification_preset = args.notification_preset or {
            font = "Monospace 10", fg = "#FFFFFF", bg = "#000000"
        }
    }

    function cal.get_week_number(m, st_day, x)
        local date = os.date("*t", m)

        local week_step = (x ~= 0 and floor((x + st_day) / 7) - 1 or 0);

        local display_time = os.time {
            year = date.year, month = date.month, day = date.day + 7 * week_step
        }

        return string.format(cal.week_number_format, os.date("%V", display_time))
    end

    function cal.sum_week_days(x, y)
        return (x + y) % 7
    end

    function cal.build(month, year)
        local current_month, current_year = tonumber(os.date("%m")), tonumber(os.date("%Y"))
        local is_current_month = (not month or not year) or (month == current_month and year == current_year)
        local today = is_current_month and tonumber(os.date("%d")) -- otherwise nil and not highlighted
        local t = os.time { year = year or current_year, month = month and month+1 or current_month+1, day = 0 }
        local d = os.date("*t", t)
        local mth_days, st_day, this_month = d.day, (d.wday-d.day-cal.week_start+1)%7, os.date("%B %Y", t)
        local notifytable = { [1] = string.format("%s%s\n", string.rep(" ", floor((28 - this_month:len())/2)), markup.bold(this_month)) }
        for day_num = 0, 6 do
            notifytable[#notifytable+1] = string.format("%3s ", os.date("%a", os.time { year = 2006, month = 1, day = day_num + cal.week_start }))
        end
        notifytable[#notifytable] = string.format("%s\n%s", notifytable[#notifytable]:sub(1, -2), string.rep(" ", st_day*4))
        local strx
        for x = 1,mth_days do
            strx = x
            if x == today then
                if x < 10 then x = " " .. x end
                strx = markup.bold(markup.color(cal.notification_preset.bg, cal.notification_preset.fg, x) .. " ")
            end
            strx = string.format("%s%s", string.rep(" ", 3 - tostring(x):len()), strx)
            notifytable[#notifytable+1] = string.format("%-4s%s", strx, (x+st_day)%7==0 and x ~= mth_days and "\n" or "")
        end
        if string.len(cal.icons or "") > 0 and today then cal.icon = cal.icons .. today .. ".png" end
        cal.month, cal.year = d.month, d.year

        if cal.week_number ~= "none" then
            local m = os.time { year = year or current_year, month = month and month or current_month, day = 1 }
            local head_prepend = string.rep(" ", tostring(string.format(cal.week_number_format, 0)):len())

            if cal.week_number == "left" then
                notifytable[1] = head_prepend .. notifytable[1] -- month-year row
                notifytable[2] = head_prepend .. notifytable[2] -- weekdays row
                notifytable[8] = notifytable[8]:gsub("\n", "\n" .. cal.get_week_number(m, st_day, 0)) -- first week of the month

                for x = 10,#notifytable do
                    if cal.sum_week_days(st_day, x) == 2 then
                        notifytable[x] = cal.get_week_number(m, st_day, x) .. notifytable[x]
                    end
                end
            elseif cal.week_number == "right" then
                notifytable[8] = notifytable[8]:gsub("\n", head_prepend .. "\n") -- weekdays row
                for x = 9,#notifytable do
                    if cal.sum_week_days(st_day, x) == 1 then
                        notifytable[x] = notifytable[x]:gsub("\n", cal.get_week_number(m, st_day, x - 7) .. "\n")
                    end
                end
                -- last week of the month
                local end_days = cal.sum_week_days(st_day, mth_days)
                if end_days ~= 0 then end_days = 7 - end_days end
                notifytable[#notifytable] = notifytable[#notifytable] .. string.rep(" ", 4 * end_days) .. cal.get_week_number(m, st_day, mth_days + end_days)
            end
        end

        return notifytable
    end

    function cal.getdate(month, year, offset)
        if not month or not year then
            month = tonumber(os.date("%m"))
            year  = tonumber(os.date("%Y"))
        end

        month = month + offset

        while month > 12 do
            month = month - 12
            year = year + 1
        end

        while month < 1 do
            month = month + 12
            year = year - 1
        end

        return month, year
    end

    function cal.hide()
        if not cal.notification then return end
        naughty.destroy(cal.notification)
        cal.notification = nil
    end

    function cal.show(seconds, month, year, scr)
        local text = tconcat(cal.build(month, year))

        if cal.three then
            local current_month, current_year = cal.month, cal.year
            local prev_month, prev_year = cal.getdate(cal.month, cal.year, -1)
            local next_month, next_year = cal.getdate(cal.month, cal.year,  1)
            text = string.format("%s\n\n%s\n\n%s",
            tconcat(cal.build(prev_month, prev_year)), text,
            tconcat(cal.build(next_month, next_year)))
            cal.month, cal.year = current_month, current_year
        end

        if cal.notification then
            local title = cal.notification_preset.title or nil
            naughty.replace_text(cal.notification, title, text)
            return
        end

        cal.notification = naughty.notify {
            preset  = cal.notification_preset,
            screen  = cal.followtag and awful.screen.focused() or scr or 1,
            icon    = cal.icon,
            timeout = type(seconds) == "number" and seconds or cal.notification_preset.timeout or 5,
            text    = text
        }
    end

    function cal.hover_on() cal.show(0) end
    function cal.move(offset)
        offset = offset or 0
        cal.month, cal.year = cal.getdate(cal.month, cal.year, offset)
        cal.show(0, cal.month, cal.year)
    end
    function cal.prev() cal.move(-1) end
    function cal.next() cal.move( 1) end

    function cal.attach(widget)
        widget:connect_signal("mouse::enter", cal.hover_on)
        widget:connect_signal("mouse::leave", cal.hide)
        widget:buttons(awful.util.table.join(
                    awful.button({}, 1, cal.prev),
                    awful.button({}, 3, cal.next),
                    awful.button({}, 2, cal.hover_on),
                    awful.button({}, 5, cal.prev),
                    awful.button({}, 4, cal.next)))
    end

    for _, widget in pairs(cal.attach_to) do cal.attach(widget) end

    return cal
end

return factory