if not LibStub then return end

local icon = LibStub('LibDBIcon-1.0')
local tip = LibStub('LibQTip-1.0')
local AceConfigRegistry = LibStub("AceConfigRegistry-3.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")

-- upvalues
local CalendarCloseEvent = CalendarCloseEvent
local CalendarGetDate = CalendarGetDate
local CalendarGetEventInfo = CalendarGetEventInfo
local CalendarGetNumDayEvents = CalendarGetNumDayEvents
local CalendarOpenEvent = CalendarOpenEvent
local CalendarSetMonth = CalendarSetMonth
local EasyMenu = EasyMenu
local OpenCalendar = OpenCalendar

local addonName, addonTable = ...
local L = addonTable.L

-- cache for events
local todaysEvents = {}
local upcomingEvents = {}
local tooltip = nil

obj = LibStub:GetLibrary('LibDataBroker-1.1'):NewDataObject('Broker_Calendar', {
  type = 'data source',
  icon = 'Interface\\AddOns\\Broker_Calendar\\icon',
})
local obj = obj
local options
local frame = CreateFrame('Frame')
frame:SetScript('OnEvent', function(self, event, ...) if self[event] then return self[event](self, event, ...) end end)
frame:RegisterEvent('PLAYER_ENTERING_WORLD')
frame:RegisterEvent('CALENDAR_UPDATE_EVENT_LIST')
frame:RegisterEvent('CALENDAR_UPDATE_INVITE_LIST')

local function printTwoCharNumber(number)
  if number < 10 then
    return '0'..number
  else
    return number
  end
end

local function getDate()
  local _, month, day, year = CalendarGetDate()
  if CalendarDB.format == nil then
    CalendarDB.format = "D.M.Y"
  end
  return CalendarDB.format:gsub('D', printTwoCharNumber(day)):gsub('M', printTwoCharNumber(month)):gsub('Y', year)
end

local function populateOptions()
  options = {
    order = 1,
      type  = "group",
      name  = "Broker_Calendar",
      args  = {
        general = {
          order	= 1,
          type	= "group",
          name	= "global",
          args	= {
            divider1 = {
              order	= 1,
              type	= "description",
              name	= "",
            },
            format = {
              order	= 2,
              type 	= "input",
              name 	= L["DATE_FORMAT"],
              desc 	= L["DATE_FORMAT_DESC"],
              get 	= function() return CalendarDB.format end,
              set 	= function(info, value)
                CalendarDB.format = value
                obj.text = getDate()
              end,
            },
            divider2 = {
              order	= 3,
              type	= "description",
              name	= "",
            },
            minimap = {
              order = 4,
              type  = "toggle",
              name  = L["ATT_MINIMAP"],
              desc  = "",
              get 	= function() return not CalendarDB.minimap.hide end,
              set 	= function()
                local hide = not CalendarDB.minimap.hide
                CalendarDB.minimap.hide = hide
                if hide then
                  icon:Hide('Broker_Calendar')
                else
                  icon:Show('Broker_Calendar')
                end
              end,
            }
          }
        }
      }
  }
end

local function getStatus(status)
  if status == CALENDAR_INVITESTATUS_INVITED then
    return CALENDAR_STATUS_INVITED
  elseif status == CALENDAR_INVITESTATUS_ACCEPTED then
    return CALENDAR_STATUS_ACCEPTED
  elseif status == CALENDAR_INVITESTATUS_DECLINED then
    return CALENDAR_STATUS_DECLINED
  elseif status == CALENDAR_INVITESTATUS_CONFIRMED then
    return CALENDAR_STATUS_CONFIRMED
  elseif status == CALENDAR_INVITESTATUS_OUT then
    return CALENDAR_STATUS_OUT
  elseif status == CALENDAR_INVITESTATUS_STANDBY then
    return CALENDAR_STATUS_STANDBY
  elseif status == CALENDAR_INVITESTATUS_SIGNEDUP then
    return CALENDAR_STATUS_SIGNEDUP
  elseif status == CALENDAR_INVITESTATUS_NOT_SIGNEDUP then
    return CALENDAR_STATUS_NOT_SIGNEDUP
  elseif status == CALENDAR_INVITESTATUS_TENTATIVE then
    return CALENDAR_STATUS_TENTATIVE
  else
    return ''
  end
end

local function populateEvents(day, month)
  -- today
  local numEvents = CalendarGetNumDayEvents(0, day)
  if numEvents > 0 then
    local title, hour, minute
    for eventIndex = 1, numEvents do
      title, hour, minute, calendarType, _, _, _, _, inviteStatus, invitedBy = CalendarGetDayEvent(0, day, eventIndex)

      if calendarType ~= 'RAID_LOCKOUT' and calendarType ~= 'RAID_RESET' then
        todaysEvents[title] = {hour, minute, invitedBy, getStatus(inviteStatus)}
      end
    end
  end

  -- upcoming
  local numEvents, title, days
  local monthOffset = 0
  local daysInMonth = select(3, CalendarGetMonth())
  for dayOffset = 1, 7 do
    if day + dayOffset > daysInMonth then
      monthOffset = monthOffset + 1
      day = 1 - dayOffset
      daysInMonth = select(3, CalendarGetMonth(monthOffset))
    end

    numEvents = CalendarGetNumDayEvents(monthOffset, day + dayOffset)
    if numEvents ~= 0 then
      for eventIndex = 1, numEvents do
        title, hour, minute, calendarType, _, _, _, _, inviteStatus, invitedBy  = CalendarGetDayEvent(monthOffset, day + dayOffset, eventIndex)

        if calendarType == 'PLAYER' or (upcomingEvents[title] == nil and todaysEvents[title] == nil and calendarType ~= 'RAID_LOCKOUT' and calendarType ~= 'RAID_RESET') then
          upcomingEvents[title] = {day + dayOffset, month + monthOffset, invitedBy, getStatus(inviteStatus)}
        end
      end
    end
  end

  CalendarCloseEvent()

  local sortedEvents = {}
  for title, time in pairs(todaysEvents) do
    table.insert(sortedEvents, {title = title, hour = time[1], minute = time[2], by = time[3], status = time[4]})
  end

  if #sortedEvents > 1 then
    -- sort by start time
    table.sort(sortedEvents, function(a, b)
      if a.hour == nil or b.hour == nil then
        return
      elseif a.hour == b.hour then
        if a.minute == b.minute then
          return a.title < b.title
        else
          return a.minute < b.minute
        end
      else
        return a.hour < b.hour
      end
    end)
  end

  todaysEvents = sortedEvents

  sortedEvents = {}
  for title, start in pairs(upcomingEvents) do
    table.insert(sortedEvents, {title = title, day = start[1], month = start[2], by = start[3], status = start[4]})
  end

  if #sortedEvents > 1 then
    -- sort by start time
    table.sort(sortedEvents, function(a, b)
      if a.month == nil or b.month == nil then
        return
      elseif a.month == b.month then
        if a.day == b.day then
          return a.title < b.title
        else
          return a.day < b.day
        end
      else
        return a.month < b.month
      end
    end)
  end

  upcomingEvents = sortedEvents
  sortedEvents = nil
end

local function reloadEvents()
  OpenCalendar()
  todaysEvents = nil
  upcomingEvents = nil
  todaysEvents = {}
  upcomingEvents = {}

  local _, month, day = CalendarGetDate()
  populateEvents(day, month)
end

local function color(text, colorCode)
  local color
  if colorCode == 'y' then
    color = '|cffffd100'
  elseif colorCode == 'o' then
    color = '|cffe59933'
  end

  return color..text..'|r'
end

local function showTodaysEvents()
  if #todaysEvents == 0 then
    tooltip:AddLine(L['NO_EVENTS_TODAY'])
  else
    tooltip:AddLine(
      color(L['TODAY']..':', 'y'),
      '',
      color(L['STARTS'], 'y')
    )

    for i = 1, #todaysEvents do
      if todaysEvents[i].hour ~= nil then
        tooltip:AddLine(
          todaysEvents[i].title..(todaysEvents[i].by == '' and '' or ' ('..L['BY']..' '..todaysEvents[i].by..')')..(todaysEvents[i].by == '' and '' or color(' - '..todaysEvents[i].status, 'o')),
          '',
          printTwoCharNumber(todaysEvents[i].hour)..':'..printTwoCharNumber(todaysEvents[i].minute)
        )
      end
    end
  end
end

local function showUpcomingEvents()
  if #upcomingEvents > 0 then
    tooltip:AddLine(' ')
    tooltip:AddLine(
      color(L['COMMING_SOON']..':', 'y'),
      '',
      color(L['STARTS'], 'y')
    )

    for i = 1, #upcomingEvents do
      if upcomingEvents[i].day ~= nil then
        tooltip:AddLine(
          upcomingEvents[i].title..(upcomingEvents[i].by == '' and '' or ' ('..L['BY']..' '..upcomingEvents[i].by..')')..(upcomingEvents[i].by == '' and '' or color(' - '..upcomingEvents[i].status, 'o')),
          '',
          printTwoCharNumber(upcomingEvents[i].day)..'.'..printTwoCharNumber(upcomingEvents[i].month)..'.'
        )
      end
    end
  end
end

local function toggleMinimap()
  local hide = not CalendarDB.minimap.hide
  CalendarDB.minimap.hide = hide
  if hide then
    icon:Hide('Broker_Calendar')
  else
    icon:Show('Broker_Calendar')
  end
end

function frame:PLAYER_ENTERING_WORLD()
  if not CalendarDB then
    CalendarDB = {}
    CalendarDB.minimap = {}
    CalendarDB.minimap.hide = false
    CalendarDB.version = 1
    CalendarDB.format = 'D.M.Y'
  end

  if icon then
    icon:Register('Broker_Calendar', obj, CalendarDB.minimap)
  end

  self:UnregisterEvent('PLAYER_ENTERING_WORLD')

  obj.text = getDate()
  OpenCalendar()
  CalendarSetMonth(0)
  local _, month, day = CalendarGetDate()
  populateEvents(day, month)

  populateOptions()
  AceConfigRegistry:RegisterOptionsTable("Broker_Calendar", options)
  AceConfigDialog:AddToBlizOptions("Broker_Calendar", nil, nil, "general")
end

function frame:CALENDAR_UPDATE_EVENT_LIST()
  reloadEvents()
end
function frame:CALENDAR_UPDATE_INVITE_LIST()
  reloadEvents()
end

function obj.OnClick(self, button)
  GameTooltip:Hide()
  GameTimeFrame_OnClick(GameTimeFrame)
end

function obj.OnLeave()
  tip:Release(tooltip)
end

function obj.OnEnter(self)
  if #todaysEvents == 0 and #upcomingEvents == 0 then
    reloadEvents()
  end

  tooltip = tip:Acquire('BrokerCalendarTooltip', 4, 'LEFT', 'RIGHT', 'RIGHT')
  local weekday, month, day, year = CalendarGetDate()

  tooltip:AddHeader(
    color('Calendar', 'y'),
    '',
    color(getDate(), 'y'),
    ''
  )
  tooltip:AddLine(' ')

  showTodaysEvents()
  showUpcomingEvents()

  tooltip:SmartAnchorTo(self)
  tooltip:Show()
end