-- note: this depends on bb_loop because we need to know when suddendeath is 
-- (bb_loop.suddendeath = true) for the abm to place bombs
bb_bomb = {}

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

--settings

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
bb_bomb.debug = false
bb_bomb.bombtimer = 3.5 -- time to explosion in sec
bb_bomb.explosiontimer = .5 -- time that an explosion ent lasts
bb_bomb.visual_size = {x = 4.5, y = 4.5} -- visual size for bomb ent
bb_bomb.visual_size2m = 1.1 -- visual size multiplier for bomb ent every sec, expands and shrinks to and from this
bb_bomb.brick_drop_chance = 60  -- chance out of 100 that a brick will drop a powerup
bb_bomb.powerups = { -- a list of powerups to drop from explosions
    "bb_powerup:extra_bomb",
    "bb_powerup:extra_bomb",
    "bb_powerup:extra_bomb",
    "bb_powerup:extra_bomb",
    "bb_powerup:extra_bomb",

    "bb_powerup:extra_speed",
    "bb_powerup:extra_speed",
    "bb_powerup:extra_speed",
    "bb_powerup:extra_speed",
    "bb_powerup:extra_speed",

    "bb_powerup:extra_power",
    "bb_powerup:extra_power",
    "bb_powerup:extra_power",
    "bb_powerup:extra_power",
    "bb_powerup:extra_power",

    "bb_powerup:extra_life",
    "bb_powerup:extra_life",
    "bb_powerup:extra_life",

    "bb_powerup:throw",
    "bb_powerup:throw",
    "bb_powerup:throw",
    "bb_powerup:throw",
    "bb_powerup:throw",

    "bb_powerup:kick",
    "bb_powerup:kick",
    "bb_powerup:kick",
    "bb_powerup:kick",
    "bb_powerup:kick",

    "bb_powerup:resurect",

    "bb_powerup:superfast",
    "bb_powerup:superfast",
    "bb_powerup:superfast",

    "bb_powerup:slow",
    "bb_powerup:slow",
    "bb_powerup:slow",

    "bb_powerup:weird_control",
    "bb_powerup:weird_control",

    "bb_powerup:no_placebomb",
    "bb_powerup:no_placebomb",
    "bb_powerup:no_placebomb",

    "bb_powerup:party",
    "bb_powerup:party",
    "bb_powerup:party",

    "bb_powerup:multidir",
    "bb_powerup:multidir",
    
}


-- note that nodes have groups that identify what to do with then during and explosion


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

-- the explosion entity

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


minetest.register_entity("bb_bomb:explosion",{
    initial_properties = {
        physical = false,
        use_texture_alpha = true,        
        collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
        visual = "sprite",
        textures = {"bb_bomb_explosion.png"},
        visual_size = {x = 1, y = 1, z = 1},
        static_save = false,
    },
    on_step = function(self, dtime, moveresult)

        self._timer = self._timer + dtime
        if self._timer > bb_bomb.explosiontimer then
            self.object:remove()
        end
    
    end,

    _timer = 0,

})




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

-- local Explosion function

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

-- "explodes" a single node, used by the explosion function
-- input: pos (p)
-- returns: true if the location p was a node (further explosion should be blocked), false otherwise 
function bb_bomb.local_explode(p)

    -- add the explosion ent
    minetest.add_entity(p, "bb_bomb:explosion" )
    -- remove powerups
    local pos1 = vector.add(p, vector.new(-.6,0,-.6))
    local pos2 = vector.add(p, vector.new(.6,0,.6))

    local objs = minetest.get_objects_in_area(pos1, pos2)
    local objs = minetest.get_objects_inside_radius(p, .5)
    for k,v in ipairs(objs) do
        local ent = v:get_luaentity()
        if ent and ent.name == "__builtin:item" then
            v:remove()
        end
        -- explode other bombs
        if ent and ent.name == "bb_bomb:bomb" then
            ent:_explode()
        end
    end
    
    local node_name = minetest.get_node(p).name
    local removable = minetest.get_item_group(node_name, "removable")
    local powerup = minetest.get_item_group(node_name, "powerup")

    -- add new powerups
    if powerup ~= 0 then
        if powerup == 2 or (powerup == 1 and math.random(1,100) < bb_bomb.brick_drop_chance) then
            local item = bb_bomb.powerups[math.random(1,#bb_bomb.powerups)]

            minetest.after(0,function(p,item)
                local obj = minetest.add_item(p,item)
                -- change visual size of dropped item
                obj:set_properties({collisionbox = {-0.49,-0.49,-0.49,0.49,0.49,0.49,},visual_size = {x = .45, y = .45}})
                local ent = obj:get_luaentity()
                ent.on_punch = function() return end
            end,p,item)
    
        end
    end

    -- remove nodes
    if removable == 1 then
        minetest.set_node(p, {name = "air"})

    end
    if node_name == "air" then
        return false 
    else
        return true
    end
    -- note bb_player globalstep will check for player proximity to explosion ents and kill them.


end



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

-- return_bomb function

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-- gives a bomb back to player (p_name not objref)
function bb_bomb.return_bomb(owner)
    if owner and owner ~= "" then
        local player = minetest.get_player_by_name(owner)
        if player then
            local inv = player:get_inventory()
            inv:add_item("main", ItemStack("bb_powerup:extra_bomb"))
        end
    end
end
--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-- Explosion function

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

-- required: pos
-- optional: poser, multidir, owner

-- defaults: power: 1, multidir: false, owner: nil

function bb_bomb.explode(pos,power,multidir,owner)
    
    local power = power or 1
    local multidir = multidir or false
    local owner = owner or nil

    -- if there is an owner, add the bomb back to their inv
    bb_bomb.return_bomb(owner)

    minetest.sound_play({
        name = "sfx_exp_medium6",
        gain = 0.7,
    }, {
        pos = pos,
        max_hear_distance = 32,  -- default, uses an euclidean metric
    }, true)


    bb_bomb.local_explode(pos)

    local dir_table = {
        vector.new(1,0,0),
        vector.new(-1,0,0),
        vector.new(0,0,1),
        vector.new(0,0,-1),
    }

    if multidir then
        table.insert(dir_table,vector.new(1,0,1))
        table.insert(dir_table,vector.new(-1,0,-1))
        table.insert(dir_table,vector.new(-1,0,1))
        table.insert(dir_table,vector.new(1,0,-1))
    end

    for _, dir_vect in pairs(dir_table) do

        for i = 1, power do
            -- get the position (p)
            local p = vector.add(pos,vector.multiply(dir_vect, i))
            if bb_bomb.local_explode(p) then
                break 
            end

        end

    end





end


-- returns an axis-aligned direction vector and angle (in radians)
function bb_bomb.yaw_to_dirvect(look_yaw)
    local pi = 3.14159
    local dir_vect = vector.new(0,0,0)
    local angle = 0
    if (look_yaw >= 7 * pi / 4 and look_yaw < 2 * pi) or (look_yaw >= 0 and look_yaw < pi / 4 ) then
        dir_vect.z = 1
        angle = 0
    end 

    if (look_yaw >= pi / 4) and (look_yaw < 3* pi / 4) then
        dir_vect.x = -1 
        angle = pi/2
    end

    if (look_yaw >= 3* pi / 4) and (look_yaw < 5 * pi / 4) then
        dir_vect.z = -1
        angle = pi
    end

    if (look_yaw >= 5* pi / 4) and (look_yaw < 7 * pi / 4) then
        dir_vect.x = 1
        angle = 3*pi/2
    end
    return dir_vect,angle
end


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

-- The bomb entity

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



-- set the bomb's properties, if defined

-- on_activate is called and staticdata is a string passed to it that can store key values in json format. Use
-- minetest.write_json(table_with_key_values) to make the staticdata table and minetest.parse_json(staticdata) to 
-- get the valuse back here
function bb_bomb.bomb_on_activate(self, staticdata, dtime_s)
    if staticdata ~= "" and staticdata ~= nil then
        local data = minetest.parse_json(staticdata) or {}
        if data._power then
            self._power = data._power
        end
        if data._owner then
            self._owner = data._owner
        end
        if data._multidir then
            self._multidir = data._multidir
        end
    end
    self.object:set_animation({x=0,y=40}, 40, 0, true)
end

-- implements pushing the bomb
function bb_bomb.bomb_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
    if puncher:is_player() then
        local player = puncher
        local inv = player:get_inventory()
        if inv:contains_item("main", ItemStack("bb_powerup:kick")) then
            -- they must be outside of the bomb to punch it
            if vector.distance(vector.add(player:get_pos(),vector.new(0,0.5,0)),self.object:get_pos()) >.5 then
                --punch bomb
                local pl_look_yaw = player:get_look_horizontal()
                local dir_vect, angle = bb_bomb.yaw_to_dirvect(pl_look_yaw)
                local dir = minetest.yaw_to_dir(pl_look_yaw)
                local vel = vector.multiply(dir,vector.new(3,0,3))
                self.object:set_velocity(vel)
                --self.object:set_yaw(angle)
                self.object:set_animation({x = 120, y = 150}, 70, 0, false)
                self.object:set_properties({collides_with_objects = false})
                self._kick = true
                self._kicktimer = 0
            end
        end
    end

end


-- what the bomb does each globalstep
function bb_bomb.bomb_on_step(self, dtime, moveresult)

    -- bombs should not exist when the game is not running
    if bb_loop.gamestate ~= "running" then
        self.object:remove()
        return
    end

    if self._runtimer then
        self._timer = self._timer + dtime
    end

    if self._falling then
        self._falling_timer = self._falling_timer + dtime
        if self._falling_timer > 2 then
            self.object:remove()
            bb_bomb.return_bomb(self._owner)
            return
        end
    end


    if self._kick then
        self._kicktimer = self._kicktimer + dtime
        if self._kicktimer > .5 then 
            -- regular stationary anim, even tho moving
            self.object:set_animation({x=0,y=40}, 40, 0, true)
        end
        -- if we have mostly stopped moving, enter the stationary state
        if vector.length(self.object:get_velocity()) < .2 then
            self.object:set_velocity(vector.new(0,0,0))
            self.object:set_properties({collides_with_objects = true})
            -- stationary anim
            self.object:set_animation({x=0,y=40}, 40, 0, true)
            self._kick = false
        end
    end

    
    if self._timer > self._inc_timer then
        -- called about every second
        self._inc_timer = self._inc_timer + 1
    end

    

    if self._throwing then
        self._throw_timer = self._throw_timer + dtime 
        if self._throw_timer > self._throw_finish_time then
            self:_throw_finish()
        end
    end

    
    -- get info for throwing on players
    local p = self.object:get_pos()
    local throwable_player = nil -- player who can throw the bomb
    local closest_dist = .6

    if self._timer > .2  and self._throwing == false then
        for _, player in pairs(minetest.get_connected_players()) do
            local pl_pos = player:get_pos()
            local p_dist = vector.distance(pl_pos, vector.add(p,vector.new(0,-.5,0)))
            if p_dist < .5 and p_dist < closest_dist then

                local inv = player:get_inventory()
                if inv:contains_item("main", ItemStack("bb_powerup:throw")) then
                    local p_controls = player:get_player_control()
                    if p_controls.jump == true then
                        throwable_player = player
                        closest_dist = p_dist
                    end
                end
            end
        end
    end

    


    -- implement throwing


    if throwable_player then
        -- get target position
        local pi = 3.14159
        -- get player's look direction
        local look_yaw = throwable_player:get_look_horizontal()
        local dir_vect = bb_bomb.yaw_to_dirvect(look_yaw)
        
        local target = vector.add(p,vector.multiply(dir_vect,2))

        self:_start_throw(target,1.5)
    end


    if self._throwing == false then
        -- check under_nodes, do something special for special under nodes
        local o_pos = self.object:get_pos()
        local pos = vector.add(o_pos,vector.new(0,-1,0))
        local under_node = minetest.get_node(pos)
        
        if vector.round(pos) ~= self._coursechange_pos then
            self._coursechange_pos = vector.round(pos)
            
            if under_node.name == "bb_nodes:floor_with_arrow" then
                self._last_arrow = vector.round(pos)
                self.object:move_to(vector.round(o_pos), false)
                local facedir = under_node.param2
                local dir = minetest.facedir_to_dir(facedir)
                self.object:set_velocity(vector.multiply(dir,3))
            end


        end

        if under_node.name == "bb_nodes:floor_with_mortar" then
            self.object:set_animation({x = 120, y = 150}, 30, 0, false)
            self.object:move_to(pos)
            local pos1 = vector.add(pos,vector.new(-20,-20,-20))
            local pos2 = vector.add(pos,vector.new(20,20,20))

            local possible_pos = minetest.find_nodes_in_area(pos1, pos2, "group:floor", false)

            local target = vector.new(0,1,0) -- default, should always be changed.
            
            local nodepos = possible_pos[math.random(1,#possible_pos)]
            if nodepos then
                target = vector.add(nodepos,vector.new(0,1,0))
            end
            self.object:move_to(vector.add(vector.new(0,2,0),pos))
            self:_start_throw(target,2,10*2)
        end

        -- make bombs fall if there is air under them.

        if under_node.name == "air" then
            self._falling = true
            self._runtimer = false
            self.object:set_animation({x=220,y=260}, 15, 0, false)
            self.object:set_pos(vector.round(o_pos))
            self.object:set_properties({physical = false})
            self.object:set_velocity(vector.new(0,0,0))
        end



    end
        
    



    

    -- explode if timer is reached
    if self._runtimer and self._timer > bb_bomb.bombtimer then
        self:_explode()
    end

end


-- -- get staticdata is called about every 18 sec while the ent is active, and saves the data for later use. because we do not need bombs to be saved, we dont need this.
-- function bb_bomb.bomb_get_staticdata(self)
--     return
-- end


minetest.register_entity("bb_bomb:bomb",{
    initial_properties = {
        physical = true,
        stepheight = 0.1,
        collide_with_objects = true,
        collisionbox = {-0.49, -0.49, -0.49, 0.49, 0.49, 0.49},
        visual = "mesh",
        mesh = "bb_bomb.b3d",
        textures = {"bb_bomb_texture.png"},
        visual_size = bb_bomb.visual_size,
        static_save = false,
    },

    on_activate = bb_bomb.bomb_on_activate,
    on_step = bb_bomb.bomb_on_step,
    get_staticdata = bb_bomb.bomb_get_staticdata,
    on_punch = bb_bomb.bomb_on_punch,
    _power = 1,
    _owner = "",
    _multidir = false,
    _timer = 0,
    _inc_timer = 0,
    _runtimer = true,
    _throw_timer = 0,
    _throwing = false,
    _throw_target = nil,
    _throw_finish_time = nil,
    _kicktimer = 0,
    _kick = false,
    _coursechange_pos = vector.new(0,0,0),
    _falling = false,
    _falling_timer = 0,
    _exploding = false,

    _start_throw = function(self,target_pos,time,anim_time)
        anim_time = anim_time or 16*time
        
        self._runtimer = false
        self._throwing = true
        self._throw_target = target_pos 
        self._throw_finish_time = time
        self.object:set_properties({physical = false})

        -- set throw anitmation
        self.object:set_animation({x=60,y=100}, anim_time, 0, false)
        local dir = vector.direction(self.object:get_pos(), target_pos)
        local dist = vector.distance(self.object:get_pos(), target_pos)
        local speed = dist / time 
        local send_vect = vector.multiply(dir,speed)
        self.object:set_velocity(send_vect)

    end,

    _throw_finish = function(self)
        self.object:set_properties({physical = true})
        self.object:set_velocity(vector.new(0,0,0))
        self.object:set_pos(self._throw_target)
        self._runtimer = true
        self._throw_timer = 0
        self._throwing = false
        self.object:set_animation({x=0,y=40}, 40, 0, true)
    end,

    _explode = function(self)
        if self._exploding == false then
            self._exploding = true
            if bb_bomb.debug then
                minetest.log("error","power:"..tostring(self._power)..", multidir:".. tostring(self._multidir)..", owner:".. tostring(self._owner))
            end
            --bb_bomb.explode(pos,power,multidir,owner)
            
            bb_bomb.explode(vector.round(self.object:get_pos()), self._power,self._multidir,self._owner)
            
            --particlepos variable set
            local particlepos = self.object:get_pos()
            --minetest.log(particlepos)

            minetest.add_particlespawner({ 
                amount = 40, 
                time = 0.1, 
                minpos = {x=particlepos.x - 0.7, y=particlepos.y - 0.7, z=particlepos.z - 0.7}, 
                maxpos = {x=particlepos.x + 0.7, y=particlepos.y + 0.7, z=particlepos.z + 0.7}, 
                minvel = {x=-2, y=1, z=-1}, 
                maxvel = {x=2, y=2, z=1}, 
                minacc = {x=-1, y=-5, z=-1}, 
                maxacc = {x=1, y=-4, z=1}, 
                minexptime = 2, 
                maxexptime = 3, 
                minsize = 0.3, 
                maxsize = 3,
                collisiondetection = false, 
                vertical = false, 
                texture = "bb_bomb_particle.png", 
                --playername = "singleplayer" 
            })
            self.object:remove()
        end
    end,

})




minetest.register_abm({
    label = "suddendeath",
    nodenames = {"group:floor"},
    interval = .5,
    chance = 200,
    action = function(pos, node, active_object_count, active_object_count_wider)
        if bb_loop.suddendeath == false then return end
        pos = vector.add(pos,vector.new(0,1,0))
        local power = math.random(1,3)
        local multidir = false
        local i = math.random(1,10)
        if i == 10 then
            multidir = true
        end
        local staticdata = minetest.write_json({
            _power = power,
            _multidir = multidir,
        })
        minetest.add_entity(pos, "bb_bomb:bomb", staticdata)
    end,
})
