import os

from ..parser import SCALAR_VALUE_TYPES
from ..parser import camel_to_snake_case
from ..parser import parse_file


SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))

HEADER_FMT = '''\
/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Erik Moqvist
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * This file was generated by pbtools.
 */

#ifndef {include_guard}
#define {include_guard}

#ifdef __cplusplus
extern "C" {{
#endif

#include "pbtools.h"
{includes}\

{types}
{declarations}
/* Internal functions. Do not use! */

{internal_declarations}
#ifdef __cplusplus
}}
#endif

#endif
'''

SOURCE_FMT = '''\
/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Erik Moqvist
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * This file was generated by pbtools.
 */

#include <limits.h>
#include "{header}"

#if CHAR_BIT != 8
#    error "Number of bits in a char must be 8."
#endif

{definitions}\
'''

MESSAGE_STRUCT_FMT = '''\
/**
 * Message {message.full_name}.
 */
{repeated_struct}

struct {message.full_name_snake_case}_t {{
    struct pbtools_message_base_t base;
{members}\
}};
'''

REPEATED_MESSAGE_STRUCT_FMT = '''\
struct {message.full_name_snake_case}_repeated_t {{
    int length;
    struct {message.full_name_snake_case}_t *items_p;
}};\
'''

ENUM_FMT = '''\
/**
 * Enum {full_name}.
 */
enum {name}_e {{
{members}
}};
'''

ENUM_MEMBER_FMT = '''\
    {enum.full_name_snake_case}_{field.name_snake_case}_e = {field.field_number}\
'''

ONEOF_STRUCT_FMT = '''\
/**
 * Oneof {oneof.full_name}.
 */
struct {oneof.full_name_snake_case}_oneof_t {{
    enum {oneof.full_name_snake_case}_choice_e choice;
    union {{
{members}
    }} value;
}};
'''

MESSAGE_DECLARATION_FMT = '''\
/**
 * Encoding and decoding of {message.full_name}.
 */
struct {message.full_name_snake_case}_t *
{message.full_name_snake_case}_new(
    void *workspace_p,
    size_t size);

int {message.full_name_snake_case}_encode(
    struct {message.full_name_snake_case}_t *self_p,
    uint8_t *encoded_p,
    size_t size);

int {message.full_name_snake_case}_decode(
    struct {message.full_name_snake_case}_t *self_p,
    const uint8_t *encoded_p,
    size_t size);
'''

REPEATED_DECLARATION_FMT = '''\
int {message.full_name_snake_case}_{field.name_snake_case}_alloc(
    struct {message.full_name_snake_case}_t *self_p,
    int length);
'''

SUB_MESSAGE_ALLOC_DECLARATION_FMT = '''\
int {message.full_name_snake_case}_{field.name_snake_case}_alloc(
    struct {message.full_name_snake_case}_t *self_p);
'''

MESSAGE_DECLARATIONS_FMT = '''\
void {message.full_name_snake_case}_init(
    struct {message.full_name_snake_case}_t *self_p,
    struct pbtools_heap_t *heap_p);

void {message.full_name_snake_case}_encode_inner(
    struct pbtools_encoder_t *encoder_p,
    struct {message.full_name_snake_case}_t *self_p);

void {message.full_name_snake_case}_decode_inner(
    struct pbtools_decoder_t *decoder_p,
    struct {message.full_name_snake_case}_t *self_p);

void {message.full_name_snake_case}_encode_repeated_inner(
    struct pbtools_encoder_t *encoder_p,
    int field_number,
    struct {message.full_name_snake_case}_repeated_t *repeated_p);

void {message.full_name_snake_case}_decode_repeated_inner(
    struct pbtools_decoder_t *decoder_p,
    struct pbtools_repeated_info_t *repeated_info_p,
    struct {message.full_name_snake_case}_repeated_t *repeated_p);
'''

INIT_ONEOF_FMT = '''\
void {oneof.full_name_snake_case}_{field.name_snake_case}_init(
    struct {message.full_name_snake_case}_t *self_p);
'''

MESSAGE_DEFINITIONS_FMT = '''\
void {name}_init(
    struct {name}_t *self_p,
    struct pbtools_heap_t *heap_p)
{{
    self_p->base.heap_p = heap_p;
{members_init}
}}

void {name}_encode_inner(
    struct pbtools_encoder_t *encoder_p,
    struct {name}_t *self_p)
{{
{encode_body}\
}}

void {name}_decode_inner(
    struct pbtools_decoder_t *decoder_p,
    struct {name}_t *self_p)
{{
{unused_decode}\
    int wire_type;
{repeated_infos}
    while (pbtools_decoder_available(decoder_p)) {{
        switch (pbtools_decoder_read_tag(decoder_p, &wire_type)) {{

{decode_body}
        default:
            pbtools_decoder_skip_field(decoder_p, wire_type);
            break;
        }}
    }}
{finalizers}\
}}
'''

ENCODE_MEMBER_FMT = '''\
    pbtools_encoder_write_{field.full_type_snake_case}(\
encoder_p, {field.field_number}, {ref}self_p->{field.name_snake_case});
'''

ENCODE_OPTIONAL_MEMBER_FMT = '''\
    if (self_p->{field.name_snake_case}.is_present) {{
        pbtools_encoder_write_{field.full_type_snake_case}_always(\
encoder_p, {field.field_number}, {ref}self_p->{field.name_snake_case}.value);
    }}
'''

ENCODE_STRING_MEMBER_FMT = '''\
    pbtools_encoder_write_string(\
encoder_p, {field.field_number}, {ref}self_p->{field.name_snake_case}_p);
'''

ENCODE_OPTIONAL_STRING_MEMBER_FMT = '''\
    if (self_p->{field.name_snake_case}.is_present) {{
        pbtools_encoder_write_string_always(\
encoder_p, {field.field_number}, {ref}self_p->{field.name_snake_case}.value_p);
    }}
'''

ENCODE_REPEATED_MEMBER_FMT = '''\
    pbtools_encoder_write_repeated_{field.full_type_snake_case}(\
encoder_p, {field.field_number}, &self_p->{field.name_snake_case});
'''

ENCODE_REPEATED_ENUM_FMT = '''\
    pbtools_encoder_write_repeated_int32(\
encoder_p, {field.field_number}, &self_p->{field.name_snake_case});
'''

ENCODE_SUB_MESSAGE_MEMBER_FMT = '''\
    pbtools_encoder_sub_message_encode(
        encoder_p,
        {field.field_number},
        &self_p->{field.name_snake_case}.base,
        (pbtools_message_encode_inner_t){field.full_type_snake_case}_encode_inner);
'''

ENCODE_SUB_MESSAGE_MEMBER_POINTER_FMT = '''\
    pbtools_encoder_sub_message_pointer_encode(
        encoder_p,
        {field.field_number},
        (struct pbtools_message_base_t *)self_p->{field.name_snake_case}_p,
        (pbtools_message_encode_inner_t){field.full_type_snake_case}_encode_inner);
'''

ENCODE_ENUM_FMT = '''\
    pbtools_encoder_write_enum(encoder_p, {field.field_number}, \
self_p->{field.name_snake_case});
'''

ENCODE_ONEOF_MEMBER_FMT = '''\
    {name}_encode(encoder_p, &self_p->{field_name});
'''

ENCODE_ONEOF_CHOICE_FMT = '''\
    case {oneof.full_name_snake_case}_choice_{field.name_snake_case}_e:
        pbtools_encoder_write_{field.full_type_snake_case}_always(
            encoder_p,
            {field.field_number},
            {ref}self_p->value.{field.name_snake_case});
        break;
'''

ENCODE_ONEOF_STRING_MEMBER_FMT = '''\
    case {oneof.full_name_snake_case}_choice_{field.name_snake_case}_e:
        pbtools_encoder_write_string_always(
            encoder_p,
            {field.field_number},
            {ref}self_p->value.{field.name_snake_case}_p);
        break;
'''

ENCODE_ONEOF_SUB_MESSAGE_MEMBER_FMT = '''\
    case {oneof.full_name_snake_case}_choice_{field.name_snake_case}_e:
        pbtools_encoder_sub_message_encode_always(
            encoder_p,
            {field.field_number},
            &self_p->value.{field.name_snake_case}.base,
            (pbtools_message_encode_inner_t){field.full_type_snake_case}_encode_inner);
        break;
'''

ENCODE_ONEOF_ENUM_FMT = '''\
    case {oneof.full_name_snake_case}_choice_{field.name_snake_case}_e:
        pbtools_encoder_write_enum_always(
            encoder_p,
            {field.field_number},
            self_p->value.{field.name_snake_case});
        break;
'''

ENCODE_REPEATED_MESSAGE_MEMBER_FMT = '''\
    {field.full_type_snake_case}_encode_repeated_inner(
        encoder_p,
        {field.field_number},
        &self_p->{field.name_snake_case});
'''

ENCODE_ONEOF_FMT = '''\
void {oneof.full_name_snake_case}_encode(
    struct pbtools_encoder_t *encoder_p,
    struct {oneof.full_name_snake_case}_oneof_t *self_p)
{{
    switch (self_p->choice) {{

{choices}
    default:
        break;
    }}
}}
'''

DECODE_MEMBER_FMT = '''\
        case {field.field_number}:
            self_p->{field.name_snake_case} = \
pbtools_decoder_read_{field.full_type_snake_case}(decoder_p, wire_type);
            break;
'''

DECODE_OPTIONAL_MEMBER_FMT = '''\
        case {field.field_number}:
            self_p->{field.name_snake_case}.is_present = true;
            self_p->{field.name_snake_case}.value = \
pbtools_decoder_read_{field.full_type_snake_case}(decoder_p, wire_type);
            break;
'''

DECODE_MEMBER_BYTES_FMT = '''\
        case {field.field_number}:
            pbtools_decoder_read_bytes(\
decoder_p, wire_type, &self_p->{field.name_snake_case});
            break;
'''

DECODE_OPTIONAL_MEMBER_BYTES_FMT = '''\
        case {field.field_number}:
            self_p->{field.name_snake_case}.is_present = true;
            pbtools_decoder_read_bytes(\
decoder_p, wire_type, &self_p->{field.name_snake_case}.value);
            break;
'''

DECODE_MEMBER_STRING_FMT = '''\
        case {field.field_number}:
            pbtools_decoder_read_string(\
decoder_p, wire_type, &self_p->{field.name_snake_case}_p);
            break;
'''

DECODE_OPTIONAL_MEMBER_STRING_FMT = '''\
        case {field.field_number}:
            self_p->{field.name_snake_case}.is_present = true;
            pbtools_decoder_read_string(\
decoder_p, wire_type, &self_p->{field.name_snake_case}.value_p);
            break;
'''

DECODE_REPEATED_SCALAR_VALUE_FMT = '''\
        case {field.field_number}:
            pbtools_repeated_info_decode_{field.type}(
                &repeated_info_{field.name_snake_case},
                decoder_p,
                wire_type);
            break;
'''

DECODE_REPEATED_ENUM_FMT = '''\
        case {field.field_number}:
            pbtools_repeated_info_decode_int32(
                &repeated_info_{field.name_snake_case},
                decoder_p,
                wire_type);
            break;
'''

DECODE_REPEATED_FIELD_FMT = '''\
        case {field.field_number}:
            pbtools_repeated_info_decode(&repeated_info_{field.name_snake_case},
                                         decoder_p,
                                         wire_type);
            break;
'''

DECODE_SUB_MESSAGE_MEMBER_FMT = '''\
        case {field.field_number}:
            pbtools_decoder_sub_message_decode(
                decoder_p,
                wire_type,
                &self_p->{field.name_snake_case}.base,
                (pbtools_message_decode_inner_t){field.full_type_snake_case}_decode_inner);
            break;
'''

DECODE_SUB_MESSAGE_MEMBER_POINTER_FMT = '''\
        case {field.field_number}:
            pbtools_decoder_sub_message_pointer_decode(
                decoder_p,
                wire_type,
                (struct pbtools_message_base_t **)&self_p->{field.name_snake_case}_p,
                sizeof(struct {field.full_type_snake_case}_t),
                (pbtools_message_init_t){field.full_type_snake_case}_init,
                (pbtools_message_decode_inner_t){field.full_type_snake_case}_decode_inner);
            break;
'''

DECODE_ENUM_FMT = '''\
        case {field.field_number}:
            self_p->{field.name_snake_case} = pbtools_decoder_read_enum(\
decoder_p, wire_type);
            break;
'''

DECODE_ONEOF_FMT = '''\
        case {field.field_number}:
            {oneof.full_name_snake_case}_{field.name_snake_case}_decode(
                decoder_p,
                wire_type,
                self_p);
            break;
'''

DECODE_ONEOF_MEMBER_BYTES_FMT = '''\
    pbtools_decoder_read_bytes(decoder_p,
                               wire_type,
                               &self_p->{oneof.name_snake_case}.value.\
{field.name_snake_case});
'''

DECODE_ONEOF_MEMBER_STRING_FMT = '''\
    pbtools_decoder_read_string(decoder_p,
                                wire_type,
                                &self_p->{oneof.name_snake_case}.value.\
{field.name_snake_case}_p);
'''

DECODE_ONEOF_MEMBER_FMT = '''\
    self_p->{oneof.name_snake_case}.value.{field.name_snake_case} = \
pbtools_decoder_read_{field.full_type_snake_case}(
        decoder_p,
        wire_type);
'''

DECODE_ONEOF_SUB_MESSAGE_MEMBER_FMT = '''\
    pbtools_decoder_sub_message_decode(
        decoder_p,
        wire_type,
        &self_p->{oneof.name_snake_case}.value.{field.name_snake_case}.base,
        (pbtools_message_decode_inner_t){field.full_type_snake_case}_decode_inner);
'''

DECODE_ONEOF_ENUM_FMT = '''\
    self_p->{oneof.name_snake_case}.value.{field.name_snake_case} = \
pbtools_decoder_read_enum(
        decoder_p,
        wire_type);
'''

INIT_ONEOF_FIELD_FMT = '''\
void {oneof.full_name_snake_case}_{field.name_snake_case}_init(
    struct {message.full_name_snake_case}_t *self_p)
{{
    self_p->{oneof.name_snake_case}.choice = \
{oneof.full_name_snake_case}_choice_{field.name_snake_case}_e;
{init}
}}
'''

DECODE_ONEOF_FIELD_FMT = '''\
static void {oneof.full_name_snake_case}_{field.name_snake_case}_decode(
    struct pbtools_decoder_t *decoder_p,
    int wire_type,
    struct {message.full_name_snake_case}_t *self_p)
{{
    {oneof.full_name_snake_case}_{field.name_snake_case}_init(self_p);
{decode}\
}}
'''

MESSAGE_DEFINITION_FMT = '''\
struct {message.full_name_snake_case}_t *
{message.full_name_snake_case}_new(
    void *workspace_p,
    size_t size)
{{
    return (pbtools_message_new(
                workspace_p,
                size,
                sizeof(struct {message.full_name_snake_case}_t),
                (pbtools_message_init_t){message.full_name_snake_case}_init));
}}

int {message.full_name_snake_case}_encode(
    struct {message.full_name_snake_case}_t *self_p,
    uint8_t *encoded_p,
    size_t size)
{{
    return (pbtools_message_encode(
                &self_p->base,
                encoded_p,
                size,
                (pbtools_message_encode_inner_t)\
{message.full_name_snake_case}_encode_inner));
}}

int {message.full_name_snake_case}_decode(
    struct {message.full_name_snake_case}_t *self_p,
    const uint8_t *encoded_p,
    size_t size)
{{
    return (pbtools_message_decode(
                &self_p->base,
                encoded_p,
                size,
                (pbtools_message_decode_inner_t)\
{message.full_name_snake_case}_decode_inner));
}}
'''

REPEATED_DEFINITION_FMT = '''\
int {message.full_name_snake_case}_{field.name_snake_case}_alloc(
    struct {message.full_name_snake_case}_t *self_p,
    int length)
{{
    return (pbtools_alloc_repeated_{field.full_type_snake_case}(
                &self_p->base,
                length,
                &self_p->{field.name_snake_case}));
}}
'''

SUB_MESSAGE_ALLOC_DEFINITION_FMT = '''\
int {message.full_name_snake_case}_{field.name_snake_case}_alloc(
    struct {message.full_name_snake_case}_t *self_p)
{{
    return (pbtools_sub_message_alloc(
                (struct pbtools_message_base_t **)&self_p->{field.name_snake_case}_p,
                self_p->base.heap_p,
                sizeof(struct {field.full_type_snake_case}_t),
                (pbtools_message_init_t){field.full_type_snake_case}_init));
}}
'''

REPEATED_ENUM_DEFINITION_FMT = '''\
int {message.full_name_snake_case}_{field.name_snake_case}_alloc(
    struct {message.full_name_snake_case}_t *self_p,
    int length)
{{
    return (pbtools_alloc_repeated_int32(
                &self_p->base,
                length,
                &self_p->{field.name_snake_case}));
}}
'''

REPEATED_MESSAGE_DEFINITION_ALLOC_FMT = '''\
int {message.full_name_snake_case}_{field.name_snake_case}_alloc(
    struct {message.full_name_snake_case}_t *self_p,
    int length)
{{
    return (pbtools_alloc_repeated(
                (struct pbtools_repeated_message_t *)&self_p->{field.name_snake_case},
                length,
                self_p->base.heap_p,
                sizeof(struct {field.full_type_snake_case}_t),
                (pbtools_message_init_t){field.full_type_snake_case}_init));
}}
'''

REPEATED_MESSAGE_DEFINITION_FMT = '''\
void {message.full_name_snake_case}_encode_repeated_inner(
    struct pbtools_encoder_t *encoder_p,
    int field_number,
    struct {message.full_name_snake_case}_repeated_t *repeated_p)
{{
    pbtools_encode_repeated_inner(
        encoder_p,
        field_number,
        (struct pbtools_repeated_message_t *)repeated_p,
        sizeof(struct {message.full_name_snake_case}_t),
        (pbtools_message_encode_inner_t){message.full_name_snake_case}_encode_inner);
}}

void {message.full_name_snake_case}_decode_repeated_inner(
    struct pbtools_decoder_t *decoder_p,
    struct pbtools_repeated_info_t *repeated_info_p,
    struct {message.full_name_snake_case}_repeated_t *repeated_p)
{{
    pbtools_decode_repeated_inner(
        decoder_p,
        repeated_info_p,
        (struct pbtools_repeated_message_t *)repeated_p,
        sizeof(struct {message.full_name_snake_case}_t),
        (pbtools_message_init_t){message.full_name_snake_case}_init,
        (pbtools_message_decode_inner_t){message.full_name_snake_case}_decode_inner);
}}
'''

REPEATED_FINALIZER_FMT = '''\
    pbtools_decoder_decode_repeated_{field.full_type_snake_case}(
        decoder_p,
        &repeated_info_{field.name_snake_case},
        &self_p->{field.name_snake_case});\
'''

REPEATED_ENUM_FINALIZER_FMT = '''\
    pbtools_decoder_decode_repeated_int32(
        decoder_p,
        &repeated_info_{field.name_snake_case},
        &self_p->{field.name_snake_case});\
'''

REPEATED_MESSAGE_FINALIZER_FMT = '''\
    {field.full_type_snake_case}_decode_repeated_inner(
        decoder_p,
        &repeated_info_{field.name_snake_case},
        &self_p->{field.name_snake_case});\
'''

OPTIONAL_STRUCT_MEMBER_FMT = '''\
    struct {{
        bool is_present;
        {type}{value_name_snake_case};
    }} {name_snake_case};\
'''


class Generator:

    def __init__(self, namespace, parsed, header_name, sub_message_pointers):
        if parsed.package is not None:
            namespace = camel_to_snake_case(parsed.package)

        self.namespace = namespace
        self.parsed = parsed
        self.header_name = header_name
        self.sub_message_pointers = sub_message_pointers

    @property
    def messages(self):
        return self.parsed.messages

    @property
    def package(self):
        return self.parsed.package

    def generate_struct_member_fmt(self, type, name_snake_case, type_kind):
        if type in ['int32', 'int64', 'uint32', 'uint64']:
            type = f'{type}_t '
        elif type in ['sint32', 'sint64']:
            type = f'{type[1:]}_t '
        elif type in ['fixed32', 'fixed64']:
            type = f'uint{type[5:]}_t '
        elif type in ['sfixed32', 'sfixed64']:
            type = f'int{type[6:]}_t '
        elif type in ['float', 'double', 'bool']:
            type = f'{type} '
        elif type == 'bytes':
            type = f'struct pbtools_bytes_t '
        elif type == 'string':
            type = f'char *'
            name_snake_case = f'{name_snake_case}_p'
        elif type_kind == 'enum':
            type = f'enum {type}_e '
        elif type_kind == 'message':
            if self.sub_message_pointers:
                type = f'struct {type}_t *'
                name_snake_case = f'{name_snake_case}_p'
            else:
                type = f'struct {type}_t '
        else:
            type += ' '

        return f'    {type}{name_snake_case};'

    def generate_optional_struct_member_fmt(self, type, name_snake_case, type_kind):
        value_name_snake_case = 'value'

        if type in ['int32', 'int64', 'uint32', 'uint64']:
            type = f'{type}_t '
        elif type in ['sint32', 'sint64']:
            type = f'{type[1:]}_t '
        elif type in ['fixed32', 'fixed64']:
            type = f'uint{type[5:]}_t '
        elif type in ['sfixed32', 'sfixed64']:
            type = f'int{type[6:]}_t '
        elif type in ['float', 'double', 'bool']:
            type = f'{type} '
        elif type == 'bytes':
            type = f'struct pbtools_bytes_t '
        elif type == 'string':
            type = f'char *'
            value_name_snake_case = f'{value_name_snake_case}_p'
        elif type_kind == 'enum':
            type = f'enum {type}_e '
        else:
            raise Exception('Only scalar value types may be optional.')

        return OPTIONAL_STRUCT_MEMBER_FMT.format(
            type=type,
            name_snake_case=name_snake_case,
            value_name_snake_case=value_name_snake_case)

    def generate_repeated_struct_member_fmt(self,
                                            type,
                                            name_snake_case,
                                            type_kind):
        if type in SCALAR_VALUE_TYPES:
            if type in ['sint32', 'sint64']:
                type = type[1:]
            elif type in ['fixed32', 'fixed64']:
                type = f'uint{type[5:]}'
            elif type in ['sfixed32', 'sfixed64']:
                type = f'int{type[6:]}'

            type = f'struct pbtools_repeated_{type}_t'
        elif type_kind == 'enum':
            type = 'struct pbtools_repeated_int32_t'
        else:
            type = f'struct {type}_repeated_t'

        return f'    {type} {name_snake_case};'

    def generate_struct_members(self, message):
        members = []

        for field in message.fields:
            if field.repeated:
                member = self.generate_repeated_struct_member_fmt(
                    field.full_type_snake_case,
                    field.name_snake_case,
                    field.type_kind)
            elif field.optional:
                member = self.generate_optional_struct_member_fmt(
                    field.full_type_snake_case,
                    field.name_snake_case,
                    field.type_kind)
            else:
                member = self.generate_struct_member_fmt(
                    field.full_type_snake_case,
                    field.name_snake_case,
                    field.type_kind)

            members.append(member)

        for oneof in message.oneofs:
            type = f'{message.full_name_snake_case}_{oneof.name_snake_case}_oneof_t'
            members.append(
                f'    struct {type} {oneof.name_snake_case};')

        members = '\n'.join(members)

        if members:
            members += '\n'

        return members

    def generate_repeated_struct(self, message):
        return REPEATED_MESSAGE_STRUCT_FMT.format(message=message)

    def generate_enum_members(self, enum):
        return ',\n'.join([
            ENUM_MEMBER_FMT.format(enum=enum, field=field)
            for field in enum.fields
        ])

    def generate_enum_type(self, enum):
        return [
            ENUM_FMT.format(full_name=enum.full_name,
                            name=enum.full_name_snake_case,
                            members=self.generate_enum_members(enum))
        ]

    def generate_oneof_members(self, oneof):
        members = []

        for field in oneof.fields:
            member = self.generate_struct_member_fmt(
                field.full_type_snake_case,
                field.name_snake_case,
                field.type_kind)
            members.append(f'    {member}')

        return '\n'.join(members)

    def generate_oneof_choices(self, oneof):
        members = [
            f'    {oneof.full_name_snake_case}_choice_none_e = 0'
        ]

        for i, field in enumerate(oneof.fields, 1):
            members.append(
                f'    {oneof.full_name_snake_case}_choice_{field.name_snake_case}_e = {i}')

        return ENUM_FMT.format(full_name=oneof.full_name,
                               name=f'{oneof.full_name_snake_case}_choice',
                               members=',\n'.join(members))

    def generate_oneof_type(self, oneof):
        types = [self.generate_oneof_choices(oneof)]
        types += [
            ONEOF_STRUCT_FMT.format(oneof=oneof,
                                    members=self.generate_oneof_members(oneof))
        ]

        return ['\n'.join(types)]

    def generate_message_types(self, message):
        types = []

        for enum in message.enums:
            types += self.generate_enum_type(enum)

        for sub_message in message.messages:
            types += self.generate_message_types(sub_message)

        for oneof in message.oneofs:
            types += self.generate_oneof_type(oneof)

        types += [
            MESSAGE_STRUCT_FMT.format(
                message=message,
                repeated_struct=self.generate_repeated_struct(message),
                members=self.generate_struct_members(message))
        ]

        return types

    def generate_types(self):
        types = []

        for enum in self.parsed.enums:
            types += self.generate_enum_type(enum)

        for message in self.messages:
            types += self.generate_message_types(message)

        return '\n'.join(types)

    def generate_declarations(self):
        declarations = []

        for message in self.messages:
            self.generate_message_declarations(message, declarations, True)

        return '\n'.join(declarations)

    def generate_message_declarations(self, message, declarations, public):
        if self.sub_message_pointers:
            for field in message.fields:
                if field.repeated:
                    declarations.append(
                        REPEATED_DECLARATION_FMT.format(message=message, field=field))
                elif field.type_kind == 'message' and self.sub_message_pointers:
                    declarations.append(
                        SUB_MESSAGE_ALLOC_DECLARATION_FMT.format(message=message,
                                                                 field=field))
        else:
            for field in message.repeated_fields:
                declarations.append(
                    REPEATED_DECLARATION_FMT.format(message=message, field=field))


        for sub_message in message.messages:
            self.generate_message_declarations(sub_message,
                                               declarations,
                                               public=False)

        for oneof in message.oneofs:
            for field in oneof.fields:
                declarations.append(
                    INIT_ONEOF_FMT.format(oneof=oneof,
                                          message=message,
                                          field=field))

        if public:
            declarations.append(
                MESSAGE_DECLARATION_FMT.format(message=message))

    def generate_internal_declarations(self):
        declarations = []

        for message in self.messages:
            self.generate_internal_message_declarations(message,
                                                        declarations)

        return '\n'.join(declarations)

    def generate_internal_message_declarations(self, message, declarations):
        declarations.append(MESSAGE_DECLARATIONS_FMT.format(message=message))

        for sub_message in message.messages:
            self.generate_internal_message_declarations(sub_message,
                                                        declarations)

    def generate_message_encode_body(self, message):
        members = []

        for field in reversed(message.fields):
            if field.type_kind == 'scalar-value-type':
                if field.repeated:
                    fmt = ENCODE_REPEATED_MEMBER_FMT
                elif field.type == 'string':
                    if field.optional:
                        fmt = ENCODE_OPTIONAL_STRING_MEMBER_FMT
                    else:
                        fmt = ENCODE_STRING_MEMBER_FMT
                else:
                    if field.optional:
                        fmt = ENCODE_OPTIONAL_MEMBER_FMT
                    else:
                        fmt = ENCODE_MEMBER_FMT
            else:
                if field.repeated:
                    if field.type_kind == 'enum':
                        fmt = ENCODE_REPEATED_ENUM_FMT
                    else:
                        fmt = ENCODE_REPEATED_MESSAGE_MEMBER_FMT
                elif field.type_kind == 'message':
                    if self.sub_message_pointers:
                        fmt = ENCODE_SUB_MESSAGE_MEMBER_POINTER_FMT
                    else:
                        fmt = ENCODE_SUB_MESSAGE_MEMBER_FMT
                else:
                    fmt = ENCODE_ENUM_FMT

            member = fmt.format(field=field,
                                ref='&' if field.type == 'bytes' else '')

            members.append(member)

        for oneof in message.oneofs:
            members.append(
                ENCODE_ONEOF_MEMBER_FMT.format(name=oneof.full_name_snake_case,
                                               field_name=oneof.name_snake_case))

        if not members:
            members = [
                '    (void)encoder_p;\n'
                '    (void)self_p;\n'
            ]

        return ''.join(members)

    def generate_message_decode_body(self, message):
        members = []

        for field in message.fields:
            if field.repeated:
                if field.type_kind == 'scalar-value-type':
                    fmt = DECODE_REPEATED_SCALAR_VALUE_FMT
                elif field.type_kind == 'enum':
                    fmt = DECODE_REPEATED_ENUM_FMT
                else:
                    fmt = DECODE_REPEATED_FIELD_FMT
            elif field.type == 'bytes':
                if field.optional:
                    fmt = DECODE_OPTIONAL_MEMBER_BYTES_FMT
                else:
                    fmt = DECODE_MEMBER_BYTES_FMT
            elif field.type == 'string':
                if field.optional:
                    fmt = DECODE_OPTIONAL_MEMBER_STRING_FMT
                else:
                    fmt = DECODE_MEMBER_STRING_FMT
            elif field.type_kind == 'scalar-value-type':
                if field.optional:
                    fmt = DECODE_OPTIONAL_MEMBER_FMT
                else:
                    fmt = DECODE_MEMBER_FMT
            elif field.type_kind == 'message':
                if self.sub_message_pointers:
                    fmt = DECODE_SUB_MESSAGE_MEMBER_POINTER_FMT
                else:
                    fmt = DECODE_SUB_MESSAGE_MEMBER_FMT
            else:
                fmt = DECODE_ENUM_FMT

            members.append(fmt.format(field=field))

        for oneof in message.oneofs:
            for field in oneof.fields:
                members.append(
                    DECODE_ONEOF_FMT.format(oneof=oneof, field=field))

        return '\n'.join(members)

    def generate_message_members_init(self, message):
        members = []

        for field in message.fields:
            name = field.name_snake_case

            if field.repeated:
                member = f'    self_p->{name}.length = 0;'
            elif field.optional:
                member = f'    self_p->{name}.is_present = false;'
            elif field.type == 'bytes':
                member = f'    pbtools_bytes_init(&self_p->{name});'
            elif field.type == 'string':
                member = f'    self_p->{name}_p = "";'
            elif field.type_kind == 'scalar-value-type':
                member = f'    self_p->{name} = 0;'
            elif field.type_kind == 'message':
                if self.sub_message_pointers:
                    member = f'    self_p->{name}_p = NULL;'
                else:
                    member = (f'    {field.full_type_snake_case}_init(&self_p->{name}, '
                              f'heap_p);')
            else:
                member = f'    self_p->{name} = 0;'

            members.append(member)

        for oneof in message.oneofs:
            members.append(f'    self_p->{oneof.name_snake_case}.choice = 0;')

        return '\n'.join(members)

    def generate_repeated_definitions(self, message):
        members = []

        for field in message.repeated_fields:
            if field.type_kind == 'scalar-value-type':
                fmt = REPEATED_DEFINITION_FMT
            elif field.type_kind == 'enum':
                fmt = REPEATED_ENUM_DEFINITION_FMT
            else:
                fmt = REPEATED_MESSAGE_DEFINITION_ALLOC_FMT

            members.append(fmt.format(message=message, field=field))

        members.append(REPEATED_MESSAGE_DEFINITION_FMT.format(message=message))

        return '\n'.join(members)

    def generate_sub_message_definitions(self, message):
        allocs = []

        for field in message.fields:
            if field.repeated:
                continue

            if field.type_kind != 'message':
                continue

            if not self.sub_message_pointers:
                continue

            allocs.append(
                SUB_MESSAGE_ALLOC_DEFINITION_FMT.format(message=message,
                                                        field=field))

        return '\n'.join(allocs)

    def generate_repeated_finalizers(self, message):
        finalizers = []

        for field in message.repeated_fields:
            if field.type_kind == 'scalar-value-type':
                fmt = REPEATED_FINALIZER_FMT
            elif field.type_kind == 'enum':
                fmt = REPEATED_ENUM_FINALIZER_FMT
            else:
                fmt = REPEATED_MESSAGE_FINALIZER_FMT

            finalizers.append(fmt.format(field=field))

        if finalizers:
            finalizers = [''] + finalizers + ['']

        return '\n'.join(finalizers)

    def generate_repeated_infos(self, message):
        variables = []
        inits = []

        for field in message.repeated_fields:
            variables.append(
                f'    struct pbtools_repeated_info_t '
                f'repeated_info_{field.name_snake_case};')
            inits.append(
                f'    pbtools_repeated_info_init('
                f'&repeated_info_{field.name_snake_case}, '
                f'{field.field_number});')

        if inits:
            inits = [''] + inits + ['']

        return '\n'.join(variables + inits)

    def generate_definitions(self):
        declarations = []
        definitions = []

        for message in self.messages:
            self.generate_message_definitions(message,
                                              declarations,
                                              definitions,
                                              public=True)

        return '\n'.join(declarations + definitions)

    def generate_oneof_init_definitions(self,
                                        message,
                                        oneof,
                                        definitions):
        for field in oneof.fields:
            name = field.name_snake_case

            if field.type == 'bytes':
                init = (f'    pbtools_bytes_init('
                        f'&self_p->{oneof.name_snake_case}.value.{name});')
            elif field.type == 'string':
                init = f'    self_p->{oneof.name_snake_case}.value.{name}_p = "";'
            elif field.type_kind == 'scalar-value-type':
                init = f'    self_p->{oneof.name_snake_case}.value.{name} = 0;'
            elif field.type_kind == 'message':
                init = (f'    {field.full_type_snake_case}_init(\n'
                        f'        &self_p->{oneof.name_snake_case}.value.{name},\n'
                        f'        self_p->base.heap_p);')
            else:
                init = f'    self_p->{oneof.name_snake_case}.value.{name} = 0;'

            definitions.append(
                INIT_ONEOF_FIELD_FMT.format(message=message,
                                            oneof=oneof,
                                            field=field,
                                            init=init))

    def generate_oneof_encode_definitions(self, oneof, definitions):
        choices = []

        for field in oneof.fields:
            if field.type_kind == 'scalar-value-type':
                if field.type == 'string':
                    fmt = ENCODE_ONEOF_STRING_MEMBER_FMT
                else:
                    fmt = ENCODE_ONEOF_CHOICE_FMT
            else:
                if field.type_kind == 'message':
                    fmt = ENCODE_ONEOF_SUB_MESSAGE_MEMBER_FMT
                else:
                    fmt = ENCODE_ONEOF_ENUM_FMT

            choice = fmt.format(oneof=oneof,
                                field=field,
                                ref='&' if field.type == 'bytes' else '')

            choices.append(choice)

        definitions.append(
            ENCODE_ONEOF_FMT.format(oneof=oneof, choices='\n'.join(choices)))

    def generate_oneof_decode_definitions(self,
                                          message,
                                          oneof,
                                          definitions):
        for field in oneof.fields:
            if field.type == 'bytes':
                fmt = DECODE_ONEOF_MEMBER_BYTES_FMT
            elif field.type == 'string':
                fmt = DECODE_ONEOF_MEMBER_STRING_FMT
            elif field.type_kind == 'scalar-value-type':
                fmt = DECODE_ONEOF_MEMBER_FMT
            elif field.type_kind == 'message':
                fmt = DECODE_ONEOF_SUB_MESSAGE_MEMBER_FMT
            else:
                fmt = DECODE_ONEOF_ENUM_FMT

            definitions.append(
                DECODE_ONEOF_FIELD_FMT.format(message=message,
                                              oneof=oneof,
                                              field=field,
                                              decode=fmt.format(oneof=oneof,
                                                                field=field)))

    def generate_oneof_definitions(self,
                                   message,
                                   oneof,
                                   definitions):
        self.generate_oneof_init_definitions(message, oneof, definitions)
        self.generate_oneof_encode_definitions(oneof, definitions)
        self.generate_oneof_decode_definitions(message, oneof, definitions)

    def generate_message_definitions(self,
                                     message,
                                     declarations,
                                     definitions,
                                     public):
        for oneof in message.oneofs:
            self.generate_oneof_definitions(message, oneof, definitions)

        for sub_message in message.messages:
            self.generate_message_definitions(sub_message,
                                              declarations,
                                              definitions,
                                              public=False)

        decode_body = self.generate_message_decode_body(message)

        if decode_body:
            unused_decode = ''
        else:
            unused_decode = '    (void)self_p;\n\n'

        definitions.append(
            MESSAGE_DEFINITIONS_FMT.format(
                name=message.full_name_snake_case,
                encode_body=self.generate_message_encode_body(message),
                decode_body=decode_body,
                unused_decode=unused_decode,
                repeated_infos=self.generate_repeated_infos(message),
                members_init=self.generate_message_members_init(message),
                finalizers=self.generate_repeated_finalizers(message)))

        sub_messages = self.generate_sub_message_definitions(message)

        if sub_messages:
            definitions.append(sub_messages)

        repeated = self.generate_repeated_definitions(message)

        if repeated:
            definitions.append(repeated)

        if public:
            definitions.append(MESSAGE_DEFINITION_FMT.format(message=message))

    def generate_includes(self):
        includes = []

        for imported in self.parsed.imports:
            filename = imported.path.replace('.proto', '.h')
            includes.append(f'#include "{filename}"')

        includes = '\n'.join(includes)

        if includes:
            includes += '\n'

        return includes

    def generate_include_guard(self):
        return f'{os.path.splitext(self.header_name)[0].upper()}_H'

    def generate(self):
        header = HEADER_FMT.format(
            include_guard=self.generate_include_guard(),
            includes=self.generate_includes(),
            types=self.generate_types(),
            declarations=self.generate_declarations(),
            internal_declarations=self.generate_internal_declarations())
        source = SOURCE_FMT.format(header=self.header_name,
                                   definitions=self.generate_definitions())

        return header, source


def generate(namespace, parsed, header_name, sub_message_pointers):
    """Generate C source code from given parsed proto-file.

    """

    return Generator(namespace, parsed, header_name, sub_message_pointers).generate()


def generate_files(infiles,
                   import_paths=None,
                   output_directory='.',
                   sub_message_pointers=False):
    """Generate C source code from proto-file(s).

    """

    for filename in infiles:
        parsed = parse_file(filename, import_paths)
        basename = os.path.basename(filename)
        name = camel_to_snake_case(os.path.splitext(basename)[0])

        filename_h = f'{name}.h'
        filename_c = f'{name}.c'

        header, source = generate(name, parsed, filename_h, sub_message_pointers)
        filename_h = os.path.join(output_directory, filename_h)
        filename_c = os.path.join(output_directory, filename_c)

        with open(filename_h, 'w') as fout:
            fout.write(header)

        with open(filename_c, 'w') as fout:
            fout.write(source)
