#line <%= __LINE__ + 1 %> "prism/templates/src/<%= File.basename(__FILE__) %>"
#include "prism/node.h"

/**
 * Attempts to grow the node list to the next size. If there is already
 * capacity in the list, this function does nothing. Otherwise it reallocates
 * the list to be twice as large as it was before. If the reallocation fails,
 * this function returns false, otherwise it returns true.
 */
static bool
pm_node_list_grow(pm_node_list_t *list, size_t size) {
    size_t requested_size = list->size + size;

    // If the requested size caused overflow, return false.
    if (requested_size < list->size) return false;

    // If the requested size is within the existing capacity, return true.
    if (requested_size < list->capacity) return true;

    // Otherwise, reallocate the list to be twice as large as it was before.
    size_t next_capacity = list->capacity == 0 ? 4 : list->capacity * 2;

    // If multiplying by 2 caused overflow, return false.
    if (next_capacity < list->capacity) return false;

    // If we didn't get enough by doubling, keep doubling until we do.
    while (requested_size > next_capacity) {
        size_t double_capacity = next_capacity * 2;

        // Ensure we didn't overflow by multiplying by 2.
        if (double_capacity < next_capacity) return false;
        next_capacity = double_capacity;
    }

    pm_node_t **nodes = (pm_node_t **) xrealloc(list->nodes, sizeof(pm_node_t *) * next_capacity);
    if (nodes == NULL) return false;

    list->nodes = nodes;
    list->capacity = next_capacity;
    return true;
}

/**
 * Append a new node onto the end of the node list.
 */
void
pm_node_list_append(pm_node_list_t *list, pm_node_t *node) {
    if (pm_node_list_grow(list, 1)) {
        list->nodes[list->size++] = node;
    }
}

/**
 * Prepend a new node onto the beginning of the node list.
 */
void
pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node) {
    if (pm_node_list_grow(list, 1)) {
        memmove(list->nodes + 1, list->nodes, list->size * sizeof(pm_node_t *));
        list->nodes[0] = node;
        list->size++;
    }
}

/**
 * Concatenate the given node list onto the end of the other node list.
 */
void
pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other) {
    if (other->size > 0 && pm_node_list_grow(list, other->size)) {
        memcpy(list->nodes + list->size, other->nodes, other->size * sizeof(pm_node_t *));
        list->size += other->size;
    }
}

/**
 * Free the internal memory associated with the given node list.
 */
void
pm_node_list_free(pm_node_list_t *list) {
    if (list->capacity > 0) {
        xfree(list->nodes);
        *list = (pm_node_list_t) { 0 };
    }
}

PRISM_EXPORTED_FUNCTION void
pm_node_destroy(pm_parser_t *parser, pm_node_t *node);

/**
 * Destroy the nodes that are contained within the given node list.
 */
static void
pm_node_list_destroy(pm_parser_t *parser, pm_node_list_t *list) {
    pm_node_t *node;
    PM_NODE_LIST_FOREACH(list, index, node) pm_node_destroy(parser, node);
    pm_node_list_free(list);
}

/**
 * Deallocate the space for a pm_node_t. Similarly to pm_node_alloc, we're not
 * using the parser argument, but it's there to allow for the future possibility
 * of pre-allocating larger memory pools.
 */
PRISM_EXPORTED_FUNCTION void
pm_node_destroy(pm_parser_t *parser, pm_node_t *node) {
    switch (PM_NODE_TYPE(node)) {
        <%- nodes.each do |node| -%>
#line <%= __LINE__ + 1 %> "prism/templates/src/<%= File.basename(__FILE__) %>"
        case <%= node.type %>: {
            <%- if node.fields.any? { |field| ![Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField].include?(field.class) } -%>
            pm_<%= node.human %>_t *cast = (pm_<%= node.human %>_t *) node;
            <%- end -%>
            <%- node.fields.each do |field| -%>
            <%- case field -%>
            <%- when Prism::Template::LocationField, Prism::Template::OptionalLocationField, Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::ConstantField, Prism::Template::OptionalConstantField, Prism::Template::DoubleField -%>
            <%- when Prism::Template::NodeField -%>
            pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>);
            <%- when Prism::Template::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_node_destroy(parser, (pm_node_t *)cast-><%= field.name %>);
            }
            <%- when Prism::Template::StringField -%>
            pm_string_free(&cast-><%= field.name %>);
            <%- when Prism::Template::NodeListField -%>
            pm_node_list_destroy(parser, &cast-><%= field.name %>);
            <%- when Prism::Template::ConstantListField -%>
            pm_constant_id_list_free(&cast-><%= field.name %>);
            <%- when Prism::Template::IntegerField -%>
            pm_integer_free(&cast-><%= field.name %>);
            <%- else -%>
            <%- raise -%>
            <%- end -%>
            <%- end -%>
            break;
        }
        <%- end -%>
#line <%= __LINE__ + 1 %> "prism/templates/src/<%= File.basename(__FILE__) %>"
        default:
            assert(false && "unreachable");
            break;
    }
    xfree(node);
}

/**
 * Returns a string representation of the given node type.
 */
PRISM_EXPORTED_FUNCTION const char *
pm_node_type_to_str(pm_node_type_t node_type)
{
    switch (node_type) {
<%- nodes.each do |node| -%>
        case <%= node.type %>:
            return "<%= node.type %>";
<%- end -%>
    }
    return "";
}

/**
 * Visit each of the nodes in this subtree using the given visitor callback. The
 * callback function will be called for each node in the subtree. If it returns
 * false, then that node's children will not be visited. If it returns true,
 * then the children will be visited. The data parameter is treated as an opaque
 * pointer and is passed to the visitor callback for consumers to use as they
 * see fit.
 */
PRISM_EXPORTED_FUNCTION void
pm_visit_node(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data) {
    if (visitor(node, data)) pm_visit_child_nodes(node, visitor, data);
}

/**
 * Visit the children of the given node with the given callback. This is the
 * default behavior for walking the tree that is called from pm_visit_node if
 * the callback returns true.
 */
PRISM_EXPORTED_FUNCTION void
pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data) {
    switch (PM_NODE_TYPE(node)) {
        <%- nodes.each do |node| -%>
        <%- if (fields = node.fields.select { |field| field.is_a?(Prism::Template::NodeField) || field.is_a?(Prism::Template::OptionalNodeField) || field.is_a?(Prism::Template::NodeListField) }).any? -%>
        case <%= node.type %>: {
            const pm_<%= node.human %>_t *cast = (const pm_<%= node.human %>_t *) node;
            <%- fields.each do |field| -%>

            // Visit the <%= field.name %> field
            <%- case field -%>
            <%- when Prism::Template::NodeField -%>
            pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data);
            <%- when Prism::Template::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_visit_node((const pm_node_t *) cast-><%= field.name %>, visitor, data);
            }
            <%- when Prism::Template::NodeListField -%>
            const pm_node_list_t *<%= field.name %> = &cast-><%= field.name %>;
            for (size_t index = 0; index < <%= field.name %>->size; index++) {
                pm_visit_node(<%= field.name %>->nodes[index], visitor, data);
            }
            <%- end -%>
            <%- end -%>

            break;
        }
        <%- else -%>
        case <%= node.type %>:
            break;
        <%- end -%>
        <%- end -%>
        case PM_SCOPE_NODE:
            break;
    }
}

// We optionally support dumping to JSON. For systems that don't want or need
// this functionality, it can be turned off with the PRISM_EXCLUDE_JSON define.
#ifndef PRISM_EXCLUDE_JSON

static void
pm_dump_json_constant(pm_buffer_t *buffer, const pm_parser_t *parser, pm_constant_id_t constant_id) {
    const pm_constant_t *constant = pm_constant_pool_id_to_constant(&parser->constant_pool, constant_id);
    pm_buffer_append_byte(buffer, '"');
    pm_buffer_append_source(buffer, constant->start, constant->length, PM_BUFFER_ESCAPING_JSON);
    pm_buffer_append_byte(buffer, '"');
}

static void
pm_dump_json_location(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_location_t *location) {
    uint32_t start = (uint32_t) (location->start - parser->start);
    uint32_t end = (uint32_t) (location->end - parser->start);
    pm_buffer_append_format(buffer, "{\"start\":%" PRIu32 ",\"end\":%" PRIu32 "}", start, end);
}

/**
 * Dump JSON to the given buffer.
 */
PRISM_EXPORTED_FUNCTION void
pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node) {
    switch (PM_NODE_TYPE(node)) {
        <%- nodes.each do |node| -%>
        case <%= node.type %>: {
            pm_buffer_append_string(buffer, "{\"type\":\"<%= node.name %>\",\"location\":", <%= node.name.bytesize + 22 %>);

            const pm_<%= node.human %>_t *cast = (const pm_<%= node.human %>_t *) node;
            pm_dump_json_location(buffer, parser, &cast->base.location);
            <%- [*node.flags, *node.fields].each_with_index do |field, index| -%>

            // Dump the <%= field.name %> field
            pm_buffer_append_byte(buffer, ',');
            pm_buffer_append_string(buffer, "\"<%= field.name %>\":", <%= field.name.bytesize + 3 %>);
            <%- case field -%>
            <%- when Prism::Template::NodeField -%>
            pm_dump_json(buffer, parser, (const pm_node_t *) cast-><%= field.name %>);
            <%- when Prism::Template::OptionalNodeField -%>
            if (cast-><%= field.name %> != NULL) {
                pm_dump_json(buffer, parser, (const pm_node_t *) cast-><%= field.name %>);
            } else {
                pm_buffer_append_string(buffer, "null", 4);
            }
            <%- when Prism::Template::NodeListField -%>
            const pm_node_list_t *<%= field.name %> = &cast-><%= field.name %>;
            pm_buffer_append_byte(buffer, '[');

            for (size_t index = 0; index < <%= field.name %>->size; index++) {
                if (index != 0) pm_buffer_append_byte(buffer, ',');
                pm_dump_json(buffer, parser, <%= field.name %>->nodes[index]);
            }
            pm_buffer_append_byte(buffer, ']');
            <%- when Prism::Template::StringField -%>
            const pm_string_t *<%= field.name %> = &cast-><%= field.name %>;
            pm_buffer_append_byte(buffer, '"');
            pm_buffer_append_source(buffer, pm_string_source(<%= field.name %>), pm_string_length(<%= field.name %>), PM_BUFFER_ESCAPING_JSON);
            pm_buffer_append_byte(buffer, '"');
            <%- when Prism::Template::ConstantField -%>
            pm_dump_json_constant(buffer, parser, cast-><%= field.name %>);
            <%- when Prism::Template::OptionalConstantField -%>
            if (cast-><%= field.name %> != PM_CONSTANT_ID_UNSET) {
                pm_dump_json_constant(buffer, parser, cast-><%= field.name %>);
            } else {
                pm_buffer_append_string(buffer, "null", 4);
            }
            <%- when Prism::Template::ConstantListField -%>
            const pm_constant_id_list_t *<%= field.name %> = &cast-><%= field.name %>;
            pm_buffer_append_byte(buffer, '[');

            for (size_t index = 0; index < <%= field.name %>->size; index++) {
                if (index != 0) pm_buffer_append_byte(buffer, ',');
                pm_dump_json_constant(buffer, parser, <%= field.name %>->ids[index]);
            }
            pm_buffer_append_byte(buffer, ']');
            <%- when Prism::Template::LocationField -%>
            pm_dump_json_location(buffer, parser, &cast-><%= field.name %>);
            <%- when Prism::Template::OptionalLocationField -%>
            if (cast-><%= field.name %>.start != NULL) {
                pm_dump_json_location(buffer, parser, &cast-><%= field.name %>);
            } else {
                pm_buffer_append_string(buffer, "null", 4);
            }
            <%- when Prism::Template::UInt8Field -%>
            pm_buffer_append_format(buffer, "%" PRIu8, cast-><%= field.name %>);
            <%- when Prism::Template::UInt32Field -%>
            pm_buffer_append_format(buffer, "%" PRIu32, cast-><%= field.name %>);
            <%- when Prism::Template::Flags -%>
            size_t flags = 0;
            pm_buffer_append_byte(buffer, '[');
            <%- node.flags.values.each_with_index do |value, index| -%>
            if (PM_NODE_FLAG_P(cast, PM_<%= node.flags.human.upcase %>_<%= value.name %>)) {
                if (flags != 0) pm_buffer_append_byte(buffer, ',');
                pm_buffer_append_string(buffer, "\"<%= value.name %>\"", <%= value.name.bytesize + 2 %>);
                flags++;
            }
            <%- end -%>
            pm_buffer_append_byte(buffer, ']');
            <%- when Prism::Template::IntegerField -%>
            pm_integer_string(buffer, &cast-><%= field.name %>);
            <%- when Prism::Template::DoubleField -%>
            pm_buffer_append_format(buffer, "%f", cast-><%= field.name %>);
            <%- else -%>
            <%- raise %>
            <%- end -%>
            <%- end -%>

            pm_buffer_append_byte(buffer, '}');
            break;
        }
        <%- end -%>
        case PM_SCOPE_NODE:
            break;
    }
}

#endif
