
minetest.register_entity(":display", {
    initial_properties = {
        visual = "sprite",
        textures = {"blank.png"},
        pointable = false,
        static_save = false
    },
    on_activate = function(e)
        e.object:set_armor_groups{immortal = 1}
    end
})

artifact.register_craftitem("cancel", {
    inventory_image = "artifact_cancel.png"
})

--- @param receivers: A list of node positions to notify.
--- @param event: The event to send.
function artifact.dispatch_event(receivers, event)
    for _, x in ipairs(receivers) do
        -- Ensure that nodes are available.
        minetest.load_area(x.pos)
        local node = minetest.get_node(x.pos)
        local def = minetest.registered_nodes[node.name]
        if def.on_signal then
            def.on_signal(x.pos, event, x.channel or "gold")
        end
    end
end


include "basics.lua"
include "doors.lua"
include "colors.lua"
include "large_doors.lua"
include "chest.lua"
include "forcefields.lua"


function artifact.load_schematic(dst, path, rot)
    -- There's not a very good way to export without the forcefields' param2, but we can _import_ without them.
    minetest.place_schematic(dst, path..".mts", rot or "0", {["artifact:forcefield"] = "air"})
    local f = io.open(path..".json")
    local meta = minetest.parse_json(f:read("a"))
    f:close()
    -- Load auxiliary metadata.
    for p, m in pairs(meta or {}) do
        local pos = dst +vector.from_string(p)
        -- Transform all position fields back into global space.
        if m.fields and m.fields.receivers then
            local receivers = minetest.deserialize(m.fields.receivers)
            for i, x in ipairs(receivers) do
                x.pos = vector.add(x.pos, dst)
                receivers[i] = x
            end
            m.fields.receivers = minetest.serialize(receivers)
        end
        minetest.get_meta(pos):from_table(m)
        local def = minetest.registered_nodes[minetest.get_node(pos).name]
        if def.on_construct then
            def.on_construct(pos)
        end
    end
end

function artifact.get_schem_size(path)
    local f = io.open(path..".mts", "rb")
        
    local function read_u16(file)
        local data = file:read(2)
        if not data or #data < 2 then return nil end
        local a, b = data:byte(1, 2)
        return a + b
    end
    local magic = f:read(4)
    if magic ~= "MTSM" then
        f:close()
        error("Not a valid .mts file (missing` MTSM` header).")
    end

    local version = read_u16(f)
    if not version or version > 4 then
        f:close()
        error("Unsupported .mts version: "..tostring(version))
    end
    
    local size_x = read_u16(f)
    local size_y = read_u16(f)
    local size_z = read_u16(f)
    
    f:close()
    
    return vector.new(size_x, size_y, size_z)
end

if artifact.debug then
    
    local link_colors = {
        "gold",
        "red",
        "green",
        "blue"
    }
    
    if minetest.get_modpath("rhotator") then
        minetest.override_item("rhotator:screwdriver", {
            pointabilities = {
                nodes = {
                    -- This gets added to everything in debug mode.
                    ["group:dig_immediate"] = true,
                    air = false,
                },
                objects = {
                    -- The display entities should all be immortal.
                    ["group:immortal"] = false
                }
            },
        })
        minetest.override_item("testtools:remover", {
            pointabilities = {
                nodes = {
                    -- This gets added to everything in debug mode.
                    ["group:dig_immediate"] = true,
                    air = false,
                },
                objects = {
                    -- The display entities should all be immortal.
                    ["group:immortal"] = false
                }
            },
        })
        minetest.override_item("testtools:node_meta_editor", {
            pointabilities = {
                nodes = {
                    -- This gets added to everything in debug mode.
                    ["group:dig_immediate"] = true,
                    air = false,
                },
                objects = {
                    -- The display entities should all be immortal.
                    ["group:immortal"] = false
                }
            },
        })
        minetest.override_item("testtools:param2tool", {
            pointabilities = {
                nodes = {
                    -- This gets added to everything in debug mode.
                    ["group:dig_immediate"] = true,
                    air = false,
                },
                objects = {
                    -- The display entities should all be immortal.
                    ["group:immortal"] = false
                }
            },
        })
    end
    
    artifact.register_craftitem("linker_tool", {
        inventory_image = "artifact_linker_tool.png",
        stack_max = 1,
        pointabilities = {
            nodes = {
                -- This gets added to everything in debug mode.
                ["group:dig_immediate"] = true,
                air = false,
            },
            objects = {
                -- The display entities should all be immortal.
                ["group:immortal"] = false
            }
        },
        on_secondary_use = function(s, p, pt)
            local m = s:get_meta()
            -- Just cycle through the colors list.
            local color = link_colors[table.indexof(link_colors, m:get("color") or "gold") %#link_colors +1]
            m:set_string("color", color)
            m:set_string("inventory_image", "[fill:16x16:0,0:"..color.."#00^artifact_linker_tool.png")
            return s
        end,
        on_place = function(s, p, pt)
            local m = artifact.players[p:get_player_name()]
            if pt.type == "node" and m._linker_target then
                local color = s:get_meta():get("color") or "gold"
                local meta = minetest.get_meta(m._linker_target)
                local receivers = minetest.deserialize(meta:get("receivers") or "return {}")
                if m.ctl.sneak then
                    local idx = 0
                    for i, x in ipairs(receivers) do
                        if vector.equals(x.pos, pt.under) then idx = i end
                    end
                    if idx > 0 then
                        table.remove(receivers, idx)
                        m.object:hud_remove(m._linker_receivers[idx])
                        table.remove(m._linker_receivers, idx)
                    end
                else
                    local idx = 0
                    for i, x in ipairs(receivers) do
                        if vector.equals(x.pos, pt.under) then idx = i end
                    end
                    -- Ensure we haven't added this pos already.
                    if idx == 0 then
                        table.insert(receivers, {pos = pt.under, channel = color})
                        table.insert(m._linker_receivers, m.object:hud_add {
                            type = "image_waypoint",
                            scale = {x=3, y=3},
                            world_pos = pt.under,
                            text = "artifact_linker_tool.png^[colorize:"..color..":64"
                        })
                    end
                end
                meta:set_string("receivers", minetest.serialize(receivers))
            end
        end,
        on_use = function(s, p, pt)
            local m = artifact.players[p:get_player_name()]
            if pt.type == "node" then
                m._linker_target = pt.under
                if m._linker_target_hud then
                    m.object:hud_remove(m._linker_target_hud)
                end
                m._linker_target_hud = m.object:hud_add {
                    type = "image_waypoint",
                    scale = {x=3, y=3},
                    world_pos = pt.under,
                    text = "artifact_linker_tool.png"
                }
                if m._linker_receivers then
                    for _, x in ipairs(m._linker_receivers) do
                        m.object:hud_remove(x)
                    end
                end
                m._linker_receivers = {}
                local receivers = minetest.deserialize(minetest.get_meta(m._linker_target):get("receivers") or "return {}")
                for _, x in ipairs(receivers) do
                    table.insert(m._linker_receivers, m.object:hud_add {
                        type = "image_waypoint",
                        scale = {x=3, y=3},
                        world_pos = x.pos,
                        text = "artifact_linker_tool.png^[colorize:"..x.channel..":64"
                    })
                end
            end
        end
    })
    
    -- To make life easier, simply require worldedit in order to export an area.
    if minetest.global_exists("worldedit") then
        minetest.mkdir(minetest.get_worldpath().."/schems")
        minetest.register_chatcommand("export", {
            privs = {server = true},
            params = "<name>",
            description = "Export the selected region as a schematic, with all node metadata stored in an adjacent JSON file.",
            func = function(name, args)
                local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name]
                print(dump(minetest.create_schematic(pos1, pos2, nil, minetest.get_worldpath().."/schems/"..args..".mts")))
                local minp = vector.sort(pos1, pos2)
                local meta = {}
                for _, pos in pairs(minetest.find_nodes_with_meta(pos1, pos2)) do
                    local mt = minetest.get_meta(pos):to_table()
                    mt.fields.initialized = nil
                    -- Transform all position fields into local coordinate space.
                    if mt.fields.receivers then
                        local receivers = minetest.deserialize(mt.fields.receivers)
                        for i, x in ipairs(receivers) do
                            x.pos = vector.subtract(x.pos, minp)
                            receivers[i] = x
                        end
                        mt.fields.receivers = minetest.serialize(receivers)
                    end
                    meta[(pos -minp):to_string()] = mt
                end
                local f = io.open(minetest.get_worldpath().."/schems/"..args..".json", "w")
                f:write(minetest.write_json(meta))
                f:flush()
                f:close()
            end
        })
    end
    
    minetest.register_chatcommand("load", {
        privs = {server = true},
        func = function(name, args)
            artifact.load_schematic(artifact.players[name].pos:round(), minetest.get_worldpath().."/schems/"..args)
        end
    })
end

