-- Script by Dr_Nope#0037

local MAP_ID="AY"


local TICRATE= TICRATE
local V_6WIDTHSPACE= V_6WIDTHSPACE
local V_ALLOWLOWERCASE= V_ALLOWLOWERCASE
local V_30TRANS= V_30TRANS


rawset(_G, "SCORING_MAP", false)

local LEVEL_END_TIMER= 80*TICRATE
local LEVEL_FINAL_DISPLAY_MAX_TIME= 8*TICRATE


local MAP_SCORE_ATTR= MAP_ID.."_score"

local LevelState= {
    ONGOING= 0,
    ENDING= 1,
    ALLDONE= 2
}



local function getmapid(n)
    if n<100 then
        return ((n<10) and ("0"..n) or tostring(n))
    end

    local x= n-100
    local p= x/36
    local q= x-(36*p)
    local a= string.char(string.byte('A')+p)
    local b= (q<10) and tostring(q) or string.char(string.byte('A')+q-10)

    return a..b
end







local Score_Board= nil

local b_map_ok= false

local b_scoreBoard_updated= false


local LevelEndTimer= 0

local HUD_state_save= {
    gametypeinfo= true,
    position= true
}

local HUD_score_track= {
    {0,0},
    {0,0},
    {0,0},
    {0,0}
}




local function manage_hud_item(name, begin)
    begin= (begin==nil) and true or begin

    if begin then
        HUD_state_save[name]= hud.enabled(name)
        hud.disable(name)
    elseif HUD_state_save[name] then
        hud.enable(name)
    end
end

local function allocate_new_score_tab()
    local i= 1
    
    Score_Board= {}
    for p in players.iterate do
        p[MAP_SCORE_ATTR]= 0

        Score_Board[i]= p
        i= $+1 
    end

    manage_hud_item("gametypeinfo")
    manage_hud_item("position")
end

local function deallocate_all()
    Score_Board= nil

    for p in players.iterate do
        p[MAP_SCORE_ATTR]= nil
    end

    if HUD_state_save.gametypeinfo then
        manage_hud_item("gametypeinfo", false)
        manage_hud_item("position", false)
    end

    LevelEndTimer= 0
end

local function add_point_to_player(player, amount)
    amount= (amount==nil) and 1 or amount

    player[MAP_SCORE_ATTR]= ($==nil and amount or ($+amount))

    local i=1
    for dp in displayplayers.iterate do
        if dp==player then
            HUD_score_track[i]= {amount, 2*TICRATE}
            break
        end
        i= $+1
    end
end

local function update_score_board()
    if Score_Board==nil then return end

    Score_Board= {}

    local i=1
    for p in players.iterate do
        if not p.valid then continue end 

        if p[MAP_SCORE_ATTR]==nil then
            p[MAP_SCORE_ATTR]= 0
        end

        Score_Board[i]= p
        i= $+1
    end

    table.sort(Score_Board,
        function(p1,p2)
            return p1[MAP_SCORE_ATTR]>p2[MAP_SCORE_ATTR]
        end
    )

    b_scoreBoard_updated= true
end

local function getMaxScore()
    local i= 1
    local m= (Score_Board[i][MAP_SCORE_ATTR] or 0)

    while i<#Score_Board do
        i= $+1
        m= ((Score_Board[i][MAP_SCORE_ATTR] or 0)>m and Score_Board[i][MAP_SCORE_ATTR] or m)
    end

    return m
end

local function getLevelState()
    local exiting_count= 0
    local players_count= 0
    for p in players.iterate do
        players_count= $+1
        if (not p.spectator) or p.valid then
            exiting_count= $+((p.exiting or (p.pflags & PF_TIMEOVER)) and 1 or 0)
        end
    end

    return (players_count==exiting_count)
            and LevelState.ALLDONE
            or  (exiting_count>0
                    and LevelState.ENDING
                    or  LevelState.ONGOING )
end

local function update_realtimes()
    update_score_board()

    local m= getMaxScore()

    local previous_score, saved_pos= 9999, 1
    for pos,player in pairs(Score_Board) do
        if (player[MAP_SCORE_ATTR]<previous_score) then
            previous_score= player[MAP_SCORE_ATTR]
            saved_pos= ((pos<=$) and $ or ($+1))
        end
        player.realtime= (player[MAP_SCORE_ATTR]~=nil and player[MAP_SCORE_ATTR]>0)
                    and (saved_pos*60*TICRATE+((player[MAP_SCORE_ATTR]%60)*TICRATE))
                    or  (-1)
    end
end

local function level_end()
    update_realtimes()

    for player in players.iterate do
        player.exiting= 1
        if player[MAP_SCORE_ATTR]==nil then
            p.realtime= -1
        end
    end
end

local function level_final_display()
    for player in players.iterate do
        player.exiting= 1
        
        player.pflags= $ & (~PF_TIMEOVER)
        player.lives= 1
    end

    update_realtimes()

    if LevelEndTimer>=(LEVEL_END_TIMER+LEVEL_FINAL_DISPLAY_MAX_TIME) then
        G_ExitLevel()
    end
end







local LineDef_Scores= {}
local LineDef_scoreDistributionCursor= {}

local function fetch_pointsForLineDef(name)
    local cur= LineDef_scoreDistributionCursor[name]
    local score= LineDef_Scores[name][cur]

    cur= ($<#(LineDef_Scores[name])) and ($+1) or $
    LineDef_scoreDistributionCursor[name]= cur

    return score
end

local function set_lineDef_scoring(name, scores_distrib)
    LineDef_Scores[name]= scores_distrib
    LineDef_scoreDistributionCursor[name]= 1

    addHook("LinedefExecute", function(line, mobj, sector)
        local player= mobj.player
        if player==nil or (not player.valid) then return end

        add_point_to_player(player, fetch_pointsForLineDef(name))
        -- print("player "..player.name.." has now "..(player[MAP_SCORE_ATTR]==nil and "nil" or player[MAP_SCORE_ATTR]).." pts")
        update_score_board()
    end, name)
end
local function reinit_scoringDistribCursors()
    for name,cursor in pairs(LineDef_scoreDistributionCursor) do
        LineDef_scoreDistributionCursor[name]= 1
    end
end

for i=1,7 do
    set_lineDef_scoring("WADEP"..i, {2,1})
end
set_lineDef_scoring("WADESOLO", {1})
set_lineDef_scoring("WADEEND", {3,2,1})








addHook("MapLoad", function(mapid)
    deallocate_all()
    b_map_ok= (getmapid(mapid)==MAP_ID)
    SCORING_MAP= b_map_ok
    if b_map_ok then
        reinit_scoringDistribCursors()
        LevelEndTimer= 0
    end
end)

addHook("ThinkFrame", function()

    if b_map_ok then
        local lvlState= getLevelState()

        if leveltime==1 then
            allocate_new_score_tab()
            LevelEndTimer= 0
        elseif lvlState==LevelState.ENDING then
            if LevelEndTimer==LEVEL_END_TIMER then
                level_end()
            end
            LevelEndTimer= ($>=0) and ($+1) or $
        elseif lvlState==LevelState.ALLDONE then
            level_final_display()

            LevelEndTimer= ($<LEVEL_END_TIMER) and (LEVEL_END_TIMER+1) or ($+1)
        elseif leveltime>1 then
            if not b_scoreBoard_updated then
                update_score_board()
            end

            for p in players.iterate do
                if p[MAP_SCORE_ATTR]==nil then
                    p[MAP_SCORE_ATTR]= 0
                end
            end
        end
    elseif Score_Board~=nil then
        deallocate_all()

    end

end)

addHook("NetVars", function(net)
    Score_Board= net(Score_Board)
    LevelEndTimer= net(LevelEndTimer)
    HUD_state_save= net(HUD_state_save)
end)

addHook("PlayerQuit", function(player, reason)
    b_scoreBoard_updated= false
end)





local HUD_stuff= {
    dupadjust= {x=320, y=200},
    duptweak= {x=0, y=0},

    patches= {
        lapn_bk= nil
    }
}

local function update_HUD_stuff(v)
    HUD_stuff.dupadjust = {x=(v.width()/v.dupx()), y=(v.height()/v.dupy())}
    HUD_stuff.duptweak = {x=((HUD_stuff.dupadjust.x - 320)/2), y=((HUD_stuff.dupadjust.y - 200)/2)}

    if HUD_stuff.patches.lapn_bk==nil then
        HUD_stuff.patches.lapn_bk= v.cachePatch("K_STLAPN")
    end
end

local function HUD_displayScoreBoard(v)
    v.fadeScreen(65280, 16)	


	v.drawString(160, 12, "* SCORES *", 0, "center")
    v.drawFill(1-HUD_stuff.duptweak.x, 26, HUD_stuff.dupadjust.x-2, 1, 0)

    local i=1
    local previous_score, saved_pos= 9999, 1
    for pos,player in pairs(Score_Board) do
        local name= player.name or "NULL"
        local y= 23+i*10

        v.drawString(100, y, name, V_6WIDTHSPACE|V_ALLOWLOWERCASE, "thin")

        local points= (player[MAP_SCORE_ATTR] or 0)
        if (points<previous_score) then
            previous_score= points
            saved_pos= ((pos<=$) and $ or ($+1))
        end
        v.drawString(40, y, ""..saved_pos..".", V_6WIDTHSPACE|V_ALLOWLOWERCASE, "thin")
        v.drawString(280, y, ""..points.." pts", V_6WIDTHSPACE|V_ALLOWLOWERCASE, "thin-right")

        i= $+1
    end
end

local function _HUD_displayBasePlayerScoreAt(v,displayPlayernum,x,y)
    local patch= HUD_stuff.patches.lapn_bk

    local i, player= 1, nil
    for dp in displayplayers.iterate do
        if i==displayPlayernum then
            player= dp
            break
        end
        i= $+1
    end

    local pts= player[MAP_SCORE_ATTR]
    local str_pts= (pts==nil) and "-" or (""..pts)
    local l_str_pts= #str_pts
    local prefix= (pts==0) and "" or ((pts<0) and "-" or "+")
    
    if splitscreen<2 then
        v.draw(x, y, patch)
        v.drawString(x+31, y+7, "pts", V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_30TRANS)
        v.drawKartString(x+17-((l_str_pts-1)*11), y+3, str_pts)

        if HUD_score_track[displayPlayernum][2]>0 and HUD_score_track[displayPlayernum][1] then
            v.drawString(x+28,y+1-(2*TICRATE-HUD_score_track[displayPlayernum][2])/4,
                prefix..HUD_score_track[displayPlayernum][1], V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_30TRANS
            )

            HUD_score_track[displayPlayernum][2]= $-1
        end
    else
        v.drawString(x+14, y, "pts", V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_30TRANS, "thin")
        v.drawString(x+7-((l_str_pts-1)*9),y,str_pts)

        if HUD_score_track[displayPlayernum][2]>0 and HUD_score_track[displayPlayernum][1] then
            v.drawString(x+10,y+1-(2*TICRATE-HUD_score_track[displayPlayernum][2])/4,
                prefix..HUD_score_track[displayPlayernum][1], V_6WIDTHSPACE|V_ALLOWLOWERCASE|V_30TRANS, "thin"
            )

            HUD_score_track[displayPlayernum][2]= $-1
        end
    end

end

local function HUD_displayPlayerScore(v, player)
    -- gfx.kart - K_STLAPN
    if splitscreen<2 then
        for i=0,splitscreen do
            _HUD_displayBasePlayerScoreAt(v,i+1,9,71+(1-splitscreen+i)*100)
        end
    else
        for i=0,splitscreen do
            _HUD_displayBasePlayerScoreAt(v,i+1,(i%2)*276+4,88+(i/2)*100)
        end
    end
end


local LEVEL_START_TIME= (6*TICRATE + (3*TICRATE/4))

hud.add(function(v, player, cam)
    if (not b_map_ok) or (not player.valid) or player.spectator then return end

    update_HUD_stuff(v)

    if (player.exiting and (not splitscreen)) or getLevelState()==LevelState.ALLDONE then
        HUD_displayScoreBoard(v)
    elseif leveltime>=(LEVEL_START_TIME-4*TICRATE) then
        HUD_displayPlayerScore(v, player)
    end
end)







--- FOR test

-- COM_AddCommand("increment_score", function(p, n, ...)
--     n= tonumber(n)
--     n= (not n) and 1 or n
--     local player= p
--     if splitscreen then
--         local i= 1
--         for dp in displayplayers.iterate do
--             if i==n then
--                 player= dp
--                 break
--             end
--             i= $+1
--         end
--     end
--     add_point_to_player(player)
--     print("player "..player.name.." has now "..(player[MAP_SCORE_ATTR]==nil and "nil" or player[MAP_SCORE_ATTR]).." pts")
--     update_score_board()
-- end)

-- COM_AddCommand("print_score_board", function(p, ...)
--     if Score_Board==nil then
--         print("Error: nil scoreboard")
--         return
--     end

--     for k,v in pairs(Score_Board) do
--         print("["..k.."] "..v.name.." ("..v[MAP_SCORE_ATTR].."pts)")
--     end
-- end)


-- COM_AddCommand("final_increment", function(p, ...)
--     add_point_to_player(p)
    
--     LevelEndTimer= LEVEL_END_TIMER
--     level_end()
-- end)




