{% import "mojolpm_macros.tmpl" as util %}


{%- macro declare_array(type, kind) %}
{%-   set mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
{%-   if kind.kind|is_nullable_kind %}
{%-     set mojom_maybe_type = kind.kind|to_unnullable_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
{%-   else %}
{%-     set mojom_maybe_type = mojom_type %}
{%-   endif %}
bool FromProto(
  const {{type}}& input,
  std::vector<{{mojom_type}}>& output);{{"\n"-}}
{%-   if kind.kind|is_array_kind %}
{{declare_array(type ~ "Entry", kind.kind)}}
{%-   elif kind.kind|is_map_kind %}
{{declare_map(type ~ "Entry", kind.kind)}}
{%-   elif kind.kind|is_nullable_kind %}
bool FromProto(
  const {{type}}Entry& input,
  {{mojom_maybe_type}}& output);{{"\n"-}}
{%-   else %}
bool FromProto(
  const {{type}}Entry& input,
  {{mojom_type}}& output);{{"\n"-}}
{%-   endif %}
{%- endmacro %}


{%- macro declare_map(type, kind) %}
{%-   set mojom_key_type = kind.key_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
{%-   set mojom_value_type = kind.value_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
{%-   if kind.value_kind|is_nullable_kind %}
{%-     set mojom_maybe_value_type = kind.value_kind|to_unnullable_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
{%-   else %}
{%-     set mojom_maybe_value_type = mojom_value_type %}
{%-   endif %}
bool FromProto(
  const {{type}}& input,
  base::flat_map<{{mojom_key_type}},
  {{mojom_value_type}}>& output);{{"\n"-}}
{%-   if kind.key_kind|is_array_kind %}
{{- declare_array(type ~ "Key", kind.key_kind)}}
{%-   elif kind.key_kind|is_map_kind %}
{{- declare_map(type ~ "Key", kind.key_kind)}}
{%-   else %}
bool FromProto(
  const {{type}}Key& input,
  {{mojom_key_type}}& output);{{"\n"-}}
{%-   endif %}
{%-   if kind.value_kind|is_array_kind %}
{{- declare_array(type ~ "Value", kind.value_kind)}}
{%-   elif kind.value_kind|is_map_kind %}
{{- declare_map(type ~ "Value", kind.value_kind)}}
{%-   elif kind.value_kind|is_nullable_kind %}
bool FromProto(
  const {{type}}Value& input,
  {{mojom_maybe_value_type}}& output);{{"\n"-}}
{%-   else %}
bool FromProto(
  const {{type}}Value& input,
  {{mojom_value_type}}& output);{{"\n"-}}
{%-   endif %}
{%- endmacro %}


{%- macro declare(parent_name, kind, name) %}
{%-   if kind|is_array_kind %}
{%-     set array_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Array" %}
{{- declare_array(array_type, kind)}}
{%-   elif kind|is_map_kind %}
{%-     set map_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Map" %}
{{- declare_map(map_type, kind)}}
{%-   endif %}
{%- endmacro %}


{%- macro define_array(type, kind) %}
{%-   set mojom_type = kind.kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
bool FromProto(
    const {{type}}& input,
    std::vector<{{mojom_type}}>& output) {
  bool result = true;

  {% if kind.length is none %}
  output.reserve(input.values_size());
  for (const auto& entry : input.values()) {
    {{mojom_type}} value{ {{- kind.kind|default_constructor_args -}} };
{%-   if kind.kind|is_nullable_kind %}
{%-     set unnullable_kind = kind.kind|to_unnullable_kind %}
{%-     set mojom_maybe_type = unnullable_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
    {{mojom_maybe_type}} maybe_value{ {{- unnullable_kind|default_constructor_args -}} };
    if (entry.has_value() && FromProto(entry.value(), maybe_value)) {
      value = std::move(maybe_value);
    }
{%-   elif kind.kind|is_map_kind or kind.kind|is_array_kind %}
    result = FromProto(entry, value);
    if (!result) {
      break;
    }
{%-   else %}
    result = FromProto(entry.value(), value);
    if (!result) {
      break;
    }
{%-   endif %}
    output.emplace_back(std::move(value));
}
{% else %}
  output.resize({{kind.length}});
  {%- for i in range(kind.length) %}
  result &= FromProto(input.value_{{i}}(), output[{{i}}]);
{%- endfor %}

{% endif %}


  return result;
}{{"\n"-}}
{%-   if kind.kind|is_array_kind %}
{{- define_array(type ~ "Entry", kind.kind)}}
{%-   elif kind.kind|is_map_kind %}
{{- define_map(type ~ "Entry", kind.kind)}}
{%-   elif kind.kind|is_nullable_kind %}
bool FromProto(
    const {{type}}Entry& input,
    {{mojom_maybe_type}}& output) {
  return FromProto(input.value(), output);
}{{"\n"-}}
{%-   else %}
bool FromProto(
    const {{type}}Entry& input,
    {{mojom_type}}& output) {
  return FromProto(input.value(), output);
}{{"\n"-}}
{%-   endif %}
{%- endmacro %}


{%- macro define_map(type, kind) %}
{%-   set mojom_key_type = kind.key_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
{%-   set mojom_value_type = kind.value_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
bool FromProto(
    const {{type}}& input,
    base::flat_map<{{mojom_key_type}},
                   {{mojom_value_type}}>& output) {
  bool result = true;
  for (const auto& entry : input.values()) {
{%-   if kind.value_kind|is_nullable_kind %}
{%-     set unnullable_value_kind = kind.value_kind|to_unnullable_kind %}
{%-     set mojom_maybe_value_type = unnullable_value_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
    {{mojom_key_type}} key{ {{- kind.key_kind|default_constructor_args -}} };
    {{mojom_value_type}} value{ {{- kind.value_kind|default_constructor_args -}} };
    {{mojom_maybe_value_type}} maybe_value{ {{- unnullable_value_kind|default_constructor_args -}} };

    if (FromProto(entry.key(), key)) {
      if (FromProto(entry.value(), maybe_value)) {
        value = std::move(maybe_value);
      }
      output.emplace(std::move(key), std::move(value));
    } else {
      result = false;
      break;
    }
{%-   else %}
    {{mojom_key_type}} key{ {{- kind.key_kind|default_constructor_args -}} };
    {{mojom_value_type}} value{ {{- kind.value_kind|default_constructor_args -}} };

    if (FromProto(entry.key(), key)
        && FromProto(entry.value(), value)) {
      output.emplace(std::move(key), std::move(value));
    } else {
      result = false;
      break;
    }
{%-   endif %}
  }

  return result;
}{{"\n"-}}
{%-   if kind.key_kind|is_array_kind %}
{{define_array(type ~ "Key", kind.key_kind)}}
{%-   elif kind.key_kind|is_map_kind %}
{{define_map(type ~ "Key", kind.key_kind)}}
{%-   else %}
bool FromProto(
    const {{type}}Key& input,
    {{mojom_key_type}}& output) {
  return FromProto(input.value(), output);
}{{"\n"-}}
{%-   endif %}
{%-   if kind.value_kind|is_array_kind %}
{{- define_array(type ~ "Value", kind.value_kind)}}
{%-   elif kind.value_kind|is_map_kind %}
{{- define_map(type ~ "Value", kind.value_kind)}}
{%-   elif kind.value_kind|is_nullable_kind %}
bool FromProto(
    const {{type}}Value& input,
    {{mojom_maybe_value_type}}& output) {
  return FromProto(input.value(), output);
}{{"\n"-}}
{%-   else %}
bool FromProto(
    const {{type}}Value& input,
    {{mojom_value_type}}& output) {
  return FromProto(input.value(), output);
}{{"\n"-}}
{%-   endif %}
{%- endmacro %}


{%- macro define(parent_name, kind, name) %}
{%-   if kind|is_array_kind %}
{%-     set array_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Array" %}
{{- define_array(array_type, kind)}}
{%-   elif kind|is_map_kind %}
{%-     set map_type = parent_name ~ "::" ~ name|under_to_camel ~ "_Map" %}
{{- define_map(map_type, kind)}}
{%-   endif %}
{%- endmacro %}


{%- macro define_enum(enum) -%}
{%-   set mojom_type = enum|cpp_wrapper_call_type(add_same_module_namespaces=true) %}
{%-   set proto_type = "::mojolpm" ~ (enum|get_qualified_name_for_kind(flatten_nested_kind=True)) %}
{%-   set enum_type = enum|get_qualified_name_for_kind(flatten_nested_kind=True) %}
bool FromProto(
    const {{proto_type}}& input,
    {{mojom_type}}& output) {
  static_assert(std::same_as<decltype(std::to_underlying(input)), int32_t>);
  return ::mojo::internal::Deserialize<{{enum_type}}>(
      std::to_underlying(input), &output);
}
{%- endmacro %}


{%- macro define_struct(struct) -%}
{%-   set mojom_type = struct|cpp_wrapper_call_type(add_same_module_namespaces=true) %}
{%-   set generated_mojom_type = struct|get_qualified_name_for_kind(flatten_nested_kind=true) %}
{%-   set proto_type = "::mojolpm" ~ generated_mojom_type %}
{%-   set struct_type = proto_type ~ "_ProtoStruct" %}
{%-   set dataview_type = generated_mojom_type ~ "DataView" %}
{%-   set data_type = (struct|get_qualified_name_for_kind(flatten_nested_kind=True, internal=True)) %}
bool FromProto(
    const {{struct_type}}& input,
    {{mojom_type}}& output) {
{%-   if struct|is_native_only_kind %}
  auto data = base::Pickle::WithUnownedBuffer(base::as_bytes(base::span(input.native_bytes())));
  base::PickleIterator iter(data);
  return IPC::ReadParam(&data, &iter, &output);
{%-   else %}
{%-     for field in struct.fields -%}
{%-       set name = field.name|camel_to_under %}
{%-       set kind = field.kind %}
{%-       set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
{% if field.default is none %}
  {{field_mojom_type}} local_{{name}}{ {{- kind|default_constructor_args -}} };
{% else %}
  {{field_mojom_type}} local_{{name}}{ {{- field|default_value(add_same_module_namespaces=true) -}} };
{% endif %}
{%-       if kind|is_nullable_kind %}
{%-         set unnullable_kind = kind|to_unnullable_kind %}
{%-         set field_maybe_mojom_type = unnullable_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
  {{field_maybe_mojom_type}} local_maybe_{{name}}{ {{- unnullable_kind|default_constructor_args -}} };
{%-       endif %}
{%-     endfor %}

{%-     for field in struct.fields -%}
{%-       set name = field.name|camel_to_under %}
{%-       set kind = field.kind %}
{%-       if not kind|is_nullable_kind %}
// Non-nullable fields with default values are `optional` fields in proto.
{%-         if field.default is none %}
  if (!FromProto(input.m_{{name}}(), local_{{name}})) {
    return false;
  }
{%-         else %}
  if (input.has_m_{{name}}()) {
    if (!FromProto(input.m_{{name}}(), local_{{name}})) {
      return false;
    }
  }
{%-         endif %}
{%-       else %}
  if (input.has_m_{{name}}() && FromProto(input.m_{{name}}(), local_maybe_{{name}})) {
    local_{{name}} = std::move(local_maybe_{{name}});
  }
{%-       endif %}
{%-     endfor %}
  auto input_from_lpm = {{generated_mojom_type}}::New(
{%-     for field in struct.fields -%}
{%-       set name = field.name|camel_to_under %}
      std::move(local_{{name}}){{ ', ' if not loop.last }}
{%-     endfor -%});
{%-     if struct|use_custom_serializer %}
  output = std::move(input_from_lpm);
  return true;
{%-     else %}
  ::mojo::Message mojolpm_message(0, 0, 0, 0, nullptr);
  ::mojo::internal::MessageFragment<{{data_type}}> mojolpm_fragment(
      mojolpm_message);
  ::mojo::internal::Serializer<{{dataview_type}}, {{generated_mojom_type}}Ptr>::Serialize(
      input_from_lpm, mojolpm_fragment);
  return ::mojo::internal::DeserializeImpl<{{dataview_type}}>(
      mojolpm_message, mojolpm_message.payload(), mojolpm_message.payload_num_bytes(),
      &output, &{{data_type}}::Validate);
{%-     endif %}
{%-   endif %}
}

bool FromProto(
    const {{proto_type}}& input,
    {{mojom_type}}& output) {
  if (input.instance_case() == {{proto_type}}::kOld) {
    {{struct_type}}* old = mojolpm::GetContext()->GetInstance<{{struct_type}}>(input.old());
    if (old) {
      return FromProto(*old, output);
    }
  } else {
    return FromProto(input.new_(), output);
  }

  return false;
}
{%- endmacro %}


{%- macro define_union(union) -%}
{%-   set mojom_type = union|cpp_wrapper_call_type(add_same_module_namespaces=true) %}
{%-   set generated_mojom_type = union|get_qualified_name_for_kind(flatten_nested_kind=true) %}
{%-   set proto_type = "::mojolpm" ~ generated_mojom_type %}
{%-   set union_type = proto_type ~ "_ProtoUnion" %}
{%-   set dataview_type = generated_mojom_type ~ "DataView" %}
{%-   set data_type = (union|get_qualified_name_for_kind(flatten_nested_kind=True, internal=True)) %}
bool FromProto(
    const {{union_type}}& input,
    {{mojom_type}}& output) {
  {{generated_mojom_type}}Ptr input_from_lpm;
  switch (input.union_member_case()) {
{%-     for field in union.fields %}
{%-       set name = field.name|camel_to_under %}
{%-       set kind = field.kind %}
{%-       set field_mojom_type = kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
    case {{union_type}}::k{{("m_" ~ name)|under_to_camel}}: {
{%-       if kind|is_nullable_kind %}
{%-         set unnullable_kind = kind|to_unnullable_kind %}
{%-         set field_maybe_mojom_type = unnullable_kind|cpp_wrapper_type(add_same_module_namespaces=true) %}
      {{field_mojom_type}} local_{{name}}{ {{- kind|default_constructor_args -}} };
      {{field_maybe_mojom_type}} maybe_local_{{name}}{ {{- unnullable_kind|default_constructor_args -}} };
      if (input.has_m_{{name}}() && FromProto(input.m_{{name}}(), maybe_local_{{name}})) {
        local_{{name}} = std::move(maybe_local_{{name}});
      }
      input_from_lpm = {{generated_mojom_type}}::New{{name|under_to_camel(digits_split=false)}}(std::move(local_{{name}}));
      break;
{%-       else %}
      {{field_mojom_type}} local_{{name}}{ {{- kind|default_constructor_args -}} };
      if (!FromProto(input.m_{{name}}(), local_{{name}})) {
        return false;
      }
      input_from_lpm = {{generated_mojom_type}}::New{{name|under_to_camel(digits_split=false)}}(std::move(local_{{name}}));
      break;
{%-       endif %}
    }
{%      endfor %}
    default: {
      return false;
    }
  }

  ::mojo::Message mojolpm_message(0, 0, 0, 0, nullptr);
  ::mojo::internal::MessageFragment<{{data_type}}> mojolpm_fragment(
      mojolpm_message);

  ::mojo::internal::Serializer<{{dataview_type}}, {{generated_mojom_type}}Ptr>::Serialize(
    input_from_lpm, mojolpm_fragment, /*inlined=*/false);
  auto validator = +[] (
      const void* data, ::mojo::internal::ValidationContext* validation_context) {
    return {{data_type}}::Validate(data, validation_context, /*inlined=*/false);
  };
  return ::mojo::internal::DeserializeImpl<{{dataview_type}}>(
      mojolpm_message, mojolpm_message.payload(), mojolpm_message.payload_num_bytes(),
      &output, validator);
}

bool FromProto(
    const {{proto_type}}& input,
    {{mojom_type}}& output) {
  if (input.instance_case() == {{proto_type}}::kOld) {
    {{union_type}}* old = mojolpm::GetContext()->GetInstance<{{union_type}}>(input.old());
    if (old) {
      return FromProto(*old, output);
    }
  } else {
    return FromProto(input.new_(), output);
  }

  return false;
}
{%- endmacro -%}
