bb_loop = {}
local storage = minetest.get_mod_storage()

bb_loop.leaderboard = minetest.deserialize(storage:get_string("leaderboard")) or {}

-- settings


bb_loop.debug = false
bb_loop.loading_time = 7 -- time for loading phase
bb_loop.celebrate_time = 5 --- time for celebration phase
bb_loop.suddendeath_timer = 180 -- time for regular gameplay until the game gives a little hint to hurry up

-- Global Vars declared for reference and init
bb_loop.winner = nil
bb_loop.gamestate = "waiting"
bb_loop.suddendeath = false
bb_loop.firstload = true
bb_loop.waitingtimer = 0 
bb_loop.loadingtimer = 0
bb_loop.runningtimer = 0
bb_loop.celebratetimer = 0
bb_loop.current_arena = nil
bb_loop.loopcounter = 1 -- a counter that is unique for each iteration of the game


-- Functions init (defined at end of file in order)

-- must always return a number, the number of connected players. cannot assume that there are any, and should return 0
bb_loop.get_player_count = function() end
bb_loop.clear_players_inv = function() end
bb_loop.get_alive_playercount = function() return 0 end
bb_loop.show_waiting_formspec = function(p_name) end
-- sets the board and moves players there
bb_loop.Start = function()  end
--should return nil if no winner, and winner name if winner
bb_loop.check_for_winner = function() return nil end
-- function to get the inventory formspec, overridden in bb_player, but callable here
bb_loop.get_inv_formspec = function(p_name) return "" end
-- returns the staus of the game for player huds (used in bb_player)
function bb_loop.get_hud_status() end


--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- waiting globalstep

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



minetest.register_globalstep(function(dtime)




    -- this globalstep only applies to the waiting phase
    if bb_loop.gamestate ~= "waiting" then return end
    
    -- keep track of waiting time, may be useful
    bb_loop.waitingtimer = bb_loop.waitingtimer + dtime

    local player_count = bb_loop.get_player_count()

    if bb_loop.debug then
        minetest.log("PLAYER_COUNT(1):".. player_count)
        minetest.log("Current_state(1):".. bb_loop.gamestate)
    end

    if player_count == 0 then return end

    if player_count == 1 then
        -- just let them know that we are waiting ...
        for _ , player in ipairs(minetest.get_connected_players()) do
            local p_name = player:get_player_name()
            bb_loop.show_waiting_formspec(p_name)
        end
    end

    if player_count > 1 then 
        --set up the next stage
        bb_loop.gamestate = "loading"
        bb_loop.waitingtimer = 0
        --close player waiting formspec
        for _ , player in ipairs(minetest.get_connected_players()) do
            local p_name = player:get_player_name()
            minetest.close_formspec(p_name, "")
        end
    end
end)


--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- loading globalstep

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


minetest.register_globalstep(function(dtime)

    -- reset the firstrun indicator
    if bb_loop.loadingtimer == 0 then
        bb_loop.firstload = true
    end
    -- this globalstep only applies to the loading phase
    if bb_loop.gamestate ~= "loading" then return end

    -- keep track of loading time, may be useful
    bb_loop.loadingtimer = bb_loop.loadingtimer + dtime

    if bb_loop.debug then
        minetest.log("loadingtimer=".. bb_loop.loadingtimer)
        
    end
    
    if bb_loop.loadingtimer > bb_loop.loading_time then
        --loading is over, reset and move on to starting phase
        bb_loop.loadingtimer = 0
        bb_loop.gamestate = "running"
        -- prep the first run toggle for the running loop
        bb_loop.first_run = true
        bb_loop.first_sd = true
        if bb_loop.debug then
            minetest.log("error","Got to where it should move on...")
            
        end

    end

    if bb_loop.firstload == true then

        bb_loop.firstload = false


        -- run the start function, will also update inv formspecs

        bb_loop.Start()

    end
    
end)





--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- running globalstep

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


minetest.register_globalstep(function(dtime)

    -- this globalstep only applies to the running phase
    
    if bb_loop.gamestate ~= "running" then return end

    if bb_loop.first_run == true then

        --update players' inv
        for _,player in pairs(minetest.get_connected_players()) do
            local p_name = player:get_player_name()
            player:set_inventory_formspec(bb_loop.get_inv_formspec(p_name))
        end
        bb_loop.first_run = false

    end

    -- keep track of running time, start suddendeath if we reach the timer
    bb_loop.runningtimer = bb_loop.runningtimer + dtime

    -- check for winners
    bb_loop.winner = bb_loop.check_for_winner()
    if bb_loop.runningtimer > bb_loop.suddendeath_timer then
        bb_loop.suddendeath = true
        --set in last phase, only do this once
        if bb_loop.first_sd then
            bb_loop.first_sd = false
            for _,player in pairs(minetest.get_connected_players()) do
                minetest.sound_play({
                    name = "alarm",
                    gain = 0.1,
                }, {
                    to_player = player:get_player_name(),        
                    fade = 0.0,   -- default, change to a value > 0 to fade the sound in
                    pitch = 1.0,  -- default
                }, true)
            end
        end

    end



    if bb_loop.winner then
        bb_loop.gamestate = "celebrate"
        minetest.after(0.1,function()
            
            local player_color = bb_player.saved_colors[bb_loop.winner]
            if player_color then
                minetest.chat_send_all(minetest.colorize(player_color,bb_loop.winner .. " wins the game!"))
            elseif bb_loop.winner then
                minetest.chat_send_all(bb_loop.winner .. " wins the game!")
                local sppos = vector.new(10,10,10)
                if bb_loop.current_arena then
                    sppos = bb_loop.current_arena.spectator_pos
                end
                local player = minetest.get_player_by_name(bb_loop.winner)
                if player then
                    local att = minetest.add_entity(sppos, "bb_player:att")
                    player:set_attach(att)
                end

                -- TODO: make a winning dance anim and implement it, maybe in bb_player instead
            end

            --keep track of who wins in the leaderboard table
            if bb_loop.winner ~= nil and bb_loop.winner ~= "" then
                if bb_loop.leaderboard[bb_loop.winner] then
                    bb_loop.leaderboard[bb_loop.winner] = bb_loop.leaderboard[bb_loop.winner] + 1
                else
                    bb_loop.leaderboard[bb_loop.winner] = 1
                end
                storage:set_string("leaderboard", minetest.serialize(bb_loop.leaderboard))
            end

        end)
        
        bb_loop.runningtimer = 0
        bb_loop.suddendeath = false
    end



end)






--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- celebrate globalstep

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

local celebrate_first_time = true
minetest.register_globalstep(function(dtime)

    -- this globalstep only applies to the celebrate phase
    if bb_loop.gamestate ~= "celebrate" then return end

    -- clear items from board
    if celebrate_first_time then

        -- increment the loop counter
        bb_loop.loopcounter = bb_loop.loopcounter + 1

        -- clear board
        for _,obj in pairs(minetest.get_objects_inside_radius(vector.new(0,0,0), 200)) do

            local ent = obj:get_luaentity()
            if ent and ent.name == "__builtin:item" then
                obj:remove()
            end
        end
        celebrate_first_time = false
    end

    -- keep track of celebrate time, stop the celebration at the end
    bb_loop.celebratetimer = bb_loop.celebratetimer + dtime

    -- TODO: add more features here like highscore and displaying the winner to all players

    if bb_loop.celebratetimer > bb_loop.celebrate_time then
        bb_loop.winner = nil
        bb_loop.celebratetimer = 0
        minetest.after(0,function()
            --set gamestate to waiting
            bb_loop.gamestate = "waiting"
            --update players inv
            for _,player in pairs(minetest.get_connected_players()) do
                local p_name = player:get_player_name()
                player:set_inventory_formspec(bb_loop.get_inv_formspec(p_name))
            end
            
        end)
        
        celebrate_first_time = true
    end

end)



-- item jump globalstep
local jumptimer = 0

minetest.register_globalstep(function(dtime)
    if not(bb_loop.gamestate == "running" ) then return end
    jumptimer = jumptimer + dtime 
    if jumptimer > 1 then 
        jumptimer = 0

        for id, ent in pairs(minetest.luaentities) do 
            if ent.name == "__builtin:item" then
                ent.object:add_velocity(vector.new(0,2,0))

            end
        end
    end

end)



--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- Functions Defined

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



-- returns the number of connected players
-- must always return a number, the number of connected players. cannot assume that there are any, and should return 0
bb_loop.get_player_count = function() 
    local count = 0
    local connected = minetest.get_connected_players()
    if connected then
        if #connected then
            count = #connected
        end
    end
    return count
end


--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


-- clears the inventory of all players online
bb_loop.clear_players_inv = function() 

    local connected = minetest.get_connected_players()
    for _ , player in ipairs( connected ) do
        local p_meta = player:get_meta()
        if p_meta:get_string("status") ~= "admin" then
            local inv = player:get_inventory()
            local list = inv:get_list("main")
            for k, v in pairs(list) do
                inv:remove_item("main", v)
            end
            inv:add_item("main", ItemStack("bb_powerup:extra_bomb")) 
            --inv:add_item("main", ItemStack("bb_powerup:extra_speed"))
            inv:add_item("main", ItemStack("bb_powerup:extra_power"))
        end
    end

end


--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


-- returns the # of 
-- alive players as well as the player name of an alive player (will be the winner if #==1)
bb_loop.get_alive_playercount = function() 
    local count = 0
    local name = ""
    for _, player in pairs(minetest.get_connected_players()) do
        local pmeta = player:get_meta()
        local p_status = pmeta:get_string("status")

        if bb_loop.debug then
            minetest.log("error","P_status: "..p_status)
        end

        if p_status == "playing" then
            count = count + 1
            name = player:get_player_name()
        end
    end

    return count, name
end


--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


-- TODO: show a formspec to a single player waiting, noncritical
bb_loop.show_waiting_formspec = function(p_name) 
    return
end




--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%



bb_loop.Start = function()  

    -- clear their inventories to reset any powerups
    bb_loop.clear_players_inv()

    -- update the inv formspec
    for _,player in pairs(minetest.get_connected_players()) do
        local p_name = player:get_player_name()
        player:set_inventory_formspec(bb_loop.get_inv_formspec(p_name))
    end

    
    -- clear the board of items
    for _,obj in pairs(minetest.get_objects_inside_radius(vector.new(0,0,0), 200)) do

        local ent = obj:get_luaentity()
        if ent and ent.name == "__builtin:item" then
            obj:remove()
        end
    end
    -- choose the arena

    local player_count = bb_loop.get_player_count()
    if bb_loop.debug then
        minetest.log("PLAYER_COUNT (2):"..player_count)
        minetest.log("Current_state(2):".. bb_loop.gamestate)
    end
    local arena = bb_schems.get_arena_by_playercount( player_count )
    bb_loop.current_arena = arena
    local arena_name = arena.name
    local arena_spectator_pos = arena.spectator_pos
    local start_locations = arena.spawns

    -- move players to the spectator pos
    for _, player in pairs(minetest.get_connected_players()) do
        local att = player:get_attach()
        if att then 
            att:set_pos(arena_spectator_pos)
        else
            local att = minetest.add_entity(arena_spectator_pos, "bb_player:att")
            player:set_attach(att)

        end
    end
    -- place the arena at the origin
    bb_schems.place_arena( arena_name )

    -- move the players to their starting locations after a time, mark them as playing
    minetest.after(bb_loop.loading_time - 2, function(start_locations)
        local len = #start_locations
        local iter = 1
        for _, player in ipairs(minetest.get_connected_players()) do 
            
            player:set_detach()
            --the iter is required to set extra players at spawn locations (tho that wont happen anyways)
            
            -- they need to be a little off the floor so they dont fall thru it
            local spawn_loc = vector.add(start_locations[iter],vector.new(0,0.1,0))
            local p_name = player:get_player_name()
            minetest.after(0.1,function(p_name)
                local player = minetest.get_player_by_name(p_name)
                if not player then return end
                player:set_pos( spawn_loc )
            end,p_name)
            
            iter = iter + 1
            if iter > len then 
                iter = 1
            end


            -- mark the player as playing
            local pmeta = player:get_meta()
            pmeta:set_string("status","playing")
            pmeta:set_string("spawn", minetest.serialize( spawn_loc ) )

        end
    
    end,start_locations)

end














--should return nil if no winner yet, "" if everyone is dead and no winner,and winner name if winner there is
bb_loop.check_for_winner = function() 
    
    local number_alive, a_name = bb_loop.get_alive_playercount()
    
    if bb_loop.debug then
        minetest.log("error","NUMBER ALIVE: "..number_alive)
        minetest.log("error","a_name: "..a_name)
    end

    if number_alive == 1 then
        return a_name
    end
    if number_alive == 0 then
        return ""
    end
    return nil 

end




function bb_loop.get_hud_status()
    local status = ""
    if bb_loop.gamestate == "waiting" then
        status = "WAITING FOR PLAYERS"
    end
    if bb_loop.gamestate == "loading" then
        status = "LOADING"
    end
    if bb_loop.gamestate == "running" then
        if bb_loop.suddendeath == false then
            local timer = bb_loop.suddendeath_timer - bb_loop.runningtimer
            local timermin = timer / 60
            local min = math.floor(timermin)
            local timersec = (timermin - min) * 60
            local sec = math.floor(timersec)
            local timerms = (timersec - sec) * 100
            local ms = math.floor(timerms)
            if string.len(tostring(min)) == 1 then
                min = "0".. tostring(min)
            end
            if string.len(tostring(sec)) == 1 then
                sec = "0".. tostring(sec)
            end
            if string.len(tostring(ms)) == 1 then
                ms = tostring(ms) .. "0"
            end
            status = tostring(min)..":"..tostring(sec)..":"..tostring(ms)
        else
            status = "SUDDEN DEATH"
        end
    end
    if bb_loop.gamestate == "celebrate" then
        status = "GAME OVER"
        if bb_loop.winner then
            status = bb_loop.winner .. " WINS!"
        end
    end
    return status
end
    




