// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2018 Jason Volk <jason@zemos.net>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice is present in all copies. The
// full license for this software is available in the LICENSE file.

ircd::m::room
ircd::m::create(const id::room &room_id,
                const id::user &creator,
                const string_view &preset)
{
	return create(createroom
	{
		{ "room_id",  room_id },
		{ "creator",  creator },
		{ "preset",   preset  },
	});
}

ircd::m::event::id::buf
ircd::m::invite(const room &room,
                const id::user &target,
                const id::user &sender)
{
	json::iov content;
	return invite(room, target, sender, content);
}

ircd::m::event::id::buf
ircd::m::invite(const room &room,
                const user::id &target,
                const user::id &sender,
                json::iov &content)
{
	json::iov event;
	const json::iov::push push[]
	{
		{ event,    { "type",        "m.room.member"  }},
		{ event,    { "sender",      sender           }},
		{ event,    { "state_key",   target           }},
		{ content,  { "membership",  "invite"         }},
	};

	return commit(room, event, content);
}

ircd::m::event::id::buf
ircd::m::redact(const room &room,
                const id::user &sender,
                const id::event &event_id,
                const string_view &reason)
{
	json::iov event;
	const json::iov::push push[]
	{
		{ event,    { "type",       "m.room.redaction"  }},
		{ event,    { "sender",      sender             }},
		{ event,    { "redacts",     event_id           }},
	};

	json::iov content;
	const json::iov::set _reason
	{
		content, !empty(reason),
		{
			"reason", [&reason]() -> json::value
			{
				return reason;
			}
		}
	};

	return commit(room, event, content);
}

ircd::m::event::id::buf
ircd::m::notice(const room &room,
                const string_view &body)
{
	return message(room, me(), body, "m.notice");
}

ircd::m::event::id::buf
ircd::m::notice(const room &room,
                const m::id::user &sender,
                const string_view &body)
{
	return message(room, sender, body, "m.notice");
}

ircd::m::event::id::buf
ircd::m::msghtml(const room &room,
                 const m::id::user &sender,
                 const string_view &html,
                 const string_view &alt,
                 const string_view &msgtype)
{
	return message(room, sender,
	{
		{ "msgtype",         msgtype                       },
		{ "format",          "org.matrix.custom.html"      },
		{ "body",            { alt?: html, json::STRING }  },
		{ "formatted_body",  { html, json::STRING }        },
	});
}

ircd::m::event::id::buf
ircd::m::message(const room &room,
                 const m::id::user &sender,
                 const string_view &body,
                 const string_view &msgtype)
{
	return message(room, sender,
	{
		{ "body",     { body,    json::STRING } },
		{ "msgtype",  { msgtype, json::STRING } },
	});
}

ircd::m::event::id::buf
ircd::m::message(const room &room,
                 const m::id::user &sender,
                 const json::members &contents)
{
	return send(room, sender, "m.room.message", contents);
}

ircd::m::event::id::buf
ircd::m::annotate(const room &room,
                  const id::user &sender,
                  const id::event &target,
                  const string_view &key)
{
	json::iov relates;
	const json::iov::push push
	{
		relates, { "key", key },
	};

	return react(room, sender, target, "m.annotation", relates);
}

ircd::m::event::id::buf
ircd::m::react(const room &room,
               const id::user &sender,
               const id::event &target,
               const string_view &rel_type,
               json::iov &relates)
{
	json::iov content;
	thread_local char buf[event::MAX_SIZE];
	const json::iov::push push[]
	{
		{ relates,  { "event_id",      target                                  }},
		{ relates,  { "rel_type",      rel_type                                }},
		{ content,  { "m.relates_to",  stringify(mutable_buffer{buf}, relates) }},
	};

	return send(room, sender, "m.reaction", content);
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
              const m::id::user &sender,
              const string_view &type,
              const string_view &state_key,
              const json::members &contents)
{
	const size_t contents_count
	{
		std::min(uint(contents.size()), json::object::max_sorted_members)
	};

	json::iov _content;
	json::iov::push content[contents_count]; // 48B each
	return send(room, sender, type, state_key, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
              const m::id::user &sender,
              const string_view &type,
              const string_view &state_key,
              const json::object &contents)
{
	const size_t contents_count
	{
		std::min(uint(contents.size()), json::object::max_sorted_members)
	};

	json::iov _content;
	json::iov::push content[contents_count]; // 48B each
	return send(room, sender, type, state_key, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop

ircd::m::event::id::buf
ircd::m::send(const room &room,
              const m::id::user &sender,
              const string_view &type,
              const string_view &state_key,
              const json::iov &content)
{
	json::iov event;
	const json::iov::push push[]
	{
		{ event,    { "sender",     sender     }},
		{ event,    { "type",       type       }},
		{ event,    { "state_key",  state_key  }},
	};

	return commit(room, event, content);
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
              const m::id::user &sender,
              const string_view &type,
              const json::members &contents)
{
	const size_t contents_count
	{
		std::min(uint(contents.size()), json::object::max_sorted_members)
	};

	json::iov _content;
	json::iov::push content[contents_count]; // 48B each
	return send(room, sender, type, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstack-usage="
ircd::m::event::id::buf
__attribute__((stack_protect))
ircd::m::send(const room &room,
              const m::id::user &sender,
              const string_view &type,
              const json::object &contents)
{
	const size_t contents_count
	{
		std::min(uint(contents.size()), json::object::max_sorted_members)
	};

	json::iov _content;
	json::iov::push content[contents_count]; // 48B each
	return send(room, sender, type, make_iov(_content, content, contents_count, contents));
}
#pragma GCC diagnostic pop

ircd::m::event::id::buf
ircd::m::send(const room &room,
              const id::user &sender,
              const string_view &type,
              const json::iov &content)
{
	json::iov event;
	const json::iov::push push[]
	{
		{ event,    { "sender",  sender  }},
		{ event,    { "type",    type    }},
	};

	return commit(room, event, content);
}

ircd::m::event::id::buf
ircd::m::commit(const room &room,
                json::iov &event,
                const json::iov &contents)
{
	// Set the room_id on the iov
	json::iov::push room_id
	{
		event, { "room_id", room.room_id }
	};

	vm::copts opts
	{
		room.copts?
			*room.copts:
			vm::default_copts
	};

	// Some functionality on this server may create an event on behalf
	// of remote users. It's safe for us to mask this here, but eval'ing
	// this event in any replay later will require special casing.
	opts.non_conform.set(event::conforms::MISMATCH_ORIGIN_SENDER);

	// Don't need this here
	opts.phase.reset(vm::phase::VERIFY);

	return vm::eval
	{
		event, contents, opts
	};
}

ircd::m::id::room::buf
ircd::m::room_id(const id::room_alias &room_alias)
{
	char buf[m::id::MAX_SIZE + 1];
	static_assert(sizeof(buf) <= 256);
	return room_id(buf, room_alias);
}

ircd::m::id::room::buf
ircd::m::room_id(const id::event &event_id)
{
	char buf[m::id::MAX_SIZE + 1];
	static_assert(sizeof(buf) <= 256);
	return room_id(buf, event_id);
}

ircd::m::id::room::buf
ircd::m::room_id(const string_view &mxid)
{
	char buf[m::id::MAX_SIZE + 1];
	static_assert(sizeof(buf) <= 256);
	return room_id(buf, mxid);
}

ircd::m::id::room::buf
ircd::m::room_id(const event::idx &event_idx)
{
	char buf[m::id::MAX_SIZE + 1];
	static_assert(sizeof(buf) <= 256);
	return room_id(buf, event_idx);
}

ircd::m::id::room
ircd::m::room_id(const mutable_buffer &out,
                 const string_view &mxid)
{
	switch(m::sigil(mxid))
	{
		case id::ROOM:
			return id::room{out, mxid};

		case id::USER:
		{
			const m::user::room user_room(mxid);
			return string_view
			{
				data(out), copy(out, user_room.room_id)
			};
		}

		case id::NODE:
		{
			const m::node node(lstrip(mxid, ':'));
			const m::node::room node_room(node);
			return string_view
			{
				data(out), copy(out, node_room.room_id)
			};
		}

		case id::EVENT:
			return room_id(out, id::event{mxid});

		default:
			return room_id(out, id::room_alias{mxid});
	}
}

ircd::m::id::room
ircd::m::room_id(const mutable_buffer &out,
                 const event::idx &event_idx)
try
{
	room::id ret;
	m::get(event_idx, "room_id", [&out, &ret]
	(const room::id &room_id)
	{
		ret = string_view { data(out), copy(out, room_id) };
	});

	return ret;
}
catch(const m::NOT_FOUND &e)
{
	throw m::NOT_FOUND
	{
		"resolving room_id from event_idx :%s",
		e.what(),
	};
}

ircd::m::id::room
ircd::m::room_id(const mutable_buffer &out,
                 const id::event &event_id)
try
{
	room::id ret;
	m::get(event_id, "room_id", [&out, &ret]
	(const room::id &room_id)
	{
		ret = string_view { data(out), copy(out, room_id) };
	});

	return ret;
}
catch(const m::NOT_FOUND &e)
{
	throw m::NOT_FOUND
	{
		"resolving room_id from event_id :%s",
		e.what(),
	};
}

ircd::m::id::room
ircd::m::room_id(const mutable_buffer &out,
                 const id::room_alias &room_alias)
try
{
	room::id ret;
	room::aliases::cache::get(room_alias, [&out, &ret]
	(const room::id &room_id)
	{
		ret = string_view { data(out), copy(out, room_id) };
	});

	return ret;
}
catch(const m::NOT_FOUND &e)
{
	throw m::NOT_FOUND
	{
		"resolving room_id from alias :%s",
		e.what(),
	};
}

bool
ircd::m::exists(const id::room_alias &room_alias,
                const bool &remote_query)
{
	if(room::aliases::cache::has(room_alias))
		return true;

	if(!remote_query)
		return false;

	return room::aliases::cache::get(std::nothrow, room_alias, []
	(const room::id &) noexcept
	{
		// no action
	});
}

int64_t
ircd::m::depth(const id::room &room_id)
{
	return std::get<int64_t>(top(room_id));
}

int64_t
ircd::m::depth(std::nothrow_t,
               const id::room &room_id)
{
	const auto it
	{
		dbs::room_events.begin(room_id)
	};

	if(!it)
		return -1;

	const auto part
	{
		dbs::room_events_key(it->first)
	};

	return std::get<0>(part);
}

ircd::m::event::idx
ircd::m::head_idx(const id::room &room_id)
{
	return std::get<event::idx>(top(room_id));
}

ircd::m::event::idx
ircd::m::head_idx(std::nothrow_t,
                  const id::room &room_id)
{
	const auto it
	{
		dbs::room_events.begin(room_id)
	};

	if(!it)
		return 0;

	const auto part
	{
		dbs::room_events_key(it->first)
	};

	return std::get<1>(part);
}

ircd::m::event::id::buf
ircd::m::head(const id::room &room_id)
{
	return std::get<event::id::buf>(top(room_id));
}

ircd::m::event::id::buf
ircd::m::head(std::nothrow_t,
              const id::room &room_id)
{
	return std::get<event::id::buf>(top(std::nothrow, room_id));
}

std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
ircd::m::top(const id::room &room_id)
{
	const auto ret
	{
		top(std::nothrow, room_id)
	};

	if(std::get<int64_t>(ret) == -1)
		throw m::NOT_FOUND
		{
			"No head for room %s", string_view{room_id}
		};

	return ret;
}

std::tuple<ircd::m::event::id::buf, int64_t, ircd::m::event::idx>
ircd::m::top(std::nothrow_t,
             const id::room &room_id)
{
	const auto it
	{
		dbs::room_events.begin(room_id)
	};

	if(!it)
		return
		{
			event::id::buf{}, -1, 0
		};

	const auto part
	{
		dbs::room_events_key(it->first)
	};

	const int64_t &depth
	{
		int64_t(std::get<0>(part))
	};

	const event::idx &event_idx
	{
		std::get<1>(part)
	};

	std::tuple<event::id::buf, int64_t, event::idx> ret
	{
		event::id::buf{}, depth, event_idx
	};

	m::event_id(std::nothrow, event_idx, [&ret]
	(const event::id &event_id)
	{
		std::get<event::id::buf>(ret) = event_id;
	});

	return ret;
}

ircd::m::id::user::buf
ircd::m::any_user(const room &room,
                  const string_view &host,
                  const string_view &membership)
{
	user::id::buf ret;
	const room::members members{room};
	members.for_each(membership, [&host, &ret]
	(const auto &user_id, const auto &event_idx)
	{
		if(host && user_id.host() != host)
			return true;

		ret = user_id;
		return false;
	});

	return ret;
}

/// Receive the join_rule of the room into buffer of sufficient size.
/// The protocol does not specify a join_rule string longer than 7
/// characters but do be considerate of the future. This function
/// properly defaults the string as per the protocol spec.
ircd::string_view
ircd::m::join_rule(const mutable_buffer &out,
                   const room &room)
{
	static const string_view default_join_rule
	{
		"invite"
	};

	string_view ret
	{
		default_join_rule
	};

	const event::keys::include keys
	{
		"content"
	};

	const m::event::fetch::opts fopts
	{
		keys, room.fopts? room.fopts->gopts : db::gopts{}
	};

	const room::state state
	{
		room, &fopts
	};

	state.get(std::nothrow, "m.room.join_rules", "", [&ret, &out]
	(const m::event &event)
	{
		const auto &content
		{
			json::get<"content"_>(event)
		};

		const json::string &rule
		{
			content.get("join_rule", default_join_rule)
		};

		ret = string_view
		{
			data(out), copy(out, rule)
		};
	});

	return ret;
}

ircd::string_view
ircd::m::version(const mutable_buffer &buf,
                 const room &room)
{
	const auto event_idx
	{
		room.get("m.room.create", "")
	};

	string_view ret;
	m::get(event_idx, "content", [&buf, &ret]
	(const json::object &content)
	{
		const json::string &version
		{
			content.get("room_version", "1"_sv)
		};

		ret = strlcpy
		{
			buf, version
		};
	});

	if(unlikely(!ret))
		throw m::NOT_FOUND
		{
			"Failed to find room %s to query its version",
			string_view{room.room_id}
		};

	return ret;
}

ircd::string_view
ircd::m::version(const mutable_buffer &buf,
                 const room &room,
                 std::nothrow_t)
{
	const auto event_idx
	{
		room.get(std::nothrow, "m.room.create", "")
	};

	string_view ret
	{
		strlcpy{buf, "1"_sv}
	};

	m::get(std::nothrow, event_idx, "content", [&buf, &ret]
	(const json::object &content)
	{
		const json::string &version
		{
			content.get("room_version", "1"_sv)
		};

		ret = strlcpy
		{
			buf, version
		};
	});

	return ret;
}

ircd::string_view
ircd::m::type(const mutable_buffer &buf,
              const room &room)
{
	string_view ret;
	const auto event_idx
	{
		room.get(std::nothrow, "m.room.create", "")
	};

	m::get(std::nothrow, event_idx, "content", [&buf, &ret]
	(const json::object &content)
	{
		const json::string &type
		{
			content.get("type")
		};

		ret = strlcpy
		{
			buf, type
		};
	});

	return ret;
}

bool
ircd::m::contains(const id::room &room_id,
                  const event::idx &event_idx)
{
	return m::query(event_idx, "room_id", [&room_id]
	(const string_view &_room_id) noexcept -> bool
	{
		return _room_id == room_id;
	});
}

ircd::m::id::user::buf
ircd::m::creator(const id::room &room_id)
{
	// Query the sender field of the event to get the creator. This is for
	// future compatibility if the content.creator field gets eliminated.
	// If the sender gets eliminated instead, include "content" in the keys.
	static const event::fetch::opts fopts
	{
		event::keys::include {"sender"}
	};

	const room::state state
	{
		room_id, &fopts
	};

	id::user::buf ret;
	state.get("m.room.create", "", [&ret]
	(const m::event &event)
	{
		ret = m::creator(event);
	});

	return ret;
}

//
// boolean suite
//

/// The only members are from our origin, in any membership state. This
/// indicates we won't have any other federation servers that could possibly
/// be party to anything about this room.
bool
ircd::m::local_only(const room &room)
{
	// Branch to test if any remote users are joined to the room, meaning
	// this result must be false; this is a fast query.
	if(remote_joined(room))
		return false;

	const room::members members
	{
		room
	};

	return members.for_each([]
	(const id::user &user_id)
	{
		return my(user_id);
	});
}

/// Member(s) from our server are presently joined to the room. Returns false
/// if there's a room on the server where all of our users have left. Note that
/// some internal rooms have no memberships at all and this will also be false.
/// This can return true if other servers have memberships in the room too, as
/// long as one of our users is joined.
bool
ircd::m::local_joined(const room &room)
{
	const room::members members
	{
		room
	};

	return !for_each([&members]
	(const homeserver &homeserver)
	{
		// return false to break and return false; true to continue
		return members.empty("join", origin(homeserver));
	});
}

/// Member(s) from another server are presently joined to the room. For example
/// if another user leaves a PM with our user who is still joined, this returns
/// false. This can return true even if the room has no memberships in any
/// state from our server, as long as there's a joined member from a remote.
bool
ircd::m::remote_joined(const room &room)
{
	const room::members members
	{
		room
	};

	return !members.for_each("join", []
	(const id::user &user_id)
	{
		return my(user_id)? true : false; // false to break.
	});
}

bool
ircd::m::visible(const room &room,
                 const string_view &mxid,
                 const event *const &event)
{
	if(event)
		return m::visible(*event, mxid);

	const m::event event_
	{
		json::members
		{
			{ "event_id",  room.event_id  },
			{ "room_id",   room.room_id   },
		}
	};

	return m::visible(event_, mxid);
}

/// Test of the join_rule of the room is the argument.
bool
ircd::m::join_rule(const room &room,
                   const string_view &rule)
{
	char buf[32];
	return join_rule(buf, room) == rule;
}

bool
ircd::m::type(const room::id &room_id,
              const string_view &type_)
{
	const m::room room
	{
		room_id
	};

	const auto event_idx
	{
		room.get(std::nothrow, "m.room.create", "")
	};

	return m::query(std::nothrow, event_idx, "content", false, [&type_]
	(const json::object &content)
	{
		const json::string &type
		{
			content.get("type")
		};

		return type == type_;
	});
}

bool
ircd::m::creator(const room::id &room_id,
                 const user::id &user_id)
{
	const auto creator_user_id
	{
		creator(room_id)
	};

	return creator_user_id == user_id;
}

bool
ircd::m::federated(const id::room &room_id)
{
	static const m::event::fetch::opts fopts
	{
		event::keys::include { "content" },
	};

	const m::room::state state
	{
		room_id, &fopts
	};

	bool ret;
	state.get("m.room.create", "", [&ret]
	(const m::event &event)
	{
		ret = json::get<"content"_>(event).get("m.federate", true);
	});

	return ret;
}

/// Determine if this is an internal room. The following must be satisfied:
///
/// - The room was created by this origin.
/// - The creator was the server itself, not any other user.
bool
ircd::m::internal(const id::room &room_id)
{
	const m::room room
	{
		room_id
	};

	if(!my(room))
		return false;

	const room::state state
	{
		room
	};

	const auto create_idx
	{
		state.get(std::nothrow, "m.room.create", "")
	};

	return m::query(std::nothrow, create_idx, "sender", []
	(const string_view &sender)
	{
		return sender == me();
	});
}

bool
ircd::m::exists(const id::room &room_id)
{
	const m::room::events it
	{
		room_id, 0UL
	};

	if(!it)
		return false;

	if(likely(it.depth() < 2UL))
		return true;

	if(my_host(room_id.host()) && creator(room_id, me()))
		return true;

	return false;
}

bool
ircd::m::exists(const room &room)
{
	return exists(room.room_id);
}

//
// util
//

bool
ircd::m::operator==(const room &a, const room &b)
{
	return !(a != b);
}

bool
ircd::m::operator!=(const room &a_, const room &b_)
{
	const string_view &a{a_.room_id}, &b{b_.room_id};
	return a != b;
}

bool
ircd::m::operator!(const room &a)
{
	return !a.room_id;
}

bool
ircd::m::my(const room &room)
{
	return my(room.room_id);
}

//
// room
//

/// A room index is just the event::idx of its create event.
ircd::m::event::idx
ircd::m::room::index(const room::id &room_id)
{
	const auto ret
	{
		index(room_id, std::nothrow)
	};

	if(!ret)
		throw m::NOT_FOUND
		{
			"No index for room %s", string_view{room_id}
		};

	return ret;
}

ircd::m::event::idx
ircd::m::room::index(const room::id &room_id,
                     std::nothrow_t)
{
	uint64_t depth{0};
	room::events it
	{
		room_id, depth
	};

	return it? it.event_idx() : 0;
}

//
// room::room
//

size_t
ircd::m::room::count()
const
{
	size_t ret(0);
	for_each(event::closure_idx_bool{[&ret]
	(const event::idx &event_idx) noexcept
	{
		++ret;
		return true;
	}});

	return ret;
}

size_t
ircd::m::room::count(const string_view &type)
const
{
	size_t ret(0);
	for_each(type, event::closure_idx_bool{[&ret]
	(const event::idx &event_idx) noexcept
	{
		++ret;
		return true;
	}});

	return ret;
}

size_t
ircd::m::room::count(const string_view &type,
                     const string_view &state_key)
const
{
	size_t ret(0);
	for_each(type, event::closure_idx_bool{[&state_key, &ret]
	(const event::idx &event_idx)
	{
		ret += query(std::nothrow, event_idx, "state_key", [&state_key]
		(const string_view &_state_key) noexcept -> bool
		{
			return state_key == _state_key;
		});

		return true;
	}});

	return ret;
}

bool
ircd::m::room::has(const string_view &type)
const
{
	return get(std::nothrow, type, event::closure{});
}

void
ircd::m::room::get(const string_view &type,
                   const event::closure &closure)
const
{
	if(!get(std::nothrow, type, closure))
		throw m::NOT_FOUND
		{
			"No events of type '%s' found in '%s'",
			type,
			room_id
		};
}

bool
ircd::m::room::get(std::nothrow_t,
                   const string_view &type,
                   const event::closure &closure)
const
{
	bool ret{false};
	for_each(type, event::closure_bool{[&ret, &closure]
	(const event &event)
	{
		if(closure)
			closure(event);

		ret = true;
		return false;
	}});

	return ret;
}

ircd::m::event::idx
ircd::m::room::get(const string_view &type)
const
{
	const event::idx ret
	{
		get(std::nothrow, type)
	};

	if(unlikely(!ret))
		throw m::NOT_FOUND
		{
			"No events of type '%s' found in '%s'",
			type,
			room_id
		};

	return ret;
}

ircd::m::event::idx
ircd::m::room::get(std::nothrow_t,
                   const string_view &type)
const
{
	event::idx ret{0};
	for_each(type, event::closure_idx_bool{[&ret]
	(const event::idx &event_idx) noexcept
	{
		ret = event_idx;
		return false;
	}});

	return ret;
}

ircd::m::event::idx
ircd::m::room::get(const string_view &type,
                   const string_view &state_key)
const
{
	return state(*this).get(type, state_key);
}

ircd::m::event::idx
ircd::m::room::get(std::nothrow_t,
                   const string_view &type,
                   const string_view &state_key)
const
{
	return state(*this).get(std::nothrow, type, state_key);
}

void
ircd::m::room::get(const string_view &type,
                   const string_view &state_key,
                   const event::closure &closure)
const
{
	const state state{*this};
	state.get(type, state_key, closure);
}

bool
ircd::m::room::get(std::nothrow_t,
                   const string_view &type,
                   const string_view &state_key,
                   const event::closure &closure)
const
{
	const state state{*this};
	return state.get(std::nothrow, type, state_key, closure);
}

bool
ircd::m::room::has(const string_view &type,
                   const string_view &state_key)
const
{
	const state state{*this};
	return state.has(type, state_key);
}

void
ircd::m::room::for_each(const event::closure &closure)
const
{
	for_each(string_view{}, closure);
}

bool
ircd::m::room::for_each(const event::closure_bool &closure)
const
{
	return for_each(string_view{}, closure);
}

void
ircd::m::room::for_each(const event::id::closure &closure)
const
{
	for_each(string_view{}, closure);
}

bool
ircd::m::room::for_each(const event::id::closure_bool &closure)
const
{
	return for_each(string_view{}, closure);
}

void
ircd::m::room::for_each(const event::closure_idx &closure)
const
{
	for_each(string_view{}, closure);
}

bool
ircd::m::room::for_each(const event::closure_idx_bool &closure)
const
{
	return for_each(string_view{}, closure);
}

void
ircd::m::room::for_each(const string_view &type,
                        const event::closure &closure)
const
{
	for_each(type, event::closure_bool{[&closure]
	(const event &event)
	{
		closure(event);
		return true;
	}});
}

bool
ircd::m::room::for_each(const string_view &type,
                        const event::closure_bool &closure)
const
{
	event::fetch event
	{
		fopts? *fopts : event::fetch::default_opts
	};

	return for_each(type, event::closure_idx_bool{[&closure, &event]
	(const event::idx &event_idx)
	{
		if(!seek(std::nothrow, event, event_idx))
			return true;

		return closure(event);
	}});
}

void
ircd::m::room::for_each(const string_view &type,
                        const event::id::closure &closure)
const
{
	for_each(type, event::id::closure_bool{[&closure]
	(const event::id &event_id)
	{
		closure(event_id);
		return true;
	}});
}

bool
ircd::m::room::for_each(const string_view &type,
                        const event::id::closure_bool &closure)
const
{
	return for_each(type, event::closure_idx_bool{[&closure]
	(const event::idx &idx)
	{
		bool ret{true};
		m::event_id(std::nothrow, idx, [&ret, &closure]
		(const event::id &event_id)
		{
			ret = closure(event_id);
		});

		return ret;
	}});
}

void
ircd::m::room::for_each(const string_view &type,
                        const event::closure_idx &closure)
const
{
	for_each(type, event::closure_idx_bool{[&closure]
	(const event::idx &idx)
	{
		closure(idx);
		return true;
	}});
}

bool
ircd::m::room::for_each(const string_view &type,
                        const event::closure_idx_bool &closure)
const
{
	static constexpr auto idx
	{
		json::indexof<event, "type"_>()
	};

	auto &column
	{
		dbs::event_column.at(idx)
	};

	events it{*this};
	for(; it; --it)
	{
		const auto &event_idx
		{
			it.event_idx()
		};

		bool match
		{
			empty(type) // allow empty type to always match and bypass query
		};

		if(!match)
			column(byte_view<string_view>(event_idx), std::nothrow, [&match, &type]
			(const string_view &value) noexcept
			{
				match = value == type;
			});

		if(match)
			if(!closure(event_idx))
				return false;
	}

	return true;
}
