
br_player_model = {}
br_player_model.on_changed = {}
br_player_model.pl = {}
local pl = br_player_model.pl

local anim = {
    idle = {
        frames = {x=1, y=60},
        loop = true,
        blend = 0.2,
    },
    walk = {
        frames = {x=71, y=90},
        loop = true,
        blend = 0.1,
    },
    punch = {
        frames = {x=101, y=110},
        loop = true,
        blend = 0.1,
    },
    walkpunch = {
        frames = {x=121, y=140},
        loop = true,
        blend = 0.1,
    },
    aim = {
        frames = {x=151, y=160},
        loop = true,
        blend = 0.2,
    },
    walkaim = {
        frames = {x=161, y=180},
        loop = true,
        blend = 0.1,
    },
    sneak = {
        frames = {x=191, y=250},
        loop = true,
        blend = 0.2,
    },
    sneakwalk = {
        frames = {x=261, y=300},
        loop = true,
        blend = 0.1,
    },
    sneakwalkpunch = {
        frames = {x=311, y=350},
        loop = true,
        blend = 0.1,
    },
    sneakpunch = {
        frames = {x=361, y=400},
        loop = true,
        blend = 0.1,
    },
    sneakwalkaim = {
        frames = {x=411, y=450},
        loop = true,
        blend = 0.1,
    },
    sneakaim = {
        frames = {x=451, y=470},
        loop = true,
        blend = 0.2,
    },
}


local function get_player_default()
    return {
        anim = "idle",
        tags = {},
        actions = {
            sneak = 0,
            sit   = 0,
            walk  = 0,
            punch = 0,
            aim   = 0,
        },
        overrides = {},
        changed_this_step = true,
    }
end

--[[
tag = {
    tag = "pmb_mod:item",
    actions = {"aim", "walk"},
}
]]--
local bone_defaults = {
    head2 = {
        pos = vector.new(0, 9, 0),
        rot = vector.new(-90, 0, 180) },
    armR = {
        pos = vector.new(-3, 7, 0),
        rot = vector.new(0, 0, 180) },
    armL = {},
    legR = {},
    legL = {},
    torso = {},
}
local texture_colors = {
    "#333", "#aa9", "#abc", "#a89", "#7a9", "#778", "#994", "#96f", "#b85", "#756052", "#4d7953", "#478"
}
local tex_offset = math.random(#texture_colors)
local function get_texture()
    tex_offset = (tex_offset) % #texture_colors + 1
    return texture_colors[tex_offset]
end

local player_height_mult = 1.30

minetest.register_on_joinplayer(function(player)
    br_player_model.pl[player] = get_player_default()
    -- player:set_eye_offset(vector.new(0, 0, 0), vector.new(4, 5, 0)) -- enable for slightly more usable 3rd person
    player:set_properties({
        mesh = 'humanoid.b3d',
        textures = {'clive.png^(clive_overlay.png^[multiply:'..get_texture()..")"},
        visual = "mesh",
        visual_size = {x=1.15, y=player_height_mult},
        damage_texture_modifier = "^[colorize:red:130",
        zoom_fov = 72.0,
    })
    minetest.after(0.1, function()
        br_player_model.do_animations(player, 0.1, true)
    end)
end)

function br_player_model.register_on_changed_animation(func)
    local oc = br_player_model.on_changed
    oc[#oc+1] = func
end
function br_player_model.on_changed_animation(player, fromanim, toanim)
    for i, func in pairs(br_player_model.on_changed) do
        func(player, fromanim, toanim)
    end
end

function br_player_model.set_bone_pos(player, nb)
    local def = bone_defaults[nb.bone]
    player:set_bone_position(nb.bone, vector.add(def.pos, nb.pos), vector.add(def.rot, nb.rot))
end
function br_player_model.reset_bone_pos(player, bone_name)
    local def = bone_defaults[bone_name]
    player:set_bone_position(bone_name, def.pos, def.rot)
end
function br_player_model.get_default_bone_pos(player, bone_name)
    return bone_defaults[bone_name]
end

local function table_has_value(tab, val)
    for i, v in pairs(tab) do
        if v == val then return true end
    end
    return false
end
-- DO NOT USE EXCEPT IN EXTREMELY SPECIFIC CIRCUMSTANCES AS IT CAN BREAK THE PLAYER ANIMATIONS UNTIL THEY REJOIN
function br_player_model.override_anim(player, animname)
    local ov = pl[player].overrides
    local i = table.indexof(ov, animname)
    if i ~= nil and i > 0 then
       table.remove(ov, i)
    end
    ov[#ov+1] = animname
end
function br_player_model.unoverride_anim(player, animname)
    for i, name in pairs(pl[player].overrides) do
        if name == animname then
            table.remove(pl[player].overrides, i)
            return
        end
    end
end

function br_player_model.set_anim(player, an)
    if pl[player].tags[an.tag] ~= nil then return false end
    pl[player].changed_this_step = true
    pl[player].tags[an.tag] = an
    for _, act in pairs(an.actions) do
        if pl[player].actions[act] ~= nil then
            pl[player].actions[act] = pl[player].actions[act] + 1
        end
    end
end
function br_player_model.unset_anim(player, tagname)
    local an = pl[player].tags[tagname]
    if an == nil then return false end
    pl[player].changed_this_step = true
    for _, act in pairs(an.actions) do
        if pl[player].actions[act] ~= nil then
            pl[player].actions[act] = pl[player].actions[act] - 1
        end
    end
    pl[player].tags[tagname] = nil
end

local non_existant_animations = {}

function br_player_model.do_move_checks(player, dtime)
    local ct = player_info.get(player)
    if not ct then return end
    -- check movement stuff for basic anims
    if ct.is_moving then br_player_model.set_anim(player, {tag="builtinwalk", actions={"walk", "walk"}})
    else br_player_model.unset_anim(player, "builtinwalk") end
    if ct.is_sneaking then br_player_model.set_anim(player, {tag="builtinsneak", actions={"sneak", "sneak"}})
    else br_player_model.unset_anim(player, "builtinsneak") end
    if ct.is_punching then br_player_model.set_anim(player, {tag="builtinpunch", actions={"punch", "punch"}})
    else br_player_model.unset_anim(player, "builtinpunch") end
end

local tpv_camera_offset = vector.new(4, 0 + ((player_height_mult-1)*16), 4)

local collisionbox = function()
    return {
    -0.3,  0.0, -0.3,
     0.3,  1.7*player_height_mult,  0.3}
end

function br_player_model.do_animations(player, dtime, force_update)
    local ct = player_info.get(player)
    if not ct then return end
    local c = ct.ctrl
    local a = pl[player].actions
    local ani = ""

    if #pl[player].overrides > 0 then
        ani = pl[player].overrides[#pl[player].overrides].override
    end

    local is_sneaking = false

    if not anim[ani] then

        -- check tag list for animation requests
        if a.sneak > 0 and a.sneak > a.sit then ani = ani.."sneak" is_sneaking = true
        elseif a.sit > 0 then ani = ani.."" end -- sit disabled
        if a.walk > 0 then ani = ani.."walk" end
        if a.punch > 0 and a.punch > a.aim then ani = ani.."punch"
        elseif a.aim > 0 then ani = ani.."aim" end

        if ani == "" then ani = "idle" end
    end

    local changed_animation = false

    if anim[ani] then
        local p = anim[ani]
        if ani ~= pl[player].anim then
            changed_animation = true
            br_player_model.on_changed_animation(player, pl[player].anim, ani)
            pl[player].anim = ani
            player:set_animation(p.frames, p.fps or 24, p.blend or 0)
        end
    elseif not non_existant_animations[ani] then
        non_existant_animations[ani] = true
        minetest.log("warning", "Animation \""..ani.."\" doesn't exist, oops")
        local p = anim.idle
        if not pl[player].anim == "idle" then
            changed_animation = true
            br_player_model.on_changed_animation(player, pl[player].anim, "idle")
            pl[player].anim = "idle"
            player:set_animation(p.frames, p.fps or 24, p.blend or 0)
        end
    end
    local fpo, tpo = player:get_eye_offset()
    if (changed_animation or force_update) and is_sneaking then
        local cbox = collisionbox()
        cbox[5] = cbox[5] - (5/16)
        player:set_properties({
            collisionbox = cbox
        })
        player:set_eye_offset({x=0, y=-(5/16)*10 + ((player_height_mult-1)*16), z=0}, vector.add(tpv_camera_offset, vector.new(0, 0, 0)))
    elseif (changed_animation or force_update) then
        player:set_properties({
            collisionbox = collisionbox()
        })
        player:set_eye_offset(vector.new(0, 0 + ((player_height_mult-1)*16), 0), tpv_camera_offset)
    end
end

function br_player_model.fix_bones(player)
    if pl[player].actions.aim > 0 then
        local l = player:get_look_dir()
        local bpos, brot = player:get_bone_position("head2")
        l.y = l.y * -60
        br_player_model.set_bone_pos(player, {
            bone = "head2",
            pos = vector.new(0, 0, 0),
            rot = vector.new(l.y, 0, 0)
        })
        br_player_model.set_bone_pos(player, {
            bone = "armR",
            pos = vector.new(0, 0, 0),
            rot = vector.new(l.y, 0, 0)
        })
        -- player:set_bone_position("head2", vector.new(0, 9, 0), vector.new(l.y, 0, 180))
        -- player:set_bone_position("armR", vector.new(-3, 7, 0), vector.new(l.y, 20, 180))
        -- player:set_bone_position("armL", vector.new(3, 7, 0), vector.new(l.y, -20, 180))
    else
        br_player_model.unset_anim(player, "armR")
        -- br_player_model.set_bone_pos(player, {
        --     bone = "armR",
        --     pos = vector.new(0, 0, 0),
        --     rot = vector.new(0, 0, 0)
        -- })
    end
end

function br_player_model.on_step(dtime)
    for _, player in pairs(minetest.get_connected_players()) do
        br_player_model.do_move_checks(player, dtime)
        if pl[player].changed_this_step then
            br_player_model.do_animations(player, dtime)
        end
        pl[player].changed_this_step = false
    end
end

minetest.register_globalstep(br_player_model.on_step)
