// Take a look at the license at the top of the repository in the LICENSE file.

// rustdoc-stripper-ignore-next
//! Traits intended for subclassing [`MapSource`](crate::MapSource).

use crate::{prelude::*, MapSource, Tile};
use gio::{AsyncResult, Cancellable};
use glib::subclass::prelude::*;
use glib::translate::*;
use std::{future::Future, pin::Pin};

pub trait MapSourceImpl: ObjectImpl {
    fn fill_tile_future(
        &self,
        tile: &Tile,
    ) -> Pin<Box<dyn Future<Output = Result<(), glib::Error>> + 'static>>;
}

mod sealed {
    pub trait Sealed {}
    impl<T: super::MapSourceImplExt> Sealed for T {}
}

pub trait MapSourceImplExt: sealed::Sealed + ObjectSubclass {
    #[allow(clippy::type_complexity)]
    fn parent_fill_tile_async<Q: IsA<Cancellable>, C: FnOnce(&AsyncResult) + Send + 'static>(
        &self,
        tile: &Tile,
        cancellable: Option<&Q>,
        callback: C,
    ) {
        unsafe {
            let data = Self::type_data();
            let parent_class = data.as_ref().parent_class() as *mut ffi::ShumateMapSourceClass;
            let f = (*parent_class)
                .fill_tile_async
                .expect("no parent \"fill_tile_async\" implementation");
            let user_data: Box<C> = Box::new(callback);

            unsafe extern "C" fn parent_fill_tile_async_trampoline<
                C: FnOnce(&AsyncResult) + Send + 'static,
            >(
                _source_object_ptr: *mut glib::gobject_ffi::GObject,
                res: *mut gio::ffi::GAsyncResult,
                user_data: glib::ffi::gpointer,
            ) {
                let res = AsyncResult::from_glib_borrow(res);
                let cb: Box<C> = Box::from_raw(user_data as *mut _);
                cb(&res)
            }

            let cancellable = cancellable.map(|p| p.as_ref());
            let callback = parent_fill_tile_async_trampoline::<C>;
            f(
                self.obj().unsafe_cast_ref::<MapSource>().to_glib_none().0,
                tile.to_glib_none().0,
                cancellable.to_glib_none().0,
                Some(callback),
                Box::into_raw(user_data) as *mut _,
            );
        }
    }

    fn parent_fill_tile_future(
        &self,
        tile: &Tile,
    ) -> Pin<Box<dyn Future<Output = Result<(), glib::Error>> + 'static>> {
        let tile = tile.clone();
        Box::pin(gio::GioFuture::new(
            &self.ref_counted(),
            move |imp, cancellable, send| {
                imp.parent_fill_tile_async(&tile, Some(cancellable), move |res| {
                    send.resolve(res.legacy_propagate_error());
                });
            },
        ))
    }
}

impl<T: MapSourceImpl> MapSourceImplExt for T {}

unsafe impl<T: MapSourceImpl> IsSubclassable<T> for MapSource {
    fn class_init(class: &mut glib::Class<Self>) {
        Self::parent_class_init::<T>(class);

        let klass = class.as_mut();
        klass.fill_tile_async = Some(map_source_fill_tile_async::<T>);
        klass.fill_tile_finish = Some(map_source_fill_tile_finish);
    }
}

unsafe extern "C" fn map_source_fill_tile_async<T: MapSourceImpl>(
    ptr: *mut ffi::ShumateMapSource,
    tile: *mut ffi::ShumateTile,
    cancellable_ptr: *mut gio::ffi::GCancellable,
    callback: gio::ffi::GAsyncReadyCallback,
    user_data: glib::ffi::gpointer,
) {
    let instance = &*(ptr as *mut T::Instance);
    let imp = instance.imp();
    let cancellable: Option<gio::Cancellable> = from_glib_none(cancellable_ptr);

    let closure = move |result: gio::LocalTask<bool>, source_object: Option<&MapSource>| {
        let result: *mut gio::ffi::GAsyncResult = result
            .unsafe_cast_ref::<gio::AsyncResult>()
            .to_glib_none()
            .0;
        let source_object = source_object
            .map(|o| o.unsafe_cast_ref::<glib::Object>())
            .to_glib_none()
            .0;
        callback.unwrap()(source_object, result, user_data)
    };

    let t = gio::LocalTask::new(None, cancellable.as_ref(), closure);

    glib::MainContext::default().spawn_local(async move {
        let res = imp.fill_tile_future(&from_glib_none(tile)).await;
        match res {
            Ok(_) => t.return_result(Ok(true)),
            Err(e) => t.return_result(Err(e)),
        }
    });
}

unsafe extern "C" fn map_source_fill_tile_finish(
    _ptr: *mut ffi::ShumateMapSource,
    res_ptr: *mut gio::ffi::GAsyncResult,
    error_ptr: *mut *mut glib::ffi::GError,
) -> glib::ffi::gboolean {
    let res: gio::AsyncResult = from_glib_none(res_ptr);
    let task = res.downcast::<gio::LocalTask<bool>>().unwrap();
    match task.propagate() {
        Ok(v) => {
            assert!(v);
            true.into_glib()
        }
        Err(e) => {
            *error_ptr = e.into_glib_ptr();
            false.into_glib()
        }
    }
}
