//! This is a fork of <https://raw.githubusercontent.com/sagebind/smplinfo/master/src/ui/widgets/table.rs> with
//! some modifications.

use std::ops::Range;
use std::{fmt::Display, hash::Hash};

use eframe::egui::{vec2, Id, Response, ScrollArea, Sense, TextStyle, Ui, Vec2, Widget};

const DEFAULT_COLUMN_WIDTH: f32 = 200.0;

/// An ordinary table. Feature set is currently pretty limited.
///
/// - `R`: The data type of a single row displayed.
/// - `C`: The type of collection holding the rows to display. Any collection
///   implementing `AsRef<[R]>` can be used.
pub struct Table<
    'selection,
    R,
    C: AsRef<[Option<R>]>,
    RowMaker: FnMut(Range<usize>) -> C,
    RowSelection: Fn(&Option<R>),
> {
    id_source: Id,
    columns: Vec<Column<R>>,
    selected_row: Option<&'selection mut Option<usize>>,
    header_height: f32,
    row_height: f32,
    cell_padding: Vec2,
    row_maker: RowMaker,
    num_rows: usize,
    pub row_action: Option<RowSelection>,
}

/// Table column definition.
struct Column<R> {
    name: String,
    value_mapper: Box<dyn FnMut(&Option<R>) -> String>,
    max_width: Option<f32>,
}

impl<
        R,
        C: AsRef<[Option<R>]>,
        RowMaker: FnMut(Range<usize>) -> C,
        RowSelection: Fn(&Option<R>),
    > Table<'static, R, C, RowMaker, RowSelection>
{
    #[allow(dead_code)]
    pub fn new(id_source: impl Hash, num_rows: usize, row_maker: RowMaker) -> Self {
        Self {
            id_source: Id::new(id_source),
            columns: Vec::new(),
            selected_row: None,
            header_height: 28.0,
            row_height: 24.0,
            cell_padding: vec2(8.0, 4.0),
            row_maker,
            num_rows,
            row_action: None,
        }
    }
}

impl<
        's,
        R,
        C: AsRef<[Option<R>]>,
        RowMaker: FnMut(Range<usize>) -> C,
        RowSelection: Fn(&Option<R>),
    > Table<'s, R, C, RowMaker, RowSelection>
{
    pub fn new_selectable(
        id_source: impl Hash,
        selected_row: &'s mut Option<usize>,
        num_rows: usize,
        selection: RowSelection,
        row_maker: RowMaker,
    ) -> Self {
        Self {
            id_source: Id::new(id_source),
            columns: Vec::new(),
            selected_row: Some(selected_row),
            header_height: 28.0,
            row_height: 24.0,
            cell_padding: vec2(8.0, 4.0),
            row_maker,
            num_rows,
            row_action: Some(selection),
        }
    }

    pub fn column(
        mut self,
        name: impl Display,
        width: f32,
        value_mapper: impl FnMut(&Option<R>) -> String + 'static,
    ) -> Self {
        self.columns.push(Column {
            name: name.to_string(),
            value_mapper: Box::new(value_mapper),
            max_width: Some(width),
        });
        self
    }

    #[allow(dead_code)]
    fn supports_selection(&self) -> bool {
        self.selected_row.is_some()
    }

    fn header_ui(&mut self, ui: &mut Ui, state: &mut State) {
        let header_text_style = TextStyle::Body;

        // Table always grows as wide as available, so the header should too.
        let (_, rect) = ui.allocate_space(vec2(ui.available_width(), self.header_height));

        // Header background
        let painter = ui.painter_at(rect);
        painter.rect_filled(
            rect,
            ui.visuals().widgets.inactive.corner_radius,
            ui.visuals().widgets.inactive.bg_fill,
        );

        let mut column_offset = 0.0;
        let column_len = self.columns.len();

        for (i, column) in self.columns.iter().enumerate() {
            let column_id = self.id_source.with("_column_").with(i);

            let desired_column_width = state.column_width(i);

            let mut column_rect = rect;
            column_rect.min.x += column_offset;

            let is_last = i == (column_len - 1);
            if !is_last && column_rect.width() > desired_column_width {
                column_rect.set_width(desired_column_width);
            }

            let response = ui.interact(column_rect, column_id, Sense::hover());

            let color = if response.hovered() {
                ui.style().visuals.widgets.hovered.fg_stroke.color
            } else {
                ui.style().visuals.widgets.inactive.fg_stroke.color
            };

            let galley = ui
                .fonts()
                .layout_no_wrap(column.name.clone(), header_text_style, color);

            if response.hovered() {
                ui.painter().rect_stroke(
                    column_rect,
                    ui.visuals().widgets.hovered.corner_radius,
                    ui.visuals().widgets.hovered.bg_stroke,
                );
            }

            let mut text_pos = column_rect.left_center();
            text_pos.x += self.cell_padding.x;
            text_pos.y -= galley.size().y / 2.0;
            ui.painter_at(column_rect).galley(text_pos, galley);

            column_offset += column_rect.width();
        }
    }
}

impl<
        's,
        R: std::fmt::Debug,
        C: AsRef<[Option<R>]>,
        RowMaker: FnMut(Range<usize>) -> C,
        RowAction: Fn(&Option<R>),
    > Widget for Table<'s, R, C, RowMaker, RowAction>
{
    fn ui(mut self, ui: &mut Ui) -> Response {
        if self.columns.is_empty() {
            panic!("uh, what do I do if no columns are defined?");
        }

        let mut state = ui
            .memory()
            .id_data_temp
            .get_or_default::<State>(self.id_source)
            .clone();

        // set the sizes on the state
        state.column_widths = self
            .columns
            .iter()
            .map(|e| e.max_width.unwrap_or(DEFAULT_COLUMN_WIDTH))
            .collect();

        // First step: compute some sizes used during rendering. Since this is a
        // homogenous table, we can figure out its exact sizes based on the
        // number of rows and columns.
        let table_rect = ui.available_rect_before_wrap();
        let response = ui.interact(table_rect, self.id_source, Sense::hover());

        self.header_ui(ui, &mut state);

        // Now render the table body, which is inside an independently
        // scrollable area.
        ScrollArea::vertical().show_rows(ui, self.row_height, self.num_rows, |ui, row_range| {
            ui.scope(|ui| {
                let maker = &mut self.row_maker;
                let rows = maker(row_range);

                // When laying out the table, don't allocate any spacing between the
                // rows.
                ui.spacing_mut().item_spacing.y = 0.0;

                // TODO: Decide row height more intelligently...
                let row_size = vec2(ui.available_width(), self.row_height);
                let cell_text_style = ui.style().body_text_style;
                let column_len = self.columns.len();

                for (row_idx, row) in rows.as_ref().iter().enumerate() {
                    let (row_id, row_rect) = ui.allocate_space(row_size);
                    let mut row_response = ui.interact(row_rect, row_id, Sense::click());
                    let mut cell_text_color = ui.style().visuals.text_color();

                    // If this row is currently selected, make it look like it is.
                    if self.selected_row == Some(&mut Some(row_idx)) {
                        cell_text_color = ui.visuals().strong_text_color();
                        ui.painter().rect_filled(
                            row_rect,
                            0.0,
                            ui.style().visuals.selection.bg_fill,
                        );
                    } else if row_response.hovered() {
                        ui.painter().rect_filled(
                            row_rect,
                            0.0,
                            ui.visuals().widgets.hovered.bg_fill,
                        );
                    } else if row_idx % 2 > 0 {
                        ui.painter()
                            .rect_filled(row_rect, 0.0, ui.visuals().faint_bg_color);
                    }

                    // Give the hovered mails a tooltip
                    if row_response.hovered() {
                        //let data = self.columns[row_id];
                        let mut hover_data = Vec::new();
                        for (_, column) in self.columns.iter_mut().enumerate() {
                            let cell_text = (column.value_mapper)(row);
                            hover_data.push(cell_text);
                        }
                        let hover_string = hover_data.join("\n");
                        row_response = row_response.on_hover_text(hover_string);
                    }

                    let mut column_offset = 0.0;

                    for (col_idx, column) in self.columns.iter_mut().enumerate() {
                        let desired_column_width = state.column_width(col_idx);
                        let cell_text = (column.value_mapper)(row);

                        let mut column_rect = row_rect;
                        column_rect.min.x += column_offset;

                        // Auto-expand the last column
                        let is_last = col_idx == (column_len - 1);
                        if !is_last && column_rect.width() > desired_column_width {
                            column_rect.set_width(desired_column_width);
                        }

                        let painter = ui.painter_at(column_rect);

                        let galley =
                            ui.fonts()
                                .layout_no_wrap(cell_text, cell_text_style, cell_text_color);

                        let mut text_pos = column_rect.left_center();
                        text_pos.x += self.cell_padding.x;
                        text_pos.y -= galley.size().y / 2.0;
                        painter.galley(text_pos, galley);

                        column_offset += column_rect.width();
                    }

                    if let Some(selected_row) = self.selected_row.as_mut() {
                        if row_response.clicked() {
                            if let Some(ref n) = self.row_action {
                                if let Some(a) = rows.as_ref().get(row_idx) {
                                    n(a)
                                }
                            }
                            **selected_row = Some(row_idx);
                        }
                    }
                }
            });
        });

        response
    }
}

/// Persistent table UI state.
#[derive(Clone, Default)]
struct State {
    /// Current width of each column. This is updated when a column is resized.
    column_widths: Vec<f32>,
}

impl State {
    fn column_width(&self, column: usize) -> f32 {
        self.column_widths
            .get(column)
            .cloned()
            .unwrap_or(DEFAULT_COLUMN_WIDTH)
    }
}
