/*

Copyright (c) 2016, 2021, Alden Torres
Copyright (c) 2016-2022, Arvid Norberg
Copyright (c) 2016, Pavel Pimenov
Copyright (c) 2017-2018, Steven Siloti
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 "test_utils.hpp"
#include "setup_transfer.hpp"

#include "libtorrent/torrent_info.hpp"
#include "libtorrent/create_torrent.hpp"
#include "libtorrent/bencode.hpp"
#include "libtorrent/announce_entry.hpp"
#include "libtorrent/aux_/escape_string.hpp" // for convert_path_to_posix
#include "libtorrent/announce_entry.hpp"
#include "libtorrent/units.hpp"
#include "libtorrent/load_torrent.hpp" // for load_torrent_buffer
#include "libtorrent/file_storage.hpp"
#include "libtorrent/aux_/path.hpp"
#include "libtorrent/units.hpp"
#include "libtorrent/aux_/vector.hpp"
#include "libtorrent/write_resume_data.hpp" // for write_torrent_file
#include "libtorrent/add_torrent_params.hpp"

#include <cstring>
#include <iostream>
#include <fstream>
#include <string>

using namespace std::literals::string_literals;

namespace {
constexpr lt::aux::path_index_t operator""_path(unsigned long long i)
{ return lt::aux::path_index_t(static_cast<std::uint32_t>(i)); }
}

#if TORRENT_ABI_VERSION < 4
// make sure creating a torrent from an existing handle preserves the
// info-dictionary verbatim, so as to not alter the info-hash
TORRENT_TEST(create_verbatim_torrent)
{
	char const test_torrent[] = "d4:infod4:name6:foobar6:lengthi12345e"
		"12:piece lengthi65536e6:pieces20:ababababababababababee";

	lt::torrent_info info(test_torrent, lt::from_span);
	lt::create_torrent t(info);

	std::vector<char> buffer = bencode(t.generate());

	TEST_CHECK(buffer == t.generate_buf());

	// now, make sure the info dictionary was unchanged
	buffer.push_back('\0');
	char const* dest_info = std::strstr(&buffer[0], "4:info");

	TEST_CHECK(dest_info != nullptr);

	// +1 and -2 here is to strip the outermost dictionary from the source
	// torrent, since create_torrent may have added items next to the info dict
	TEST_CHECK(memcmp(dest_info, test_torrent + 1, sizeof(test_torrent)-3) == 0);
}
#endif

TORRENT_TEST(auto_piece_size)
{
	std::int64_t const kiB = 1024;
	std::int64_t const MiB = 1024 * 1024;
	std::int64_t const GiB = 1024 * 1024 * 1024;
	std::array<std::pair<std::int64_t, std::int64_t>, 11> samples{{
		{100LL,     16 * kiB},
		{3 * MiB,   32 * kiB},
		{11 * MiB,  64 * kiB},
		{43 * MiB,  128 * kiB},
		{172 * MiB, 256 * kiB},
		{688 * MiB, 512 * kiB},
		{3 * GiB,   1 * MiB},
		{11 * GiB,  2 * MiB},
		{44 * GiB,  4 * MiB},
		{176 * GiB, 8 * MiB},
		{704 * GiB, 16 * MiB},
	}};

	for (auto const& t : samples)
	{
#if TORRENT_ABI_VERSION < 4
		{
			lt::file_storage fs;
			fs.add_file("a", t.first);
			lt::create_torrent ct(fs, 0);
			TEST_CHECK(ct.piece_length() == static_cast<int>(t.second));
		}
#endif
		{
			std::vector<lt::create_file_entry> files;
			files.emplace_back("a", t.first);
			lt::create_torrent ct(std::move(files), 0);
			TEST_CHECK(ct.piece_length() == static_cast<int>(t.second));
		}
	}
}

namespace {
int test_piece_size(int const piece_size, lt::create_flags_t const f = {})
{
	std::int64_t const MiB = 1024 * 1024;
	std::vector<lt::create_file_entry> files;
	files.emplace_back("a", 100 * MiB);
	lt::create_torrent ct(std::move(files), piece_size, f);
#if TORRENT_ABI_VERSION < 4
	{
		lt::file_storage fs;
		fs.add_file("a", 100 * MiB);
		lt::create_torrent ct2(fs, piece_size, f);
		TEST_EQUAL(ct2.piece_length(), ct.piece_length());
	}
#endif
	return ct.piece_length();
}
}

TORRENT_TEST(piece_size_restriction_16kB)
{
	// v2 torrents must have piece sizes of at least 16 kiB
	TEST_EQUAL(test_piece_size(15000), 16 * 1024);
	TEST_EQUAL(test_piece_size(500), 16 * 1024);
	TEST_THROW(test_piece_size(15000, lt::create_torrent::v1_only));
	TEST_THROW(test_piece_size(8000, lt::create_torrent::v1_only));
	TEST_EQUAL(test_piece_size(8192, lt::create_torrent::v1_only), 8192);
}

TORRENT_TEST(piece_size_quanta)
{
	// v2 torrents must have power-of-two piece sizes
	TEST_EQUAL(test_piece_size(32 * 1024), 32 * 1024);
	TEST_EQUAL(test_piece_size(32 * 1024, lt::create_torrent::v1_only), 32 * 1024);
	TEST_THROW(test_piece_size(48 * 1024));
	TEST_EQUAL(test_piece_size(48 * 1024, lt::create_torrent::v1_only), 48 * 1024);
	TEST_THROW(test_piece_size(47 * 1024, lt::create_torrent::v1_only));
	TEST_THROW(test_piece_size(47 * 1024));
}

TORRENT_TEST(piece_size_limit)
{
	// the max piece size is determined by piece_picker::max_blocks_per_piece
	TEST_EQUAL(test_piece_size(0x8000000), 0x8000000);
	TEST_THROW(test_piece_size(0x8000001, lt::create_torrent::v1_only));
	TEST_THROW(test_piece_size(0x10000000));
}

#if TORRENT_ABI_VERSION < 4
TORRENT_TEST(create_torrent_round_trip)
{
	char const test_torrent[] = "d8:announce26:udp://testurl.com/announce7:comment22:this is a test comment13:creation datei1337e4:infod6:lengthi12345e4:name6:foobar12:piece lengthi65536e6:pieces20:ababababababababababee";
	lt::torrent_info info1(test_torrent, lt::from_span);
	TEST_EQUAL(info1.comment(), "this is a test comment");
	TEST_EQUAL(info1.trackers().size(), 1);
	TEST_EQUAL(info1.trackers().front().url, "udp://testurl.com/announce");

	lt::create_torrent t(info1);

	std::vector<char> buffer = bencode(t.generate());
	TEST_CHECK(buffer == t.generate_buf());
	lt::torrent_info info2(buffer, lt::from_span);

	TEST_EQUAL(info2.comment(), "this is a test comment");
	TEST_EQUAL(info2.trackers().size(), 1);
	TEST_EQUAL(info2.trackers().front().url, "udp://testurl.com/announce");
	TEST_CHECK(info1.info_hashes() == info2.info_hashes());
	TEST_CHECK(info2.hash_for_piece(0_piece) == info1.hash_for_piece(0_piece));
}
#endif

namespace {

void test_round_trip_torrent(std::string const& name)
{
	std::string const root_dir = lt::parent_path(lt::current_working_directory());
	std::string const filename = lt::combine_path(lt::combine_path(root_dir, "test_torrents"), name);
	std::vector<char> v2_buffer;
	lt::error_code ec;
	int const ret = load_file(filename, v2_buffer, ec);
	TEST_CHECK(ret == 0);
	TEST_CHECK(!ec);

	lt::bdecode_node in_torrent = lt::bdecode(v2_buffer);

#if TORRENT_ABI_VERSION < 4
	{
		lt::torrent_info info1(v2_buffer, lt::from_span);
		lt::create_torrent t(info1);

		std::vector<char> const out_buffer = bencode(t.generate());
		lt::bdecode_node out_torrent = lt::bdecode(out_buffer);
		TEST_CHECK(out_buffer == t.generate_buf());

		TEST_CHECK(out_torrent.dict_find("info").data_section()
			== in_torrent.dict_find("info").data_section());

		auto in_piece_layers = in_torrent.dict_find("piece layers").data_section();
		auto out_piece_layers = out_torrent.dict_find("piece layers").data_section();
		TEST_CHECK(out_piece_layers == in_piece_layers);
	}
#endif

	auto atp = lt::load_torrent_buffer(v2_buffer);
	lt::entry e = lt::write_torrent_file(atp);
	std::vector<char> out_buffer = bencode(e);

	lt::bdecode_node out_torrent = lt::bdecode(out_buffer);

	TEST_CHECK(out_torrent.dict_find("info").data_section()
		== in_torrent.dict_find("info").data_section());

#if TORRENT_ABI_VERSION < 4
	auto in_piece_layers = in_torrent.dict_find("piece layers").data_section();
	auto out_piece_layers = out_torrent.dict_find("piece layers").data_section();
	TEST_CHECK(out_piece_layers == in_piece_layers);
#endif
}

}

TORRENT_TEST(create_torrent_round_trip_v2)
{
	test_round_trip_torrent("v2_only.torrent");
}

TORRENT_TEST(create_torrent_round_trip_hybrid_missing_tailpad)
{
	test_round_trip_torrent("v2_hybrid-missing-tailpad.torrent");
}

TORRENT_TEST(create_torrent_round_trip_hybrid)
{
	test_round_trip_torrent("v2_hybrid.torrent");
}

TORRENT_TEST(create_torrent_round_trip_empty_file)
{
	test_round_trip_torrent("v2_empty_file.torrent");
}

// check that attempting to create a torrent containing both
// a file and directory with the same name is not allowed
TORRENT_TEST(v2_path_conflict)
{
	std::vector<lt::create_file_entry> fs;

	for (int i = 0; i < 2; ++i)
	{
		switch (i)
		{
		case 0:
			fs.emplace_back("test/A/tmp", 0x4000);
			fs.emplace_back("test/a", 0x4000);
			fs.emplace_back("test/A", 0x4000);
			fs.emplace_back("test/filler", 0x4000);
			break;
		case 1:
			fs.emplace_back("test/long/path/name/that/collides", 0x4000);
			fs.emplace_back("test/long/path", 0x4000);
			fs.emplace_back("test/filler-1", 0x4000);
			fs.emplace_back("test/filler-2", 0x4000);
			break;
		}

		lt::create_torrent t(std::move(fs), 0x4000);
		lt::sha256_hash const dummy("01234567890123456789012345678901");
		lt::piece_index_t::diff_type zero(0);
		t.set_hash2(0_file, zero, dummy);
		t.set_hash2(1_file, zero, dummy);
		t.set_hash2(2_file, zero, dummy);
		t.set_hash2(3_file, zero, dummy);
		TEST_THROW(t.generate());
		TEST_THROW(t.generate_buf());
	}
}

TORRENT_TEST(v2_only)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/A", 0x8002);
	fs.emplace_back("test/B", 0x4002);
	lt::create_torrent t(std::move(fs), 0x4000, lt::create_torrent::v2_only);

	using p = lt::piece_index_t::diff_type;
	t.set_hash2(0_file, p(0), lt::sha256_hash::max());
	t.set_hash2(0_file, p(1), lt::sha256_hash::max());
	t.set_hash2(0_file, p(2), lt::sha256_hash::max());
	// file 1 is a pad file
	t.set_hash2(2_file, p(0), lt::sha256_hash::max());
	t.set_hash2(2_file, p(1), lt::sha256_hash::max());

	std::vector<char> const buffer = lt::bencode(t.generate());
	TEST_CHECK(buffer == t.generate_buf());

#if TORRENT_ABI_VERSION < 4
	{
		lt::torrent_info info(buffer, lt::from_span);
		TEST_CHECK(info.info_hashes().has_v2());
		TEST_CHECK(!info.info_hashes().has_v1());
		TEST_EQUAL(info.layout().file_name(0_file), "A");
		TEST_CHECK(info.layout().pad_file_at(1_file));
		TEST_EQUAL(info.layout().file_name(2_file), "B");
		TEST_EQUAL(info.name(), "test");

		lt::create_torrent t2(info);
		std::vector<char> const buffer2 = bencode(t2.generate());
		TEST_CHECK(buffer2 == t2.generate_buf());

		TEST_CHECK(buffer == buffer2);
	}
#endif

	std::shared_ptr<lt::torrent_info const> info = lt::load_torrent_buffer(buffer).ti;
	TEST_CHECK(info->info_hashes().has_v2());
	TEST_CHECK(!info->info_hashes().has_v1());
	TEST_EQUAL(info->layout().file_name(0_file), "A");
	TEST_CHECK(info->layout().pad_file_at(1_file));
	TEST_EQUAL(info->layout().file_name(2_file), "B");
	TEST_EQUAL(info->name(), "test");

}

TORRENT_TEST(v2_only_set_hash)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/A", 0x8002);
	lt::create_torrent t(std::move(fs), 0x4000, lt::create_torrent::v2_only);

	TEST_THROW(t.set_hash(0_piece, lt::sha1_hash::max()));
}

#if TORRENT_HAS_SYMLINK || !defined TORRENT_WINDOWS
namespace {

void check(int ret)
{
	if (ret == 0) return;
	lt::error_code ec(errno, lt::generic_category());
	std::cerr << "call failed: " << ec.message() << '\n';
	throw std::runtime_error(ec.message());
}

}
#endif

#if TORRENT_HAS_SYMLINK

TORRENT_TEST(create_torrent_symlink)
{
	lt::error_code ec;
	lt::create_directories("test-torrent/a/b/c", ec);
	lt::create_directories("test-torrent/d/", ec);
	std::ofstream f1("test-torrent/a/b/c/file-1");
	check(::truncate("test-torrent/a/b/c/file-1", 1000));
	std::ofstream f2("test-torrent/d/file-2");
	check(::truncate("test-torrent/d/file-2", 1000));
	check(::symlink("../a/b/c/file-1", "test-torrent/d/test-link-1"));
	check(::symlink("a/b/c/file-1", "test-torrent/test-link-2"));
	check(::symlink("file-1", "test-torrent/a/b/c/test-link-3"));
	check(::symlink("../../../d/file-2", "test-torrent/a/b/c/test-link-4"));

	auto check = [](lt::create_torrent& t) {
		lt::set_piece_hashes(t, ".", [] (lt::piece_index_t) {});

		std::vector<char> torrent = lt::bencode(t.generate());

		TEST_CHECK(torrent == t.generate_buf());
		lt::add_torrent_params atp = lt::load_torrent_buffer(torrent);
		auto& ti = *atp.ti;

		// directly inspect the bencoded v1 files list to confirm the
		// stored "symlink path" entries are relative to the torrent root
		// and do NOT have the torrent's own name as their first element
		lt::bdecode_node const root = lt::bdecode(torrent);
		lt::bdecode_node const files_list = root.dict_find("info").dict_find_list("files");
		TEST_CHECK(files_list);
		int direct_found = 0;
		for (int i = 0; i < files_list.list_size(); ++i)
		{
			lt::bdecode_node const file_e = files_list.list_at(i);
			lt::bdecode_node const sympath = file_e.dict_find_list("symlink path");
			if (!sympath) continue;
			TEST_CHECK(sympath.list_size() > 0);
			// the torrent name "test-torrent" must not appear as the first
			// component of any stored symlink path
			TEST_CHECK(sympath.list_at(0).string_value() != "test-torrent");
			++direct_found;
		}
		TEST_EQUAL(direct_found, 4);

		int found = 0;
		for (auto i : ti.layout().file_range())
		{
			auto const filename = ti.layout().file_path(i);

			if (filename == "test-torrent/d/test-link-1"
				|| filename == "test-torrent/test-link-2"
				|| filename == "test-torrent/a/b/c/test-link-3")
			{
				TEST_EQUAL(ti.layout().symlink(i), "test-torrent/a/b/c/file-1");
				++found;
			}
			else if (filename == "test-torrent/a/b/c/test-link-4")
			{
				TEST_EQUAL(ti.layout().symlink(i), "test-torrent/d/file-2");
				++found;
			}
		}
		TEST_EQUAL(found, 4);
	};

#if TORRENT_ABI_VERSION < 4
	{
		lt::file_storage fs;
		lt::add_files(fs, "test-torrent"
			, [](std::string n){ std::cout << n << '\n'; return true; }, lt::create_torrent::symlinks);

		lt::create_torrent t(fs, 16 * 1024, lt::create_torrent::symlinks);
		check(t);
	}
#endif

	{
		auto files = lt::list_files("test-torrent"
			, [](std::string n){ std::cout << n << '\n'; return true; }, lt::create_torrent::symlinks);

		lt::create_torrent t(std::move(files), 16 * 1024, lt::create_torrent::symlinks);
		check(t);
	}
}

TORRENT_TEST(create_torrent_symlink_escape)
{
	lt::error_code ec;
	lt::create_directories("escape-torrent/sub", ec);
	TEST_CHECK(!ec);
	std::ofstream f1("escape-torrent/sub/inside-file");
	check(::truncate("escape-torrent/sub/inside-file", 1000));

	// these symlinks all reach outside the torrent root and must be
	// silently dropped from the resulting file list
	check(::symlink("../../outside", "escape-torrent/escape-1"));
	check(::symlink("../../../some/other/file", "escape-torrent/sub/escape-2"));
	check(::symlink("/tmp/elsewhere", "escape-torrent/escape-3"));

	// this one stays inside the torrent root and must be kept
	check(::symlink("inside-file", "escape-torrent/sub/inside-link"));

	auto check = [](lt::create_torrent& t) {
		lt::set_piece_hashes(t, ".", [](lt::piece_index_t) {});

		std::vector<char> torrent = lt::bencode(t.generate());

		TEST_CHECK(torrent == t.generate_buf());
		lt::add_torrent_params atp = lt::load_torrent_buffer(torrent);
		auto& ti = *atp.ti;

		bool found_inside_link = false;
		for (auto i : ti.layout().file_range())
		{
			auto const filename = ti.layout().file_path(i);
			// none of the escaping symlinks should appear in the torrent
			TEST_CHECK(filename != "escape-torrent/escape-1");
			TEST_CHECK(filename != "escape-torrent/sub/escape-2");
			TEST_CHECK(filename != "escape-torrent/escape-3");

			if (filename == "escape-torrent/sub/inside-link")
			{
				TEST_EQUAL(ti.layout().symlink(i), "escape-torrent/sub/inside-file");
				found_inside_link = true;
			}
		}
		TEST_CHECK(found_inside_link);
	};

#if TORRENT_ABI_VERSION < 4
	{
		lt::file_storage fs;
		lt::add_files(
			fs, "escape-torrent", [](std::string) { return true; }, lt::create_torrent::symlinks);

		lt::create_torrent t(fs, 16 * 1024, lt::create_torrent::symlinks);
		check(t);
	}
#endif

	{
		auto files = lt::list_files(
			"escape-torrent",
			[](std::string n) {
				std::cout << n << '\n';
				return true;
			},
			lt::create_torrent::symlinks);

		lt::create_torrent t(std::move(files), 16 * 1024, lt::create_torrent::symlinks);
		check(t);
	}
}

TORRENT_TEST(create_torrent_symlink_pred)
{
	lt::error_code ec;
	lt::create_directories("pred-torrent/sub", ec);
	std::ofstream f1("pred-torrent/sub/keep-me");
	check(::truncate("pred-torrent/sub/keep-me", 1000));
	std::ofstream f2("pred-torrent/sub/skip-me");
	check(::truncate("pred-torrent/sub/skip-me", 1000));

	// two symlinks: one to a file the predicate accepts, one to a file
	// the predicate rejects
	check(::symlink("sub/keep-me", "pred-torrent/link-keep"));
	check(::symlink("sub/skip-me", "pred-torrent/link-skip"));

	auto check = [](lt::create_torrent& t) {
		lt::set_piece_hashes(t, ".", [](lt::piece_index_t) {});

		std::vector<char> torrent = lt::bencode(t.generate());

		TEST_CHECK(torrent == t.generate_buf());
		lt::add_torrent_params atp = lt::load_torrent_buffer(torrent);
		auto& ti = *atp.ti;

		bool found_link_keep = false;
		for (auto i : ti.layout().file_range())
		{
			auto const filename = ti.layout().file_path(i);
			// link-skip points at skip-me which the predicate rejected
			TEST_CHECK(filename != "pred-torrent/link-skip");
			// the rejected regular file itself must also be absent
			TEST_CHECK(filename != "pred-torrent/sub/skip-me");

			if (filename == "pred-torrent/link-keep")
			{
				TEST_EQUAL(ti.layout().symlink(i), "pred-torrent/sub/keep-me");
				found_link_keep = true;
			}
		}
		TEST_CHECK(found_link_keep);
	};

#if TORRENT_ABI_VERSION < 4
	{
		lt::file_storage fs;
		lt::add_files(
			fs,
			"pred-torrent",
			[](std::string n) {
				// reject the regular file "skip-me" AND any symlink whose
				// resolved target is "skip-me". The second pred call (on the
				// resolved target) is what we're testing.
				return n.find("skip-me") == std::string::npos;
			},
			lt::create_torrent::symlinks);

		lt::create_torrent t(fs, 16 * 1024, lt::create_torrent::symlinks);
		check(t);
	}
#endif

	{
		auto files = lt::list_files(
			"pred-torrent",
			[](std::string n) {
				// reject the regular file "skip-me" AND any symlink whose
				// resolved target is "skip-me". The second pred call (on the
				// resolved target) is what we're testing.
				return n.find("skip-me") == std::string::npos;
			},
			lt::create_torrent::symlinks);

		lt::create_torrent t(std::move(files), 16 * 1024, lt::create_torrent::symlinks);
		check(t);
	}
}

#endif

#ifndef TORRENT_WINDOWS

TORRENT_TEST(v2_attributes)
{
	std::ofstream f1("file-1");
	check(::truncate("file-1", 1000));
	check(::chmod("file-1", S_IWUSR | S_IRUSR | S_IXUSR));

	auto check = [](lt::create_torrent& t) {
		lt::set_piece_hashes(t, ".", [] (lt::piece_index_t) {});

		lt::entry e = t.generate();

		std::cout << e.to_string() << '\n';

		TEST_EQUAL(e["info"]["attr"].string(), "x");
		TEST_EQUAL(e["info"]["file tree"]["file-1"][""]["attr"].string(), "x");
	};

#if TORRENT_ABI_VERSION < 4
	{
		lt::file_storage fs;
		lt::add_files(fs, "file-1");

		lt::create_torrent t(fs, 16 * 1024, {});
		check(t);
	}
#endif

	{
		auto files = lt::list_files("file-1");

		lt::create_torrent t(std::move(files), 16 * 1024, {});
		check(t);
	}

}
#endif

TORRENT_TEST(v1_only_set_hash2)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/A", 0x8002);
	lt::create_torrent t(std::move(fs), 0x4000, lt::create_torrent::v1_only);

	using p = lt::piece_index_t::diff_type;
	TEST_THROW(t.set_hash2(0_file, p(0), lt::sha256_hash::max()));
}

// if we don't specify a v2-only flag, but only set v2 hashes, the created
// torrent is implicitly v2-only
TORRENT_TEST(implicit_v2_only)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/A", 0x8002);
	fs.emplace_back("test/B", 0x4002);
	lt::create_torrent t(std::move(fs), 0x4000);

	using p = lt::piece_index_t::diff_type;
	t.set_hash2(0_file, p(0), lt::sha256_hash::max());
	t.set_hash2(0_file, p(1), lt::sha256_hash::max());
	t.set_hash2(0_file, p(2), lt::sha256_hash::max());
	// file 1 is a pad file
	t.set_hash2(2_file, p(0), lt::sha256_hash::max());
	t.set_hash2(2_file, p(1), lt::sha256_hash::max());

	std::vector<char> const buffer = lt::bencode(t.generate());
	TEST_CHECK(buffer == t.generate_buf());

	{
		lt::add_torrent_params atp = lt::load_torrent_buffer(buffer);
		auto& info = *atp.ti;
		TEST_CHECK(info.info_hashes().has_v2());
		TEST_CHECK(!info.info_hashes().has_v1());
		TEST_EQUAL(info.layout().file_name(0_file), "A");
		TEST_EQUAL(info.layout().pad_file_at(1_file), true);
		TEST_EQUAL(info.layout().file_name(2_file), "B");
		TEST_EQUAL(info.name(), "test");
	}

	auto atp = lt::load_torrent_buffer(buffer);
	TEST_CHECK(atp.ti->info_hashes().has_v2());
	TEST_CHECK(!atp.ti->info_hashes().has_v1());
	TEST_EQUAL(atp.ti->layout().file_name(0_file), "A");
	TEST_EQUAL(atp.ti->layout().pad_file_at(1_file), true);
	TEST_EQUAL(atp.ti->layout().file_name(2_file), "B");
	TEST_EQUAL(atp.ti->name(), "test");
}

// if we don't specify a v1-only flag, but only set v1 hashes, the created
// torrent is implicitly v1-only
TORRENT_TEST(implicit_v1_only)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/A", 0x8002);
	fs.emplace_back("test/B", 0x4002);
	lt::create_torrent t(std::move(fs), 0x4000);

	for (lt::piece_index_t i : t.piece_range())
		t.set_hash(i, lt::sha1_hash::max());

	std::vector<char> const buffer = lt::bencode(t.generate());
	TEST_CHECK(buffer == t.generate_buf());

	{
		lt::add_torrent_params atp = lt::load_torrent_buffer(buffer);
		auto& info = *atp.ti;
		TEST_CHECK(!info.info_hashes().has_v2());
		TEST_CHECK(info.info_hashes().has_v1());
		TEST_EQUAL(info.layout().file_name(0_file), "A");
		TEST_EQUAL(info.layout().pad_file_at(1_file), true);
		TEST_EQUAL(info.layout().file_name(2_file), "B");
		TEST_EQUAL(info.name(), "test");
	}

	auto info = lt::load_torrent_buffer(buffer).ti;
	TEST_CHECK(!info->info_hashes().has_v2());
	TEST_CHECK(info->info_hashes().has_v1());
	TEST_EQUAL(info->layout().file_name(0_file), "A");
	TEST_EQUAL(info->layout().pad_file_at(1_file), true);
	TEST_EQUAL(info->layout().file_name(2_file), "B");
	TEST_EQUAL(info->name(), "test");
}

namespace {

template <typename Fun>
lt::add_torrent_params test_field(Fun f)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("A", 0x4000);
	lt::create_torrent t(std::move(fs), 0x4000);
	for (lt::piece_index_t i : t.piece_range())
		t.set_hash(i, lt::sha1_hash::max());

	f(t);

	std::vector<char> const buffer = lt::bencode(t.generate());
	TEST_CHECK(buffer == t.generate_buf());
	return lt::load_torrent_buffer(buffer);
}
}

TORRENT_TEST(no_creation_date)
{
	auto atp = test_field([](lt::create_torrent& t){
		t.set_creation_date(0);
	});
	TEST_EQUAL(atp.creation_date, 0);
}

TORRENT_TEST(creation_date)
{
	auto atp = test_field([](lt::create_torrent& t){
		t.set_creation_date(1337);
	});
	TEST_EQUAL(atp.creation_date, 1337);
}

TORRENT_TEST(comment)
{
	auto atp = test_field([](lt::create_torrent& t){
		t.set_comment("foobar");
	});
	TEST_EQUAL(atp.comment, "foobar");
}

TORRENT_TEST(creator)
{
	auto atp = test_field([](lt::create_torrent& t){
		t.set_creator("foobar");
	});
	TEST_EQUAL(atp.created_by, "foobar");
}

TORRENT_TEST(dht_nodes)
{
	auto atp = test_field([](lt::create_torrent& t){
		t.add_node({"foobar"s, 1337});
	});
	using nodes = std::vector<std::pair<std::string, int>>;
	TEST_CHECK((atp.dht_nodes == nodes{{"foobar", 1337}}));
}

TORRENT_TEST(ssl_cert)
{
	auto atp = test_field([](lt::create_torrent& t){
		t.set_root_cert("foobar");
	});
	TEST_EQUAL(atp.ti->ssl_cert(), "foobar");
}

TORRENT_TEST(priv)
{
	auto atp = test_field([](lt::create_torrent& t){
		t.set_priv(true);
	});
	TEST_CHECK(atp.ti->priv());
}

TORRENT_TEST(piece_layer)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/large", 0x8000);
	fs.emplace_back("test/small-1", 0x4000);
	fs.emplace_back("test/small-2", 0x3fff);
	lt::create_torrent t(std::move(fs), 0x4000);

	using p = lt::piece_index_t::diff_type;
	t.set_hash2(0_file, p(0), lt::sha256_hash::max());
	t.set_hash2(0_file, p(1), lt::sha256_hash::max());
	t.set_hash2(1_file, p(0), lt::sha256_hash::max());
	t.set_hash2(2_file, p(0), lt::sha256_hash::max());

	std::vector<char> const buffer = lt::bencode(t.generate());
	TEST_CHECK(buffer == t.generate_buf());
	auto atp = lt::load_torrent_buffer(buffer);
	TEST_EQUAL(atp.merkle_trees.size(), 4);
	TEST_EQUAL(atp.merkle_trees[0_file].size(), 2);
	TEST_EQUAL(atp.merkle_trees[1_file].size(), 0);
	TEST_EQUAL(atp.merkle_trees[2_file].size(), 0);
	TEST_EQUAL(atp.merkle_trees[3_file].size(), 0);

#if TORRENT_ABI_VERSION < 4
	auto info = std::make_shared<lt::torrent_info>(buffer, lt::from_span);
	TEST_EQUAL(info->piece_layer(0_file).size(), lt::sha256_hash::size() * 2);
	TEST_EQUAL(info->piece_layer(1_file).size(), lt::sha256_hash::size());
	TEST_EQUAL(info->piece_layer(2_file).size(), lt::sha256_hash::size());
	TEST_EQUAL(info->piece_layer(3_file).size(), 0);
#endif
}

TORRENT_TEST(pieces_root_empty_file)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-empty", 0);
	fs.emplace_back("test/2-small", 0x3fff);
	fs.emplace_back("test/3-empty", 0);
	lt::create_torrent t(std::move(fs), 0x4000);

	using p = lt::piece_index_t::diff_type;
	t.set_hash2(1_file, p(0), lt::sha256_hash::max());

	lt::entry e = t.generate();
	TEST_CHECK(!e["info"]["files tree"]["test"]["1-empty"].find_key("pieces root"));
	TEST_CHECK(!e["info"]["files tree"]["test"]["2-small"].find_key("pieces root"));
	TEST_CHECK(!e["info"]["files tree"]["test"]["3-small"].find_key("pieces root"));

	auto info = lt::load_torrent_buffer(lt::bencode(e)).ti;

	TEST_CHECK(info->layout().root(0_file).is_all_zeros());
	TEST_CHECK(!info->layout().root(1_file).is_all_zeros());
}

namespace {

std::string test_create_torrent(std::vector<lt::create_file_entry> fs, int const piece_size
	, lt::create_flags_t const flags)
{
	lt::create_torrent ct(std::move(fs), piece_size, flags);
	ct.set_creation_date(1337);
	if (!(flags & lt::create_torrent::v2_only))
		for (lt::piece_index_t i : ct.piece_range())
			ct.set_hash(i, lt::sha1_hash::max());
	if (!(flags & lt::create_torrent::v1_only))
		for (auto const f : ct.file_range())
			if (!(ct.file_at(f).flags & lt::file_storage::flag_pad_file))
				for (auto const p : ct.file_piece_range(f))
					ct.set_hash2(f, p, lt::sha256_hash::max());
	auto e = ct.generate();
	std::string buf;
	lt::bencode(std::back_inserter(buf), e);
	std::vector<char> const buf2 = lt::bencode(e);
	TEST_CHECK(buf2 == ct.generate_buf());
	return buf;
}

}

TORRENT_TEST(v1_tail_padding)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	fs.emplace_back("test/2-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files)
		, "d13:creation datei1337e4:infod5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"d6:lengthi16383e4:pathl7:2-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee");
}

// padding goes before empty files, not after
TORRENT_TEST(v1_empty_file_placement)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	fs.emplace_back("test/2-empty", 0);
	fs.emplace_back("test/3-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files)
		, "d13:creation datei1337e4:infod5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"d6:lengthi0e4:pathl7:2-emptyee"
		"d6:lengthi16383e4:pathl7:3-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee");
}

// despite the files being added in one order, the torrent is still created with
// files in the canonical order
TORRENT_TEST(v1_file_sorting)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/2-small", 0x3fff);
	fs.emplace_back("test/1-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files)
		, "d13:creation datei1337e4:infod5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"d6:lengthi16383e4:pathl7:2-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee");
}

// This is a backwards compatibility feature
TORRENT_TEST(v1_no_tail_padding)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	fs.emplace_back("test/2-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files_no_tail_padding)
		, "d13:creation datei1337e4:infod5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"d6:lengthi16383e4:pathl7:2-smallee"
		"e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee");
}

// padding goes after empty files in backwards compatibility mode
TORRENT_TEST(v1_empty_file_placement_backwards_compatibility)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	fs.emplace_back("test/2-empty", 0);
	fs.emplace_back("test/3-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only | lt::create_torrent::canonical_files_no_tail_padding)
		, "d13:creation datei1337e4:infod5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d6:lengthi0e4:pathl7:2-emptyee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"d6:lengthi16383e4:pathl7:3-smallee"
		"e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee");
}

// the v1_only flag does not arrange files canonically (i.e. no ordering nor
// padding)
TORRENT_TEST(v1_no_padding)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	fs.emplace_back("test/2-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v1_only)
		, "d13:creation datei1337e4:infod5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d6:lengthi16383e4:pathl7:2-smallee"
		"e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee");
}

TORRENT_TEST(hybrid)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	fs.emplace_back("test/2-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, {})
		, "d13:creation datei1337e4:infod"
		"9:file tree"
		"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"7:2-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"e"
		"5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"d6:lengthi16383e4:pathl7:2-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"e"
		"12:meta versioni2e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"e"
		"12:piece layersde"
		"e");
}

TORRENT_TEST(hybrid_single_file)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("1-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, {})
		, "d13:creation datei1337e4:infod"
		"9:file tree"
		"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"e"
		"6:lengthi16383e"
		"12:meta versioni2e"
		"4:name7:1-small12:piece lengthi16384e"
		"6:pieces20:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"e"
		"12:piece layersde"
		"e");
}

TORRENT_TEST(hybrid_single_file_with_directory)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, {})
		, "d13:creation datei1337e4:infod"
		"9:file tree"
		"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"e"
		"5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"e"
		"12:meta versioni2e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces20:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"e"
		"12:piece layersde"
		"e");
}

// this is a backwards compatibility feature
TORRENT_TEST(hybrid_no_tail_padding)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/1-small", 0x3fff);
	fs.emplace_back("test/2-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::canonical_files_no_tail_padding)
		, "d13:creation datei1337e4:infod"
		"9:file tree"
		"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"7:2-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"e"
		"5:filesl"
		"d6:lengthi16383e4:pathl7:1-smallee"
		"d4:attr1:p6:lengthi1e4:pathl4:.pad1:1ee"
		"d6:lengthi16383e4:pathl7:2-smallee"
		"e"
		"12:meta versioni2e"
		"4:name4:test12:piece lengthi16384e"
		"6:pieces40:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"e"
		"12:piece layersde"
		"e");
}

TORRENT_TEST(v2_only_file_sorting)
{
	std::vector<lt::create_file_entry> fs;
	fs.emplace_back("test/2-small", 0x3fff);
	fs.emplace_back("test/1-small", 0x3fff);
	TEST_EQUAL(test_create_torrent(std::move(fs), 0x4000, lt::create_torrent::v2_only)
		, "d13:creation datei1337e4:infod"
		"9:file tree"
		"d7:1-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"7:2-smalld0:d6:lengthi16383e11:pieces root32:"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"
		"ee"
		"e"
		"12:meta versioni2e"
		"4:name4:test12:piece lengthi16384e"
		"e"
		"12:piece layersde"
		"e");
}

namespace {
std::shared_ptr<lt::torrent_info const>
make_load_torrent(std::vector<lt::create_file_entry> files, int const piece_size)
{
	lt::create_torrent ct(std::move(files), piece_size);
	for (lt::piece_index_t i : ct.piece_range())
		ct.set_hash(i, lt::sha1_hash::max());
	auto e = ct.generate();
	std::vector<char> const buf = lt::bencode(e);
	auto atp = lt::load_torrent_buffer(buf);
	return atp.ti;
}
}

// make sure we fill in padding with small files
TORRENT_TEST(canonicalize_pad)
{
	std::vector<lt::create_file_entry> files;
	files.emplace_back("s/2", 0x7000);
	files.emplace_back("s/1", 1);
	files.emplace_back("s/3", 0x7001);

	auto ti = make_load_torrent(files, 0x4000);
	lt::file_storage const& fs = ti->layout();

	TEST_EQUAL(fs.num_files(), 6);

	TEST_EQUAL(fs.file_size(0_file), 1);
	TEST_EQUAL(fs.file_name(0_file), "1");
	TEST_EQUAL(fs.pad_file_at(0_file), false);

	TEST_EQUAL(fs.file_size(1_file), 0x4000 - 1);
	TEST_EQUAL(fs.pad_file_at(1_file), true);

	TEST_EQUAL(fs.file_size(2_file), 0x7000);
	TEST_EQUAL(fs.file_name(2_file), "2");
	TEST_EQUAL(fs.pad_file_at(2_file), false);

	TEST_EQUAL(fs.file_size(3_file), 0x8000 - 0x7000);
	TEST_EQUAL(fs.pad_file_at(3_file), true);

	TEST_EQUAL(fs.file_size(4_file), 0x7001);
	TEST_EQUAL(fs.file_name(4_file), "3");
	TEST_EQUAL(fs.pad_file_at(4_file), false);
	TEST_EQUAL(fs.size_on_disk(), 0x7000 + 1 + 0x7001);

	TEST_EQUAL(fs.file_size(5_file), 0x8000 - 0x7001);
	TEST_EQUAL(fs.pad_file_at(5_file), true);
}

// make sure canonicalize sorts by path correctly
TORRENT_TEST(canonicalize_path)
{
	std::vector<lt::create_file_entry> files;
	files.emplace_back("b/2/a", 0x4000);
	files.emplace_back("b/1/a", 0x4000);
	files.emplace_back("b/3/a", 0x4000);
	files.emplace_back("b/11", 0x4000);

	auto ti = make_load_torrent(files, 0x4000);
	lt::file_storage const& fs = ti->layout();

	TEST_EQUAL(fs.num_files(), 4);

	using lt::combine_path;

	TEST_EQUAL(fs.file_path(0_file), combine_path("b", combine_path("1", "a")));
	TEST_EQUAL(fs.file_path(1_file), combine_path("b", "11"));
	TEST_EQUAL(fs.file_path(2_file), combine_path("b", combine_path("2", "a")));
	TEST_EQUAL(fs.file_path(3_file), combine_path("b", combine_path("3", "a")));
}

TORRENT_TEST(file_num_blocks)
{
	std::vector<lt::create_file_entry> files;
	files.emplace_back("test/0", 0x5000);
	files.emplace_back("test/1", 0x2000);
	files.emplace_back("test/2", 0x8000);
	files.emplace_back("test/3", 0x8001);
	files.emplace_back("test/4", 1);
	files.emplace_back("test/5", 0);

	auto ti = make_load_torrent(files, 0x8000);
	lt::file_storage const& fs = ti->layout();

	// generally the number of blocks in a file is:
	// (file_size + lt::default_block_size - 1) / lt::default_block_size

	TEST_EQUAL(fs.file_num_blocks(0_file), 2);
	// pad file at index 1
	TEST_CHECK(fs.pad_file_at(1_file));
	TEST_EQUAL(fs.file_num_blocks(2_file), 1);
	// pad file at index 3
	TEST_CHECK(fs.pad_file_at(3_file));
	TEST_EQUAL(fs.file_num_blocks(4_file), 2);
	TEST_EQUAL(fs.file_num_blocks(5_file), 3);
	// pad file at index 6
	TEST_CHECK(fs.pad_file_at(6_file));
	TEST_EQUAL(fs.file_num_blocks(7_file), 1);
	// pad file at index 8
	TEST_CHECK(fs.pad_file_at(8_file));
	TEST_EQUAL(fs.file_num_blocks(9_file), 0);
}

TORRENT_TEST(file_num_pieces)
{
	std::vector<lt::create_file_entry> files;
	files.emplace_back("test/0", 0x5000);
	files.emplace_back("test/1", 0x2000);
	files.emplace_back("test/2", 0x8000);
	files.emplace_back("test/3", 0x8001);
	files.emplace_back("test/4", 1);
	files.emplace_back("test/5", 0);

	auto ti = make_load_torrent(files, 0x8000);
	lt::file_storage const& fs = ti->layout();

	// generally the number of blocks in a file is:
	// (file_size + lt::default_block_size - 1) / lt::default_block_size

	TEST_EQUAL(fs.file_num_pieces(0_file), 1);
	// pad file at index 1
	TEST_CHECK(fs.pad_file_at(1_file));
	TEST_EQUAL(fs.file_num_pieces(2_file), 1);
	// pad file at index 3
	TEST_CHECK(fs.pad_file_at(3_file));
	TEST_EQUAL(fs.file_num_pieces(4_file), 1);
	TEST_EQUAL(fs.file_num_pieces(5_file), 2);
	// pad file at index 6
	TEST_CHECK(fs.pad_file_at(6_file));
	TEST_EQUAL(fs.file_num_pieces(7_file), 1);
	// pad file at index 8
	TEST_CHECK(fs.pad_file_at(8_file));
	TEST_EQUAL(fs.file_num_pieces(9_file), 0);
}

TORRENT_TEST(coalesce_path)
{
	using lt::combine_path;

	std::vector<lt::create_file_entry> files;
	files.emplace_back("test/a", 10000);
	files.emplace_back("test/b", 20000);
	files.emplace_back("test/c/a", 30000);
	files.emplace_back("test/c/b", 40000);
	auto ti = make_load_torrent(files, 0x4000);
	lt::file_storage const& fs = ti->layout();

	// pad files should be created, to make sure the pad files also share the
	// same path entries

	TEST_EQUAL(fs.paths().size(), 3);
	TEST_EQUAL(fs.paths()[0_path], "");
	TEST_EQUAL(fs.paths()[1_path], ".pad");
	TEST_EQUAL(fs.paths()[2_path], "c");
}

using cfv = lt::aux::vector<lt::create_file_entry, lt::file_index_t>;

#if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
#define SEPARATOR "\\"
#else
#define SEPARATOR "/"
#endif

TORRENT_TEST(canonalize_aligned)
{
	cfv files;
	files.emplace_back("test/1-small", 0x4000);
	files.emplace_back("test/2-small", 0x4000);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, false);

	TEST_EQUAL(new_files.size(), 2);
	TEST_EQUAL(new_files[0_file].filename, "test/1-small");
	TEST_EQUAL(new_files[1_file].filename, "test/2-small");
	TEST_EQUAL(total, 0x8000);
}

TORRENT_TEST(canonalize_order)
{
	cfv files;
	files.emplace_back("test/2-small", 0x4000);
	files.emplace_back("test/1-small", 0x4000);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, false);

	TEST_EQUAL(new_files.size(), 2);
	TEST_EQUAL(new_files[0_file].filename, "test/1-small");
	TEST_EQUAL(new_files[1_file].filename, "test/2-small");
	TEST_EQUAL(total, 0x8000);
}

TORRENT_TEST(canonalize_tail_padding)
{
	cfv files;
	files.emplace_back("test/1-small", 0x4000);
	files.emplace_back("test/2-small", 0x4000);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, false);

	TEST_EQUAL(new_files.size(), 4);
	TEST_EQUAL(new_files[0_file].filename, "test/1-small");
	TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
	TEST_EQUAL(new_files[2_file].filename, "test/2-small");
	TEST_EQUAL(new_files[3_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
	TEST_EQUAL(total, 0x10000);
}

TORRENT_TEST(canonalize_empty_file)
{
	cfv files;
	files.emplace_back("test/1-small", 0x4000);
	files.emplace_back("test/2-empty", 0);
	files.emplace_back("test/3-small", 0x4000);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, false);

	TEST_EQUAL(new_files.size(), 5);
	TEST_EQUAL(new_files[0_file].filename, "test/1-small");
	TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
	TEST_EQUAL(new_files[2_file].filename, "test/2-empty");
	TEST_EQUAL(new_files[3_file].filename, "test/3-small");
	TEST_EQUAL(new_files[4_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
	TEST_EQUAL(total, 0x10000);
}

TORRENT_TEST(canonalize_single_file)
{
	cfv files;
	files.emplace_back("test/1-small", 0x4000);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, false);

	TEST_EQUAL(new_files.size(), 1);
	TEST_EQUAL(new_files[0_file].filename, "test/1-small");
	TEST_EQUAL(total, 0x4000);
}

// this is a backwards compatibility feature
TORRENT_TEST(canonalize_no_tail_padding)
{
	cfv files;
	files.emplace_back("test/1-small", 0x4000);
	files.emplace_back("test/2-small", 0x4000);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x8000, true);

	TEST_EQUAL(new_files.size(), 3);
	TEST_EQUAL(new_files[0_file].filename, "test/1-small");
	TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "16384");
	TEST_EQUAL(new_files[2_file].filename, "test/2-small");
	TEST_EQUAL(total, 0xc000);
}

TORRENT_TEST(canonalize_tree)
{
	cfv files;
	files.emplace_back("test/2/2-small", 0x3fff);
	files.emplace_back("test/2/1-small", 0x3fff);
	files.emplace_back("test/1/2-small", 0x3fff);
	files.emplace_back("test/1/1-small", 0x3fff);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, false);

	TEST_EQUAL(new_files.size(), 8);
	TEST_EQUAL(new_files[0_file].filename, "test/1/1-small");
	TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
	TEST_EQUAL(new_files[2_file].filename, "test/1/2-small");
	TEST_EQUAL(new_files[3_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
	TEST_EQUAL(new_files[4_file].filename, "test/2/1-small");
	TEST_EQUAL(new_files[5_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
	TEST_EQUAL(new_files[6_file].filename, "test/2/2-small");
	TEST_EQUAL(new_files[7_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
	TEST_EQUAL(total, 0x10000);
}

// this is a backwards compatibility feature
TORRENT_TEST(canonalize_tree_no_tail_padding)
{
	cfv files;
	files.emplace_back("test/2/2-small", 0x3fff);
	files.emplace_back("test/2/1-small", 0x3fff);
	files.emplace_back("test/1/2-small", 0x3fff);
	files.emplace_back("test/1/1-small", 0x3fff);
	auto const [new_files, total] = lt::aux::canonicalize(std::move(files), 0x4000, true);

	TEST_EQUAL(new_files.size(), 7);
	TEST_EQUAL(new_files[0_file].filename, "test/1/1-small");
	TEST_EQUAL(new_files[1_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
	TEST_EQUAL(new_files[2_file].filename, "test/1/2-small");
	TEST_EQUAL(new_files[3_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
	TEST_EQUAL(new_files[4_file].filename, "test/2/1-small");
	TEST_EQUAL(new_files[5_file].filename, "test" SEPARATOR ".pad" SEPARATOR "1");
	TEST_EQUAL(new_files[6_file].filename, "test/2/2-small");
	TEST_EQUAL(total, 0x10000 - 1);
}
