#include "torrent_view.hpp"
#include "print.hpp"
#include "libtorrent/torrent_status.hpp"

const int header_size = 2;

std::string torrent_state(lt::torrent_status const& s)
{
	static char const* state_str[] =
		{"checking (q)", "checking", "dl metadata"
		, "downloading", "finished", "seeding", "allocating", "checking (r)"};

	if (s.errc) return s.errc.message();
	std::string ret;
	if (s.paused && !s.auto_managed) ret += "paused";
	else if (s.paused && s.auto_managed) ret += "queued";
	else if (s.upload_mode) ret += "upload mode";
	else ret += state_str[s.state];
	if (!s.paused && !s.auto_managed) ret += " [F]";
	char buf[10];
	std::snprintf(buf, sizeof(buf), " (%.1f%%)", s.progress_ppm / 10000.f);
	ret += buf;
	return ret;
}

bool compare_torrent(lt::torrent_status const* lhs, lt::torrent_status const* rhs)
{
	if (lhs->queue_position != -1 && rhs->queue_position != -1)
	{
		// both are downloading, sort by queue pos
		return lhs->queue_position < rhs->queue_position;
	}
	else if (lhs->queue_position == -1 && rhs->queue_position == -1)
	{
		// both are seeding, sort by seed-rank
		if (lhs->seed_rank != rhs->seed_rank)
			return lhs->seed_rank > rhs->seed_rank;

		return lhs->info_hash < rhs->info_hash;
	}

	return (lhs->queue_position == -1) < (rhs->queue_position == -1);
}

torrent_view::torrent_view()
	: m_active_torrent(0)
	, m_scroll_position(0)
	, m_torrent_filter(0)
	, m_width(80)
	, m_height(30)
{}

void torrent_view::set_size(int width, int height)
{
	if (m_width == width && m_height == height) return;

	m_width = width;
	m_height = height;
	render();
}

int torrent_view::filter() const
{
	return m_torrent_filter;
}

void torrent_view::set_filter(int filter)
{
	if (filter == m_torrent_filter) return;
	m_torrent_filter = filter;

	update_filtered_torrents();
	render();
}

// returns the lt::torrent_status of the currently selected torrent.
lt::torrent_status const& torrent_view::get_active_torrent() const
{
	if (m_active_torrent >= int(m_filtered_handles.size()))
		m_active_torrent = int(m_filtered_handles.size()) - 1;
	if (m_active_torrent < 0) m_active_torrent = 0;
	TORRENT_ASSERT(m_active_torrent >= 0);

	return *m_filtered_handles[m_active_torrent];
}

lt::torrent_handle torrent_view::get_active_handle() const
{
	if (m_active_torrent >= int(m_filtered_handles.size()))
		m_active_torrent = int(m_filtered_handles.size()) - 1;
	if (m_active_torrent < 0) m_active_torrent = 0;
	TORRENT_ASSERT(m_active_torrent >= 0);

	if (m_filtered_handles.empty()) return lt::torrent_handle();

	return m_filtered_handles[m_active_torrent]->handle;
}

void torrent_view::update_torrents(std::vector<lt::torrent_status> const& st)
{
	std::set<lt::torrent_handle> updates;
	bool need_filter_update = false;
	for (std::vector<lt::torrent_status>::const_iterator i = st.begin();
		i != st.end(); ++i)
	{
		auto j = m_all_handles.find(*i);
		// add new entries here
		if (j == m_all_handles.end())
		{
			j = m_all_handles.insert(*i).first;
			if (show_torrent(*j))
			{
				m_filtered_handles.push_back(&*j);
				need_filter_update = true;
			}
		}
		else
		{
			bool prev_show = show_torrent(*j);
			((lt::torrent_status&)*j) = *i;
			if (prev_show != show_torrent(*j))
				need_filter_update = true;
			else
				updates.insert(i->handle);
		}
	}
	if (need_filter_update)
	{
		update_filtered_torrents();
		render();
	}
	else
	{
		int torrent_index = 0;
		for (std::vector<lt::torrent_status const*>::iterator i
			= m_filtered_handles.begin();
			i != m_filtered_handles.end(); ++torrent_index)
		{
			if (torrent_index < m_scroll_position
				|| torrent_index >= m_scroll_position + m_height - header_size)
			{
				++i;
				continue;
			}

			lt::torrent_status const& s = **i;
			++i;

			if (!s.handle.is_valid())
				continue;

			if (updates.count(s.handle) == 0)
				continue;

			set_cursor_pos(0, header_size + torrent_index - m_scroll_position);
			print_torrent(s, torrent_index == m_active_torrent);
		}
	}
}

int torrent_view::height() const
{
	return m_height;
}

void torrent_view::arrow_up()
{
	if (m_filtered_handles.empty()) return;
	if (m_active_torrent <= 0) return;

	if (m_active_torrent - 1 < m_scroll_position)
	{
		--m_active_torrent;
		m_scroll_position = m_active_torrent;
		TORRENT_ASSERT(m_scroll_position >= 0);
		render();
		return;
	}

	set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position);
	print_torrent(*m_filtered_handles[m_active_torrent], false);
	--m_active_torrent;
	TORRENT_ASSERT(m_active_torrent >= 0);

	set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position);
	print_torrent(*m_filtered_handles[m_active_torrent], true);
}

void torrent_view::arrow_down()
{
	if (m_filtered_handles.empty()) return;
	if (m_active_torrent >= int(m_filtered_handles.size()) - 1) return;

	int bottom_pos = m_height - header_size - 1;
	if (m_active_torrent - m_scroll_position + 1 > bottom_pos)
	{
		++m_active_torrent;
		m_scroll_position = m_active_torrent - bottom_pos;
		TORRENT_ASSERT(m_scroll_position >= 0);
		render();
		return;
	}

	set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position);
	print_torrent(*m_filtered_handles[m_active_torrent], false);

	TORRENT_ASSERT(m_active_torrent >= 0);
	++m_active_torrent;

	set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position);
	print_torrent(*m_filtered_handles[m_active_torrent], true);
}

void torrent_view::render()
{
	print_tabs();
	print_headers();

	int lines_printed = header_size;

	int torrent_index = 0;

	for (std::vector<lt::torrent_status const*>::iterator i = m_filtered_handles.begin();
		i != m_filtered_handles.end(); ++torrent_index)
	{
		if (torrent_index < m_scroll_position)
		{
			++i;
			continue;
		}
		if (lines_printed >= m_height)
			break;

		lt::torrent_status const& s = **i;
		if (!s.handle.is_valid())
		{
			i = m_filtered_handles.erase(i);
			continue;
		}
		++i;

		set_cursor_pos(0, torrent_index + header_size - m_scroll_position);
		print_torrent(s, torrent_index == m_active_torrent);
		++lines_printed;
	}

	clear_rows(torrent_index + header_size, m_height);
}

void torrent_view::print_tabs()
{
	set_cursor_pos(0, 0);

	char str[400];
	int pos = 0;
	char const* filter_names[] = { "all", "downloading", "non-paused"
		, "seeding", "queued", "stopped", "checking"};
	for (int i = 0; i < int(sizeof(filter_names)/sizeof(filter_names[0])); ++i)
	{
		pos += std::snprintf(str+ pos, sizeof(str) - pos, "%s[%s]%s"
			, m_torrent_filter == i?esc("7"):""
			, filter_names[i], m_torrent_filter == i?esc("0"):"");
	}
	pos += std::snprintf(str + pos, sizeof(str) - pos, "\x1b[K");

	if (m_width + 1 < int(sizeof(str)))
		str[m_width + 1] = '\0';
	print(str);
}

void torrent_view::print_headers()
{
	set_cursor_pos(0, 1);

	char str[400];

	// print title bar for torrent list
	std::snprintf(str, sizeof(str)
		, " %-3s %-50s %-35s %-17s %-17s %-11s %-6s %-6s %-4s\x1b[K"
		, "#", "Name", "Progress", "Download", "Upload", "Peers (D:S)"
		, "Down", "Up", "Flags");

	if (m_width + 1 < int(sizeof(str)))
		str[m_width + 1] = '\0';

	print(str);
}

void torrent_view::print_torrent(lt::torrent_status const& s, bool selected)
{
	int pos = 0;
	char str[512];

	// the active torrent is highligted in the list
	// this inverses the forground and background colors
	char const* selection = "";
	if (selected)
		selection = "\x1b[1m\x1b[44m";

	char queue_pos[16] = {0};
	if (s.queue_position == -1)
		std::snprintf(queue_pos, sizeof(queue_pos), "-");
	else
		std::snprintf(queue_pos, sizeof(queue_pos), "%d", s.queue_position);

	std::string name = s.name;
	if (name.size() > 50) name.resize(50);

	color_code progress_bar_color = col_yellow;
	if (s.errc) progress_bar_color = col_red;
	else if (s.paused) progress_bar_color = col_blue;
	else if (s.state == lt::torrent_status::downloading_metadata)
		progress_bar_color = col_magenta;
	else if (s.current_tracker.empty())
		progress_bar_color = col_green;

	pos += std::snprintf(str + pos, sizeof(str) - pos, "%s%-3s %-50s %s%s %s (%s) "
		"%s (%s) %5d:%-5d %s %s %c"
		, selection
		, queue_pos
		, name.c_str()
		, progress_bar(s.progress_ppm / 1000, 35, progress_bar_color, '-', '#', torrent_state(s)).c_str()
		, selection
		, color(add_suffix(s.download_rate, "/s"), col_green).c_str()
		, color(add_suffix(s.total_download), col_green).c_str()
		, color(add_suffix(s.upload_rate, "/s"), col_red).c_str()
		, color(add_suffix(s.total_upload), col_red).c_str()
		, s.num_peers - s.num_seeds, s.num_seeds
		, color(add_suffix(s.all_time_download), col_green).c_str()
		, color(add_suffix(s.all_time_upload), col_red).c_str()
		, s.need_save_resume?'S':' ');

	// if this is the selected torrent, restore the background color
	if (selected)
		pos += std::snprintf(str + pos, sizeof(str) - pos, "%s", esc("0"));

	pos += std::snprintf(str + pos, sizeof(str) - pos, "\x1b[K");

	print(str);
}

bool torrent_view::show_torrent(lt::torrent_status const& st)
{
	switch (m_torrent_filter)
	{
		case torrents_all: return true;
		case torrents_downloading:
			return !st.paused
				&& st.state != lt::torrent_status::seeding
				&& st.state != lt::torrent_status::finished;
		case torrents_not_paused: return !st.paused;
		case torrents_seeding:
			return !st.paused
				&& (st.state == lt::torrent_status::seeding
				|| st.state == lt::torrent_status::finished);
		case torrents_queued: return st.paused && st.auto_managed;
		case torrents_stopped: return st.paused && !st.auto_managed;
		case torrents_checking: return st.state == lt::torrent_status::checking_files;
	}
	return true;
}

// refresh all pointers in m_filtered_handles. This must be done when
// inserting or removing elements from m_all_handles, since pointers may
// be invalidated or when a torrent changes status to either become
// visible or filtered
void torrent_view::update_filtered_torrents()
{
	m_scroll_position = 0;
	m_filtered_handles.clear();
	for (auto const& h : m_all_handles)
	{
		if (!show_torrent(h)) continue;
		m_filtered_handles.push_back(&h);
	}
	if (m_active_torrent >= int(m_filtered_handles.size())) m_active_torrent = int(m_filtered_handles.size()) - 1;
	if (m_active_torrent < 0) m_active_torrent = 0;
	TORRENT_ASSERT(m_active_torrent >= 0);
	std::sort(m_filtered_handles.begin(), m_filtered_handles.end(), &compare_torrent);
}