
pmb_wrench.pl = {}
local pl = pmb_wrench.pl

function pmb_wrench.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

-- returns an index list of vectors within a cube defined buy two points
function pmb_wrench.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

function pmb_wrench.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

function pmb_wrench.get_shell(player)
    local creative = (player and minetest.is_creative_enabled(player:get_player_name()) or false)
    if (not creative) and minetest.get_modpath("pmb_gamemodes") then creative = pmb_gamemodes.player_has_tag(player, "creative") end
    return {
        name = "",
        node = {name="air"},
        build_list = nil,
        ent = {},
        creative = creative
    }
end

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

function pmb_wrench.get_tool_range(player)
    local hand = minetest.registered_items[""]
    local wield = player:get_wielded_item():get_definition()
    return math.max(wield.range or 4, hand.range or 4)
end

function pmb_wrench.get_pointed_thing(player, eyepos, liquids)
    if not eyepos then eyepos = pmb_wrench.get_eyepos(player) end
    local range = pmb_wrench.get_tool_range(player)
    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 nil
end

local function tex(name)
    return {name,name,name,name,name,name}
end

local textures = {
    tex("pmb_wrench_wrench_ENTITY.png^[multiply:#8df"),
    tex("pmb_wrench_wrench_ENTITY.png^[multiply:#ed8"),
}

function pmb_wrench.get_entity_texture(num)
    return textures[num]
end


local entity = {
    initial_properties = {
        physical = false,
        textures = textures[1],
        visual = "cube",
        visual_size = {x=1.01, y=1.01},
        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 pmb_wrench.pl[self._parent]) or (not pmb_wrench.pl[self._parent]["pos"..self._type]) then
            self.object:remove()
            return
        end
    end,
}
minetest.register_entity("pmb_wrench:wrench_ENTITY", entity)


function pmb_wrench.chat_send_player(player, text)
    minetest.chat_send_player(player:get_player_name(), minetest.colorize("#fea", "[wrench] ") .. text)
end


function pmb_wrench.add_entity(pos, ent_type, player)
    local object = minetest.add_entity(pos, "pmb_wrench:wrench_ENTITY")
    local tex = pmb_wrench.get_entity_texture(ent_type)
    object:set_properties({
        textures = tex
    })
    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


function pmb_wrench.hide_entity(player, n)
    if not pl[player] then return end
    if not pl[player].ent[n] then return end
    local props = pl[player].ent[n].object:get_properties()
    if not props then return end
    if props.is_visible then
        pl[player].ent[n].object:set_properties({
            is_visible = false,
        })
    end
end


function pmb_wrench.show_entity(player, n)
    if pl[player].ent[n] and (not pl[player].ent[n].object:get_properties().is_visible) then
        pl[player].ent[n].object:set_properties({
            is_visible = true,
        })
    end
end

-- it's like a magic
function pmb_wrench.escape_texture(texture)
	return texture:gsub(".", {["\\"] = "\\\\", ["^"] = "\\^", [":"] = "\\:"})
end


function pmb_wrench.update_hud(player)
    if not pl[player] then return end
    local imagename = "blank.png"
    while pl[player].node do
        local def = minetest.registered_nodes[pl[player].node.name]
        if not def then break end

        if def.inventory_image ~= "" and def.name ~= "air" then
            imagename = def.inventory_image
            break
        end
        if def.wield_image ~= "" and def.name ~= "air" then
            imagename = def.wield_image
            break
        end

        local tiles = def.tiles
        if type(tiles) == "string" then imagename = tiles break end
        for i, tile in pairs(tiles or {}) do
            if type(tile) == "string" then imagename = tile break end
            imagename = tile and tile.name break
        end

        break
    end

    imagename = pmb_wrench.escape_texture(imagename)
    -- cram it into a 16x16, so it doesn't overflow or look bad
    imagename = "[combine:16x16:0,0=\\("..imagename.."\\)"
    if pl[player].node_hud then
        player:hud_change(pl[player].node_hud, "text", imagename)
    else
        pl[player].node_hud = player:hud_add({
            hud_elem_type = "image",
            type = "image",
            alignment = {x=0.5, y=0.5},
            position = {x=0.5, y=0.8},
            name = "pmb_wrench_node",
            text = imagename,
            z_index = 800,
            scale = {x = 8, y = 8},
            offset = {x = 0, y = 0},
        })
    end
    -- this is the layer on top / frame
    local imagename2 = "pmb_wrench_hud_bg.png^[opacity:170"
    if pl[player].node_hud2 then
        player:hud_change(pl[player].node_hud2, "text", imagename2)
    else
        pl[player].node_hud2 = player:hud_add({
            hud_elem_type = "image",
            type = "image",
            alignment = {x=0.5, y=0.5},
            position = {x=0.5, y=0.8},
            name = "pmb_wrench_node2",
            text = imagename2,
            z_index = 801,
            scale = {x = 2, y = 2},
            offset = {x = -4, y = -4},
        })
    end
end


function pmb_wrench.do_building_creative(player, dtime, itemstack)
    if pl[player].build_list then
        minetest.bulk_set_node(pl[player].build_list, pl[player].node)
        pmb_wrench.chat_send_player(player, minetest.colorize("#aaa", "Finished building!"))
        pl[player].build_list = nil
    end
end


function pmb_wrench.do_building(player, dtime, itemstack)
    while pl[player].build_list do
        local creative = pl[player].creative
        if creative then return pmb_wrench.do_building_creative(player, dtime, itemstack) end

        -- only at start of build, after releasing click
        if not pl[player].build_index then
            pl[player].build_index = 0
            pl[player]._timer = 0.6
            minetest.sound_play(("pmb_wrench_clicks"), {
                gain = 0.8,
                pitch = 1,
                object = player,
            })
            return
        end

        -- only place nodes every n seconds
        pl[player]._timer = (pl[player]._timer or 0) - dtime
        if (not creative) and pl[player]._timer > 0 then break end
        pl[player]._timer = 0.05

        pl[player].build_index = (pl[player].build_index or 0) + 1
        local build_pos = pl[player].build_list[pl[player].build_index]
        -- if you reach the end of the stack, quit
        if not build_pos then
            pl[player].build_list = nil
            pl[player].build_index = nil
            pmb_wrench.chat_send_player(player, minetest.colorize("#aaa", "Finished building!"))
            minetest.sound_play(("pmb_wrench_plip"), {
                gain = 0.2,
                pitch = 0.4,
                object = player,
            })
            break
        end


        -- check they actually have the items
        local inv = player:get_inventory()
        if (not creative) and not (inv:contains_item("main", ItemStack(pl[player].node.name))) then
            minetest.sound_play(("pmb_wrench_not_allowed"), {
                gain = 0.4,
                pitch = 0.95,
                object = player,
            })
            pl[player].build_list = nil
            pl[player].build_index = nil
            break
        end

        -- don't overwrite existing nodes, but you are allowed to dig buildable_to nodes
        local node = minetest.get_node(build_pos)
        local nd = minetest.registered_nodes[node.name]
        if (not creative) and not (nd and nd.buildable_to) then
            break
        end

        -- go through inventory and subtract an item
        local found_item = creative
        if not found_item then
            for i=0, inv:get_size("main") do
                local stack = inv:get_stack("main", i)
                if stack:get_name() == pl[player].node.name then
                    found_item = true

                    if creative then break end -- don't use items if in creative
                    stack:take_item(1)
                    inv:set_stack("main", i, stack)
                    break
                end
            end
        end

        if found_item then
            -- don't dig buildable_to node unless in creative
            if (not creative) and (node.name ~= "air") then
                minetest.dig_node(build_pos)
            end
            -- actually place the node
            minetest.set_node(build_pos, pl[player].node)

            -- don't oom
            if not creative then
                pmb_node_update.update_node_propagate(build_pos, "place", player, 15)
                minetest.sound_play(("pmb_wrench_plip"), {
                    gain = 0.1,
                    pitch = 0.5,
                    max_hear_distance = 30,
                    pos = build_pos,
                })
            end
        else
            -- reset and give up building this shape
            pl[player].build_list = nil
            pl[player].build_index = nil
        end

        if not creative then
            break
        end
    end
end


function pmb_wrench.squaredist(p1, p2)
    return (((p1.x - p2.x) ^ 2) + ((p1.y - p2.y) ^ 2) + ((p1.z - p2.z) ^ 2))
end


function pmb_wrench.get_pointed_position(player)
    local eyepos = pmb_wrench.get_eyepos(player)
    local pointed_thing = pmb_wrench.get_pointed_thing(player, eyepos)
    local ret
    local ctrl = player:get_player_control()
    -- allow player to wrench solid blocks too when pressing aux1
    if pointed_thing and ctrl and ctrl.aux1 then
        ret = vector.copy(pointed_thing.under)
    elseif pointed_thing then
        ret = vector.copy(pointed_thing.above)
    end
    -- if not pointing at a node, just use the eyepos
    if not ret then
        ret = eyepos + (player:get_look_dir() * 4)
        return ret, false
    end

    return ret, true
end


function pmb_wrench.set_position(player, n)
    -- move the end marker
    pl[player]["pos"..n] = vector.floor(vector.offset(pl[player]["pos"..n], 0.5, 0.5, 0.5))
    if pl[player].ent[n] then
        pl[player].ent[n].object:set_pos(pl[player]["pos"..n])
    else
        pmb_wrench.add_entity(pl[player]["pos"..n], n, player)
    end
end
