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

This file is part of On the Grid.

On the Grid 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.

On the Grid 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 On the Grid. If not, see <https://www.gnu.org/licenses/>.
*/

use crate::{CoreApi, ResourceManager};
use adw::prelude::*;

use glib::Propagation;
use gtk::{gdk::Key, EventControllerKey};
use otg_core::{
    settings::{SettingsRequest, SettingsResponse},
    CoreRequest, CoreResponse, GameReviewViewModel,
};
use sgf::GameRecord;
use std::sync::{Arc, RwLock};

use crate::views::{GameReview, HomeView, SettingsView};

/*
#[derive(Clone)]
enum AppView {
    Home,
}
*/

// An application window should generally contain
// - an overlay widget
// - the main content in a stack on the bottom panel of the overlay
// - the settings and the about page in bins atop the overlay
#[derive(Clone)]
pub struct AppWindow {
    pub window: adw::ApplicationWindow,

    // content is a stack which contains the view models for the application. These are the main
    // elements that users want to interact with: the home page, the game library, a review, a game
    // itself, perhaps also chat rooms and player lists on other networks. stack contains the
    // widgets that need to be rendered. The two of these work together in order to ensure that
    // we can maintain the state of previous views. Since the two of these work together, they are
    // a candidate for extraction into a new widget or a new struct.
    stack: adw::NavigationView,
    // view_states: Vec<AppView>,

    // Overlays are for transient content, such as about and settings, which can be accessed from
    // anywhere but shouldn't be part of the main application flow.
    overlay: gtk::Overlay,
    core: CoreApi,

    // Not liking this, but I have to keep track of the settings view model separately from
    // anything else. I'll have to look into this later.
    settings_view_model: Arc<RwLock<Option<SettingsView>>>,

    resources: ResourceManager,
}

impl AppWindow {
    pub fn new(app: &adw::Application, core: CoreApi, resources: ResourceManager) -> Self {
        let window = Self::setup_window(app);
        let overlay = Self::setup_overlay();
        let stack = adw::NavigationView::new();
        // let view_states = vec![];

        window.set_content(Some(&overlay));
        overlay.set_child(Some(&stack));

        let s = Self {
            window,
            stack,
            // view_states,
            overlay,
            core,
            settings_view_model: Default::default(),
            resources,
        };

        let home = s.setup_home();

        s.stack.push(&home);

        s
    }

    pub fn open_game_review(&self, game_record: GameRecord) {
        let header = adw::HeaderBar::new();
        let game_review = GameReview::new(
            GameReviewViewModel::new(game_record),
            self.resources.clone(),
        );

        let layout = gtk::Box::builder()
            .orientation(gtk::Orientation::Vertical)
            .build();
        layout.append(&header);
        layout.append(&game_review.widget());

        // This controller ensures that navigational keypresses get sent to the game review so that
        // they're not changing the cursor focus in the app.
        let keypress_controller = EventControllerKey::new();
        keypress_controller.connect_key_pressed({
            move |s, key, _, _| {
                println!("layout keypress: {}", key);
                if s.forward(&game_review.widget()) {
                    Propagation::Stop
                } else {
                    Propagation::Proceed
                }
            }
        });

        layout.add_controller(keypress_controller);

        let page = adw::NavigationPage::builder()
            .can_pop(true)
            .title("Game Review")
            .child(&layout)
            .build();
        self.stack.push(&page);
    }

    pub fn open_settings(&self) {
        // This should return instantly and allow the UI to continue being functional. However,
        // some tests indicate that this may not actually be working correctly, and that a
        // long-running background thread may delay things.
        glib::spawn_future_local({
            let s = self.clone();
            async move {
                if let CoreResponse::Settings(SettingsResponse(settings)) = s
                    .core
                    .dispatch(CoreRequest::Settings(SettingsRequest::Get))
                    .await
                {
                    let view_model = SettingsView::new(
                        &s.window,
                        settings,
                        {
                            let s = s.clone();
                            move |config| {
                                glib::spawn_future_local({
                                    let s = s.clone();
                                    async move {
                                        s.core
                                            .dispatch(CoreRequest::Settings(SettingsRequest::Set(
                                                config,
                                            )))
                                            .await
                                    }
                                });
                                s.close_overlay();
                            }
                        },
                        {
                            let s = s.clone();
                            move || {
                                s.close_overlay();
                            }
                        },
                    );
                    s.overlay.add_overlay(&view_model);
                    *s.settings_view_model.write().unwrap() = Some(view_model);
                }
            }
        });
    }

    pub fn close_overlay(&self) {
        let mut view = self.settings_view_model.write().unwrap();
        if let Some(ref mut settings) = *view {
            self.overlay.remove_overlay(settings);
            *view = None;
        }
    }

    fn setup_window(app: &adw::Application) -> adw::ApplicationWindow {
        adw::ApplicationWindow::builder()
            .application(app)
            .width_request(800)
            .height_request(500)
            .build()
    }

    fn setup_header() -> adw::HeaderBar {
        let header = adw::HeaderBar::builder()
            .title_widget(&gtk::Label::new(Some("On the Grid")))
            .build();

        let app_menu = gio::Menu::new();
        let menu_item = gio::MenuItem::new(Some("Configuration"), Some("app.show_settings"));
        app_menu.append_item(&menu_item);

        let hamburger = gtk::MenuButton::builder()
            .icon_name("open-menu-symbolic")
            .build();
        hamburger.set_menu_model(Some(&app_menu));

        header.pack_end(&hamburger);
        header
    }

    fn setup_overlay() -> gtk::Overlay {
        gtk::Overlay::new()
    }

    fn setup_home(&self) -> adw::NavigationPage {
        let header = Self::setup_header();
        let home = HomeView::new(self.core.clone(), {
            let s = self.clone();
            move |game| s.open_game_review(game)
        });

        let layout = gtk::Box::builder()
            .orientation(gtk::Orientation::Vertical)
            .build();
        layout.append(&header);
        layout.append(&home);

        adw::NavigationPage::builder()
            .can_pop(false)
            .title("Home")
            .child(&layout)
            .build()
    }
}