// Fragments - preferences_dialog.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 adw::prelude::*;
use adw::subclass::prelude::*;
use ashpd::desktop::open_uri::OpenFileRequest;
use ashpd::WindowIdentifier;
use glib::{clone, subclass, Properties};
use gtk::{glib, CompositeTemplate};

use crate::app::FrgApplication;
use crate::backend::{FrgConnectionManager, FrgDirectoryType};
use crate::i18n::{i18n, i18n_f};
use crate::settings::{settings_manager, Key};
use crate::utils;

mod imp {
    use super::*;

    #[derive(Default, Debug, CompositeTemplate, Properties)]
    #[template(resource = "/de/haeckerfelix/Fragments/gtk/preferences_dialog.ui")]
    #[properties(wrapper_type = super::FrgPreferencesDialog)]
    pub struct FrgPreferencesDialog {
        #[template_child]
        remote_notice_group: TemplateChild<adw::PreferencesGroup>,
        #[template_child]
        remote_notice_row: TemplateChild<adw::ActionRow>,

        // General page
        #[template_child]
        inhibit_suspend_active_torrents_switch: TemplateChild<gtk::Switch>,
        #[template_child]
        trash_original_torrent_files_switch: TemplateChild<gtk::Switch>,
        #[template_child]
        notifications_downloaded_switch: TemplateChild<gtk::Switch>,
        #[template_child]
        notifications_new_torrent_switch: TemplateChild<gtk::Switch>,
        #[template_child]
        remote_access_switch: TemplateChild<gtk::Switch>,
        #[template_child]
        open_webinterface_row: TemplateChild<adw::ActionRow>,

        // Downloading page
        #[template_child]
        download_dir_button: TemplateChild<gtk::Button>,
        #[template_child]
        download_dir_content: TemplateChild<adw::ButtonContent>,
        #[template_child]
        incomplete_dir_expander: TemplateChild<adw::ExpanderRow>,
        #[template_child]
        incomplete_dir_button: TemplateChild<gtk::Button>,
        #[template_child]
        incomplete_dir_content: TemplateChild<adw::ButtonContent>,
        #[template_child]
        download_queue_expander: TemplateChild<adw::ExpanderRow>,
        #[template_child]
        download_queue_size_spinbutton: TemplateChild<adw::SpinRow>,
        #[template_child]
        start_added_torrents_switch: TemplateChild<gtk::Switch>,

        // Network page
        #[template_child]
        port_spinbutton: TemplateChild<adw::SpinRow>,
        #[template_child]
        random_port_switch: TemplateChild<gtk::Switch>,
        #[template_child]
        port_forwarding_switch: TemplateChild<gtk::Switch>,
        #[template_child]
        port_test_label: TemplateChild<gtk::Label>,
        #[template_child]
        port_test_button: TemplateChild<gtk::Button>,
        #[template_child]
        max_peers_torrent_spinbutton: TemplateChild<adw::SpinRow>,
        #[template_child]
        max_peers_overall_spinbutton: TemplateChild<adw::SpinRow>,
        #[template_child]
        encryption_comborow: TemplateChild<adw::ComboRow>,

        #[property(get)]
        connection_manager: FrgConnectionManager,
    }

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

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

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

    #[glib::derived_properties]
    impl ObjectImpl for FrgPreferencesDialog {
        fn constructed(&self) {
            self.parent_constructed();

            let client = self.connection_manager.client();
            let session = client.session();

            // We show/hide few widgets depending on the connection type (remote/local)
            self.connection_manager.connect_current_connection_notify(
                clone!(@weak self as this => move |_|{
                    this.update_remote_buttons();
                }),
            );

            // General page
            //
            settings_manager::bind_property(
                Key::InhibitSuspendActiveTorrents,
                &*self.inhibit_suspend_active_torrents_switch,
                "active",
            );
            self.inhibit_suspend_active_torrents_switch
                .connect_active_notify(|_| {
                    FrgApplication::default().update_inhibit_state();
                });
            settings_manager::bind_property(
                Key::TrashOriginalTorrentFiles,
                &*self.trash_original_torrent_files_switch,
                "active",
            );
            settings_manager::bind_property(
                Key::EnableNotificationsNewTorrent,
                &*self.notifications_new_torrent_switch,
                "active",
            );
            settings_manager::bind_property(
                Key::EnableNotificationsDownloaded,
                &*self.notifications_downloaded_switch,
                "active",
            );
            settings_manager::bind_property(
                Key::RemoteAccess,
                &*self.remote_access_switch,
                "active",
            );
            self.remote_access_switch.connect_state_set(clone!(@weak self as this => @default-panic, move |switch, _| {
                settings_manager::set_boolean(Key::RemoteAccess, switch.is_active());
                switch.set_sensitive(false);

                let fut = clone!(@weak switch, @weak this => async move {
                    if let Err(err) = FrgConnectionManager::default().reconnect().await{
                        utils::inapp_notification(&i18n("Unable to restart Transmission daemon"), Some(this.obj().upcast_ref::<gtk::Widget>()));
                        warn!("Unable to restart Transmission daemon: {}", err.to_string());
                    }

                    switch.set_sensitive(true);
                });
                glib::spawn_future_local(fut);
                glib::Propagation::Proceed
            }));
            self.open_webinterface_row
                .connect_activated(clone!(@weak self as this => move |_| {
                    let fut = async move {
                        let uri = url::Url::parse("http://127.0.0.1:9091/").unwrap();
                        let native = this.obj().native().unwrap();
                        let identifier = WindowIdentifier::from_native(&native).await;
                        if let Err(err) = OpenFileRequest::default()
                            .identifier(identifier)
                            .send_uri(&uri)
                            .await
                        {
                            debug!("Unable to open webinterface: {:?}", err);
                            utils::inapp_notification("Unable to open webinterface", Some(this.obj().upcast_ref::<gtk::Widget>()));
                        }
                    };
                    glib::spawn_future_local(fut);
                }));

            // Downloading page
            //
            client.connect_is_connected_notify(clone!(@weak self as this => move |_|{
                this.update_paths();
            }));

            session.connect_download_dir_notify(glib::clone!(@weak self as this => move |_| {
                this.update_paths();
            }));

            self.download_dir_button.connect_clicked(move |btn| {
                let parent = btn.root().unwrap().downcast::<gtk::Window>().unwrap();
                utils::session_dir_filechooser(&parent, FrgDirectoryType::Download);
            });

            session
                .bind_property(
                    "incomplete-dir-enabled",
                    &*self.incomplete_dir_expander,
                    "enable-expansion",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session.connect_incomplete_dir_enabled_notify(
                glib::clone!(@weak self as this => move |_| {
                    FrgConnectionManager::default().check_directories();
                }),
            );

            session.connect_incomplete_dir_notify(glib::clone!(@weak self as this => move |_| {
                this.update_paths();
            }));

            self.incomplete_dir_button.connect_clicked(move |btn| {
                let parent = btn.root().unwrap().downcast::<gtk::Window>().unwrap();
                utils::session_dir_filechooser(&parent.upcast(), FrgDirectoryType::Incomplete);
            });

            session
                .bind_property(
                    "download-queue-enabled",
                    &*self.download_queue_expander,
                    "enable-expansion",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session
                .bind_property(
                    "download-queue-size",
                    &*self.download_queue_size_spinbutton,
                    "value",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session
                .bind_property(
                    "start-added-torrents",
                    &*self.start_added_torrents_switch,
                    "active",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            // Network page
            //
            session
                .bind_property("peer-port", &*self.port_spinbutton, "value")
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session
                .bind_property(
                    "peer-port-random-on-start",
                    &*self.random_port_switch,
                    "active",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session
                .bind_property(
                    "port-forwarding-enabled",
                    &*self.port_forwarding_switch,
                    "active",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session
                .bind_property(
                    "peer-limit-per-torrent",
                    &*self.max_peers_torrent_spinbutton,
                    "value",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session
                .bind_property(
                    "peer-limit-global",
                    &*self.max_peers_overall_spinbutton,
                    "value",
                )
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            session
                .bind_property("encryption", &*self.encryption_comborow, "selected")
                .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL)
                .build();

            self.update_paths();
            self.update_remote_buttons();
        }
    }

    impl WidgetImpl for FrgPreferencesDialog {}

    impl AdwDialogImpl for FrgPreferencesDialog {}

    impl PreferencesDialogImpl for FrgPreferencesDialog {}

    #[gtk::template_callbacks]
    impl FrgPreferencesDialog {
        #[template_callback]
        async fn port_test_clicked(&self) {
            let client = self.connection_manager.client();

            self.port_test_button.set_sensitive(false);
            let msg = i18n("Testing…");
            self.port_test_label.set_label(&msg);

            self.port_test_label.remove_css_class("heading");
            self.port_test_label.remove_css_class("error");
            self.port_test_label.remove_css_class("success");

            let msg = match client.test_port().await {
                Ok(succeeded) => {
                    if succeeded {
                        self.port_test_label.add_css_class("success");
                        i18n("Port is open. You can communicate with other peers.")
                    } else {
                        self.port_test_label.add_css_class("error");
                        i18n("Port is closed. Communication with other peers is limited.\nCheck your router or firewall if port forwarding is enabled for your computer. ")
                    }
                }
                Err(_) => {
                    self.port_test_label.add_css_class("error");
                    i18n("Test failed. Make sure you are connected to the internet.")
                }
            };

            self.port_test_label.add_css_class("heading");
            self.port_test_label.set_label(&msg);
            self.port_test_button.set_sensitive(true);
        }

        fn update_paths(&self) {
            let session = self.connection_manager.client().session();

            if let Some(file) = session.download_dir() {
                let download_dir = file.basename().unwrap().to_str().unwrap().to_string();
                self.download_dir_content.set_label(&download_dir);
            }
            if let Some(file) = session.incomplete_dir() {
                let incomplete_dir = file.basename().unwrap().to_str().unwrap().to_string();
                self.incomplete_dir_content.set_label(&incomplete_dir);
            }
        }

        fn update_remote_buttons(&self) {
            let is_fragments = self.connection_manager.current_connection().is_fragments();
            let title = self.connection_manager.current_connection().title();

            self.download_dir_button.set_sensitive(is_fragments);
            self.incomplete_dir_button.set_sensitive(is_fragments);

            self.remote_notice_group.set_visible(!is_fragments);
            self.remote_notice_row
                .set_title(&i18n_f("You are connected with “{}”", &[&title]));
        }
    }
}

glib::wrapper! {
    pub struct FrgPreferencesDialog(
        ObjectSubclass<imp::FrgPreferencesDialog>)
        @extends gtk::Widget, adw::Dialog, adw::PreferencesDialog;
}

impl Default for FrgPreferencesDialog {
    fn default() -> Self {
        glib::Object::new()
    }
}
