
local pl = {}
aom_radial.pl = pl

function aom_radial.check_player(player)
    local pi = pl[player]
    if pi == nil then pi = {}; pl[player] = pi end
    return pi
end

--------------
-- MATH ------
--------------
function aom_radial.shortangledist(a0, a1)
    return ((((a1 - a0) % (math.pi*2)) + (math.pi*3)) % (math.pi*2)) - math.pi
end

function aom_radial.clamp(v, m, n)
    return math.min(n, math.max(v, m))
end
---Get the X Y point at an angle
local FORWARD = vector.new(0,0,1)
local RIGHT = vector.new(1,0,0)
function aom_radial.get_radial_position(angle)
    return vector.rotate_around_axis(RIGHT, FORWARD, angle)
end


--------------
-- INTERNAL --
--------------
local sine_timer = 0
local deselected_scale_factor = 0.6

function aom_radial.deselect_last_item(player, pi)
    if not pi.last_selection_index then return end
    local last_def = pi.menudef.items[pi.last_selection_index]
    if pi.last_selection_index and pi.hud_list[pi.last_selection_index.."image"] then
        player:hud_change(pi.hud_list[pi.last_selection_index.."image"].id, "text", (last_def.image or "white.png") .. "^[opacity:120")
        local scale = (last_def.scale_start or 1) * deselected_scale_factor
        player:hud_change(pi.hud_list[pi.last_selection_index.."image"].id, "scale", {x=scale,y=scale})
    end
    pi.last_selection_index = nil
    pi.selected_def = nil
end

function aom_radial.select_item(player, pi, i)
    aom_radial.deselect_last_item(player, pi)
    local def = pi.menudef.items[i]
    if not def then return end
    if pi.hud_list[i.."image"] then
        player:hud_change(pi.hud_list[i.."image"].id, "text", (def.image or "white.png") .. "")
        local scale = (def.scale_start or 1)
        player:hud_change(pi.hud_list[i.."image"].id, "scale", {x=scale,y=scale})
    end
    pi.selected_def = def
end

function aom_radial.get_selection(player)
    local pi = aom_radial.check_player(player)

    local max_items = #pi.menudef.items
    local angle = pi.look_angle
    local angle_per_i = math.pi * 2 / max_items
    local angle_offset = math.pi

    local i

    if pi.look_x^2 + pi.look_y^2 < 100 then
        i = nil
    else
        i = (angle + angle_offset) / angle_per_i
        i = math.floor(((i+0.5) % max_items) + 1)
    end

    -- minetest.log(dump(i))

    if pi.last_selection_index ~= i then
        aom_radial.select_item(player, pi, i)
    end

    pi.last_selection_index = i

    return i
end

function aom_radial.fade_hud_step(player, dtime, hud_info)
    if hud_info.scale == nil then
        hud_info.scale = (hud_info.scale_start or -1) * deselected_scale_factor
    end
    hud_info.scale = hud_info.scale - dtime * (hud_info.scale_start or 1) * 6
    if hud_info.scale < 0 then
        player:hud_remove(hud_info.id)
        return true
    else
        player:hud_change(hud_info.id, "scale", {x = hud_info.scale, y = hud_info.scale})
    end
end

function aom_radial.fade_huds(player, dtime, pi)
    for i = #(pi.fading_huds or {}), 1, -1 do
        local hud_list = pi.fading_huds[i]
        for k = #hud_list, 1, -1 do
            local hud_info = hud_list[k]
            if aom_radial.fade_hud_step(player, dtime, hud_info) then
                table.remove(hud_list, k)
                -- minetest.log("removed hud def from inner list")
            end
        end
        if #hud_list <= 0 then
            table.remove(pi.fading_huds, i)
            -- minetest.log("removed a list from fading hud lists")
        end
    end
end

function aom_radial.start_fading_hud(player, pi)
    if not pi.fading_huds then pi.fading_huds = {} end
    local list = {}
    for name, hud in pairs(pi.hud_list) do
        table.insert(list, hud)
    end
    table.insert(pi.fading_huds, list)
end

local enable_hud_fade = true

function aom_radial.close_radial_menu(player, skip_fade)
    local pi = aom_radial.check_player(player)
    if pi.menudef == nil then return end
    if enable_hud_fade and not skip_fade then
        aom_radial.start_fading_hud(player, pi)
    else
        for itemname, hud_info in pairs(pi.hud_list) do
            player:hud_remove(hud_info.id)
        end
    end
    pi.menudef = nil
    pi.hud_list = nil
    pi._init = false
    pi.meta = {}
end

function aom_radial.init_huds(player)
    local pi = aom_radial.check_player(player)
    local max_items = #pi.menudef.items
    local angle = 0
    local dir
    local dist = 140
    if max_items > 8 then
        dist = 140 + 20 * (max_items-8)
    end
    local angle_per_i = math.pi * 2 / max_items
    local angle_offset = math.pi
    pi.hud_list = {}

    pi.hud_list["CROSSHAIR"] = {
        id = COMPAT.hud_add(player, {
            type = "image",
            alignment = {x=0, y=0},
            position = {x=0.5, y=0.5},
            name = "aom_radial:crosshair",
            text = "crosshair.png",
            z_index = 699,
            scale = {x = 2, y = 2},
            offset = {x = 0, y = 0},
        }),
        scale_start = 2,
    }

    for i, def in ipairs(pi.menudef.items) do
        angle = ((i-1) * angle_per_i) + angle_offset
        dir = aom_radial.get_radial_position(angle)
        if pi.menudef.on_segment_added then
            pi.menudef:on_segment_added(player, pi, i, dir, dir * dist)
        end
        local scale = def.scale or 1
        pi.hud_list[i.."image"] = {
            id = COMPAT.hud_add(player, {
                type = "image",
                alignment = {x=0, y=0},
                position = {x=0.5, y=0.5},
                name = "aom_radial:"..(def.name or tostring(i)),
                text = (def.image or "white.png") .. "^[opacity:120",
                z_index = 600 + i,
                scale = {x = scale*deselected_scale_factor, y = scale*deselected_scale_factor},
                offset = {x = dir.x*dist, y = dir.y*dist},
            }),
            scale_start = scale,
        }
        if def.text ~= nil then
            pi.hud_list[i.."text"] = {
                id = COMPAT.hud_add(player, {
                    type = "image",
                    alignment = {x=0, y=0},
                    position = {x=0.5, y=0.5},
                    name = "aom_radial:"..(def.name or tostring(i)).."text",
                    text = def.text,
                    z_index = 600 + i,
                    scale = {x = scale, y = scale},
                    offset = {x = dir.x*dist, y = dir.y*dist},
                }),
                scale_start = scale,
            }
        end
    end

    if pi.menudef.after_init_huds then
        pi.menudef:after_init_huds(player, pi)
    end
end

-- true if name==nil and any radial open, or only if the name of the radial==name
function aom_radial.is_radial_open(player, name)
    local pi = aom_radial.check_player(player)
    if pi.menudef and ((name == nil) or (pi.menudef.name == name)) then
        return pi.menudef.name or "unknown"
    else
        return false
    end
end

-- actually show the menu and start tracking the selection etc
function aom_radial.show_radial_menu(player, menudef)
    local pi = aom_radial.check_player(player)
    aom_radial.close_radial_menu(player)
    pi.meta = {}
    pi._init = false
    pi.menudef = menudef
    aom_radial.init_huds(player)
end



local mouse_sensitivity = 140
function aom_radial.step_player(player, dtime)
    local pi = aom_radial.check_player(player)
    if not pi.menudef then return end
    -- when a radial menu is first opened, do init stuff to prevent "leftover state"
    if not pi._init then
        pi._last_yaw = player:get_look_horizontal()
        pi._last_pitch = player:get_look_vertical()
        pi.look_x = 0
        pi.look_y = 0
        pi._init = true
        pi.selected_def = nil
        pi.last_selection_index = nil
    end

    -- convert look dir into XY mouse movement
    local dx = aom_radial.shortangledist(pi._last_yaw, player:get_look_horizontal())
    local dy = aom_radial.shortangledist(pi._last_pitch, player:get_look_vertical())
    -- keep track of last vals so you can get only the difference
    pi._last_yaw = player:get_look_horizontal()
    pi._last_pitch = player:get_look_vertical()
    -- update the actual "cursor" position according to the delta movement
    pi.look_x = pi.look_x - (dx * mouse_sensitivity)
    pi.look_y = pi.look_y + (dy * mouse_sensitivity)
    pi.look_x = aom_radial.clamp(pi.look_x, -50, 50)
    pi.look_y = aom_radial.clamp(pi.look_y, -50, 50)

    -- smoothly reset if mouse not moved for a while
    if pi.grace and pi.grace > 0 then pi.grace = pi.grace - dtime end
    if (dx^2 + dy^2 < 0.001) then
        if ((pi.grace or 0) <= 0) then
            pi.look_x = pi.look_x * 0.8
            pi.look_y = pi.look_y * 0.8
        end
    else
        pi.grace = 0.5
    end
    -- it's cheating sure, but it works
    pi.look_angle = minetest.dir_to_yaw(vector.new(pi.look_y, 0, pi.look_x):normalize())

    -- minetest.log("angle: " .. tostring(pi.look_angle))
    -- move the crosshair to show the selection dir
    player:hud_change(pi.hud_list["CROSSHAIR"].id, "offset", {x = pi.look_x, y = pi.look_y})

    aom_radial.get_selection(player)

    -- check if you selected something, and if so, select and highlight it
    local selected_name = pi.selected_def and pi.selected_def.name
    local result = nil
    if pi.menudef.on_step ~= nil then
        result = pi.menudef:on_step(player, dtime, selected_name, pi)
    end
    if result == false then
        aom_radial.close_radial_menu(player)
    elseif result == true then
        if pi.menudef.on_select then
            pi.menudef:on_select(player, selected_name, pi)
        end
        aom_radial.close_radial_menu(player)
        return
    end
end

minetest.register_globalstep(function(dtime)
    sine_timer = (sine_timer + dtime) % 1000
    for i, player in ipairs(minetest.get_connected_players()) do
        local pi = aom_radial.check_player(player)
        aom_radial.step_player(player, dtime)
        aom_radial.fade_huds(player, dtime, pi)
    end
end)
