/*
Copyright 2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>

This file is part of FitnessTrax.

FitnessTrax is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

FitnessTrax is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License along with FitnessTrax. If not, see <https://www.gnu.org/licenses/>.
*/

use gtk::prelude::*;
use std::{cell::RefCell, rc::Rc};

#[derive(Clone, Debug)]
pub struct ParseError;

type Renderer<T> = dyn Fn(&T) -> String;
type Parser<T> = dyn Fn(&str) -> Result<T, ParseError>;

#[derive(Clone)]
pub struct TextEntry<T: Clone + std::fmt::Debug> {
    value: Rc<RefCell<Option<T>>>,
    widget: gtk::Entry,
    #[allow(unused)]
    renderer: Rc<Renderer<T>>,
    parser: Rc<Parser<T>>,
}

impl<T: Clone + std::fmt::Debug> std::fmt::Debug for TextEntry<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        write!(
            f,
            "{{ value: {:?}, widget: {:?} }}",
            self.value, self.widget
        )
    }
}

// I do not understand why the data should be 'static.
impl<T: Clone + std::fmt::Debug + 'static> TextEntry<T> {
    pub fn new<R, V>(placeholder: &str, value: Option<T>, renderer: R, parser: V) -> Self
    where
        R: Fn(&T) -> String + 'static,
        V: Fn(&str) -> Result<T, ParseError> + 'static,
    {
        let widget = gtk::Entry::builder().placeholder_text(placeholder).build();
        if let Some(ref v) = value {
            widget.set_text(&renderer(v))
        }

        let s = Self {
            value: Rc::new(RefCell::new(value)),
            widget,
            renderer: Rc::new(renderer),
            parser: Rc::new(parser),
        };

        s.widget.buffer().connect_text_notify({
            let s = s.clone();
            move |buffer| s.handle_text_change(buffer)
        });

        s
    }

    fn handle_text_change(&self, buffer: &gtk::EntryBuffer) {
        if buffer.text().is_empty() {
            *self.value.borrow_mut() = None;
            self.widget.remove_css_class("error");
            return;
        }
        match (self.parser)(buffer.text().as_str()) {
            Ok(v) => {
                *self.value.borrow_mut() = Some(v);
                self.widget.remove_css_class("error");
            }
            // need to change the border to provide a visual indicator of an error
            Err(_) => {
                self.widget.add_css_class("error");
            }
        }
    }

    #[allow(unused)]
    pub fn value(&self) -> Option<T> {
        let v = self.value.borrow().clone();
        self.value.borrow().clone()
    }

    pub fn set_value(&self, value: Option<T>) {
        if let Some(ref v) = value {
            self.widget.set_text(&(self.renderer)(v))
        }
        *self.value.borrow_mut() = value;
    }

    #[allow(unused)]
    pub fn grab_focus(&self) {
        self.widget.grab_focus();
    }

    pub fn widget(&self) -> gtk::Widget {
        self.widget.clone().upcast::<gtk::Widget>()
    }
}