// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

syntax = "proto2";
package fuzzing;

import "third_party/dawn/src/dawn/fuzzers/lpmfuzz/dawn_custom_lpm.proto";


// These are hardcoded limits for Dawn Object allocations based on type to help
// guide the fuzzer towards reusing existing objects.
{% for type in by_category["object"] %}
    {% set type_key = type.name.canonical_case() %}
    enum {{ type.name.CamelCase() }}Id {
        {% if type_key in cmd_records["lpm_info"]["limits"] %}
            {% for n in range(cmd_records["lpm_info"]["limits"][type_key]) %}
                {{ type.name.SNAKE_CASE() }}_{{ loop.index }} = {{ loop.index }};
            {% endfor %}
        {% else %}
            {% for n in range(cmd_records["lpm_info"]["limits"]["default"]) %}
                {{ type.name.SNAKE_CASE() }}_{{ loop.index }} = {{ loop.index }};
            {% endfor %}
        {% endif %}
        INVALID_{{ type.name.SNAKE_CASE() }} = {{ cmd_records["lpm_info"]["invalid object id"] }};
    };
{% endfor %}

{% for type in by_category["enum"] %}
    enum {{as_cppType(type.name)}} {
        {% for value in type.values %}
            {{ as_cppType(type.name) }}{{as_cppEnum(value.name)}} = {{ value.value }};
        {% endfor %}
    };
{% endfor %}


{% for type in by_category["bitmask"] %}
    enum {{as_cppType(type.name)}} {
        {% for value in type.values %}
            {{ as_cppType(type.name) }}{{as_cppEnum(value.name)}} = {{ value.value }};
        {% endfor %}
    };
{% endfor %}

{% macro optionality(member) -%}
    {%- if member.optional -%}
        optional
    {%- else -%}
        required
    {%- endif -%}
{%- endmacro %}

{% macro lift_string_proto_member(member, count) -%}
    {{ optionality(member) }} string {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
    {% set count.value = count.value + 1 %}

{%- endmacro %}


{% macro lift_float_array_proto_member(member, count) -%}
    repeated float {{ as_varName(member.name) }} = {{ count.value }};
    {% set count.value = count.value + 1 %}
{%- endmacro %}


{% macro lift_object_member(member, count) %}
    {{ member.type.name.CamelCase() }}Id {{ as_protobufNameLPM(member.name) }}
{% endmacro %}


{% macro lift_objectid_member(member, count) %}
    {{ member.id_type.name.CamelCase() }}Id {{ as_protobufNameLPM(member.name) }}
{% endmacro %}


{% macro lift_varlength_proto_member(member, count) -%}
    {% if member.type in by_category["object"] %}
        repeated {{ lift_object_member(member, count) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% elif member.type.name.get() == "object id" %}
        repeated {{ lift_objectid_member(member, count) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% else %}
        repeated {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% endif %}
{%- endmacro %}


{% macro lift_dawn_member_pass_by_value(record, name, member, count) %}
    {% if member.type.name.get() == "ObjectHandle" or
       member.type.category == "function pointer" or
       member.type.name.get() == "void *" %}
       //* These types should not be serialized using protobuf structures.
       //* Handled by the serializer.
    {% elif member.type in by_category["bitmask"] %}
        repeated {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% elif member.type in by_category["object"] %}
        {{ optionality(member) }}  {{ lift_object_member(member, count) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% elif member.type.name.get() == "ObjectId" %}
        {{ optionality(member) }}  {{ lift_objectid_member(member, count) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% elif member.type in by_category["structure"] or
       member.type in by_category["enum"] %}
        {{ optionality(member) }}  {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% else %}
        {{ optionality(member) }}  {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% endif %}
{% endmacro %}

{% macro lift_dawn_member_pass_by_pointer(record, name, member, count) %}
    {% if member.type.name.get() == "uint8_t" %}
        //* This types should not be serialized using protobuf structures.
        //* Handled by the serializer.
    {% elif member.type in by_category["structure"] and
       member.length == "constant" and
       member.constant_length == 1 %}
        {{ optionality(member) }}  {{ as_protobufTypeLPM(member) }} {{ as_protobufNameLPM(member.name) }} = {{ count.value }};
        {% set count.value = count.value + 1 %}
    {% elif member.type.name.get() == "char" and
       member.length == 'strlen' %}
        {{ lift_string_proto_member(member, count) }}
    {% elif member.type.name.get() == "float" %}
        {{ lift_float_array_proto_member(member, count) }}
    {% elif member.length != 'constant' %}
        {{ lift_varlength_proto_member(member, count) }}
    {% else %}
        //* There shouldn't be any other pass-by-pointer types in
        //* dawn*.json, if any are added we would like to know at compile time
        {{ unreachable_code() }}
    {% endif %}
{% endmacro %}

{% macro lift_proto_members_helper(record, name, members) %}
    {% set count = namespace(value=1) %}
    {% for member in members %}
        {% if member.skip_serialize == True %}
            //  {{ member.name.camelCase()}}.skip_serialize
        {% elif member.annotation == 'value' %}
            {{ lift_dawn_member_pass_by_value(record, name, member, count) }}
        {% elif member.annotation == 'const*' %}
            {{ lift_dawn_member_pass_by_pointer(record, name, member, count) }}
        {% endif %}
    {% endfor %}
{% endmacro %}


{% for structure in by_category["structure"] %}
    message {{structure.name.CamelCase()}} {
        {{ lift_proto_members_helper(structure, structure.name, structure.members) }}
    }
{% endfor %}


{% for command in cmd_records["proto_generated_commands"] %}
    message {{command.name.CamelCase()}} {
        {{ lift_proto_members_helper(command, command.name, command.members) }}
    }
{% endfor %}

message Command {
    oneof command {
        {% for command in cmd_records["proto_all_commands"] %}
            {{command.name.CamelCase()}} {{command.name.camelCase()}} = {{ loop.index }};
        {% endfor %}
    }
}

message Program {
    repeated Command commands = 1;
}
