//! Server module for {{stinger.name}} IPC
//! 
//! This module is only available when the "server" feature is enabled.


/*
DO NOT MODIFY THIS FILE{# <-- Ignore this because you are editing the template file. #}.  It is automatically generated and changes will be over-written
on the next generation.

This is the Server for the {{stinger.name}} interface.
*/

use mqttier::{MqttierClient{%if stinger.methods|length > 0 or stinger.properties|length > 0 %}, ReceivedMessage{%endif%}, PublishResult};

#[allow(unused_imports)]
use crate::payloads::{MethodReturnCode, *};
{%if stinger.methods|length > 0 %}
use std::any::Any;
use async_trait::async_trait;
use tokio::sync::Mutex as AsyncMutex;
use std::sync::{Arc, Mutex};
{%endif%}
{%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
use serde_json;
use tokio::sync::{mpsc{%if stinger.properties|length > 0 %}, watch{%endif%}};
{%endif%}
use tokio::task::JoinError;
use std::future::Future;
use std::pin::Pin;
type SentMessageFuture = Pin<Box<dyn Future<Output = Result<(), MethodReturnCode>> + Send>>;
#[cfg(feature = "server")]
#[allow(unused_imports)]
use tracing::{debug, info, warn, error};

{%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
/// This struct is used to store all the MQTTv5 subscription ids
/// for the subscriptions the client will make.
#[derive(Clone, Debug)]
struct {{stinger.name | UpperCamelCase }}ServerSubscriptionIds {
    {%for method_name, method in stinger.methods.items()-%}
    {{method_name | snake_case}}_method_req: usize,
    {%endfor%}
    {%for prop_name, prop in stinger.properties.items()-%}{%if not prop.read_only %}
    {{prop_name | snake_case}}_property_update: usize,
    {%endif%}{%endfor%}
}

{%if stinger.properties | length > 0%}
#[derive(Clone)]
struct {{stinger.name | UpperCamelCase}}Properties {
    {%for prop_name, prop in stinger.properties.items()-%}
    {{prop_name | snake_case}}_topic: Arc<String>,
    {{prop_name | snake_case}}: Arc<AsyncMutex<Option<{{prop_name|UpperCamelCase}}Property>>>,
    {{prop_name | snake_case}}_tx_channel: watch::Sender<Option<{%if prop.arg_list|length == 1%}{{prop.arg_list[0].rust_type}}{%else%}{{prop_name|UpperCamelCase}}Property{%endif%}>>,
    {%-endfor%}
}

{%endif%}
{%endif%}{# any methods or properties #}
#[derive(Clone)]
pub struct {{stinger.rust.server_struct_name}} {
    mqttier_client: MqttierClient,
    {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
    /// Temporarily holds the receiver for the MPSC channel.  The Receiver will be moved
    /// to a process loop when it is needed.  MQTT messages will be received with this.
    msg_streamer_rx: Arc<Mutex<Option<mpsc::Receiver<ReceivedMessage>>>>,

    /// The Sender side of MQTT messages that are received from the broker.  This tx
    /// side is cloned for each subscription made.
    #[allow(dead_code)]
    msg_streamer_tx: mpsc::Sender<ReceivedMessage>,
    {%endif%}
    {%if stinger.methods|length > 0 %}
    /// Struct contains all the method handlers.
    method_handlers: Arc<AsyncMutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>,
    {%endif%}
    {%if stinger.properties|length > 0%}
    /// Struct contains all the properties.
    properties: {{stinger.name | UpperCamelCase}}Properties,
    {%endif%}
    {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
    /// Subscription IDs for all the subscriptions this makes.
    subscription_ids: {{stinger.name | UpperCamelCase }}ServerSubscriptionIds,
    {%endif%}

    /// Copy of MQTT Client ID
    #[allow(dead_code)]
    pub client_id: String,

    pub instance_id: String,
}

impl {{stinger.rust.server_struct_name}} {
    pub async fn new(connection: &mut MqttierClient{%if stinger.methods|length > 0%}, method_handlers: Arc<AsyncMutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>> {%endif%}, instance_id: String) -> Self {
        {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
        // Create a channel for messages to get from the MqttierClient object to this {{stinger.rust.server_struct_name}} object.
        // The Connection object uses a clone of the tx side of the channel.
        let (message_received_tx, message_received_rx) = mpsc::channel::<ReceivedMessage>(64);

        // Create method handler struct
        {%for method_name, method in stinger.methods.items()-%}
        let subscription_id_{{method_name | snake_case}}_method_req = connection.subscribe(format!("{{method.topic}}", instance_id), 2, message_received_tx.clone()).await;
        let subscription_id_{{method_name | snake_case}}_method_req = subscription_id_{{method_name | snake_case}}_method_req.unwrap_or_else(|_| usize::MAX);

        {%endfor%}
        {%for prop_name, prop in stinger.properties.items()-%}{%if not prop.read_only %}
        let subscription_id_{{prop_name | snake_case}}_property_update = connection.subscribe(format!("{{prop.update_topic}}", instance_id), 1, message_received_tx.clone()).await;
        let subscription_id_{{prop_name | snake_case}}_property_update = subscription_id_{{prop_name | snake_case}}_property_update.unwrap_or_else(|_| usize::MAX);

        {%else-%}
        {%endif%}{%endfor%}
        
        // Create structure for subscription ids.
        let sub_ids = {{stinger.name | UpperCamelCase }}ServerSubscriptionIds {
            {%for method_name, method in stinger.methods.items()-%}
            {{method_name | snake_case}}_method_req: subscription_id_{{method_name | snake_case}}_method_req,
            {%endfor%}
            {%for prop_name, prop in stinger.properties.items()-%}{%if not prop.read_only -%}
            {{prop_name | snake_case}}_property_update: subscription_id_{{prop_name | snake_case}}_property_update,
            {%endif%}{%endfor%}
        };
        {%endif%}

        {%if stinger.properties|length > 0%}
        let property_values = {{stinger.name | UpperCamelCase}}Properties {
        {%-for prop_name, prop in stinger.properties.items()%}
            {{prop_name | snake_case}}_topic: Arc::new(format!("{{prop.value_topic}}", instance_id)),
            {{prop_name | snake_case}}: Arc::new(AsyncMutex::new(None)),
            {{prop_name | snake_case}}_tx_channel: watch::channel(None).0,

        {%-endfor%}
        };
        {%endif%}



        {{stinger.rust.server_struct_name}} {
            mqttier_client: connection.clone(),
            {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
            msg_streamer_rx: Arc::new(Mutex::new(Some(message_received_rx))),
            msg_streamer_tx: message_received_tx,{%if stinger.methods | length > 0 %}
            method_handlers: method_handlers,{%endif%}{%if stinger.properties | length > 0 %}
            properties: property_values,{%endif%}
            subscription_ids: sub_ids,
            {%endif%}
            client_id: connection.client_id.to_string(),
            instance_id,
        }
    }

    /// Converts a oneshot receiver for the publish result into a Future that resolves to
    pub async fn oneshot_to_future(publish_oneshot: tokio::sync::oneshot::Receiver<PublishResult>) -> SentMessageFuture {
        Box::pin(async move {
            let publish_result = publish_oneshot.await;
            match publish_result {
                Ok(PublishResult::Acknowledged(_))
                | Ok(PublishResult::Completed(_))
                | Ok(PublishResult::Sent(_)) => Ok(()),

                Ok(PublishResult::TimedOut) => Err(MethodReturnCode::Timeout(
                    "Timed out publishing signal".to_string(),
                )),

                Ok(PublishResult::SerializationError(s)) => {
                    Err(MethodReturnCode::ServerSerializationError(s))
                }

                Ok(PublishResult::Error(s)) => Err(MethodReturnCode::TransportError(s)),

                Err(_) => Err(MethodReturnCode::UnknownError(
                    "Error publishing signal".to_string(),
                )),
            }
        })
    }

    pub async fn wrap_return_code_in_future(rc: MethodReturnCode) -> SentMessageFuture {
        Box::pin(async move {
            match rc {
                MethodReturnCode::Success => Ok(()),
                _ => Err(rc),
            }
        })
    }
    {%if stinger.methods | length > 0 or stinger.properties | length > 0 %}
    /// Publishes an error response to the given response topic with the given correlation data.
    async fn publish_error_response(publisher: MqttierClient, response_topic: Option<String>, correlation_data: Option<Vec<u8>>, err: &MethodReturnCode) {
        if let Some(resp_topic) = response_topic {
            let corr_data = correlation_data.unwrap_or_default();
            let (return_code, debug_message) = err.to_code();
            let _ = publisher.publish_error_response(resp_topic, debug_message.unwrap_or_default(), corr_data, return_code).await;
        } else {
            info!("No response topic found in message properties; cannot send error response.");
        }
    }
    {%endif-%}
    {%for sig_name, sig in stinger.signals.items()-%}
    /// Emits the {{sig_name}} signal with the given arguments.
    pub async fn emit_{{sig_name|snake_case}}(&mut self, {%for arg in sig.arg_list%}{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> SentMessageFuture {
        let data = {{sig_name|UpperCamelCase}}SignalPayload {
            {%for arg in sig.arg_list%}
        {{arg.name|snake_case}}: {{arg.name|snake_case}},
            {%endfor%}
        };
        let published_oneshot = self.mqttier_client.publish_structure(format!("{{sig.topic}}", self.instance_id), &data).await;
        {{stinger.rust.server_struct_name}}::oneshot_to_future(published_oneshot).await
    }
    {%endfor%}
    {%for method_name, method in stinger.methods.items()%}
    /// Handles a request message for the {{method_name}} method.
    async fn handle_{{method_name|snake_case}}_request(publisher: MqttierClient, handlers: Arc<AsyncMutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>, msg: ReceivedMessage) {
        let opt_corr_data = msg.correlation_data;
        let opt_resp_topic = msg.response_topic;
        {%if method.arg_list | length > 0 -%}
        let payload_vec = msg.payload;
        let payload_obj = serde_json::from_slice::<{{method_name | UpperCamelCase}}RequestObject>(&payload_vec);
        if payload_obj.is_err() {
            error!("Error deserializing request payload for {{method_name}}: {:?}", payload_obj.err());
            {{stinger.rust.server_struct_name}}::publish_error_response(publisher, opt_resp_topic, opt_corr_data, &MethodReturnCode::ServerDeserializationError("Failed to deserialize request payload".to_string())).await;
            return;
        }
        // Unwrap is OK here because we just checked for error.
        let payload = payload_obj.unwrap();
        {%endif%}{# has arg_list #} 
        // call the method handler
        let rc: Result<{{method.return_value_rust_type}}, MethodReturnCode> = {
            let handler_guard = handlers.lock().await;
            handler_guard.handle_{{method_name|snake_case}}({%for arg in method.arg_list%}payload.{{arg.name|snake_case}}{%if not loop.last%}, {%endif%}{%endfor%}).await
        };

        if let Some(resp_topic) = opt_resp_topic {
            let corr_data = opt_corr_data.unwrap_or_default();
            match rc {
                Ok({%if method.return_arg_list|length == 0 %}_{%endif%}retval) => {
                    {% if method.return_arg_list|length == 0 %}
                    let empty_resp = {{method.return_value_name | UpperCamelCase}}{};
                    let _fut_publish_result = publisher.publish_response(resp_topic, &empty_resp, corr_data).await;
                    {%elif method.return_arg_list|length == 1 %}
                    let resp_obj = {{method.return_value_name | UpperCamelCase}} {
                        {{method.return_arg_list[0].name | snake_case}}: retval,
                    };
                    let _fut_publish_result = publisher.publish_response(resp_topic, &resp_obj, corr_data).await;
                    {% else %}
                    let _fut_publish_result = publisher.publish_response(resp_topic, &retval, corr_data).await;
                    {% endif %}
                }
                Err(err) => {
                    info!("Error occurred while handling {{method_name}}: {:?}", &err);
                    {{stinger.rust.server_struct_name}}::publish_error_response(publisher, Some(resp_topic), Some(corr_data), &err).await;
                }
            }
        } else {
            // Without a response topic, we cannot send a response.
            info!("No response topic provided, so no publishing response to `{{method_name}}`.");
        }
    }
    {%endfor%}

    {%-for prop_name, prop in stinger.properties.items()%}
    async fn publish_{{prop_name}}_value(publisher: MqttierClient, topic: String, data: {{prop_name|UpperCamelCase}}Property) -> SentMessageFuture
    {
        let published_oneshot = publisher.publish_state(topic, &data, 1).await;
        {{stinger.rust.server_struct_name}}::oneshot_to_future(published_oneshot).await
    }
    {%if not prop.read_only %}
    /// This is called because of an MQTT request to update the property value.
    /// It updates the local value, notifies any watchers, and publishes the new value.
    /// If there is an error, it can publish back if a response topic was provided.
    async fn update_{{prop_name}}_value(publisher: MqttierClient, topic: Arc<String>, property_pointer: Arc<AsyncMutex<Option<{{prop_name|UpperCamelCase}}Property>>>, watch_sender: watch::Sender<Option<{%if prop.arg_list|length == 1%}{{prop.arg_list[0].rust_type}}{%else%}{{prop_name|UpperCamelCase}}Property{%endif%}>>, msg: ReceivedMessage) -> SentMessageFuture
    {
        let payload_str = String::from_utf8_lossy(&msg.payload).to_string();

        let new_property_structure: {{prop_name | UpperCamelCase}}Property = {
            match serde_json::from_str(&payload_str) {
                Ok(obj) => obj,
                Err(e) => {
                    error!("Failed to parse JSON received over MQTT to update '{{prop_name}}' property: {:?}", e);
                    return {{stinger.rust.server_struct_name}}::wrap_return_code_in_future(MethodReturnCode::ServerDeserializationError("Failed to deserialize property '{{prop_name}}' payload".to_string())).await;
                }
            }
        };

        let mut property_guard = property_pointer.lock().await;
        *property_guard = Some(new_property_structure.clone());
        drop(property_guard);

        let topic2: String = topic.as_ref().clone();
        let data_to_send_to_watchers = {%if prop.arg_list|length == 1%}new_property_structure.{{prop.arg_list[0].name|snake_case}}.clone(){%else%}new_property_structure.clone(){%endif%};
        match watch_sender.send(Some(data_to_send_to_watchers)) {
            Ok(_) => {}
            Err(e) => {
                error!("Failed to notify local watchers for '{{prop_name}}' property: {:?}", e);
            }
        };
        {{stinger.rust.server_struct_name}}::publish_{{prop_name}}_value(publisher, topic2, new_property_structure).await
    }

    pub async fn watch_{{prop_name}}(&self) -> watch::Receiver<Option<{{prop.rust_type}}>> {
        self.properties.{{prop_name}}_tx_channel.subscribe()
    }
    {%endif%}{# not read only property #}

    /// Sets the value{%if prop.arg_list|length > 1%}s{%endif%} of the {{prop_name}} property.
    /// As a consequence, it notifies any watchers and publishes the new value to MQTT.
    pub async fn set_{{prop_name}}(&mut self, data: {{prop.rust_type}}) -> SentMessageFuture {
        let prop = self.properties.{{prop_name|snake_case}}.clone();
        
        let new_prop_obj = {% if prop.arg_list | length == 1 %}{{prop_name|UpperCamelCase}}Property {
            {{prop.arg_list[0].name}}: data.clone(),
        }{% else %}data.clone(){% endif %};

        // Set the server's copy of the property value{%if prop.arg_list|length > 1%}s{%endif%}.
        let mut property_data_guard = prop.lock().await;
        *property_data_guard = Some(new_prop_obj.clone());
        let property_obj = property_data_guard.clone();
        drop(property_data_guard);

        // Notify watchers of the new property value.
        let data_to_send_to_watchers = {%if prop.arg_list|length == 1%}Some(data.clone()){%else%}property_obj.clone(){%endif%};
        let send_result = self.properties.{{prop_name|snake_case}}_tx_channel.send_if_modified(|current_data| {
            if current_data != &data_to_send_to_watchers {
                *current_data = data_to_send_to_watchers;
                true
            } else {
                false
            }
        });

        // Send value to MQTT if it has changed.
        if !send_result {
            debug!("Property '{{prop_name}}' value not changed, so not notifying watchers.");
            return {{stinger.rust.server_struct_name}}::wrap_return_code_in_future(MethodReturnCode::Success).await;
        } else {
            if let Some(prop_obj) = property_obj {
                let publisher2 = self.mqttier_client.clone();
                let topic2 = self.properties.{{prop_name}}_topic.as_ref().clone();
                {{stinger.rust.server_struct_name}}::publish_{{prop_name}}_value(publisher2, topic2, prop_obj).await
            } else {
                {{stinger.rust.server_struct_name}}::wrap_return_code_in_future(MethodReturnCode::UnknownError("Could not find property object".to_string())).await
            }
        }
    }
    {%endfor%}

    /// Starts the tasks that process messages received.
    /// In the task, it loops over messages received from the rx side of the message_receiver channel.
    /// Based on the subscription id of the received message, it will call a function to handle the
    /// received message.
    pub async fn run_loop(&mut self) -> Result<(), JoinError> {
        // Make sure the MqttierClient is connected and running.
        let _ = self.mqttier_client.run_loop().await;

        {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
        // Take ownership of the RX channel that receives MQTT messages.  This will be moved into the loop_task.
        let mut message_receiver = {
            self.msg_streamer_rx.lock().unwrap().take().expect("msg_streamer_rx should be Some")
        };

        {%if stinger.methods|length > 0 -%}
        let method_handlers = self.method_handlers.clone();
        let _ = self.method_handlers.lock().await.initialize(self.clone()).await;
        {%endif-%}

        let sub_ids = self.subscription_ids.clone();

        {%-if stinger.methods|length > 0 %}
        let publisher = self.mqttier_client.clone();
        {%endif%}

        {%-if stinger.properties | length > 0 %}
        let properties = self.properties.clone();
        {%endif%}

        let interface_publisher = self.mqttier_client.clone();
        let instance_id = self.instance_id.clone();
        tokio::spawn(async move {
            let mut interval = tokio::time::interval(std::time::Duration::from_secs(120));
            loop {
                interval.tick().await;
                let info = crate::interface::InterfaceInfo::new()
                    .interface_name("{{stinger.name}}".to_string())
                    .title("{{stinger.title}}".to_string())
                    .version("{{stinger.version}}".to_string())
                    .instance(instance_id.clone())
                    .connection_topic(format!("client/{}/online", interface_publisher.client_id))
                    .build();
                let _ = interface_publisher.publish_status(format!("{{stinger.interface_info_topic}}", instance_id), &info, 150).await;
            }
        });
        let loop_task = tokio::spawn(async move {
            while let Some(msg) = message_receiver.recv().await {
                {%if stinger.properties | length > 0 %}
                let opt_resp_topic = msg.response_topic.clone();
                let opt_corr_data = msg.correlation_data.clone();
                {%endif%}
                
                {%-for method_name, method in stinger.methods.items()%}
                {%if not loop.first%}else {%endif%}if msg.subscription_id == sub_ids.{{method_name | snake_case}}_method_req {
                    {{stinger.rust.server_struct_name}}::handle_{{method_name|snake_case}}_request(publisher.clone(), method_handlers.clone(), msg).await;
                }
                {%-endfor%}{# methods #}
                {%if stinger.properties_rw | length > 0 %}
                else {
                    let update_prop_future = {
                        {%-for prop_name, prop in stinger.properties_rw.items()%}
                            {%if not loop.first %}else {%endif%}if msg.subscription_id == sub_ids.{{prop_name | snake_case}}_property_update {
                                {{stinger.rust.server_struct_name}}::update_{{prop_name | snake_case}}_value(publisher.clone(), properties.{{prop_name | snake_case}}_topic.clone(), properties.{{prop_name | snake_case}}.clone(), properties.{{prop_name | snake_case}}_tx_channel.clone(), msg).await
                            }
                        {%endfor%}{# properties #}
                            else {
                                {{stinger.rust.server_struct_name}}::wrap_return_code_in_future(MethodReturnCode::NotImplemented("Could not find a property matching the request".to_string())).await
                            }
                    };
                    match update_prop_future.await {
                        Ok(_) => debug!("Successfully processed update  property"),
                        Err(e) => {
                            error!("Error processing update to '{{prop_name}}' property: {:?}", e);
                            if let Some(resp_topic) = opt_resp_topic {
                                {{stinger.rust.server_struct_name}}::publish_error_response(publisher.clone(), Some(resp_topic), opt_corr_data, &e).await;
                            } else {
                                warn!("No response topic found in message properties; cannot send error response.");
                            }
                        }
                    }
                }
                {%endif%}{# any read-write properties #}
            }
        });
        let _ = tokio::join!(loop_task);
        {%endif%} {# any methods or properties #}
        
        warn!("Server receive loop completed. Exiting run_loop.");
        Ok(())
    }

}
{%if stinger.methods|length > 0 %}
#[async_trait]
pub trait {{stinger.name | UpperCamelCase }}MethodHandlers: Send + Sync {

    async fn initialize(&mut self, server: {{stinger.rust.server_struct_name}}) -> Result<(), MethodReturnCode>;

    {%for method_name, method in stinger.methods.items()-%}
    /// Pointer to a function to handle the {{method_name}} method request.
    async fn handle_{{method_name|snake_case}}(&self, {%for arg in method.arg_list%}{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> Result<{{method.return_value_rust_type}}, MethodReturnCode>;
    
    {%endfor%}

    fn as_any(&self) -> &dyn Any;
}
{%endif%}{# methods #}