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

local pl = pmb_wrench.pl

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

local function 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
pmb_wrench.add_entity = add_entity


local function 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
pmb_wrench.hide_entity = hide_entity


local function 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
pmb_wrench.show_entity = show_entity

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

local function 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 = 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
pmb_wrench.update_hud = update_hud


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


local function 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
pmb_wrench.do_building = do_building

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


local max_dist = 16
local function 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
pmb_wrench.get_pointed_position = get_pointed_position


local function 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
        add_entity(pl[player]["pos"..n], n, player)
    end
end
pmb_wrench.set_position = set_position


pmb_wrench.prototype = {
    on_use = function (itemstack, player, pointed_thing)
        if not pl[player] then pl[player] = pmb_wrench.get_shell(player) end
        minetest.sound_play(("pmb_wrench_plip"), {
            gain = 0.1,
            pitch = 0.6,
            object = player,
        })

        pl[player].pos1 = get_pointed_position(player)

        if pl[player].pos1 then
            pl[player].build_list = nil
            pl[player].build_index = nil
            add_entity(pl[player].pos1, 1, player)
            set_position(player, 1)
            add_entity(pl[player].pos1, 2, player)
        end
    end,
    -- SELECT A NODE
    on_place = function(itemstack, player, pointed_thing)
        if not minetest.is_player(player) then return itemstack end
        minetest.sound_play(("pmb_wrench_plip"), {
            gain = 0.1,
            pitch = 0.8,
            object = player,
        })
        if not pl[player] then pl[player] = pmb_wrench.get_shell(player) end
        pl[player].node = minetest.get_node(pointed_thing.under)
        update_hud(player)
        pl[player].build_list = nil
        pl[player].build_index = nil
        pmb_wrench.chat_send_player(player,
            minetest.colorize("#aaa", "Wrench set to ") ..
            minetest.colorize("#9df", pl[player].node.name or "air")
        )
    end,
    -- SELECT AIR
    on_secondary_use = function(itemstack, player)
        minetest.sound_play(("pmb_wrench_plip"), {
            gain = 0.1,
            pitch = 0.8,
            object = player,
        })
        if not pl[player] then pl[player] = pmb_wrench.get_shell(player) end
        local pt = pmb_wrench.get_pointed_thing(player, nil, true)
        if pt then
            pl[player].node = minetest.get_node(pt.under)
            pmb_wrench.chat_send_player(player,
                minetest.colorize("#aaa", "Wrench set to ") ..
                minetest.colorize("#9df", pl[player].node.name or "air")
            )
            return itemstack
        end
        pl[player].node = {name="air"}
        update_hud(player)
        pmb_wrench.chat_send_player(player,
            minetest.colorize("#aaa", "Wrench set to ") ..
            minetest.colorize("#9df", pl[player].node.name or "air")
        )
        return itemstack
    end,
    -- MAIN
    on_step = function(itemstack, player, dtime)
        if not pl[player] then pl[player] = pmb_wrench.get_shell(player) return end
        local ctrl = player:get_player_control()
        local creative = pl[player].creative

        -- show a preview of where you're placing it when you press a key
        if (not pl[player].pos2) and ctrl and (not ctrl.dig) then
            local pointed, at_node = get_pointed_position(player)
            if at_node or ctrl.sneak then
                pl[player].pos1 = pointed
                set_position(player, 1)
                show_entity(player, 1)
            else
                hide_entity(player, 1)
            end
        elseif (not pl[player].pos2) and ctrl and ctrl.dig then
            pl[player].pos1 = get_pointed_position(player)
            set_position(player, 1)
            show_entity(player, 1)
        end

        if ctrl and ctrl.dig and pl[player].pos1 then
            pl[player].pos2 = get_pointed_position(player)

            -- make sure you're not able to make giant server-crashing cubes:
            local dist = squaredist(pl[player].pos1, pl[player].pos2)
            if (not creative) and (dist > max_dist^2) or dist > 200^2 then
                local dir = vector.direction(pl[player].pos1, pl[player].pos2)
                pl[player].pos2 = vector.add(vector.multiply(dir, max_dist), pl[player].pos1)
            end

            set_position(player, 2)
        end

        -- actually place nodes
        if pl[player].pos2 and ctrl and (not ctrl.dig) then
            pl[player].pos1, pl[player].pos2 = pmb_wrench.sort_positions(pl[player].pos1, pl[player].pos2)
            local nodelist = pmb_wrench.all_nodes_in(pl[player].pos1, pl[player].pos2)
            -- set the list of node positions to place at
            pl[player].build_list = (pl[player].node.name ~= "air" or creative) and nodelist
            pl[player].pos1, pl[player].pos2 = nil, nil
            pmb_wrench.remove_all_ents(player)

            -- if they already have no items for this, just don't bother
            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
            end
            pl[player].pos2 = nil
        end

        -- do the building tick
        do_building(player, dtime, itemstack)
    end,
    on_select = function(itemstack, player)
        if not pl[player] then pl[player] = pmb_wrench.get_shell(player) end
        update_hud(player)
    end,
    on_deselect = function(itemstack, player)
        pmb_wrench.remove_all_ents(player)
        pl[player].pos2 = nil
        pl[player].pos1 = nil
        if pl[player].build_list 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
        end
        if pl[player].node_hud then
            player:hud_remove(pl[player].node_hud)
            player:hud_remove(pl[player].node_hud2)
            pl[player].node_hud = nil
            pl[player].node_hud2 = nil
        end
    end,
}

minetest.register_tool("pmb_wrench:wrench", {
    description = S("Wrench"),
    _tt_color = 5,
    _tt_long_desc = S("Fill tool for faster and more convenient building"),
    _tt_how_to_use =(
        S("[place] to select a node").."\n"..
        S("[dig] and drag to wrench between points").."\n"..
        S("Hold [aux1] to select the \"under\" position").."\n"..
        S("Hold [sneak] to show where you're pointing in air")),
    inventory_image = "pmb_wrench.png",
    wield_image = "pmb_wrench.png",
    groups = { admin_tools = 1 },
    range = 6,
    on_use = pmb_wrench.prototype.on_use,
    on_place = pmb_wrench.prototype.on_place,
    on_secondary_use = pmb_wrench.prototype.on_secondary_use,
    on_step = pmb_wrench.prototype.on_step,
    on_select = pmb_wrench.prototype.on_select,
    on_deselect = pmb_wrench.prototype.on_deselect,
})

if minetest.get_modpath("pmb_tcraft") then
    pmb_tcraft.register_craft({
        output = "pmb_wrench:wrench",
        items = {
            ["pmb_items:iron_bar"] = 10,
        },
    })
end
