// Matrix Construct
//
// Copyright (C) Matrix Construct Developers, Authors & Contributors
// Copyright (C) 2016-2019 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.

///////////////////////////////////////////////////////////////////////////////
//
// m/filter.h
//

bool
ircd::m::match(const room_event_filter &filter,
               const event::idx &event_idx)
{
	m::event::keys::include include_keys;

	if(json::get<"contains_url"_>(filter))
		include_keys.set("content");

	if(json::get<"rooms"_>(filter) || json::get<"not_rooms"_>(filter))
		include_keys.set("room_id");

	if(json::get<"types"_>(filter) || json::get<"not_types"_>(filter))
		include_keys.set("type");

	if(json::get<"senders"_>(filter) || json::get<"not_senders"_>(filter))
		include_keys.set("sender");

	const m::event::fetch event
	{
		std::nothrow, event_idx, m::event::fetch::opts
		{
			include_keys,
		}
	};

	if(unlikely(!event.valid))
		return false;

	return match(filter, event);
}

//TODO: globular expression
//TODO: tribool for contains_url; we currently ignore the false value.
bool
ircd::m::match(const room_event_filter &filter,
               const event &event)
{
	if(json::get<"contains_url"_>(filter) == true)
		if(!json::get<"content"_>(event).has("url"))
			return false;

	for(const json::string room_id : json::get<"not_rooms"_>(filter))
		if(json::get<"room_id"_>(event) == room_id)
			return false;

	if(empty(json::get<"rooms"_>(filter)))
		return match(event_filter{filter}, event);

	for(const json::string room_id : json::get<"rooms"_>(filter))
		if(json::get<"room_id"_>(event) == room_id)
			return match(event_filter{filter}, event);

	return false;
}

bool
ircd::m::match(const event_filter &filter,
               const event::idx &event_idx)
{
	m::event::keys::include include_keys;

	if(json::get<"types"_>(filter) || json::get<"not_types"_>(filter))
		include_keys.set("type");

	if(json::get<"senders"_>(filter) || json::get<"not_senders"_>(filter))
		include_keys.set("sender");

	const m::event::fetch event
	{
		std::nothrow, event_idx, m::event::fetch::opts
		{
			include_keys,
		}
	};

	if(unlikely(!event.valid))
		return false;

	return match(filter, event);
}

//TODO: globular expression
bool
ircd::m::match(const event_filter &filter,
               const event &event)
{
	for(const json::string type : json::get<"not_types"_>(filter))
		if(json::get<"type"_>(event) == type)
			return false;

	for(const json::string sender : json::get<"not_senders"_>(filter))
		if(json::get<"sender"_>(event) == sender)
			return false;

	if(empty(json::get<"senders"_>(filter)) && empty(json::get<"types"_>(filter)))
		return true;

	if(empty(json::get<"senders"_>(filter)))
	{
		for(const json::string type : json::get<"types"_>(filter))
			if(json::get<"type"_>(event) == type)
				return true;

		return false;
	}

	if(empty(json::get<"types"_>(filter)))
	{
		for(const json::string sender : json::get<"senders"_>(filter))
			if(json::get<"sender"_>(event) == sender)
				return true;

		return false;
	}

	return true;
}

//
// filter
//

/// Convenience interface for filters out of common `?filter=` query string
/// arguments. This function expects a raw urlencoded value of the filter
/// query parameter. It detects if the value is an "inline" filter by being
/// a valid JSON object; otherwise it considers the value an ID and fetches
/// the filter stored previously by the user.
std::string
ircd::m::filter::get(const string_view &val,
                     const m::user &user)
{
	if(!val)
		return {};

	const bool is_inline
	{
		startswith(val, "{") || startswith(val, "%7B")
	};

	if(is_inline)
		return util::string(val.size(), [&val]
		(const mutable_buffer &buf)
		{
			return url::decode(buf, val);
		});

	if(!user.user_id)
		return {};

	char idbuf[m::event::STATE_KEY_MAX_SIZE];
	const string_view &id
	{
		url::decode(idbuf, val)
	};

	const m::user::filter filter
	{
		user
	};

	return filter.get(id);
}

//
// filter::filter
//

ircd::m::filter::filter(const user &user,
                        const string_view &filter_id,
                        const mutable_buffer &buf)
{
	const json::object &obj
	{
		user::filter(user).get(buf, filter_id)
	};

	new (this) m::filter
	{
		obj
	};
}

//
// room_filter
//

ircd::m::room_filter::room_filter(const mutable_buffer &buf,
                                  const json::members &members)
:super_type::tuple
{
	json::stringify(mutable_buffer{buf}, members)
}
{
}

//
// state_filter
//

ircd::m::state_filter::state_filter(const mutable_buffer &buf,
                                    const json::members &members)
:super_type::tuple
{
	json::stringify(mutable_buffer{buf}, members)
}
{
}

//
// room_event_filter
//

ircd::m::room_event_filter::room_event_filter(const mutable_buffer &buf,
                                              const json::members &members)
:super_type::tuple
{
	json::stringify(mutable_buffer{buf}, members)
}
{
}

//
// event_filter
//

ircd::m::event_filter::event_filter(const mutable_buffer &buf,
                                    const json::members &members)
:super_type::tuple
{
	json::stringify(mutable_buffer{buf}, members)
}
{
}
