--[[
Filename: GuildCharNames.lue
Author: Sasky
Version: 1.1.0

Released under GPLv2. See libraries for their individual licenses
]]--
local LC = LibStub('libChat-1.0')

GuildCharInfo = {}

--[[ GuildCharInfo:init()
	@post change events registered
	@post lookup table initialized
]]--
GuildCharInfo.init = function()
	local emptyDefault = {}
    GuildCharInfo.lut = ZO_SavedVars:NewAccountWide('GuildCharacterInfo', 0.9, "cache", emptyDefault)
    GuildCharInfo.createLUT()
    GuildCharInfo.player = GetDisplayName()

    GuildCharInfo.initUI()

	-- register for guild events
	EVENT_MANAGER:RegisterForEvent("GuildMemberLeveled", EVENT_GUILD_MEMBER_CHARACTER_LEVEL_CHANGED, GuildCharInfo.guildUpdateEvent)
	EVENT_MANAGER:RegisterForEvent("GuildMemberLoginout", EVENT_GUILD_MEMBER_PLAYER_STATUS_CHANGED, GuildCharInfo.guildUpdateEvent)
	EVENT_MANAGER:RegisterForEvent("GuildMemberAdded", EVENT_GUILD_MEMBER_ADDED, GuildCharInfo.guildUpdateEvent)
	EVENT_MANAGER:UnregisterForEvent("GuildCharacterInfo", EVENT_PLAYER_ACTIVATED, GuildCharInfo.init)
    LINK_HANDLER:RegisterCallback(LINK_HANDLER.LINK_CLICKED_EVENT, GuildCharInfo.linkHandler)

    LC:registerName(GuildCharInfo.getFormattedNameLink)

    --UI init: create close button
    --From AI research grid
    GuildCharInfo.uiClose = WINDOW_MANAGER:CreateControl("GCINameBoxCloseButton", GCINameBox, CT_BUTTON)
    GuildCharInfo.uiClose:SetDimensions(15, 15)
    GuildCharInfo.uiClose:SetAnchor(TOPRIGHT, GCINameBox, TOPRIGHT, 0 , 0)
    GuildCharInfo.uiClose:SetState(BSTATE_NORMAL)
    GuildCharInfo.uiClose:SetMouseOverBlendMode(0)
    GuildCharInfo.uiClose:SetEnabled(true)
    GuildCharInfo.uiClose:SetNormalTexture("/esoui/art/buttons/clearslot_down.dds")
    GuildCharInfo.uiClose:SetMouseOverTexture("/esoui/art/buttons/clearslot_up.dds")
    GuildCharInfo.uiClose:SetHandler("OnClicked", function(_) GCINameBox:SetHidden(true) end)
end

--[[ GuildCharInfo:createLUT()
	@post lookup table initialized
]]--
GuildCharInfo.createLUT = function()
	local g, guild, acctName, hasChar, charName, lvl, vr, _
	for g=1,GetNumGuilds() do
		guild = GetGuildId(g)
		for i=1,GetNumGuildMembers(guild) do
			acctName = GetGuildMemberInfo(guild,i)
			hasChar, charName, _, _, _, lvl, vr = GetGuildMemberCharacterInfo(guild,i)
			if hasChar then
				GuildCharInfo.putToLUT(acctName, charName, lvl, vr, guild, i)
			end
		end
	end
end

--[[GuildCharInfo:PutToLUT(acctName, charName, charLevel, charVR)
	@param acctName - name of player account (@foo)
	@param charName - name of current character. String stripping optional
	@param charLevel - level of current character
	@param charVR - veteran rank of current character

	@post Character added to LUT. If account already exists and charLevel > level in LUT,
		will replace main character
]]--
GuildCharInfo.putToLUT = function(acctName, charName, charLevel, charVR, guild, index)
	if charLevel == nil then charLevel = 0 end
	if charVR == nil then charVR = 0 end

	charName = charName:gsub("%^.+", "")

	--Init if first time with this character
	if GuildCharInfo.lut[acctName] == nil then
		GuildCharInfo.lut[acctName] = {}
		GuildCharInfo.lut[acctName].level = 0 -- forces main update
	end
	--Always update charName and index
	GuildCharInfo.lut[acctName].name = charName
    GuildCharInfo.lut[acctName].guild = guild
    GuildCharInfo.lut[acctName].index = index
	--Update main if on higher level char
	if GuildCharInfo.lut[acctName].level < (charLevel + charVR) then
		GuildCharInfo.lut[acctName].main = charName
		GuildCharInfo.lut[acctName].level = charLevel + charVR
	end

	--d(acctName ..",".. charName ..",".. charLevel ..",".. charVR)
end

--[[GuildCharInfo:guildUpdateEvent(eventCode, guildId, acctName)
	@param eventCode - not used (but needed for callback signature)
	@param guildId - guild for the event
	@param acctName - account name that was updated
	Loops through the guild (guildId) until finds acctName. Updates that entry and exits
	@post LUT entry for <acctName> updated
]]--
GuildCharInfo.guildUpdateEvent = function(_, guildId, acctName)
	--Do a full rescan if relevant to current player
	if GuildCharInfo.player == acctName then
		GuildCharInfo.createLUT()
		return
	end

	local aName, _
	--d(eventCode .. "," .. guildId .. "," .. acctName)
	for i=1,GetNumGuildMembers(guildId) do
		aName = GetGuildMemberInfo(guildId,i)
		if aName == acctName then
			local hasChar, charName, _, _, _, lvl, vr = GetGuildMemberCharacterInfo(guildId,i)
			if hasChar then
				--d("Found update: " .. acctName .. ", " .. charName .. ", " .. lvl .. "+" .. vr)
				GuildCharInfo.putToLUT(acctName, charName, lvl, vr, guildId, i)
			end
			return
		end
	end
	d("No update found for " .. acctName)
end
--[[ GuildCharInfo:getCharName(acctName)
	@return current character name for acctName
	If no entry, will passthrough acctName unaltered
]]--
GuildCharInfo.getCharName = function(acctName)
	if GuildCharInfo.lut[acctName] ~= nil and GuildCharInfo.lut[acctName].name ~= nil then
		return GuildCharInfo.lut[acctName].name
	end
	return acctName
end

--[[ GuildCharInfo:getCharName(acctName)
	@return main character name for acctName
	If no entry, will passthrough acctName unaltered
]]--
GuildCharInfo.getMainCharName = function(acctName)
	if GuildCharInfo.lut[acctName] ~= nil and GuildCharInfo.lut[acctName].main ~= nil then
		return GuildCharInfo.lut[acctName].main
	end
	return acctName
end

--[[
	@param acctName - account name or text
	@param info - info object from receiveMessage
	@param includeMain - (boolean) whether to show main character name.
		Will not show if current matches main. Default: true
	@param includeAcct - (boolean) whether to show the account name.
		Will not show if name is not in lookup table. Default: false
	@return name link of format [CurrentCharacter] (Main character) @account
]]--

GuildCharInfo.generateLink = function(acctName)
    local color, text = GuildCharInfo.cfg.linkColor,  GuildCharInfo.cfg.linkText
    if color == nil or #color ~= 6 then color = "EEEEEE" end
    if text == nil then text = "[*]" end

    text = GuildCharInfo.unsanitizeSavedText(text)

	local data = acctName
	data = data .. "," .. GuildCharInfo.lut[acctName].guild
    data = data .. "," .. GuildCharInfo.lut[acctName].index
    data = data .. ",[]" --Fix for pNames link handling on self
    local link = "|H" .. color .. ":playerDetail:" .. data .. "|h" .. text .. "|h"
    return link
end

--[[
    Function for libChat hook
    @param channel - chat channel code
    @param acctName - sender of chat message
    @return formatted name link [Character] (Main) [*]
]]--
GuildCharInfo.getFormattedNameLink = function(channel, acctName, _)
    local ChanInfoArray = ZO_ChatSystem_GetChannelInfo()
    local info = ChanInfoArray[channel]

    local includeMain, includeAcct = GuildCharInfo.cfg.showMain, GuildCharInfo.cfg.showAcct

	--Short-circuit if not in LUT
	if GuildCharInfo.lut[acctName] == nil then
		if info.playerLinkable then
			return ZO_LinkHandler_CreatePlayerLink(acctName:gsub("%^.*x$",""))
		else
			return acctName
		end
	end

	--Parameter defaults
	if includeMain == nil then includeMain = true end
	if includeAcct == nil then includeAcct = false end

	local name, main = GuildCharInfo.lut[acctName].name, GuildCharInfo.lut[acctName].main

	if main == name then includeMain = false end

	if info.playerLinkable and acctName ~= GuildCharInfo.player then
		name = ZO_LinkHandler_CreatePlayerLink(name)
	end

	if includeMain then
		name = name .. " (" .. main .. ")"
	end

    --TODO: Make the account name into a link
	if includeAcct then
		name = name .. acctName
	end

	--Add info tag link
	name = name .. " " .. GuildCharInfo.generateLink(acctName)
	return name
end

--Utility function to kill NIL values
local function nn(val)
	if val == nil then return "" end
	return val
end

GuildCharInfo.classLookup = {}
GuildCharInfo.classLookup[1] = "dragonknight"
GuildCharInfo.classLookup[2] = "sorcerer"
GuildCharInfo.classLookup[3] = "nightblade"
GuildCharInfo.classLookup[6] = "templar"

--[[
    Handler function for more info on links (the [*] next to char names)
    @param linkType - string for what type of link was clicked.
        we handle playerDetail only
    @param varargs - string concatenation of args.
        Format: accountName,guildID,guildIndex
    @post shows UI box with player info
]]--
GuildCharInfo.linkHandler = function(_, _, _, _, linkType, varargs)
	if linkType ~= "playerDetail" then return false end

    local acct, guild, index = SplitString(",", varargs)
    local _, name, zone, class, _, level, vr = GetGuildMemberCharacterInfo(guild, index)
    --local main = GuildCharInfo.lut[acct].main

	level = tonumber(level)
	if level >= 50 then
		level = "VR " .. vr
	end
    GCINameBoxName:SetText(nn(name):gsub("%^.*x$",""))
    GCINameBoxLevel:SetText(nn(level))
    GCINameBoxClassIcon:SetTexture( "/esoui/art/contacts/social_classicon_" .. nn(GuildCharInfo.classLookup[class]) .. ".dds" )
	GCINameBoxZone:SetText(nn(zone))
    GCINameBoxAccount:SetText(nn(acct))
    --GCINameBoxMain:SetText(nn("(" .. main .. ")"))

    --Resize box
    local line1 = GCINameBoxName:GetStringWidth(name) + 25 + 20
    --local line2 = GCINameBoxLevel:GetWidth() + GCINameBoxAccount:GetWidth() + 10
    --local line3 = GCINameBoxZone:GetWidth()
    local lineWidth = line1
    GCINameBox:SetWidth(lineWidth)

    GCINameBox:SetHidden(false)
	return true
end

local function n0(val) if val == nil then return 0 else return val end end

--[[
    Change a 6-digit hex string into RGB values
    @param hex - 6-digit hex string in range 000000 to FFFFFF
        Note: if not length 6, wil return NIL values
    @return 3 values: red, green, blue
        All format decimal of range [0,1]
]]--
GuildCharInfo.hex2rgb = function(hex)
    --Return nil on invalid input
    if #hex ~= 6 then return nil, nil, nil end
    --Convert each two digits to a number
    local r= GuildCharInfo.hex2dec(string.sub(hex, 1, 2))
    local g= GuildCharInfo.hex2dec(string.sub(hex, 3, 4))
    local b= GuildCharInfo.hex2dec(string.sub(hex, 5, 6))
    return n0(r)/255,n0(g)/255,n0(b)/255
end

--[[
    Change (r, g, b) into a 6-digit hex format string
    @param r - red channel intensity. Integer in [0, 255]
    @param g - green channel intensity. Integer in [0, 255]
    @param b - blue channel intensity. Integer in [0, 255]
    @return 6-digit hex string in range 000000 to FFFFFF
 ]]
GuildCharInfo.rgb2hex = function(r, g, b)
    local str = GuildCharInfo.num2hex2(r)
    str = str .. GuildCharInfo.num2hex2(g)
    str = str .. GuildCharInfo.num2hex2(b)
    return str
end

GuildCharInfo.num2hex2 = function(d)
    if d > 1 then d = 1 end
    if d < 0 then d = 0 end
    local str = string.format("%X", d*255)
    if #str == 1 then str = "0" .. str end
    return str
end

--Quick hex2dec taking advantage of the fact tonumber recognizes hex input
GuildCharInfo.hex2dec = function(dd)
    dd = "0x" .. dd
    return tonumber(dd)
end

GuildCharInfo.sanitizeSavedText = function(txt)
    txt = txt:gsub("&","&a;")
    txt = txt:gsub("%[","&o;")
    txt = txt:gsub("]","&c;")
    return txt
end

GuildCharInfo.unsanitizeSavedText = function(txt)
    txt = txt:gsub("&o;", "[")
    txt = txt:gsub("&c;", "]")
    txt = txt:gsub("&a;", "&")
    return txt
end

--[[
    Init function to create UI configuration menu and managed config variables
    @post - UI menu created
    @post - preferences created and saved
]]--
GuildCharInfo.initUI = function()
    --Defaults
    local cfgDefault = {}
    cfgDefault.showMain = true
    cfgDefault.showAcct = false
    cfgDefault.showLink = true
    cfgDefault.linkText = "&o;*&c;]"
    cfgDefault.linkColor = "EEEEEE"
    GuildCharInfo.cfg = ZO_SavedVars:NewAccountWide('GuildCharacterInfo', 0.9, "config", cfgDefault)
    --
    --Create menu panel
    --
    local LAM = LibStub("LibAddonMenu-1.0")
    local pre = "GuildCharInfoUI"
    GuildCharInfo.panelID = LAM:CreateControlPanel("GuildCharNamesUI", "Guild Character Info")
    --Name formatting options
    LAM:AddHeader(GuildCharInfo.panelID, pre.."h1", "Name Format")
    LAM:AddCheckbox(GuildCharInfo.panelID, pre.."h1c1", "Account Name",
        "Show the account name (@name) in guild chat after the character name",
        function() return GuildCharInfo.cfg.showAcct end,
        function(val) GuildCharInfo.cfg.showAcct = val end,
        false --No warning
    )
    LAM:AddCheckbox(GuildCharInfo.panelID, pre.."h1c2", "Main Character Name",
        "Show the name of the highest character in guild chat after current character name",
        function() return GuildCharInfo.cfg.showMain end,
        function(val) GuildCharInfo.cfg.showMain = val end,
        false --No warning
    )
    --Link options
    LAM:AddHeader(GuildCharInfo.panelID, pre.."h2", "Info Link Format")
    LAM:AddCheckbox(GuildCharInfo.panelID, pre.."h2c1", "Show Link",
        "Show the info link after the name",
        function() return GuildCharInfo.cfg.showLink end,
        function(val) GuildCharInfo.cfg.showLink = val end,
        true, "If disabled, there is no way to view the player info card"
    )
    LAM:AddColorPicker(GuildCharInfo.panelID, pre.."h2c2", "Link Color",
        "Change the color of the text link (default [*]) that shows the info card",
        function() return GuildCharInfo.hex2rgb(GuildCharInfo.cfg.linkColor) end,
        function(r,g,b) GuildCharInfo.cfg.linkColor = GuildCharInfo.rgb2hex(r,g,b) end,
        false -- No warning
    )
    LAM:AddEditBox(GuildCharInfo.panelID, pre.."h2c3", "Link text",
        "Text shown after character name which on click shows the info box",
        false, -- Not multiline
        function() return GuildCharInfo.unsanitizeSavedText(GuildCharInfo.cfg.linkText) end,
        function(val) GuildCharInfo.cfg.linkText = GuildCharInfo.sanitizeSavedText(val) end,
        true, "If using pChat, do NOT include a '^' in the link text"
    )
end


EVENT_MANAGER:RegisterForEvent("GuildCharacterInfo", EVENT_PLAYER_ACTIVATED, GuildCharInfo.init)