/*
Copyright 2023-2024, 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,
    types::DayInterval,
    view_models::DayDetailViewModel,
    views::{DayDetailView, HistoricalView, PlaceholderView, View, WelcomeView},
};
use adw::prelude::*;
use chrono::{Duration, Local};

use gio::resources_lookup_data;
use gtk::STYLE_PROVIDER_PRIORITY_USER;
use std::{cell::RefCell, path::PathBuf, rc::Rc};

/// The application window, or the main window, is the main user interface for the app. Almost
/// everything occurs here.
#[derive(Clone)]
pub struct AppWindow {
    app: App,
    layout: gtk::Box,
    current_view: Rc<RefCell<View>>,
    settings: gio::Settings,
    navigation: adw::NavigationView,
}

impl AppWindow {
    /// Construct a new App Window.
    ///
    /// adw_app is an Adwaita application. Application windows need to have access to this, but
    /// otherwise we don't use this.
    ///
    /// app is a core [crate::app::App] object which encapsulates all of the basic logic.
    pub fn new(
        app_id: &str,
        resource_path: &str,
        adw_app: &adw::Application,
        ft_app: App,
    ) -> AppWindow {
        let window = adw::ApplicationWindow::builder()
            .application(adw_app)
            .width_request(800)
            .height_request(746)
            .build();
        window.connect_destroy(|s| {
            let _ = gtk::prelude::WidgetExt::activate_action(s, "app.quit", None);
        });

        let stylesheet = String::from_utf8(
            resources_lookup_data(
                &format!("{}style.css", resource_path),
                gio::ResourceLookupFlags::NONE,
            )
            .expect("stylesheet must be available in the resources")
            .to_vec(),
        )
        .expect("to parse stylesheet");

        let provider = gtk::CssProvider::new();
        provider.load_from_data(&stylesheet);

        #[allow(deprecated)]
        let context = window.style_context();
        #[allow(deprecated)]
        context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER);

        let navigation = adw::NavigationView::new();

        let layout = gtk::Box::builder()
            .orientation(gtk::Orientation::Vertical)
            .build();

        let initial_view = View::Placeholder(PlaceholderView::default().upcast());

        let header_bar = adw::HeaderBar::new();

        let main_menu = gio::Menu::new();
        main_menu.append(Some("About"), Some("app.about"));
        main_menu.append(Some("Quit"), Some("app.quit"));
        let main_menu_button = gtk::MenuButton::builder()
            .icon_name("open-menu")
            .direction(gtk::ArrowType::Down)
            .halign(gtk::Align::End)
            .menu_model(&main_menu)
            .build();
        header_bar.pack_end(&main_menu_button);

        layout.append(&initial_view.widget());

        let nav_layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
        nav_layout.append(&header_bar);
        nav_layout.append(&layout);
        navigation.push(
            &adw::NavigationPage::builder()
                .can_pop(false)
                .title("FitnessTrax")
                .child(&nav_layout)
                .build(),
        );

        window.set_content(Some(&navigation));
        window.present();

        let s = Self {
            app: ft_app,
            layout,
            current_view: Rc::new(RefCell::new(initial_view)),
            settings: gio::Settings::new(app_id),
            navigation,
        };

        s.load_records();

        s.navigation.connect_popped({
            let s = s.clone();
            move |_, _| {
                if let View::Historical(_) = *s.current_view.borrow() {
                    s.load_records();
                }
            }
        });

        s
    }

    fn show_welcome_view(&self) {
        let view = View::Welcome(WelcomeView::new({
            let s = self.clone();
            move |path| s.on_apply_config(path)
        }));
        self.swap_main(view);
    }

    fn show_historical_view(&self, interval: DayInterval) {
        let on_select_day = {
            let s = self.clone();
            move |date| {
                let s = s.clone();
                glib::spawn_future_local(async move {
                    let view_model = DayDetailViewModel::new(date, s.app.clone()).await.unwrap();
                    let layout = gtk::Box::new(gtk::Orientation::Vertical, 0);
                    layout.append(&adw::HeaderBar::new());
                    // layout.append(&DayDetailView::new(date, records, s.app.clone()));
                    layout.append(&DayDetailView::new(view_model));
                    let page = &adw::NavigationPage::builder()
                        .title(date.format("%Y-%m-%d").to_string())
                        .child(&layout)
                        .build();
                    s.navigation.push(page);
                });
            }
        };

        let view = View::Historical(HistoricalView::new(
            self.app.clone(),
            interval,
            Rc::new(on_select_day),
        ));
        self.swap_main(view);
    }

    fn load_records(&self) {
        glib::spawn_future_local({
            let s = self.clone();
            async move {
                if s.app.database_is_open() {
                    let end = Local::now().date_naive();
                    let start = end - Duration::days(7);
                    s.show_historical_view(DayInterval { start, end });    
                } else {
                    s.show_welcome_view();
                }
            }
        });
    }

    // Switch views.
    //
    // This function only replaces the old view with the one which matches the current view state.
    // It is responsible for ensuring that the new view goes into the layout in the correct
    // position.
    fn swap_main(&self, view: View) {
        let mut current_widget = self.current_view.borrow_mut();
        self.layout.remove(&current_widget.widget());
        *current_widget = view;
        self.layout.append(&current_widget.widget());
    }

    #[allow(unused)]
    fn on_apply_config(&self, path: PathBuf) {
        glib::spawn_future_local({
            let s = self.clone();
            async move {
                if s.app.open_db(path.clone()).await.is_ok() {
                    let _ = s.settings.set("series-path", path.to_str().unwrap());
                    s.load_records();
                }
            }
        });
    }
}
