//! This module provides a high-level access to the server-side of Tango.

pub mod macros;

use std::borrow::{Borrow, Cow};
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use std::fmt::{self, Debug};
use std::sync::{Arc, Weak};
use std::pin::Pin;

use tokio::sync::{RwLock, RwLockMappedWriteGuard, RwLockReadGuard, RwLockWriteGuard};
use async_trait::async_trait;

use crate::backend::{DevBackend, DevCommandBackend, DevGetAttrInfoBackend, DevGetCommandInfoBackend, DevReadAttrBackend, DevWriteAttrBackend};
use crate::error::{Error, ErrorKind, Result, reason};
use crate::dev::{AttrWrite, DevProxy, DevState};
use crate::data::{Data, DataKind};


/// This represent the server-side context of a device, this handle allows manipulating
/// the attributes, commands and properties of a device.
#[derive(Debug)]
pub struct DevCtx<Model> {
    /// Weak back reference to the context itself, because it only exists in an Arc.
    this: Weak<DevCtx<Model>>,
    /// The name of the device.
    name: Arc<str>,
    /// The admin device name.
    admin_dev_name: Arc<str>,
    /// The model is inside a R/W lock in order to be able to provide some kind of support
    /// for serialization, when requested by the class/attr/command definition. We are
    /// using a read/write lock because we also want to allow models to define their own
    /// serialization model.
    model: RwLock<Option<Model>>,
    /// The interface of the device.
    iface: RwLock<DefInterface<Model>>,
}

/// The interface, mutex-protected, interface of the device.
#[derive(Debug)]
struct DefInterface<Model> {
    /// The set of commands.
    commands: HashSet<CommandDefSlot<Model>>,
}

impl<Model> DevCtx<Model> {

    #[inline]
    fn this(&self) -> Arc<Self> {
        // If this function is called, this means that the top-level caller have a 
        // Arc<Self> because we only ever create backends inside Arc<...>, so we can
        // safely unwrap the upgrade.
        self.this.upgrade().unwrap()
    }

    /// Wait for a shared access to the model, returning the lock guard.
    pub async fn read_model(&self) -> RwLockReadGuard<'_, Model> {
        RwLockReadGuard::map(self.model.read().await,
            |model| model.as_ref().expect("The model is not yet readable as you are in the initializer"))
    }

    /// Wait for an exclusive access to the model, returning the lock guard.
    pub async fn write_model(&self) -> RwLockMappedWriteGuard<'_, Model> {
        RwLockWriteGuard::map(self.model.write().await,
            |model| model.as_mut().expect("The model is not yet writable as you are in the initializer"))
    }
    
    /// Get the name of the device, in the domain/family/member format.
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn proxy(&self) -> DevProxy {
        todo!()
    }

    pub fn add_command(&self, def: CommandDef<Model>) {
        let mut iface = self.iface.blocking_write();
        iface.commands.insert(CommandDefSlot(def));
    }

}

/// This implementations allows this device to acts very much like a 
#[async_trait]
impl<Model: Debug + Send + Sync> DevBackend for DevCtx<Model> {
    
    async fn name(&self) -> Result<Arc<str>> {
        Ok(self.name.clone())
    }

    async fn admin_dev_name(&self) -> Result<Arc<str>> {
        Ok(self.admin_dev_name.clone())
    }

    async fn description(&self) -> Result<Arc<str>> {
        todo!()
    }

    async fn ping(&self) -> Result<()> {
        Ok(())
    }

    async fn state(&self) -> Result<DevState> {
        todo!()
    }
    
    async fn status(&self) -> Result<String> {
        todo!()
    }

    async fn black_box(&self, max: Option<usize>) -> Result<Vec<String>> {
        todo!()
    }

    async fn command(&self, name: &str, data: Data<'_>) -> Result<Box<dyn DevCommandBackend>> {

        let iface = self.iface.read().await;
        let command = iface.commands.get(name)
            .ok_or_else(|| Error::new(ErrorKind::CommandNotFound)
                .with_append_description(name))?;

        // Later, we could have a pool for data, which will avoid some allocation 
        // overhead, therefore inside the exec function, any non-owned data could be made
        // owned __into__ the given output data, thus potentially optimizing if the data
        // was of the same owned type.
        let mut res = Data::Void;
        (command.0.exec)(self, name, data, &mut res).await?;

        #[derive(Debug)]
        #[repr(transparent)]
        struct DevCommandFromData(Data<'static>);
        impl DevCommandBackend for DevCommandFromData {
            fn get(&self) -> Result<Data<'_>> {
                Ok(self.0.as_view().into_owned())
            }
        }

        Ok(Box::new(DevCommandFromData(res)))

    }

    async fn read_attr(&self, names: Option<&[&str]>) -> Result<Box<dyn DevReadAttrBackend>> {
        todo!()
    }

    async fn write_attr(&self, attrs: &[AttrWrite<'_, Data<'_>>]) -> Result<Box<dyn DevWriteAttrBackend>> {
        todo!()
    }

    async fn get_command_info(&self, names: Option<&[&str]>) -> Result<Box<dyn DevGetCommandInfoBackend>> {
        todo!()
    }

    async fn get_attr_info(&self, names: Option<&[&str]>) -> Result<Box<dyn DevGetAttrInfoBackend>> {
        todo!()
    }

}

/// Inner structure to hold a [`CommandDef`] inside a hashset.
#[derive(Debug)]
#[repr(transparent)]
struct CommandDefSlot<Model>(CommandDef<Model>);

impl<Model> Borrow<str> for CommandDefSlot<Model> {
    fn borrow(&self) -> &str {
        &self.0.name
    }
}

impl<Model> PartialEq for CommandDefSlot<Model> {
    fn eq(&self, other: &Self) -> bool {
        self.0.name == other.0.name
    }
}

impl<Model> Eq for CommandDefSlot<Model> { }

impl<Model> Hash for CommandDefSlot<Model> {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.name.hash(state);
    }
}

/// Represent the definition of a device with its handler and meta information. It's 
/// highly recommanded to use the [`dev_def`] macro to construct this type.
pub struct DevDef<Model> {
    class_name: Box<str>,
    init: for<'a> fn(&'a DevCtx<Model>) -> Pin<Box<dyn Future<Output = Result<Model>> + 'a>>,
    delete: for<'a> fn(&'a DevCtx<Model>) -> Pin<Box<dyn Future<Output = ()> + 'a>>,
}

/// Represent the definition of a command with its handler and meta information.
pub struct CommandDef<Model> {
    pub name: Cow<'static, str>,
    pub in_description: Cow<'static, str>,
    pub out_description: Cow<'static, str>,
    pub in_kind: DataKind,
    pub out_kind: DataKind,
    pub exec: for<'a> fn(ctx: &'a DevCtx<Model>, name: &'a str, input: Data<'a>, output: &'a mut Data<'static>) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>,
}

impl<Model> Debug for CommandDef<Model> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CommandDef")
            .field("name", &self.name)
            .field("in_description", &self.in_description)
            .field("out_description", &self.out_description)
            .field("in_kind", &self.in_kind)
            .field("out_kind", &self.out_kind)
            .finish()
    }
}


#[cfg(test)]
mod test {

    use crate::dev::AttrWrite;

    use super::*;

    pub struct Foo {

    }

    impl Foo {

        // const DEF: DevDef<Self> = dev_def! {
        //     model: Foo,
        //     init: Foo::init,
        // };

        // const CMD_DEF: CommandDef<Self> = command_def! {
        //     name: "ExclusiveCommand",
        //     in_description: "This command takes nothing as input",
        //     out_description: "This command outputs nothing",
        //     exec(mut): Foo::exclusive_command,
        // };

        async fn init(ctx: &DevCtx<Self>) -> Result<Self> {

            ctx.add_command(crate::command_def! {
                name: "ExclusiveCommand",
                in_description: "This command takes nothing as input",
                out_description: "This command outputs nothing",
                exec(mut): Foo::exclusive_command,
            });

            // ctx.add_command(command_def! {
            //     name: "SharedCommand",
            //     in_description: "This command takes nothing as input",
            //     out_description: "This command outputs nothing",
            //     exec(ref): Foo::shared_command,
            // });

            // ctx.add_attribute(attr_def! {
            //     name: "MyAttribute",
            //     description: "Blah blah",
            //     kind: Void,
            //     read: #[ctx] #[sync] Foo::read_my_attribute,
            //     write: #[full] Foo::write_my_attribute,
            // });

            Ok(Foo {

            })

        }

        async fn exclusive_command(&mut self, name: &str, data: ()) -> Result<&str> {
            todo!()
        }

        async fn shared_command(&self, data: ()) -> Result<()> {
            todo!()
        }

        async fn read_my_attribute(&self, ctx: &DevCtx<Self>) -> Result<u16> {
            Ok(65)
        }

        async fn write_my_attribute(&self, value: AttrWrite<'_, u16>) -> Result<()> {
            Ok(())
        }

    }

    pub fn main() {

        // tango::server::run(Foo::DEF);
        // tango::server::run((Foo::DEF,));

    }

}
