
function desk_placer(x, y, placer, version, rotation)
    x = math.floor(x * 4 + 0.5) / 4
    y = math.floor(y * 4 + 0.5) / 4
    placer:static_model("base:teach_objs/desk_two_chair", x, 0.0, y, rotation * (math.pi / 2))
    placer:placement_bound(x - 0.75, y - 0.75, 1.5, 1.5)
    if rotation % 2 == 0 then
        placer:collision_bound(x - 0.5, y - 0.25, 1.0, 0.5)
        placer:selection_bound(x - 0.5, 0.0, y - 0.25, 1.0, 0.5, 0.5)
    else
        placer:collision_bound(x - 0.25, y - 0.5, 0.5, 1.0)
        placer:selection_bound(x - 0.25, 0.0, y - 0.5, 0.5, 0.5, 1.0)
    end
    if not placer:contains_bound(x - 0.75, y - 0.75, 1.5, 1.5) then
        placer:fail("Desk not inside the room")
    end
    if not placer:contains_bound(x - 1.0, y - 1.0, 2.0, 2.0) then
        placer:fail("Desk too close to the wall")
    end
    return 0
end

function chair_placer(x, y, placer, version, rotation)
    x = math.floor(x * 4 + 0.5) / 4
    y = math.floor(y * 4 + 0.5) / 4
    placer:static_model("base:teach_objs/single_chair", x, 0.0, y, rotation * (math.pi / 2))
    local c = math.cos(-rotation * (math.pi / 2))
    local s = math.sin(-rotation * (math.pi / 2))
    placer:placement_bound(
        x - 0.25 + math.min(c, 0) * 0.5 + math.min(s, 0) * 0.25,
        y - 0.25 + math.min(s, 0) * 0.5 + math.min(-c, 0) * 0.25,
        0.5 + math.abs(c) * 0.5 + math.abs(s) * 0.25,
        0.5 + math.abs(s) * 0.5 + math.abs(c) * 0.25
    )
    placer:selection_bound(x - 0.2, 0.0, y - 0.2, 0.4, 0.4, 0.4)
    if not placer:contains_bound(x - 0.25, y - 0.25, 0.5, 0.5) then
        placer:fail("Chair not inside the room")
    end
    if not placer:contains_bound(x - 0.5, y - 0.5, 1.0, 1.0) then
        placer:fail("Chair too close to the wall")
    end
    return 0
end

function bench_placer(x, y, placer, version, rotation)
	local lx, ly = math.floor(x), math.floor(y)
	local nearest, nearest_dist = nil, math.huge
	for _, dir in ipairs(direction.ALL) do
		if level.get_wall(lx, ly, dir) ~= "none" then
			local ox, oy = direction.offset(dir)
			local dx = (lx + 0.5 + ox * 0.5) - x
			local dy = (ly + 0.5 + oy * 0.5) - y
			local distance = math.sqrt(dx*dx + dy*dy)
			if distance < nearest_dist then
				nearest = dir
				nearest_dist = distance
			end
		end
	end

	if nearest == nil then
        local rot = rotation % 4
        if rot == 0 then
            nearest = "north"
        elseif rot == 1 then
            nearest = "east"
        elseif rot == 2 then
            nearest = "south"
        else
            nearest = "west"
        end
	end

    local ox, oy = direction.offset(nearest)

    local valid = level.get_wall(lx, ly, nearest) ~= "door"
    if not valid then
        placer:fail("Blocked by door")
    end
    local rotation = 0.0
    if nearest == "north" then
        rotation = math.pi
    elseif nearest == "east" then
        rotation = math.pi * 1.5
    elseif nearest == "west" then
        rotation = math.pi / 2.0
    end
    placer:static_model("base:external/bench", lx + 0.5 + ox * 0.26, 0.0, ly + 0.5 + oy * 0.26, rotation)
    placer:selection_bound(lx + 0.1, 0.0, ly + 0.1, 0.8, 0.5, 0.8)

    placer:placement_bound(lx, ly, 1.0, 1.0)

    if not placer:in_bounds(lx,ly) or not placer:owns_tile(lx, ly) then
        placer:fail("Must be attached to the room")
    end
    return 0
end

function sofa_placer(x, y, placer, version, rotation)
    local lx, ly = math.floor(x * 2.0) * 0.5, math.floor(y * 2.0) * 0.5
    local nearest
    local rot = rotation % 4
    local rotation = 0.0
    if rot == 0 then
        nearest = "north"
        rotation = math.pi
    elseif rot == 1 then
        nearest = "east"
        rotation = math.pi * 1.5
    elseif rot == 2 then
        nearest = "south"
        rotation = 0.0
    else
        nearest = "west"
        rotation = math.pi / 2.0
    end

    local ox, oy = direction.offset(nearest)

    local valid = level.get_wall(lx, ly, nearest) ~= "door"
    if not valid then
        placer:fail("Blocked by door")
    end
    placer:static_model("base:staff_room/sofa", lx + 0.5 + ox * 0.20, 0.0, ly + 0.5 + oy * 0.20, rotation)
    placer:selection_bound(
        lx - math.abs(oy) * 0.5, 0.0, ly - math.abs(ox) * 0.5,
        1.0 + math.abs(oy), 0.5, 1.0 + math.abs(ox)
    )

    placer:placement_bound(
        lx - math.abs(oy) * 0.5, ly - math.abs(ox) * 0.5,
        1.0 + math.abs(oy), 1.0 + math.abs(ox)
    )

    if not placer:contains_bound(
        lx - math.abs(oy) * 0.5, ly - math.abs(ox) * 0.5,
        1.0 + math.abs(oy), 1.0 + math.abs(ox)
    ) then
        placer:fail("Must be attached to the room")
    end
    return 0
end

function counter_placer(x, y, placer, version, rotation)
    x = math.floor(x)
    y = math.floor(y)
    placer:static_model("base:shop/counter_food", x + 0.5, 0.0, y + 0.5, rotation * (math.pi / 2))
    local c = math.floor(math.cos(-rotation * (math.pi / 2)) + 0.5)
    local s = math.floor(math.sin(-rotation * (math.pi / 2)) + 0.5)
    placer:collision_bound(
        x - 0.25 * math.min(-c, 0),
        y - 0.25 * math.min(-s, 0),
        0.75 + 0.25 * math.abs(s),
        0.75 + 0.25 * math.abs(c)
    )
    placer:placement_bound(
        x - math.abs(c),
        y - math.abs(s),
        1 + 2 * math.abs(c),
        1 + 2 * math.abs(s)
    )
    placer:selection_bound(x, 0.0, y, 1.0, 0.5, 1.0)
    if not placer:contains_bound(
        x - math.abs(c),
        y - math.abs(s),
        1 + 2 * math.abs(c),
        1 + 2 * math.abs(s)
    ) then
        placer:fail("Counter not inside the room")
    end
    return 0
end

function plant_placer(x, y, placer, version, rotation)
    local mdl = placer.parameters['model']
    local texture = placer.parameters['texture']
    local check = placer.parameters['check']
    local fail_reason = placer.parameters['fail_reason'] or "Tile must be empty"
    x = math.floor(x * 4) / 4 - (1/4)
    y = math.floor(y * 4) / 4 - (1/4)

    local current = level.get_tile(x, y)

    if texture == nil then
        placer:static_model(mdl, x + (1/4), 0.0, y + (1/4), rotation * (math.pi / 2))
    else
        placer:static_model_tex(mdl, texture, x + (1/4), 0.0, y + (1/4), rotation * (math.pi / 2))
    end
    placer:collision_bound(
        x, y,
        1/2,
        1/2
    )
    placer:placement_bound(
        x, y,
        1/2,
        1/2
    )
    placer:selection_bound(
        x, 0.0, y,
        1/2,
        1/2,
        1/2
    )
    if not placer:contains_bound(
        x, y,
        1/2,
        1/2
    ) then
        placer:fail("Plant not inside the room")
    end

    if check ~= null and not current.has_property(check) then
        placer:fail(fail_reason)
    end

    return 0
end

function cafe_table_placer(x, y, placer, version, rotation)
    x = math.floor(x * 4) / 4 - (1/4)
    y = math.floor(y * 4) / 4 - (1/4)
    placer:static_model("base:shop/cafe_table", x + (1/4), 0.0, y + (1/4), 0)
    placer:collision_bound(
        x, y,
        1/2,
        1/2
    )
    placer:placement_bound(
        x - 1/2, y - 1/2,
        1.5,
        1.5
    )
    placer:selection_bound(
        x - 1/2, 0.0, y - 1/2,
        1.5,
        1/2,
        1.5
    )
    if not placer:contains_bound(
        x - 1/2, y - 1/2,
        1.5,
        1.5
    ) then
        placer:fail("Plant not inside the room")
    end
    return 0
end

function waiting_area_placer(x, y, placer)
    x = math.floor(x)
    y = math.floor(y)
    if not placer:owns_tile(x, y) then
        placer:fail("Must be placed on a free space")
        return
    end

    placer:set_floor(x, y, "waiting_area")
    placer:selection_bound(x, 0.0, y, 1.0, 0.05, 1.0)
    placer:placement_bound(x, y, 1, 1)

    if not placer:contains_bound(x, y, 1, 1) then
        placer:fail("Must be placed within a building")
        return
    end
    return 0
end

function floor_placer(x, y, placer)
    x = math.floor(x)
    y = math.floor(y)

    local tile = placer:absolute_name(placer.parameters['tile'])
    local check = placer.parameters['check']
    local disable_building_walls = (placer.parameters['disable_building_walls'] or "true") == "true"
    local fail_reason = placer.parameters['fail_reason'] or "Tile must be empty"

    local current = level.get_tile(x, y)

    placer:blocks_tile(x, y)
    placer:selection_bound(x, 0.0, y, 1.0, 0.05, 1.0)

    if placer:owns_tile(x, y) and current.has_property(check) then
        placer:set_floor(x, y, tile)

        if level.get_room_type_at(x, y) == "base:building" and disable_building_walls then
            placer:set_no_walls(x, y)
        end
    else
        placer:remove_on_error()
        if not placer:owns_tile(x, y) then
            placer:fail("Tile must owned by the room")
        else
            placer:fail(fail_reason)
        end
    end

    if not placer:contains_bound(x, y, 1, 1) then
        placer:fail("Must be placed within a building")
        return
    end
    return 0
end

function bush_placer(x, y, placer, version, rotation)
    local mdl = placer.parameters['model']
    x = math.floor(x)
    y = math.floor(y)
    placer:static_model(mdl, x + 0.5, 0.0, y + 0.5, rotation * (math.pi / 2))
    placer:collision_bound(
        x, y,
        1, 1
    )
    placer:placement_bound(
        x, y,
        1, 1
    )
    placer:selection_bound(
        x, 0.0, y,
        1,
        1/2,
        1
    )
    if not placer:contains_bound(
        x, y,
        1,
        1
    ) then
        placer:fail("Bush not inside the room")
    end
    return 0
end

function pond_placer(x, y, placer)
    x = math.floor(x)
    y = math.floor(y)


    local current = level.get_tile(x, y)
    if not placer:owns_tile(x, y) or not current.has_property("grass") then
        placer:fail("Must be placed on a free space")
        return
    end

    if current.get_name() == tile then
        placer:fail("Tile already placed here")
        return
    end

    if level.get_room_type_at(x, y) == "base:building" then
        placer:set_no_walls(x, y)
    end

    placer:set_floor(x, y, "garden_pond")
    placer:static_model("garden/pond_water", x + 0.5, -0.01, y + 0.5, 0.0)
    placer:selection_bound(x, 0.0, y, 1.0, 0.05, 1.0)
    placer:collision_bound(x, y, 1.0, 1.0)
    placer:placement_bound(x, y, 1.0, 1.0)

    if not placer:contains_bound(x, y, 1, 1) then
        placer:fail("Must be placed within a building")
        return
    end
    return 0
end