// Fragments - file_sorter.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::cell::{Cell, RefCell};

use glib::subclass::prelude::*;
use glib::{Enum, Properties};
use gtk::glib;
use gtk::prelude::*;
use gtk::subclass::sorter::SorterImpl;
use human_sort::compare;
use transmission_gobject::TrFile;

mod imp {
    use super::*;

    #[derive(Debug, Default, Properties)]
    #[properties(wrapper_type = super::FrgFileSorter)]
    pub struct FrgFileSorter {
        #[property(get, set=Self::set_descending)]
        descending: Cell<bool>,
        #[property(get, set=Self::set_sorting, builder(FrgFileSorting::default()))]
        sorting: RefCell<FrgFileSorting>,
        #[property(get, set=Self::set_folders_before_files)]
        folders_before_files: Cell<bool>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for FrgFileSorter {
        const NAME: &'static str = "FrgFileSorter";
        type Type = super::FrgFileSorter;
        type ParentType = gtk::Sorter;
    }

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

            // Default value
            self.folders_before_files.set(true);
        }
    }

    impl SorterImpl for FrgFileSorter {
        fn order(&self) -> gtk::SorterOrder {
            gtk::SorterOrder::Total
        }

        fn compare(&self, item1: &glib::Object, item2: &glib::Object) -> gtk::Ordering {
            let a = item1.downcast_ref::<TrFile>().unwrap();
            let b = item2.downcast_ref::<TrFile>().unwrap();
            Self::file_cmp(
                a,
                b,
                self.obj().sorting(),
                self.obj().descending(),
                self.obj().folders_before_files(),
            )
            .into()
        }
    }

    impl FrgFileSorter {
        fn set_descending(&self, descending: bool) {
            self.descending.set(descending);
            self.obj().changed(gtk::SorterChange::Different);
        }

        fn set_sorting(&self, sorting: FrgFileSorting) {
            *self.sorting.borrow_mut() = sorting;
            self.obj().changed(gtk::SorterChange::Different);
        }

        fn set_folders_before_files(&self, folders_before_files: bool) {
            self.folders_before_files.set(folders_before_files);
            self.obj().changed(gtk::SorterChange::Different);
        }

        fn file_cmp(
            a: &TrFile,
            b: &TrFile,
            sorting: FrgFileSorting,
            descending: bool,
            folders_before_files: bool,
        ) -> std::cmp::Ordering {
            let mut file_a = a.clone();
            let mut file_b = b.clone();

            if folders_before_files {
                if file_a.is_folder() && !file_b.is_folder() {
                    return std::cmp::Ordering::Less;
                }

                if !file_a.is_folder() && file_b.is_folder() {
                    return std::cmp::Ordering::Greater;
                }
            }

            if descending {
                std::mem::swap(&mut file_a, &mut file_b);
            }

            match sorting {
                FrgFileSorting::Default => std::cmp::Ordering::Equal,
                FrgFileSorting::Name => compare(&file_a.title(), &file_b.title()),
                FrgFileSorting::Size => file_a.length().cmp(&file_b.length()),
            }
        }
    }
}

glib::wrapper! {
    pub struct FrgFileSorter(ObjectSubclass<imp::FrgFileSorter>) @extends gtk::Sorter;
}

impl FrgFileSorter {
    pub fn new() -> Self {
        glib::Object::new()
    }
}

impl Default for FrgFileSorter {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Display, Copy, Debug, Clone, PartialEq, Enum)]
#[repr(u32)]
#[enum_type(name = "FrgFileSorting")]
#[derive(Default)]
pub enum FrgFileSorting {
    Default,
    #[default]
    Name,
    Size,
}

impl From<&str> for FrgFileSorting {
    fn from(string: &str) -> Self {
        match string {
            "default" => Self::Default,
            "name" => Self::Name,
            "size" => Self::Size,
            _ => panic!("Invalid enum string"),
        }
    }
}
