
local doors = {}

function artifact.rotate_selectionbox(box, rot)
    local a = vector.new(box[1], box[2], box[3]):rotate(rot)
    local b = vector.new(box[4], box[5], box[6]):rotate(rot)
    return {
        a.x, a.y, a.z,
        b.x, b.y, b.z
    }
end

minetest.register_entity(":artifact:door", {
    initial_properties = {
        visual = "mesh",
        mesh = "artifact_door.gltf",
        textures = {"artifact_door_wood.png"},
        selectionbox = {
            -0.5, -0.5, -0.5,
             0.5,  1.5, -6/16
        },
        physical = true
    },
    _interact_time = 0.2,
    _interact_marker_offset = function(e)
        return (e._open and vector.new(e.inverted and 0.5 or -0.5, 0.5, 0) or vector.new(0, 0.5, 0.5)):rotate(e.object:get_rotation())
    end,
    box_open_normal = {
        -0.5, -0.5, -0.5,
        -6/16, 1.5,  0.5
    },
    box_open_inverted = {
        0.5, -0.5, -0.5,
        6/16, 1.5,  0.5
    },
    box_closed_normal = {
        -0.5, -0.5, 0.5,
         0.5,  1.5, 6/16
    },
    on_activate = function(e, data)
        local node = minetest.get_node(e.object:get_pos())
        if not node.name:find "door" then
            e.object:remove()
            return
        end
        e.object:set_armor_groups{immortal = 1}
        extend(e, minetest.deserialize(data) or {})
        if e.type == "iron" then
            if e._locked == nil then
                e._locked = true
            end
            e.object:set_properties {
                textures = {"artifact_door_iron.png"},
            }
        end
        if e.rotation then
            e:rotate(e.rotation)
        else
            e:rotate(vector.zero())
        end
        e._name = ""..math.random()
        
        local nm = minetest.get_meta(e.object:get_pos())
        local open = nm:get("open") == "true"
        if open and not e._open then
            e:open(true)
        elseif not open and e._open then
            e:close(true)
        end
        if nm:get_string("locked") == "true" then
            e._locked = true
        end
        if e._locked then
            e._no_interact = true
        end
        if e.inverted then e:invert() end
        doors[e.object:get_pos():round():to_string()] = e
    end,
    on_deactivate = function(e)
        doors[e.object:get_pos():round():to_string()] = nil
    end,
    on_interact = function(e)
        if e._locked then return end
        if e._open then
            e:close()
        else
            e:open()
        end
    end,
    open = function(e, snap)
        if e._open then return end
        e._open = true
        e._no_interact = true
        e._name = ""..math.random()
        e.object:set_animation({x=snap and 0.5 or 0,y=0.5}, 1.5, 0.1, false)
        minetest.after(snap and 0 or 0.1, function()
            local pos = e.object:get_pos():round()
            minetest.get_meta(pos):set_string("open", "true")
            local box = artifact.rotate_selectionbox(e.inverted and e.box_open_inverted or e.box_open_normal, e.rotation)
            e.object:set_properties {
                selectionbox = box,
                collisionbox = table.copy(box)
            }
        end)
        if not e._locked then
            minetest.after(snap and 0 or 0.25, function()
                e._no_interact = nil
            end)
        end
    end,
    close = function(e, snap)
        if not e._open then return end
        e._open = nil
        e._no_interact = true
        e._name = ""..math.random()
        e.object:set_animation({x=snap and 1 or 0.5,y=1}, 1.5, 0.1, false)
        minetest.after(snap and 0 or 0.1, function()
            local pos = e.object:get_pos():round()
            minetest.get_meta(pos):set_string("open", "false")
            local box = artifact.rotate_selectionbox(e.box_closed_normal, e.rotation)
            e.object:set_properties {
                selectionbox = box,
                collisionbox = box
            }
        end)
        if not e._locked then
            minetest.after(snap and 0 or 0.25, function()
                e._no_interact = nil
            end)
        end
    end,
    invert = function(e)
        e.inverted = true
        local box = artifact.rotate_selectionbox(e.box_closed_normal, e.rotation)
        e.object:set_properties {
            mesh = "artifact_door_inverted.gltf",
            selectionbox = box,
            collisionbox = box
        }
    end,
    get_staticdata = function(e)
        return minetest.serialize{type = e.type, _locked = e._locked, rotation = e.rotation, inverted = e.inverted}
    end,
    on_punch = artifact.debug and function(e)
        local nm = minetest.get_meta(e.object:get_pos())
        if nm:get_string("locked") == "true" then
            e._locked = true
        end
        if e._locked then
            e._no_interact = true
        end
    end or nil,
    unlock = function(e)
        if e._locked then
            e._locked = false
            e._no_interact = nil
        end
    end,
    rotate = function(e, rot)
        e.object:set_rotation(rot)
        e.rotation = rot
        local box = artifact.rotate_selectionbox(e._open and (e.inverted and e.box_open_inverted or e.box_open_normal) or e.box_closed_normal, e.rotation)
        e.object:set_properties {
            selectionbox = box,
            collisionbox = box
        }
    end,
    on_whack = function(e)
        if e.type == "wood" then
            local pos = e.object:get_pos():round()
            minetest.remove_node(pos)
            minetest.add_particlespawner {
                pos = {
                    min = pos:offset(-0.5, -0.5, -0.5),
                    max = pos:offset(0.5, 1.5, 0.5)
                },
                vel = {
                    min = vector.new(-1, 0, -1) *1.5,
                    max = vector.new(1, 2, 1) *1.5
                },
                acc = vector.new(0,-9.81,0),
                collisiondetection = true,
                amount = 50,
                texture = "artifact_door_wood.png^[sheet:2x8:0,3",
                time = 0.1
            }
            return true
        end
    end
})

local function register_basic_door(type)
    local function onload(pos)
        local m = minetest.get_meta(pos)
        -- Dynamically initialize doors that were mapgen'd in.
        if not m:contains("initialized") then
            m:set_string("initialized", "true")
            local rot = artifact.facedir_to_rotation(minetest.get_node(pos).param2)
            minetest.add_entity(pos, "artifact:door", minetest.serialize{type = type, rotation = rot})
        end
    end
    local function ondestruct(pos, reason)
        doors[pos:to_string()].object:remove()
        doors[pos:to_string()] = nil
    end
    local function onsignal(pos, event)
        local door = doors[vector.to_string(pos)]
        if event.type == "on" then
            if door then
                door:open()
            else
                local node = minetest.get_node(pos)
                node.name = "door_"..e.type.."_open"
                minetest.swap_node(pos, node)
            end
        elseif event.type == "off" then
            if door then
                door:close()
            else
                local node = minetest.get_node(pos)
                node.name = "door_"..e.type.."_open"
                minetest.swap_node(pos, node)
            end
        end
    end
    artifact.register_node("door_"..type, {
        drawtype = "airlike",
        walkable = false,
        selection_box = {
            type = "fixed",
            fixed = {
                -0.5, -0.5, 0.5,
                 0.5,  1.5, 6/16
            }
        },
        paramtype2 = "facedir",
        tiles = {"artifact_door_"..type..".png"},
        use_texture_alpha = "clip",
        paramtype = "light",
        pointable = false,
        groups = {call_on_load = 1, whackable = type == "wood" and 1 or nil},
        on_construct = onload,
        on_destruct = ondestruct,
        on_load = onload,
        on_signal = onsignal,
        on_place = function(s, p, pt)
            local out, pos = minetest.item_place_node(s, p, pt)
            if artifact.players[p:get_player_name()].ctl.sneak then
                minetest.get_meta(pos):set_string("inverted", "true")
                doors[pos:to_string()]:invert()
            end
            return out
        end,
        on_rotate = function(pos, node, p, click, param2)
            node.param2 = param2
            minetest.swap_node(pos, node)
            local rot = artifact.facedir_to_rotation(param2)
            doors[pos:to_string()]:rotate(rot)
            return true
        end
    })
end

register_basic_door("wood")
register_basic_door("iron")

