/*
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 crate::{
    app::App,
    components::{DateRangePicker, DaySummary},
    types::DayInterval,
    view_models::DayDetailViewModel,
};
use glib::Object;
use gtk::{prelude::*, subclass::prelude::*};
use std::{cell::RefCell, rc::Rc};

/// The historical view will show a window into the main database. It will show some version of
/// daily summaries, daily details, and will provide all functions the user may need for editing
/// records.
pub struct HistoricalViewPrivate {
    app: Rc<RefCell<Option<App>>>,
    list_view: gtk::ListView,
    date_range_picker: DateRangePicker,
}

#[glib::object_subclass]
impl ObjectSubclass for HistoricalViewPrivate {
    const NAME: &'static str = "HistoricalView";
    type Type = HistoricalView;
    type ParentType = gtk::Box;

    fn new() -> Self {
        let factory = gtk::SignalListItemFactory::new();
        factory.connect_setup(move |_, list_item| {
            list_item
                .downcast_ref::<gtk::ListItem>()
                .expect("should be a ListItem")
                .set_child(Some(&DaySummary::new()));
        });

        let date_range_picker = DateRangePicker::default();

        let s = Self {
            app: Rc::new(RefCell::new(None)),
            list_view: gtk::ListView::builder()
                .factory(&factory)
                .single_click_activate(true)
                .show_separators(true)
                .build(),
            date_range_picker,
        };

        factory.connect_bind({
            let app = s.app.clone();
            move |_, list_item| {
                let date = list_item
                    .downcast_ref::<gtk::ListItem>()
                    .expect("should be a ListItem")
                    .item()
                    .and_downcast::<Date>()
                    .expect("should be a Date");

                let summary = list_item
                    .downcast_ref::<gtk::ListItem>()
                    .expect("should be a ListItem")
                    .child()
                    .and_downcast::<DaySummary>()
                    .expect("should be a DaySummary");

                if let Some(app) = app.borrow().clone() {
                    glib::spawn_future_local(async move {
                        let view_model = DayDetailViewModel::new(date.date(), app.clone())
                            .await
                            .unwrap();
                        summary.set_data(view_model);
                    });
                }
            }
        });

        s
    }
}

impl ObjectImpl for HistoricalViewPrivate {}
impl WidgetImpl for HistoricalViewPrivate {}
impl BoxImpl for HistoricalViewPrivate {}

glib::wrapper! {
    pub struct HistoricalView(ObjectSubclass<HistoricalViewPrivate>) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable;
}

impl HistoricalView {
    pub fn new<SelectFn>(app: App, interval: DayInterval, on_select_day: Rc<SelectFn>) -> Self
    where
        SelectFn: Fn(chrono::NaiveDate) + 'static,
    {
        let s: Self = Object::builder().build();
        s.set_orientation(gtk::Orientation::Vertical);
        s.set_css_classes(&["historical"]);

        *s.imp().app.borrow_mut() = Some(app);

        s.imp().date_range_picker.connect_on_search({
            let s = s.clone();
            move |interval| s.set_interval(interval)
        });
        s.set_interval(interval);

        s.imp().list_view.connect_activate({
            let on_select_day = on_select_day.clone();
            move |s, idx| {
                // This gets triggered whenever the user clicks on an item on the list.
                let item = s.model().unwrap().item(idx).unwrap();
                let date = item.downcast_ref::<Date>().unwrap();
                on_select_day(date.date());
            }
        });

        let scroller = gtk::ScrolledWindow::builder()
            .child(&s.imp().list_view)
            .hexpand(true)
            .vexpand(true)
            .hscrollbar_policy(gtk::PolicyType::Never)
            .build();

        s.append(&s.imp().date_range_picker);
        s.append(&scroller);

        s
    }

    pub fn set_interval(&self, interval: DayInterval) {
        let mut model = gio::ListStore::new::<Date>();
        let mut days = interval.days().map(Date::new).collect::<Vec<Date>>();
        days.reverse();
        model.extend(days.into_iter());
        self.imp()
            .list_view
            .set_model(Some(&gtk::NoSelection::new(Some(model))));
        self.imp().date_range_picker.set_interval(interval.start, interval.end);
    }
}

#[derive(Default)]
pub struct DatePrivate {
    date: RefCell<chrono::NaiveDate>,
}

#[glib::object_subclass]
impl ObjectSubclass for DatePrivate {
    const NAME: &'static str = "Date";
    type Type = Date;
}

impl ObjectImpl for DatePrivate {}

glib::wrapper! {
    pub struct Date(ObjectSubclass<DatePrivate>);
}

impl Date {
    pub fn new(date: chrono::NaiveDate) -> Self {
        let s: Self = Object::builder().build();
        *s.imp().date.borrow_mut() = date;
        s
    }

    pub fn date(&self) -> chrono::NaiveDate {
        *self.imp().date.borrow()
    }
}