
local large_doors = {}
artifact.large_doors = large_doors

minetest.register_entity(":artifact:large_door_display", {
    initial_properties = {
        visual = "mesh",
        mesh = "artifact_door_large.gltf",
        textures = {"artifact_door_large.png"},
        selectionbox = {
            -1.5, -0.5, -2/16,
             1.5,  2.5,  2/16
        },
        pointable = artifact.debug
    },
    on_activate = function(e, data)
        local pos = e.object:get_pos():round()
        local node = minetest.get_node(pos)
        if not node.name:find "large_door" then
            e.object:remove()
            return
        end
        e.object:set_armor_groups{immortal = 1}
        extend(e, minetest.deserialize(data) or {})
        if e.rotation then
            e.rotation.y = e.rotation.y +math.pi
            e:rotate(e.rotation)
        else
            e.rotation = vector.zero()
        end
        e._name = ""..math.random()
        
        if e._locked == nil then
            e._locked = true
        end
        
        local nm = minetest.get_meta(e.object:get_pos())
        if (node.name:find "_open") and not e._open then
            e:open(true)
        elseif not (node.name:find "_open") and e._open then
            e:close(true)
        end
        -- In case our saved state differs from the node's stored state, e.g. if a trigger
        -- updated an effector in an unloaded area and couldn't get at the display entity,
        -- we should always be sure to match the node meta.
        if nm:get_string("locked") == "true" then
            e._locked = true
        end
        
        if nm:contains("locks") then
            e.locks = minetest.deserialize(nm:get_string("locks"))
        end

        if e.locks then
            e:set_locks(e.locks)
        end
        
        e.collider_a = minetest.add_entity(pos, "display")
        e.collider_a:set_properties {
            physical = true,
            collisionbox = artifact.rotate_selectionbox({
                -0.75, -1.5, -2/16,
                 0.75,  1.5,  2/16
            }, e.rotation)
        }
        e.collider_a:set_attach(e.object, "a")
        
        e.collider_b = minetest.add_entity(pos, "display")
        e.collider_b:set_properties {
            physical = true,
            collisionbox = artifact.rotate_selectionbox({
                -0.75, -1.5, -2/16,
                 0.75,  1.5,  2/16
            }, e.rotation)
        }
        e.collider_b:set_attach(e.object, "b")
        
        large_doors[e.object:get_pos():round():to_string()] = e
    end,
    on_deactivate = function(e)
        large_doors[e.object:get_pos():round():to_string()] = nil
        if e.collider_a then
            e.collider_a:remove()
            e.collider_a = nil
        end
        if e.collider_b then
            e.collider_b:remove()
            e.collider_b = nil
        end
    end,
    get_staticdata = function(e)
        return minetest.serialize{type = e.type, _locked = e._locked, rotation = e.rotation, locks = e.locks}
    end,
    open = function(e)
        -- We're already open, so there's nothing to do.
        if e._open then return end
        e._open = true
        e._animating = true
        e.object:set_animation({x=0,y=1}, 1.25, 0.1, false)
        local pos = e.object:get_pos():round()
        minetest.get_meta(pos):set_string("open", "true")
        
        artifact.play_sound {
            name = "artifact_large_door_open",
            pos = e.object:get_pos(),
            range = 10,
        }
        
        local m = minetest.get_meta(pos)
        local on_open = m:get("on_open")
        if not artifact.debug and on_open then
            if on_open == "play_final_scene" then
                artifact.story.play_final_scene()
            elseif on_open == "play_end_scene" then
                artifact.story.play_end_scene()
            end
            m:set_string("on_open", "")
        end
        
        minetest.after(0.75, function()
            e._animating = nil
        end)
    end,
    close = function(e)
        if not e._open then return end
        e._open = false
        e._animating = true
        e.object:set_animation({x=1,y=2}, 1.25, 0.1, false)
        local pos = e.object:get_pos():round()
        minetest.get_meta(pos):set_string("open", "false")
        
        artifact.play_sound {
            name = "artifact_large_door_close",
            pos = e.object:get_pos(),
            range = 10,
        }
        
        minetest.after(0.75, function()
            e._animating = nil
        end)
    end,
    on_step = function(e)
        -- If we're locked or in a transition state, there's no need to run these checks.
        if e._locked or e._animating then return end
        -- We don't use objects_inside_radius to avoid wasting time finding and ignoring display entities.
        -- Instead, we just do a radius check on each player.
        local pos = e.object:get_pos()
        local found = artifact.sidekick.pos and artifact.sidekick.pos:distance(pos) < 4
        for _, m in pairs(artifact.players) do
            if m.object:get_pos():distance(pos) < 4 then
                found = true
                break
            end
        end
        if found and not e._open then
            e:open()
        elseif not found and e._open then
            e:close()
        end
    end,
    rotate = function(e, rot)
        e.object:set_rotation(rot)
        e.rotation = rot
        e.object:set_properties {
            selectionbox = artifact.rotate_selectionbox({
                -1.5, -0.5, -2/16,
                 1.5,  2.5,  2/16
            }, e.rotation)
        }
        if e.collider_a then
        e.collider_a:set_properties {
            collisionbox = artifact.rotate_selectionbox({
                -0.75, -1.5, -2/16,
                 0.75,  1.5,  2/16
            }, e.rotation)
        }
        end
        if e.collider_b then
        e.collider_b:set_properties {
            collisionbox = artifact.rotate_selectionbox({
                -0.75, -1.5, -2/16,
                 0.75,  1.5,  2/16
            }, e.rotation)
        }
        end
    end,
    -- We include this so we can update the entity's locks without having to unload and reload them.
    on_punch = function(e)
        local locks = minetest.deserialize(minetest.get_meta(e.object:get_pos():round()):get_string("locks"))
        e:set_locks(locks)
    end,
    set_locks = function(e, locks)
        e.locks = locks
        local mod = ""
        local locked = false
        for i, color in ipairs(locks) do
            if not locks[color] then locked = true end
            mod = mod..":"..((i -1) *5 +74)..",0=artifact_lock_"..color.."_"..(locks[color] and "on" or "off")..".png"
        end
        e._locked = locked
        e.object:set_properties {
            textures = {"[combine:128x128:0,0=artifact_door_large.png"..mod}
        }
    end,
})

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)
        local locks = minetest.deserialize(m:get("locks") or "return nil") or {red = false, blue = false, green = false, "red", "green", "blue"}
        minetest.add_entity(pos, "artifact:large_door_display", minetest.serialize{locks = locks}):get_luaentity():rotate(rot)
        m:set_string("locks", minetest.serialize(locks))
    end
end

local function ondestruct(pos)
    large_doors[pos:to_string()].object:remove()
    large_doors[pos:to_string()] = nil
end

local function onsignal(pos, event, channel)
    if event.type == "on" or event.type == "pulse" then
        local e = large_doors[vector.to_string(pos)]
        if e then
            local locks = e.locks
            if locks[channel] ~= nil then
                locks[channel] = true
            end
            artifact.play_sound {
                name = "artifact_lock_light",
                pos = e.object:get_pos(),
                range = 10,
            }
            local idx = table.indexof(locks, channel)
            local rot = artifact.facedir_to_rotation(minetest.get_node(pos).param2)
            minetest.add_particlespawner {
                pos = pos +vector.new(-0.6 +(idx *0.3), 2.25, -0.2):rotate(rot),
                vel = {
                    min = vector.new(-1, 0, -1):rotate(rot),
                    max = vector.new(1, 2, -0.2):rotate(rot),
                },
                acc = vector.new(0, -9.81, 0),
                texture = {
                    name = "[fill:1x1:0,0:"..artifact.colors[channel],
                    alpha_tween = {1, 0}
                },
                glow = 8,
                time = 0.1,
                size = 0.25,
                amount = 20,
                exptime = 0.3
            }
            minetest.add_particlespawner {
                pos = pos +vector.new(-0.6 +(idx *0.3), 2.25, 0.2):rotate(rot),
                vel = {
                    min = vector.new(-1, 0, 0.2):rotate(rot),
                    max = vector.new(1, 2, 1):rotate(rot),
                },
                acc = vector.new(0, -8, 0),
                texture = {
                    name = "[fill:1x1:0,0:"..artifact.colors[channel],
                    alpha_tween = {1, 0}
                },
                glow = 8,
                time = 0.1,
                size = 0.25,
                amount = 20,
                exptime = 0.3
            }
            e:set_locks(locks)
            minetest.get_meta(pos):set_string("locks", minetest.serialize(locks))
        else
            local m = minetest.get_meta(pos)
            local locks = minetest.deserialize(m:get_string("locks"))
            if locks[channel] ~= nil then
                locks[channel] = true
            end
            e:set_locks(locks)
            m:set_string("locks", minetest.serialize(locks))
        end
    end
end

artifact.register_node("large_door", {
    drawtype = "airlike",
    paramtype = "light",
    walkable = false,
    selection_box = {
        type = "fixed",
        fixed = {
            -1.5, -0.5, -2/16,
             1.5,  3.5,  2/16
        }
    },
    paramtype2 = "facedir",
    groups = {call_on_load = 1},
    on_construct = onload,
    on_load = onload,
    on_destruct = ondestruct,
    on_signal = onsignal
})
