/*

Copyright (c) 2019 Paul-Louis Ageneau
Copyright (c) 2020-2021, Arvid Norberg
Copyright (c) 2020, Paul-Louis Ageneau
Copyright (c) 2021, Alden Torres
All rights reserved.

You may use, distribute and modify this code under the terms of the BSD license,
see LICENSE file.
*/

#ifndef TORRENT_WEBSOCKET_TRACKER_CONNECTION_HPP_INCLUDED
#define TORRENT_WEBSOCKET_TRACKER_CONNECTION_HPP_INCLUDED

#include "libtorrent/config.hpp"

#if TORRENT_USE_RTC

#include "libtorrent/aux_/rtc_signaling.hpp" // for rtc_offer and rtc_answer
#include "libtorrent/aux_/websocket_stream.hpp"
#include "libtorrent/error_code.hpp"
#include "libtorrent/io_context.hpp"
#include "libtorrent/peer_id.hpp"
#include "libtorrent/aux_/resolver_interface.hpp"
#include "libtorrent/aux_/tracker_manager.hpp" // for tracker_connection
#include "libtorrent/aux_/ssl.hpp"

#include <boost/beast/core/flat_buffer.hpp>

#include <deque>
#include <map>
#include <memory>
#include <tuple>
#include <variant>
#include <optional>

namespace libtorrent::aux {

struct tracker_answer
{
	sha1_hash info_hash;
	peer_id pid;
	aux::rtc_answer answer;
};

struct TORRENT_EXTRA_EXPORT websocket_tracker_connection : tracker_connection
{
	friend class tracker_manager;

	websocket_tracker_connection(
		io_context& ios
		, tracker_manager& man
		, tracker_request const& req
		, std::weak_ptr<request_callback> cb);
	~websocket_tracker_connection() override = default;

	void start() override;
	void close() override;

	bool is_started() const;
	bool is_open() const;

	// aborts every non-stopped-event request on this connection (it may
	// multiplex several torrents' requests). Returns true if a
	// stopped-event request remains, meaning the connection must stay open.
	bool prune_non_stopped_requests();

	void queue_request(tracker_request req, std::weak_ptr<request_callback> cb);
	void queue_answer(tracker_answer ans);

private:
	std::shared_ptr<websocket_tracker_connection> shared_from_this()
	{
		return std::static_pointer_cast<websocket_tracker_connection>(
			tracker_connection::shared_from_this());
	}

	void send_pending();
	void do_send(tracker_request const& req);
	void do_send(tracker_answer const& ans);
	void do_read();
	void on_timeout(error_code const& ec) override;
	void on_connect(error_code const& ec);
	void on_read(error_code ec, std::size_t bytes_read);
	void on_write(error_code const& ec, std::size_t bytes_written);
	// wraps tracker_connection::fail
	void fail(error_code const& ec, operation_t op);
	// does the actual work of close(); takes the real failure reason so
	// callers that just called fail(ec, op) can report it to every
	// multiplexed torrent, instead of the generic reason close() uses on
	// its own (e.g. session shutdown, where there is no specific error)
	void close(error_code const& ec, operation_t op);

	io_context& m_io_context;
	ssl::context* m_ssl_context;
	std::shared_ptr<aux::websocket_stream> m_websocket;
	boost::beast::flat_buffer m_read_buffer{RTC_MAX_MESSAGE_SIZE};
	std::string m_write_data;

	using tracker_message = std::variant<tracker_request, tracker_answer>;
	std::deque<std::tuple<tracker_message, std::weak_ptr<request_callback>>> m_pending;

	// pending is true from the point a request is sent until its first
	// tracker_response is delivered. close() reports an error for any
	// entry still pending, so a torrent multiplexed onto this connection
	// (i.e. not the most recently sent request) still gets notified when
	// the connection is torn down before it received a response. req is
	// the entry's own request, since it may not be the most recently sent
	// request on this connection (tracker_req()).
	struct callback_entry
	{
		std::weak_ptr<request_callback> cb;
		tracker_request req;
		bool pending = true;
	};
	// on_read() holds an iterator into m_callbacks across a reentrant call
	// into cb->on_rtc_offer()/on_rtc_answer(), which may indirectly insert
	// into this same map (e.g. by queuing another request on this
	// connection); this relies on stable iterators. Do not change this to
	// an unordered_map without also fixing on_read() to not hold an
	// iterator across that call.
	std::map<sha1_hash, callback_entry> m_callbacks;
	std::map<sha1_hash, int> m_offer_quota;

	bool m_sending = false;
};

struct websocket_tracker_response {
	sha1_hash info_hash;
	std::optional<tracker_response> resp;
	std::optional<aux::rtc_offer> offer;
	std::optional<aux::rtc_answer> answer;
};

TORRENT_EXTRA_EXPORT std::variant<websocket_tracker_response, std::string>
	parse_websocket_tracker_response(span<char const> message, error_code &ec);

}

#endif // TORRENT_USE_RTC

#endif // TORRENT_WEBSOCKET_TRACKER_CONNECTION_HPP_INCLUDED
