local mod_name = minetest.get_current_modname()
local mod_path = minetest.get_modpath(mod_name)
local S = minetest.get_translator(mod_name)

local pl = {}

local function get_shell()
    return {
        name = "",
        node = {name="air"},
        ent = {}
    }
end

local function get_eyepos(player)
    local eyepos = vector.add(player:get_pos(), vector.multiply(player:get_eye_offset(), 0.1))
    eyepos.y = eyepos.y + player:get_properties().eye_height
    return eyepos
end

local get_tool_range = (minetest.get_modpath("aom_util") and aom_util.get_tool_range) or function (itemstack)
    local range = itemstack:get_definition().range
    if not range then
        range = minetest.registered_items[""].range or 4
    end
    return range
end

local function get_pointed_thing(player, eyepos, liquids)
    if not eyepos then eyepos = get_eyepos(player) end
    local range = get_tool_range(player:get_wielded_item())
    local ray = minetest.raycast(eyepos, vector.add(eyepos, vector.multiply(player:get_look_dir(), range)), false, (liquids == true))
    for pointed_thing in ray do
        if pointed_thing.type == "node" then
            return pointed_thing
        end
    end
    return
end

local function remove_all_ents(player)
    for i=1, 2 do
        if pl[player].ent[i] then
            pl[player].ent[i].object:remove()
        end
    end
end


local function sort_positions(v, b)
    return vector.new(
        ((v.x <= b.x) and v.x) or b.x,
        ((v.y <= b.y) and v.y) or b.y,
        ((v.z <= b.z) and v.z) or b.z
    ),
    vector.new(
        ((v.x >= b.x) and v.x) or b.x,
        ((v.y >= b.y) and v.y) or b.y,
        ((v.z >= b.z) and v.z) or b.z
    )
end
local function all_nodes_in(v, b)
    local list = {}
    for z=v.z, b.z do
        for y=v.y, b.y do
            for x=v.x, b.x do
                list[#list+1] = vector.new(x,y,z)
            end
        end
    end
    return list
end


local function tex(name)
    return {name,name,name,name,name,name}
end
local entity = {
    initial_properties = {
        physical = false,
        textures = tex("aom_admin_tools_fill_ENTITY.png^[colorize:#04f:100"),
        visual = "cube",
        visual_size = {x=1.1, y=1.1},
        collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2,},
        use_texture_alpha = true,
        pointable = false,
        glow = 14,
        static_save = false,
    },
    _parent = false,
    _timer = 1,
    _type = 1,
    on_step = function(self, dtime, moveresult)
        if self._timer < 0 then self._timer = 1
        else self._timer = self._timer - dtime return end

        if (not self._parent) or (not pl[self._parent]) or (not pl[self._parent]["pos"..self._type]) then
            self.object:remove()
            return
        end
    end,
}
minetest.register_entity("aom_admin_tools:fill_ENTITY_1", entity)
entity = table.copy(entity)
entity.initial_properties = table.copy(entity.initial_properties)
entity.initial_properties.textures = tex("aom_admin_tools_fill_ENTITY.png^[colorize:#ff0:100")
minetest.register_entity("aom_admin_tools:fill_ENTITY_2", entity)


local function add_entity(pos, ent_type, player)
    local object = minetest.add_entity(pos, "aom_admin_tools:fill_ENTITY_"..ent_type)
    local self = object:get_luaentity()
    if not self then object:remove() return end
    self._parent = player
    if pl[player].ent[ent_type] then
        pl[player].ent[ent_type].object:remove()
    end
    pl[player].ent[ent_type] = self
end


local function send_message(player, color, message)
    minetest.chat_send_player(player:get_player_name(),
    "[fill tool] "..minetest.colorize(color, message))
end

minetest.register_tool("aom_admin_tools:fill", {
    description = S("Fill Tool"),
    _tt_color = "#f0f",
    _tt_how_to_use = (
        S("[place] to select a node type to place") .. "\n" ..
        S("[dig] and drag and let go to fill between points") .. "\n" ..
        S("[aux1] to select \"under\" the pointed position")),
    inventory_image = "aom_admin_tools_fill.png",
    wield_image = "aom_admin_tools_fill.png",
    wield_scale = {x=0.5, y=0.5, z=5},
    groups = { admin_tools = 1, not_in_creative_inventory = 1 },
    range = 10,
    -- liquids_pointable = true,
    on_use = function (itemstack, player, pointed_thing)
        if not core.is_player(player) then return end
        if not pl[player] then pl[player] = get_shell() end
        local pi = player:get_player_control()
        minetest.sound_play(("aom_admin_tools_click"), {
            gain = 0.1,
            pitch = 0.6,
            object = player,
        })
        if (not pointed_thing) or pointed_thing.type ~= "node" then
            pl[player].pos1 = vector.floor(vector.offset(get_eyepos(player), 0.5, 0.5, 0.5))
        elseif pi.aux1 then
            pl[player].pos1 = vector.copy(pointed_thing.under)
        else
            pl[player].pos1 = vector.copy(pointed_thing.above)
        end
        if pl[player].pos1 then
            add_entity(pl[player].pos1, 1, player)
            add_entity(pl[player].pos1, 2, player)
        end
    end,
    on_place = function(itemstack, player, pointed_thing)
        if not core.is_player(player) then return end
        minetest.sound_play(("aom_admin_tools_click"), {
            gain = 0.1,
            pitch = 0.8,
            object = player,
        })
        if not pl[player] then pl[player] = get_shell() end
        pl[player].node = table.copy(minetest.get_node(pointed_thing.under))
            -- @1 is the technical node name ("aom_stone:cobble")
        send_message(player, "#f7f", S("selection set to @1", pl[player].node.name))
    end,
    on_secondary_use = function(itemstack, player)
        if not core.is_player(player) then return end
        minetest.sound_play(("aom_admin_tools_click"), {
            gain = 0.1,
            pitch = 0.8,
            object = player,
        })
        if not pl[player] then pl[player] = get_shell() end
        local pt = get_pointed_thing(player, nil, true)
        if pt then
            pl[player].node = table.copy(minetest.get_node(pt.under))
            -- @1 is the technical node name ("aom_stone:cobble")
            send_message(player, "#f7f", S("selection set to @1", pl[player].node.name))
            return itemstack
        end
        pl[player].node = {name="air"}
        send_message(player, "#6af", S("selection set to air"))
        return itemstack
    end,
    _on_step = function(itemstack, player, dtime)
        if not pl[player] then pl[player] = get_shell() return end
        if not pl[player].pos1 then return end

        local eyepos = get_eyepos(player)
        local pointed_thing = get_pointed_thing(player, eyepos)
        local pi = player:get_player_control()
        -- allow player to fill solid blocks too when pressing aux1
        if pointed_thing and pi.aux1 then
            pl[player].pos2 = vector.copy(pointed_thing.under)
        elseif pointed_thing then
            pl[player].pos2 = vector.copy(pointed_thing.above)
        end
        -- if not pointing at a node, just use the eyepos
        if not pl[player].pos2 then pl[player].pos2 = eyepos end
        -- move the end marker
        pl[player].pos2 = vector.floor(vector.offset(pl[player].pos2, 0.5, 0.5, 0.5))
        if pl[player].ent[2] then
            pl[player].ent[2].object:set_pos(pl[player].pos2)
        end

        -- actually place nodes
        if (pl[player].pos2 ~= nil) and (not pi.dig) then
            pl[player].pos1, pl[player].pos2 = sort_positions(pl[player].pos1, pl[player].pos2)
            local nodelist = all_nodes_in(pl[player].pos1, pl[player].pos2)
            minetest.bulk_set_node(nodelist, pl[player].node)
            minetest.sound_play(("aom_admin_tools_click"), {
                gain = 0.1,
                pitch = 0.95,
                object = player,
            })
            pl[player].pos1, pl[player].pos2 = nil, nil
            remove_all_ents(player)
        end
        pl[player].pos2 = nil
    end,
    _on_deselect = function(itemstack, player)
        remove_all_ents(player)
        pl[player].pos2 = nil
        pl[player].pos1 = nil
    end,
})
