{# ei-scanner jinja template, for `` #}
#![allow(unknown_lints, unused_imports, unused_parens, clippy::useless_conversion, clippy::double_parens, clippy::match_single_binding, clippy::unused_unit, clippy::empty_docs, clippy::doc_lazy_continuation)]

// GENERATED FILE

{# TODO handle destructor #}
{# TODO handle context_type #}

use crate::wire;

{% macro interface_type(interface) -%}
    super::{{interface.plainname}}::{{interface.plainname|camel}}
{%- endmacro %}

{% macro arg_type(arg, owned, generic) -%}
    {% if arg.enum != None %} {{ arg.enum.name|camel }}
    {% elif arg.protocol_type == 'string' and owned %} String
    {% elif arg.protocol_type == 'string' %} &str
    {% elif arg.protocol_type == 'int32' %} i32
    {% elif arg.protocol_type == 'uint32' %} u32
    {% elif arg.protocol_type == 'int64' %} i64
    {% elif arg.protocol_type == 'uint64' %} u64
    {% elif arg.protocol_type == 'object' and owned %} {{interface_type(arg.interface)}}
    {% elif arg.protocol_type == 'object' %} &{{interface_type(arg.interface)}}
    {% elif arg.protocol_type == 'new_id' and arg.interface %} {{interface_type(arg.interface)}}
    {% elif arg.protocol_type == 'new_id' and arg.interface_arg and generic %} {{arg.interface_arg.name|camel}}
    {% elif arg.protocol_type == 'new_id' and arg.interface_arg %} crate::Object
    {% elif arg.protocol_type == 'float' %} f32
    {% elif arg.protocol_type == 'fd' and owned %} std::os::unix::io::OwnedFd
    {% elif arg.protocol_type == 'fd' %} std::os::unix::io::BorrowedFd
    {% else %} unhandled_arg_type_{{arg.protocol_type}}
    {% endif %}
{%- endmacro %}

{% macro incoming_enum() -%}
    {% if extra.eis %}
    Request
    {% else %}
    Event
    {% endif %}
{%- endmacro %}

{% macro module() -%}
    {% if extra.eis %}
    crate::eis
    {% else %}
    crate::ei
    {% endif %}
{%- endmacro %}

{% for interface in interfaces %}
/** {{interface.description.text|ei_escape_names}} */
pub mod {{interface.plainname}} {
    use crate::wire;

    #[derive(Clone, Debug, Hash, Eq, PartialEq)]
    pub struct {{interface.plainname|camel}}(pub(crate) crate::Object);

    impl {{interface.plainname|camel}} {
        pub fn version(&self) -> u32 {
            self.0.version()
        }
    }

    impl crate::private::Sealed for {{interface.plainname|camel}} {}

    impl wire::Interface for {{interface.plainname|camel}} {
        const NAME: &'static str = "{{interface.protocol_name}}";
        const VERSION: u32 = {{interface.version}};
        {% if extra.eis %}
        const CLIENT_SIDE: bool = false;
        {% else %}
        const CLIENT_SIDE: bool = true;
        {% endif %}

        fn new_unchecked(object: crate::Object) -> Self {
            Self(object)
        }

	fn as_object(&self) -> &crate::Object {
            &self.0
	}

        fn as_arg(&self) -> wire::Arg<'_> {
            self.0.as_arg()
        }
    }

    impl {{module()}}::Interface for {{interface.plainname|camel}} {}

    impl {{interface.plainname|camel}} {
        {% for outgoing in interface.outgoing %}
        /** {{outgoing.description.text|ei_escape_names}} */
        pub fn {{outgoing.name}}<
            {%- for arg in outgoing.arguments %}
            {% if arg.interface_arg_for %}
            {{arg.name|camel}}: {{module()}}::Interface
            {% endif %}
            {% endfor -%}
        >(
            &self,
            {%- for arg in outgoing.arguments %}
            {% if arg.protocol_type != 'new_id' and not arg.interface_arg_for %}
            {{arg.name}}: {{arg_type(arg, false, false)}},
            {% endif %}
            {% endfor -%}
            ) -> (
                {%- for arg in outgoing.arguments %}
                {% if arg.protocol_type == 'new_id' %}
                {{arg_type(arg, true, true)}}
                {% endif %}
                {% endfor -%}
            ) {
            {%- for arg in outgoing.arguments %}
            {% if arg.protocol_type == 'new_id' and arg.interface_arg %}
            let {{arg.name}} = self.0.backend_weak().new_object({{arg.interface_arg.name|camel}}::NAME.to_string(), {{arg.version_arg.name}});
            {% elif arg.protocol_type == 'new_id' %}
            let {{arg.name}} = self.0.backend_weak().new_object("{{arg.interface.protocol_name}}".to_string(), {{arg.version_arg.name}});
            {% endif -%}
            {% endfor -%}

            let args = &[
            {%- for arg in outgoing.arguments %}
            {% if arg.interface_arg_for %}
                wire::Arg::{{arg.protocol_type|camel}}({{arg.name|camel}}::NAME),
            {% else %}
                wire::Arg::{{arg.protocol_type|camel}}({{arg.name}}
                {% if arg.protocol_type == 'new_id' %}
                .id()
                {% endif %}
                .into()),
            {% endif %}
            {% endfor -%}
            ];

            self.0.request({{outgoing.opcode}}, args);
            {% if outgoing.is_destructor %}
                self.0.backend_weak().remove_id(self.0.id());
            {% endif %}

            (
            {%- for arg in outgoing.arguments %}
            {% if arg.protocol_type == 'new_id' %}
            {% if arg.interface %}
                {{arg_type(arg, true, false)}}({{arg.name}})
            {% elif arg.interface_arg %}
                {{arg.name}}.downcast_unchecked()
            {% else -%}
                unreachable
            {% endif -%}
            {% endif -%}
            {% endfor -%}
            )
        }

        {% endfor %}
    }

    {% for enum in interface.enums %}
    pub use crate::eiproto_enum::{{interface.plainname}}::{{enum.camel_name}};
    {% endfor %}

    #[non_exhaustive]
    #[derive(Debug)]
    pub enum {{incoming_enum()}} {
        {% for incoming in interface.incoming %}
            /** {{incoming.description.text|ei_escape_names}} */
            {{ incoming.name|camel }}
                {% if incoming.arguments %}
                { {% for arg in incoming.arguments %}
                    {% if not (arg.version_arg_for or arg.interface_arg_for) %}
                    /** {{arg.summary}} */
                    {{arg.name}}: {{arg_type(arg, true, false)}},
                    {% endif %}
                {% endfor %} }
                {% endif %},
        {% endfor %}
    }

    impl {{incoming_enum()}} {
        pub(super) fn op_name(operand: u32) -> Option<&'static str> {
            match operand {
            {% for incoming in interface.incoming %}
                {{incoming.opcode}} => Some("{{incoming.name}}"),
            {% endfor %}
                _ => None
            }
        }

        pub(super) fn parse(operand: u32, _bytes: &mut wire::ByteStream) -> Result<Self, wire::ParseError> {
            match operand {
            {% for incoming in interface.incoming %}
                {{incoming.opcode}} => {
                    {% for arg in incoming.arguments %}
                        let {{arg.name}} = _bytes.read_arg()?;
                    {% endfor %}

                    Ok(Self::{{ incoming.name|camel }}
                    {% if incoming.arguments %} {
                    {% for arg in incoming.arguments %}
                        {% if arg.version_arg_for or arg.interface_arg_for %}
                        {% elif arg.protocol_type == 'new_id' and arg.interface_arg %}
                        {{arg.name}}: _bytes.backend().new_peer_object({{arg.name}}, {{arg.interface_arg.name}}, {{arg.version_arg.name}})?,
                        {% elif arg.protocol_type == 'new_id' %}
                        {{arg.name}}: _bytes.backend().new_peer_interface({{arg.name}}, {{arg.version_arg.name}})?,
                        {% else %}
                        {{arg.name}},
                        {% endif %}
                    {% endfor %} }
                    {% endif %} )
                }
            {% endfor %}
         opcode => Err(wire::ParseError::InvalidOpcode("{{interface.plainname}}", opcode)),
            }
        }

        #[allow(unused_imports, unused_mut, unused_variables, unreachable_code, unreachable_patterns)]
        pub(super) fn args(&self) -> Vec<wire::Arg<'_>> {
            use crate::{wire::OwnedArg, Interface};
            let mut args = Vec::new();
            match self {
            {% for incoming in interface.incoming %}
                {% if incoming.arguments %}
                Self::{{ incoming.name|camel }} {
                    {% for arg in incoming.arguments %}
                    {% if not (arg.version_arg_for or arg.interface_arg_for) %}
                    {{arg.name}},
                    {% endif %}
                    {% endfor %}
                } => {
                    {% for arg in incoming.arguments %}
                    {% if not (arg.version_arg_for or arg.interface_arg_for) %}
                    args.push({{arg.name}}.as_arg());
                    {% endif %}
                    {% endfor %}
                }
                {% else %}
                Self::{{ incoming.name|camel }} => {}
                {% endif %}
            {% endfor %}
                _ => unreachable!()
            }
            args
        }
    }
}

pub use {{interface.plainname}}::{{interface.plainname|camel}};

{% endfor %}

#[non_exhaustive]
#[derive(Debug)]
pub enum {{incoming_enum()}} {
    {% for interface in interfaces %}
        {{interface.plainname|camel}}({{interface.plainname}}::{{interface.plainname|camel}}, {{interface.plainname}}::{{incoming_enum()}}),
    {% endfor %}
}

impl {{incoming_enum()}} {
    pub(crate) fn op_name(interface: &str, operand: u32) -> Option<&'static str> {
        match interface {
            {% for interface in interfaces %}
                "{{interface.protocol_name}}" =>
                    {{interface.plainname}}::{{incoming_enum()}}::op_name(operand),
            {% endfor %}
            _ => None,
        }
    }

    pub(crate) fn parse(object: crate::Object, operand: u32, bytes: &mut wire::ByteStream) -> Result<Self, wire::ParseError> {
        match object.interface() {
            {% for interface in interfaces %}
                "{{interface.protocol_name}}" => Ok(Self::{{interface.plainname|camel}}(
                    object.downcast_unchecked(), 
                    {{interface.plainname}}::{{incoming_enum()}}::parse(operand, bytes)?)
                ),
            {% endfor %}
            intr => Err(wire::ParseError::InvalidInterface(intr.to_owned())),
        }
    }
}

impl wire::MessageEnum for {{incoming_enum()}} {
    fn args(&self) -> Vec<wire::Arg<'_>> {
        match self {
            {% for interface in interfaces %}
            Self::{{interface.plainname|camel}}(_, x) => x.args(),
            {% endfor %}
        }
    }
}
