use {
    crate::{
        client::{Client, ClientError},
        leaks::Tracker,
        object::{Object, Version},
        wire::{WpSecurityContextV1Id, wp_security_context_v1::*},
    },
    std::{
        cell::{Cell, RefCell},
        rc::Rc,
    },
    thiserror::Error,
    uapi::OwnedFd,
};

pub struct WpSecurityContextV1 {
    pub id: WpSecurityContextV1Id,
    pub client: Rc<Client>,
    pub tracker: Tracker<Self>,
    pub version: Version,
    pub listen_fd: Rc<OwnedFd>,
    pub close_fd: Rc<OwnedFd>,
    pub sandbox_engine: RefCell<Option<String>>,
    pub app_id: RefCell<Option<String>>,
    pub instance_id: RefCell<Option<String>>,
    pub committed: Cell<bool>,
}

impl WpSecurityContextV1 {
    fn check_committed(&self) -> Result<(), WpSecurityContextV1Error> {
        if self.committed.get() {
            return Err(WpSecurityContextV1Error::Committed);
        }
        Ok(())
    }
}

impl WpSecurityContextV1RequestHandler for WpSecurityContextV1 {
    type Error = WpSecurityContextV1Error;

    fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        self.client.remove_obj(self)?;
        Ok(())
    }

    fn set_sandbox_engine(
        &self,
        req: SetSandboxEngine<'_>,
        _slf: &Rc<Self>,
    ) -> Result<(), Self::Error> {
        self.check_committed()?;
        let val = &mut *self.sandbox_engine.borrow_mut();
        if val.is_some() {
            return Err(WpSecurityContextV1Error::EnginSet);
        }
        *val = Some(req.name.to_string());
        Ok(())
    }

    fn set_app_id(&self, req: SetAppId<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        self.check_committed()?;
        let val = &mut *self.app_id.borrow_mut();
        if val.is_some() {
            return Err(WpSecurityContextV1Error::AppSet);
        }
        *val = Some(req.app_id.to_string());
        Ok(())
    }

    fn set_instance_id(&self, req: SetInstanceId<'_>, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        self.check_committed()?;
        let val = &mut *self.instance_id.borrow_mut();
        if val.is_some() {
            return Err(WpSecurityContextV1Error::InstanceSet);
        }
        *val = Some(req.instance_id.to_string());
        Ok(())
    }

    fn commit(&self, _req: Commit, _slf: &Rc<Self>) -> Result<(), Self::Error> {
        self.check_committed()?;
        self.committed.set(true);
        self.client.state.security_context_acceptors.spawn(
            &self.client.state,
            self.sandbox_engine.take(),
            self.app_id.take(),
            self.instance_id.take(),
            &self.listen_fd,
            &self.close_fd,
            self.client.bounding_caps_for_children.get(),
        );
        Ok(())
    }
}

object_base! {
    self = WpSecurityContextV1;
    version = self.version;
}

impl Object for WpSecurityContextV1 {}

simple_add_obj!(WpSecurityContextV1);

#[derive(Debug, Error)]
pub enum WpSecurityContextV1Error {
    #[error(transparent)]
    ClientError(Box<ClientError>),
    #[error("The sandbox engine has already been set")]
    EnginSet,
    #[error("The app id has already been set")]
    AppSet,
    #[error("The instance id has already been set")]
    InstanceSet,
    #[error("The context has already been committed")]
    Committed,
}
efrom!(WpSecurityContextV1Error, ClientError);
