local S = minetest.get_translator(blockbomber.modname)

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

local function return_bomb(owner) end

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

--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("block_bomber_mg: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, "block_bomber_mg: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 == "block_bomber_mg: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()
                local old_on_punch = ent.on_punch
                
                -- override the on_punch in a way that won't change the behavior outside the minigame, since we are affecting the entire template for builtin item
                ent.on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
                    if not arena_lib then return old_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage) end
                    local pos = self.object:get_pos()
                    local arena = arena_lib.get_arena_by_pos(pos, blockbomber.modname)
                    if (not arena) or (arena and not(arena.in_game)) then
                        return old_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
                    end
                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

end



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

-- return_bomb function

--%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-- gives a bomb back to player (p_name not objref)
function return_bomb(owner)
    if owner and owner ~= "" then
        -- minetest.debug("Attempting to return bomb to ".. owner)
        local mod = arena_lib.get_mod_by_player(owner)
        if not mod or mod ~= blockbomber.modname then return end
        local arena = arena_lib.get_arena_by_player(owner)
        if not arena then return end
        if not arena.in_game then return end
        if not arena.players[owner] then return end

        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
    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 values 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 p_name = player:get_player_name()
        local mod = arena_lib.get_mod_by_player(p_name)
        if not mod then return end
        if not mod == blockbomber.modname then return end
        local arena = arena_lib.get_arena_by_player(p_name)
        if not arena.players[p_name] then return end
        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
    local arena = arena_lib.get_arena_by_pos(self.object:get_pos(), blockbomber.modname)
    if not arena then self.object:remove() return end
    if not arena.in_game 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()
            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 > .5  and self._throwing == false then
        for pl_name, stats in pairs(arena.players) do
            -- if stats.alive then
                local player = minetest.get_player_by_name(pl_name)
                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
    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)
            
            local arrow_group = minetest.get_item_group(under_node.name,"arrow")
            if arrow_group == 1 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
        local mortar_group = minetest.get_item_group(under_node.name,"mortar")
        if mortar_group == 1 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
            local found_node = false
            for i=0,20 do 
                if minetest.find_node_near(vector.add(p,vector.new(0,-i,0)), .3, {"group:solid"}, true) then 
                    found_node = true 
                end
                
            end
            if found_node then 
                local vel = self.object:get_velocity()
                self.object:set_pos(vector.round(o_pos))
                self.object:set_velocity(vector.new(0,-25,0))
                minetest.after(.5,function()
                    if self and self.object then
                        self.object:set_velocity(vel)
                    end
                end)
            else
                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



    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("block_bomber_mg:bomb",{
    initial_properties = {
        physical = true,
        stepheight = 0.5,
        collide_with_objects = true,
        collisionbox = {-0.45, -0.45, -0.45, 0.45, 0.45, 0.45},
        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.2, 
                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=2, z=-1}, 
                maxvel = {x=2, y=4, z=1}, 
                minacc = {x=-1, y=-6, z=-1}, 
                maxacc = {x=1, y=-5, z=1}, 
                minexptime = 1, 
                maxexptime = 2, 
                minsize = 2, 
                maxsize = 6,
                collisiondetection = true, 
                vertical = false, 
                texture = "bb_bomb_particle.png", 
                --playername = "singleplayer" 
            })
            self.object:remove()
        end
    end,

})



