use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};

use downcast::Downcast;

use wayland_commons::debug;
use wayland_commons::filter::Filter;
use wayland_commons::map::ObjectMap;
use wayland_commons::wire::Message;
use wayland_commons::{MessageGroup, ThreadGuard};

use crate::{Interface, Main, Proxy};

mod connection;
mod display;
mod proxy;
mod queues;

pub(crate) use self::display::DisplayInner;
pub(crate) use self::proxy::ProxyInner;
pub(crate) use self::queues::EventQueueInner;

/// Flag to toggle debug output.
static WAYLAND_DEBUG: AtomicBool = AtomicBool::new(false);

/// A handle to the object map internal to the library state.
///
/// This type is only used by code generated by `wayland-scanner`, and can not
/// be instantiated directly.
pub struct ProxyMap {
    map: Arc<Mutex<ObjectMap<self::proxy::ObjectMeta>>>,
    connection: Arc<Mutex<self::connection::Connection>>,
}

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

impl ProxyMap {
    pub(crate) fn make(
        map: Arc<Mutex<ObjectMap<self::proxy::ObjectMeta>>>,
        connection: Arc<Mutex<self::connection::Connection>>,
    ) -> ProxyMap {
        ProxyMap { map, connection }
    }

    /// Returns the Proxy corresponding to a given id
    pub fn get<I: Interface + AsRef<Proxy<I>> + From<Proxy<I>>>(
        &mut self,
        id: u32,
    ) -> Option<Proxy<I>> {
        ProxyInner::from_id(id, self.map.clone(), self.connection.clone()).map(|object| {
            debug_assert!(I::NAME == "<anonymous>" || object.is_interface::<I>());
            Proxy::wrap(object)
        })
    }

    /// Returns the Proxy corresponding to a given id, and create a dead one if none is found
    pub fn get_or_dead<I: Interface + AsRef<Proxy<I>> + From<Proxy<I>>>(
        &mut self,
        id: u32,
    ) -> Proxy<I> {
        self.get(id).unwrap_or_else(|| {
            Proxy::wrap(ProxyInner::dead::<I>(id, self.map.clone(), self.connection.clone()))
        })
    }

    /// Creates a new proxy for given id
    pub fn get_new<I: Interface + AsRef<Proxy<I>> + From<Proxy<I>>>(
        &mut self,
        id: u32,
    ) -> Option<Main<I>> {
        debug_assert!(self
            .map
            .lock()
            .unwrap()
            .find(id)
            .map(|obj| obj.is_interface::<I>())
            .unwrap_or(true));
        ProxyInner::from_id(id, self.map.clone(), self.connection.clone()).map(Main::wrap)
    }
}

/*
 * Dispatching logic
 */
#[allow(clippy::large_enum_variant)]
pub(crate) enum Dispatched {
    Yes,
    NoDispatch(Message, ProxyInner),
    BadMsg,
}

pub(crate) trait Dispatcher: Downcast + Send {
    fn dispatch(
        &mut self,
        msg: Message,
        proxy: ProxyInner,
        map: &mut ProxyMap,
        data: crate::DispatchData,
    ) -> Dispatched;
}

mod dispatcher_impl {
    // this mod has the sole purpose of silencing these `dead_code` warnings...
    #![allow(dead_code)]
    use super::Dispatcher;
    impl_downcast!(Dispatcher);
}

pub(crate) struct ImplDispatcher<
    I: Interface + AsRef<Proxy<I>> + From<Proxy<I>>,
    F: FnMut(I::Event, Main<I>, crate::DispatchData<'_>) + 'static,
> {
    _i: ::std::marker::PhantomData<&'static I>,
    implementation: F,
}

impl<I, F> Dispatcher for ImplDispatcher<I, F>
where
    I: Interface + AsRef<Proxy<I>> + From<Proxy<I>> + Sync,
    F: FnMut(I::Event, Main<I>, crate::DispatchData<'_>) + 'static + Send,
    I::Event: MessageGroup<Map = ProxyMap>,
{
    fn dispatch(
        &mut self,
        msg: Message,
        proxy: ProxyInner,
        map: &mut ProxyMap,
        data: crate::DispatchData,
    ) -> Dispatched {
        let opcode = msg.opcode as usize;

        if WAYLAND_DEBUG.load(Ordering::Relaxed) {
            debug::print_dispatched_message(
                proxy.object.interface,
                proxy.id,
                proxy.object.events[opcode].name,
                &msg.args,
            );
        }

        let message = match I::Event::from_raw(msg, map) {
            Ok(v) => v,
            Err(()) => return Dispatched::BadMsg,
        };

        if message.since() > proxy.version() {
            eprintln!(
                "Received an event {} requiring version >= {} while proxy {}@{} is version {}.",
                proxy.object.events[opcode].name,
                message.since(),
                proxy.object.interface,
                proxy.id,
                proxy.version()
            );
            return Dispatched::BadMsg;
        }

        if message.is_destructor() {
            proxy.object.meta.alive.store(false, Ordering::Release);
            {
                // cleanup the map as appropriate
                let mut map = proxy.map.lock().unwrap();
                let server_destroyed = map
                    .with(proxy.id, |obj| {
                        obj.meta.client_destroyed = true;
                        obj.meta.server_destroyed
                    })
                    .unwrap_or(false);
                if server_destroyed {
                    map.remove(proxy.id);
                }
            }
        }

        (self.implementation)(message, Main::<I>::wrap(proxy), data);

        Dispatched::Yes
    }
}

pub(crate) fn make_dispatcher<I, E>(filter: Filter<E>) -> Arc<Mutex<dyn Dispatcher + Send>>
where
    I: Interface + AsRef<Proxy<I>> + From<Proxy<I>> + Sync,
    E: From<(Main<I>, I::Event)> + 'static,
    I::Event: MessageGroup<Map = ProxyMap>,
{
    let guard = ThreadGuard::new(filter);
    Arc::new(Mutex::new(ImplDispatcher {
        _i: ::std::marker::PhantomData,
        implementation: move |evt, proxy, data| guard.get().send((proxy, evt).into(), data),
    }))
}

pub(crate) fn default_dispatcher() -> Arc<Mutex<dyn Dispatcher + Send>> {
    struct DefaultDisp;
    impl Dispatcher for DefaultDisp {
        fn dispatch(
            &mut self,
            msg: Message,
            proxy: ProxyInner,
            _map: &mut ProxyMap,
            _data: crate::DispatchData,
        ) -> Dispatched {
            Dispatched::NoDispatch(msg, proxy)
        }
    }

    Arc::new(Mutex::new(DefaultDisp))
}
