// 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.

using namespace ircd;

mapi::header
IRCD_MODULE
{
	"Federation :Query"
};

const string_view
query_description
{R"(
Performs a single query request on the receiving homeserver. 
The Query Type part of the path specifies the kind of query 
being made, and its query arguments have a meaning specific to 
that kind of query. The response is a JSON-encoded object whose 
meaning also depends on the kind of query.
)"};

m::resource
query_resource
{
	"/_matrix/federation/v1/query/",
	{
		query_description,
		resource::DIRECTORY
	}
};

static m::resource::response
get__query_profile(client &client,
                   const m::resource::request &request);

static m::resource::response
get__query_directory(client &client,
                     const m::resource::request &request);

m::resource::response
get__query(client &client,
           const m::resource::request &request)
{
	const auto type
	{
		request.parv[0]
	};

	if(type == "profile")
		return get__query_profile(client, request);

	if(type == "directory")
		return get__query_directory(client, request);

	throw m::NOT_FOUND
	{
		"Query type not found."
	};
}

m::resource::method
method_get
{
	query_resource, "GET", get__query,
	{
		method_get.VERIFY_ORIGIN
	}
};

m::resource::response
get__query_profile(client &client,
                   const m::resource::request &request)
{
	m::user::id::buf user_id
	{
		url::decode(user_id, request.query.at("user_id"))
	};

	char field_buf[512];
	const string_view field
	{
		url::decode(field_buf, request.query["field"])
	};

	const m::user user
	{
		user_id
	};

	const m::user::profile profile
	{
		user
	};

	if(!empty(field))
	{
		profile.get(field, [&client]
		(const string_view &field, const string_view &value)
		{
			m::resource::response
			{
				client, json::members
				{
					{ field, value }
				}
			};
		});

		return {};
	}

	m::resource::response::chunked response
	{
		client, http::OK
	};

	json::stack out
	{
		response.buf, response.flusher()
	};

	json::stack::object top
	{
		out
	};

	profile.for_each([&top]
	(const string_view &key, const string_view &val)
	{
		json::stack::member
		{
			top, key, val
		};

		return true;
	});

	return response;
}

m::resource::response
get__query_directory(client &client,
                     const m::resource::request &request)
{
	m::room::alias::buf room_alias
	{
		url::decode(room_alias, request.query.at("room_alias"))
	};

	const auto room_id
	{
		m::room_id(room_alias)
	};

	const unique_buffer<mutable_buffer> buf
	{
		4_KiB
	};

	json::stack out{buf};
	json::stack::object top
	{
		out
	};

	json::stack::member
	{
		top, "room_id", room_id
	};

	json::stack::array servers
	{
		top, "servers"
	};

	servers.append(my_host());
	if(visible(m::room(room_id), request.node_id))
	{
		static const size_t max_servers
		{
			#ifdef NAME_MAX
				size(buf) / NAME_MAX - 2
			#else
				size(buf) / rfc1035::NAME_MAX - 2
			#endif
		};

		const m::room::origins origins
		{
			room_id
		};

		size_t i(0);
		origins.for_each(m::room::origins::closure_bool{[&i, &servers]
		(const string_view &origin)
		{
			if(my_host(origin))
				return true;

			if(m::fed::avail(origin))
				return true;

			servers.append(origin);
			return ++i < max_servers;
		}});
	}

	servers.~array();
	top.~object();
	return m::resource::response
	{
		client, json::object
		{
			out.completed()
		}
	};
}
