{% from "constant_definition.tmpl" import constant_def %}
{% from "enum_definition.tmpl" import enum_def %}
{% from "data_types_definition.tmpl" import struct_def %}

{%- macro declare_params(parameters, boxed=false) %}
{%-   for param in parameters -%}
{{param.kind|java_type(boxed)}} {{param|name}}
{%- if not loop.last %}, {% endif %}
{%-   endfor %}
{%- endmacro %}

{% macro declare_request_params(method) %}
{{declare_params(method.parameters)}}
{%-   if method.response_parameters != None -%}
{%- if method.parameters %}, {% endif %}
{{method|interface_response_name}} callback
{%-   endif -%}
{% endmacro %}

{%- macro declare_callback(method) -%}
interface {{method|interface_response_name}} {
  public void call(
{%- if method.result_response != None %}
      Result<{{method.result_response.success_kind|java_type(true)}},
             {{method.result_response.failure_kind|java_type(true)}}> result
{%- else -%}
{%-   for param in method.response_parameters %}
      {{param.kind|java_type(false)}} {{param|name}}
{%-     if not loop.last %}, {% endif %}
{%-   endfor -%}
{%- endif -%}
      );
}
{%- endmacro -%}

{%- macro run_callback(variable, parameters) -%}
{%-   if parameters -%}
{%-     for param in parameters -%}
{{variable}}.{{param|name}}
{%-   if not loop.last %}, {% endif %}
{%-     endfor -%}
{%-   endif -%}
{%- endmacro -%}


{%- macro result_converter_name(method, callback) -%}
{{method|interface_response_name}}_Converter
{%- endmacro -%}

{%- macro flags_for_method(method, is_request) -%}
{{flags(method.response_parameters != None, is_request, method.sync)}}
{%- endmacro -%}

{%- macro flags(use_response_flag, is_request, is_sync) -%}
{%-  if not use_response_flag -%}
org.chromium.mojo.bindings.MessageHeader.NO_FLAG
{%-   elif is_request: -%}
org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG
{%-   else -%}
org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG
{%-   if is_sync -%}
 | org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_SYNC_FLAG
{%-   endif  -%}
{%-  endif -%}
{%- endmacro -%}

{%- macro manager_class(interface, fully_qualified=False) -%}
{% if fully_qualified %}org.chromium.mojo.bindings.Interface.{% endif %}Manager<{{interface|name}}, {{interface|name}}.Proxy>
{%- endmacro -%}

{%- macro manager_def(interface) -%}
public static final {{manager_class(interface, True)}} MANAGER =
        new {{manager_class(interface, True)}}() {

    @Override
    public String getName() {
        return "{{namespace}}.{{interface.name}}";
    }

    @Override
    public int getVersion() {
      return {{interface.version}};
    }

    @Override
    public Proxy buildProxy(org.chromium.mojo.system.Core core,
                            org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
        return new Proxy(core, messageReceiver);
    }

    @Override
    public org.chromium.mojo.bindings.Stub<{{interface|name}}> buildStub(
            org.chromium.mojo.system.Core core,
            {{interface|name}} impl,
            int interfaceId) {
        return new {{interface|name}}Stub(core, impl, interfaceId);
    }

    @Override
    public {{interface|name}}[] buildArray(int size) {
      return new {{interface|name}}[size];
    }
};
{%- endmacro -%}

{%- macro accept_body(interface, with_response) -%}
try {
    org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
            message.asServiceMessage();
    org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
    int flags = {{flags(with_response, True)}};
    if (header.hasFlag(org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_SYNC_FLAG)) {
        flags = flags | org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_SYNC_FLAG;
    }
    if (!header.hasExactFlags(flags)) {
        throw new org.chromium.mojo.bindings.InvalidHeaderException(
            "expected flags: " + flags + ", but got: " + header.getFlags()
        );
    }
    switch(header.getMethodId()) {
{%   if with_response %}
        case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_MESSAGE_ID:
            return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRun(
                    getCore(), {{interface|name}}_Internal.MANAGER, messageWithHeader, receiver);
{%   else %}
        case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID:
            return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRunOrClosePipe(
                    {{interface|name}}_Internal.MANAGER, messageWithHeader);
{%   endif %}
{%   for method in interface.methods %}
{%     if (with_response and method.response_parameters != None) or
        (not with_response and method.response_parameters == None) %}
{%       set request_struct = method.param_struct %}
{%       if with_response %}
{%         set response_struct = method.response_param_struct %}
{%       endif %}
        case {{method|method_ordinal_name}}: {
{%       if method.parameters %}
            {{request_struct|name}} data =
                    {{request_struct|name}}.deserialize(messageWithHeader.getPayload());
{%       else %}
            {{request_struct|name}}.deserialize(messageWithHeader.getPayload());
{%       endif %}
            getImpl().{{method|name}}({{run_callback('data', method.parameters)}}{% if with_response %}{% if method.parameters %}, {% endif %}new {{response_struct|name}}ProxyToResponder(getCore(), receiver, header.getRequestId()){% endif %});
            return true;
        }
{%     endif %}
{%   endfor %}
        default:
            throw new org.chromium.mojo.bindings.UnknownMethodException(
                "unknown method: " + header.getMethodId());
    }
} catch (org.chromium.mojo.bindings.DeserializationException e) {
    throw new org.chromium.mojo.bindings.UnmarshalException(e);
}
{%- endmacro -%}

{% macro interface_def(interface) %}
public interface {{interface|name}} extends org.chromium.mojo.bindings.Interface {
{%  for constant in interface.constants %}

    {{constant_def(constant)|indent(4)}}
{%  endfor %}
{%  for enum in interface.enums %}

    {{enum_def(enum, false)|indent(4)}}
{% endfor %}

    public interface Proxy extends {{interface|name}}, org.chromium.mojo.bindings.Interface.Proxy {
    }

    {{manager_class(interface)}} MANAGER = {{interface|name}}_Internal.MANAGER;
{% for method in interface.methods %}
    void {{method|name}}({{declare_request_params(method)}});
{%   if method.response_parameters != None %}
    {{declare_callback(method)|indent(4)}}
{%   endif %}
{% endfor %}
}
{% endmacro %}

{% macro interface_internal_def(interface) %}
class {{interface|name}}_Internal {

    {{manager_def(interface)|indent(4)}}

{% for method in interface.methods %}
    private static final int {{method|method_ordinal_name}} = {{method.ordinal}};
{% endfor %}

    static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements {{interface|name}}.Proxy {

        Proxy(org.chromium.mojo.system.Core core,
              org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) {
            super(core, messageReceiver);
        }
{% for method in interface.methods %}

        @Override
        public void {{method|name}}({{declare_request_params(method)}}) {
{%   set request_struct = method.param_struct %}
            {{request_struct|name}} _message = new {{request_struct|name}}();
{%   for param in method.parameters %}
            _message.{{param|name}} = {{param|name}};
{%   endfor %}
            try {
{%   if method.response_parameters != None %}
                getProxyHandler().getMessageReceiver().acceptWithResponder(
                        _message.serializeWithHeader(
                                getProxyHandler().getCore(),
                                new org.chromium.mojo.bindings.MessageHeader(
                                        org.chromium.mojo.bindings.MessageHeader.TEMPORARY_DEFAULT_INTERFACE_ID,
                                        {{method|method_ordinal_name}},
                                        {{flags_for_method(method, True)}},
                                        0)),
                        new {{method.response_param_struct|name}}ForwardToCallback(callback));
{%   else %}
                getProxyHandler().getMessageReceiver().accept(
                        _message.serializeWithHeader(
                                getProxyHandler().getCore(),
                                new org.chromium.mojo.bindings.MessageHeader(
                                    org.chromium.mojo.bindings.MessageHeader.TEMPORARY_DEFAULT_INTERFACE_ID,
                                    {{method|method_ordinal_name}})));
{%   endif %}
            } catch (org.chromium.mojo.bindings.BadMessageException e) {
                // Should not be possible, because we serialized above.
                throw new IllegalStateException(e);
            }
        }
{% endfor %}

    }

    static final class {{interface|name}}Stub extends org.chromium.mojo.bindings.Stub<{{interface|name}}> {

        {{interface|name}}Stub(org.chromium.mojo.system.Core core, {{interface|name}} impl, int interfaceId) {
            super(core, impl, interfaceId);
        }

        @Override
        public boolean accept(org.chromium.mojo.bindings.Message message) throws org.chromium.mojo.bindings.BadMessageException {
            {{accept_body(interface, False)|indent(12)}}
        }

        @Override
        public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver)
                throws org.chromium.mojo.bindings.BadMessageException {
            {{accept_body(interface, True)|indent(12)}}
        }
    }
{% for method in interface.methods %}

{%-   if method.result_response != None -%}
{%-    set result_param = method.response_parameters[0] %}
{%-    set success_kind = method.result_response.success_kind -%}
{%-    set failure_kind = method.result_response.failure_kind %}
{%-    set generic_typing = '<' + success_kind|java_type(true) + ', ' + failure_kind|java_type(true) + '>' %}
{%-    set mojo_type = result_param.kind|java_type(false) %}

    static class {{result_converter_name(method)}} {
        static {{mojo_type}} convert(Result{{generic_typing}} result) {
            var mojoObj = new {{mojo_type}}();
            if (result.isSuccess()) {
                mojoObj.setSuccess(result.get());
            } else {
                mojoObj.setFailure(result.getError());
            }
            return mojoObj;
        }

        static Result{{generic_typing}} convert({{mojo_type}} mojoObj) {
            return mojoObj.which() == {{mojo_type}}.Tag.Success ?
                Result.of(mojoObj.getSuccess()) :
                Result.ofError(mojoObj.getFailure());
        }
    }
{%-   endif %}

    {{ struct_def(method.param_struct, True)|indent(4) }}
{%   if method.response_parameters != None %}
{%   set response_struct = method.response_param_struct %}

    {{ struct_def(response_struct, True)|indent(4) }}

    static class {{response_struct|name}}ForwardToCallback implements org.chromium.mojo.bindings.MessageReceiver {
        private final {{interface|name}}.{{method|interface_response_name}} mCallback;

        {{response_struct|name}}ForwardToCallback({{interface|name}}.{{method|interface_response_name}} callback) {
            this.mCallback = callback;
        }

        @Override
        public boolean accept(org.chromium.mojo.bindings.Message message) throws org.chromium.mojo.bindings.BadMessageException {
            try {
                org.chromium.mojo.bindings.ServiceMessage messageWithHeader =
                        message.asServiceMessage();
                org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader();
                if (header.getMethodId() != {{method|method_ordinal_name}}) {
                    return false;
                }
                if (!header.hasExactFlags({{flags_for_method(method, False)}})) {
                    return false;
                }
{%   if method.response_parameters|length %}
                {{response_struct|name}} response = {{response_struct|name}}.deserialize(messageWithHeader.getPayload());
{%   endif %}
{%-  if method.result_response != None %}
{%-    set result_param = method.response_parameters[0] %}
                var mojoObj = response.{{result_param|name}};
                mCallback.call({{result_converter_name(method)}}.convert(mojoObj));
{%-  else %}
                mCallback.call({{run_callback('response', method.response_parameters)}});
{%-  endif %}
                return true;
            } catch (org.chromium.mojo.bindings.DeserializationException e) {
                throw new org.chromium.mojo.bindings.UnmarshalException(e);
            }
        }

        @Override
        public void close() {}
    }

    static class {{response_struct|name}}ProxyToResponder implements {{interface|name}}.{{method|interface_response_name}} {

        private final org.chromium.mojo.system.Core mCore;
        private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver;
        private final long mRequestId;

        {{response_struct|name}}ProxyToResponder(
                org.chromium.mojo.system.Core core,
                org.chromium.mojo.bindings.MessageReceiver messageReceiver,
                long requestId) {
            mCore = core;
            mMessageReceiver = messageReceiver;
            mRequestId = requestId;
        }

        @Override
{%-  if method.result_response -%}
{%-   set success_kind = method.result_response.success_kind %}
{%-   set failure_kind = method.result_response.failure_kind %}
        public void call(Result<{{success_kind|java_type(true)}}, {{failure_kind|java_type(true)}}> result) {
{%-  else %}
        public void call({{declare_params(method.response_parameters)}}) {
{%-  endif %}
            {{response_struct|name}} _response = new {{response_struct|name}}();


{%-  if method.result_response -%}
{%-    set result_param = method.response_parameters[0] %}
            _response.{{result_param|name}} = {{result_converter_name(method)}}.convert(result);
{%-  else %}
{%   for param in method.response_parameters %}
            _response.{{param|name}} = {{param|name}};
{%   endfor %}
{%-  endif %}
            try {
                org.chromium.mojo.bindings.ServiceMessage _message =
                        _response.serializeWithHeader(
                                mCore,
                                new org.chromium.mojo.bindings.MessageHeader(
                                        org.chromium.mojo.bindings.MessageHeader.TEMPORARY_DEFAULT_INTERFACE_ID,
                                        {{method|method_ordinal_name}},
                                        {{flags_for_method(method, False)}},
                                        mRequestId));
                mMessageReceiver.accept(_message);
            } catch (org.chromium.mojo.bindings.BadMessageException e) {
                // Should not be possible, because we serialized above.
                throw new IllegalStateException(e);
            }
        }
    }
{%   endif %}
{% endfor %}

}
{% endmacro %}
