// Fragments - utils.rs
// Copyright (C) 2022-2023  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::path::PathBuf;

use adw::prelude::*;
use gio::SimpleAction;
use glib::{clone, BindingFlags};
use gtk::{gio, glib};
use transmission_gobject::{TrTorrent, TrTorrentStatus};

use crate::backend::{FrgConnectionManager, FrgDirectoryType};
use crate::i18n::{i18n, ni18n_f};
use crate::ui::{FrgApplicationWindow, FrgTorrentDialog};

pub fn inapp_notification(text: &str, widget: Option<&gtk::Widget>) {
    let toast = adw::Toast::builder().title(text).timeout(10).build();

    if let Some(widget) = widget {
        // Is `widget` part of a PreferencesDialog?
        if let Some(preferences_dialog) = widget.ancestor(adw::PreferencesDialog::static_type()) {
            let preferences_dialog = preferences_dialog
                .downcast::<adw::PreferencesDialog>()
                .unwrap();
            preferences_dialog.add_toast(toast);
            return;
        }

        // Is `widget` a FrgTorrentDialog?
        if let Some(torrent_dialog) = widget.downcast_ref::<FrgTorrentDialog>() {
            torrent_dialog.toast_overlay().add_toast(toast);
            return;
        }
    }

    // Fallback to FrgApplicationWindow
    let window = FrgApplicationWindow::default();
    window.toast_overlay().add_toast(toast)
}

pub fn system_notification<T: AsRef<str>>(title: T, subtitle: T, icon: Option<&str>) {
    let notification = gio::Notification::new(title.as_ref());
    notification.set_body(Some(subtitle.as_ref()));

    if let Some(icon) = icon {
        match gio::Icon::for_string(icon) {
            Ok(gicon) => notification.set_icon(&gicon),
            Err(err) => debug!("Unable to display notification: {:?}", err),
        }
    }

    gio::Application::default()
        .unwrap()
        .send_notification(None, &notification);
    debug!("System Notification: {}", title.as_ref());
}

pub fn bind_connected_property(action: &SimpleAction) {
    let client = FrgConnectionManager::default().client();
    client
        .bind_property("is-connected", action, "enabled")
        .flags(BindingFlags::SYNC_CREATE)
        .build();
}

pub fn format_size(size: i64) -> String {
    glib::format_size(size.try_into().unwrap_or(0)).to_string()
}

pub fn format_speed(speed: i32) -> String {
    format!("{}/s", format_size(speed as i64))
}

pub fn format_time(total_seconds: u32, limit_at_day: bool) -> String {
    if total_seconds == u32::MAX || total_seconds == 4294967294 {
        warn!("Invalid seconds: {}", total_seconds);
        return "?".into();
    } else if total_seconds == 1 || total_seconds == 0 {
        return String::new();
    }

    if limit_at_day && total_seconds > 86400 {
        return i18n("more than a day");
    }

    let seconds: u32 = total_seconds % 60;
    let minutes: u32 = (total_seconds % 3600) / 60;
    let hours: u32 = (total_seconds % 86400) / 3600;
    let days: u32 = (total_seconds % (86400 * 60)) / 86400;

    let str_days = ni18n_f("{} day", "{} days", days, &[&days.to_string()]);
    let str_hours = ni18n_f("{} hour", "{} hours", hours, &[&hours.to_string()]);
    let str_minutes = ni18n_f("{} minute", "{} minutes", minutes, &[&minutes.to_string()]);
    let str_seconds = ni18n_f("{} second", "{} seconds", seconds, &[&seconds.to_string()]);

    if days > 0 {
        format!("{str_days}, {str_hours}")
    } else if hours > 0 {
        format!("{str_hours}, {str_minutes}")
    } else if minutes > 0 {
        str_minutes
    } else if seconds > 0 {
        str_seconds
    } else {
        String::new()
    }
}

pub fn eta_text(torrent: &TrTorrent) -> String {
    match torrent.status() {
        TrTorrentStatus::Stopped => i18n("Paused"),
        TrTorrentStatus::Download => {
            let total_seconds: u32 = torrent.eta().try_into().unwrap_or(1);
            format_time(total_seconds, true)
        }
        // i18n: "Queued" is the current status of a torrent
        TrTorrentStatus::DownloadWait => i18n("Queued"),
        TrTorrentStatus::Check => i18n("Checking…"),
        // i18n: "Queued" is the current status of a torrent
        TrTorrentStatus::CheckWait => i18n("Queued"),
        TrTorrentStatus::Seed => i18n("Seeding…"),
        // i18n: "Queued" is the current status of a torrent
        TrTorrentStatus::SeedWait => i18n("Queued"),
    }
}

pub fn set_mimetype_image(mimetype: &str, image: &gtk::Image) {
    let mimetype = if mimetype.is_empty()
        || mimetype == "0"
        || mimetype == "application/x-zerosize"
        || mimetype == "application/octet-stream"
    {
        // Fallback to avoid ugly generic icon
        "text-x-generic"
    } else {
        mimetype
    };

    let icon = gio::content_type_get_symbolic_icon(mimetype);
    image.set_from_gicon(&icon);
}

pub fn session_dir_filechooser(parent: &gtk::Window, directory_type: FrgDirectoryType) {
    let (property_name, title) = match directory_type {
        FrgDirectoryType::Download => ("download-dir", i18n("Select Download Directory")),
        FrgDirectoryType::Incomplete => ("incomplete-dir", i18n("Select Incomplete Directory")),
        FrgDirectoryType::None => return,
    };

    let dialog = gtk::FileDialog::new();
    dialog.set_title(&title);
    dialog.set_accept_label(Some(&i18n("_Select")));
    dialog.select_folder(
        Some(parent),
        gio::Cancellable::NONE,
        clone!(@weak dialog, @weak parent => move |result| {
            match result {
                Ok(folder) => {
                    debug!("Selected directory: {:?}", folder.path());

                    let session = FrgConnectionManager::default().client().session();
                    session.set_property(property_name, folder);

                    FrgConnectionManager::default().check_directories();
                }
                Err(err) => {
                    warn!("Selected directory could not be accessed {:?}", err);
                }
            }
        }),
    );
}

/// Checks whether a directory is accessible and writable
pub fn check_writable(dir: PathBuf) -> bool {
    debug!("Check if directory is writable: {:?}", dir);

    let dummy = dir.join("dummy");
    let res = std::fs::write(&dummy, "0").and_then(|_| std::fs::remove_file(dummy));

    res.is_ok()
}
