local battleGeneral = require("scripts/battleGeneral")
local battlePlayer = require("scripts/battlePlayer")

local onlinePlayPlayers = require("scripts/onlinePlay_players")
local onlinePlay = require("scripts/onlinePlay")

local blockRespawning = {}


blockRespawning.justHitBlocks = {}
blockRespawning.blocksToRespawn = {}


blockRespawning.blockIDRespawnTimes = {
    [90] = -1,
    [282] = -1,
    [283] = -1,
}

blockRespawning.defaultRespawnTime = 64*60
blockRespawning.maxPlayersRespawnTimeMultiplier = 0.2

blockRespawning.modeTimeMultipliers = {
    [battleGeneral.gameMode.BOMBS] = 0.2,
}


local respawnBlockCommand = onlinePlay.createCommand("blockRespawning_respawn",onlinePlay.IMPORTANCE_MAJOR)
local playerExplodeCommand = onlinePlay.createCommand("blockRespawning_splodePlayer",onlinePlay.IMPORTANCE_MAJOR)


local function spawnPoofEffects(minX,minY,maxX,maxY)
    local effectGap = 32
    local effectCountX = math.max(1,math.floor((maxX - minX)/effectGap + 0.5))
    local effectCountY = math.max(1,math.floor((maxY - minY)/effectGap + 0.5))

    for effectIndexX = 1,effectCountX do
        for effectIndexY = 1,effectCountY do
            local effectX = (minX + maxX)*0.5
            local effectY = (minY + maxY)*0.5

            if effectCountX > 1 then
                effectX = effectX + effectGap*((effectIndexX - 1)/(effectCountX - 1) - 0.5)
            end

            if effectCountY > 1 then
                effectY = effectY + effectGap*((effectIndexY - 1)/(effectCountY - 1) - 0.5)
            end

            Effect.spawn(10,effectX - 16,effectY - 16)
        end
    end
end


local function getRespawnTime(block)
    local time = blockRespawning.blockIDRespawnTimes[block.id] or blockRespawning.defaultRespawnTime

    if time < 0 then
        return -1
    end

    local playerCount = battlePlayer.getActivePlayerCount()

    if playerCount > 2 then
        -- Reduce respawn time for more than 2 players
        time = time*math.lerp(1,blockRespawning.maxPlayersRespawnTimeMultiplier, math.invlerp(2,battleGeneral.maxOnlinePlayers,playerCount))
    end

    time = time*(blockRespawning.modeTimeMultipliers[battleGeneral.mode] or 1)

    return time
end

local function setAsBlockToRespawn(block)
    if table.icontains(blockRespawning.blocksToRespawn,block) then
        return
    end

    local respawnTime = getRespawnTime(block)
    if respawnTime < 0 then
        return
    end

    table.insert(blockRespawning.blocksToRespawn,{
        block = block,
        respawnTimer = respawnTime,
        obstructedTimer = 0,
    })
end

local function respawnBlock(block)
    local makePoofEffects = false

    -- Restore layer
    if block.layerName == "Destroyed Blocks" then
        block.layerName = "Default"
    end

    -- Set hidden, if necessary
    if block.isHidden and not block.layerObj.isHidden then
        block.isHidden = false
        makePoofEffects = true
    end

    -- Restore ID and contents
    local originalID = block:mem(0x06,FIELD_WORD)

    if block.id ~= originalID then
        block.id = originalID
        makePoofEffects = true
    end

    block.contentID = block:mem(0x08,FIELD_WORD)

    -- Spawn poofs
    if makePoofEffects then
        spawnPoofEffects(block.x,block.y,block.x + block.width,block.y + block.height)
    end

    blockRespawning.onBlockRespawn(block)
end

function respawnBlockCommand.onReceive(sourcePlayerIdx, blockIdx)
    if sourcePlayerIdx ~= onlinePlay.hostPlayerIdx then
        return
    end

    respawnBlock(Block(blockIdx))
end


local function hitHadEffect(blockEntry)
    local block = blockEntry.block

    return (
        block.layerName == "Destroyed Blocks"

        or blockEntry.id ~= block.id
        or blockEntry.contentID ~= block.contentID
        or blockEntry.invisible ~= block:mem(0x5A,FIELD_BOOL)

        or blockEntry.hitStart ~= block:mem(0x52,FIELD_WORD)
        or blockEntry.hitEnd ~= block:mem(0x54,FIELD_WORD)

        --or (block:mem(0x58,FIELD_BOOL) and not blockEntry.hitFlag)
    )
end

function blockRespawning.onPostBlockHit(block,fromTop,playerObj)
    if table.icontains(blockRespawning.justHitBlocks,block) then
        return
    end

    table.insert(blockRespawning.justHitBlocks,{
        block = block,

        id = block.id,
        contentID = block.contentID,
        invisible = block:mem(0x5A,FIELD_BOOL),

        hitStart = block:mem(0x52,FIELD_WORD),
        hitEnd = block:mem(0x54,FIELD_WORD),
    })
end

function blockRespawning.onPostBlockRemove(block,makeEffects,delete)
    block:mem(0x02,FIELD_WORD,0)

    if delete then
        return
    end

    setAsBlockToRespawn(block)
end


local function respawnIsObstructed(block)
    for _,p in ipairs(Player.getIntersecting(block.x - 64,block.y - 64,block.x + block.width + 64,block.y + block.height + 64)) do
        if battlePlayer.getPlayerIsActive(p) then
            return true
        end
    end

    return false
end

local function explodePlayer(p)
    if onlinePlayPlayers.ownsPlayer(p) then
        Defines.earthquake = 4
        SFX.play(43)
        p:kill()
    end

    Effect.spawn(69,p.x + p.width*0.5,p.y + p.height*0.5)
end

local function killNuisancePlayers(block) -- no cheese!
    for _,p in ipairs(Player.getIntersecting(block.x,block.y,block.x + block.width,block.y + block.height)) do
        if p.forcedState == FORCEDSTATE_NONE then
            if onlinePlay.currentMode == onlinePlay.MODE_HOST then
                playerExplodeCommand:send(0,p.idx)
            end

            explodePlayer(p)
        end
    end
end

function playerExplodeCommand.onReceive(sourcePlayerIdx, playerIdx)
    if sourcePlayerIdx ~= onlinePlay.hostPlayerIdx then
        return
    end

    local p = Player(playerIdx)

    explodePlayer(p)
end


local function updateRespawningBlock(blockEntry)
    local block = blockEntry.block

    if not block.isValid then
        return true
    end

    block:mem(0x02,FIELD_WORD,0)

    if onlinePlay.currentMode == onlinePlay.MODE_CLIENT then
        return true
    end

    blockEntry.respawnTimer = math.max(0,blockEntry.respawnTimer - 1)
    --Text.print(blockEntry.respawnTimer,block.x - camera.x,block.y - camera.y)

    if blockEntry.respawnTimer <= 0 then
        if respawnIsObstructed(block) then
            blockEntry.obstructedTimer = blockEntry.obstructedTimer + 1

            if blockEntry.obstructedTimer >= 256 and block.isHidden then
                blockEntry.obstructedTimer = 1
                killNuisancePlayers(block)
            end

            return false
        elseif blockEntry.obstructedTimer > 0 then
            blockEntry.respawnTimer = math.max(0,getRespawnTime(block)*0.5)
            blockEntry.obstructedTimer = 0

            return false
        end

        if onlinePlay.currentMode == onlinePlay.MODE_HOST then
            respawnBlockCommand:send(0,block.idx)
        end

        respawnBlock(block)
        return true
    end

    return false
end

function blockRespawning.onTickEnd()
    -- Blocks that were just hit
    local i = 1

    while (blockRespawning.justHitBlocks[i] ~= nil) do
        local blockEntry = blockRespawning.justHitBlocks[i]
        local block = blockEntry.block

        if block.isValid then
            if hitHadEffect(blockEntry) then
                setAsBlockToRespawn(block)
            end

            block:mem(0x02,FIELD_WORD,0)
        end

        blockRespawning.justHitBlocks[i] = nil
        i = i + 1
    end

    -- Respawning
    local i = 1

    while (blockRespawning.blocksToRespawn[i] ~= nil) do
        if updateRespawningBlock(blockRespawning.blocksToRespawn[i]) then
            table.remove(blockRespawning.blocksToRespawn,i)
        else
            i = i + 1
        end
    end
end


function blockRespawning.onInitAPI()
    registerEvent(blockRespawning,"onPostBlockHit")
    registerEvent(blockRespawning,"onPostBlockRemove")
    registerEvent(blockRespawning,"onTickEnd")

    registerCustomEvent(blockRespawning,"onBlockRespawn")
end


return blockRespawning