local Vec = vector
Guns4d = {
    players = {},
    handler_by_ObjRef = {},
    gun_by_ObjRef = {}, --used for getting the gun object by the ObjRef of the gun
    version = {1, 3, 1}
}
--default config values, config will be added soon:tm:
Guns4d.config = {
    show_mag_inv_ammo_bar = true,
    show_mag_inv_ammo_count = true,
    show_gun_inv_ammo_count = true,
    control_hybrid_toggle_threshold = .3,
    control_held_toggle_threshold = 0,
    empty_symbol = "E",
    default_damage_group = "fleshy",
    infinite_ammo_priv = "guns4d_infinite_ammo",
    interpret_initial_wear_as_ammo = false,
    punch_from_player_not_gun = true,
    vertical_rotation_factor = 25, --the rate at which the gun moves to the player's look vetically. With bone interpolation as of 5.9 this can be a lot higher now.
    player_axial_interpolation_time = .05, --the time it takes for the gun to reach the new angle (around the player's eye) set by the server. This includes recoil, sway, or anything that shifts the gun without misaligning sights.
    gun_axial_interpolation_time = .05, --the same as player_axial but for the rotation of the gun.
    translation_interpolation_time = .09, --time it takes for nonrotation components (i.e. hip offset, aiming offset, bone location, etc) to interpolate.
    simple_headshot = true, --holdover feature before a more complex system is implemented
    simple_headshot_body_ratio = .75, --percentage of hitbox height that is body.
    default_fov = 80,
    headshot_damage_factor = 1.75,
    enable_touchscreen_command_name = "guns4d_enable_touchmode",
    minimum_supersonic_energy_assumption = 900, --used to determine the energy of a "supersonic" bullet for bullet whizzing sound effects
    default_audio_attenuation_rate = .8, --changes the dropoff rate of sound. Acts as a multiplier for the distance used to calculate inverse square law. Most guns (from the gun packs) set their own, so this is mainly for reloads.
    mix_supersonic_and_subsonic_sounds = true,
    default_pass_sound_mixing_factor = 10,
    third_person_gain_multiplier = 1/3,
    default_penetration_iteration_distance = .25,
    maximum_bullet_holes = 20,
    inventory_listname = "main",
    aim_out_multiplier = 1.5,
    --enable_assert = false,
    realistic_items = false
    --`["official_content.replace_ads_with_bloom"] = false,
    --`["official_content.uses_magazines"] = true
}
local modpath = minetest.get_modpath("guns4d")

local conf = Settings(modpath.."/guns4d_settings.conf"):to_table() or {}
local mt_conf = minetest.settings:to_table() --allow use of MT config for servers that regularly update 4dguns through it's development
for i, v in pairs(Guns4d.config) do
    --Guns4d.config[i] = conf[i] or minetest.settings["guns4d."..i] or Guns4d.config[i]
    --cant use or because it'd evaluate to false if the setting is alse
    if mt_conf["guns4d."..i] ~= nil then
        Guns4d.config[i] = mt_conf["guns4d."..i]
    elseif conf[i] ~= nil then
        Guns4d.config[i] = conf[i]
    end
end

minetest.rmdir(modpath.."/temp", true)
minetest.mkdir(modpath.."/temp")

local path = modpath .. "/common"
dofile(path.."/misc_helpers.lua")
dofile(path.."/item_entities.lua")
dofile(path.."/play_sound.lua")
dofile(path.."/visual_effects.lua")
dofile(path.."/default_controls.lua")
dofile(path.."/touch_support.lua")
dofile(path.."/block_values.lua")
dofile(path.."/ammo_api.lua")
dofile(path.."/menus_and_guides.lua")
path = modpath .. "/classes"
dofile(path.."/Bullet_hole.lua")
dofile(path.."/Bullet_ray.lua")
dofile(path.."/Control_handler.lua")
dofile(path.."/Ammo_handler.lua")
dofile(path.."/Part_handler.lua")
dofile(path.."/Sprite_scope.lua")
dofile(path.."/Reflector_sight.lua")
dofile(path.."/Dynamic_crosshair.lua")
dofile(path.."/Gun.lua") --> loads /classes/gun_construct.lua
dofile(path.."/Player_model_handler.lua")
dofile(path.."/Player_handler.lua")

--model compatibility
path = modpath .. "/models"
dofile(path.."/3darmor/init.lua")

--infinite ammo
minetest.register_privilege(Guns4d.config.infinite_ammo_priv, {
    description = "allows player to have infinite ammo.",
    give_to_singleplayer = false,
    on_grant = function(name, granter_name)
        local handler = Guns4d.players[name]
        handler.infinite_ammo = true
        minetest.chat_send_player(name, "infinite ammo enabled by "..(granter_name or "unknown"))
        if handler.gun then
            handler.gun:update_image_and_text_meta()
        end
    end,
    on_revoke = function(name, revoker_name)
        local handler = Guns4d.players[name]
        handler.infinite_ammo = false
        minetest.chat_send_player(name, "infinite ammo disabled by "..(revoker_name or "unknown"))
        if handler.gun then
            handler.gun:update_image_and_text_meta()
        end
    end,
})
minetest.register_chatcommand("ammoinf", {
    parameters = "player",
    description = "quick toggle infinite ammo",
    privs = {privs=true},
    func = function(caller, arg)
        local trgt
        local args = string.split(arg, " ")
        local set_arg
        if #args > 1 then
            trgt = args[1]
            set_arg = args[2]
        else
            set_arg = args[1]
            trgt = caller
        end
        local handler = Guns4d.players[trgt]
        local set_to
        if set_arg then
            if set_arg == "true" then
                set_to = true
            elseif set_arg ~= "false" then --if it's false we leave it as nil
                minetest.chat_send_player(caller, "cannot toggle ammoinf, invalid value:"..set_arg)
                return
            end
        else
            set_to = not handler.infinite_ammo --if it's false set it to nil, otherwise set it to true.
            if set_to == false then set_to = nil end
        end
        local privs = minetest.get_player_privs(trgt)
        privs[Guns4d.config.infinite_ammo_priv] = set_to
        minetest.set_player_privs(trgt, privs)
        minetest.chat_send_player(caller, "infinite ammo "..((set_to and "granted to") or "revoked from") .." user '"..trgt.."'")
        handler.infinite_ammo = set_to or false
        if handler.gun then
            handler.gun:update_image_and_text_meta()
            handler.player:set_wielded_item(handler.gun.itemstack)
        end
    end
})

--player handling
setmetatable(Guns4d.handler_by_ObjRef, {
    __mode = "kv"
})
setmetatable(Guns4d.gun_by_ObjRef, {
    __mode = "kv"
})
local player_handler = Guns4d.player_handler
local objref_mtable
minetest.register_on_joinplayer(function(player)
    --ObjRef overrides will be integrated into leef (eventually TM)
    if not objref_mtable then
        objref_mtable = getmetatable(player)

        local old_set_fov = objref_mtable.set_fov
        Guns4d.old_set_fov = old_set_fov
        function objref_mtable.set_fov(self, ...)
            local handler = Guns4d.handler_by_ObjRef[self]
            local fov = select(1, ...)
            if handler then --check, just in case it's not a player (and thus should throw an error)
                if fov == 0 then
                    fov = Guns4d.config.default_fov
                elseif select(2, ...) == true then
                    fov = Guns4d.config.default_fov*fov
                end
                handler.default_fov = fov
                if handler.fov_lock then return end
            end
            local args = {...}
            args[1] = fov --basically permenantly set the player's fov to 80, making multipliers and resets return there.
            old_set_fov(self, unpack(args))
        end

        local old_get_pos = objref_mtable.get_pos
        function objref_mtable.get_pos(self, ...)
            local gun = Guns4d.gun_by_ObjRef[self]
            local mt_pos = old_get_pos(self, ...)
            if mt_pos then --mods (including this) will frequently use this as a check if an ent is still around.
                if (not gun) or gun.released then
                    return mt_pos
                else
                    local v, _, _ = gun:get_pos()
                    return v
                end
            end
        end
        function objref_mtable._guns4d_old_get_pos(self, ...)
            return old_get_pos(self, ...)
        end

        local old_set_animation = objref_mtable.set_animation
        --put vargs there for maintainability.
        function objref_mtable.set_animation(self, frame_range, frame_speed, frame_blend, frame_loop, ...)
            local gun = Guns4d.gun_by_ObjRef[self]
            if gun then
                local data = gun.animation_data
                data.runtime = 0
                data.fps = frame_speed or 15
                --dont use the frame_range table because it's parent could get GCed if it's a metatable (proxy table issue.)
                data.frames.x = frame_range.x
                data.frames.y = frame_range.y
                data.current_frame = data.frames.x
            end
            return old_set_animation(self, frame_range, frame_speed, frame_blend, frame_loop, ...)
        end

        local old_set_frame_speed = objref_mtable.set_animation_frame_speed
        function objref_mtable.set_animation_frame_speed(self, frame_speed, ...)
            local gun = Guns4d.gun_by_ObjRef[self]
            if gun then
                gun.animation_data.fps = frame_speed or 15
            end
            old_set_frame_speed(self, frame_speed, ...)
        end

        local old_remove = objref_mtable.remove
        function objref_mtable.remove(self)
            local gun = Guns4d.gun_by_ObjRef[self]
            if gun then
                Guns4d.gun_by_ObjRef[self] = nil
            end
            return old_remove(self)
        end



        --[[minetest.after(1, function(playername)
            minetest.get_player_by_name(playername):hud_add({
                hud_elem_type = "compass",
                text = "gun_mrkr.png",
                scale = {x=1, y=1},
                alignment = {x=0,y=0},
                position = {x=.5,y=.5},
                size = {x=200, y=200},
                offset = {x=-.5,y=.5},
                direction = 0
            })
        end, player:get_player_name())]]
    end
end)
--we grab the ObjRef metatable from the first available source.
--because we want guns to function as real objects, we have to override the metatable get_pos() for all objects
--this is made more efficient by using a table lookup for ObjRefs we want to update properly.
--"uns4d[ObjRef] = gun" is declared on_activate() in the entity.
--[[minetest.after(0, function()

end)]]
minetest.register_on_joinplayer(function(player)
    local pname = player:get_player_name()
    Guns4d.players[pname] = player_handler:new({player=player}) --player handler does just what it sounds like- see classes/Player_handler
    Guns4d.handler_by_ObjRef[player] = Guns4d.players[pname]
    player:set_fov(Guns4d.config.default_fov)
end)
minetest.register_on_leaveplayer(function(player)
    local pname = player:get_player_name()
    Guns4d.players[pname]:prepare_deletion()
    Guns4d.players[pname] = nil
    Guns4d.handler_by_ObjRef[player] = nil
end)
--ticks are rarely used, but still ideal for rare checks with minimal overhead.
TICK = 0
minetest.register_globalstep(function(dt)
    TICK = TICK + 1
    if TICK > 100000 then TICK = 0 end
    for player, handler in pairs(Guns4d.players) do
        if not handler then
            --spawn the player handler. The player handler handles the gun(s),
            --the player's model, and controls
            handler = player_handler:new({player=player})
        end
        handler:update(dt)
    end
end)