local module = {}

local settings = require('common.settings')
local fonts = settings.fonts
local colors = settings.colors

local lap = require("sectors.lap")
local players = require('common.players')
local deltabar = require('deltabar.first')
local deltabar_lap = require('deltabar.lap')

---@param position integer
---@param pos_diff integer
---@param car_index integer
---@param leaderboard_index integer
---@param interval string
---@param tag_color rgbm|nil
---@param sector_times integer[]
---@param hotlap_laptime integer
---@param hotlap_lapcount integer
---@param hotlap_compname string
---@return table
local function make_info(position, pos_diff, interval, sector_times, last_lap_sector_times, car_index, leaderboard_index, tag_color, hotlap_laptime, hotlap_lapcount, hotlap_compname)
    local out = {
        position=position,
        pos_diff=pos_diff,
        car_index=car_index,
        leaderboard_index=leaderboard_index,
        interval=interval,
        tag_color=tag_color,
        hotlap_laptime=hotlap_laptime,
        hotlap_lapcount=hotlap_lapcount,
        hotlap_compname=hotlap_compname
    }
    
    if sector_times ~= nil then
        if #sector_times == 0 then
            out.sector_times = last_lap_sector_times
        else
            out.sector_times = sector_times
        end
    end
    
    return out
end

local max_people = 6
local to_draw = table.new(max_people, 0) -- we keep this sorted
local positions_when_session_started    = table.new(0, 10) -- if there's more people it's not a problem, it resizes automatically. We just don't expect more than 10 in general *except rare cases*
local positions_when_race_started       = table.new(0, 10) -- if there's more people it's not a problem, it resizes automatically. We just don't expect more than 10 in general *except rare cases*
local positions_when_mc_session_started = table.new(0, 10) -- if there's more people it's not a problem, it resizes automatically. We just don't expect more than 10 in general *except rare cases*
local positions_when_mc_race_started    = table.new(0, 10) -- if there's more people it's not a problem, it resizes automatically. We just don't expect more than 10 in general *except rare cases*

local interval_to_each_player = table.new(40, 0) -- the interval the player has to this player (and himself (aka '-'), for simplicity)

local animation_times_per_player = table.new(40, 0)
local player_lapcount_tracker = table.new(40, 0)
local show_overtime = false

local last_refresh_counter = -100 -- force the first update
local notification_timer = 40 -- in seconds
local show_leader_final_lap = false

local show_banner = true
local is_showing_banner = false
local animation_start_time = 0
local green_flag_banner_show_time = 5
local old_flag_state = nil
local old_flag_time = 0 -- when we started to track the old flag so we can change only when we are sure we can
local real_flag_background = colors.BG
local real_flag_text = ''

Leaderboard_ExtensionTime = 0.18 -- in seconds
local current_player_count = 2
local player_start_count = 2 -- so we can animate it
local player_target_count = 2 -- so we can animate it
local multiclass_anim_time = 0.5

local long_names_scroll_timers = table.new(50, 0) ---@type table<number, number?>

local function make_racepos_info(racepos)
    return {
        start_racepos = racepos,
        current_racepos = racepos,
        update_time = Time + notification_timer
    }
end

function module.on_session_start()
    table.clear(long_names_scroll_timers)
    table.clear(positions_when_session_started)
    table.clear(positions_when_race_started)
    table.clear(positions_when_mc_session_started)
    table.clear(positions_when_mc_race_started)
    table.clear(animation_times_per_player)
    table.clear(player_lapcount_tracker)
    table.clear(to_draw)
    table.clear(interval_to_each_player)
    show_leader_final_lap = false
end

function module.init()end

local function estimate_interval_number(speed, spline_diff, negative_sign)
    local sim_info = ac.getSim()
    local track_length = sim_info.trackLengthM
    local speedclamp = 18 -- in m/s, aka 65 kmh
    local distance_to_make = spline_diff * track_length
    if math.abs(speed) < speedclamp then speed = speedclamp * math.sign(speed) end
    if negative_sign then speed = speed * -1 end
    return 1000 * distance_to_make / speed
end

local function estimate_interval(speed, spline_diff, negative_sign)
    return lap.time_delta_to_string(estimate_interval_number(speed, spline_diff, negative_sign))
end

local function get_interval(front_pos, front_lapdata, front_spline, front_speed, front_leaderboard_index, back_pos, back_lapdata, back_spline, back_speed, back_leaderboard_index, negative_sign)
    -- it should always be > 0 but it can get < 0 if in a race start one person
    -- is behind the finish line and the other in front, I know it sucks but for
    -- the game they are in the same lap but one is at < 10% of the track and the other at > 90%
    local spline_diff = front_pos - back_pos
    while spline_diff < 0 do
        spline_diff = spline_diff + 1
    end
    
    
    -- if there's more than 1 lap of difference count the number of laps, this gets priority over other methods
    if spline_diff > 1 and Leaderboard_Relative == false then
        local sign = '+'
        if negative_sign then sign = '-' end
        return string.format("L %s%d", sign, math.floor(spline_diff))
    end
    spline_diff = spline_diff - math.floor(spline_diff)
    
    -- when one of them surpasses the other the leaderboard doesn't instantly change... sigh...
    -- at least if they are too close we can just estimate using their velocity
    local leaderboard = players.get_leaderboard()
    if Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
        leaderboard = players.get_multiclass_leaderboard()
    end
    local back_car_index  = leaderboard[back_leaderboard_index].car.index
    local front_car_index = leaderboard[front_leaderboard_index].car.index
    local back_car_worldpos  = ac.getCar(back_car_index).position
    local front_car_worldpos = ac.getCar(front_car_index).position
    local back_pos_v2  = vec2(back_car_worldpos.x, back_car_worldpos.z)
    local front_pos_v2 = vec2(front_car_worldpos.x, front_car_worldpos.z)
    local car_dist = (front_pos_v2 - back_pos_v2):length()
    if car_dist <= 20 then
        if front_spline < 0.5 and back_spline > 0.5 then
            -- front person crossed the finish line and back person didn't
            local time_to_finish_line   = estimate_interval_number(back_speed, 1 - back_spline, negative_sign)
            local time_from_finish_line = estimate_interval_number(back_speed, front_spline, negative_sign)
            return lap.time_delta_to_string(time_to_finish_line + time_from_finish_line)
        end
        return estimate_interval(back_speed, spline_diff, negative_sign)
    end
    
    -- if we don't have lap data, estimate
    if back_lapdata == nil or front_lapdata == nil then
        return estimate_interval(back_speed, spline_diff, negative_sign)
    end
    
    -- we have both lap data, maybe we can calculate precisely?
    -- first, using the lap furthest on, check how much the back person has to recover
    local back_idx = deltabar_lap.index_for_offset(front_lapdata, back_spline)
    if back_idx < 0 then -- if we have no data we can only estimate
        return estimate_interval(back_speed, spline_diff, negative_sign)
    end
    
    if back_idx == front_lapdata.next_index - 1 then
        -- either the front player teleported, meaning we have a hole in the data,
        -- or they are SO close they are basically touching.
        -- run an estimate, so we don't show '+0.0s' by mystake
        return estimate_interval(back_speed, spline_diff, negative_sign)
    end

    -- time of person in front is the latest entry (technically currentLapTime),
    -- time of person in back is how much time the front person had when he was behind
    -- the difference is how much time the person in front took to make the given distance
    -- aka how much you need to recover to surpass him, aka the time we are looking for
    local time_of_front_person = front_lapdata.elapsed_seconds[front_lapdata.next_index - 1]
    local time_of_back_person  = front_lapdata.elapsed_seconds[back_idx]

    -- if they are in the same lap, their interval is just the difference between their times
    if back_spline <= front_spline then
        if negative_sign then
            return lap.time_delta_to_string(time_of_back_person - time_of_front_person)
        else
            return lap.time_delta_to_string(time_of_front_person - time_of_back_person)
        end
    end
    
    -- since the back person has yet to cross the finish line (while the person in front already has),
    -- the total interval time between them is how much it takes the back person to cross the finish line
    -- plus the current lap time of the person who already crossed it
    -- the second one is trivial to get, but we have to estimate the first one.
    -- to estimate it we check the time the player took in his last lap.
    local back_person_last_lap = deltabar.get_player_previous_lap_data(back_car_index)
    if back_person_last_lap == nil then
        -- no data on last lap, nothing to do, we must estimate
        -- at least we can improve the estimate, we don't need the full distance between them,
        -- just up to the finish line
        local time_to_finish_line = estimate_interval_number(back_speed, 1 - back_spline, negative_sign)
        return lap.time_delta_to_string(time_to_finish_line + time_of_front_person)
    end
    
    -- back person has a lap, use that to estimate
    local back_last_lap_idx = deltabar_lap.index_for_offset(back_person_last_lap, back_spline)
    if back_last_lap_idx < 0 then
        -- still not good enough, run the estimate
        local time_to_finish_line = estimate_interval_number(back_speed, 1 - back_spline, negative_sign)
        return lap.time_delta_to_string(time_to_finish_line + time_of_front_person)
    end
    
    local time_to_reach_this_point_in_last_lap = back_person_last_lap.elapsed_seconds[back_last_lap_idx]
    local time_of_last_lap = back_person_last_lap.elapsed_seconds[back_person_last_lap.next_index-1]
    local time_to_finish_line = time_of_last_lap - time_to_reach_this_point_in_last_lap
    local interval_numb = time_to_finish_line + time_of_front_person
    if negative_sign then interval_numb = interval_numb * -1 end
    return lap.time_delta_to_string(interval_numb)
end


local function is_driver_valid(leaderboard_entry, leaderboard_index)
    -- COPYPASTE(cogno): from update_info(...)
    if leaderboard_entry == nil then return false end

    -- he's still connecting, ignore him. ALSO some servers have fake people connecting and disconnecting,
    -- we MUST ignore them, if we don't the leaderboard flickers every other frame!
    local drivername = ac.getDriverName(leaderboard_entry.car.index)
    if drivername == nil or drivername == '' then return false end -- ignore data we don't have!
    
    -- in prac/qual we don't draw people offline UNLESS they have a leaderboard time
    -- in race we still draw them (they will have OFF in their data, since they are offline, that's ok!)
    local session = players.get_current_session()
    if session.type == ac.SessionType.Practice or session.type == ac.SessionType.Qualify then
        local person_best_laptime = 0
        if Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
            person_best_laptime = players.get_car_best_laptime_multiclass(leaderboard_index)
        else
            person_best_laptime = players.get_car_best_laptime(leaderboard_index)
        end
        local is_to_draw = leaderboard_entry.car.isConnected or (person_best_laptime ~= nil and person_best_laptime > 0)
        if is_to_draw == false then return false end
    end

    -- we are in relative we need to ignore people not moving in pitlane
    if Leaderboard_Relative then
        -- to simplify I'm just going to check if he's in his pit spot
        local focused, changed = players.get_focused_car()
        if leaderboard_entry.car.index == focused then return true end -- always consider the player!
        if leaderboard_entry.car.isInPit then return false end -- not moving in pitlane, ignore him
        if leaderboard_entry.car.isConnected == false then return false end -- we don't care about people offline, ever
    end

    return true
end

-- NOTE(cogno): most of the times racepos_number = leaderboard_index + 1, except for multiclass stuff where for example you can be 5th overall but 1st of your class
local function update_info(racepos_number, leaderboard_index, draw_index)
    local leaderboard = players.get_leaderboard()
    if Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
        leaderboard = players.get_multiclass_leaderboard()
    end
    local leaderboard_info = leaderboard[leaderboard_index]
    local is_valid = is_driver_valid(leaderboard[leaderboard_index], leaderboard_index)
    if not is_valid then return false end

    -- if it's valid then it's also not nil
    -- get it's initial position so we can draw the up/down green/red arrow
    local car_info = leaderboard_info.car
    local position_info = nil
    if Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
        position_info = positions_when_mc_session_started[car_info.index]
        if car_info.isRaceFinished then position_info = positions_when_mc_race_started[car_info.index] end
    else
        position_info = positions_when_session_started[car_info.index]
        if car_info.isRaceFinished then position_info = positions_when_race_started[car_info.index] end
    end
    local started_at = position_info.start_racepos
    
    local interval = interval_to_each_player[car_info.index]
    local pos_diff = started_at - racepos_number
    local sector_times = car_info.currentSplits
    local last_sector_times, last_sector_count = players.get_last_splits(car_info.index)

    -- add multiclass color based on tag
    local multiclass_elapsed = Time - Leaderboard_Multiclass_EnabledTime
    local mc_lerp_t = settings.anim_lerp(multiclass_elapsed, Leaderboard_Multiclass_Enabled, multiclass_anim_time)
    local tag_color = nil
    if mc_lerp_t > 0 then -- so we don't take up time while we don't need it
        local tag_info = players.get_multiclass_info(car_info.index)
        if tag_info ~= nil then tag_color = tag_info.color end
    end

    local hotlap_laptime = 0
    local session = players.get_current_session()
    local hotlap_lapcount = 0
    local hotlap_compname = ""
    if session.type == ac.SessionType.Hotlap then
        hotlap_lapcount = players.get_lapcount(0)
        if draw_index == 0 then
            hotlap_laptime = car_info.lapTimeMs
            hotlap_compname = ac.getTyresName(0)
        else
            -- this gets called only when the lap is completed to "append" a new one
            hotlap_laptime = players.get_previous_laptime(0)
            hotlap_compname = ac.getTyresName(0)
            -- since the sectors data we get comes from csp, we get it by reference
            -- so when it updates for this lap we loose data on the old laps
            -- to prevent this we must manually clone the table ourselves
            -- (table.clone is not enough, because it clones the c table not the data inside it!)
            sector_times = table.new(last_sector_count, 0)
            for i=0, last_sector_count do
                sector_times[i] = last_sector_times[i]
            end
        end
    end

    to_draw[draw_index] = make_info(racepos_number, pos_diff, interval, sector_times, last_sector_times, car_info.index, leaderboard_index, tag_color, hotlap_laptime, hotlap_lapcount, hotlap_compname)
    return true
end

local function hotlap_update_info()
    -- shift all times forward by 1 (except index 0 which is the one we are making right now)
    local elements_count = table.nkeys(to_draw) - 1 -- ignore the first row (current lap)
    for i=elements_count, 1, -1 do
        to_draw[i+1] = table.clone(to_draw[i], true)
    end
    -- we delete later the extra laps, because it depends on the current settings that might change randomly

    -- add the new one in the first spot
    update_info(1, 0, 1)
end

local on_show_animation_start = 0
local is_showing = true
local is_paused = false
function module.on_open()
    if is_paused == false then
        on_show_animation_start = Time
        is_showing = true
    end
    is_paused = false
end

function module.on_close()
    is_paused = ac.getSim().isPaused
    if is_paused == false then
        is_showing = false
    end
end

local function driver_is_in_front(car_trackpos, player_trackpos)
    if player_trackpos > 0.5 then
        if car_trackpos > player_trackpos or car_trackpos < player_trackpos - 0.5 then
            return true
        end
    else
        if car_trackpos > player_trackpos and car_trackpos < player_trackpos + 0.5 then
            return true
        end
    end
    return false
end

local drivers_in_front = table.new(20, 0) -- TAG: GarbageSucks
local drivers_behind   = table.new(20, 0) -- TAG: GarbageSucks
function module.update()
    local focused, changed = players.get_focused_car()
    local leaderboard = players.get_leaderboard()
    local mc_leaderboard = players.get_multiclass_leaderboard()
    local my_leaderboard_index = -1
    if Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
        leaderboard = mc_leaderboard
        my_leaderboard_index = players.get_player_multiclass_leaderboard_index()
    else
        my_leaderboard_index = players.get_player_leaderboard_index()
    end

    -- find your entry in the leaderboard
    local session = players.get_current_session()
    local sim_info = ac.getSim()
    local race_started = true
    if session.type == ac.SessionType.Race and sim_info.raceFlagType == ac.FlagType.None then
        -- race hasn't started yet
        race_started = false
    end
    
    for i=0, #leaderboard-1 do
        local car_data = leaderboard[i].car
        local car_index = car_data.index
        local position_info = positions_when_session_started[car_index]
        local car_racepos = i + 1 --players.get_racepos(i)
        if position_info == nil then
            position_info = make_racepos_info(car_racepos)
        end
        
        if car_racepos ~= position_info.current_racepos then
            position_info.update_time = Time + notification_timer
            position_info.current_racepos = car_racepos
        end
        
        if position_info.update_time <= Time then
            position_info = make_racepos_info(car_racepos)
        end
        
        -- forcefully save it's position if the race is started, but also if we've connected AFTER the race start
        if race_started == false or positions_when_race_started[car_index] == nil then
            position_info = make_racepos_info(car_racepos)
            positions_when_race_started[car_index] = make_racepos_info(car_racepos)
        end
        
        positions_when_session_started[car_index] = position_info
    end

    -- same but for multiclass
    -- COPYPASTE(cogno): from the code above, easily compressible
    for i=0, #mc_leaderboard-1 do
        local car_data = mc_leaderboard[i].car
        local car_index = car_data.index
        local position_info = positions_when_mc_session_started[car_index]
        local car_racepos = i + 1
        if position_info == nil then
            position_info = make_racepos_info(car_racepos)
        end
        
        if car_racepos ~= position_info.current_racepos then
            position_info.update_time = Time + notification_timer
            position_info.current_racepos = car_racepos
        end
        
        if position_info.update_time <= Time then
            position_info = make_racepos_info(car_racepos)
        end
        
        -- forcefully save it's position if the race is started, but also if we've connected AFTER the race start
        if race_started == false or positions_when_mc_race_started[car_index] == nil then
            position_info = make_racepos_info(car_racepos)
            positions_when_mc_race_started[car_index] = make_racepos_info(car_racepos)
        end
        
        positions_when_mc_session_started[car_index] = position_info
    end
    
    -- animation controller
    for i=0, #leaderboard-1 do
        local car_index = leaderboard[i].car.index
        local player_lapcount = players.get_lapcount(car_index)
        
        if player_lapcount_tracker[car_index] == nil then player_lapcount_tracker[car_index] = 0 end
        if animation_times_per_player[car_index] == nil then animation_times_per_player[car_index] = 0 end
        
        if player_lapcount_tracker[car_index] ~= player_lapcount then
            -- player finished a lap, start his animation, worst case we don't draw the gradient anyway
            if leaderboard[i].car.isLastLapValid then animation_times_per_player[car_index] = Time end
            if session.type == ac.SessionType.Hotlap and player_lapcount > 0 then
                -- lap ended, record his time for display
                hotlap_update_info()
            end
        end
        
        -- the fucking special case of the special case of the special case this is yuck
        if i == 0 then
            if session.type == ac.SessionType.Race and session.isTimedRace then
                if session.hasAdditionalLap and session.overtimeMs > 0 then
                    if player_lapcount_tracker[car_index] ~= player_lapcount then
                        show_leader_final_lap = true
                    end
                end
            end
        end
        
        player_lapcount_tracker[car_index] = player_lapcount
    end
    
    -- calculate intervals to each car, updated as player wants
    local refresh_counter = math.floor(Time * 1000 / Leaderboard_RefreshRate)
    if Leaderboard_Relative then refresh_counter = math.floor(Time * 1000 / Leaderboard_Relative_RefreshRate) end
    if refresh_counter ~= last_refresh_counter or interval_to_each_player[0] == nil or interval_to_each_player[0] == '' then
        last_refresh_counter = refresh_counter
        local player_lapcount = players.get_lapcount(focused)
        local player_spline = ac.getCar(focused).splinePosition
        local player_pos = player_lapcount + player_spline
        local player_current_lap_data = deltabar.get_player_current_lap_data(focused)
        local player_speed = ac.getCar(focused).speedMs
        
        for i=0, #leaderboard-1 do
            local car_info = leaderboard[i].car
            local car_index = car_info.index
            local driver_lapcount = players.get_lapcount(car_index)
            local driver_spline = car_info.splinePosition
            local driver_pos = driver_lapcount + driver_spline
            local driver_current_lap_data = deltabar.get_player_current_lap_data(car_index)
            
            -- player never has an interval
            -- and also we don't show it until the race is ACTUALLY started
            -- and if the race is finished we don't update it anymore, so you can see how far away you where when it ended
            -- sometimes isRaceFinished doesn't work, so we add manual checks
            local race_finished = car_info.isRaceFinished
            if not session.isTimedRace and session.type == ac.SessionType.Race then -- timed races are harder to handle because of the lap they have to finish, so I'll just ignore them for now...
                race_finished = race_finished or driver_lapcount >= session.laps
            end
            if not race_finished then
                local interval = ''
                if car_index == focused or (session.type == ac.SessionType.Race and sim_info.raceFlagType == ac.FlagType.None) then
                    interval = '-'
                else
                    local driver_speed = car_info.speedMs
                    if (Leaderboard_Relative == false and i < my_leaderboard_index) or (Leaderboard_Relative and driver_is_in_front(driver_spline, player_spline)) then
                        -- player is behind
                        interval = get_interval(
                            driver_pos, driver_current_lap_data, driver_spline, driver_speed, i,
                            player_pos, player_current_lap_data, player_spline, player_speed, my_leaderboard_index,
                            false
                        )
                    else
                        -- player is in front
                        interval = get_interval(
                            player_pos, player_current_lap_data, player_spline, player_speed, i,
                            driver_pos, driver_current_lap_data, driver_spline, driver_speed, my_leaderboard_index,
                            true
                        )
                    end
                end
                
                interval_to_each_player[car_index] = interval
            end
        end
    end

    if Leaderboard_Relative then
        current_player_count = Leaderboard_PeopleCount_Relative
    else
        current_player_count = Leaderboard_PeopleCount
    end
    local board_anim_elapsed = Time - Leaderboard_Extended_StartTime
    local table_closing = Leaderboard_ShowExtended == false and board_anim_elapsed < Leaderboard_ExtensionTime
    if Leaderboard_ShowExtended or table_closing then current_player_count = 13 end

    if Leaderboard_Extended_ThisFrame then
        Leaderboard_Extended_ThisFrame = false
        player_start_count = table.nkeys(to_draw)
        if Leaderboard_ShowExtended then
            player_target_count = 13 -- same 13 as above
        else
            if Leaderboard_Relative then
                player_target_count = Leaderboard_PeopleCount_Relative
            else
                player_target_count = Leaderboard_PeopleCount
            end
        end

        player_target_count = player_target_count * 2 + 1
        if Leaderboard_Relative == false then player_target_count = player_target_count + 1 end -- in relative we don't show the leader, but we do in other modes
    end

    local car_count = #leaderboard
    local draw_index = 0
    if session.type ~= ac.SessionType.Hotlap then
        -- we draw each lap in append mode in hotlap, we don't want to clear info
        table.clear(to_draw)
    end
    if session.type == ac.SessionType.Hotlap then
        local lapcount = players.get_lapcount(0)
        if lapcount == 0 then table.clear(to_draw) end
        -- for some reason AC likes to make a fake lap when we reset the session,
        -- so we have to forcefully clear the table for some time
        
        update_info(1, 0, 0) -- the current lap the player is making

        for i=Leaderboard_HotlapCount+1, 12 do
            to_draw[i] = nil -- keep only the last n laps
        end
    elseif Leaderboard_Relative then
        -- we want to show people near the player, make a list of their track positions (with the info
        -- we need), then sort it and use it
        local add_front_index = 1
        local add_back_index  = 1
        local player_trackpos = ac.getCar(focused).splinePosition
        local player_relative_data = nil
        table.clear(drivers_in_front)
        table.clear(drivers_behind)
        for i=0, car_count-1 do
            local lb_info = leaderboard[i]
            if lb_info ~= nil and lb_info.car ~= nil then
                local is_valid = is_driver_valid(lb_info, i)
                if is_valid then -- only work on people we CAN add, ignore the others
                    local car_trackpos = lb_info.car.splinePosition
                    local lb_index = i
                    local car_index = lb_info.car.index
                    local racepos = i+1
                    
                    local to_add = {
                        car_trackpos = car_trackpos,
                        lb_index = lb_index,
                        car_index = car_index,
                        racepos = racepos
                    }
                    
                    if car_index == focused then
                        player_relative_data = to_add
                    else
                        -- remember that sorting starts from 1
                        if driver_is_in_front(car_trackpos, player_trackpos) then
                            drivers_in_front[add_front_index] = to_add
                            add_front_index = add_front_index + 1
                        else
                            drivers_behind[add_back_index] = to_add
                            add_back_index = add_back_index + 1
                        end
                    end
                end
            end
        end
        
        table.sort(drivers_in_front, function(a,b)
            if b == nil then return false end -- to be extra sure...
            if a == nil then return false end -- to be extra sure...
            
            -- driver is in front it it's before the finish lap in front of the player OR after the finish lap before the end of the upper bound
            local trackpos_a_remap = a.car_trackpos - player_trackpos
            local trackpos_b_remap = b.car_trackpos - player_trackpos
            if trackpos_a_remap < 0 then trackpos_a_remap = trackpos_a_remap + 1 end
            if trackpos_b_remap < 0 then trackpos_b_remap = trackpos_b_remap + 1 end
            return trackpos_a_remap < trackpos_b_remap
        end)
        table.sort(drivers_behind, function(a,b)
            if b == nil then return false end -- to be extra sure...
            if a == nil then return false end -- to be extra sure...
            
            -- driver is in front it it's before the finish lap in front of the player OR after the finish lap before the end of the upper bound
            local trackpos_a_remap = player_trackpos - a.car_trackpos
            local trackpos_b_remap = player_trackpos - b.car_trackpos
            if trackpos_a_remap < 0 then trackpos_a_remap = trackpos_a_remap + 1 end
            if trackpos_b_remap < 0 then trackpos_b_remap = trackpos_b_remap + 1 end
            return trackpos_a_remap < trackpos_b_remap
        end)

        -- draw each list in order
        local people_we_can_draw_before = current_player_count -- before means on top of the player in the leaderboard
        local people_we_can_draw_after  = current_player_count -- after means below the player in the leaderboard
        local valid_people_before_player = table.nkeys(drivers_in_front)
        local valid_people_after_player  = table.nkeys(drivers_behind)
        -- add the rest we can draw to the counts
        -- COPYPASTE(cogno): from normal leaderboard behaviour below, with some edits to remove the leader
        if valid_people_before_player < current_player_count then
            people_we_can_draw_after = current_player_count * 2 - valid_people_before_player
            people_we_can_draw_before = valid_people_before_player
        end
        if valid_people_after_player < current_player_count then
            people_we_can_draw_before = current_player_count * 2 - valid_people_after_player
            people_we_can_draw_after = valid_people_after_player
        end

        local people_added = 0
        for i=1, people_we_can_draw_before do
            local data = drivers_in_front[people_we_can_draw_before - i + 1]
            if data ~= nil then
                update_info(data.racepos, data.lb_index, people_added)
                people_added = people_added + 1 -- no need to check if valid, we only work on valid ones!
            end
        end

        -- player in the middle
        if player_relative_data ~= nil then -- should never happen
            update_info(player_relative_data.racepos, player_relative_data.lb_index, people_added)
            people_added = people_added + 1 -- no need to check if valid, we only work on valid ones!
        end
        
        for i=1, people_we_can_draw_after do
            local data = drivers_behind[i]
            if data ~= nil then
                update_info(data.racepos, data.lb_index, people_added)
                people_added = people_added + 1 -- no need to check if valid, we only work on valid ones!
            end
        end
    else
        if car_count <= 2 + current_player_count * 2 then
            -- we have enough space to draw all of them ALWAYS
            for i=0, car_count-1 do
                local racepos = i+1
                local added = update_info(racepos, i, draw_index)
                if added then draw_index = draw_index + 1 end
            end
        else
            -- first of all, calculate how many people are valid BEFORE the player and how many are valid AFTER the player
            local valid_people_before_player = 0
            local valid_people_after_player = 0
            for i=0, #leaderboard-1 do
                if i ~= my_leaderboard_index then -- ignore the player
                    local is_valid = is_driver_valid(leaderboard[i], i)
                    if is_valid then
                        if i < my_leaderboard_index then
                            valid_people_before_player = valid_people_before_player + 1
                        end
                        if i > my_leaderboard_index then
                            valid_people_after_player = valid_people_after_player + 1
                        end
                    end
                end
            end
            
            if valid_people_before_player + valid_people_after_player <= 5 then -- plus the player 6
                -- simple case, there are at most 6 valid people TOTAL, just draw them all!
                for i=0, #leaderboard-1 do
                    local racepos = i+1
                    local added = update_info(racepos, i, draw_index)
                    if added then draw_index = draw_index + 1 end
                end
            else
                -- complex case, we have more than 6 (valid) people to draw, we must balance between how many to draw before and after the player
                
                -- if before the player we can't draw anything, then the player is the first, duh
                local player_first = valid_people_before_player == 0
                
                -- now, how many do we draw before/after the spot of the player?
                if player_first then
                    -- if the player is first, then we know we should draw all persons after him, just do that!
                    local valids_added = 0
                    for i=my_leaderboard_index, #leaderboard-1 do
                        if valids_added < 2 + current_player_count * 2 then
                            local racepos = i+1
                            local added = update_info(racepos, i, draw_index)
                            if added then
                                draw_index = draw_index + 1
                                valids_added = valids_added + 1
                            end
                        end
                    end
                else
                    -- the first is NOT the player, how much do we split?
                    local people_we_can_draw_before = current_player_count
                    local people_we_can_draw_after  = current_player_count
                    -- add the rest we can draw to the counts
                    if valid_people_before_player < current_player_count + 1 then -- the leader is always before!
                        people_we_can_draw_after = current_player_count * 2 - valid_people_before_player + 1 -- leader is factored int
                        people_we_can_draw_before = valid_people_before_player - 1 -- don't count the leader again
                    end
                    if valid_people_after_player < current_player_count then
                        people_we_can_draw_before = current_player_count * 2 - valid_people_after_player
                        people_we_can_draw_after = valid_people_after_player
                    end

                    -- now just draw them!
                    -- first the leader
                    local index_of_leader = -1
                    for i=0, my_leaderboard_index-1 do
                        local racepos = i+1
                        local added = update_info(racepos, i, draw_index)
                        if added then
                            index_of_leader = i
                            draw_index = draw_index + 1
                            break
                        end
                    end

                    -- draw people before me
                    local valids_added = 0
                    for i=my_leaderboard_index-1, index_of_leader+1, -1 do
                        if valids_added < people_we_can_draw_before then
                            local racepos = i+1
                            local added = update_info(racepos, i, draw_index)
                            if added then
                                draw_index = draw_index + 1
                                valids_added = valids_added + 1
                            end
                        end
                    end
                    
                    -- now that we've added them reverse their order so they are drawn correctly
                    for i=1, math.floor(valids_added / 2) do
                        local tmp = table.clone(to_draw[i], true)
                        to_draw[i] = table.clone(to_draw[valids_added - i + 1], true)
                        to_draw[valids_added - i + 1] = tmp
                    end
                    
                    -- draw me
                    local player_racepos = my_leaderboard_index+1
                    update_info(player_racepos, my_leaderboard_index, draw_index)
                    draw_index = draw_index + 1 -- we surely add ourselves right?
                    
                    -- draw people after me
                    local valids_added = 0
                    for i=my_leaderboard_index+1, #leaderboard-1 do
                        if valids_added < people_we_can_draw_after then
                            local racepos = i+1
                            local added = update_info(racepos, i, draw_index)
                            if added then
                                draw_index = draw_index + 1
                                valids_added = valids_added + 1
                            end
                        end
                    end
                end
            end
        end
    end
end

local function reduce_jitter(old_width)
    -- since time text changes a lot, using it to set elements will 
    -- make them vibrate a lot, so we set it inside a little extra space, but
    -- you can't just add a value, it will still vibrate!
    return math.ceil(old_width / 10) * 10
end

local function maybe_start_banner_animation(flag_state)
    -- if we immediately change we might need to go back the next frame, since it jumps back
    -- and forth too much we actually wait to be sure, about 100ms of the same flag with no interruption
    local changed = false
    if old_flag_state == nil or (old_flag_state == flag_state and old_flag_time + 0.1 <= Time) then
        changed = true
    end
    
    if old_flag_state == nil or old_flag_state ~= flag_state or old_flag_time == 0 then
        old_flag_time = Time
        old_flag_state = flag_state
    end
    return changed
end

local function convert_temp(temp_celsius)
    if Leaderboard_ShowFah then
        return string.format("%d° F", 32 + (temp_celsius * 9 / 5))
    else
        return string.format("%d° C", temp_celsius)
    end
end

local function draw_sectors(sectors_count, to_draw_best_sector_times, sector1_pos_cl, rect_width, sector_rect_pad, mp_best_sector_times, driver_sector_times)
    local small_rect_height = 3 * LeaderboardScale
    local big_rect_height   = 6 * LeaderboardScale
    local vertical_offset   = 1 * LeaderboardScale
    for sector_index=0, sectors_count-1 do
        local current_pos_cl = sector1_pos_cl + vec2((rect_width + sector_rect_pad) * sector_index, 0)
        local rect_size = vec2(rect_width, small_rect_height)
        local sector_color = colors.GRAY
        local personal_best_sector_time = nil
        local mp_best_sector_time = mp_best_sector_times[sector_index]
        local current_sector_time = driver_sector_times[sector_index]
        if to_draw_best_sector_times ~= nil then personal_best_sector_time = to_draw_best_sector_times[sector_index] end
        
        if current_sector_time ~= nil and current_sector_time > 0 then
            -- we have a sector time, compare it your best and multiplayer best
            rect_size.y = big_rect_height
            sector_color = colors.LIGHT_GRAY
            if personal_best_sector_time ~= nil and personal_best_sector_time > 0 and current_sector_time <= personal_best_sector_time  then
                sector_color = colors.GREEN
            end
            if mp_best_sector_time ~= nil and mp_best_sector_time > 0 and current_sector_time <= mp_best_sector_time then
                sector_color = colors.PURPLE
            end
        end
        
        local pos_tl = current_pos_cl + vec2(0, big_rect_height / 2 + vertical_offset - rect_size.y)
        ui.drawRectFilled(pos_tl, pos_tl + rect_size, sector_color)
    end
end

-- gap is time between this driver and the leader
local function draw_gap(best_of_this, best_of_first, gap_pos, normal_fontsize, highlight_color)
    local gap_string = '-'
    if best_of_this ~= nil and best_of_this > 0 and best_of_first ~= nil and best_of_first > 0 then
        gap_string = lap.time_positive_delta_to_string(best_of_this - best_of_first)
    end
    ui.dwriteDrawText(gap_string, normal_fontsize, gap_pos, highlight_color)
end

local function draw_best_laptime(best_lap_pos, best_of_this, normal_fontsize, highlight_color)
    local best_lap_string = lap.time_to_string(best_of_this)
    ui.dwriteDrawText(best_lap_string, normal_fontsize, best_lap_pos, highlight_color)
end

local function draw_pos(is_retired, pos_diff, line_tr, pos_tl, line_height, normal_fontsize, highlight_color)
    if is_retired then
        ui.dwriteDrawText('RET', normal_fontsize, pos_tl, highlight_color)
    else
        if pos_diff == 0 then
            ui.dwriteDrawText('-', normal_fontsize, pos_tl, highlight_color)
        else
            local pos_center_left = line_tr + vec2(9 * LeaderboardScale, line_height / 2)
            local arrow_width = 12 * LeaderboardScale
            if pos_diff > 0 then
                -- green arrow up
                local offset = vec2(0, 3) * LeaderboardScale
                ui.pathClear()
                ui.pathLineTo(pos_center_left + offset)
                ui.pathLineTo(pos_center_left + offset + vec2(arrow_width, -arrow_width)/2)
                ui.pathLineTo(pos_center_left + offset + vec2(arrow_width, 0))
                ui.pathStroke(colors.GREEN, false, 3 * LeaderboardScale)
            else
                -- red arrow down
                local offset = vec2(0, -3) * LeaderboardScale
                ui.pathClear()
                ui.pathLineTo(pos_center_left + offset)
                ui.pathLineTo(pos_center_left + offset + vec2(arrow_width, arrow_width)/2)
                ui.pathLineTo(pos_center_left + offset + vec2(arrow_width, 0))
                ui.pathStroke(colors.RED, false, 3 * LeaderboardScale)
            end
            local text_pos = pos_tl + vec2(arrow_width + 3 * LeaderboardScale, 0)
            ui.dwriteDrawText(math.abs(pos_diff), normal_fontsize, text_pos, highlight_color)
        end
    end
end

local function draw_interval(is_retired, interval_text, normal_fontsize, interval_center_pos, highlight_color)
    if is_retired then
        local interval_text_size = ui.measureDWriteText('RET', normal_fontsize)
        ui.dwriteDrawText('RET', normal_fontsize, interval_center_pos - interval_text_size / 2, highlight_color)
    else
        local interval_text_size = ui.measureDWriteText(interval_text, normal_fontsize)
        ui.dwriteDrawText(interval_text, normal_fontsize, interval_center_pos - interval_text_size / 2, highlight_color)
    end
end

local function draw_last_laptime(laptime_pos, last_lap_text, normal_fontsize, highlight_color)
    ui.dwriteDrawText(last_lap_text, normal_fontsize, laptime_pos, highlight_color)
end

local function draw_air_tarmac(textpos, airtext, roadtext, normal_fontsize, temptexts_pad, air_textsize, airtemp_textsize, tarmac_textsize, roadtemp_textsize, color_gray, color_white, air_tarmac_font, girp_tack, grip_text_size, grip_percent_text_size, wind_speed, wind_text_size, wind_speed_text_size, wind_dir, players_text_size, player_count_text_size, player_pos, player_count, weather_text_size, current_weather_text_size, weather_text)
    
    ui.pushDWriteFont(air_tarmac_font)
    if Leaderboard_ShowAirTemp then
        ui.dwriteDrawText('Air:', normal_fontsize, textpos, color_gray)
        textpos = textpos + vec2(temptexts_pad + air_textsize.x, 0)
        ui.dwriteDrawText(airtext, normal_fontsize, textpos, color_white)
        textpos = textpos + vec2(temptexts_pad * 3 + airtemp_textsize.x, 0)
    end
    if Leaderboard_ShowTarmacTemp then
        ui.dwriteDrawText('Tarmac:', normal_fontsize, textpos, color_gray)
        textpos = textpos + vec2(temptexts_pad + tarmac_textsize.x, 0)
        ui.dwriteDrawText(roadtext, normal_fontsize, textpos, color_white)
        textpos = textpos + vec2(temptexts_pad * 3 + roadtemp_textsize.x, 0)
    end
    if Leaderboard_ShowGripPercent then
        ui.dwriteDrawText('Grip:', normal_fontsize, textpos, color_gray)
        textpos = textpos + vec2(temptexts_pad + grip_text_size.x, 0)
        ui.dwriteDrawText(string.format("%.1f",girp_tack).."%", normal_fontsize, textpos, color_white)
        textpos = textpos + vec2(temptexts_pad * 3 + grip_percent_text_size.x, 0)
    end
    if Leaderboard_ShowWindInfo then
        ui.dwriteDrawText('Wind:', normal_fontsize, textpos, color_gray)
        textpos = textpos + vec2(temptexts_pad * 2 + wind_text_size.x, 0)
        ui.beginRotation()
        ui.drawImage(
            settings.get_asset("cursor"),
            textpos + vec2(0,air_textsize.y / 2 - 1 * LeaderboardScale) - vec2(54 / 2,48 / 2) * vec2(0.15, 0.2) * LeaderboardScale,
            textpos + vec2(0,air_textsize.y / 2 - 1 * LeaderboardScale) + vec2(54 / 2,48 / 2) * vec2(0.15, 0.2) * LeaderboardScale,
            color_white
        )
        ui.endRotation(wind_dir)
        textpos = textpos + vec2(temptexts_pad + 5 * LeaderboardScale, 0)
        ui.dwriteDrawText(string.format("%.0f",wind_speed).."Km/h", normal_fontsize, textpos, color_white)
        textpos = textpos + vec2(temptexts_pad * 2 + wind_speed_text_size.x, 0)
    end
    if Leaderboard_ShowPlayerCount then
        ui.dwriteDrawText("Players:", normal_fontsize, textpos, color_gray)
        textpos = textpos + vec2(temptexts_pad * 2 + players_text_size.x, 0)
        ui.dwriteDrawText(string.format("%d/%d", player_pos, player_count), normal_fontsize, textpos, color_white)
        textpos = textpos + vec2(temptexts_pad * 3 + player_count_text_size.x, 0)
    end

    if Leaderboard_ShowWeather then
        ui.dwriteDrawText("Weather:", normal_fontsize, textpos, color_gray)
        textpos = textpos + vec2(temptexts_pad + weather_text_size.x, 0)
        ui.dwriteDrawText(weather_text, normal_fontsize, textpos, color_white)
        textpos = textpos + vec2(temptexts_pad * 3 + current_weather_text_size.x, 0)
    end

    ui.popDWriteFont()
end

local function draw_belt_air_tarmac(textpos, airtext, roadtext, normal_fontsize, temptexts_pad, air_textsize, airtemp_textsize, tarmac_textsize, roadtemp_textsize,
    color_gray, color_white, air_tarmac_font, girp_tack, grip_text_size, grip_percent_text_size, wind_speed, wind_text_size, wind_speed_text_size, wind_dir, board_width, full_info_size , track_info_count, players_text_size, player_count_text_size, player_pos, player_count, weather_text_size, current_weather_text_size, weather_text)

    ui.pushClipRect(textpos, textpos + vec2(board_width, grip_text_size.y))
    local chunk_width = full_info_size --- 20 * LeaderboardScale
    local moved_pos = 0

    if track_info_count <= 2 then
        moved_pos = textpos
        draw_air_tarmac(moved_pos, airtext, roadtext, normal_fontsize, temptexts_pad, air_textsize, airtemp_textsize, tarmac_textsize, roadtemp_textsize,
        color_gray, color_white, air_tarmac_font, girp_tack, grip_text_size, grip_percent_text_size, wind_speed, wind_text_size, wind_speed_text_size, wind_dir, players_text_size, player_count_text_size, player_pos, player_count, weather_text_size, current_weather_text_size, weather_text)
    else
        moved_pos = textpos + vec2(-math.floor(Time * (60 + 60 * (Leaderboard_SessionScrollingSpeed / 100)) * LeaderboardScale) % chunk_width,0)
        
        local chunks_we_fit = math.ceil(board_width / chunk_width)
        for i=-1, chunks_we_fit-1 do
            local chunk_pos = moved_pos + vec2(chunk_width * i, 0)
            draw_air_tarmac(chunk_pos, airtext, roadtext, normal_fontsize, temptexts_pad, air_textsize, airtemp_textsize, tarmac_textsize, roadtemp_textsize,
            color_gray, color_white, air_tarmac_font, girp_tack, grip_text_size, grip_percent_text_size, wind_speed, wind_text_size, wind_speed_text_size, wind_dir, players_text_size, player_count_text_size, player_pos, player_count, weather_text_size, current_weather_text_size, weather_text)
        end
    end

    ui.popClipRect()
end

local CAROUSEL_STATE = {
    Idle = 0,
    Swap = 1,
}
local CAROUSEL_TEXTS = {
    AirTarmac = 0,
    Relative = 1,
    Standings = 2,
}
local current_carousel_state = CAROUSEL_STATE.Idle
local current_display_text   = CAROUSEL_TEXTS.AirTarmac
local display_text_entering  = CAROUSEL_TEXTS.AirTarmac
local current_anim_time = 0

local click_anim_timer = 1
function module.main()
    local focused, changed = players.get_focused_car()
    local draw_top_left = vec2(0, 22)
    local draw_size = ui.windowSize() - vec2(0, 22)
    local draw_center = draw_top_left + draw_size / 2
    local sim_info = ac.getSim()
    local session = players.get_current_session()
    local leaderboard = players.get_leaderboard()
    local normal_fontsize = settings.fontsize(10) * LeaderboardScale
    
    -- we have automatic scaling / positioning of some elements, here the are:
    -- > we calculate lap text so we can position correctly.
    --   we want the L to be aligned one after the other vertically, so we want:
    --   L 2
    --   L 8
    --   but also
    --   L   7 <- notice the extra whitespaces
    --   L 132
    -- > we calculate the width of intervals so we can center them correctly
    -- > in practice we show the gap between peoples best time and first person's best times,
    --   since that changes sizes we need to know it
    -- > in practice we show the best time, that takes space
    -- > in race we show the last time, that takes space
    -- > if enabled we show the tyre compound (short name), we need to consider that also for spacing!
    -- PERF(cogno): we don't have to calculate ALL of these every frame, only the ones we actually draw!.
    local line_count = table.nkeys(to_draw)
    local longest_number_width = 0
    local lapcount_font = fonts.archivo_medium
    local nametext_font = fonts.archivo_bold
    local min_space_for_int  = 55 * LeaderboardScale -- NOT RANDOM: if there's nothing to draw we have some default space so they don't overlap
    local min_space_for_gap  = 45 * LeaderboardScale -- NOT RANDOM: if there's nothing to draw we have some default space so they don't overlap
    local min_space_for_best = 55 * LeaderboardScale -- NOT RANDOM: if there's nothing to draw we have some default space so they don't overlap
    local min_space_for_last = 80 * LeaderboardScale -- NOT RANDOM: if there's nothing to draw we have some default space so they don't overlap
    local min_space_for_pos  = 35 * LeaderboardScale -- we NEVER resize it but this is a good place to keep it
    local longest_compound_shortname = 0
    ui.pushDWriteFont(lapcount_font)
    local table_text_size = ui.measureDWriteText('999', normal_fontsize) -- used to vertically align table elements, calculated once
    local best_of_first = nil
    if Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
        best_of_first = players.get_car_best_laptime_multiclass(0)
    else
        best_of_first = players.get_car_best_laptime(0)
    end
    for i=0, line_count-1 do
        local data_to_draw = to_draw[i]
        if data_to_draw ~= nil then
            -- lap text
            local lap_count = players.get_lapcount(data_to_draw.car_index)
            local text_size = ui.measureDWriteText(lap_count + 1, normal_fontsize)
            longest_number_width = math.max(longest_number_width, text_size.x)
            
            -- interval
            text_size = ui.measureDWriteText(data_to_draw.interval, normal_fontsize)
            text_size.x = reduce_jitter(text_size.x)
            min_space_for_int = math.max(min_space_for_int, text_size.x)
            
            -- gap to best time
            local best_of_this = 0
            if Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
                best_of_this = players.get_car_best_laptime_multiclass(data_to_draw.leaderboard_index)
            else
                best_of_this = players.get_car_best_laptime(data_to_draw.leaderboard_index)
            end
            local gap_string = '-'
            if best_of_this ~= nil and best_of_this > 0 and best_of_first ~= nil and best_of_first > 0 then
                gap_string = lap.time_positive_delta_to_string(best_of_this - best_of_first)
            end
            text_size = ui.measureDWriteText(gap_string, normal_fontsize)
            min_space_for_gap = math.max(min_space_for_gap, text_size.x)
            
            -- width of best time
            local best_laptime_string = lap.time_to_string(best_of_this)
            text_size = ui.measureDWriteText(best_laptime_string, normal_fontsize)
            min_space_for_best = math.max(min_space_for_best, text_size.x)
            
            if session.type == ac.SessionType.Hotlap then
                -- width of last time
                local last_laptime_string = lap.time_to_string(data_to_draw.hotlap_laptime)
                text_size = ui.measureDWriteText(last_laptime_string, normal_fontsize)
                min_space_for_last = math.max(min_space_for_last, reduce_jitter(text_size.x))

                -- tyre compound name
                text_size = ui.measureDWriteText(data_to_draw.hotlap_compname, normal_fontsize)
                longest_compound_shortname = math.max(longest_compound_shortname, text_size.x)

                -- gap stuff
                if i ~= 0 then
                    local gap_string = '-'
                    if best_of_this - data_to_draw.hotlap_laptime > 0 then
                        gap_string = lap.time_positive_delta_to_string(best_of_this - data_to_draw.hotlap_laptime)
                    end
                    text_size = ui.measureDWriteText(gap_string, normal_fontsize)
                    min_space_for_gap = math.max(min_space_for_gap, text_size.x)
                end
            else
                -- width of last time
                local last_laptime_string = lap.time_to_string(players.get_previous_laptime(data_to_draw.car_index))
                text_size = ui.measureDWriteText(last_laptime_string, normal_fontsize)
                min_space_for_last = math.max(min_space_for_last, text_size.x)

                -- tyre compound name
                local comp_name = ac.getTyresName(data_to_draw.car_index)
                text_size = ui.measureDWriteText(comp_name, normal_fontsize)
                longest_compound_shortname = math.max(longest_compound_shortname, text_size.x)
            end

        end
    end
    local longest_playername_width = 0
    if Leaderboard_ScrollLongNames == false then
        ui.pushDWriteFont(nametext_font)
        for i=0, #leaderboard-1 do
            local playername = ""
            if Leaderboard_ShowCarNames then
                playername = ac.getCarName(leaderboard[i].car.index)
            else
                playername = ac.getDriverName(leaderboard[i].car.index)
            end
            local text_size = ui.measureDWriteText(playername, normal_fontsize)
            longest_playername_width = math.max(longest_playername_width, text_size.x)
        end
        ui.popDWriteFont()
    end
    
    -- session type
    local session_state = sim_info.raceSessionType
    local session_text = "UNDF"
    if session_state == ac.SessionType.Practice   then session_text = 'PRAC' end
    if session_state == ac.SessionType.Qualify    then session_text = 'QUAL' end
    if session_state == ac.SessionType.Race       then session_text = 'RACE' end
    if session_state == ac.SessionType.Hotlap     then session_text = 'HOTL' end
    if session_state == ac.SessionType.TimeAttack then session_text = 'TIME' end
    if session_state == ac.SessionType.Drift      then session_text = 'DRFT' end
    if session_state == ac.SessionType.Drag       then session_text = 'DRAG' end
    local letter_size = ui.measureDWriteText(session_text, normal_fontsize)
    
    -- lap texts (or times, depends on session type)
    ui.pushDWriteFont(fonts.archivo_bold)
    local lap_text = "Lap:"
    local lap_text_size = ui.measureDWriteText(lap_text, normal_fontsize)
    local time_text = "Time:"
    local time_text_size = ui.measureDWriteText(time_text, normal_fontsize)
    
    -- time and lap count strings
    local time_string = ''
    local time_s = math.abs(sim_info.sessionTimeLeft) / 1000
    local time_min = math.floor(time_s / 60)
    time_s = time_s - time_min * 60
    local time_hour = math.floor(time_min / 60)
    time_min = time_min - time_hour * 60
    time_string = string.format("%d:%02d:%02d", time_hour, time_min, time_s)
    if sim_info.sessionTimeLeft > 0 then show_overtime = true end
    if show_overtime and sim_info.sessionTimeLeft < 0 then time_string = "Overtime" end
    
    local index_of_first_person = -1
    if leaderboard[0] ~= nil then
        index_of_first_person = leaderboard[0].car.index
    end
    local lap_string = string.format("%d", players.get_lapcount(focused)+1)
    local lapcount_of_leader = players.get_lapcount(index_of_first_person)
    local race_lap_string = string.format("%d / %d", math.clamp(lapcount_of_leader + 1, 0, session.laps), session.laps)
    
    local hide_lap = false
    if session.isTimedRace then
        race_lap_string = string.format("%d", lapcount_of_leader + 1)
        local final_lap = session.hasAdditionalLap == false and sim_info.sessionTimeLeft < 0
        if show_leader_final_lap then final_lap = true end
        -- TAG(cogno): 
        if final_lap then
            time_string = "Overtime"
            hide_lap = true
        end
    end
    
    local time_number_size = ui.measureDWriteText(time_string, normal_fontsize)
    local lap_number_size = ui.measureDWriteText(lap_string, normal_fontsize)
    local race_lap_size = ui.measureDWriteText(race_lap_string, normal_fontsize)
    
    ui.popDWriteFont()
    local session_info_padding = vec2(10, 14) * LeaderboardScale
    local line_width = 2 * LeaderboardScale
    local temptexts_pad = 5 * LeaderboardScale

    -- since time text changes a lot, using it to set elements will 
    -- make them vibrate a lot, so we set it inside a little extra space, but
    -- you can't just add a value, it will still vibrate!
    time_number_size.x = reduce_jitter(time_number_size.x)
    
    local session_min_width = session_info_padding.x * 2 + letter_size.x + line_width
    if session_state == ac.SessionType.Practice or session_state == ac.SessionType.Qualify or session_state == ac.SessionType.Hotlap then
        session_min_width = session_min_width + time_number_size.x + time_text_size.x + lap_number_size.x + lap_text_size.x + session_info_padding.x * 3 + temptexts_pad * 2
    elseif session_state == ac.SessionType.Race then
        session_min_width = session_min_width + session_info_padding.x * 2
        if hide_lap == false then
            session_min_width = session_min_width + lap_text_size.x + race_lap_size.x + temptexts_pad
        end
        if session.isTimedRace then
            session_min_width = session_min_width + temptexts_pad + session_info_padding.x + time_number_size.x + time_text_size.x
        end
    end
    
    if Leaderboard_Relative_ChangedThisFrame and session.type ~= ac.SessionType.Hotlap then
        Leaderboard_Relative_ChangedThisFrame = false
        if Leaderboard_Relative then
            display_text_entering = CAROUSEL_TEXTS.Relative
        else
            display_text_entering = CAROUSEL_TEXTS.Standings
        end
        current_carousel_state = CAROUSEL_STATE.Swap
        current_anim_time = 0 -- start animation
    end

    if current_anim_time > 0.3 then
        -- animation finished
        current_carousel_state = CAROUSEL_STATE.Idle
        current_display_text = display_text_entering
    end

    if current_carousel_state == CAROUSEL_STATE.Idle and current_display_text ~= CAROUSEL_TEXTS.AirTarmac and current_anim_time > 5 then
        display_text_entering = CAROUSEL_TEXTS.AirTarmac
        current_anim_time = 0
        current_carousel_state = CAROUSEL_STATE.Swap
    end
    current_anim_time = math.clamp(current_anim_time + Dt, 0, 10)

    -- we also write "Air: 26°C Tarmac: 24°C" so we need to fit that
    local second_line_horizontal_offset = 11 * LeaderboardScale
    local air_tarmac_font = fonts.archivo_medium
    local airtext  = convert_temp(sim_info.ambientTemperature)
    local roadtext = convert_temp(sim_info.roadTemperature)

    local girp_tack = sim_info.roadGrip * 100
    local wind_speed = sim_info.windSpeedKmh
    local wind_dir = sim_info.windDirectionDeg


    local player_count = #leaderboard
    local player_racepos = -1
    for i=0, player_count-1 do
        if leaderboard[i].car.index == focused then
            player_racepos = players.get_racepos(i)
            break
        end
    end

    --
    -- weather
    --
    local weather_text = "Unavailable"
    if sim_info.isWeatherFXActive then
        -- weather is not always available (i think only offline and when enabled)
        local weather_type = sim_info.weatherType
        if weather_type == ac.WeatherType.LightThunderstorm then weather_text = "Light Thunderstorm" end
        if weather_type == ac.WeatherType.Thunderstorm      then weather_text = "Thunderstorm" end
        if weather_type == ac.WeatherType.HeavyThunderstorm then weather_text = "Heavy Thunderstorm" end
        if weather_type == ac.WeatherType.LightDrizzle      then weather_text = "Light Drizzle" end
        if weather_type == ac.WeatherType.Drizzle           then weather_text = "Drizzle" end
        if weather_type == ac.WeatherType.HeavyDrizzle      then weather_text = "Heavy Drizzle" end
        if weather_type == ac.WeatherType.LightRain         then weather_text = "Light Rain" end
        if weather_type == ac.WeatherType.Rain              then weather_text = "Rain" end
        if weather_type == ac.WeatherType.HeavyRain         then weather_text = "Heavy Rain" end
        if weather_type == ac.WeatherType.LightSnow         then weather_text = "Light Snow" end
        if weather_type == ac.WeatherType.Snow              then weather_text = "Snow" end
        if weather_type == ac.WeatherType.HeavySnow         then weather_text = "Heavy Snow" end
        if weather_type == ac.WeatherType.LightSleet        then weather_text = "Light Sleet" end
        if weather_type == ac.WeatherType.Sleet             then weather_text = "Sleet" end
        if weather_type == ac.WeatherType.HeavySleet        then weather_text = "Heavy Sleet" end
        if weather_type == ac.WeatherType.Clear             then weather_text = "Clear" end
        if weather_type == ac.WeatherType.FewClouds         then weather_text = "Few Clouds" end
        if weather_type == ac.WeatherType.ScatteredClouds   then weather_text = "Scattered Clouds" end
        if weather_type == ac.WeatherType.BrokenClouds      then weather_text = "Broken Clouds" end
        if weather_type == ac.WeatherType.OvercastClouds    then weather_text = "Overcast Clouds" end
        if weather_type == ac.WeatherType.Fog               then weather_text = "Fog" end
        if weather_type == ac.WeatherType.Mist              then weather_text = "Mist" end
        if weather_type == ac.WeatherType.Smoke             then weather_text = "Smoke" end
        if weather_type == ac.WeatherType.Haze              then weather_text = "Haze" end
        if weather_type == ac.WeatherType.Sand              then weather_text = "Sand" end
        if weather_type == ac.WeatherType.Dust              then weather_text = "Dust" end
        if weather_type == ac.WeatherType.Squalls           then weather_text = "Squalls" end
        if weather_type == ac.WeatherType.Tornado           then weather_text = "Tornado" end
        if weather_type == ac.WeatherType.Hurricane         then weather_text = "Hurricane" end
        if weather_type == ac.WeatherType.Cold              then weather_text = "Cold" end
        if weather_type == ac.WeatherType.Hot               then weather_text = "Hot" end
        if weather_type == ac.WeatherType.Windy             then weather_text = "Windy" end
        if weather_type == ac.WeatherType.Hail              then weather_text = "Hail" end
        if weather_type == 100                              then weather_text = "No Clouds" end -- added by Sol, available in source code, but not in 0.2.3 API (though it still works)
    end

    ui.pushDWriteFont(air_tarmac_font)

    local air_textsize      = ui.measureDWriteText('Air:', normal_fontsize)
    local airtemp_textsize  = ui.measureDWriteText(airtext, normal_fontsize)
    local tarmac_textsize   = ui.measureDWriteText('Tarmac:', normal_fontsize)
    local roadtemp_textsize = ui.measureDWriteText(roadtext, normal_fontsize)

    local grip_text_size = ui.measureDWriteText('Grip:', normal_fontsize)
    local grip_percent_text_size = ui.measureDWriteText(string.format("%.1f",girp_tack).."%", normal_fontsize)
    local wind_text_size = ui.measureDWriteText('Wind:', normal_fontsize)
    local players_text_size = ui.measureDWriteText('Players:', normal_fontsize)
    local player_count_text_size = ui.measureDWriteText(string.format("%d/%d", player_racepos, player_count), normal_fontsize)
    local wind_speed_text_size = ui.measureDWriteText(string.format("%.0f",wind_speed).."Km/h", normal_fontsize)
    local weather_text_size = ui.measureDWriteText("Weather:", normal_fontsize)
    local current_weather_text_size = ui.measureDWriteText(weather_text, normal_fontsize)

    ui.popDWriteFont()
    local air_tarmac_size = second_line_horizontal_offset * 2 + air_textsize.x + temptexts_pad * 5 + airtemp_textsize.x + tarmac_textsize.x + roadtemp_textsize.x

    local full_track_info_size = 0
    local track_info_count = 0

    if Leaderboard_ShowAirTemp then
        full_track_info_size = full_track_info_size + temptexts_pad * 4 + airtemp_textsize.x + air_textsize.x
        track_info_count = track_info_count + 1
    end

    if Leaderboard_ShowTarmacTemp then
        full_track_info_size = full_track_info_size + temptexts_pad * 4 + tarmac_textsize.x + roadtemp_textsize.x
        track_info_count = track_info_count + 1
    end

    if Leaderboard_ShowGripPercent then
        full_track_info_size = full_track_info_size + temptexts_pad * 4 + grip_text_size.x + grip_percent_text_size.x
        track_info_count = track_info_count + 1
    end

    if Leaderboard_ShowWindInfo then
        full_track_info_size = full_track_info_size + temptexts_pad * 5 + wind_speed_text_size.x +  wind_text_size.x + 5 * LeaderboardScale
        track_info_count = track_info_count + 1
    end

    if Leaderboard_ShowPlayerCount then
        full_track_info_size = full_track_info_size + temptexts_pad * 5 + player_count_text_size.x + players_text_size.x + 5 * LeaderboardScale
        track_info_count = track_info_count + 1
    end

    if Leaderboard_ShowWeather then
        full_track_info_size = full_track_info_size + temptexts_pad * 4 + weather_text_size.x + current_weather_text_size.x
        track_info_count = track_info_count + 1
    end

    --local full_track_info = second_line_horizontal_offset * 2 + air_textsize.x + temptexts_pad * 17 + airtemp_textsize.x + tarmac_textsize.x + roadtemp_textsize.x + grip_text_size.x + grip_percent_text_size.x + wind_text_size.x + 7 * LeaderboardScale + wind_speed_text_size.x
    session_min_width = math.max(session_min_width, air_tarmac_size)
    -- NOTE(cogno): here's how we handle different sessions
    -- PRACTICE (PRAC):
    -- top left hast Time (decreasing) then your Laps (from 0)
    -- right side is Sectors (squares that show sectors), 
    -- Gap (from person to first) and Best lap time (for that person)
    -- leaderboard is in order of best lap times (alphabetic if best lap time == 0)
    -- QUALIFY (QUAL): identic to PRAC
    -- RACE:
    -- top left is Lap of first car / session total (starting from 1)
    -- leaderboard in order of race position
    -- right side is Pos (arrows that show positions gained / lost),
    -- Int (interval from player to people in front / back) and Last 
    -- lap time (of that person)
    --
    -- as of now we don't do any other session
    --                     - Cogno 2024/04/09
    
    
    
    -- calculate the minimum line length we need
    local quad_padding = 2 * LeaderboardScale
    local line_height = settings.line_height * LeaderboardScale
    local rect_pad = vec2(quad_padding, quad_padding)
    local rect_size = vec2(line_height, line_height) - rect_pad * 2
    local text_pad = vec2(7,7) * LeaderboardScale
    local textsize_L = ui.measureDWriteText('L', normal_fontsize)
    ui.popDWriteFont()
    local border_pad = 12
    local max_space_for_drivernames = longest_playername_width
    if Leaderboard_ScrollLongNames then
        max_space_for_drivernames = 150 * LeaderboardScale
    end
    local max_leftside_width = rect_size.x + quad_padding * 2 + text_pad.x * 2 + max_space_for_drivernames + longest_number_width + textsize_L.x + border_pad
    
    if Leaderboard_ShowTyres then
        max_leftside_width = max_leftside_width + longest_compound_shortname + text_pad.x * 6
    end

    -- check if we need to make space to put the multiclass tags
    local need_space_for_tags = false
    if Leaderboard_Multiclass_Enabled then
        for i=0, #leaderboard-1 do
            local car_index = leaderboard[i].car.index
            local driver_tag = players.get_multiclass_info(car_index)
            if driver_tag ~= nil and driver_tag.name ~= nil and driver_tag.name ~= "" then
                need_space_for_tags = true
                break
            end
        end
    end

    local mc_rect_width = 3 * LeaderboardScale
    local mc_padding    = 5 * LeaderboardScale
    if need_space_for_tags then
        max_leftside_width = max_leftside_width + mc_rect_width + mc_padding
    end
    local resize_width = math.max(max_leftside_width, session_min_width)
    
    local flag_height         = 33  * LeaderboardScale -- height of 'green flag' zone
    local title_height        = 37  * LeaderboardScale -- height of space with 'Pos Int Last' texts
    local default_board_width = 190 * LeaderboardScale -- default width of left side of leaderboard (the one with R | Lap 25 / 44)
    local board_width = math.max(default_board_width, math.floor(resize_width)) -- we stretch the size to fit the text (if needed)
    
    --
    -- automatic resize of right side of leatherboard (called "info side")
    --
    local pad             = 8  * LeaderboardScale
    local sector_rect_pad = 4  * LeaderboardScale
    local rect_width      = 22 * LeaderboardScale
    local sec_gap_padding = 20 * LeaderboardScale
    local sectors_count = #players.get_sector_splits()
    
    local min_space_for_race_info = sec_gap_padding * 2 + pad * 2 + min_space_for_pos + min_space_for_int + min_space_for_last
    local sectors_space = math.max(sectors_count, 3) * (sector_rect_pad + rect_width)
    local min_space_for_quali_info = pad * 2 + sectors_space + sec_gap_padding * 2 + min_space_for_gap + min_space_for_best
    if Leaderboard_Relative then
        min_space_for_quali_info = pad * 2 + sec_gap_padding + min_space_for_int + min_space_for_last -- we never show the pos, too confusing
        min_space_for_race_info  = pad * 2 + sec_gap_padding + min_space_for_int + min_space_for_last -- we never show the pos, too confusing
    end
    if session.type == ac.SessionType.Hotlap then
        min_space_for_quali_info = pad * 2 + sectors_space + sec_gap_padding * 2 + min_space_for_gap + min_space_for_last
    end
    local info_width = min_space_for_race_info
    if session_state == ac.SessionType.Practice or session_state == ac.SessionType.Qualify or session_state == ac.SessionType.Hotlap then
        info_width = min_space_for_quali_info
    end
    info_width = math.round(info_width)
    
    local header_height = flag_height + title_height
    local header_size = vec2(board_width, header_height)
    local banner_size = vec2(board_width + info_width, 30 * LeaderboardScale) -- notification of important flags at the bottom with scrolling text
    
    local corner_tl = draw_center - draw_size / 2
    
    local window_center = ui.windowPos() + ui.windowSize() / 2
    local screensize = vec2(sim_info.windowWidth, sim_info.windowHeight) / ac.getUI().uiScale
    if window_center.x > screensize.x / 2 then
        corner_tl = draw_center + vec2(draw_size.x / 2 - banner_size.x, -draw_size.y / 2)
    end

    local top_center = corner_tl + vec2(board_width)
    local info_start = top_center + vec2(0, flag_height)

    local flagtype = sim_info.raceFlagType
    local flag_text = "GREEN FLAG"
    local flag_background = colors.GREEN
    local flagtext_color = colors.BLACK
    if flagtype == ac.FlagType.None then
        flag_text = ''
        flag_background = colors.BG
        -- for practice / qualify no flag is actually green flag
        if session_state == ac.SessionType.Qualify or session_state == ac.SessionType.Practice or session_state == ac.SessionType.Hotlap then
            flag_text = "GREEN FLAG"
            flag_background = colors.GREEN
        end
    elseif flagtype == ac.FlagType.Caution then
        flag_text = "YELLOW FLAG"
        flag_background = colors.YELLOW
    elseif flagtype == ac.FlagType.Stop then
        flag_text = "BLACK FLAG"
        flag_background = colors.BLACK
        flagtext_color = colors.WHITE
    elseif flagtype == ac.FlagType.ReturnToPits then
        flag_text = "PENALTY"
        flag_background = colors.RED
        flagtext_color = colors.WHITE
    elseif flagtype == ac.FlagType.FasterCar then
        flag_text = "BLUE FLAG"
        flag_background = colors.BLUE
        flagtext_color = colors.WHITE
    elseif flagtype == ac.FlagType.OneLapLeft then
        flag_text = "WHITE FLAG"
        flag_background = colors.WHITE
    elseif flagtype == ac.FlagType.Finished then
        flag_text = "RACE OVER"
        if session_state == ac.SessionType.Practice then
            flag_text = "PRACTICE OVER"
        elseif session_state == ac.SessionType.Qualify then
            flag_text = "QUALIFY OVER"
        end
        flag_background = colors.WHITE
    end
    if flag_text == '' then
        show_banner = false
        real_flag_background = colors.BG
        real_flag_text = flag_text
    else
        local changed = maybe_start_banner_animation(flagtype)
        if changed then
            if real_flag_text ~= flag_text then -- so it doesn't get called every frame
                show_banner = true
                is_showing_banner = false
                animation_start_time = Time
            end
            real_flag_background = flag_background
            real_flag_text = flag_text
        end
    end
    if real_flag_text == 'GREEN FLAG' then
        if Time - animation_start_time > green_flag_banner_show_time then
            show_banner = false
            animation_start_time = Time
        end
    end
    local table_width = board_width + info_width
    
    -- if someone moves the ui we show him the full leaderboard so he can balance it accordingly
    local force_seen = false
    if Leaderboard_ShowExtended == false and ui.mouseDown(ui.MouseButton.Left) and settings.is_inside(ui.mouseLocalPos(), ui.windowSize() / 2, ui.windowSize() / 2) then
        line_count = current_player_count * 2 + 1
        if Leaderboard_Relative == false then line_count = line_count + 1 end
        force_seen = true
    end

    local full_lb_row_count = current_player_count * 2 + 2
    local total_size = vec2(table_width, line_height * full_lb_row_count + banner_size.y + header_height + 2 * LeaderboardScale)
    local total_center = corner_tl + total_size / 2
    players.play_intro_anim_setup(total_center, total_size, on_show_animation_start, is_showing)

    -- backgrounds
    local pixel_aligned = vec2(math.floor(top_center.x), math.floor(top_center.y))
    ui.drawRectFilled(corner_tl, corner_tl + header_size, colors.DARK_BG, 5 * LeaderboardScale, ui.CornerFlags.TopLeft)
    ui.drawRectFilled(pixel_aligned, pixel_aligned + vec2(info_width, flag_height), real_flag_background, 5 * LeaderboardScale, ui.CornerFlags.TopRight)
    ui.drawRectFilled(info_start, info_start + vec2(info_width, title_height), colors.BG)

    local dots_texture_string = settings.get_asset("leaderboard_dot")
    local dots_size = vec2(default_board_width, header_height)
    ui.drawImageQuad(
        dots_texture_string,
        corner_tl,
        corner_tl + vec2(dots_size.x, 0),
        corner_tl + dots_size,
        corner_tl + vec2(0, dots_size.y),
        rgbm(1, 1, 1, 0.7)
    )
    
    --
    -- top texts
    --
    ui.pushDWriteFont(fonts.archivo_bold)
    
    local letter_pos = corner_tl + session_info_padding
    ui.dwriteDrawText(session_text, normal_fontsize, letter_pos)
    
    -- vertical separator bar
    local line_center = letter_pos + vec2(letter_size.x + session_info_padding.x, letter_size.y / 2)
    local separator_height = 25 * LeaderboardScale
    local line_pos = line_center - vec2(line_width, separator_height) / 2
    ui.drawRectFilled(line_pos, line_pos + vec2(line_width, separator_height), colors.WHITE)
    
    -- lap texts (or times, depends on session type)
    local lap_text_startpos = line_center + vec2(session_info_padding.x, 0)
    local top_text_pos = lap_text_startpos - vec2(0, lap_text_size.y / 2)
    if session_state == ac.SessionType.Practice or session_state == ac.SessionType.Qualify or session_state == ac.SessionType.Hotlap then
        -- PRAC | Time 20:00.000 Lap 0
        ui.dwriteDrawText(time_text, normal_fontsize, top_text_pos, colors.TEXT_GRAY)
        top_text_pos = top_text_pos + vec2(time_text_size.x + temptexts_pad, 0)
        ui.dwriteDrawText(time_string, normal_fontsize, top_text_pos, colors.WHITE)
        top_text_pos = top_text_pos + vec2(time_number_size.x + session_info_padding.x, 0)
        ui.dwriteDrawText(lap_text, normal_fontsize, top_text_pos, colors.TEXT_GRAY)
        top_text_pos = top_text_pos + vec2(lap_text_size.x + temptexts_pad, 0)
        ui.dwriteDrawText(lap_string, normal_fontsize, top_text_pos, colors.WHITE)
    elseif session_state == ac.SessionType.Race then
        if session.isTimedRace then
            ui.dwriteDrawText(time_text, normal_fontsize, top_text_pos, colors.TEXT_GRAY)
            top_text_pos = top_text_pos + vec2(time_text_size.x + temptexts_pad, 0)
            ui.dwriteDrawText(time_string, normal_fontsize, top_text_pos, colors.WHITE)
            top_text_pos = top_text_pos + vec2(time_number_size.x + session_info_padding.x, 0)
        end
        if hide_lap == false then
            ui.dwriteDrawText(lap_text, normal_fontsize, top_text_pos, colors.TEXT_GRAY)
            top_text_pos = top_text_pos + vec2(lap_text_size.x + temptexts_pad, 0)
            ui.dwriteDrawText(race_lap_string, normal_fontsize, top_text_pos, colors.WHITE)
        end
    end
    
    -- air and tarmac
    local second_line_vertical_offset = 11 * LeaderboardScale
    local carousel_lerp_t = math.clamp(settings.remap(current_anim_time, 0, 0.3, 0, 1), 0, 1)
    local carousel_exit_offset  = carousel_lerp_t       * 10 * LeaderboardScale
    local carousel_enter_offset = (carousel_lerp_t - 1) * 10 * LeaderboardScale

    local temps_pos_exit  = corner_tl + vec2(carousel_exit_offset, flag_height)  + vec2(second_line_horizontal_offset, second_line_vertical_offset)
    local temps_pos_enter = corner_tl + vec2(carousel_enter_offset, flag_height) + vec2(second_line_horizontal_offset, second_line_vertical_offset)
    
    local color_gray_exit   = colors.TEXT_GRAY:clone()
    local color_white_exit  = colors.WHITE:clone()
    local color_gray_enter  = colors.TEXT_GRAY:clone()
    local color_white_enter = colors.WHITE:clone()
    color_gray_exit.mult   = 1 - carousel_lerp_t
    color_white_exit.mult  = 1 - carousel_lerp_t
    color_gray_enter.mult  = carousel_lerp_t
    color_white_enter.mult = carousel_lerp_t
    
    -- API(cogno): uuuuh having this many ifs and checks kind of sucks, can you avoid it?
    if current_carousel_state == CAROUSEL_STATE.Idle then
        if current_display_text == CAROUSEL_TEXTS.AirTarmac then
            draw_belt_air_tarmac(temps_pos_enter, airtext, roadtext, normal_fontsize, temptexts_pad, air_textsize, airtemp_textsize, tarmac_textsize, roadtemp_textsize, color_gray_enter, color_white_enter, air_tarmac_font, girp_tack, grip_text_size, grip_percent_text_size, wind_speed, wind_text_size, wind_speed_text_size, wind_dir, board_width - second_line_horizontal_offset * 2, full_track_info_size, track_info_count, players_text_size, player_count_text_size, player_racepos, player_count, weather_text_size, current_weather_text_size, weather_text)
        elseif current_display_text == CAROUSEL_TEXTS.Relative then
            ui.dwriteDrawText("RELATIVE", normal_fontsize, temps_pos_enter + vec2(-1 * LeaderboardScale, 0), color_white_enter)
        elseif current_display_text == CAROUSEL_TEXTS.Standings then
            ui.dwriteDrawText("STANDINGS", normal_fontsize, temps_pos_enter + vec2(-1 * LeaderboardScale, 0), color_white_enter)
        end
    else
        if current_display_text == CAROUSEL_TEXTS.AirTarmac then
            draw_belt_air_tarmac(temps_pos_enter, airtext, roadtext, normal_fontsize, temptexts_pad, air_textsize, airtemp_textsize, tarmac_textsize, roadtemp_textsize, color_gray_enter, color_white_enter, air_tarmac_font, girp_tack, grip_text_size, grip_percent_text_size, wind_speed, wind_text_size, wind_speed_text_size, wind_dir, board_width - second_line_horizontal_offset * 2,full_track_info_size, track_info_count, players_text_size, player_count_text_size, player_racepos, player_count, weather_text_size, current_weather_text_size, weather_text)
        elseif current_display_text == CAROUSEL_TEXTS.Relative then
            ui.dwriteDrawText("RELATIVE", normal_fontsize, temps_pos_exit + vec2(-1 * LeaderboardScale, 0), color_white_exit)
        elseif current_display_text == CAROUSEL_TEXTS.Standings then
            ui.dwriteDrawText("STANDINGS", normal_fontsize, temps_pos_exit + vec2(-1 * LeaderboardScale, 0), color_white_exit)
        end
        
        if display_text_entering == CAROUSEL_TEXTS.AirTarmac then
            draw_belt_air_tarmac(temps_pos_enter, airtext, roadtext, normal_fontsize, temptexts_pad, air_textsize, airtemp_textsize, tarmac_textsize, roadtemp_textsize, color_gray_enter, color_white_enter, air_tarmac_font, girp_tack, grip_text_size, grip_percent_text_size, wind_speed, wind_text_size, wind_speed_text_size, wind_dir, board_width - second_line_horizontal_offset * 2, full_track_info_size, track_info_count, players_text_size, player_count_text_size, player_racepos, player_count, weather_text_size, current_weather_text_size, weather_text)
        elseif display_text_entering == CAROUSEL_TEXTS.Relative then
            ui.dwriteDrawText("RELATIVE", normal_fontsize, temps_pos_enter + vec2(-1 * LeaderboardScale, 0), color_white_enter)
        elseif display_text_entering == CAROUSEL_TEXTS.Standings then
            ui.dwriteDrawText("STANDINGS", normal_fontsize, temps_pos_enter + vec2(-1 * LeaderboardScale, 0), color_white_enter)
        end
    end
    
    
    -- flag text
    local flag_textsize = settings.fontsize(10) * LeaderboardScale
    local flag_text_size = ui.measureDWriteText(real_flag_text, flag_textsize)
    local flag_text_pos = top_center + vec2(7 * LeaderboardScale, flag_text_size.y / 2 + 6 * LeaderboardScale)
    ui.dwriteDrawText(real_flag_text, flag_textsize, flag_text_pos, flagtext_color)
    
    if Leaderboard_Relative and session_state ~= ac.SessionType.Hotlap then
        -- only 'Int' and 'Last' because 'Pos' is too confusing
        local info_text_pad = vec2(pad, second_line_vertical_offset)
        local int_text_pos  = info_start + info_text_pad + vec2(15 * LeaderboardScale, 0) -- 15 to manually set it somewhat centered so we don't have to calculate text size
        local last_text_pos = info_start + info_text_pad + vec2(sec_gap_padding + min_space_for_int + 15 * LeaderboardScale, 0) -- 15 to manually set it somewhat centered so we don't have to calculate text size
        ui.dwriteDrawText('Int', normal_fontsize, int_text_pos, colors.WHITE)
        ui.dwriteDrawText('Last', normal_fontsize, last_text_pos, colors.WHITE)
    else
        if session_state == ac.SessionType.Hotlap then
            -- 'Sec Gap Last'
            local info_text_pad = vec2(pad, second_line_vertical_offset)
            local sec_text_pos  = info_start + info_text_pad
            local gap_text_pos  = info_start + info_text_pad + vec2(sectors_space + sec_gap_padding, 0)
            local best_text_pos = info_start + info_text_pad + vec2(sectors_space + sec_gap_padding * 2 + min_space_for_gap, 0)
            ui.dwriteDrawText('Sectors', normal_fontsize, sec_text_pos, colors.WHITE)
            ui.dwriteDrawText('Gap', normal_fontsize, gap_text_pos, colors.WHITE)
            ui.dwriteDrawText('Last', normal_fontsize, best_text_pos, colors.WHITE)
        elseif session_state == ac.SessionType.Practice or session_state == ac.SessionType.Qualify then
            -- 'Sec Gap Best'
            local info_text_pad = vec2(pad, second_line_vertical_offset)
            local sec_text_pos  = info_start + info_text_pad
            local gap_text_pos  = info_start + info_text_pad + vec2(sectors_space + sec_gap_padding, 0)
            local best_text_pos = info_start + info_text_pad + vec2(sectors_space + sec_gap_padding * 2 + min_space_for_gap, 0)
            ui.dwriteDrawText('Sectors', normal_fontsize, sec_text_pos, colors.WHITE)
            ui.dwriteDrawText('Gap', normal_fontsize, gap_text_pos, colors.WHITE)
            ui.dwriteDrawText('Best', normal_fontsize, best_text_pos, colors.WHITE)
        elseif session_state == ac.SessionType.Race then
            -- 'Pos Int Last'
            local info_text_pad = vec2(pad, second_line_vertical_offset)
            local pos_text_pos  = info_start + info_text_pad
            local int_text_pos  = info_start + info_text_pad + vec2(min_space_for_pos + sec_gap_padding                         + 15 * LeaderboardScale, 0) -- 15 to manually set it somewhat centered so we don't have to calculate text size
            local last_text_pos = info_start + info_text_pad + vec2(min_space_for_pos + sec_gap_padding * 2 + min_space_for_int + 15 * LeaderboardScale, 0) -- 15 to manually set it somewhat centered so we don't have to calculate text size
            ui.dwriteDrawText('Pos', normal_fontsize, pos_text_pos, colors.WHITE)
            ui.dwriteDrawText('Int', normal_fontsize, int_text_pos, colors.WHITE)
            ui.dwriteDrawText('Last', normal_fontsize, last_text_pos, colors.WHITE)
        end
    end

    --
    -- calculate all players best sectors (the best first sector ever, the best second etc.)
    --
    local session_best_lap = players.get_best_laptime()
    local mp_best_sector_times = players.get_best_sector_times()
    
    
    -- draw every line with info inside
    local board_anim_elapsed = Time - Leaderboard_Extended_StartTime
    local extended_lerp_t = math.clamp(settings.remap(board_anim_elapsed, 0, Leaderboard_ExtensionTime, 0, 1), 0, 1)
    local anim_line_count = settings.remap(extended_lerp_t, 0, 1, player_start_count, player_target_count)
    if extended_lerp_t >= 1 then anim_line_count = line_count end
    local board_start = corner_tl + vec2(0, header_height)
    local board_height = anim_line_count * line_height + banner_size.y + 2 * LeaderboardScale
    local board_end = board_start + vec2(board_width + info_width, board_height)
    ui.pushClipRect(board_start, board_end)
    local rect_radius = 5 * LeaderboardScale
    local multiclass_elapsed = Time - Leaderboard_Multiclass_EnabledTime
    local mc_lerp_t = settings.anim_lerp(multiclass_elapsed, Leaderboard_Multiclass_Enabled, multiclass_anim_time)
    local player_lapcount = players.get_lapcount(0)
    local player_total_lapcount = player_lapcount + ac.getCar(focused).splinePosition
    for player_index=0, line_count-1 do
        local line_tl = corner_tl + vec2(0, player_index * line_height + header_height)
        local left_color = colors.DARK_BG
        local right_color = colors.BG
        if player_index % 2 == 1 then
            left_color = colors.BG
            right_color = colors.LIGHT_BG
        end
        local data_to_draw = to_draw[player_index]
        if force_seen then
            -- we're forcefully drawing the leaderboard so the user can know how much space it uses on screen
            local line_top_center = line_tl + vec2(board_width, 0)
            local line_bot_right  = line_tl + vec2(board_width + info_width, line_height)
            -- backgrounds (left then right)
            if player_index == line_count - 1 then
                ui.drawRectFilled(line_tl, line_tl + vec2(board_width, line_height), left_color)
                ui.drawRectFilled(line_top_center, line_bot_right, right_color)
            else
                ui.drawRectFilled(line_tl, line_tl + vec2(board_width, line_height), left_color)
                ui.drawRectFilled(line_top_center, line_bot_right, right_color)
            end
            ui.drawRectFilled(line_tl + rect_pad, line_tl + rect_pad + rect_size, colors.WHITE, rect_radius, ui.CornerFlags.BottomRight)
        end
        if data_to_draw ~= nil then
            -- data available, draw it
            local to_draw_car_info = ac.getCar(data_to_draw.car_index)
            local lap_count = players.get_lapcount(data_to_draw.car_index)
            
            local player_move_data = players.get_player_movement_info(data_to_draw.car_index)
            local elapsed = Time
            if player_move_data ~= nil then elapsed = elapsed - player_move_data.movement_change_time end
            local highlight_color = colors.WHITE
            local square_color = highlight_color
            
            if Leaderboard_Relative and elapsed > 3 then
               highlight_color = colors.TEXT_LIGHT_GRAY
               square_color = colors.TEXT_LIGHT_GRAY
            end
            if to_draw_car_info.isConnected == false then
                highlight_color = colors.TEXT_LIGHT_GRAY
                square_color    = colors.WHITE
            end
            if data_to_draw.car_index == 0 then -- only the player gets the yellow row
                highlight_color = colors.TEXT_YELLOW
            end
            if data_to_draw.car_index == focused then -- only the focused player gets the yellow square
                square_color    = colors.TEXT_YELLOW
            end
            if session.type == ac.SessionType.Hotlap then
                if player_index == 0 then
                    highlight_color = colors.TEXT_YELLOW
                    square_color = colors.TEXT_YELLOW
                else
                    highlight_color = colors.WHITE
                    square_color = colors.WHITE
                end
                lap_count = data_to_draw.hotlap_lapcount
            end
            if Leaderboard_Relative and data_to_draw.car_index ~= focused and session_state == ac.SessionType.Race then
                local driver_spline = to_draw_car_info.splinePosition
                local diff = player_total_lapcount - (lap_count + driver_spline)
                local diff_abs = math.abs(diff)
                if diff_abs >= 0.5 then
                    if diff < 0 then
                        if elapsed > 3 then
                            highlight_color = colors.BLUE_RELATIVE2_STILL
                            square_color    = colors.BLUE_RELATIVE2_STILL
                        else
                            highlight_color = colors.BLUE_RELATIVE2
                            square_color    = colors.BLUE_RELATIVE2
                        end
                    else
                        if elapsed > 3 then
                            highlight_color = colors.BLUE_RELATIVE_STILL
                            square_color    = colors.BLUE_RELATIVE_STILL
                        else
                            highlight_color = colors.BLUE_RELATIVE
                            square_color    = colors.BLUE_RELATIVE
                        end
                    end
                end
            end
            
            local texture_color = nil
            
            local last_laptime = players.get_previous_laptime(data_to_draw.car_index)
            local best_laptime = 0
            if session.type == ac.SessionType.Hotlap then
                if player_index ~= 0 then
                    best_laptime = data_to_draw.hotlap_laptime
                    last_laptime = data_to_draw.hotlap_laptime
                end
            elseif Leaderboard_Multiclass_Enabled and Leaderboard_Multiclass_FilterEnabled and Leaderboard_Relative == false then
                best_laptime = players.get_car_best_laptime_multiclass(data_to_draw.leaderboard_index)
            else
                best_laptime = players.get_car_best_laptime(data_to_draw.leaderboard_index)
            end
            local last_lap_text = ''
            if session.type == ac.SessionType.Hotlap then
                last_lap_text = lap.time_to_string(data_to_draw.hotlap_laptime)
            elseif to_draw_car_info.isRetired then
                last_lap_text = 'RET'
            else
                last_lap_text = lap.time_to_string(last_laptime)
            end
            local laptime_color = highlight_color
            local curve_t = settings.get_curve_t(animation_times_per_player[data_to_draw.car_index], true, Leaderboard_AnimDuration)
            if last_laptime > 0 and last_laptime ~= nil and to_draw_car_info.isLastLapValid then
                if session.type ~= ac.SessionType.Hotlap then
                    if best_laptime ~= nil and best_laptime > 0 and last_laptime <= best_laptime then
                        texture_color = colors.GREEN
                    end
                    if session_best_lap ~= nil and session_best_lap > 0 and last_laptime <= session_best_lap then
                        texture_color = colors.PURPLE
                    end
                elseif player_index == 1 then
                    -- in hotlap only draw the purple color (if it's time) and on the latest lap only
                    if session_best_lap ~= nil and session_best_lap > 0 and last_laptime <= session_best_lap then
                        texture_color = colors.PURPLE
                    end
                end
            end
            
            -- backgrounds (left then right)
            local full_line_size = vec2(board_width + info_width, line_height)
            local line_top_center = line_tl + vec2(board_width, 0)
            local line_bot_right  = line_tl + full_line_size
            if force_seen == false then
                if player_index == line_count - 1 then
                    ui.drawRectFilled(line_tl, line_tl + vec2(board_width, line_height), left_color)
                    ui.drawRectFilled(line_top_center, line_bot_right, right_color)
                else
                    ui.drawRectFilled(line_tl, line_tl + vec2(board_width, line_height), left_color)
                    ui.drawRectFilled(line_top_center, line_bot_right, right_color)
                end
            end
            local mouse_in_line = settings.is_inside(ui.mouseLocalPos(), line_bot_right - full_line_size / 2, full_line_size / 2)
            if mouse_in_line then
                local peak_time = 0.08
                local t1 = math.clamp(settings.remap(click_anim_timer, 0,         peak_time,     0, 1), 0, 1)
                local t2 = math.clamp(settings.remap(click_anim_timer, peak_time, peak_time * 2, 0, 1), 0, 1)
                local alpha = settings.remap(t1 - t2, 0, 1, 0.25, 0.6)
                ui.drawImageQuad(
                    settings.get_asset("white"),
                    line_top_center + vec2(0, line_height),
                    line_tl + vec2(0, line_height),
                    line_tl,
                    line_top_center,
                    rgbm(1, 1, 1, alpha)
                )
                if ui.mouseClicked(ui.MouseButton.Left) then
                    click_anim_timer = 0 -- start animation
                    if data_to_draw.car_index == focused then
                        ac.focusCar(0) -- go back to player
                    else
                        ac.focusCar(data_to_draw.car_index) -- switch to the new guy
                    end
                end
            end
            if session.type ~= ac.SessionType.Hotlap then
                ui.drawRectFilled(line_tl + rect_pad, line_tl + rect_pad + rect_size, square_color, rect_radius, ui.CornerFlags.BottomRight)
            end
            
            if texture_color ~= nil then
                ui.drawImageQuad(
                    settings.get_asset("white"),
                    line_top_center + vec2(80, 0) * LeaderboardScale,
                    vec2(line_bot_right.x, line_top_center.y),
                    line_bot_right,
                    vec2(line_top_center.x, line_bot_right.y) + vec2(80, 0) * LeaderboardScale,
                    rgbm(texture_color.r, texture_color.g, texture_color.b, settings.remap(curve_t, 0, 1, 0, 0.55))
                )
            end
            
            -- position number
            local rect_center = line_tl + rect_pad + rect_size / 2
            local number_textsize = ui.measureDWriteText(data_to_draw.position, normal_fontsize)
            local number_pos = rect_center - number_textsize / 2
            if session.type ~= ac.SessionType.Hotlap then
                ui.dwriteDrawText(data_to_draw.position, normal_fontsize, number_pos, colors.BLACK)
            end
            
            local name_text_pos = vec2()
            if session.type == ac.SessionType.Hotlap then
                name_text_pos = line_tl + vec2(session_info_padding.x, text_pad.y)
            else
                name_text_pos = line_tl + vec2(quad_padding * 2 + rect_size.x, 0) + text_pad
                if need_space_for_tags then
                    name_text_pos = name_text_pos + vec2(mc_padding + mc_rect_width, 0)
                end
            end

            -- player name
            ui.pushDWriteFont(nametext_font)
            do
                local line_text = ""
                if Leaderboard_ShowCarNames == false then
                    line_text = ac.getDriverName(data_to_draw.car_index)
                else
                    line_text = ac.getCarName(data_to_draw.car_index)
                end
                
                local driver_name_size = ui.measureDWriteText(line_text, normal_fontsize)
                local space_for_text = max_space_for_drivernames
                
                local scroll_timer = long_names_scroll_timers[data_to_draw.car_index]
                if driver_name_size.x > space_for_text then
                    -- if the text doesn't have enough space, create the data we need
                    if scroll_timer == nil then scroll_timer = 0
                    else                        scroll_timer = scroll_timer + Dt
                    end
                else
                    -- if we have enough space, and we were animating we don't need to anymore
                    if scroll_timer ~= nil then
                        scroll_timer = nil
                    end
                end
                long_names_scroll_timers[data_to_draw.car_index] = scroll_timer
                
                if scroll_timer == nil or Leaderboard_ScrollLongNames == false then
                    -- simple text, we have enough space, no need to scroll it
                    ui.dwriteDrawText(line_text, normal_fontsize, name_text_pos, highlight_color)
                else
                    -- complex text, scroll it back and forth as explained above
                    local gradient_width = 30 * LeaderboardScale
                    local scroll_length = driver_name_size.x - space_for_text + gradient_width
                    local scroll_duration = scroll_length / 20 -- velocity = 20px/s
                    local total_anim_length = 1 + scroll_duration + 3 + scroll_duration + 3
                    local animation_looptime = scroll_timer % total_anim_length
                    
                    local lerp_forward  = settings.animate_lerp(animation_looptime, 1, scroll_duration) -- 1s of NOTHING, then moves forward
                    local lerp_backward = settings.animate_lerp(animation_looptime, 1 + scroll_duration + 3, scroll_duration) -- after moving forward, 3s of nothing then comes back at the same speed
                    
                    local text_pos = name_text_pos - vec2(scroll_length * (lerp_forward - lerp_backward), 0)
                    ui.beginGradientShade()
                    ui.pushClipRect(name_text_pos, name_text_pos + vec2(500, 500))
                    ui.dwriteDrawText(line_text, normal_fontsize, text_pos, colors.WHITE)
                    ui.popClipRect()
                    ui.endGradientShade(name_text_pos + vec2(space_for_text - gradient_width, 0), name_text_pos + vec2(space_for_text, 0), highlight_color, colors.TRANSP, true)
                end
            end
            ui.popDWriteFont()
            
            -- draw multiclass rectangles
            if mc_lerp_t > 0 then -- so we don't take up time while we don't draw them
                local mc_rect_center = rect_center + vec2(rect_size.x / 2 + mc_padding + mc_rect_width / 2, 0)
                local mc_rect_height = line_height - 8 * LeaderboardScale
                local mc_rect_size = vec2(mc_rect_width, mc_rect_height)
                local mc_rect_top = mc_rect_center - mc_rect_size / 2
                local mc_rect_current_height = mc_rect_height * mc_lerp_t
                local mc_rect_bot = mc_rect_top + vec2(mc_rect_width, mc_rect_current_height)
                if data_to_draw.tag_color ~= nil then
                    ui.drawRectFilled(mc_rect_top, mc_rect_bot, data_to_draw.tag_color)
                end
            end

            -- lapcount per player
            ui.pushDWriteFont(lapcount_font)
            local line_tr = line_tl + vec2(board_width, 0)
            local lap_text_tl = line_tr + vec2(-text_pad.x - border_pad - longest_number_width, text_pad.y)
            if to_draw_car_info.isConnected == false then
                local off_textsize = ui.measureDWriteText("OFF", normal_fontsize)
                local off_text_tl = line_tr + vec2(-off_textsize.x - text_pad.x, text_pad.y)
                ui.dwriteDrawText("OFF", normal_fontsize, off_text_tl, highlight_color)
            elseif to_draw_car_info.isRaceFinished then
                local fin_textsize = ui.measureDWriteText("FIN", normal_fontsize)
                local fin_text_tl = line_tr + vec2(-fin_textsize.x - text_pad.x, text_pad.y)
                ui.dwriteDrawText("FIN", normal_fontsize, fin_text_tl, highlight_color)
            elseif to_draw_car_info.isRetired then
                local pit_textsize = ui.measureDWriteText("RET", normal_fontsize)
                local pit_text_tl = line_tr + vec2(-pit_textsize.x - text_pad.x, text_pad.y)
                ui.dwriteDrawText("RET", normal_fontsize, pit_text_tl, highlight_color)
            elseif to_draw_car_info.isInPitlane and session.type ~= ac.SessionType.Hotlap then
                local pit_textsize = ui.measureDWriteText("PIT", normal_fontsize)
                local pit_text_tl = line_tr + vec2(-pit_textsize.x - text_pad.x, text_pad.y)
                ui.dwriteDrawText("PIT", normal_fontsize, pit_text_tl, highlight_color)
            elseif to_draw_car_info.isInPitlane and session.type == ac.SessionType.Hotlap and player_index == 0 then
                local pit_textsize = ui.measureDWriteText("PIT", normal_fontsize)
                local pit_text_tl = line_tr + vec2(-pit_textsize.x - text_pad.x, text_pad.y)
                ui.dwriteDrawText("PIT", normal_fontsize, pit_text_tl, highlight_color)
            else
                local lapcount_textsize = ui.measureDWriteText(lap_count + 1, normal_fontsize)
                local number_text_tl = line_tr + vec2(-lapcount_textsize.x - text_pad.x, text_pad.y)
                ui.dwriteDrawText("L", normal_fontsize, lap_text_tl, highlight_color)
                ui.dwriteDrawText(lap_count + 1, normal_fontsize, number_text_tl, highlight_color)
            end
            
            if Leaderboard_ShowTyres then
                local tire_shortname = nil
                if session.type == ac.SessionType.Hotlap then tire_shortname = data_to_draw.hotlap_compname
                else tire_shortname = ac.getTyresName(data_to_draw.car_index)
                end
                local tire_pos = lap_text_tl + vec2(-text_pad.x * 3 - longest_compound_shortname, 0)
                ui.dwriteDrawText(tire_shortname, normal_fontsize, tire_pos, highlight_color)
            end
            
            if Leaderboard_Relative and session_state ~= ac.SessionType.Hotlap then
                -- only int and last because pos is too confusing
                local interval_center_pos = line_tr + vec2(pad + min_space_for_int / 2, line_height / 2) -- NOTE(cogno): interval text is aligned in the CENTER
                local laptime_size = ui.measureDWriteText(last_lap_text, normal_fontsize)
                local laptime_pos = line_tr + vec2(info_width - laptime_size.x - pad, line_height/2 - table_text_size.y / 2) -- NOTE(cogno): last laptime is aligned on the RIGHT
                
                draw_interval(to_draw_car_info.isRetired, data_to_draw.interval, normal_fontsize, interval_center_pos, highlight_color)
                draw_last_laptime(laptime_pos, last_lap_text, normal_fontsize, laptime_color)
            else
                if session_state == ac.SessionType.Race then
                    -- pos int last
                    local pos_tl              = line_tr + vec2(pad, line_height / 2 - table_text_size.y / 2)
                    local interval_center_pos = line_tr + vec2(pad + min_space_for_pos + sec_gap_padding + min_space_for_int / 2, line_height / 2) -- NOTE(cogno): interval text is aligned in the CENTER
                    local laptime_size = ui.measureDWriteText(last_lap_text, normal_fontsize)
                    local laptime_pos = line_tr + vec2(info_width - laptime_size.x - pad, line_height/2 - table_text_size.y / 2) -- NOTE(cogno): last laptime is aligned on the RIGHT
                    
                    draw_pos(to_draw_car_info.isRetired, data_to_draw.pos_diff, line_tr, pos_tl, line_height, normal_fontsize, highlight_color)
                    draw_interval(to_draw_car_info.isRetired, data_to_draw.interval, normal_fontsize, interval_center_pos, highlight_color)
                    draw_last_laptime(laptime_pos, last_lap_text, normal_fontsize, laptime_color)
                elseif session_state == ac.SessionType.Hotlap then
                    -- sec gap Last
                    local to_draw_best_sector_times = players.get_player_best_sector_times(data_to_draw.car_index)
                    local sector1_pos_cl = line_tr + vec2(pad, line_height / 2)
                    local driver_sector_times = data_to_draw.sector_times
                    local gap_pos     = line_tr + vec2(pad + sectors_space + sec_gap_padding,                         line_height / 2 - table_text_size.y / 2)
                    local laptime_pos = line_tr + vec2(pad + sectors_space + sec_gap_padding * 2 + min_space_for_gap, line_height / 2 - table_text_size.y / 2)
                    
                    draw_sectors(sectors_count, to_draw_best_sector_times, sector1_pos_cl, rect_width, sector_rect_pad, mp_best_sector_times, driver_sector_times)
                    draw_gap(best_laptime, best_of_first, gap_pos, normal_fontsize, highlight_color)
                    draw_last_laptime(laptime_pos, last_lap_text, normal_fontsize, laptime_color)
                elseif session_state == ac.SessionType.Practice or session_state == ac.SessionType.Qualify then
                    -- sec gap best
                    local to_draw_best_sector_times = players.get_player_best_sector_times(data_to_draw.car_index)
                    local sector1_pos_cl = line_tr + vec2(pad, line_height / 2)
                    local driver_sector_times = data_to_draw.sector_times
                    local gap_pos      = line_tr + vec2(pad + sectors_space + sec_gap_padding,                         line_height / 2 - table_text_size.y / 2)
                    local best_lap_pos = line_tr + vec2(pad + sectors_space + sec_gap_padding * 2 + min_space_for_gap, line_height / 2 - table_text_size.y / 2)
                    
                    draw_sectors(sectors_count, to_draw_best_sector_times, sector1_pos_cl, rect_width, sector_rect_pad, mp_best_sector_times, driver_sector_times)
                    draw_gap(best_laptime, best_of_first, gap_pos, normal_fontsize, highlight_color)
                    draw_best_laptime(best_lap_pos, best_laptime, normal_fontsize, highlight_color)
                end
            end

            ui.popDWriteFont()
        end
    end
    click_anim_timer = click_anim_timer + Dt

    local banner_current_size = banner_size:clone()
    local current_time = Time - animation_start_time
    local show = true
    local from_right = true
    local banner_start_pos = corner_tl + vec2(0, header_height + line_count * line_height)
    if show_banner and is_showing_banner then
        -- nothing to do
    elseif show_banner then
        local banner_appear_time = 0.4
        local anim_percent = current_time / banner_appear_time
        if anim_percent >= 1 then is_showing_banner = true end
        banner_current_size.x = math.lerp(0, table_width, math.clamp(anim_percent, 0, 1))
    elseif is_showing_banner then
        local banner_hide_time = 0.4
        local anim_percent = current_time / banner_hide_time
        if anim_percent >= 1 then is_showing_banner = false end
        banner_current_size.x = math.lerp(table_width, 0, math.clamp(anim_percent, 0, 1))
        from_right = false
    else
        show = false -- hide everything
    end
    
    local banner_pos_tl = banner_start_pos + vec2(table_width - banner_current_size.x, 2 * LeaderboardScale)
    if from_right == false then
        banner_pos_tl = banner_start_pos + vec2(0, 2) * LeaderboardScale
    end

    local left_color = colors.DARK_BG
    local right_color = colors.BG
    if line_count % 2 == 0 then
        left_color = colors.BG
        right_color = colors.LIGHT_BG
    end
    
    local line_top_center = banner_start_pos + vec2(board_width, 0)
    
    if show or force_seen then
        ui.pushDWriteFont(fonts.archivo_bold)
        
        -- we have 2 pixels of padding that we make appear/disappear with the notification flag, so we 
        -- can properly space the banner from the squares of the player. If we don't you would see the
        -- last player square with 2 pixels of distance (instead of 4), which kind of sucks
        if from_right == false then
            ui.drawRectFilled(banner_pos_tl, banner_start_pos + banner_current_size, real_flag_background)
            ui.pushClipRect(banner_pos_tl + vec2(0, -2) * LeaderboardScale, banner_start_pos + banner_current_size)
        else
            ui.drawRectFilled(banner_pos_tl, banner_start_pos + banner_size, real_flag_background)
            ui.pushClipRect(banner_pos_tl + vec2(0, -2) * LeaderboardScale, banner_start_pos + banner_size)
        end
        
        ui.drawRectFilled(banner_start_pos, banner_start_pos + vec2(board_width, 2 * LeaderboardScale), left_color)
        ui.drawRectFilled(line_top_center, line_top_center + vec2(info_width, 2 * LeaderboardScale), right_color)
        local chunk_fontsize = settings.fontsize(16) * LeaderboardScale
        local chunk_textwidth = ui.measureDWriteText(real_flag_text, chunk_fontsize)
        local chunk_width = chunk_textwidth.x + 30 * LeaderboardScale
        local banner_textpos = banner_start_pos + vec2((-math.floor(Time * 120 * LeaderboardScale)) % chunk_width, banner_size.y / 2 - chunk_textwidth.y / 2 + 2 * LeaderboardScale)
        local chunks_we_fit = math.ceil(banner_size.x / chunk_width)
        for i=-1, chunks_we_fit-1 do
            local chunk_pos = banner_textpos + vec2(chunk_width * i, 0)
            ui.dwriteDrawText(real_flag_text, chunk_fontsize, chunk_pos, flagtext_color)
        end
        ui.popClipRect()
        ui.popDWriteFont()
    end
    ui.popDWriteFont()
    ui.popClipRect()
    
    players.play_intro_anim(total_center, total_size, on_show_animation_start, LeaderboardScale)
    settings.lock_app(total_center, total_size, APPNAMES.leaderboard, LeaderboardScale)
    settings.auto_scale_window(total_size * 1.01, APPNAMES.leaderboard)
    settings.auto_place_once(total_size, APPNAMES.leaderboard)
end


return module
