------------------------------------------------------
-- Custom HUD Rendering by Agent X and xLuigiGamerx --
------------------------------------------------------

if incompatibleClient then return 0 end

-- localize functions to improve performance - n-hud.lua
local og_hud_get_value,og_hud_set_value,djui_hud_print_text,tostring,hud_set_flash,get_global_timer,hud_get_flash,djui_hud_get_screen_width,math_ceil,obj_get_first_with_behavior_id,get_behavior_from_id,count_objects_with_behavior,djui_hud_render_rect,djui_hud_set_resolution,djui_hud_get_screen_height,djui_hud_set_color,djui_hud_set_font,djui_hud_measure_text,djui_chat_message_create,hud_is_hidden,djui_is_playerlist_open = hud_get_value,hud_set_value,djui_hud_print_text,tostring,hud_set_flash,get_global_timer,hud_get_flash,djui_hud_get_screen_width,math.ceil,obj_get_first_with_behavior_id,get_behavior_from_id,count_objects_with_behavior,djui_hud_render_rect,djui_hud_set_resolution,djui_hud_get_screen_height,djui_hud_set_color,djui_hud_set_font,djui_hud_measure_text,djui_chat_message_create,hud_is_hidden,djui_is_playerlist_open

--[[
    Some functions we need for the hud
    Color hud code written by EmilyEmmi
    Djui box code written by xLuigiGamerx (Use outside of character select is forbidden as these functions were made for another mod I'm planning to release)
]]

---@param text string
---@return nil, number, number, number, number
local function convert_color(text)
    if text:sub(2, 2) ~= "#" then
        return nil
    end
    text = text:sub(3, -2)
    local rstring, gstring, bstring = "", "", ""
    if text:len() ~= 3 and text:len() ~= 6 then return 255, 255, 255, 255 end
    if text:len() == 6 then
        rstring = text:sub(1, 2) or "ff"
        gstring = text:sub(3, 4) or "ff"
        bstring = text:sub(5, 6) or "ff"
    else
        rstring = text:sub(1, 1) .. text:sub(1, 1)
        gstring = text:sub(2, 2) .. text:sub(2, 2)
        bstring = text:sub(3, 3) .. text:sub(3, 3)
    end
    local r = tonumber("0x" .. rstring) or 255
    local g = tonumber("0x" .. gstring) or 255
    local b = tonumber("0x" .. bstring) or 255
    return r, g, b, 255
end

---@param text string
---@param get_color boolean|nil
---@return string, string, string, boolean
local function remove_color(text, get_color)
    local start = text:find("\\")
    local next = 1
    while (next ~= nil) and (start ~= nil) do
        start = text:find("\\")
        if start ~= nil then
            next = text:find("\\", start + 1)
            if next == nil then
                next = text:len() + 1
            end

            if get_color then
                local color = text:sub(start, next)
                local render = text:sub(1, start - 1)
                text = text:sub(next + 1)
                return text, color, render
            else
                text = text:sub(1, start - 1) .. text:sub(next + 1)
            end
        end
    end
    return text
end

---@param text string
---@return string
local function generate_rainbow_text(text)
    local preResult = {}
    local postResult = {}
    for match in text:gmatch(string.format(".", "(.)")) do
        table.insert(preResult, match)
    end

    RED = djui_menu_get_rainbow_string_color(0)
    GREEN = djui_menu_get_rainbow_string_color(1)
    BLUE = djui_menu_get_rainbow_string_color(2)
    YELLOW = djui_menu_get_rainbow_string_color(3)

    local sRainbowColors = {
        [0] = YELLOW,
        [1] = RED,
        [2] = GREEN,
        [3] = BLUE,
    }

    for i = 1, #preResult do
        rainbow = sRainbowColors[i % 4]
        table.insert(postResult, rainbow .. preResult[i])
    end

    local result = table.concat(postResult, "")
    return result
end

---@param x integer X Offset
---@param y integer Y Offset
---@param width integer Width
---@param height integer Height
---@param rectColor DjuiColor Rect Color
---@param borderColor DjuiColor Border Color
local function djui_hud_render_djui(x, y, width, height, rectColor, borderColor)

    djui_hud_set_color(borderColor.r, borderColor.g, borderColor.b, borderColor.a)

    -- Left
    djui_hud_render_rect(x, y, 8, height)
    -- Right
    djui_hud_render_rect(x + width - 8, y, 8, height)
    -- Up
    djui_hud_render_rect(x + 8, y, width - 16, 8)
    -- Down
    djui_hud_render_rect(x + 8, y + height - 8, width - 16, 8)


    -- Inner Rect

    djui_hud_set_color(rectColor.r, rectColor.g, rectColor.b, rectColor.a)
    djui_hud_render_rect(x + 8, y + 8, width - 16, height - 16)

end

---@param text string Text
---@param x integer X Offset
---@param y integer Y Offset
---@param scale integer Scale
---@param red integer Default text red value
---@param green integer Default text green value
---@param blue integer Default text blue value
---@param alpha integer Default text alpha value
local function djui_hud_print_text_with_color(text, x, y, scale, red, green, blue, alpha)
    djui_hud_set_color(red or 255, green or 255, blue or 255, alpha or 255)
    local space = 0
    local color = ""
    text, color, render = remove_color(text, true)
    while render ~= nil do
        local r, g, b, a = convert_color(color)
        if alpha then a = alpha end
        djui_hud_print_text(render, x + space, y, scale)
        if r then djui_hud_set_color(r, g, b, a) end
        space = space + djui_hud_measure_text(render) * scale
        text, color, render = remove_color(text, true)
    end
    djui_hud_print_text(text, x + space, y, scale)
end

---@param text string Message
---@param headerYOffset integer A y offset for the header
---@param tr integer Text Red
---@param tg integer Text Green
---@param tb integer Text Blue
---@param a integer Text Alpha Value
---@param scale integer Scale
---@param x integer X Offset
---@param y integer Y Offset
---@param width integer Width
---@param height integer Height
---@param rectColor DjuiColor Rect Color
---@param borderColor DjuiColor Border Color
local function djui_hud_render_header_box(text, headerYOffset, tr, tg, tb, a, scale, x, y, width, height, rectColor, borderColor)
    local headerFont = djui_menu_get_theme().panels.hudFontHeader and FONT_HUD or FONT_MENU
    djui_hud_set_font(headerFont)

    local hudFont = headerFont == FONT_HUD
    local scaleMultiplier = hudFont and 4 * 0.7 or 1
    local headerFontOffset = hudFont and 31.65 or 14.5
    local defaultHeaderOffset = y + headerFontOffset + headerYOffset

    djui_hud_render_djui(x, y, width, height, rectColor, borderColor)
    djui_hud_print_text_with_color(text, x + width / 2 - (djui_hud_measure_text(remove_color(text, false)) * scale * scaleMultiplier) / 2, defaultHeaderOffset, scaleMultiplier, tr, tg, tb, a)
end

---------------------
-- Real HUD Stuffs --
---------------------

-- Updates the Chracter Select hud flags along with the vanilla hud flags

local sCharSelectHudDisplayFlags = og_hud_get_value(HUD_DISPLAY_FLAGS) | HUD_DISPLAY_FLAG_LIVES | HUD_DISPLAY_FLAG_STAR_COUNT | HUD_DISPLAY_FLAG_CAMERA -- Initializes custom hud flags

function flags_update()
    sCharSelectHudDisplayFlags = sCharSelectHudDisplayFlags & (og_hud_get_value(HUD_DISPLAY_FLAGS) | HUD_DISPLAY_FLAG_LIVES | HUD_DISPLAY_FLAG_STAR_COUNT | HUD_DISPLAY_FLAG_CAMERA) -- Updated the custom flags
    og_hud_set_value(HUD_DISPLAY_FLAGS, og_hud_get_value(HUD_DISPLAY_FLAGS) & ~(HUD_DISPLAY_FLAG_LIVES | HUD_DISPLAY_FLAG_STAR_COUNT | HUD_DISPLAY_FLAG_CAMERA)) -- Update the vanilla flags
end
hook_event(HOOK_ON_HUD_RENDER_BEHIND, flags_update)

-- Modified Vanilla Functions --
--[[
    These are `_G` on their own to replace vanilla functions
]]

---@param type HudDisplayValue
---@return integer
function _G.hud_get_value(type)
    if type == HUD_DISPLAY_FLAGS then
        return sCharSelectHudDisplayFlags | og_hud_get_value(HUD_DISPLAY_FLAGS)
    else
        return og_hud_get_value(type)
    end
end

---@param type HudDisplayValue
---@param value integer
--- Sets a HUD display value
function _G.hud_set_value(type, value)
    if type == HUD_DISPLAY_FLAGS then
        sCharSelectHudDisplayFlags = value
        og_hud_set_value(type, value & ~(HUD_DISPLAY_FLAG_LIVES | HUD_DISPLAY_FLAG_STAR_COUNT | HUD_DISPLAY_FLAG_CAMERA))
    else
        og_hud_set_value(type, value)
    end
end

-- Old CS Hud Functions --

---Hides the specified custom hud element
---@param hudElement HUDDisplayFlag
function hud_hide_element(hudElement)
    dev_mode_log_to_console("The `charSelect.hud_hide_element()` function is deprecated, please use normal vanilla functions as they have been modified to work with Character Select.", CONSOLE_MESSAGE_WARNING)
    hud_set_value(HUD_DISPLAY_FLAGS, hud_get_value(HUD_DISPLAY_FLAGS) & ~hudElement)
    return true
end

---Shows the specified custom hud element
---@param hudElement HUDDisplayFlag
function hud_show_element(hudElement)
    dev_mode_log_to_console("The `charSelect.hud_show_element()` function is deprecated, please use normal vanilla functions as they have been modified to work with Character Select.", CONSOLE_MESSAGE_WARNING)
    hud_set_value(HUD_DISPLAY_FLAGS, hud_get_value(HUD_DISPLAY_FLAGS) | hudElement)
    return true
end

---Gets the specified custom hud element's state
---@param hudElement HUDDisplayFlag
---@return boolean
function hud_get_element(hudElement)
    dev_mode_log_to_console("The `charSelect.hud_get_element()` function is deprecated, please use normal vanilla functions as they have been modified to work with Character Select.", CONSOLE_MESSAGE_WARNING)
    return (hud_get_value(HUD_DISPLAY_FLAGS) & hudElement) ~= 0
end

local MATH_DIVIDE_16 = 1/16
local MATH_DIVIDE_32 = 1/32
local MATH_DIVIDE_64 = 1/64

local FONT_USER = FONT_NORMAL

---@param localIndex integer
---@return string
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function name_from_local_index(localIndex)
    if localIndex == nil then localIndex = 0 end
    local p = gCSPlayers[localIndex]
    for i = 1, #characterTable do
        if characterTable[i].saveName == p.saveName then
            return characterTable[i][(p.currAlt and p.currAlt or 1)].name
        end
    end
    return "???"
end

---@param localIndex integer
---@return table
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function color_from_local_index(localIndex)
    if localIndex == nil then localIndex = 0 end
    local p = gCSPlayers[localIndex]
    for i = 1, #characterTable do
        if characterTable[i].saveName == p.saveName then
            return characterTable[i][(p.currAlt and p.currAlt or 1)].color
        end
    end
    return {r = 255, g = 255, b = 255}
end

---@param localIndex integer
---@return TextureInfo|string
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them.
--- This function can return nil. if this is the case, render `djui_hud_print_text("?", x, y, 1)`
function life_icon_from_local_index(localIndex)
    if localIndex == nil then localIndex = 0 end
    local p = gCSPlayers[localIndex]
    for i = 1, #characterTable do
        local char = characterTable[i]
        if char.saveName == p.saveName then
            return char[(p.currAlt and p.currAlt or 1)].lifeIcon
        end
    end
    return "?"
end

---@param localIndex integer
---@return TextureInfo
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function star_icon_from_local_index(localIndex)
    if localIndex == nil then localIndex = 0 end
    local p = gCSPlayers[localIndex]
    for i = 1, #characterTable do
        local char = characterTable[i]
        if char.saveName == p.saveName then
            return char[(p.currAlt and p.currAlt or 1)].starIcon
        end
    end
    return gTextures.star
end

local TYPE_STRING = "string"
---@param localIndex integer
---@param x integer
---@param y integer
---@param scale integer
function render_life_icon_from_local_index(localIndex, x, y, scale)
    if localIndex == nil then localIndex = 0 end
    local lifeIcon = life_icon_from_local_index(localIndex)
    local startFont = djui_hud_get_font()
    local startColor = djui_hud_get_color()

    if type(lifeIcon) == TYPE_STRING then
        local color = color_from_local_index(localIndex)
        djui_hud_set_font(FONT_RECOLOR_HUD)
        djui_hud_set_color(color.r/startColor.r*255, color.g/startColor.g*255, color.b/startColor.b*255, startColor.a)
        djui_hud_print_text(lifeIcon, x, y, scale)
        -- Reset HUD Modifications
        djui_hud_set_font(startFont)
        djui_hud_set_color(startColor.r, startColor.g, startColor.b, startColor.a)
    else
        djui_hud_render_texture(lifeIcon, x, y, scale / (lifeIcon.width * MATH_DIVIDE_16), scale / (lifeIcon.height * MATH_DIVIDE_16))
    end
end

---@param localIndex integer
---@param prevX integer
---@param prevY integer
---@param prevScale integer
---@param x integer
---@param y integer
---@param scale integer
function render_life_icon_from_local_index_interpolated(localIndex, prevX, prevY, prevScale, x, y, scale)
    if localIndex == nil then localIndex = 0 end
    local lifeIcon = life_icon_from_local_index(localIndex)
    local startFont = djui_hud_get_font()
    local startColor = djui_hud_get_color()

    if type(lifeIcon) == TYPE_STRING then
        local color = color_from_local_index(localIndex)
        djui_hud_set_font(FONT_RECOLOR_HUD)
        djui_hud_set_color(color.r/startColor.r*255, color.g/startColor.g*255, color.b/startColor.b*255, startColor.a)
        djui_hud_print_text_interpolated(lifeIcon, prevX - prevScale/4, prevY - 10*prevScale - prevScale/4, prevScale, x - scale/4, y - 10*scale - scale/4, scale)
        -- Reset HUD Modifications
        djui_hud_set_font(startFont)
        djui_hud_set_color(startColor.r, startColor.g, startColor.b, startColor.a)
    else
        djui_hud_render_texture_interpolated(lifeIcon, prevX, prevY, prevScale / (lifeIcon.width * MATH_DIVIDE_16), prevScale / (lifeIcon.height * MATH_DIVIDE_16), x, y, scale / (lifeIcon.width * MATH_DIVIDE_16), scale / (lifeIcon.height * MATH_DIVIDE_16))
    end
end

---@param localIndex integer
---@param x integer
---@param y integer
---@param scale integer
function render_star_icon_from_local_index(localIndex, x, y, scale)
    if localIndex == nil then localIndex = 0 end
    local starIcon = star_icon_from_local_index(localIndex)
    djui_hud_render_texture(starIcon, x, y, scale / (starIcon.width * MATH_DIVIDE_16), scale / (starIcon.height * MATH_DIVIDE_16))
end

---@param localIndex integer
---@param x integer
---@param y integer
---@param scale integer
function render_star_icon_from_local_index_interpolated(localIndex, prevX, prevY, prevScale, x, y, scale)
    if localIndex == nil then localIndex = 0 end
    local starIcon = star_icon_from_local_index(localIndex)
    djui_hud_render_texture_interpolated(starIcon, prevX, prevY, prevScale / (starIcon.width * MATH_DIVIDE_16), prevScale / (starIcon.height * MATH_DIVIDE_16), x, y, scale / (starIcon.width * MATH_DIVIDE_16), scale / (starIcon.height * MATH_DIVIDE_16))
end

-- Health Meter --
local TEXT_DEFAULT_METER_PREFIX = "char-select-custom-meter-"
local TEX_DEFAULT_METER_LEFT = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."left")
local TEX_DEFAULT_METER_RIGHT = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."right")
local TEX_DEFAULT_METER_PIE1 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie1")
local TEX_DEFAULT_METER_PIE2 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie2")
local TEX_DEFAULT_METER_PIE3 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie3")
local TEX_DEFAULT_METER_PIE4 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie4")
local TEX_DEFAULT_METER_PIE5 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie5")
local TEX_DEFAULT_METER_PIE6 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie6")
local TEX_DEFAULT_METER_PIE7 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie7")
local TEX_DEFAULT_METER_PIE8 = get_texture_info(TEXT_DEFAULT_METER_PREFIX.."pie8")
local defaultMeterInfo = {
    label = {
        left = TEX_DEFAULT_METER_LEFT,
        right = TEX_DEFAULT_METER_RIGHT,
    },
    pie = {
        TEX_DEFAULT_METER_PIE1,
        TEX_DEFAULT_METER_PIE2,
        TEX_DEFAULT_METER_PIE3,
        TEX_DEFAULT_METER_PIE4,
        TEX_DEFAULT_METER_PIE5,
        TEX_DEFAULT_METER_PIE6,
        TEX_DEFAULT_METER_PIE7,
        TEX_DEFAULT_METER_PIE8,
    }
}

local TEXT_DEFAULT_COURSE_PREFIX = "char-select-custom-course-"
local TEX_DEFAULT_COURSE_TOP = get_texture_info(TEXT_DEFAULT_COURSE_PREFIX.."top")
local TEX_DEFAULT_COURSE_BOTTOM = get_texture_info(TEXT_DEFAULT_COURSE_PREFIX.."bottom")
local defaultCourseInfo = {
    top = TEX_DEFAULT_COURSE_TOP,
    bottom = TEX_DEFAULT_COURSE_BOTTOM,
}

---@param localIndex integer
---@return table
--- This assumes multiple characters will not have the same model,
--- Icons can only be seen by users who have the character avalible to them
function health_meter_from_local_index(localIndex)
    localIndex = localIndex or 0
    local p = gCSPlayers[localIndex]
    for i = 1, #characterTable do
        local char = characterTable[i]
        if char.saveName == p.saveName and char[(p.currAlt and p.currAlt or 1)].healthTexture ~= nil then
            if not char[(p.currAlt and p.currAlt or 1)].healthTexture.label then
                char[(p.currAlt and p.currAlt or 1)].healthTexture.label = defaultMeterInfo.label
            elseif not char[(p.currAlt and p.currAlt or 1)].healthTexture.pie then
                char[(p.currAlt and p.currAlt or 1)].healthTexture.pie = defaultMeterInfo.pie
            end
            return char[(p.currAlt and p.currAlt or 1)].healthTexture
        end
    end
    return defaultMeterInfo
end

---@param localIndex integer
---@param health integer
---@param x integer
---@param y integer
---@param scaleX integer
---@param scaleY integer
function render_health_meter_from_local_index(localIndex, health, x, y, scaleX, scaleY)
    localIndex = localIndex or 0
    health = health >> 8
    local textureTable = health_meter_from_local_index(localIndex)
    local tex = textureTable.label.left
    djui_hud_render_texture(tex, x, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
    tex = textureTable.label.right
    djui_hud_render_texture(tex, x + 31*scaleX*MATH_DIVIDE_64, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
    if health > 0 then
        tex = textureTable.pie[health]
        djui_hud_render_texture(tex, x + 15*scaleX*MATH_DIVIDE_64, y + 16*scaleY*MATH_DIVIDE_64, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_32) * MATH_DIVIDE_64)
    end
end

---@param localIndex integer
---@param health integer
---@param x integer
---@param y integer
---@param scaleX integer
---@param scaleY integer
---@param prevX integer
---@param prevY integer
---@param prevScaleX integer
---@param prevScaleY integer
function render_health_meter_from_local_index_interpolated(localIndex, health, prevX, prevY, prevScaleX, prevScaleY, x, y, scaleX, scaleY)
    localIndex = localIndex or 0
    health = health >> 8
    local textureTable = health_meter_from_local_index(localIndex)
    local tex = textureTable.label.left
    djui_hud_render_texture_interpolated(tex, prevX, prevY, prevScaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, prevScaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64, x, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
    tex = textureTable.label.right
    djui_hud_render_texture_interpolated(tex, prevX + 31*prevScaleX*MATH_DIVIDE_64, prevY, prevScaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, prevScaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64, x + 31*scaleX*MATH_DIVIDE_64, y, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_64) * MATH_DIVIDE_64)
    if health > 0 then
        tex = textureTable.pie[health]
        djui_hud_render_texture_interpolated(tex, prevX + 15*prevScaleX*MATH_DIVIDE_64, prevY + 16*scaleY*MATH_DIVIDE_64, prevScaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, prevScaleY / (tex.height * MATH_DIVIDE_32) * MATH_DIVIDE_64, x + 15*scaleX*MATH_DIVIDE_64, y + 16*scaleY*MATH_DIVIDE_64, scaleX / (tex.width * MATH_DIVIDE_32) * MATH_DIVIDE_64, scaleY / (tex.height * MATH_DIVIDE_32) * MATH_DIVIDE_64)
    end
end

local pieTextureNames = {
    "one_segments",
    "two_segments",
    "three_segments",
    "four_segments",
    "five_segments",
    "six_segments",
    "seven_segments",
    "full",
}

local function render_hud_health()
    if currChar == 1 and characterTable[1].currAlt == 1 then
        texture_override_reset("texture_power_meter_left_side")
        texture_override_reset("texture_power_meter_right_side")
		for i = 1, 8 do
			texture_override_reset("texture_power_meter_" .. pieTextureNames[i])
		end
        return
    end
	local textureTable = characterTable[currChar][characterTable[currChar].currAlt].healthTexture
	if textureTable then -- sets health HUD to custom textures
		if textureTable.label.left and textureTable.label.right then -- if left and right label textures exist. BOTH have to exist to be set!
			texture_override_set("texture_power_meter_left_side", textureTable.label.left)
			texture_override_set("texture_power_meter_right_side", textureTable.label.right)
		end
        for i = 1, 8 do
            texture_override_set("texture_power_meter_" .. pieTextureNames[i], (textureTable.pie and textureTable.pie[i]) and textureTable.pie[i] or defaultMeterInfo.pie[i])
        end
	else -- resets the health HUD
        texture_override_set("texture_power_meter_left_side", defaultMeterInfo.label.left)
        texture_override_set("texture_power_meter_right_side", defaultMeterInfo.label.right)
		for i = 1, 8 do
			texture_override_set("texture_power_meter_" .. pieTextureNames[i], defaultMeterInfo.pie[i])
		end
	end
end

local function render_hud_act_select_course()
    if currChar == 1 then
        texture_override_reset("texture_menu_course_upper")
        texture_override_reset("texture_menu_course_lower")
        return
    end
	local textureTable = characterTable[currChar][characterTable[currChar].currAlt].courseTexture
	if textureTable then -- sets health HUD to custom textures
		if textureTable.top and textureTable.bottom then -- if left and right label textures exist. BOTH have to exist to be set!
			texture_override_set("texture_menu_course_upper", textureTable.top)
			texture_override_set("texture_menu_course_lower", textureTable.bottom)
		end
	else -- resets the course HUD
        texture_override_set("texture_menu_course_upper", defaultCourseInfo.top)
        texture_override_set("texture_menu_course_lower", defaultCourseInfo.bottom)
	end
end

local function render_hud_mario_lives()
    if (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_LIVES) == 0 then return end

    local x = 22
    local y = 15 -- SCREEN_HEIGHT - 209 - 16
    render_life_icon_from_local_index(0, x, y, 1)
    djui_hud_print_text("@", x + 16, y, 1)
    djui_hud_print_text(tostring(hud_get_value(HUD_DISPLAY_LIVES)):gsub("-", "M"), x + 32, y, 1)
end

local function render_hud_stars()
    if (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_STAR_COUNT) == 0 then return end
    if hud_get_flash ~= nil then
        -- prevent star count from flashing outside of castle
        if gNetworkPlayers[0].currCourseNum ~= COURSE_NONE then hud_set_flash(0) end

        if hud_get_flash() == 1 and (get_global_timer() & 0x08) == 0 then
            return
        end
    end

    local x = math_ceil(djui_hud_get_screen_width() - 76)
    if x % 2 ~= 0 then
        x = x - 1
    end
    local y = math_ceil(240 - 209 - 16)

    local showX = 0
    local hudDisplayStars = hud_get_value(HUD_DISPLAY_STARS)
    if hudDisplayStars < 100 then showX = 1 end

    render_star_icon_from_local_index(0, x, y, 1)
    if showX == 1 then
        djui_hud_print_text("@", x + 16, y, 1)
    end
    djui_hud_print_text(tostring(hudDisplayStars):gsub("-", "M"), (showX * 14) + x + 16, y, 1)
end

local function render_hud_camera_status()
    if (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_CAMERA) == 0 or (hud_get_value(HUD_DISPLAY_FLAGS) & HUD_DISPLAY_FLAG_CAMERA_AND_POWER) == 0 then return end

    local x = djui_hud_get_screen_width() - 54
    local y = 205
    local cameraHudStatus = hud_get_value(HUD_DISPLAY_CAMERA_STATUS) -- doesn't show the status of freecam because it's currently bugged when we hide the hud camera -- TODO: keep nagging the coopers to "fix `hud_get_value(HUD_DISPLAY_CAMERA_STATUS)` when using freecam and hiding the hud camera" :trollface:

    if cameraHudStatus == CAM_STATUS_NONE then return end

    djui_hud_render_texture(gTextures.camera, x, y, 1, 1)

    switch(cameraHudStatus & CAM_STATUS_MODE_GROUP, {
        [CAM_STATUS_MARIO] = function()
            render_life_icon_from_local_index(0, x + 16, y, 1)
        end,
        [CAM_STATUS_LAKITU] = function()
            djui_hud_render_texture(gTextures.lakitu, x + 16, y, 1, 1)
        end,
        [CAM_STATUS_FIXED] = function()
            djui_hud_render_texture(gTextures.no_camera, x + 16, y, 1, 1)
        end
    })

    switch(cameraHudStatus & CAM_STATUS_C_MODE_GROUP, {
        [CAM_STATUS_C_DOWN] = function()
            djui_hud_render_texture(gTextures.arrow_down, x + 4, y + 16, 1, 1)
        end,
        [CAM_STATUS_C_UP] = function()
            djui_hud_render_texture(gTextures.arrow_up, x + 4, y - 8, 1, 1)
        end
    })
end

-- Act Select Hud --

local STAR_SELECTOR_NOT_SELECTED = 0
local STAR_SELECTOR_SELECTED     = 1
local STAR_SELECTOR_100_COINS    = 2

local sVisibleStars = 0
local function render_act_select_hud()
    local course = gNetworkPlayers[0].currCourseNum
    if course == 0 or not obj_get_first_with_behavior_id(id_bhvActSelector) then return end

    if sVisibleStars == 0 then
        local starObj = obj_get_first_with_behavior_id(id_bhvActSelectorStarType)
        while starObj do
            if starObj.oStarSelectorType ~= STAR_SELECTOR_100_COINS then
                sVisibleStars = sVisibleStars + 1
            end
            starObj = obj_get_next_with_same_behavior_id(starObj)
        end
    end

    for a = 1, sVisibleStars do
        local x = (139 - sVisibleStars * 17 + a * 34) + (djui_hud_get_screen_width() / 2) - 160 + 0.5
        for j = 1, MAX_PLAYERS - 1 do -- 0 is not needed, you're never supposed to see yourself in act select
            local np = gNetworkPlayers[j]
            if np and np.connected and np.currCourseNum == course and np.currActNum == a then
                render_life_icon_from_local_index(j, x - 4, 17, 1)
                break
            end
        end
    end

    local selectedStar = obj_get_first_with_behavior_id(id_bhvActSelectorStarType)
    local starsList = {}
    while selectedStar do
        table.insert(starsList, selectedStar)
        selectedStar = obj_get_next_with_same_behavior_id(selectedStar)
    end

    if (sVisibleStars > 0) then
        local playersInAct = 0
        local sSelectedActIndex = 0
        for i = 1, #starsList do
            local curStar = starsList[i]
            if curStar.oStarSelectorType == STAR_SELECTOR_SELECTED then
                sSelectedActIndex = i - 1
            end
        end
        local gCurrCourseNum = gNetworkPlayers[0].currCourseNum
        for j = 1, MAX_PLAYERS - 1 do
            local np = gNetworkPlayers[j]
            if not (np or np.connected) then goto continue end
            if (np.currCourseNum ~= gCurrCourseNum) then goto continue end
            if (np.currActNum ~= sSelectedActIndex + 1) then goto continue end
            playersInAct = playersInAct + 1
            ::continue::
        end

        if (playersInAct > 0) then
            local message = ""
            if (playersInAct == 1) then
                message = message .. "     Join      "
            else
                message = message .. string.format("%d Players", playersInAct)
            end

            djui_hud_set_font(FONT_NORMAL)
            djui_hud_set_color(100, 100, 100, 255)
            local textScale = .5
            local textWidth = djui_hud_measure_text(message) * textScale

            local xPos = ((sSelectedActIndex + 1) * 34 - sVisibleStars * 17 + 139 - (textWidth / 2) + 4) + (djui_hud_get_screen_width() / 2) - 160 + 2
            local yPos = -1

            if message:find("Players") then
                message = string.format("%d Player", playersInAct)
            end
            djui_hud_print_text(message, xPos, yPos, textScale) -- Not fully accurate because the font in act select is stretched in a way unachievable with normal fonts, will revisit in the future

        end
    end
end

---@param table table
---@return table
function zero_index_to_one_index(table)
    local tableOne = {}
    for i = 0, #table do
        tableOne[i+1] = table[i]
    end
    return tableOne
end

local activeNonCSMods = {}
local nonCSModPosition = 0
local CSPacks = 0
for i = 0, #gActiveMods do
    if gActiveMods[i].name == "Character Select" then
        table.insert(activeNonCSMods, tostring(gActiveMods[i].name))
        nonCSModPosition = #activeNonCSMods
    elseif (remove_color(gActiveMods[i].name):sub(1, 4) ~= "[CS]" and gActiveMods[i].category ~= "cs") then
        table.insert(activeNonCSMods, tostring(gActiveMods[i].name))
    else
        CSPacks = CSPacks + 1
    end
end

activeNonCSMods[nonCSModPosition] = "Character Select (+"..CSPacks..")"

function render_playerlist_and_modlist()

    -- DjuiTheme Data
    local sDjuiTheme = djui_menu_get_theme()
    local hudFont = sDjuiTheme.panels.hudFontHeader
    local rectColor = sDjuiTheme.threePanels.rectColor
    local borderColor = sDjuiTheme.threePanels.borderColor

    -- PlayerList
    playerListWidth = 710
    playerListHeight = (16 * 32) + (16 - 1) * 4 + (32 + 16) + 32 + 32
    local x = djui_hud_get_screen_width()/2 - playerListWidth/2
    local y = djui_hud_get_screen_height()/2 - playerListHeight/2

    listMargins = 16
    local playerList = {}

    gNetworkPlayersOne = zero_index_to_one_index(gNetworkPlayers)
    for index, player in pairs(gNetworkPlayersOne) do
        if player.connected then
            playerList[#playerList + 1] = player
        end
    end 

    local playersString = hudFont and djui_language_get("PLAYER_LIST", "PLAYERS") or generate_rainbow_text(djui_language_get("PLAYER_LIST", "PLAYERS"))

    djui_hud_render_header_box(playersString, 0, 0xff, 0xff, 0xff, 0xff, 1, x, y, playerListWidth, playerListHeight, rectColor, borderColor)
    djui_hud_set_font(FONT_USER)
    for i = 1, #playerList do
        np = playerList[i]

        local v = (i % 2) == 0 and 16 or 32
        djui_hud_set_color(v, v, v, 128)
        local entryWidth = playerListWidth - ((8 + listMargins) * 2)
        local entryHeight = 32
        local entryX = x + 8 + listMargins
        local entryY = y + 88 + ((entryHeight + 4) * (i-1))
        djui_hud_render_rect(entryX, entryY, entryWidth, entryHeight)

        playerNameColor = {
            r = 127 + network_player_get_override_palette_color_channel(np, CAP, 0) / 2,
            g = 127 + network_player_get_override_palette_color_channel(np, CAP, 1) / 2,
            b = 127 + network_player_get_override_palette_color_channel(np, CAP, 2) / 2
        }

        djui_hud_set_color(255, 255, 255, 255)
        render_life_icon_from_local_index(np.localIndex, entryX, entryY, 2)
        djui_hud_print_text_with_color(np.name, entryX + 40, entryY, 1, playerNameColor.r, playerNameColor.g, playerNameColor.b, 255)

        local levelName = np.overrideLocation ~= "" and np.overrideLocation or get_level_name(np.currCourseNum, np.currLevelNum, np.currAreaIndex)
        if levelName then
            djui_hud_print_text_with_color(levelName, ((entryX + entryWidth) - djui_hud_measure_text((string.gsub(levelName, "\\(.-)\\", "")))) - 126, entryY, 1, 0xdc, 0xdc, 0xdc, 255)
        end

        if np.currActNum then
            currActNum = np.currActNum == 99 and "Done" or np.currActNum ~= 0 and "# "..tostring(np.currActNum) or ""
            printedcurrActNum = currActNum
            djui_hud_print_text_with_color(printedcurrActNum, entryX + entryWidth - djui_hud_measure_text(printedcurrActNum) - 18, entryY, 1, 0xdc, 0xdc, 0xdc, 255)
        end

        if np.description then
            djui_hud_print_text_with_color(np.description, (entryX + 278) - (djui_hud_measure_text((string.gsub(np.description, "\\(.-)\\", "")))/2), entryY, 1, np.descriptionR, np.descriptionG, np.descriptionB, np.descriptionA)
        end
    end

    -- ModList

    local modListWidth = 280
    local modListHeight = (#activeNonCSMods * 32) + (#activeNonCSMods - 1) * 4 + (32 + 16) + 32 + 32
    local mX = djui_hud_get_screen_width()/2 + 363
    local mY = djui_hud_get_screen_height()/2 - modListHeight/2

    local modsString = hudFont and djui_language_get("MODLIST", "MODS") or generate_rainbow_text(djui_language_get("MODLIST", "MODS"))

    djui_hud_render_header_box(modsString, 0, 0xff, 0xff, 0xff, 0xff, 1, mX, mY, modListWidth, modListHeight, rectColor, borderColor)
    djui_hud_set_font(FONT_USER)

    for i = 0, #activeNonCSMods - 1 do
        --local i = i - packFilter
        v = (i % 2) ~= 0 and 16 or 32
        djui_hud_set_color(v, v, v, 128)
        local entryWidth = modListWidth - ((8 + listMargins) * 2)
        local entryHeight = 32
        local entryX = mX + 8 + listMargins
        local entryY = mY + 124 + 0 + ((entryHeight + 4) * (i - 1))
        djui_hud_render_rect(entryX, entryY, entryWidth, entryHeight)
        local modName = activeNonCSMods[i + 1]
        local stringSubCount = 23
        local inColor = false
        for i = 1, #modName do
            if modName:sub(i, i) == "\\" then
                inColor = not inColor
            end
            if inColor then
                stringSubCount = stringSubCount + 1
            end
        end
        djui_hud_print_text_with_color(modName:sub(1, stringSubCount), entryX, entryY, 1, 0xdc, 0xdc, 0xdc, 255)
    end
end

-- Yes the ending stuffs is hardcoded, no there's not much of a better way to do it

local DIALOG_ENDING_REPLACE_1 = "$CHARNAME!"
local DIALOG_ENDING_REPLACE_2 = "Thank you $CHARNAME!"
local DIALOG_ENDING_REPLACE_3 = "...for $CHARNAME..."

local END_PEACH_CUTSCENE_DIALOG_1 = 6
local END_PEACH_CUTSCENE_DIALOG_2 = 7
local END_PEACH_CUTSCENE_DIALOG_3 = 10
local END_PEACH_CUTSCENE_RUN_TO_CASTLE = 11

local fadeLength = 5
local function render_hud_ending_dialog()
    djui_hud_set_font(FONT_TINY)
    local m = gMarioStates[0]
    if m.action ~= ACT_END_PEACH_CUTSCENE then return end

    local width = djui_hud_get_screen_width()

    local charName = characterTable[currChar][characterTable[currChar].currAlt].name
    local string = ""
    local startTime = 0
    local endTime = 0
    if m.actionArg == END_PEACH_CUTSCENE_DIALOG_1 and m.actionTimer >= 230 and m.actionTimer <= 275 then
        string = DIALOG_ENDING_REPLACE_1
        startTime = 230
        endTime = 275
    elseif m.actionArg == END_PEACH_CUTSCENE_DIALOG_2 and m.actionTimer >= 75 and m.actionTimer <= 130 then
        string = DIALOG_ENDING_REPLACE_2
        startTime = 75
        endTime = 130
    elseif m.actionArg == END_PEACH_CUTSCENE_DIALOG_3 and m.actionTimer >= 130 and m.actionTimer <= 195 then
        string = DIALOG_ENDING_REPLACE_3
        startTime = 130
        endTime = 195
    elseif m.actionArg == END_PEACH_CUTSCENE_RUN_TO_CASTLE and m.actionTimer >= 95 and m.actionTimer <= 150 then
        string = DIALOG_ENDING_REPLACE_1
        startTime = 95
        endTime = 150
    end

    if string ~= "" then
        djui_hud_set_color(0, 0, 0, 255)
        djui_hud_render_rect(0, 210, width, 30)
        string = string:gsub("$CHARNAME", charName)
        local opacity = 255
        local startToTimer = m.actionTimer - startTime
        local endToTimer = endTime - m.actionTimer
        if startToTimer >= 0 then
            opacity = math.min(startToTimer, fadeLength)/fadeLength * 255
        end
        if endToTimer >= 0 and startToTimer >= fadeLength then
            opacity = math.min(endToTimer, fadeLength)/fadeLength * 255
        end
        djui_hud_set_color(255, 255, 255, opacity)
        local x = width*0.5 - djui_hud_measure_text(string)*0.5
        djui_hud_print_text(string, x, 210, 1)
    end
end

-- Nametags Powermeter Hud --

local nametagsActionBlacklist = {
    [ACT_START_CROUCHING] = true,
    [ACT_CROUCHING]       = true,
    [ACT_STOP_CROUCHING]  = true,
    [ACT_START_CRAWLING]  = true,
    [ACT_CRAWLING]        = true,
    [ACT_STOP_CRAWLING]   = true,
    [ACT_IN_CANNON]       = true,
    [ACT_DISAPPEARED]     = true,
}

local FADE_SCALE = 4.0

--- @class StateExtras
--- @field public prevPos Vec3f
--- @field public prevScale number
--- @field public inited boolean

--- @type StateExtras[]
local sStateExtras = {}

for i = 0, MAX_PLAYERS - 1 do
    sStateExtras[i] = {}
    local _ENV = setmetatable(sStateExtras[i], { __index = _G })
    prevPos   = gVec3fZero()
    prevScale = 0.0
    inited    = false
end

local function render_nametag_powermeter()
    local sGlobalTimer = get_global_timer()
    for i = 1, MAX_PLAYERS - 1 do
        local m = gMarioStates[i]
        if is_player_active(m) == 0 then goto continue end
        local np = gNetworkPlayers[i]
        if not np.currAreaSyncValid then goto continue end

        if nametagsActionBlacklist[m.action] then goto continue end

        if (m.marioBodyState.mirrorMario or m.marioBodyState.updateHeadPosTime ~= sGlobalTimer) then goto continue end

        local pos = gVec3fZero()
        local out = gVec3fZero()

        vec3f_copy(pos, m.marioBodyState.headPos)
        pos.y = pos.y + 100

        if not djui_hud_world_pos_to_screen_pos(pos, out) then
            goto continue
        end

        local scale = -300 / out.z * djui_hud_get_fov_coeff()

        out.y = out.y - 16 * scale

        local alpha = (math.min(np.fadeOpacity << 3, 255)) * math.clamp(FADE_SCALE - scale, 0.0, 1.0)

        local e = sStateExtras[i]
        if not e.inited then
            vec3f_copy(e.prevPos, out)
            e.prevScale = scale
            e.inited = true
        end

        if gNametagsSettings.showHealth then
            djui_hud_set_color(255, 255, 255, alpha)
            local healthScale = 90 * scale
            local prevHealthScale = 90 * e.prevScale
            render_health_meter_from_local_index_interpolated(i, m.health,
                e.prevPos.x - (prevHealthScale * 0.5), e.prevPos.y - 72 * scale, prevHealthScale, prevHealthScale,
                        out.x - (    healthScale * 0.5),        out.y - 72 * scale,     healthScale,     healthScale
            )
        end

        vec3f_copy(e.prevPos, out)
        e.prevScale = scale

        ::continue::
    end
end

local function nametags_reset()
    for i = 0, MAX_PLAYERS - 1 do
        sStateExtras[i].inited = false
    end
end

local function on_level_init()
    nametags_reset()
end

hook_event(HOOK_ON_LEVEL_INIT, on_level_init)

local sServerSettings = gServerSettings
local sNametagsSettings = gNametagsSettings

_G.gServerSettings = {
    enablePlayerList            = sServerSettings.enablePlayerList,
    enablePlayersInLevelDisplay = sServerSettings.enablePlayersInLevelDisplay,
}

_G.gNametagsSettings = {
    showHealth  = sNametagsSettings.showHealth,
}

local _ServerSettingsMetaTable = {
    __index = function (t, k)
        return rawget(t, k) or sServerSettings[k]
    end,
    __newindex = function (_, k, v)
        sServerSettings[k] = v
    end,
}

local _NametagsSettingsMetaTable = {
    __index = function (t, k)
        return rawget(t, k) or sNametagsSettings[k]
    end,
    __newindex = function (_, k, v)
        sNametagsSettings[k] = v
    end,
}

setmetatable(gServerSettings,   _ServerSettingsMetaTable)
setmetatable(gNametagsSettings, _NametagsSettingsMetaTable)

function nametags_settings()
    if sNametagsSettings.showHealth then
        gNametagsSettings.showHealth = not gNametagsSettings.showHealth
    end
    sNametagsSettings.showHealth = false
end

hook_event(HOOK_ON_NAMETAGS_RENDER, nametags_settings)

local function on_hud_render_behind()
    FONT_USER = djui_menu_get_font()
    djui_hud_set_resolution(RESOLUTION_N64)
    djui_hud_set_font(FONT_HUD)

    render_nametag_powermeter() -- Render before setting the color, it sets its own

    djui_hud_set_color(255, 255, 255, 255)

    if gNetworkPlayers[0].currActNum == 99 or gMarioStates[0].action == ACT_INTRO_CUTSCENE or hud_is_hidden() then return end

    sServerSettings.enablePlayersInLevelDisplay = false -- Disables the original playersInLevel Display

    local enablePlayersInLevelDisplay = gServerSettings.enablePlayersInLevelDisplay
    if not obj_get_first_with_behavior_id(id_bhvActSelector) then
        render_hud_mario_lives()
        render_hud_stars()
        render_hud_camera_status()
        render_hud_health()
        sVisibleStars = 0
    else
        if enablePlayersInLevelDisplay then
            render_act_select_hud()
        end
        render_hud_act_select_course()
    end
end


local function on_hud_render()
    djui_hud_set_resolution(RESOLUTION_N64)
    djui_hud_set_font(FONT_HUD)
    djui_hud_set_color(255, 255, 255, 255)

    if gNetworkPlayers[0].currActNum == 99 then
        render_hud_ending_dialog()
    end

    sServerSettings.enablePlayerList = false -- Disables the original playerlist and modlist

    local enablePlayerList = gServerSettings.enablePlayerList
    djui_hud_set_resolution(RESOLUTION_DJUI)

    if djui_attempting_to_open_playerlist() and enablePlayerList then
        render_playerlist_and_modlist()
    end
end

hook_event(HOOK_ON_HUD_RENDER_BEHIND, on_hud_render_behind)
hook_event(HOOK_ON_HUD_RENDER, on_hud_render)
