// Copyright Andrew Resch 2009. Use, modification and distribution is
// subject to the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include "boost_python.hpp"
#include "libtorrent/socket.hpp"
#include "libtorrent/address.hpp"
#include "libtorrent/error_code.hpp"
#include "libtorrent/session_stats.hpp" // for stats_metric
#include "libtorrent/kademlia/announce_flags.hpp"
#include "libtorrent/time.hpp"
#include "libtorrent/torrent_flags.hpp"
#include "libtorrent/units.hpp"
#include "libtorrent/sha1_hash.hpp"
#include "libtorrent/disk_interface.hpp" // for open_file_state
#include "libtorrent/aux_/noexcept_movable.hpp"
#include "libtorrent/peer_info.hpp"
#include "libtorrent/alert_types.hpp"
#include "libtorrent/session_types.hpp" // for save_state_flags_t
#include "libtorrent/file_storage.hpp" // for file_flags_t
#include "libtorrent/alert.hpp"
#include "libtorrent/create_torrent.hpp" // for create_flags_t
#include "libtorrent/portmap.hpp" // for port_mapping_t
#include "libtorrent/peer_class.hpp"
#include "libtorrent/pex_flags.hpp"
#include "libtorrent/string_view.hpp"
#include "libtorrent/storage_defs.hpp"
#include "libtorrent/kademlia/announce_flags.hpp"
#include "libtorrent/write_resume_data.hpp"
#include <vector>
#include <map>

using namespace boost::python;
namespace bp = boost::python;

template <class T>
struct endpoint_to_tuple
{
	static PyObject* convert(T const& ep)
	{
		return incref(bp::make_tuple(ep.address().to_string(), ep.port()).ptr());
	}
};

struct piece_block_to_tuple
{
	static PyObject* convert(lt::piece_block const& pb)
	{
		return incref(bp::make_tuple(pb.piece_index, pb.block_index).ptr());
	}
};

template <class T>
struct tuple_to_endpoint
{
	tuple_to_endpoint() { converter::registry::push_back(&convertible, &construct, type_id<T>()); }

	static void* convertible(PyObject* x)
	{
		if (!PyTuple_Check(x)) return nullptr;
		if (PyTuple_Size(x) != 2) return nullptr;
		extract<std::string> ip(object(borrowed(PyTuple_GetItem(x, 0))));
		if (!ip.check()) return nullptr;
		extract<std::uint16_t> port(object(borrowed(PyTuple_GetItem(x, 1))));
		if (!port.check()) return nullptr;
		lt::error_code ec;
		lt::make_address(ip, ec);
		if (ec) return nullptr;
		return x;
	}

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = ((converter::rvalue_from_python_storage<T*>*)data)->storage.bytes;

		object o(borrowed(x));
		data->convertible = new (storage)
			T(lt::make_address(static_cast<std::string>(extract<std::string>(o[0]))),
			  extract<std::uint16_t>(o[1]));
	}
};

template <class T1, class T2>
struct pair_to_tuple
{
	static PyObject* convert(const std::pair<T1, T2>& p)
	{
		return incref(bp::make_tuple(p.first, p.second).ptr());
	}
};

template <typename Addr>
struct address_to_tuple
{
	static PyObject* convert(Addr const& addr)
	{
		return incref(bp::object(addr.to_string()).ptr());
	}
};

template <class T1, class T2>
struct tuple_to_pair
{
	tuple_to_pair()
	{
		converter::registry::push_back(&convertible, &construct, type_id<std::pair<T1, T2>>());
	}

	static void* convertible(PyObject* x)
	{
		return (PyTuple_Check(x) && PyTuple_Size(x) == 2) ? x : nullptr;
	}

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage =
			((converter::rvalue_from_python_storage<std::pair<T1, T2>>*)data)->storage.bytes;

		object o(borrowed(x));
		std::pair<T1, T2> p;
		p.first = extract<T1>(o[0]);
		p.second = extract<T2>(o[1]);
		data->convertible = new (storage) std::pair<T1, T2>(p);
	}
};

struct from_string_view
{
	static PyObject* convert(lt::string_view v)
	{
		str ret(v.data(), v.size());
		return incref(ret.ptr());
	}
};

struct to_string_view
{
	to_string_view()
	{
		converter::registry::push_back(&convertible, &construct, type_id<lt::string_view>());
	}

	static void* convertible(PyObject* x)
	{
		return
#if PY_VERSION_HEX < 0x03020000
			PyString_Check(x) ? x :
#endif
			PyUnicode_Check(x) ? x
							   : nullptr;
	}

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage =
			((converter::rvalue_from_python_storage<lt::string_view>*)data)->storage.bytes;

#if PY_VERSION_HEX < 0x03020000
		if (PyString_Check(x))
		{
			data->convertible =
				new (storage) lt::string_view(PyString_AsString(x), PyString_Size(x));
		}
		else
#endif
		{
			Py_ssize_t size = 0;
			char const* unicode = PyUnicode_AsUTF8AndSize(x, &size);
			data->convertible = new (storage) lt::string_view(unicode, size);
		}
	}
};

template <typename Map>
struct map_to_dict
{
	static PyObject* convert(Map const& m)
	{
		dict ret;
		for (auto const& e : m)
			ret[e.first] = e.second;
		return incref(ret.ptr());
	}
};

template <class T1, class T2, class Map = std::map<T1, T2>>
struct dict_to_map
{
	dict_to_map() { converter::registry::push_back(&convertible, &construct, type_id<Map>()); }

	static void* convertible(PyObject* x) { return PyDict_Check(x) ? x : nullptr; }

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = ((converter::rvalue_from_python_storage<Map>*)data)->storage.bytes;

		dict o(borrowed(x));
		Map m;

		stl_input_iterator<T1> i(o.keys()), end;
		for (; i != end; ++i)
		{
			T1 const& key = *i;
			m[key] = extract<T2>(o[key]);
		}
		data->convertible = new (storage) Map(m);
	}
};

template <class T>
struct vector_to_list
{
	static PyObject* convert(T const& v)
	{
		list l;
		for (int i = 0; i < int(v.size()); ++i)
		{
			l.append(v[i]);
		}
		return incref(l.ptr());
	}
};

template <class T>
struct list_to_vector
{
	list_to_vector() { converter::registry::push_back(&convertible, &construct, type_id<T>()); }

	static void* convertible(PyObject* x) { return PyList_Check(x) ? x : nullptr; }

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = ((converter::rvalue_from_python_storage<T>*)data)->storage.bytes;

		T p;
		int const size = int(PyList_Size(x));
		p.reserve(size);
		for (int i = 0; i < size; ++i)
		{
			object o(borrowed(PyList_GetItem(x, i)));
			p.push_back(extract<typename T::value_type>(o));
		}
		data->convertible = new (storage) T(std::move(p));
	}
};

template <class T, typename IndexType = int>
struct list_to_bitfield
{
	list_to_bitfield() { converter::registry::push_back(&convertible, &construct, type_id<T>()); }

	static void* convertible(PyObject* x) { return PyList_Check(x) ? x : nullptr; }

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = ((converter::rvalue_from_python_storage<T>*)data)->storage.bytes;

		T p;
		int const size = int(PyList_Size(x));
		p.resize(size);
		for (int i = 0; i < size; ++i)
		{
			object o(borrowed(PyList_GetItem(x, i)));
			if (extract<bool>(o))
				p.set_bit(IndexType{i});
			else
				p.clear_bit(IndexType{i});
		}
		data->convertible = new (storage) T(std::move(p));
	}
};

template <class T>
struct bitfield_to_list
{
	static PyObject* convert(T const& v)
	{
		list ret;
		for (auto const i : v)
			ret.append(i);
		return incref(ret.ptr());
	}
};

template <class T>
struct from_strong_typedef
{
	using underlying_type = typename T::underlying_type;

	static PyObject* convert(const T& v)
	{
		object o(static_cast<underlying_type>(v));
		return incref(o.ptr());
	}
};

template <typename T>
struct to_strong_typedef
{
	using underlying_type = typename T::underlying_type;

	to_strong_typedef() { converter::registry::push_back(&convertible, &construct, type_id<T>()); }

	static void* convertible(PyObject* x) { return PyNumber_Check(x) ? x : nullptr; }

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = ((converter::rvalue_from_python_storage<T>*)data)->storage.bytes;
		data->convertible = new (storage) T(extract<underlying_type>(object(borrowed(x))));
	}
};

template <typename T>
struct to_enum_class
{
	using underlying_type = typename std::underlying_type<T>::type;

	to_enum_class() { converter::registry::push_back(&convertible, &construct, type_id<T>()); }

	static void* convertible(PyObject* x) { return PyNumber_Check(x) ? x : nullptr; }

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = ((converter::rvalue_from_python_storage<T>*)data)->storage.bytes;
		data->convertible = new (storage) T(static_cast<T>(
			static_cast<underlying_type>(extract<underlying_type>(object(borrowed(x))))
		));
	}
};

template <class T>
struct from_bitfield_flag
{
	using underlying_type = typename T::underlying_type;

	static PyObject* convert(T const v)
	{
		// this is because python uses "long int" to represent integral values
		// internally, it cannot hold large unsigned values
		auto const val = static_cast<underlying_type>(v)
			& static_cast<std::uint64_t>(std::numeric_limits<long int>::max());
		object o(val);
		return incref(o.ptr());
	}
};

template <typename T>
struct to_bitfield_flag
{
	using underlying_type = typename T::underlying_type;

	to_bitfield_flag() { converter::registry::push_back(&convertible, &construct, type_id<T>()); }

	static void* convertible(PyObject* x) { return PyNumber_Check(x) ? x : nullptr; }

	static void construct(PyObject* x, converter::rvalue_from_python_stage1_data* data)
	{
		void* storage = ((converter::rvalue_from_python_storage<T>*)data)->storage.bytes;
		data->convertible = new (storage) T(extract<underlying_type>(object(borrowed(x))));
	}
};

void bind_converters()
{
	// C++ -> python conversions
	to_python_converter<std::pair<int, int>, pair_to_tuple<int, int>>();
	to_python_converter<
		std::pair<lt::piece_index_t, lt::download_priority_t>,
		pair_to_tuple<lt::piece_index_t, lt::download_priority_t>>();
	to_python_converter<lt::tcp::endpoint, endpoint_to_tuple<lt::tcp::endpoint>>();
	to_python_converter<lt::udp::endpoint, endpoint_to_tuple<lt::udp::endpoint>>();
	to_python_converter<lt::address, address_to_tuple<lt::address>>();
	to_python_converter<std::pair<std::string, int>, pair_to_tuple<std::string, int>>();
	to_python_converter<
		std::pair<std::string, std::string>,
		pair_to_tuple<std::string, std::string>>();
	to_python_converter<lt::piece_block, piece_block_to_tuple>();

	to_python_converter<
		std::vector<lt::create_file_entry>,
		vector_to_list<std::vector<lt::create_file_entry>>>();
	to_python_converter<
		std::vector<lt::stats_metric>,
		vector_to_list<std::vector<lt::stats_metric>>>();
	to_python_converter<
		std::vector<lt::open_file_state>,
		vector_to_list<std::vector<lt::open_file_state>>>();
	to_python_converter<std::vector<lt::sha1_hash>, vector_to_list<std::vector<lt::sha1_hash>>>();
	to_python_converter<std::vector<std::string>, vector_to_list<std::vector<std::string>>>();
	to_python_converter<std::vector<int>, vector_to_list<std::vector<int>>>();
	to_python_converter<
		std::vector<lt::download_priority_t>,
		vector_to_list<std::vector<lt::download_priority_t>>>();
	to_python_converter<
		std::vector<lt::tcp::endpoint>,
		vector_to_list<std::vector<lt::tcp::endpoint>>>();
	to_python_converter<
		std::vector<lt::udp::endpoint>,
		vector_to_list<std::vector<lt::udp::endpoint>>>();
	to_python_converter<
		std::vector<std::pair<std::string, int>>,
		vector_to_list<std::vector<std::pair<std::string, int>>>>();
	to_python_converter<
		std::vector<std::pair<std::string, std::string>>,
		vector_to_list<std::vector<std::pair<std::string, std::string>>>>();
	to_python_converter<
		std::vector<lt::port_mapping_t>,
		vector_to_list<std::vector<lt::port_mapping_t>>>();
	to_python_converter<std::vector<lt::piece_block>, vector_to_list<std::vector<lt::piece_block>>>(
	);

	to_python_converter<
		lt::typed_bitfield<lt::piece_index_t>,
		bitfield_to_list<lt::typed_bitfield<lt::piece_index_t>>>();
	to_python_converter<lt::bitfield, bitfield_to_list<lt::bitfield>>();

	to_python_converter<lt::queue_position_t, from_strong_typedef<lt::queue_position_t>>();
	to_python_converter<lt::piece_index_t, from_strong_typedef<lt::piece_index_t>>();
	to_python_converter<lt::download_priority_t, from_strong_typedef<lt::download_priority_t>>();
	to_python_converter<lt::file_index_t, from_strong_typedef<lt::file_index_t>>();
	to_python_converter<lt::port_mapping_t, from_strong_typedef<lt::port_mapping_t>>();
	to_python_converter<lt::peer_class_t, from_strong_typedef<lt::peer_class_t>>();
	to_python_converter<lt::connection_type_t, from_bitfield_flag<lt::connection_type_t>>();
	to_python_converter<lt::torrent_flags_t, from_bitfield_flag<lt::torrent_flags_t>>();
	to_python_converter<lt::peer_flags_t, from_bitfield_flag<lt::peer_flags_t>>();
	to_python_converter<lt::peer_source_flags_t, from_bitfield_flag<lt::peer_source_flags_t>>();
	to_python_converter<
		lt::bandwidth_state_flags_t,
		from_bitfield_flag<lt::bandwidth_state_flags_t>>();
	to_python_converter<lt::file_open_mode_t, from_bitfield_flag<lt::file_open_mode_t>>();
	to_python_converter<lt::status_flags_t, from_bitfield_flag<lt::status_flags_t>>();
	to_python_converter<lt::alert_category_t, from_bitfield_flag<lt::alert_category_t>>();
	to_python_converter<lt::resume_data_flags_t, from_bitfield_flag<lt::resume_data_flags_t>>();
	to_python_converter<lt::add_piece_flags_t, from_bitfield_flag<lt::add_piece_flags_t>>();
	to_python_converter<lt::pause_flags_t, from_bitfield_flag<lt::pause_flags_t>>();
	to_python_converter<lt::deadline_flags_t, from_bitfield_flag<lt::deadline_flags_t>>();
	to_python_converter<lt::save_state_flags_t, from_bitfield_flag<lt::save_state_flags_t>>();
	to_python_converter<lt::remove_flags_t, from_bitfield_flag<lt::remove_flags_t>>();
	to_python_converter<lt::reopen_network_flags_t, from_bitfield_flag<lt::reopen_network_flags_t>>(
	);
	to_python_converter<lt::file_flags_t, from_bitfield_flag<lt::file_flags_t>>();
	to_python_converter<lt::create_flags_t, from_bitfield_flag<lt::create_flags_t>>();
	to_python_converter<lt::pex_flags_t, from_bitfield_flag<lt::pex_flags_t>>();
	to_python_converter<lt::reannounce_flags_t, from_bitfield_flag<lt::reannounce_flags_t>>();
	to_python_converter<lt::dht::announce_flags_t, from_bitfield_flag<lt::dht::announce_flags_t>>();
	to_python_converter<lt::file_progress_flags_t, from_bitfield_flag<lt::file_progress_flags_t>>();
	to_python_converter<lt::write_torrent_flags_t, from_bitfield_flag<lt::write_torrent_flags_t>>();
	to_python_converter<lt::picker_flags_t, from_bitfield_flag<lt::picker_flags_t>>();
	to_python_converter<lt::string_view, from_string_view>();

	// work-around types
	to_python_converter<
		lt::aux::noexcept_movable<lt::address>,
		address_to_tuple<lt::aux::noexcept_movable<lt::address>>>();
	to_python_converter<
		lt::aux::noexcept_movable<lt::tcp::endpoint>,
		endpoint_to_tuple<lt::aux::noexcept_movable<lt::tcp::endpoint>>>();
	to_python_converter<
		lt::aux::noexcept_movable<lt::udp::endpoint>,
		endpoint_to_tuple<lt::aux::noexcept_movable<lt::udp::endpoint>>>();
	to_python_converter<
		lt::aux::noexcept_movable<std::map<lt::piece_index_t, lt::bitfield>>,
		map_to_dict<lt::aux::noexcept_movable<std::map<lt::piece_index_t, lt::bitfield>>>>();
	to_python_converter<
		lt::aux::noexcept_movable<std::map<lt::file_index_t, std::string>>,
		map_to_dict<lt::aux::noexcept_movable<std::map<lt::file_index_t, std::string>>>>();
	to_python_converter<
		std::map<lt::file_index_t, std::string>,
		map_to_dict<std::map<lt::file_index_t, std::string>>>();
	to_python_converter<lt::session_flags_t, from_bitfield_flag<lt::session_flags_t>>();

#if TORRENT_ABI_VERSION == 1
	to_python_converter<std::vector<char>, vector_to_list<std::vector<char>>>();
	list_to_vector<std::vector<char>>();
	to_python_converter<
		lt::aux::noexcept_movable<std::vector<char>>,
		vector_to_list<lt::aux::noexcept_movable<std::vector<char>>>>();
	list_to_vector<lt::aux::noexcept_movable<std::vector<char>>>();

#ifndef TORRENT_DISABLE_DHT
	to_python_converter<std::vector<lt::dht_lookup>, vector_to_list<std::vector<lt::dht_lookup>>>();
#endif
#endif

	// python -> C++ conversions
	tuple_to_pair<int, int>();
	tuple_to_pair<std::string, int>();
	tuple_to_pair<std::string, std::string>();
	tuple_to_endpoint<lt::tcp::endpoint>();
	tuple_to_endpoint<lt::udp::endpoint>();
	tuple_to_pair<lt::piece_index_t, lt::download_priority_t>();
	dict_to_map<lt::file_index_t, std::string>();
	list_to_vector<std::vector<int>>();
	list_to_vector<std::vector<lt::download_priority_t>>();
	list_to_vector<std::vector<std::string>>();
	list_to_vector<std::vector<lt::tcp::endpoint>>();
	list_to_vector<std::vector<lt::udp::endpoint>>();
	list_to_vector<std::vector<std::pair<std::string, int>>>();
	list_to_vector<std::vector<std::pair<std::string, std::string>>>();
	list_to_vector<std::vector<lt::sha1_hash>>();
	list_to_vector<std::vector<lt::create_file_entry>>();

	// work-around types
	list_to_vector<lt::aux::noexcept_movable<std::vector<int>>>();
	list_to_vector<lt::aux::noexcept_movable<std::vector<lt::download_priority_t>>>();
	list_to_vector<lt::aux::noexcept_movable<std::vector<std::string>>>();
	list_to_vector<lt::aux::noexcept_movable<std::vector<lt::tcp::endpoint>>>();
	list_to_vector<lt::aux::noexcept_movable<std::vector<lt::udp::endpoint>>>();
	list_to_vector<lt::aux::noexcept_movable<std::vector<std::pair<std::string, int>>>>();
	list_to_vector<lt::aux::noexcept_movable<std::vector<lt::sha1_hash>>>();
	dict_to_map<
		lt::piece_index_t,
		lt::bitfield,
		lt::aux::noexcept_movable<std::map<lt::piece_index_t, lt::bitfield>>>();
	dict_to_map<
		lt::file_index_t,
		std::string,
		lt::aux::noexcept_movable<std::map<lt::file_index_t, std::string>>>();

	// bitfield types
	list_to_bitfield<lt::typed_bitfield<lt::piece_index_t>, lt::piece_index_t>();
	list_to_bitfield<lt::bitfield>();

	bitfield_to_list<lt::typed_bitfield<lt::piece_index_t>>();
	bitfield_to_list<lt::bitfield>();

	to_strong_typedef<lt::queue_position_t>();
	to_strong_typedef<lt::piece_index_t>();
	to_strong_typedef<lt::download_priority_t>();
	to_strong_typedef<lt::file_index_t>();
	to_strong_typedef<lt::port_mapping_t>();
	to_strong_typedef<lt::peer_class_t>();
	to_enum_class<lt::move_flags_t>();
	to_enum_class<lt::dht_log_alert::dht_module_t>();
#if TORRENT_ABI_VERSION <= 2
	to_enum_class<lt::event_t>();
#endif
	to_bitfield_flag<lt::torrent_flags_t>();
	to_bitfield_flag<lt::peer_flags_t>();
	to_bitfield_flag<lt::peer_source_flags_t>();
	to_bitfield_flag<lt::bandwidth_state_flags_t>();
	to_bitfield_flag<lt::file_open_mode_t>();
	to_bitfield_flag<lt::status_flags_t>();
	to_bitfield_flag<lt::alert_category_t>();
	to_bitfield_flag<lt::resume_data_flags_t>();
	to_bitfield_flag<lt::add_piece_flags_t>();
	to_bitfield_flag<lt::pause_flags_t>();
	to_bitfield_flag<lt::deadline_flags_t>();
	to_bitfield_flag<lt::save_state_flags_t>();
	to_bitfield_flag<lt::remove_flags_t>();
	to_bitfield_flag<lt::reopen_network_flags_t>();
	to_bitfield_flag<lt::file_flags_t>();
	to_bitfield_flag<lt::create_flags_t>();
	to_bitfield_flag<lt::pex_flags_t>();
	to_bitfield_flag<lt::reannounce_flags_t>();
	to_bitfield_flag<lt::dht::announce_flags_t>();
	to_string_view();
	to_bitfield_flag<lt::session_flags_t>();
	to_bitfield_flag<lt::file_progress_flags_t>();
	to_bitfield_flag<lt::write_torrent_flags_t>();
	to_bitfield_flag<lt::picker_flags_t>();
}
