//! Native callback and legacy asynchronous-delegate adapters.

use std::any::Any;
use std::fmt;
use std::sync::{Arc, Condvar, Mutex};

use libremetaverse_types::compat::{DictionaryEntry, Object};

use crate::{DictionaryEventAction, Error};

/// Completion object returned by mapped delegate `BeginInvoke` methods.
#[derive(Clone)]
pub struct AsyncInvocation {
    state: Arc<AsyncInvocationState>,
    async_state: Object,
}

struct AsyncInvocationState {
    result: Mutex<Option<Result<(), Error>>>,
    completed: Condvar,
}

impl fmt::Debug for AsyncInvocation {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("AsyncInvocation")
            .field("async_state", &self.async_state)
            .field("completed", &self.is_completed())
            .finish()
    }
}

impl AsyncInvocation {
    pub(crate) fn pending(async_state: Object) -> Self {
        Self {
            state: Arc::new(AsyncInvocationState {
                result: Mutex::new(None),
                completed: Condvar::new(),
            }),
            async_state,
        }
    }

    pub(crate) fn complete(&self, result: Result<(), Error>) {
        *self
            .state
            .result
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner) = Some(result);
        self.state.completed.notify_all();
    }

    #[must_use]
    pub fn is_completed(&self) -> bool {
        self.state
            .result
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner)
            .is_some()
    }

    #[must_use]
    pub fn async_state(&self) -> &Object {
        &self.async_state
    }

    pub(crate) fn wait(&self) -> Result<(), Error> {
        let result = self
            .state
            .result
            .lock()
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        let result = self
            .state
            .completed
            .wait_while(result, |result| result.is_none())
            .unwrap_or_else(std::sync::PoisonError::into_inner);
        result.expect("completion predicate guarantees a result")
    }
}

type DictionaryHandler = Arc<dyn Fn(DictionaryEventAction, DictionaryEntry) + Send + Sync>;

/// Opaque target accepted by the metadata-compatible delegate constructor.
pub struct DictionaryCallbackTarget(pub DictionaryHandler);

impl fmt::Debug for DictionaryCallbackTarget {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str("DictionaryCallbackTarget(..)")
    }
}

#[derive(Clone)]
pub struct DictionaryChangeCallback(DictionaryHandler);

impl DictionaryChangeCallback {
    pub fn from_callback(
        callback: impl Fn(DictionaryEventAction, DictionaryEntry) + Send + Sync + 'static,
    ) -> Self {
        Self(Arc::new(callback))
    }

    pub fn new(object: Object, method: isize) -> Result<Self, Error> {
        if method != 0 {
            return Err(Error::Argument);
        }
        object
            .downcast_arc::<DictionaryCallbackTarget>()
            .map(|target| Self(Arc::clone(&target.0)))
            .ok_or(Error::Argument)
    }

    pub fn begin_invoke(
        &self,
        action: DictionaryEventAction,
        entry: DictionaryEntry,
        callback: Box<dyn Fn(&dyn Any) + Send + Sync>,
        object: Object,
    ) -> Result<Box<dyn Any + Send + Sync>, Error> {
        let invocation = AsyncInvocation::pending(object);
        let worker_invocation = invocation.clone();
        let handler = Arc::clone(&self.0);
        std::thread::Builder::new()
            .name("dictionary-callback".to_owned())
            .spawn(move || {
                let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                    handler(action, entry);
                }))
                .map_err(|_| Error::InvalidOperation);
                worker_invocation.complete(result);
                let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                    callback(&worker_invocation);
                }));
            })
            .map_err(|_| Error::InvalidOperation)?;
        Ok(Box::new(invocation))
    }

    pub fn end_invoke(&self, result: Box<dyn Any + Send + Sync>) -> Result<(), Error> {
        result
            .downcast::<AsyncInvocation>()
            .map_err(|_| Error::Argument)?
            .wait()
    }

    pub fn invoke(
        &self,
        action: DictionaryEventAction,
        entry: DictionaryEntry,
    ) -> Result<(), Error> {
        (self.0)(action, entry);
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic::{AtomicUsize, Ordering};

    #[test]
    fn asynchronous_delegate_can_be_observed_and_ended() {
        let calls = Arc::new(AtomicUsize::new(0));
        let observed = Arc::clone(&calls);
        let callback = DictionaryChangeCallback::from_callback(move |_, _| {
            observed.fetch_add(1, Ordering::Relaxed);
        });
        let result = callback
            .begin_invoke(
                DictionaryEventAction::Add,
                DictionaryEntry(Object::Integer(1), Object::Integer(2)),
                Box::new(|_| {}),
                Object::Undefined,
            )
            .unwrap();
        callback.end_invoke(result).unwrap();
        assert_eq!(calls.load(Ordering::Relaxed), 1);
    }
}
