// Fragments - window.rs
// Copyright (C) 2022-2024  Felix Häcker <haeckerfelix@gnome.org>
//
// This program 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.
//
// This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.

use std::str::FromStr;

use adw::subclass::prelude::AdwApplicationWindowImpl;
use glib::{clone, subclass};
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use gtk::{gdk, gio, glib, CompositeTemplate};
use magnet_uri::MagnetURI;
use transmission_gobject::TrTorrent;
use url::Url;

use crate::app::FrgApplication;
use crate::backend::{FrgConnectionManager, FrgPollingMode};
use crate::i18n::{i18n, i18n_f};
use crate::settings::{settings_manager, Key};
use crate::ui::{FrgConnectionBox, FrgConnectionPopover};
use crate::{config, utils};

mod imp {
    use super::*;

    #[derive(Default, Debug, CompositeTemplate)]
    #[template(resource = "/de/haeckerfelix/Fragments/gtk/window.ui")]
    pub struct FrgApplicationWindow {
        #[template_child]
        headerbar: TemplateChild<adw::HeaderBar>,
        #[template_child]
        window_title: TemplateChild<adw::WindowTitle>,
        #[template_child]
        app_menu_button: TemplateChild<gtk::MenuButton>,
        #[template_child]
        connection_menu: TemplateChild<gtk::MenuButton>,
        #[template_child]
        pub search_button: TemplateChild<gtk::Button>,
        #[template_child]
        pub search_bar: TemplateChild<gtk::SearchBar>,
        #[template_child]
        pub search_entry: TemplateChild<gtk::SearchEntry>,
        #[template_child]
        pub connection_box: TemplateChild<FrgConnectionBox>,
        #[template_child]
        connection_popover: TemplateChild<FrgConnectionPopover>,
        #[template_child]
        pub toast_overlay: TemplateChild<adw::ToastOverlay>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for FrgApplicationWindow {
        const NAME: &'static str = "FrgApplicationWindow";
        type ParentType = adw::ApplicationWindow;
        type Type = super::FrgApplicationWindow;

        fn class_init(klass: &mut Self::Class) {
            klass.bind_template();
        }

        fn instance_init(obj: &subclass::InitializingObject<Self>) {
            obj.init_template();
        }
    }

    impl ObjectImpl for FrgApplicationWindow {
        fn constructed(&self) {
            self.parent_constructed();

            let obj = self.obj();
            let cm = FrgConnectionManager::default();

            // Default size
            let width = settings_manager::integer(Key::WindowWidth);
            let height = settings_manager::integer(Key::WindowHeight);
            obj.set_default_size(width, height);

            // Development style
            if config::PROFILE == "development" {
                obj.add_css_class("devel");
            }

            // Search
            let app = FrgApplication::default();
            self.search_entry
                .bind_property("text", &app, "search")
                .bidirectional()
                .build();
            self.search_bar.connect_entry(&self.search_entry.get());

            // Recolor headerbar purple for remote connections
            cm.connect_current_connection_notify(
                clone!(@weak self as this => move |manager|{
                    if !manager.current_connection().is_fragments(){
                        let subtitle = i18n_f("Remote control \"{}\"", &[&manager.current_connection().title()]);
                        this.window_title.set_subtitle(&subtitle);
                        this.obj().add_css_class("remote");
                    }else{
                        this.window_title.set_subtitle("");
                        this.obj().remove_css_class("remote");
                    }
                }),
            );

            // Show connection button if we have more than the standard local connection
            cm.connections().connect_items_changed(
                clone!(@weak self as this => move |model,_,_,_|{
                    let show = model.n_items() > 1;
                    this.connection_menu.set_visible(show);
                }),
            );

            // Check clipboard content for magnet links
            obj.connect_is_active_notify(move |window| {
                if window.is_active() {
                    let display = gdk::Display::default().unwrap();
                    let clipboard = display.primary_clipboard();

                    let fut = clone!(@weak window, @weak clipboard => async move {
                        let content = clipboard.read_text_future().await;
                        let mut clear_clipboard = false;

                        if let Ok(Some(text)) = content {
                            // Check for magnet link
                            if let Ok(magnet_link) = MagnetURI::from_str(&text) {
                                let fallback = String::new();
                                let info_hash = magnet_link.info_hash().unwrap_or(&fallback);
                                let cm = FrgConnectionManager::default();

                                // Transmission lowercases info hashes internally
                                if cm.client().torrents().torrent_by_hash(info_hash.to_lowercase()).is_none() {
                                    debug!("Detected new magnet link: {}", &text);
                                    window.imp().magnet_link_notification(&text, magnet_link.name());
                                    clear_clipboard = true;
                                } else {
                                    debug!("Ignore magnet link, torrent is already added.");
                                }
                            }

                            // Check for torrent link
                            if let Ok(url) = Url::parse(&text) {
                                // We only support http/https, and no file links because of sandboxing
                                if url.path().ends_with(".torrent") && (url.scheme() == "http" || url.scheme() == "https") {
                                    debug!("Detected torrent link: {}", &text);
                                    window.imp().torrent_link_notification(&url);
                                    clear_clipboard = true;
                                }
                            }
                        }

                        if clear_clipboard {
                            // To avoid that the clipboard toast is shown multiple times, we clear the clipboard
                            clipboard.set_text("");
                        }
                    });
                    glib::spawn_future_local(fut);
                }
            });

            // torrent-added notification
            cm.client().connect_local("torrent-added", false, clone!(@weak self as this => @default-return None, move |torrent|{
                if settings_manager::boolean(Key::EnableNotificationsNewTorrent){
                    let torrent: TrTorrent = torrent[1].get().unwrap();
                    utils::system_notification(i18n("New torrent added"), torrent.name(), Some("folder-download-symbolic"));
                }
                None
            }));

            // torrent-downloaded notification
            cm.client().connect_local("torrent-downloaded", false, clone!(@weak self as this => @default-return None, move |torrent|{
                if settings_manager::boolean(Key::EnableNotificationsDownloaded){
                    let torrent: TrTorrent = torrent[1].get().unwrap();
                    utils::system_notification(i18n("Torrent completely downloaded"), torrent.name(), Some("folder-download-symbolic"));
                }
                None
            }));

            // Lower polling rate when window isn't active
            obj.connect_is_active_notify(clone!(@weak self as this => move |_| {
                this.update_polling_mode();
            }));

            obj.connect_suspended_notify(clone!(@weak self as this => move |_| {
                this.update_polling_mode();
            }));
        }
    }

    impl WidgetImpl for FrgApplicationWindow {}

    impl WindowImpl for FrgApplicationWindow {
        fn close_request(&self) -> glib::Propagation {
            self.parent_close_request();

            debug!("Saving window geometry.");
            let window = self.obj();
            let (width, height) = window.default_size();

            settings_manager::set_integer(Key::WindowWidth, width);
            settings_manager::set_integer(Key::WindowHeight, height);

            let fut = clone!(@weak window => async move {
                window.set_visible(false);

                // Wait till transmission daemon is stopped
                FrgConnectionManager::default().disconnect()
                    .await
                    .expect("Unable to stop the transmission daemon");

                FrgApplication::default().quit();
            });
            glib::spawn_future_local(fut);

            glib::Propagation::Stop
        }
    }

    impl ApplicationWindowImpl for FrgApplicationWindow {}

    impl AdwApplicationWindowImpl for FrgApplicationWindow {}

    impl FrgApplicationWindow {
        fn update_polling_mode(&self) {
            let mode = if self.obj().is_suspended() {
                FrgPollingMode::SuspendedWindow
            } else if !self.obj().is_active() {
                FrgPollingMode::InactiveWindow
            } else {
                FrgPollingMode::Regular
            };
            FrgConnectionManager::default().set_polling_mode(mode);
        }

        fn magnet_link_notification(&self, magnet_link: &str, title: Option<&str>) {
            let text = match title {
                Some(name) => i18n_f("Add magnet link “{}” from clipboard?", &[name]),
                None => i18n("Add magnet link from clipboard?"),
            };

            let toast = adw::Toast::new(&text);
            toast.set_timeout(0);
            toast.set_button_label(Some(&i18n("_Add")));
            toast.set_action_name(Some("app.add-link"));
            toast.set_action_target_value(Some(&magnet_link.to_variant()));

            self.toast_overlay.get().add_toast(toast);
        }

        fn torrent_link_notification(&self, torrent_link: &Url) {
            let mut msg = i18n_f(
                "Add torrent from “{}”?",
                &[torrent_link.host_str().unwrap_or("unknown")],
            );

            // Try finding the exact torrent file name
            if let Some(segments) = torrent_link.path_segments() {
                if let Some(last) = segments.last() {
                    msg = i18n_f("Add “{}” from clipboard?", &[last])
                }
            }
            let toast = adw::Toast::new(&msg);
            toast.set_timeout(0);
            toast.set_button_label(Some(&i18n("_Add")));
            toast.set_action_name(Some("app.add-link"));
            toast.set_action_target_value(Some(&torrent_link.to_string().to_variant()));

            self.toast_overlay.get().add_toast(toast);
        }
    }
}

// Wrap FrgApplicationWindow into a usable gtk-rs object
glib::wrapper! {
    pub struct FrgApplicationWindow(
        ObjectSubclass<imp::FrgApplicationWindow>)
        @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
        @implements gio::ActionMap, gio::ActionGroup;
}

// FrgApplicationWindow implementation itself
impl FrgApplicationWindow {
    pub fn new(app: &FrgApplication) -> Self {
        glib::Object::builder::<FrgApplicationWindow>()
            .property("application", app)
            .build()
    }

    pub async fn set_connection(&self, connection_uuid: String) {
        // Hide searchbar again when changing connection
        self.imp().search_bar.set_search_mode(false);

        self.imp()
            .connection_box
            .set_connection(connection_uuid)
            .await;
    }

    pub fn show_search(&self, toggle: bool) {
        let imp = self.imp();

        if imp.search_button.is_sensitive() {
            if toggle {
                imp.search_bar
                    .set_search_mode(!imp.search_bar.is_search_mode());
            } else {
                imp.search_bar.set_search_mode(true);
            }
        }
    }

    pub fn toast_overlay(&self) -> adw::ToastOverlay {
        self.imp().toast_overlay.get()
    }
}

impl Default for FrgApplicationWindow {
    fn default() -> Self {
        FrgApplication::default()
            .active_window()
            .unwrap()
            .downcast()
            .unwrap()
    }
}
