LeoDolmenRunner = {
    name = "LeoDolmenRunner",
    displayName = "Leo's Dolmen Runner",
    version = "1.0.1",
    chatPrefix = "|c39B027LeoDolmenRunner|r: ",
    hidden = true,
    timeout = 900,
    defaults = {
        autoTravel = false,
        direction = 1,
        started = false,
        run = {
            startedTime = 0,
            dolmensClosed = 0,
            xpPerMinute = 0,
            minutesToLevel = 0,
            hoursToLevel = 0,
            events = {}
        }
    },
    directions = {
        cw = 1,
        ccw = -1
    },
    wayshrines = {
        -- summerset 353 358 349 357 365
        [59] = { region="Alik'r Desert", cw = 60, ccw = 155},
        [60] = { region="Alik'r Desert", cw = 155, ccw = 59},
        [155] = { region="Alik'r Desert", cw = 59, ccw = 60},
        [127] = { region="Auridon", cw = 175, ccw = 176},
        [175] = { region="Auridon", cw = 176, ccw = 127},
        [176] = { region="Auridon", cw = 127, ccw = 175},
        [39] = { region="Bangkorai", cw = 206, ccw = 35},
        [206] = { region="Bangkorai", cw = 35, ccw = 39},
        [35] = { region="Bangkorai", cw = 39, ccw = 206},
        [30] = { region="Deshaan", cw = 27, ccw = 80},
        [27] = { region="Deshaan", cw = 80, ccw = 30},
        [80] = { region="Deshaan", cw = 30, ccw = 27},
        [95] = { region="Eastmarch", cw = 92, ccw = 90},
        [92] = { region="Eastmarch", cw = 90, ccw = 95},
        [90] = { region="Eastmarch", cw = 95, ccw = 92},
        [20] = { region="Glenumbra", cw = 6, ccw = 2},
        [6] = { region="Glenumbra", cw = 2, ccw = 20},
        [2] = { region="Glenumbra", cw = 20, ccw = 6},
        [207] = { region="Grahtwood", cw = 164, ccw = 21},
        [164] = { region="Grahtwood", cw = 21, ccw = 207},
        [21] = { region="Grahtwood", cw = 207, ccw = 164},
        [148] = { region="Greenshade", cw = 152, ccw = 149},
        [152] = { region="Greenshade", cw = 149, ccw = 148},
        [149] = { region="Greenshade", cw = 148, ccw = 152},
        [103] = { region="Malabal Tor", cw = 100, ccw = 105},
        [100] = { region="Malabal Tor", cw = 105, ccw = 103},
        [105] = { region="Malabal Tor", cw = 103, ccw = 100},
        [162] = { region="Reaper's March", cw = 158, ccw = 157},
        [158] = { region="Reaper's March", cw = 157, ccw = 162},
        [157] = { region="Reaper's March", cw = 162, ccw = 158},
        [10] = { region="Ravenspire", cw = 13, ccw = 86},
        [13] = { region="Ravenspire", cw = 86, ccw = 10},
        [86] = { region="Ravenspire", cw = 10, ccw = 13},
        [51] = { region="Shadowfen", cw = 53, ccw = 47},
        [53] = { region="Shadowfen", cw = 47, ccw = 51},
        [47] = { region="Shadowfen", cw = 51, ccw = 53},
        [73] = { region="Stonefalls", cw = 67, ccw = 71},
        [67] = { region="Stonefalls", cw = 71, ccw = 73},
        [71] = { region="Stonefalls", cw = 73, ccw = 67},
        [17] = { region="Stormhaven", cw = 19, ccw = 31},
        [19] = { region="Stormhaven", cw = 31, ccw = 17},
        [31] = { region="Stormhaven", cw = 17, ccw = 19},
        [110] = { region="The Rift", cw = 116, ccw = 114},
        [116] = { region="The Rift", cw = 114, ccw = 110},
        [114] = { region="The Rift", cw = 110, ccw = 116}
    }
}

local LastUpdateTimestamp = GetTimeStamp()

function LeoDolmenRunner.log(message)
    d(LeoDolmenRunner.chatPrefix .. message)
end

function LeoDolmenRunner.onFastTravelInteraction(currentWayshrine)
    if LeoDolmenRunner.wayshrines[currentWayshrine] and LeoDolmenRunner.data.autoTravel then
        local nextWayshrine = nil
        if LeoDolmenRunner.data.direction == LeoDolmenRunner.directions.cw then
            nextWayshrine = LeoDolmenRunner.wayshrines[currentWayshrine].cw
        else
            nextWayshrine = LeoDolmenRunner.wayshrines[currentWayshrine].ccw
        end
        local discovered, name = GetFastTravelNodeInfo(nextWayshrine)
        if not discovered then
            LeoDolmenRunner.log("Can't fast travel: " .. name .. " not discovered yet.")
            return
        end
        FastTravelToNode(nextWayshrine)
    end
end

local function calculateXpPerMinute(xp, timestamp)
    local diff = GetDiffBetweenTimeStamps(GetTimeStamp(), timestamp)
    diff = (diff == 0) and 1 or diff

    return xp / (diff / 60)
end

local function TimeToLevel()
    local xpToLevel = 0
    if (CanUnitGainChampionPoints("player")) then
        xpToLevel = GetNumChampionXPInChampionPoint(GetPlayerChampionPointsEarned("player")) - GetPlayerChampionXP()
    else
        xpToLevel = GetNumExperiencePointsInLevel(GetUnitLevel("player")) - GetUnitXP("player")
    end

    local minutesToLevel = math.ceil(xpToLevel / LeoDolmenRunner.data.run.xpPerMinute)

    if minutesToLevel < 60 then return 0, minutesToLevel end

    local hoursToLevel = math.floor(minutesToLevel / 60)
    minutesToLevel = minutesToLevel - (hoursToLevel * 60)

    return hoursToLevel, minutesToLevel
end

function LeoDolmenRunner:UpdateData()
    local xpGained = 0
    local firstEventTimestamp = 0
    if (#LeoDolmenRunner.data.run.events > 0) then
        for i, event in pairs(LeoDolmenRunner.data.run.events) do
            if GetDiffBetweenTimeStamps(GetTimeStamp(), event.timestamp) > LeoDolmenRunner.timeout then
                LeoDolmenRunner.data.run.events[i] = nil
            else
                xpGained = xpGained + event.xp

                if firstEventTimestamp == 0 or event.timestamp < firstEventTimestamp then
                    firstEventTimestamp = event.timestamp
                end

            end
        end
    end

    LeoDolmenRunner.data.run.xpPerMinute = calculateXpPerMinute(xpGained, firstEventTimestamp)

    if xpGained > 0 then
        LeoDolmenRunner.data.run.hoursToLevel, LeoDolmenRunner.data.run.minutesToLevel = TimeToLevel()
    end
end

function LeoDolmenRunner:OnExperienceGain(reason, level, previousExperience, currentExperience, championPoints)
    local event = {
        timestamp = GetTimeStamp(),
        xp = currentExperience - previousExperience
    }
    table.insert(LeoDolmenRunner.data.run.events, event)

    if reason == 7 then LeoDolmenRunner.data.run.dolmensClosed = LeoDolmenRunner.data.run.dolmensClosed + 1 end

    self:UpdateData()
end

local function getColor(value, max)
    local rate = value / max
    if rate > 0.8 then return '10FF10' end
    if rate > 0.5 then return 'FFFF00' end
    return 'FF1010'
end

function LeoDolmenRunner:UpdateTrainingGear()
    local stack = 0
    local stackRepair = 0
    local maxItems = 9
    local numItems = 0
    local bag = SHARED_INVENTORY:GenerateFullSlotData(nil,BAG_WORN)
    for _, data in pairs(bag) do
        local link = GetItemLink(data.bagId, data.slotIndex)
        if GetItemLinkTraitType(link) == ITEM_TRAIT_TYPE_ARMOR_TRAINING or GetItemLinkTraitType(link) == ITEM_TRAIT_TYPE_WEAPON_TRAINING then
            local activeWeaponPair = GetActiveWeaponPairInfo()
            if ( GetItemLinkItemType(link) == ITEMTYPE_ARMOR
                or
                (activeWeaponPair == ACTIVE_WEAPON_PAIR_MAIN and (data.slotIndex == EQUIP_SLOT_MAIN_HAND or data.slotIndex == EQUIP_SLOT_OFF_HAND))
                or
                (activeWeaponPair == ACTIVE_WEAPON_PAIR_BACKUP and (data.slotIndex == EQUIP_SLOT_BACKUP_MAIN or data.slotIndex == EQUIP_SLOT_BACKUP_OFF)))
            then
                local quality = select(8, GetItemInfo(data.bagId, data.slotIndex))
                local condition = GetItemCondition(data.bagId, data.slotIndex)
                local equipType = select(6, GetItemInfo(data.bagId, data.slotIndex))
                numItems = numItems + 1
                local xp = 0
                if GetItemLinkItemType(link) == ITEMTYPE_ARMOR then
                    xp = 6 + quality
                elseif equipType == EQUIP_TYPE_TWO_HAND then
                    numItems = numItems + 1
                    xp = 4 + quality
                else
                    xp = 2 + (0.5 * quality)
                end
                stack = stack + xp
                if condition == 0 then stackRepair = stackRepair + xp end
            end
        end
    end

    local text = ""

    if stackRepair > 0 then
        text = "|c" .. getColor(stack - stackRepair, stack) .. " " .. (stack - stackRepair) .. " / " .. stack .. "%|r (Repair)"
    else
        text = "|c" .. getColor(stack, stack) .. stack .. "%|r"
    end

    LeoDolmenRunnerWindowPanelXpFromGear:SetText(text)
    LeoDolmenRunnerWindowPanelTrainingGear:SetText("|c" .. getColor(numItems, maxItems) .. numItems .. " / " .. maxItems .. "|r")
end

function LeoDolmenRunner:CreateUI()
    if LeoDolmenRunner.data.started then
        LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Stop")
    else
        LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Start")
    end
    self:UpdateTrainingGear()
end

local function FormatNumber(num)
    return zo_strformat("<<1>>", ZO_LocalizeDecimalNumber(num))
end

local function FormatTime(seconds, short, colorizeCountdown)
    if short == nil then short = false end
    local formats = {
        dhm = SI_TIME_FORMAT_DDHHMM_DESC_SHORT,
        day = SI_TIME_FORMAT_DAYS,
        hm = SI_TIME_FORMAT_HHMM_DESC_SHORT,
        hms = SI_TIME_FORMAT_HHMMSS_DESC_SHORT,
        hour = SI_TIME_FORMAT_HOURS,
        ms = SI_TIME_FORMAT_MMSS_DESC_SHORT,
        m = SI_TIME_FORMAT_MINUTES
    }
    if seconds and seconds > 0 then
        local ss = seconds % 60
        local mm = math.floor(seconds / 60)
        local hh = math.floor(mm / 60)
        mm = mm % 60
        local dn = math.floor(hh / 24)
        local hhdn = hh - (dn*24)

        local ssF = string.format("%02d", ss)
        local mmF = string.format("%02d", mm)
        local hhF = string.format("%02d", hh)
        local hhdnF = string.format("%02d", hhdn)

        local result = ''
        if dn > 0 then
            if short then
                result = ZO_CachedStrFormat(GetString(formats.day), dn) .." "..ZO_CachedStrFormat(GetString(formats.hour), hhdnF)
            else
                result = ZO_CachedStrFormat(GetString(formats.dhm), dn, hhdnF, mmF)
            end
        elseif hh > 0 then
            if short then
                result = ZO_CachedStrFormat(GetString(formats.hm), hhF, mmF)
            else
                result = ZO_CachedStrFormat(GetString(formats.hms), hhF, mmF, ssF)
            end
        elseif mm >= 0 then result = ZO_CachedStrFormat(GetString(formats.ms), mmF, ssF)
        end
        return result
    else return ZO_CachedStrFormat(GetString(formats.m), 0) end
end

function LeoDolmenRunner:UpdateUI()

    self:UpdateTrainingGear()

    local currentTimestamp = GetTimeStamp()
    if LeoDolmenRunner.data.started then
        if GetDiffBetweenTimeStamps(currentTimestamp, LastUpdateTimestamp) >= 5 then
            self:UpdateData()
            LastUpdateTimestamp = currentTimestamp
        end

        local secs = currentTimestamp - LeoDolmenRunner.data.run.startedTime
        if secs > 0 then
            LeoDolmenRunnerWindowPanelTime:SetText(FormatTime(secs))
        end
    end

    LeoDolmenRunnerWindowPanelDolmensClosed:SetText(LeoDolmenRunner.data.run.dolmensClosed)
    LeoDolmenRunnerWindowPanelXP:SetText(FormatNumber(math.ceil(LeoDolmenRunner.data.run.xpPerMinute)) .. " / min")

    LeoDolmenRunnerWindowPanelXPNext:SetText(ZO_CachedStrFormat(GetString(SI_TIME_FORMAT_HHMM_DESC_SHORT), LeoDolmenRunner.data.run.hoursToLevel, LeoDolmenRunner.data.run.minutesToLevel))

    zo_callLater(function() self:UpdateUI() end, 1000)
end

function LeoDolmenRunner:StartStop()
    if LeoDolmenRunner.data.started then
        LeoDolmenRunner.data.started = false
        LeoDolmenRunner.data.autoTravel = false
        LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Start")
    else
        LeoDolmenRunner.data.run = LeoDolmenRunner.defaults.run
        LeoDolmenRunner.data.run.startedTime = GetTimeStamp()
        LeoDolmenRunner.data.started = true
        LeoDolmenRunner.data.autoTravel = true
        LeoDolmenRunnerWindowPanelStartStopLabel:SetText("Stop")
    end
end

function LeoDolmenRunner:OnWindowMoveStop()
    LeoDolmenRunner.data.position = {
        left = LeoDolmenRunnerWindow:GetLeft(),
        top = LeoDolmenRunnerWindow:GetTop()
    }
end

function LeoDolmenRunner:RestorePosition()
    local position = LeoDolmenRunner.data.position or { left = 200; top = 200; }
    local left = position.left
    local top = position.top

    LeoDolmenRunnerWindow:ClearAnchors()
    LeoDolmenRunnerWindow:SetAnchor(TOPLEFT, GuiRoot, TOPLEFT, left, top)
    LeoDolmenRunnerWindow:SetDrawTier(DT_MEDIUM)
end

function LeoDolmenRunner:Initialize()
    local showButton, feedbackWindow = LibFeedback:initializeFeedbackWindow(LeoDolmenRunner,
        LeoDolmenRunner.name,LeoDolmenRunnerWindow, "@LeandroSilva",
            {TOPRIGHT, LeoDolmenRunnerWindow, TOPRIGHT,-50,3},
            {0,1000,10000,"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=Y9KM4PZU2UZ6A"},
            "If you found a bug, have a request or a suggestion, or simply wish to donate, send a mail.")
    LeoDolmenRunner.feedback = feedbackWindow
    LeoDolmenRunner.feedback:SetDrawLayer(DL_OVERLAY)
    LeoDolmenRunner.feedback:SetDrawTier(DT_MEDIUM)

    LeoDolmenRunnerWindowTitle:SetText(LeoDolmenRunner.displayName .. " v" .. LeoDolmenRunner.version)

    SLASH_COMMANDS["/ldr"] = function(cmd)
        LeoDolmenRunnerWindow:ToggleHidden()
    end

    if GetDisplayName() == "@LeandroSilva" then
        SLASH_COMMANDS["/rr"] = function(cmd)
            ReloadUI()
        end
    end

    self:RestorePosition()

    self:CreateUI()
    self:UpdateUI()
end

local function OnAddOnLoaded(event, addonName)
    if addonName ~= LeoDolmenRunner.name then return end

    EVENT_MANAGER:UnregisterForEvent(LeoDolmenRunner.name, EVENT_ADD_ON_LOADED)
    LeoDolmenRunner.data = LibSavedVars:NewCharacterSettings( LeoDolmenRunner.name .. "_Data", "Characters", LeoDolmenRunner.defaults )

    LeoDolmenRunner:Initialize()

    CALLBACK_MANAGER:RegisterCallback("LAM-PanelControlsCreated", OnSettingsControlsCreated)
    EVENT_MANAGER:RegisterForEvent(LeoDolmenRunner.name, EVENT_START_FAST_TRAVEL_INTERACTION, function(eventId, ...) LeoDolmenRunner.onFastTravelInteraction(...) end)
    EVENT_MANAGER:RegisterForEvent(LeoDolmenRunner.name, EVENT_EXPERIENCE_GAIN, function(eventId, ...)
        LeoDolmenRunner:OnExperienceGain(...)
    end)

    LeoDolmenRunner.log("started.")
end

EVENT_MANAGER:RegisterForEvent(LeoDolmenRunner.name, EVENT_ADD_ON_LOADED, OnAddOnLoaded)