// SPDX-FileCopyrightText: 2023 Gleb Smirnov <glebsmirnov0708@gmail.com>
//
// SPDX-License-Identifier: GPL-3.0-or-later

use gtk::prelude::*;
use gtk::subclass::prelude::*;

use gtk::{gdk, glib};

mod imp {
    use super::*;

    use std::cell::Cell;

    use gdk::{Key, ModifierType};
    use gettextrs::gettext;
    use gtk::{template_callbacks, TemplateChild};

    /// Private part of [`SearchEntry`].
    ///
    /// [`SearchEntry`]: super::SearchEntry
    #[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
    #[properties(wrapper_type = super::SearchEntry)]
    #[template(resource = "/io/gitlab/liferooter/TextPieces/ui/search-entry.ui")]
    pub struct SearchEntry {
        #[template_child]
        text: TemplateChild<gtk::Text>,

        #[template_child]
        info: TemplateChild<gtk::Label>,

        #[property(get, set)]
        occurrence_count: Cell<u32>,

        #[property(get, set)]
        occurrence_position: Cell<i32>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for SearchEntry {
        const NAME: &'static str = "TextPiecesSearchEntry";
        type Type = super::SearchEntry;
        type ParentType = gtk::Widget;
        type Interfaces = (gtk::Editable,);

        fn class_init(class: &mut Self::Class) {
            class.bind_template();
            class.bind_template_callbacks();
            class.set_css_name("entry");

            let move_previous_keys = [
                (Key::Up, ModifierType::empty()),
                (Key::Return, ModifierType::SHIFT_MASK),
                (Key::KP_Enter, ModifierType::SHIFT_MASK),
                (
                    Key::Return,
                    ModifierType::CONTROL_MASK | ModifierType::SHIFT_MASK,
                ),
                (
                    Key::KP_Enter,
                    ModifierType::CONTROL_MASK | ModifierType::SHIFT_MASK,
                ),
            ];

            for (key, modifiers) in move_previous_keys {
                class.add_binding_action(key, modifiers, "search.move-previous");
            }

            let move_next_keys = [
                (Key::Down, ModifierType::empty()),
                (Key::Return, ModifierType::empty()),
                (Key::KP_Enter, ModifierType::empty()),
                (Key::Return, ModifierType::CONTROL_MASK),
                (Key::KP_Enter, ModifierType::CONTROL_MASK),
            ];

            for (key, modifiers) in move_next_keys {
                class.add_binding_action(key, modifiers, "search.move-next")
            }
        }

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

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

            self.obj()
                .connect_occurrence_count_notify(|obj| obj.imp().update_position());
            self.obj()
                .connect_occurrence_position_notify(|obj| obj.imp().update_position());
        }

        fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
            if !self.delegate_set_property(id, value, pspec) {
                unimplemented!()
            }
        }

        fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
            self.delegate_get_property(id, pspec).unwrap()
        }
    }

    impl WidgetImpl for SearchEntry {
        fn grab_focus(&self) -> bool {
            self.text.grab_focus()
        }
    }

    impl EditableImpl for SearchEntry {
        fn delegate(&self) -> Option<gtk::Editable> {
            Some(self.text.get().upcast())
        }
    }

    impl SearchEntry {
        fn update_position(&self) {
            let occurrence_info = Some(self.occurrence_count.get())
                .filter(|&count| count != 0)
                .and_then(|count| {
                    Some(self.occurrence_position.get())
                        .filter(|&position| position != -1)
                        .map(|position| (position, count))
                });

            if let Some((position, count)) = occurrence_info {
                self.info.set_label(&gettext!("{} of {}", position, count));
            } else {
                self.info.set_label("");
            }
        }
    }

    #[template_callbacks]
    impl SearchEntry {
        #[template_callback]
        fn on_text_notify(&self, pspec: &glib::ParamSpec) {
            let pspec = self
                .obj()
                .class()
                .find_property(pspec.name())
                .expect("Failed to get property \"text\"");
            self.obj().notify_by_pspec(&pspec);
        }

        #[template_callback]
        fn on_text_activate(&self) {
            self.obj()
                .activate_action("search.move-next", None)
                .expect("Failed to activate action `search.move-next`");
        }
    }
}

glib::wrapper! {
    /// Entry for editor search bar.
    pub struct SearchEntry(ObjectSubclass<imp::SearchEntry>)
        @extends gtk::Widget,
        @implements gtk::Editable, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget;
}
