// SPDX-License-Identifier: GPL-3.0-or-later
use gtk::{gdk, glib, glib::closure_local, prelude::*, subclass::prelude::*};
use matrix_sdk::encryption::verification::QrVerificationData;

mod camera;
mod qr_code_detector;

pub use camera::{Camera, CameraExt};

mod imp {
    use std::cell::RefCell;

    use adw::subclass::prelude::*;
    use glib::subclass::{InitializingObject, Signal};
    use gtk::CompositeTemplate;
    use once_cell::sync::Lazy;

    use super::*;

    #[derive(Debug, CompositeTemplate, Default)]
    #[template(resource = "/org/gnome/Fractal/ui/contrib/qr_code_scanner/mod.ui")]
    pub struct QrCodeScanner {
        #[template_child]
        pub stack: TemplateChild<gtk::Stack>,
        #[template_child]
        pub picture: TemplateChild<gtk::Picture>,
        pub handler: RefCell<Option<glib::SignalHandlerId>>,
    }

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

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

        fn instance_init(obj: &InitializingObject<Self>) {
            obj.init_template();
        }
    }
    impl ObjectImpl for QrCodeScanner {
        fn signals() -> &'static [Signal] {
            static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
                vec![Signal::builder("code-detected")
                    .param_types([QrVerificationDataBoxed::static_type()])
                    .run_first()
                    .build()]
            });
            SIGNALS.as_ref()
        }
    }
    impl WidgetImpl for QrCodeScanner {
        fn unmap(&self) {
            self.parent_unmap();
            self.obj().stop();
        }
    }
    impl BinImpl for QrCodeScanner {}
}

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

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

    pub fn stop(&self) {
        let imp = self.imp();

        if let Some(paintable) = imp.picture.paintable() {
            imp.picture.set_paintable(gdk::Paintable::NONE);
            if let Some(handler) = imp.handler.take() {
                paintable.disconnect(handler);
            }
        }
    }

    pub async fn start(&self) {
        let imp = self.imp();
        let camera = camera::Camera::default();

        if let Some(paintable) = camera.paintable().await {
            self.stop();

            imp.picture.set_paintable(Some(&paintable));

            let callback = glib::clone!(
                #[weak(rename_to = obj)]
                self,
                #[upgrade_or]
                None,
                move |args: &[glib::Value]| {
                    let code = args
                        .get(1)
                        .unwrap()
                        .get::<QrVerificationDataBoxed>()
                        .unwrap();
                    obj.emit_by_name::<()>("code-detected", &[&code]);

                    None
                }
            );
            let handler = paintable.connect_local("code-detected", false, callback);

            imp.handler.replace(Some(handler));
            imp.stack.set_visible_child_name("camera");
        } else {
            imp.stack.set_visible_child_name("no-camera");
        }
    }

    pub fn connect_code_detected<F: Fn(&Self, QrVerificationData) + 'static>(
        &self,
        f: F,
    ) -> glib::SignalHandlerId {
        self.connect_closure(
            "code-detected",
            true,
            closure_local!(move |obj: Self, data: QrVerificationDataBoxed| {
                f(&obj, data.0);
            }),
        )
    }
}

#[derive(Clone, Debug, PartialEq, Eq, glib::Boxed)]
#[boxed_type(name = "QrVerificationDataBoxed")]
struct QrVerificationDataBoxed(QrVerificationData);
