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("pmb_util") and pmb_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("pmb_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,
    },
    _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("pmb_admin_tools:fill_ENTITY_1", entity)
entity = table.copy(entity)
entity.initial_properties = table.copy(entity.initial_properties)
entity.initial_properties.textures = tex("pmb_admin_tools_fill_ENTITY.png^[colorize:#ff0:100")
minetest.register_entity("pmb_admin_tools:fill_ENTITY_2", entity)


local function add_entity(pos, ent_type, player)
    local object = minetest.add_entity(pos, "pmb_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("pmb_admin_tools:fill", {
    description = S("Fill tool"),
    _tt_color = "#f0f",
    _tt_how_to_use = S("Right click to select a node, left click drag to fill between points. Use aux1 to select \"under\" the pointed position."),
    inventory_image = "pmb_admin_tools_fill.png",
    wield_image = "pmb_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 pl[player] then pl[player] = get_shell() end
        local pi = player:get_player_control()
        minetest.sound_play(("pmb_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 minetest.is_player(player) then return itemstack end
        minetest.sound_play(("pmb_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))
        send_message(player, "#f7f", "selection set to "..pl[player].node.name)
    end,
    on_secondary_use = function(itemstack, player)
        minetest.sound_play(("pmb_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))
            send_message(player, "#f7f", "selection set to "..pl[player].node.name)
            return itemstack
        end
        pl[player].node = {name="air"}
        send_message(player, "#6af", "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(("pmb_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,
})
