/*
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%}};

#[allow(unused_imports)]
use {{stinger.rust.common_package_name}}::payloads::{*, MethodResultCode};
{%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
use serde_json;
use std::sync::{Arc, Mutex};
use tokio::sync::mpsc;
{%endif%}
use tokio::task::JoinError;

{%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.methods|length > 0 %}
#[derive(Clone)]
struct {{stinger.name | UpperCamelCase }}ServerMethodHandlers {
    {%for method_name, method in stinger.methods.items()-%}
    /// Pointer to a function to handle the {{method_name}} method request.
    method_handler_for_{{method_name|snake_case}}: Arc<Mutex<Box<dyn Fn({%for arg in method.arg_list%}{{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%})->Result<{{method.return_value_rust_type}}, MethodResultCode> + Send>>>,
    {%endfor%}
}{%endif%}{# method handlers struct #}
{%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>,
    {%-if prop.arg_list | length > 1%}
    {{prop_name | snake_case}}: Arc<Mutex<Option<{{prop.rust_type}}>>>,
    {%-else%}
    {{prop_name | snake_case}}: Arc<Mutex<Option<{{prop.arg_list[0].rust_type}}>>>,
    {%endif%}
    {%-endfor%}
}
{%endif%}
{%endif%}{# any methods or properties #}

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: 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 handlers for the various methods.
    method_handlers: {{stinger.name | UpperCamelCase }}ServerMethodHandlers,{%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,
}

impl {{stinger.rust.server_struct_name}} {
    pub async fn new(connection: &mut MqttierClient) -> 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("{{method.topic}}".to_string(), 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("{{prop.update_topic}}".to_string(), 2, 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%}
        {%if stinger.methods | length > 0 %}
        // Create structure for method handlers.
        let method_handlers = {{stinger.name | UpperCamelCase }}ServerMethodHandlers {
            {%-for method_name, method in stinger.methods.items()-%}
            method_handler_for_{{method_name|snake_case}}: Arc::new(Mutex::new(Box::new( |{%for arg in method.arg_list-%}_{{loop.index}}{%if not loop.last%}, {%endif%}{%endfor%}| { Err(MethodResultCode::ServerError) } ))),
            {%endfor%}
        };
        {%endif%}{# method handlers #}
        
        // 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(String::from("{{prop.value_topic}}")),
            {%if prop.arg_list | length > 1 -%}
            {{prop_name | snake_case}}: Arc::new(Mutex::new(None)),
            {%else%}
            {{prop_name | snake_case}}: Arc::new(Mutex::new(None)),
            {%-endif%}
        {%-endfor%}
        };
        {%endif%}

        {{stinger.rust.server_struct_name}} {
            mqttier_client: connection.clone(),
            {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
            msg_streamer_rx: 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(),
        }
    }

    {%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%}) {
        let data = {{sig_name|UpperCamelCase}}SignalPayload {
            {%for arg in sig.arg_list%}
            {{arg.name}}: {{arg.name|snake_case}},
            {%endfor%}
        };
        let _ = self.mqttier_client.publish_state("{{sig.topic}}".to_string(), &data, 1).await;
    }
    {%endfor%}

    {%for method_name, method in stinger.methods.items()-%}
    /// Sets the function to be called when a request for the {{method_name}} method is received.
    pub fn set_method_handler_for_{{method_name|snake_case}}(&mut self, cb: impl Fn({%for arg in method.arg_list%}{{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%})->Result<{{method.return_value_rust_type}}, MethodResultCode> + 'static + Send) {
        self.method_handlers.method_handler_for_{{method_name|snake_case}} = Arc::new(Mutex::new(Box::new(cb)));
    }
    {%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: &mut {{stinger.name | UpperCamelCase }}ServerMethodHandlers, msg: ReceivedMessage) {
        let opt_corr_data = msg.correlation_data;
        let opt_resp_topic = msg.response_topic;
        {%if method.return_value_type != false -%}
        let payload_vec = msg.payload;
        let payload = serde_json::from_slice::<{{method_name | UpperCamelCase}}RequestObject>(&payload_vec).unwrap();
        {%endif%}
        // call the method handler
        let rv: {{method.return_value_rust_type}} = {
            let func_guard = handlers.method_handler_for_{{method_name|snake_case}}.lock().unwrap();
            (*func_guard)({%for arg in method.arg_list%}payload.{{arg.name}}{%if not loop.last%}, {%endif%}{%endfor%}).unwrap()
        };

        {%-if method.return_value_type == "primitive" -%}
        let rv = {{method_name | UpperCamelCase}}ReturnValue {
            {{method.return_value_property_name}}: rv,
        };
        {%endif%}

        if let Some(resp_topic) = opt_resp_topic {
            let corr_data = opt_corr_data.unwrap_or_default();
            publisher.publish_response(resp_topic, &rv, corr_data).await.expect("Failed to publish response structure");
        } else {
            eprintln!("No response topic found in message properties.");
        }
    }
    {%endfor%}

    {%-for prop_name, prop in stinger.properties.items()%}
    async fn publish_{{prop_name}}_value(publisher: MqttierClient, topic: String, data: {{prop.rust_type}})
    {
        {%-if prop.arg_list | length == 1%}
        let new_data = {{prop_name | UpperCamelCase}}Property {
            {{prop.arg_list[0].name}}: data,
        };
        let _pub_result = publisher.publish_state(topic, &new_data, 1).await;
        {%else%}
        let _pub_result = publisher.publish_state(topic, &data, 1).await;
        {%endif%}
    }
    {%if not prop.read_only %}
    async fn update_{{prop_name}}_value(publisher: MqttierClient, topic: Arc<String>, data: Arc<Mutex<Option<{{prop.rust_type}}>>>, msg: ReceivedMessage)
    {
        let payload_str = String::from_utf8_lossy(&msg.payload).to_string();
        let new_data: {{prop_name | UpperCamelCase}}Property = serde_json::from_str(&payload_str).unwrap();
        let mut locked_data = data.lock().unwrap();
        {%-if prop.arg_list | length == 1%}
        *locked_data = Some(new_data.{{prop.arg_list[0].name}});
        {%-else%}
        *locked_data = Some(new_data.clone());
        {%endif%}
        let publisher2 = publisher.clone();
        let topic2: String = topic.as_ref().clone();
        {%-if prop.arg_list | length == 1%}
        let data2 = new_data.{{prop.arg_list[0].name}};
        {%-else%}
        let data2 = new_data;
        {%endif%}
        let _ = tokio::spawn(async move {
            {{stinger.rust.server_struct_name}}::publish_{{prop_name}}_value(publisher2, topic2, data2).await;
        });
    }
    {%endif%}{# not read only property #}
    pub async fn set_{{prop_name}}(&mut self, data: {{prop.rust_type}}) {
        println!("Setting {{prop_name}} of type {{prop.rust_type}}");
        let prop = self.properties.{{prop_name}}.clone();
        {
            let mut locked_data = prop.lock().unwrap();
            *locked_data = Some(data.clone());
        }

        let publisher2 = self.mqttier_client.clone();
        let topic2 = self.properties.{{prop_name}}_topic.as_ref().clone();
        let _ = tokio::spawn(async move {
            println!("Will publish property {{prop_name}} of type {{prop.rust_type}} to {}", topic2);
            {{stinger.rust.server_struct_name}}::publish_{{prop_name}}_value(publisher2, topic2, data).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 receive_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.take().expect("msg_streamer_rx should be Some");

        {%if stinger.methods|length > 0 -%}
        let mut method_handlers = self.method_handlers.clone();
        {%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 loop_task = tokio::spawn(async move {
            while let Some(msg) = message_receiver.recv().await {
                {%-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(), &mut method_handlers, msg).await;
                }
                {%-endfor%}{# methods #}
                {%-for prop_name, prop in stinger.properties.items()%}{%if not prop.read_only %}
                {%if not loop.first or (stinger.methods | length > 0) %}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(), msg).await;
                }
                {%-endif%}{%endfor%}{# properties #}
            }
            println!("No more messages from message_receiver channel");
        });
        let _ = tokio::join!(loop_task);
        {%endif%} {# any methods or properties #}
        
        println!("Server receive loop completed [error?]");
        Ok(())
    }

}
