/*

Copyright (c) 2010, 2014-2020, 2022, Arvid Norberg
Copyright (c) 2016-2017, Alden Torres
All rights reserved.

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

#include "test.hpp"
#include "libtorrent/settings_pack.hpp"
#include "libtorrent/aux_/session_settings.hpp"
#include "libtorrent/entry.hpp"
#include "libtorrent/bencode.hpp"
#include "libtorrent/bdecode.hpp"
#include <iostream>

using namespace lt;
using namespace lt::aux;

TORRENT_TEST(default_settings)
{
	aux::session_settings sett;

	entry e;
	save_settings_to_dict(non_default_settings(sett), e.dict());
	// all default values are supposed to be skipped
	// by save_settings
	TEST_EQUAL(e.dict().size(), 0);

#if TORRENT_USE_IOSTREAM
	if (e.dict().size() > 0)
		std::cout << e << std::endl;
#endif
}

TORRENT_TEST(default_settings2)
{
	aux::session_settings sett;

	settings_pack def = default_settings();

	for (int i = 0; i < settings_pack::num_string_settings; ++i)
	{
		TEST_EQUAL(sett.get_str(settings_pack::string_type_base + i)
			, def.get_str(settings_pack::string_type_base + i));
	}

	for (int i = 0; i < settings_pack::num_int_settings; ++i)
	{
		TEST_EQUAL(sett.get_int(settings_pack::int_type_base + i)
			, def.get_int(settings_pack::int_type_base + i));
	}

	for (int i = 0; i < settings_pack::num_bool_settings; ++i)
	{
		TEST_EQUAL(sett.get_bool(settings_pack::bool_type_base + i)
			, def.get_bool(settings_pack::bool_type_base + i));
	}
}

TORRENT_TEST(apply_pack)
{
	aux::session_settings sett;
	settings_pack sp;
	sp.set_int(settings_pack::max_out_request_queue, 1337);

	TEST_CHECK(sett.get_int(settings_pack::max_out_request_queue) != 1337);

	apply_pack(&sp, sett);

	TEST_EQUAL(sett.get_int(settings_pack::max_out_request_queue), 1337);
	entry e;
	save_settings_to_dict(non_default_settings(sett), e.dict());
	TEST_EQUAL(e.dict().size(), 1);

	std::string out;
	bencode(std::back_inserter(out), e);
	TEST_EQUAL(out, "d21:max_out_request_queuei1337ee");
}

TORRENT_TEST(sparse_pack)
{
	settings_pack pack;
	TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), false);

	pack.set_bool(settings_pack::send_redundant_have, true);

	TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), true);
	TEST_EQUAL(pack.has_val(settings_pack::user_agent), false);
	TEST_EQUAL(pack.get_bool(settings_pack::send_redundant_have), true);
}

TORRENT_TEST(test_name)
{
#define TEST_NAME(n) \
	TEST_EQUAL(setting_by_name(#n), settings_pack:: n); \
	TEST_EQUAL(name_for_setting(settings_pack:: n), std::string(#n))

#if TORRENT_ABI_VERSION == 1
	TEST_NAME(contiguous_recv_buffer);
#endif
	TEST_NAME(choking_algorithm);
	TEST_NAME(seeding_piece_quota);
#if TORRENT_ABI_VERSION == 1
	TEST_NAME(half_open_limit);
	TEST_NAME(mmap_cache);
#endif
	TEST_NAME(peer_turnover_interval);
	TEST_NAME(peer_fingerprint);
	TEST_NAME(proxy_tracker_connections);
	TEST_NAME(predictive_piece_announce);
	TEST_NAME(max_metadata_size);
	TEST_NAME(num_optimistic_unchoke_slots);

	TEST_EQUAL(name_for_setting(settings_pack::peer_tos), std::string("peer_dscp"));
	TEST_EQUAL(setting_by_name("peer_tos"), settings_pack::peer_dscp); \
}

TORRENT_TEST(clear)
{
	settings_pack pack;
	TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), false);

	pack.set_bool(settings_pack::send_redundant_have, true);

	TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), true);
	TEST_EQUAL(pack.has_val(settings_pack::user_agent), false);
	TEST_EQUAL(pack.get_bool(settings_pack::send_redundant_have), true);

	pack.clear();

	TEST_EQUAL(pack.has_val(settings_pack::send_redundant_have), false);
	TEST_EQUAL(pack.has_val(settings_pack::user_agent), false);
}

TORRENT_TEST(clear_single_int)
{
	settings_pack sp;
	sp.set_int(settings_pack::max_out_request_queue, 1337);

	TEST_EQUAL(sp.get_int(settings_pack::max_out_request_queue), 1337);

	sp.clear(settings_pack::max_out_request_queue);

	// when cleared, we'll get the default value
	TEST_EQUAL(sp.get_int(settings_pack::max_out_request_queue), 500);
}

TORRENT_TEST(clear_single_bool)
{
	settings_pack sp;
	sp.set_bool(settings_pack::send_redundant_have, false);

	TEST_EQUAL(sp.get_bool(settings_pack::send_redundant_have), false);

	sp.clear(settings_pack::send_redundant_have);

	// when cleared, we'll get the default value
	TEST_EQUAL(sp.get_bool(settings_pack::send_redundant_have), true);
}

TORRENT_TEST(clear_single_string)
{
	settings_pack sp;
	sp.set_str(settings_pack::user_agent, "foobar");

	TEST_EQUAL(sp.get_str(settings_pack::user_agent), "foobar");

	sp.clear(settings_pack::user_agent);

	// when cleared, we'll get the default value
	TEST_EQUAL(sp.get_str(settings_pack::user_agent), "libtorrent/2.1.1.0");
}

TORRENT_TEST(duplicates)
{
	settings_pack p;
	p.set_str(settings_pack::peer_fingerprint, "abc");
	p.set_str(settings_pack::peer_fingerprint, "cde");
	p.set_str(settings_pack::peer_fingerprint, "efg");
	p.set_str(settings_pack::peer_fingerprint, "hij");

	TEST_EQUAL(p.get_str(settings_pack::peer_fingerprint), "hij");
}

TORRENT_TEST(load_pack_from_dict)
{
	aux::session_settings p1;
	p1.set_str(settings_pack::peer_fingerprint, "abc");
	p1.set_int(settings_pack::max_out_request_queue, 1337);
	p1.set_bool(settings_pack::send_redundant_have, false);

	entry e;
	save_settings_to_dict(non_default_settings(p1), e.dict());

	std::string s;
	bencode(std::back_inserter(s), e);

	bdecode_node n;
	error_code ec;
	int ret = bdecode(s.data(), s.data() + int(s.size()), n, ec);
	TEST_EQUAL(ret, 0);
	TEST_CHECK(!ec);

	settings_pack p2 = load_pack_from_dict(n);
	TEST_EQUAL(p2.get_str(settings_pack::peer_fingerprint), "abc");
	TEST_EQUAL(p2.get_int(settings_pack::max_out_request_queue), 1337);
	TEST_EQUAL(p2.get_bool(settings_pack::send_redundant_have), false);
}

TORRENT_TEST(settings_pack_abi)
{
	// make sure enum values are preserved across libtorrent versions
	// for ABI compatibility
	// These values are only allowed to change across major versions

	TEST_EQUAL(settings_pack::string_type_base, 0x0000);
	TEST_EQUAL(settings_pack::int_type_base, 0x4000);
	TEST_EQUAL(settings_pack::bool_type_base, 0x8000);
	TEST_EQUAL(settings_pack::type_mask, 0xc000);

	// strings
	TEST_EQUAL(settings_pack::outgoing_interfaces, settings_pack::string_type_base + 4);
	TEST_EQUAL(settings_pack::dht_bootstrap_nodes, settings_pack::string_type_base + 11);

	// bool
	TEST_EQUAL(settings_pack::use_dht_as_fallback, settings_pack::bool_type_base + 4);
	TEST_EQUAL(settings_pack::auto_manage_prefer_seeds, settings_pack::bool_type_base + 12);
	TEST_EQUAL(settings_pack::proxy_tracker_connections, settings_pack::bool_type_base + 67);

	// ints
	TEST_EQUAL(settings_pack::tracker_completion_timeout, settings_pack::int_type_base + 0);
	TEST_EQUAL(settings_pack::tracker_receive_timeout, settings_pack::int_type_base + 1);
	TEST_EQUAL(settings_pack::stop_tracker_timeout, settings_pack::int_type_base + 2);
	TEST_EQUAL(settings_pack::max_suggest_pieces, settings_pack::int_type_base + 66);
	TEST_EQUAL(settings_pack::connections_slack, settings_pack::int_type_base + 86);
	TEST_EQUAL(settings_pack::aio_threads, settings_pack::int_type_base + 104);
	TEST_EQUAL(settings_pack::max_http_recv_buffer_size, settings_pack::int_type_base + 115);
	TEST_EQUAL(settings_pack::web_seed_name_lookup_retry, settings_pack::int_type_base + 128);
	TEST_EQUAL(settings_pack::close_file_interval, settings_pack::int_type_base + 129);
	TEST_EQUAL(settings_pack::max_web_seed_connections, settings_pack::int_type_base + 131);
	TEST_EQUAL(settings_pack::resolver_cache_timeout, settings_pack::int_type_base + 132);
}

namespace {

bool empty_pack(settings_pack const& sp)
{
	for (std::uint16_t i = 0; i < settings_pack::num_string_settings; ++i)
		if (sp.has_val(i | settings_pack::string_type_base)) return false;
	for (std::uint16_t i = 0; i < settings_pack::num_int_settings; ++i)
		if (sp.has_val(i | settings_pack::int_type_base)) return false;
	for (std::uint16_t i = 0; i < settings_pack::num_bool_settings; ++i)
		if (sp.has_val(i | settings_pack::bool_type_base)) return false;
	return true;
}

}

TORRENT_TEST(non_default_settings)
{
	aux::session_settings def;
	settings_pack const p = non_default_settings(def);
	TEST_CHECK(empty_pack(p));
}

TORRENT_TEST(non_default_settings2)
{
	aux::session_settings def;
	def.set_int(settings_pack::max_out_request_queue, 1337);
	settings_pack const p = non_default_settings(def);
	TEST_EQUAL(p.get_int(settings_pack::max_out_request_queue), 1337);
}

TORRENT_TEST(save_settings_to_dict)
{
	settings_pack p;
	p.set_str(settings_pack::peer_fingerprint, "abc");
	p.set_int(settings_pack::max_out_request_queue, 1337);
	p.set_bool(settings_pack::send_redundant_have, false);

	entry e;
	save_settings_to_dict(p, e.dict());
	std::string buf;
	bencode(std::back_inserter(buf), e);
	TEST_EQUAL(buf, "d21:max_out_request_queuei1337e16:peer_fingerprint3:abc19:send_redundant_havei0ee");
}

// MSVC doesn't support making these arrays constexpr yet (which is required to
// initialize them before global constructors)
#if !defined _MSC_VER
namespace {
// make sure a global constructor has access to the default values, to
// initialize itself with
lt::aux::session_settings g_sett;
}

TORRENT_TEST(global_constructors)
{
	TEST_CHECK(g_sett.get_int(lt::settings_pack::aio_threads) > 0);
}
#endif

TORRENT_TEST(i2p_tunnel_variance_settings)
{
#if TORRENT_USE_I2P
	lt::settings_pack pack;

	// Default values should be 0
	TEST_EQUAL(pack.get_int(lt::settings_pack::i2p_inbound_length_variance), 0);
	TEST_EQUAL(pack.get_int(lt::settings_pack::i2p_outbound_length_variance), 0);

	pack.set_int(lt::settings_pack::i2p_inbound_length_variance, 2);
	pack.set_int(lt::settings_pack::i2p_outbound_length_variance, -3);
	TEST_EQUAL(pack.get_int(lt::settings_pack::i2p_inbound_length_variance), 2);
	TEST_EQUAL(pack.get_int(lt::settings_pack::i2p_outbound_length_variance), -3);

	// After clearing, it should go back to defaults
	pack.clear(lt::settings_pack::i2p_inbound_length_variance);
	pack.clear(lt::settings_pack::i2p_outbound_length_variance);
	TEST_EQUAL(pack.get_int(lt::settings_pack::i2p_inbound_length_variance), 0);
	TEST_EQUAL(pack.get_int(lt::settings_pack::i2p_outbound_length_variance), 0);
#endif
}
