LeoAltholic = {}
LeoAltholic.name = "LeoAltholic"
LeoAltholic.displayName = "Leo's Altholic"
LeoAltholic.version = "0.9.5"

LeoAltholic.maxTraits = select(3,GetSmithingResearchLineInfo(1,1))
LeoAltholic.jewelryMaxTraits = select(3,GetSmithingResearchLineInfo(7,1))
LeoAltholic.panelList = { "Bio", "Stats", "Skills", "Champion", "Inventory", "Research" }
LeoAltholic.craftResearch = {CRAFTING_TYPE_BLACKSMITHING,CRAFTING_TYPE_CLOTHIER,CRAFTING_TYPE_WOODWORKING,CRAFTING_TYPE_JEWELRYCRAFTING}

function LeoAltholic.loadPlayerDataPart(skillType, baseElem)
    if skillType == nil then
        return
    end
    local numSkillLines = GetNumSkillLines(skillType)
    for i = 1, numSkillLines do
        local name, rank, discovered, skillLineId, advised, unlockText = GetSkillLineInfo(skillType,i)
        if name == nil then
            name = i;
        end
        if discovered then
            baseElem[name]    = {}
            local baseElemTable = baseElem[name]
            local numAbilities = GetNumSkillAbilities(skillType, i)
            baseElemTable.name = name
            baseElemTable.id = i
            baseElemTable.numAbilities = numAbilities
            baseElemTable.rank = rank
            baseElemTable.skillLineId = skillLineId
            LeoAltholic.loadPlayerTradeDetails( name, baseElem, baseElemTable, skillType, i, numAbilities )
        end
    end
end

function LeoAltholic.loadPlayerTradeDetails(parentName, parentTableElem, tradeTableElem, skillType, i, numAbilities )
    parentTableElem[parentName].list  = {}
    local selElemSubTable = parentTableElem[parentName].list
    local skillIndex = i
    for aj = 1, numAbilities do
        local name, icon, earnedRank, passive, ultimate, purchased, progressionIndex = GetSkillAbilityInfo(skillType, i, aj)
        local currentUpgradeLevel, maxUpgradeLevel = GetSkillAbilityUpgradeInfo(skillType, skillIndex, aj)
        local _, _, nextUpgradeEarnedRank = GetSkillAbilityNextUpgradeInfo(skillType, skillIndex, aj)
        local plainName = zo_strformat(SI_ABILITY_NAME, name)
        name = ZO_Skills_GenerateAbilityName(SI_ABILITY_NAME_AND_UPGRADE_LEVELS, name, currentUpgradeLevel, maxUpgradeLevel, progressionIndex)
        selElemSubTable[plainName] = {}
        local selL = selElemSubTable[plainName]
        selL.plainName = plainName
        selL.name = name
        selL.order = aj
        selL.earnedRank = earnedRank
        selL.currentUpgradeLevel = currentUpgradeLevel
        selL.maxUpgradeLevel = maxUpgradeLevel
        selL.nextUpgradeEarnedRank = nextUpgradeEarnedRank
    end
end

function LeoAltholic.InitCharsList()
    if LeoAltholic.savedVariables.CharList == nil then LeoAltholic.savedVariables.CharList = {} end

    local function getStat(stat) return GetPlayerStat(stat, STAT_BONUS_OPTION_APPLY_BONUS, STAT_SOFT_CAP_OPTION_APPLY_SOFT_CAP) end

    local numChars = GetNumCharacters()
    for k, v in pairs(LeoAltholic.savedVariables.CharList) do
        local deleted = true
        for i = 1, numChars do
            local charName = GetCharacterInfo(i)
            charName = charName:gsub("%^.+", "")
            if k == charName then
                deleted = false
                break
            end
        end
        if deleted then
            LeoAltholic.savedVariables.CharList[k] = nil
        end
    end

    LeoAltholic.CharName = GetUnitName("player")
    LeoAltholic.CharNum = 0
    local char = LeoAltholic.savedVariables.CharList[LeoAltholic.CharName] or {
        bio = {}
    }

    char.bio.name = LeoAltholic.CharName
    char.bio.gender = GetUnitGender("player")
    if char.bio.gender == 1 then
        char.bio.genderName = "Female"
    else
        char.bio.genderName = "Male"
    end
    char.bio.level = GetUnitLevel("player")
    char.bio.effectiveLevel = GetUnitEffectiveLevel("player")
    char.bio.isChampion = IsUnitChampion("player")
    char.bio.canChampion = CanUnitGainChampionPoints("player")
    char.bio.championPoints = GetPlayerChampionPointsEarned("player")
    char.bio.race = GetUnitRace("player")
    char.bio.raceId = GetUnitRaceId("player")
    char.bio.class = GetUnitClass("player")
    char.bio.classId = GetUnitClassId("player")
    char.bio.alliance = {
        id = GetUnitAlliance("player"),
        name = GetAllianceName(GetUnitAlliance("player")),
        rank = GetUnitAvARank("player"),
        points = GetCarriedCurrencyAmount(CURT_ALLIANCE_POINTS)
    }
    char.secondsPlayed = GetSecondsPlayed()
    char.bounty = GetBounty()

    local riding = {GetRidingStats()}
    local ridetime = GetTimeUntilCanBeTrained()/1000 or 0
    if ridetime > 1 then ridetime = ridetime + GetTimeStamp() end

    char.attributes = {
        unspent = GetAttributeUnspentPoints(),
        health = {
            points = GetAttributeSpentPoints(ATTRIBUTE_HEALTH),
            max = getStat(STAT_HEALTH_MAX),
            recovery = getStat(STAT_HEALTH_REGEN_COMBAT)
        },
        magicka = {
            points = GetAttributeSpentPoints(ATTRIBUTE_MAGICKA),
            max = getStat(STAT_MAGICKA_MAX),
            recovery = getStat(STAT_MAGICKA_REGEN_COMBAT)
        },
        stamina = {
            points = GetAttributeSpentPoints(ATTRIBUTE_STAMINA),
            max = getStat(STAT_STAMINA_MAX),
            recovery = getStat(STAT_STAMINA_REGEN_COMBAT)
        },
        riding = {
            capacity = riding[1],
            capacityMax = riding[2],
            stamina = riding[3],
            staminaMax = riding[4],
            speed = riding[5],
            speedMax = riding[6],
            time = ridetime
        },
        weapon = {
            damage = getStat(STAT_WEAPON_POWER),
            critical = getStat(STAT_CRITICAL_STRIKE),
            criticalChance = GetCriticalStrikeChance(getStat(STAT_CRITICAL_STRIKE))
        },
        spell = {
            damage = getStat(STAT_SPELL_POWER),
            critical = getStat(STAT_SPELL_CRITICAL),
            criticalChance = GetCriticalStrikeChance(getStat(STAT_SPELL_CRITICAL))
        }
    }
    char.attributes.total = char.attributes.unspent + char.attributes.health.points + char.attributes.magicka.points + char.attributes.stamina.points

    char.resistances = {
        armor             = getStat(STAT_ARMOR_RATING),
        spell      = getStat(STAT_SPELL_RESIST),
        crit       = getStat(STAT_CRITICAL_RESISTANCE),
        cold       = getStat(STAT_DAMAGE_RESIST_COLD),
        disease    = getStat(STAT_DAMAGE_RESIST_DISEASE),
        drown      = getStat(STAT_DAMAGE_RESIST_DROWN),
        earth      = getStat(STAT_DAMAGE_RESIST_EARTH),
        fire       = getStat(STAT_DAMAGE_RESIST_FIRE),
        generic    = getStat(STAT_DAMAGE_RESIST_GENERIC),
        magic      = getStat(STAT_DAMAGE_RESIST_MAGIC),
        oblivion   = getStat(STAT_DAMAGE_RESIST_OBLIVION),
        physical   = getStat(STAT_DAMAGE_RESIST_PHYSICAL),
        poison     = getStat(STAT_DAMAGE_RESIST_POISON),
        shock      = getStat(STAT_DAMAGE_RESIST_SHOCK),
        start      = getStat(STAT_DAMAGE_RESIST_START),
        mitigation        = getStat(STAT_MITIGATION),
        physical   = getStat(STAT_PHYSICAL_RESIST),
        spell_mitigation  = getStat(STAT_SPELL_MITIGATION),
        miss              = getStat(STAT_MISS),
        parry             = getStat(STAT_PARRY),
        block             = getStat(STAT_BLOCK),
        dodge             = getStat(STAT_DODGE)
    }

    char.skills = { }
    char.skills.unspent = GetAvailableSkillPoints()
    char.skills.skyShards = GetNumSkyShards()

    local skillType = SKILL_TYPE_ARMOR
    char.skills.armor = {}
    local baseElem = char.skills.armor
    local numSkillLines = GetNumSkillLines(skillType)
    for i = 1, numSkillLines do
        local name, rank, discovered, skillLineId, advised, unlockText = GetSkillLineInfo(skillType,i)
        if name == nil then
            name = i;
        end
        baseElem[name]  = {}
        local baseElemTable = baseElem[name]
        local numAbilities = GetNumSkillAbilities(skillType, i)
        baseElemTable.name = name
        baseElemTable.id = i
        baseElemTable.numAbilities = numAbilities
        baseElemTable.rank = rank
        baseElemTable.skillLineId = skillLineId
    end

    --
    skillType = SKILL_TYPE_WORLD
    char.skills.world = {}
    baseElem = char.skills.world
    LeoAltholic.loadPlayerDataPart(skillType,baseElem)

    --
    skillType = SKILL_TYPE_CLASS
    char.skills.class = {}
    baseElem = char.skills.class
    LeoAltholic.loadPlayerDataPart(skillType,baseElem)

    --
    skillType = SKILL_TYPE_GUILD
    char.skills.guild = {}
    baseElem = char.skills.guild
    LeoAltholic.loadPlayerDataPart(skillType,baseElem)

    --
    skillType = SKILL_TYPE_RACIAL
    char.skills.racial = {}
    baseElem = char.skills.racial
    LeoAltholic.loadPlayerDataPart(skillType,baseElem)

    --
    skillType = SKILL_TYPE_WEAPON
    char.skills.weapon = {}
    baseElem = char.skills.weapon
    LeoAltholic.loadPlayerDataPart(skillType,baseElem)

    --
    skillType = SKILL_TYPE_AVA
    char.skills.ava = {}
    baseElem = char.skills.ava
    LeoAltholic.loadPlayerDataPart(skillType,baseElem)

    skillType = SKILL_TYPE_TRADESKILL
    char.skills.craft = {}
    local baseTableElem = char.skills.craft
    numSkillLines = GetNumSkillLines(skillType)
    for i = 1, numSkillLines do
        local name, rank, discovered, skillLineId, advised, unlockText = GetSkillLineInfo(skillType,i)
        if name == nil then
            name = i;
        end
        baseTableElem[name]    = {}
        local selElemTable = baseTableElem[name]
        local numAbilities = GetNumSkillAbilities(skillType, i)
        selElemTable.name = name
        selElemTable.id = i
        selElemTable.numAbilities = numAbilities
        selElemTable.rank = rank
        selElemTable.skillLineId = skillLineId
        LeoAltholic.loadPlayerTradeDetails( name, baseTableElem, selElemTable, skillType, i, numAbilities )
    end

    local function GetBonus(craft)
        local skillType, skillId = GetCraftingSkillLineIndices(craft)
        local _, rank = GetSkillLineInfo(skillType,skillId)
        return {rank = rank, max = GetMaxSimultaneousSmithingResearch(craft) or 1}
    end

    char.research = {}
    for _,craft in pairs(LeoAltholic.craftResearch) do
        local craftName = GetCraftingSkillName(craft)
        char.research[craftName] = GetBonus(craft)
        for line = 1, GetNumSmithingResearchLines(craft) do
            local lineName = GetSmithingResearchLineInfo(craft, line)
            char.research[craftName][lineName] = {}
            for trait = 1, LeoAltholic.maxTraits do
                local traitType = GetSmithingResearchLineTraitInfo(craft, line, trait)
                local traitName = GetString('SI_ITEMTRAITTYPE',traitType)
                if not char.research[craftName][lineName][traitName] then
                    char.research[craftName][lineName][traitName] = {}
                end
            end
        end
    end

    for _,craft in pairs(LeoAltholic.craftResearch) do
        local craftName = GetCraftingSkillName(craft)
        for line = 1, GetNumSmithingResearchLines(craft) do
            local lineName = GetSmithingResearchLineInfo(craft, line)
            for trait = 1, LeoAltholic.maxTraits do
                local traitType, _, known = GetSmithingResearchLineTraitInfo(craft, line, trait)
                local traitName = GetString('SI_ITEMTRAITTYPE',traitType)
                if known == false then
                    local _,remaining = GetSmithingResearchLineTraitTimes(craft,line,trait)
                    if remaining and remaining > 0 then
                        char.research[craftName][lineName][traitName] = remaining + GetTimeStamp()
                    else
                        char.research[craftName][lineName][traitName] = false
                    end
                else
                    char.research[craftName][lineName][traitName] = true
                end
            end
        end
    end

    char.champion = {}
    for _, attribute in ipairs({ATTRIBUTE_HEALTH, ATTRIBUTE_MAGICKA, ATTRIBUTE_STAMINA}) do
        char.champion[attribute] = {}
        char.champion[attribute].spent = 0
        char.champion[attribute].unspent = GetNumUnspentChampionPoints(attribute)
        char.champion[attribute].disciplines = {}
    end

    for i = 1, GetNumChampionDisciplines() do
        local dispName = GetChampionDisciplineName(i)
        local attribute = GetChampionDisciplineAttribute(i)
        char.champion[attribute].disciplines[i] = {
            spent = GetNumPointsSpentInChampionDiscipline(i),
            skills = {}
        }
        char.champion[attribute].spent = char.champion[attribute].spent + char.champion[attribute].disciplines[i].spent
        for j = 1, GetNumChampionDisciplineSkills(i) do
--            local skillName = GetChampionSkillName(i, j)
            char.champion[attribute].disciplines[i].skills[j] = GetNumPointsSpentOnChampionSkill(i, j)
        end
    end

    char.inventory = {}
    char.inventory.size = GetBagSize(BAG_BACKPACK)
    char.inventory.used = GetNumBagUsedSlots(BAG_BACKPACK)
    char.inventory.free = GetNumBagFreeSlots(BAG_BACKPACK)

    local _, _, soulGemEmpty = GetSoulGemInfo(SOUL_GEM_TYPE_EMPTY, char.bio.level, true)
    local _, _, soulGemFilled = GetSoulGemInfo(SOUL_GEM_TYPE_FILLED, char.bio.level, true)
    char.inventory.soulGemEmpty = soulGemEmpty
    char.inventory.soulGemFilled = soulGemFilled

    char.inventory.ap = GetAlliancePoints()
    char.inventory.gold = GetCurrentMoney()
    char.inventory.telvar = GetCarriedCurrencyAmount(CURT_TELVAR_STONES)
    char.inventory.writVoucher = GetCarriedCurrencyAmount(CURT_WRIT_VOUCHERS)

    if char.stats == nil then char.stats = {} end

    LeoAltholic.savedVariables.CharList[LeoAltholic.CharName] = char
end

local function copy(obj, seen)
    if type(obj) ~= 'table' then return obj end
    if seen and seen[obj] then return seen[obj] end
    local s = seen or {}
    local res = setmetatable({}, getmetatable(obj))
    s[obj] = res
    for k, v in pairs(obj) do res[copy(k, s)] = copy(v, s) end
    return res
end

function LeoAltholic.GetCharacters()
    local playerLines =  {}
    local i = 1
    for k, v in pairs(LeoAltholic.savedVariables.CharList) do
        if k == nil then return end
        playerLines[i] = copy(v)
        i = i + 1
    end
    table.sort(playerLines, function(a, b)
        return a.bio.name < b.bio.name
    end)

    return playerLines
end

function LeoAltholic:OnUpdate()
    if LeoAltholic:isHidden() then
        return
    end

    local control
    for x,char in pairs(LeoAltholic.GetCharacters()) do

        local riding = '|t20:20:esoui/art/mounts/ridingskill_speed.dds|t ' .. char.attributes.riding.speed .. '%' ..
                ' |t20:20:esoui/art/mounts/ridingskill_stamina.dds|t ' .. char.attributes.riding.stamina ..
                ' |t20:20:esoui/art/mounts/ridingskill_capacity.dds|t ' .. char.attributes.riding.capacity ..
                ' |t22:22:esoui/art/miscellaneous/timer_32.dds|t ' .. LeoAltholic.GetTime(char.attributes.riding.time - GetTimeStamp())
        control = WINDOW_MANAGER:GetControlByName('LeoAltholicWindowBioPanel_Character'..x..'Riding')
        control:SetText(riding)

        for _,craft in pairs(LeoAltholic.craftResearch) do
            local i = 1
            local craftName = GetCraftingSkillName(craft)
            for line = 1, GetNumSmithingResearchLines(craft) do
                local lineName, lineIcon = GetSmithingResearchLineInfo(craft, line)
                for trait = 1, LeoAltholic.maxTraits do
                    local traitType = GetSmithingResearchLineTraitInfo(craft, line, trait)
                    local traitName = GetString('SI_ITEMTRAITTYPE',traitType)
                    local traitData = char.research[craftName][lineName][traitName]
                    if type(traitData) == 'number' then
                        control = WINDOW_MANAGER:GetControlByName('LeoAltholicWindowResearchPanel_Character'..x..craftName..i.."Timer")
                        control:SetText(LeoAltholic.GetTime(traitData - GetTimeStamp()))
                        i = i + 1
                    end
                end
            end
        end
    end

end

function LeoAltholic.formatNumber(amount)
    if amount == nil then return nil; end
    if type(amount) == "string" then amount = tonumber( amount ) end
    if type(amount) ~= "number" then return amount; end
    if amount < 1000 then return amount; end
    return FormatIntegerWithDigitGrouping( amount, GetString( SI_DIGIT_GROUP_SEPARATOR ) )
end

function LeoAltholic.Initialize()
    LeoAltholic.savedVariables = ZO_SavedVars:NewAccountWide("LeoAltholicSavedVariables", 1)

    local LibFeedback = LibStub:GetLibrary("LibFeedback")
    local showButton, feedbackWindow = LibFeedback:initializeFeedbackWindow(LeoAltholic,
        LeoAltholic.name,LeoAltholicWindow, "@LeandroSilva",
        {TOPRIGHT, LeoAltholicWindow, TOPRIGHT,-50,3},
        {0,500,1000,10000},
        "If you found a bug, have a request or a suggestion, or simply wish to donate, send a mail.")
    LeoAltholic.feedback = feedbackWindow

    LeoAltholic.RestorePosition()

    LeoAltholic.InitCharsList()
    LeoAltholic.InitializeCharacterFrames()
    LeoAltholic.DisplayCharacterFrames()

    SLASH_COMMANDS["/rr"] = function(cmd)
        ReloadUI()
    end

    SLASH_COMMANDS["/rc"] = function(cmd)
        LeoAltholic.savedVariables.CharList = {}
        ReloadUI()
    end

    SLASH_COMMANDS["/leoalt"] = function(cmd)
        LeoAltholic:ShowUI()
    end
end

function LeoAltholic.NewMovementInUIMode(eventCode)
    if not LeoAltholicWindow:IsHidden() then LeoAltholic:HideUI() end
end

function LeoAltholic.OnChampionPerksSceneStateChange(oldState,newState)
    if newState == SCENE_SHOWING then
        if not LeoAltholicWindow:IsHidden() then LeoAltholic:HideUI() end
    end
end

function LeoAltholic.OnPlayerDeactivated(event, addonName)
    EVENT_MANAGER:UnregisterForEvent(LeoAltholic.Name, EVENT_PLAYER_DEACTIVATED)
    LeoAltholic.InitCharsList()
end

function LeoAltholic.OnAddOnLoaded(event, addonName)
    if addonName == LeoAltholic.name then
        EVENT_MANAGER:UnregisterForEvent(LeoAltholic.Name, EVENT_ADD_ON_LOADED)
        LeoAltholic.Initialize()
--        d(LeoAltholic.name .. " started.")
    end
end

--SCENE_MANAGER:RegisterTopLevel(LeoAltholicWindow, false)
EVENT_MANAGER:RegisterForEvent(LeoAltholic.name, EVENT_ADD_ON_LOADED, LeoAltholic.OnAddOnLoaded)
EVENT_MANAGER:RegisterForEvent(LeoAltholic.name, EVENT_PLAYER_DEACTIVATED, LeoAltholic.OnPlayerDeactivated)
EVENT_MANAGER:RegisterForUpdate(LeoAltholic.name, 5000, function(...) LeoAltholic:OnUpdate(...) end)
EVENT_MANAGER:RegisterForEvent(LeoAltholic.name, EVENT_NEW_MOVEMENT_IN_UI_MODE, LeoAltholic.NewMovementInUIMode)
CHAMPION_PERKS_SCENE:RegisterCallback('StateChange', LeoAltholic.OnChampionPerksSceneStateChange)