use super::{data_writer::DataWriterAsync, domain_participant::DomainParticipantAsync};
use crate::{
    dcps::{
        actor::Actor,
        domain_participant_mail::{DcpsDomainParticipantMail, PublisherServiceMail},
        listeners::{
            data_writer_listener::DcpsDataWriterListener, publisher_listener::DcpsPublisherListener,
        },
        status_condition::DcpsStatusCondition,
    },
    dds_async::topic_description::TopicDescriptionAsync,
    infrastructure::{
        error::DdsResult,
        instance::InstanceHandle,
        qos::{DataWriterQos, PublisherQos, QosKind, TopicQos},
        status::StatusKind,
        time::Duration,
    },
    publication::{
        data_writer_listener::DataWriterListener, publisher_listener::PublisherListener,
    },
    runtime::{ChannelSend, DdsRuntime, OneshotReceive},
};
use alloc::vec::Vec;

/// Async version of [`Publisher`](crate::publication::publisher::Publisher).
pub struct PublisherAsync<R: DdsRuntime> {
    handle: InstanceHandle,
    participant: DomainParticipantAsync<R>,
}

impl<R: DdsRuntime> Clone for PublisherAsync<R> {
    fn clone(&self) -> Self {
        Self {
            handle: self.handle,
            participant: self.participant.clone(),
        }
    }
}

impl<R: DdsRuntime> PublisherAsync<R> {
    pub(crate) fn new(handle: InstanceHandle, participant: DomainParticipantAsync<R>) -> Self {
        Self {
            handle,
            participant,
        }
    }

    pub(crate) fn participant_address(&self) -> &R::ChannelSender<DcpsDomainParticipantMail<R>> {
        self.participant.participant_address()
    }
}

impl<R: DdsRuntime> PublisherAsync<R> {
    /// Async version of [`create_datawriter`](crate::publication::publisher::Publisher::create_datawriter).
    #[tracing::instrument(skip(self, a_topic, a_listener))]
    pub async fn create_datawriter<Foo>(
        &self,
        a_topic: &TopicDescriptionAsync<R>,
        qos: QosKind<DataWriterQos>,
        a_listener: Option<impl DataWriterListener<R, Foo> + Send + 'static>,
        mask: &[StatusKind],
    ) -> DdsResult<DataWriterAsync<R, Foo>> {
        let topic_name = a_topic.get_name();
        let status_condition = Actor::spawn(
            DcpsStatusCondition::default(),
            self.participant.spawner_handle(),
        );
        let writer_status_condition_address = status_condition.address();
        let listener_sender =
            a_listener.map(|l| DcpsDataWriterListener::spawn(l, self.participant.spawner_handle()));
        let (reply_sender, reply_receiver) = R::oneshot();
        self.participant_address()
            .send(DcpsDomainParticipantMail::Publisher(
                PublisherServiceMail::CreateDataWriter {
                    publisher_handle: self.handle,
                    topic_name,
                    qos,
                    status_condition,
                    listener_sender,
                    mask: mask.to_vec(),
                    participant_address: self.participant_address().clone(),
                    reply_sender,
                },
            ))
            .await?;
        let guid = reply_receiver.receive().await??;

        Ok(DataWriterAsync::new(
            guid,
            writer_status_condition_address,
            self.clone(),
            a_topic.clone(),
        ))
    }

    /// Async version of [`delete_datawriter`](crate::publication::publisher::Publisher::delete_datawriter).
    #[tracing::instrument(skip(self, a_datawriter))]
    pub async fn delete_datawriter<Foo>(
        &self,
        a_datawriter: &DataWriterAsync<R, Foo>,
    ) -> DdsResult<()> {
        let (reply_sender, reply_receiver) = R::oneshot();
        self.participant_address()
            .send(DcpsDomainParticipantMail::Publisher(
                PublisherServiceMail::DeleteDataWriter {
                    publisher_handle: self.handle,
                    datawriter_handle: a_datawriter.get_instance_handle().await,
                    reply_sender,
                },
            ))
            .await?;
        reply_receiver.receive().await?
    }

    /// Async version of [`delete_datawriter`](crate::publication::publisher::Publisher::lookup_datawriter).
    #[tracing::instrument(skip(self))]
    pub async fn lookup_datawriter<Foo>(
        &self,
        topic_name: &str,
    ) -> DdsResult<Option<DataWriterAsync<R, Foo>>> {
        todo!()
    }

    /// Async version of [`delete_datawriter`](crate::publication::publisher::Publisher::suspend_publications).
    #[tracing::instrument(skip(self))]
    pub async fn suspend_publications(&self) -> DdsResult<()> {
        todo!()
    }

    /// Async version of [`delete_datawriter`](crate::publication::publisher::Publisher::resume_publications).
    #[tracing::instrument(skip(self))]
    pub async fn resume_publications(&self) -> DdsResult<()> {
        todo!()
    }

    /// Async version of [`delete_datawriter`](crate::publication::publisher::Publisher::begin_coherent_changes).
    #[tracing::instrument(skip(self))]
    pub async fn begin_coherent_changes(&self) -> DdsResult<()> {
        todo!()
    }

    /// Async version of [`delete_datawriter`](crate::publication::publisher::Publisher::end_coherent_changes).
    #[tracing::instrument(skip(self))]
    pub async fn end_coherent_changes(&self) -> DdsResult<()> {
        todo!()
    }

    /// Async version of [`delete_datawriter`](crate::publication::publisher::Publisher::wait_for_acknowledgments).
    #[tracing::instrument(skip(self))]
    pub async fn wait_for_acknowledgments(&self, _max_wait: Duration) -> DdsResult<()> {
        todo!()
    }

    /// Async version of [`get_participant`](crate::publication::publisher::Publisher::get_participant).
    #[tracing::instrument(skip(self))]
    pub fn get_participant(&self) -> DomainParticipantAsync<R> {
        self.participant.clone()
    }

    /// Async version of [`delete_contained_entities`](crate::publication::publisher::Publisher::delete_contained_entities).
    #[tracing::instrument(skip(self))]
    pub async fn delete_contained_entities(&self) -> DdsResult<()> {
        todo!()
    }

    /// Async version of [`set_default_datawriter_qos`](crate::publication::publisher::Publisher::set_default_datawriter_qos).
    #[tracing::instrument(skip(self))]
    pub async fn set_default_datawriter_qos(&self, qos: QosKind<DataWriterQos>) -> DdsResult<()> {
        let (reply_sender, reply_receiver) = R::oneshot();
        self.participant_address()
            .send(DcpsDomainParticipantMail::Publisher(
                PublisherServiceMail::SetDefaultDataWriterQos {
                    publisher_handle: self.handle,
                    qos,
                    reply_sender,
                },
            ))
            .await?;
        reply_receiver.receive().await?
    }

    /// Async version of [`get_default_datawriter_qos`](crate::publication::publisher::Publisher::get_default_datawriter_qos).
    #[tracing::instrument(skip(self))]
    pub async fn get_default_datawriter_qos(&self) -> DdsResult<DataWriterQos> {
        let (reply_sender, reply_receiver) = R::oneshot();
        self.participant_address()
            .send(DcpsDomainParticipantMail::Publisher(
                PublisherServiceMail::GetDefaultDataWriterQos {
                    publisher_handle: self.handle,
                    reply_sender,
                },
            ))
            .await?;
        reply_receiver.receive().await?
    }

    /// Async version of [`copy_from_topic_qos`](crate::publication::publisher::Publisher::copy_from_topic_qos).
    #[tracing::instrument(skip(self))]
    pub async fn copy_from_topic_qos(
        &self,
        _a_datawriter_qos: &mut DataWriterQos,
        _a_topic_qos: &TopicQos,
    ) -> DdsResult<()> {
        todo!()
    }
}

impl<R: DdsRuntime> PublisherAsync<R> {
    /// Async version of [`set_qos`](crate::publication::publisher::Publisher::set_qos).
    #[tracing::instrument(skip(self))]
    pub async fn set_qos(&self, qos: QosKind<PublisherQos>) -> DdsResult<()> {
        let (reply_sender, reply_receiver) = R::oneshot();
        self.participant_address()
            .send(DcpsDomainParticipantMail::Publisher(
                PublisherServiceMail::SetPublisherQos {
                    publisher_handle: self.handle,
                    qos,
                    reply_sender,
                },
            ))
            .await?;
        reply_receiver.receive().await?
    }

    /// Async version of [`get_qos`](crate::publication::publisher::Publisher::get_qos).
    #[tracing::instrument(skip(self))]
    pub async fn get_qos(&self) -> DdsResult<PublisherQos> {
        let (reply_sender, reply_receiver) = R::oneshot();
        self.participant_address()
            .send(DcpsDomainParticipantMail::Publisher(
                PublisherServiceMail::GetPublisherQos {
                    publisher_handle: self.handle,
                    reply_sender,
                },
            ))
            .await?;
        reply_receiver.receive().await?
    }

    /// Async version of [`set_listener`](crate::publication::publisher::Publisher::set_listener).
    #[tracing::instrument(skip(self, a_listener))]
    pub async fn set_listener(
        &self,
        a_listener: Option<impl PublisherListener<R> + Send + 'static>,
        mask: &[StatusKind],
    ) -> DdsResult<()> {
        let (reply_sender, reply_receiver) = R::oneshot();
        let listener_sender =
            a_listener.map(|l| DcpsPublisherListener::spawn(l, self.participant.spawner_handle()));
        self.participant_address()
            .send(DcpsDomainParticipantMail::Publisher(
                PublisherServiceMail::SetPublisherListener {
                    publisher_handle: self.handle,
                    listener_sender,
                    mask: mask.to_vec(),
                    reply_sender,
                },
            ))
            .await?;
        reply_receiver.receive().await?
    }

    /// Async version of [`get_status_changes`](crate::publication::publisher::Publisher::get_status_changes).
    #[tracing::instrument(skip(self))]
    pub async fn get_status_changes(&self) -> DdsResult<Vec<StatusKind>> {
        todo!()
    }

    /// Async version of [`enable`](crate::publication::publisher::Publisher::enable).
    #[tracing::instrument(skip(self))]
    pub async fn enable(&self) -> DdsResult<()> {
        todo!()
    }

    /// Async version of [`get_instance_handle`](crate::publication::publisher::Publisher::get_instance_handle).
    #[tracing::instrument(skip(self))]
    pub async fn get_instance_handle(&self) -> InstanceHandle {
        self.handle
    }
}
