// Copyright 2020 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Copyright (c) 2015-2017 Ivo Wetzel
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//! Adapted from https://github.com/behnam/rust-cursive-table-view
//! A basic table view implementation for [cursive](https://crates.io/crates/cursive).

#![deny(
	missing_docs,
	missing_copy_implementations,
	trivial_casts,
	trivial_numeric_casts,
	unsafe_code,
	unused_import_braces,
	unused_qualifications
)]

// Crate Dependencies ---------------------------------------------------------
use cursive;

// STD Dependencies -----------------------------------------------------------
use std::cmp::{self, Ordering};
use std::collections::HashMap;
use std::hash::Hash;
use std::rc::Rc;

// External Dependencies ------------------------------------------------------
use cursive::align::HAlign;
use cursive::direction::Direction;
use cursive::event::{Callback, Event, EventResult, Key};
use cursive::theme::ColorStyle;
use cursive::theme::PaletteColor::{Highlight, HighlightInactive, Primary};
use cursive::vec::Vec2;
use cursive::view::{ScrollBase, View};
use cursive::With;
use cursive::{Cursive, Printer};

/// A trait for displaying and sorting items inside a
/// [`TableView`](struct.TableView.html).
pub trait TableViewItem<H>: Clone + Sized
where
	H: Eq + Hash + Copy + Clone + 'static,
{
	/// Method returning a string representation of the item for the
	/// specified column from type `H`.
	fn to_column(&self, column: H) -> String;

	/// Method comparing two items via their specified column from type `H`.
	fn cmp(&self, other: &Self, column: H) -> Ordering
	where
		Self: Sized;
}

/// View to select an item among a list, supporting multiple columns for
/// sorting.
///
/// # Examples
///
/// ```rust
/// # extern crate cursive;
/// # extern crate cursive_table_view;
/// # use std::cmp::Ordering;
/// # use cursive_table_view::{TableView, TableViewItem};
/// # use cursive::align::HAlign;
/// # fn main() {
/// // Provide a type for the table's columns
/// #[derive(Copy, Clone, PartialEq, Eq, Hash)]
/// enum BasicColumn {
///     Name,
///     Count,
///     Rate
/// }
///
/// // Define the item type
/// #[derive(Clone, Debug)]
/// struct Foo {
///     name: String,
///     count: usize,
///     rate: usize
/// }
///
/// impl TableViewItem<BasicColumn> for Foo {
///
///     fn to_column(&self, column: BasicColumn) -> String {
///         match column {
///             BasicColumn::Name => self.name.to_string(),
///             BasicColumn::Count => format!("{}", self.count),
///             BasicColumn::Rate => format!("{}", self.rate)
///         }
///     }
///
///     fn cmp(&self, other: &Self, column: BasicColumn) -> Ordering where Self: Sized {
///         match column {
///             BasicColumn::Name => self.name.cmp(&other.name),
///             BasicColumn::Count => self.count.cmp(&other.count),
///             BasicColumn::Rate => self.rate.cmp(&other.rate)
///         }
///     }
///
/// }
///
/// // Configure the actual table
/// let table = TableView::<Foo, BasicColumn>::new()
///                      .column(BasicColumn::Name, "Name", |c| c.width(20))
///                      .column(BasicColumn::Count, "Count", |c| c.align(HAlign::Center))
///                      .column(BasicColumn::Rate, "Rate", |c| {
///                          c.ordering(Ordering::Greater).align(HAlign::Right).width(20)
///                      })
///                      .default_column(BasicColumn::Name);
/// # }
/// ```
pub struct TableView<T: TableViewItem<H> + PartialEq, H: Eq + Hash + Copy + Clone + 'static> {
	enabled: bool,
	scrollbase: ScrollBase,
	last_size: Vec2,

	column_select: bool,
	columns: Vec<TableColumn<H>>,
	column_indices: HashMap<H, usize>,

	focus: usize,
	items: Vec<T>,
	rows_to_items: Vec<usize>,

	on_sort: Option<Rc<dyn Fn(&mut Cursive, H, Ordering)>>,
	// TODO Pass drawing offsets into the handlers so a popup menu
	// can be created easily?
	on_submit: Option<Rc<dyn Fn(&mut Cursive, usize, usize)>>,
	on_select: Option<Rc<dyn Fn(&mut Cursive, usize, usize)>>,
}

impl<T: TableViewItem<H> + PartialEq, H: Eq + Hash + Copy + Clone + 'static> TableView<T, H> {
	/// Creates a new empty `TableView` without any columns.
	///
	/// A TableView should be accompanied by a enum of type `H` representing
	/// the table columns.
	pub fn new() -> Self {
		Self {
			enabled: true,
			scrollbase: ScrollBase::new(),
			last_size: Vec2::new(0, 0),

			column_select: false,
			columns: Vec::new(),
			column_indices: HashMap::new(),

			focus: 0,
			items: Vec::new(),
			rows_to_items: Vec::new(),

			on_sort: None,
			on_submit: None,
			on_select: None,
		}
	}

	/// Adds a column for the specified table column from type `H` along with
	/// a title for its visual display.
	///
	/// The provided callback can be used to further configure the
	/// created [`TableColumn`](struct.TableColumn.html).
	pub fn column<S: Into<String>, C: FnOnce(TableColumn<H>) -> TableColumn<H>>(
		mut self,
		column: H,
		title: S,
		callback: C,
	) -> Self {
		self.column_indices.insert(column, self.columns.len());
		self.columns
			.push(callback(TableColumn::new(column, title.into())));

		// Make the first column the default one
		if self.columns.len() == 1 {
			self.default_column(column)
		} else {
			self
		}
	}

	/// Sets the initially active column of the table.
	pub fn default_column(mut self, column: H) -> Self {
		if self.column_indices.contains_key(&column) {
			for c in &mut self.columns {
				c.selected = c.column == column;
				if c.selected {
					c.order = c.default_order;
				} else {
					c.order = Ordering::Equal;
				}
			}
		}
		self
	}

	/// Sorts the table using the specified table `column` and the passed
	/// `order`.
	pub fn sort_by(&mut self, column: H, order: Ordering) {
		if self.column_indices.contains_key(&column) {
			for c in &mut self.columns {
				if c.column == column {
					c.order = order;
				} else {
					c.order = Ordering::Equal;
				}
			}
		}

		self.sort_items(column, order);
	}

	/// Sorts the table using the currently active column and its
	/// ordering.
	pub fn sort(&mut self) {
		if let Some((column, order)) = self.order() {
			self.sort_items(column, order);
		}
	}

	/// Returns the currently active column that is used for sorting
	/// along with its ordering.
	///
	/// Might return `None` if there are currently no items in the table
	/// and it has not been sorted yet.
	pub fn order(&self) -> Option<(H, Ordering)> {
		for c in &self.columns {
			if c.order != Ordering::Equal {
				return Some((c.column, c.order));
			}
		}
		None
	}

	/// Disables this view.
	///
	/// A disabled view cannot be selected.
	pub fn disable(&mut self) {
		self.enabled = false;
	}

	/// Re-enables this view.
	pub fn enable(&mut self) {
		self.enabled = true;
	}

	/// Enable or disable this view.
	pub fn set_enabled(&mut self, enabled: bool) {
		self.enabled = enabled;
	}

	/// Returns `true` if this view is enabled.
	pub fn is_enabled(&self) -> bool {
		self.enabled
	}

	/// Sets a callback to be used when a selected column is sorted by
	/// pressing `<Enter>`.
	///
	/// # Example
	///
	/// ```norun
	/// rt(|siv: &mut Cursive, column: BasicColumn, order: Ordering| {});
	/// ```
	pub fn set_on_sort<F>(&mut self, cb: F)
	where
		F: Fn(&mut Cursive, H, Ordering) + 'static,
	{
		self.on_sort = Some(Rc::new(move |s, h, o| cb(s, h, o)));
	}

	/// Sets a callback to be used when a selected column is sorted by
	/// pressing `<Enter>`.
	///
	/// Chainable variant.
	///
	/// # Example
	///
	/// ```norun
	/// siv: &mut Cursive, column: BasicColumn, order: Ordering| {});
	/// ```
	pub fn on_sort<F>(self, cb: F) -> Self
	where
		F: Fn(&mut Cursive, H, Ordering) + 'static,
	{
		self.with(|t| t.set_on_sort(cb))
	}

	/// Sets a callback to be used when `<Enter>` is pressed while an item
	/// is selected.
	///
	/// Both the currently selected row and the index of the corresponding item
	/// within the underlying storage vector will be given to the callback.
	///
	/// # Example
	///
	/// ```norun
	/// bmit(|siv: &mut Cursive, row: usize, index: usize| {});
	/// ```
	pub fn set_on_submit<F>(&mut self, cb: F)
	where
		F: Fn(&mut Cursive, usize, usize) + 'static,
	{
		self.on_submit = Some(Rc::new(move |s, row, index| cb(s, row, index)));
	}

	/// Sets a callback to be used when `<Enter>` is pressed while an item
	/// is selected.
	///
	/// Both the currently selected row and the index of the corresponding item
	/// within the underlying storage vector will be given to the callback.
	///
	/// Chainable variant.
	///
	/// # Example
	///
	/// ```norun
	/// (|siv: &mut Cursive, row: usize, index: usize| {});
	/// ```
	pub fn on_submit<F>(self, cb: F) -> Self
	where
		F: Fn(&mut Cursive, usize, usize) + 'static,
	{
		self.with(|t| t.set_on_submit(cb))
	}

	/// Sets a callback to be used when an item is selected.
	///
	/// Both the currently selected row and the index of the corresponding item
	/// within the underlying storage vector will be given to the callback.
	///
	/// # Example
	///
	/// ```norun
	/// lect(|siv: &mut Cursive, row: usize, index: usize| {});
	/// ```
	pub fn set_on_select<F>(&mut self, cb: F)
	where
		F: Fn(&mut Cursive, usize, usize) + 'static,
	{
		self.on_select = Some(Rc::new(move |s, row, index| cb(s, row, index)));
	}

	/// Sets a callback to be used when an item is selected.
	///
	/// Both the currently selected row and the index of the corresponding item
	/// within the underlying storage vector will be given to the callback.
	///
	/// Chainable variant.
	///
	/// # Example
	///
	/// ```norun
	/// (|siv: &mut Cursive, row: usize, index: usize| {});
	/// ```
	pub fn on_select<F>(self, cb: F) -> Self
	where
		F: Fn(&mut Cursive, usize, usize) + 'static,
	{
		self.with(|t| t.set_on_select(cb))
	}

	/// Removes all items from this view.
	pub fn clear(&mut self) {
		self.items.clear();
		self.rows_to_items.clear();
		self.focus = 0;
	}

	/// Returns the number of items in this table.
	pub fn len(&self) -> usize {
		self.items.len()
	}

	/// Returns `true` if this table has no items.
	pub fn is_empty(&self) -> bool {
		self.items.is_empty()
	}

	/// Returns the index of the currently selected table row.
	pub fn row(&self) -> Option<usize> {
		if self.items.is_empty() {
			None
		} else {
			Some(self.focus)
		}
	}

	/// Selects the row at the specified index.
	pub fn set_selected_row(&mut self, row_index: usize) {
		self.focus = row_index;
		self.scrollbase.scroll_to(row_index);
	}

	/// Selects the row at the specified index.
	///
	/// Chainable variant.
	pub fn selected_row(self, row_index: usize) -> Self {
		self.with(|t| t.set_selected_row(row_index))
	}

	/// Sets the contained items of the table.
	///
	/// The currently active sort order is preserved and will be applied to all
	/// items. The selected item will also be preserved.
	pub fn set_items(&mut self, items: Vec<T>) {
		let mut new_location = 0;
		if let Some(old_item_location) = self.item() {
			let old_item = self.items.get(old_item_location).unwrap();
			for (i, new_item) in items.iter().enumerate() {
				if old_item == new_item {
					new_location = i;
					break;
				}
			}
		}

		self.items = items;
		self.rows_to_items = Vec::with_capacity(self.items.len());

		for i in 0..self.items.len() {
			self.rows_to_items.push(i);
		}

		if let Some((column, order)) = self.order() {
			self.sort_by(column, order);
		}

		self.scrollbase
			.set_heights(self.last_size.y.saturating_sub(2), self.rows_to_items.len());

		self.set_selected_item(new_location);
	}

	/// Sets the contained items of the table.
	///
	/// The order of the items will be preserved even when the table is sorted.
	///
	/// Chainable variant.
	pub fn items(self, items: Vec<T>) -> Self {
		self.with(|t| t.set_items(items))
	}

	/// Returns a immutable reference to the item at the specified index
	/// within the underlying storage vector.
	pub fn borrow_item(&mut self, index: usize) -> Option<&T> {
		self.items.get(index)
	}

	/// Returns a mutable reference to the item at the specified index within
	/// the underlying storage vector.
	pub fn borrow_item_mut(&mut self, index: usize) -> Option<&mut T> {
		self.items.get_mut(index)
	}

	/// Returns a immutable reference to the items contained within the table.
	pub fn borrow_items(&mut self) -> &Vec<T> {
		&self.items
	}

	/// Returns a mutable reference to the items contained within the table.
	///
	/// Can be used to modify the items in place.
	pub fn borrow_items_mut(&mut self) -> &mut Vec<T> {
		&mut self.items
	}

	/// Returns the index of the currently selected item within the underlying
	/// storage vector.
	pub fn item(&self) -> Option<usize> {
		if self.items.is_empty() || self.focus >= self.rows_to_items.len() {
			None
		} else {
			Some(self.rows_to_items[self.focus])
		}
	}

	/// Selects the item at the specified index within the underlying storage
	/// vector.
	pub fn set_selected_item(&mut self, item_index: usize) {
		// TODO optimize the performance for very large item lists
		if item_index < self.items.len() {
			for (row, item) in self.rows_to_items.iter().enumerate() {
				if *item == item_index {
					self.focus = row;
					self.scrollbase.scroll_to(row);
					break;
				}
			}
		}
	}

	/// Selects the item at the specified index within the underlying storage
	/// vector.
	///
	/// Chainable variant.
	pub fn selected_item(self, item_index: usize) -> Self {
		self.with(|t| t.set_selected_item(item_index))
	}

	/// Inserts a new item into the table.
	///
	/// The currently active sort order is preserved and will be applied to the
	/// newly inserted item.
	pub fn insert_item(&mut self, item: T) {
		self.items.push(item);
		self.rows_to_items.push(self.items.len());

		self.scrollbase
			.set_heights(self.last_size.y.saturating_sub(2), self.rows_to_items.len());

		if let Some((column, order)) = self.order() {
			self.sort_by(column, order);
		}
	}

	/// Removes the item at the specified index within the underlying storage
	/// vector and returns it.
	pub fn remove_item(&mut self, item_index: usize) -> Option<T> {
		if item_index < self.items.len() {
			// Move the selection if the currently selected item gets removed
			if let Some(selected_index) = self.item() {
				if selected_index == item_index {
					self.focus_up(1);
				}
			}

			// Remove the sorted reference to the item
			self.rows_to_items.retain(|i| *i != item_index);

			// Adjust remaining references
			for ref_index in &mut self.rows_to_items {
				if *ref_index > item_index {
					*ref_index -= 1;
				}
			}

			// Update scroll height to prevent out of index drawing
			self.scrollbase
				.set_heights(self.last_size.y.saturating_sub(2), self.rows_to_items.len());

			// Remove actual item from the underlying storage
			Some(self.items.remove(item_index))
		} else {
			None
		}
	}

	/// Removes all items from the underlying storage and returns them.
	pub fn take_items(&mut self) -> Vec<T> {
		self.scrollbase
			.set_heights(self.last_size.y.saturating_sub(2), 0);
		self.set_selected_row(0);
		self.rows_to_items.clear();
		self.items.drain(0..).collect()
	}
}

impl<T: TableViewItem<H> + PartialEq, H: Eq + Hash + Copy + Clone + 'static> TableView<T, H> {
	fn draw_columns<C: Fn(&Printer<'_, '_>, &TableColumn<H>)>(
		&self,
		printer: &Printer<'_, '_>,
		sep: &str,
		callback: C,
	) {
		let mut column_offset = 0;
		let column_count = self.columns.len();
		for (index, column) in self.columns.iter().enumerate() {
			let printer = &printer.offset((column_offset, 0)).focused(true);

			callback(printer, column);

			if index < column_count - 1 {
				printer.print((column.width + 1, 0), sep);
			}

			column_offset += column.width + 3;
		}
	}

	fn sort_items(&mut self, column: H, order: Ordering) {
		if !self.is_empty() {
			let old_item = self.item();

			let mut rows_to_items = self.rows_to_items.clone();
			rows_to_items.sort_by(|a, b| {
				if order == Ordering::Less {
					self.items[*a].cmp(&self.items[*b], column)
				} else {
					self.items[*b].cmp(&self.items[*a], column)
				}
			});
			self.rows_to_items = rows_to_items;
			if let Some(o) = old_item {
				self.set_selected_item(o)
			}
		}
	}

	fn draw_item(&self, printer: &Printer<'_, '_>, i: usize) {
		self.draw_columns(printer, "┆ ", |printer, column| {
			let value = self.items[self.rows_to_items[i]].to_column(column.column);
			column.draw_row(printer, value.as_str());
		});
	}

	fn focus_up(&mut self, n: usize) {
		self.focus -= cmp::min(self.focus, n);
	}

	fn focus_down(&mut self, n: usize) {
		self.focus = cmp::min(self.focus + n, self.items.len() - 1);
	}

	fn active_column(&self) -> usize {
		self.columns.iter().position(|c| c.selected).unwrap_or(0)
	}

	fn column_cancel(&mut self) {
		self.column_select = false;
		for column in &mut self.columns {
			column.selected = column.order != Ordering::Equal;
		}
	}

	fn column_next(&mut self) -> bool {
		let column = self.active_column();
		if column < self.columns.len() - 1 {
			self.columns[column].selected = false;
			self.columns[column + 1].selected = true;
			true
		} else {
			false
		}
	}

	fn column_prev(&mut self) -> bool {
		let column = self.active_column();
		if column > 0 {
			self.columns[column].selected = false;
			self.columns[column - 1].selected = true;
			true
		} else {
			false
		}
	}

	fn column_select(&mut self) {
		let next = self.active_column();
		let column = self.columns[next].column;
		let current = self
			.columns
			.iter()
			.position(|c| c.order != Ordering::Equal)
			.unwrap_or(0);

		let order = if current != next {
			self.columns[next].default_order
		} else if self.columns[current].order == Ordering::Less {
			Ordering::Greater
		} else {
			Ordering::Less
		};

		self.sort_by(column, order);
	}
}

impl<T: TableViewItem<H> + PartialEq + 'static, H: Eq + Hash + Copy + Clone + 'static> View
	for TableView<T, H>
{
	fn draw(&self, printer: &Printer<'_, '_>) {
		self.draw_columns(printer, "╷ ", |printer, column| {
			let color = if column.order != Ordering::Equal || column.selected {
				if self.column_select && column.selected && self.enabled && printer.focused {
					Highlight
				} else {
					HighlightInactive
				}
			} else {
				Primary
			};

			printer.with_color(ColorStyle::from(color), |printer| {
				column.draw_header(printer);
			});
		});

		self.draw_columns(
			&printer.offset((0, 1)).focused(true),
			"┴─",
			|printer, column| {
				printer.print_hline((0, 0), column.width + 1, "─");
			},
		);

		let printer = &printer.offset((0, 2)).focused(true);
		self.scrollbase.draw(printer, |printer, i| {
			let color = if i == self.focus {
				if !self.column_select && self.enabled && printer.focused {
					Highlight
				} else {
					HighlightInactive
				}
			} else {
				Primary
			};

			printer.with_color(ColorStyle::from(color), |printer| {
				self.draw_item(printer, i);
			});
		});
	}

	fn layout(&mut self, size: Vec2) {
		if size == self.last_size {
			return;
		}

		let item_count = self.items.len();
		let column_count = self.columns.len();

		// Split up all columns into sized / unsized groups
		let (mut sized, mut usized): (Vec<&mut TableColumn<H>>, Vec<&mut TableColumn<H>>) = self
			.columns
			.iter_mut()
			.partition(|c| c.requested_width.is_some());

		// Subtract one for the separators between our columns (that's column_count - 1)
		let mut available_width = size.x.saturating_sub(column_count.saturating_sub(1) * 3);

		// Reduce the with in case we are displaying a scrollbar
		if size.y.saturating_sub(1) < item_count {
			available_width = available_width.saturating_sub(2);
		}

		// Calculate widths for all requested columns
		let mut remaining_width = available_width;
		for column in &mut sized {
			column.width = match *column.requested_width.as_ref().unwrap() {
				TableColumnWidth::Percent(width) => cmp::min(
					(size.x as f32 / 100.0 * width as f32).ceil() as usize,
					remaining_width,
				),
				TableColumnWidth::Absolute(width) => width,
			};
			remaining_width = remaining_width.saturating_sub(column.width);
		}

		// Spread the remaining with across the unsized columns
		let remaining_columns = usized.len();
		for column in &mut usized {
			column.width = (remaining_width as f32 / remaining_columns as f32).floor() as usize;
		}

		self.scrollbase
			.set_heights(size.y.saturating_sub(2), item_count);
		self.last_size = size;
	}

	fn take_focus(&mut self, _: Direction) -> bool {
		self.enabled && !self.items.is_empty()
	}

	fn on_event(&mut self, event: Event) -> EventResult {
		if !self.enabled {
			return EventResult::Ignored;
		}

		let last_focus = self.focus;
		match event {
			Event::Key(Key::Right) => {
				if self.column_select {
					if !self.column_next() {
						return EventResult::Ignored;
					}
				} else {
					self.column_select = true;
				}
			}
			Event::Key(Key::Left) => {
				if self.column_select {
					if !self.column_prev() {
						return EventResult::Ignored;
					}
				} else {
					self.column_select = true;
				}
			}
			Event::Key(Key::Up) if self.focus > 0 || self.column_select => {
				if self.column_select {
					self.column_cancel();
				} else {
					self.focus_up(1);
				}
			}
			Event::Key(Key::Down) if self.focus + 1 < self.items.len() || self.column_select => {
				if self.column_select {
					self.column_cancel();
				} else {
					self.focus_down(1);
				}
			}
			Event::Key(Key::PageUp) => {
				self.column_cancel();
				self.focus_up(10);
			}
			Event::Key(Key::PageDown) => {
				self.column_cancel();
				self.focus_down(10);
			}
			Event::Key(Key::Home) => {
				self.column_cancel();
				self.focus = 0;
			}
			Event::Key(Key::End) => {
				self.column_cancel();
				self.focus = self.items.len() - 1;
			}
			Event::Key(Key::Enter) => {
				if self.column_select {
					self.column_select();

					if self.on_sort.is_some() {
						let c = &self.columns[self.active_column()];
						let column = c.column;
						let order = c.order;

						let cb = self.on_sort.clone().unwrap();
						return EventResult::Consumed(Some(Callback::from_fn(move |s| {
							cb(s, column, order)
						})));
					}
				} else if !self.is_empty() && self.on_submit.is_some() {
					let cb = self.on_submit.clone().unwrap();
					let row = self.row().unwrap();
					let index = self.item().unwrap();
					return EventResult::Consumed(Some(Callback::from_fn(move |s| {
						cb(s, row, index)
					})));
				}
			}
			_ => return EventResult::Ignored,
		}

		let focus = self.focus;
		self.scrollbase.scroll_to(focus);

		if self.column_select {
			EventResult::Consumed(None)
		} else if !self.is_empty() && last_focus != focus {
			let row = self.row().unwrap();
			let index = self.item().unwrap();
			EventResult::Consumed(
				self.on_select
					.clone()
					.map(|cb| Callback::from_fn(move |s| cb(s, row, index))),
			)
		} else {
			EventResult::Ignored
		}
	}
}

/// A type used for the construction of columns in a
/// [`TableView`](struct.TableView.html).
pub struct TableColumn<H: Copy + Clone + 'static> {
	column: H,
	title: String,
	selected: bool,
	alignment: HAlign,
	order: Ordering,
	width: usize,
	default_order: Ordering,
	requested_width: Option<TableColumnWidth>,
}

enum TableColumnWidth {
	Percent(usize),
	Absolute(usize),
}

impl<H: Copy + Clone + 'static> TableColumn<H> {
	/// Sets the default ordering of the column.
	pub fn ordering(mut self, order: Ordering) -> Self {
		self.default_order = order;
		self
	}

	/// Sets the horizontal text alignment of the column.
	pub fn align(mut self, alignment: HAlign) -> Self {
		self.alignment = alignment;
		self
	}

	/// Sets how many characters of width this column will try to occupy.
	pub fn width(mut self, width: usize) -> Self {
		self.requested_width = Some(TableColumnWidth::Absolute(width));
		self
	}

	/// Sets what percentage of the width of the entire table this column will
	/// try to occupy.
	pub fn width_percent(mut self, width: usize) -> Self {
		self.requested_width = Some(TableColumnWidth::Percent(width));
		self
	}

	fn new(column: H, title: String) -> Self {
		Self {
			column: column,
			title: title,
			selected: false,
			alignment: HAlign::Left,
			order: Ordering::Equal,
			width: 0,
			default_order: Ordering::Less,
			requested_width: None,
		}
	}

	fn draw_header(&self, printer: &Printer<'_, '_>) {
		let order = match self.order {
			Ordering::Less => "^",
			Ordering::Greater => "v",
			Ordering::Equal => " ",
		};

		let header = match self.alignment {
			HAlign::Left => format!(
				"{:<width$} [{}]",
				self.title,
				order,
				width = self.width.saturating_sub(4)
			),
			HAlign::Right => format!(
				"{:>width$} [{}]",
				self.title,
				order,
				width = self.width.saturating_sub(4)
			),
			HAlign::Center => format!(
				"{:^width$} [{}]",
				self.title,
				order,
				width = self.width.saturating_sub(4)
			),
		};
		printer.print((0, 0), header.as_str());
	}

	fn draw_row(&self, printer: &Printer<'_, '_>, value: &str) {
		let value = match self.alignment {
			HAlign::Left => format!("{:<width$} ", value, width = self.width),
			HAlign::Right => format!("{:>width$} ", value, width = self.width),
			HAlign::Center => format!("{:^width$} ", value, width = self.width),
		};
		printer.print((0, 0), value.as_str());
	}
}

#[cfg(test)]
mod test {
	use crate::tui::peers::PeerColumn;
	use crate::tui::table::TableView;
	use chrono::Utc;
	use grin_core::ser::ProtocolVersion;
	use grin_servers::PeerStats;
	use std::cmp::Ordering;

	#[test]
	pub fn test_set_items_preserves_selected_item() {
		let mut table = TableView::<PeerStats, PeerColumn>::new();
		let ps1 = PeerStats {
			addr: "123.0.0.1".to_string(),
			..TestPeerStats::default()
		};
		let ps2 = PeerStats {
			addr: "123.0.0.2".to_string(),
			..TestPeerStats::default()
		};

		let mut items = vec![ps1, ps2];
		table.set_items(items.clone());
		assert_eq!(table.item().unwrap(), 0);

		items.reverse();
		table.set_items(items);
		assert_eq!(table.item().unwrap(), 1);
	}

	#[test]
	pub fn test_set_items_preserves_order() {
		let mut table = TableView::<PeerStats, PeerColumn>::new();
		let ps1 = PeerStats {
			addr: "123.0.0.1".to_string(),
			received_bytes_per_sec: 10,
			..TestPeerStats::default()
		};
		let ps2 = PeerStats {
			addr: "123.0.0.2".to_string(),
			received_bytes_per_sec: 80,
			..TestPeerStats::default()
		};

		let items = vec![ps1, ps2];
		table.set_items(items);
		assert_eq!(table.rows_to_items[0], 0);
		table.sort_by(PeerColumn::UsedBandwidth, Ordering::Greater);

		assert_eq!(table.rows_to_items[0], 1);
	}

	struct TestPeerStats(PeerStats);

	impl TestPeerStats {
		fn default() -> PeerStats {
			PeerStats {
				state: "Connected".to_string(),
				addr: "127.0.0.1".to_string(),
				version: ProtocolVersion::local(),
				user_agent: "".to_string(),
				total_difficulty: 0,
				height: 0,
				direction: "Outbound".to_string(),
				last_seen: Utc::now(),
				sent_bytes_per_sec: 0,
				received_bytes_per_sec: 0,
			}
		}
	}
}
