{%- from "struct.tmpl" import declare_struct -%}

{% macro field_names(field_list) %}
  {{- field_list|map(attribute="name")|join(", ") -}}
{% endmacro %}


{% macro field_types(field_list) %}
  {{- field_list|map(attribute="kind")|map("to_rust_type")|join(", ") -}}
{% endmacro %}

{% macro typed_fields(field_list) %}
{%- for field in field_list -%}
  {{-field.name}}: {{field.kind|to_rust_type}} {{- ", " if not loop.last}}
{%- endfor-%}
{% endmacro %}

{% macro args_struct(method) %}
  {{- method.param_struct.name -}}
{% endmacro %}

{% macro response_struct(method) %}
  {{- method.response_param_struct.name -}}
{% endmacro %}

{% macro declare_interface(interface) -%}
{# ACTUAL CODE BEGINS HERE #}

{%- for method in interface.methods %}

{{- declare_struct(args_struct(method), method.param_struct, true)}}

impl {{args_struct(method)}} {
  fn serialize_from_parts(registrar: &impl Registrar, {{typed_fields(method.param_struct.fields)}})
    -> bindings::message::MojomMessage
  {
    let (payload, handles, interface_ids_offset) =
       mojom_value_parser::serialize(Self { {{field_names(method.param_struct.fields)}} }, registrar);
    {%- if method.response_parameters %}
    let flags = bindings::message_header::MessageHeaderFlags::EXPECTS_RESPONSE;
    {%- else %}
    let flags = bindings::message_header::MessageHeaderFlags::empty();
    {%- endif %}
    bindings::message::MojomMessage {
      header: bindings::message_header::MessageHeader::new(
        0, {{ method.ordinal }}, flags, 0, interface_ids_offset),
      payload,
      handles,
      raw_message_handle: None,
    }
  }
}

{%- if method.response_parameters %}
{{declare_struct(response_struct(method), method.response_param_struct, true)}}

impl {{response_struct(method)}} {
  fn serialize_from_parts(registrar: &impl Registrar, {{typed_fields(method.response_param_struct.fields)}})
    -> bindings::message::MojomMessage
  {
    let (payload, handles, interface_ids_offset) =
      mojom_value_parser::serialize(Self { {{field_names(method.response_param_struct.fields)}} }, registrar);
    let flags = bindings::message_header::MessageHeaderFlags::IS_RESPONSE;
    bindings::message::MojomMessage {
      header: bindings::message_header::MessageHeader::new(
        0, {{ method.ordinal }}, flags, 0, interface_ids_offset
      ),
      payload,
      handles,
      raw_message_handle: None,
    }
  }
}
{%- endif -%}
{% endfor %}

pub trait {{interface.name}} : bindings::interface::internal::ImplementThisViaMacro {
{#- Define one function for each message type the interface declares #}
{%- for method in interface.methods %}
  fn {{method.name}}(&mut self{{", " if method.param_struct.fields}}
  {{- typed_fields(method.param_struct.fields)}}
  {%- if method.response_parameters -%}
    ,
    {#- Align the response callback with the first argument for readability #}
    {{" " * (method.name|length + 1)}} response_callback: impl Send + 'static + FnOnce(
      {{- field_types(method.response_param_struct.fields) -}}
    )
  {%- endif -%}
  ) where Self: Sized;
{%- endfor %}

  // Note to users: Do not override this function when implementing the trait
  #[allow(unused_variables, unused_mut)] // If no messages send a response
  fn handle_incoming_message(
      &mut self,
      message: bindings::message::MojomMessage,
      sender: ResponseSender,
      send_response: impl FnOnce(bindings::message::MojomMessage) + Send + 'static)
    where Self: Sized
  {
    // We could also do this with std::mem::transmute due to the null-pointer optimization,
    // but that's unsafe, and the compiler can figure it out anyway.
    let mut handles = message.handles.into_iter().map(|h| Some(h)).collect::<Vec<_>>();
    match message.header.name {
      {%- for method in interface.methods %}
      {{method.ordinal}} => {
        let {{"_" if not method.parameters}}parsed: {{args_struct(method)}} = match
          mojom_value_parser::deserialize_exact(&message.payload, &mut handles, message.header.interface_ids_offset(), &sender)
        {
          Ok(parsed) => parsed,
          Err(err) => {
            let _ = message.raw_message_handle.unwrap().report_bad_message(&err.to_string());
            return;
          }
        };
        {%- if method.response_parameters %}
        let callback = move |{{typed_fields(method.response_param_struct.fields)}}| {
          send_response({{response_struct(method)}}::serialize_from_parts(&sender, {{field_names(method.response_param_struct.fields)}}))
        };
        {%- endif %}
        self.{{method.name}}(
          {%- for field in method.param_struct.fields -%}
            {#- #}parsed.{{field.name}},
          {% endfor -%}
          {%- if method.response_parameters -%} callback {%- endif -%}
        );
      }
      {%- endfor %}
      _ => todo!("Got a bad mojo message!")
    }
  }
}

{%- set callback_ty_name = "%sResponseCallback"|format(interface.name) %}

pub enum {{callback_ty_name}} {
{%- for method in interface.methods if method.response_parameters %}
  {{method.name}}(Box<dyn Send + 'static + FnOnce({{field_types(method.response_param_struct.fields)}})>),
{%- endfor %}
}

impl bindings::interface::MojomInterface for dyn {{interface.name}} {
  type DynTy = dyn {{interface.name}};

  type ResponseCallbackTy = {{callback_ty_name}};

  #[allow(unused_variables, unused_mut)] // If no messages take a response
  fn handle_incoming_response(
    mut message: bindings::message::MojomMessage,
    sender: ResponseSender,
    send_response: Self::ResponseCallbackTy)
  {
    // We could also do this with std::mem::transmute due to the null-pointer optimization,
    // but that's unsafe, and the compiler can figure it out anway.
    let mut handles = message.handles.into_iter().map(|h| Some(h)).collect::<Vec<_>>();
    match (message.header.name, send_response) {
      {%- for method in interface.methods if method.response_parameters %}
      ({{method.ordinal}}, {{callback_ty_name}}::{{method.name}}(callback)) => {
        let parsed: {{response_struct(method)}} = match
          mojom_value_parser::deserialize_exact(&message.payload, &mut handles, message.header.interface_ids_offset(), &sender)
        {
          Ok(parsed) => parsed,
          Err(err) => {
            let _ = message.raw_message_handle.unwrap().report_bad_message(&err.to_string());
            return;
          }
        };
        callback(
          {%- for field in method.response_param_struct.fields -%}
            {#- #}parsed.{{field.name}} {{- ", " if not loop.last -}}
          {% endfor -%}
        );
      }
      {%- endfor %}
      _ => todo!("Got a bad mojo message!")
    }
  }

  fn handle_incoming_message(
      &mut self,
      _message: bindings::message::MojomMessage,
      _sender: ResponseSender,
      _send_response: impl FnOnce(bindings::message::MojomMessage) + Send + 'static) {
    unimplemented!("Call the appropriate trait method directly on the Remote object instead")
  }
}

impl bindings::interface::DynMojomInterface for dyn {{interface.name}} {}

impl<Marker> {{interface.name}} for bindings::remote::GenericRemote<dyn {{interface.name}}, Marker> {
  {%- for method in interface.methods %}
  fn {{method.name}}(&mut self{{", " if method.param_struct.fields}}
  {{- typed_fields(method.param_struct.fields)}}
  {%- if method.response_parameters -%}
    ,
    {#- Align the response callback with the first argument for readability #}
    {{" " * (method.name|length + 1)}} response_callback: impl Send + 'static + FnOnce(
      {{- field_types(method.response_param_struct.fields) -}}
    )
  {%- endif -%}
  ) {
    {#- Function body begins here #}
    let message = {{args_struct(method)}}::serialize_from_parts(self.as_registrar(), {{field_names(method.param_struct.fields)}});
    {%- if method.response_parameters %}
    let callback = Some({{callback_ty_name}}::{{method.name}}(Box::new(response_callback)));
    {%- else %}
    let callback = None;
    {%- endif %}
    self.send_message_internal(message, callback);
  }
{% endfor -%}
}

{%- endmacro -%}