<%- string_type = Prism::Template::JAVA_STRING_TYPE -%>
package org.prism;

import java.lang.Override;
import java.lang.String;
import java.lang.StringBuilder;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;

// GENERATED BY <%= File.basename(__FILE__) %>
// @formatter:off
public abstract class Nodes {

    public static final <%= string_type %>[] EMPTY_STRING_ARRAY = {};

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Nullable {
    }

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface UnionType {
        Class<? extends Node>[] value();
    }

    public static final class Location {

        public static final Location[] EMPTY_ARRAY = {};

        public final int startOffset;
        public final int length;

        public Location(int startOffset, int length) {
            this.startOffset = startOffset;
            this.length = length;
        }

        public int endOffset() {
            return startOffset + length;
        }
    }

    public static final class Source {
        public final byte[] bytes;
        private int startLine = 1;
        private int[] lineOffsets = null;

        Source(byte[] bytes) {
          this.bytes = bytes;
        }

        void setStartLine(int startLine) {
            this.startLine = startLine;
        }

        void setLineOffsets(int[] lineOffsets) {
            this.lineOffsets = lineOffsets;
        }

        // 1-based
        public int line(int byteOffset) {
            return startLine + findLine(byteOffset);
        }

        // 0-based
        public int findLine(int byteOffset) {
            if (byteOffset >= bytes.length) byteOffset = bytes.length - 1;
            assert byteOffset >= 0 : byteOffset;
            int index = Arrays.binarySearch(lineOffsets, byteOffset);
            int line;
            if (index < 0) {
                line = -index - 2;
            } else {
                line = index;
            }
            assert line >= 0 && line <= getLineCount() : line;
            return line;
        }

        public int getLineCount() {
            return lineOffsets.length;
        }
    }

    public static abstract class Node {

        public static final Node[] EMPTY_ARRAY = {};

        <%- if Prism::Template::INCLUDE_NODE_ID -%>
        public final int nodeId;
        <%- end -%>
        public final int startOffset;
        public final int length;
        private boolean newLineFlag = false;

        <%- if Prism::Template::INCLUDE_NODE_ID -%>
        public Node(int nodeId, int startOffset, int length) {
            this.nodeId = nodeId;
        <%- else -%>
        public Node(int startOffset, int length) {
        <%- end -%>
            this.startOffset = startOffset;
            this.length = length;
        }

        public final int endOffset() {
            return startOffset + length;
        }

        public final boolean hasNewLineFlag() {
            return newLineFlag;
        }

        public void setNewLineFlag(Source source, boolean[] newlineMarked) {
            int line = source.findLine(this.startOffset);
            if (!newlineMarked[line]) {
                newlineMarked[line] = true;
                this.newLineFlag = true;
            }
        }

        public void setNewLineFlag(boolean newLineFlag) {
            this.newLineFlag = newLineFlag;
        }

        public abstract <T> T accept(AbstractNodeVisitor<T> visitor);

        public abstract <T> void visitChildNodes(AbstractNodeVisitor<T> visitor);

        public abstract Node[] childNodes();

        @Override
        public String toString() {
            return toString("");
        }

        protected abstract String toString(String indent);
    }
<%# FLAGS -%>
    <%- flags.each do |flag| -%>

    /**
     * <%= Prism::Template::JavaDoc.escape(flag.comment) %>
     */
    public static final class <%= flag.name %> implements Comparable<<%= flag.name %>> {
        <%- flag.values.each_with_index do |value, index| -%>

        // <%= value.comment %>
        public static final short <%= value.name %> = 1 << <%= index + Prism::Template::COMMON_FLAGS_COUNT %>;
        <%- end -%>

        <%- flag.values.each do |value| -%>
        public static boolean is<%= value.camelcase %>(short flags) {
            return (flags & <%= value.name %>) != 0;
        }

        <%- end -%>
        private final short flags;

        public <%= flag.name %>(short flags) {
            this.flags = flags;
        }

        @Override
        public int hashCode() {
            return flags;
        }

        @Override
        public boolean equals(Object other) {
            if (!(other instanceof <%= flag.name %>)) {
                return false;
            }

            return flags == ((<%= flag.name %>) other).flags;
        }

        @Override
        public int compareTo(<%= flag.name %> other) {
            return flags - other.flags;
        }

        <%- flag.values.each do |value| -%>
        public boolean is<%= value.camelcase %>() {
            return (flags & <%= value.name %>) != 0;
        }

        <%- end -%>
    }
<%- end -%>
<%# NODES -%>
    <%- nodes.each do |node| -%>

    /**
     * <pre>
    <%- node.each_comment_java_line do |line| -%>
     *<%= line %>
    <%- end -%>
     * </pre>
     */
    public static final class <%= node.name -%> extends Node {
        <%- if node.needs_serialized_length? -%>
        public final int serializedLength;
        <%- end -%>
        <%- if node.flags -%>
        public final short flags;
        <%- end -%>
        <%- node.semantic_fields.each do |field| -%>
        <%- if field.comment -%>
        /**
         * <pre>
        <%- field.each_comment_java_line do |line| -%>
         *<%= line %>
        <%- end -%>
         * </pre>
         */
        <%- end -%>
        <%- if field.class.name.include?("Optional") -%>
        @Nullable
        <%- end -%>
        <%- if field.respond_to?(:union_kind) && field.union_kind -%>
        @UnionType({ <%= field.union_kind.map { |t| "#{t}.class" }.join(", ") %> })
        <%- end -%>
        public final <%= field.java_type %> <%= field.name %>;
        <%- end -%>

        <%-
          params = []
          params << "int nodeId" if Prism::Template::INCLUDE_NODE_ID
          params << "int startOffset" << "int length"
          params << "int serializedLength" if node.needs_serialized_length?
          params << "short flags" if node.flags
          params.concat(node.semantic_fields.map { |field| "#{field.java_type} #{field.name}" })
        -%>
        public <%= node.name -%>(<%= params.join(", ") %>) {
        <%- if Prism::Template::INCLUDE_NODE_ID -%>
            super(nodeId, startOffset, length);
        <%- else -%>
            super(startOffset, length);
        <%- end -%>
        <%- if node.needs_serialized_length? -%>
            this.serializedLength = serializedLength;
        <%- end -%>
        <%- if node.flags -%>
            this.flags = flags;
        <%- end -%>
        <%- node.semantic_fields.each do |field| -%>
            this.<%= field.name %> = <%= field.name %>;
        <%- end -%>
        }
        <%# methods for flags -%>
        <%- if (node_flags = node.flags) -%>
        <%- node_flags.values.each do |value| -%>

        public boolean is<%= value.camelcase %>() {
            return <%= node_flags.name %>.is<%= value.camelcase %>(flags);
        }
        <%- end -%>
        <%- end -%>
        <%# potential override of setNewLineFlag() -%>
        <%- if node.newline == false -%>

        @Override
        public void setNewLineFlag(Source source, boolean[] newlineMarked) {
            // Never mark <%= node.name %> with a newline flag, mark children instead
        }
        <%- elsif node.newline.is_a?(String) -%>

        @Override
        public void setNewLineFlag(Source source, boolean[] newlineMarked) {
          <%- field = node.semantic_fields.find { |f| f.name == node.newline } or raise node.newline -%>
          <%- case field -%>
          <%- when Prism::Template::NodeField -%>
            this.<%= field.name %>.setNewLineFlag(source, newlineMarked);
          <%- when Prism::Template::NodeListField -%>
            Node first = this.<%= field.name %>.length > 0 ? this.<%= field.name %>[0] : null;
            if (first != null) {
                first.setNewLineFlag(source, newlineMarked);
            }
          <%- else raise field.class.name -%>
          <%- end -%>
        }
        <%- end -%>

        public <T> void visitChildNodes(AbstractNodeVisitor<T> visitor) {
          <%- node.semantic_fields.each do |field| -%>
            <%- case field -%>
            <%- when Prism::Template::NodeListField -%>
            for (Nodes.Node child : this.<%= field.name %>) {
                child.accept(visitor);
            }
            <%- when Prism::Template::NodeField -%>
            this.<%= field.name %>.accept(visitor);
            <%- when Prism::Template::OptionalNodeField -%>
            if (this.<%= field.name %> != null) {
                this.<%= field.name %>.accept(visitor);
            }
            <%- end -%>
          <%- end -%>
        }

        public Node[] childNodes() {
          <%- if node.semantic_fields.none?(Prism::Template::NodeListField) and node.semantic_fields.none?(Prism::Template::NodeKindField) -%>
            return EMPTY_ARRAY;
          <%- elsif node.semantic_fields.one?(Prism::Template::NodeListField) and node.semantic_fields.none?(Prism::Template::NodeKindField) -%>
            return this.<%= node.semantic_fields.grep(Prism::Template::NodeListField).first.name %>;
          <%- elsif node.semantic_fields.none?(Prism::Template::NodeListField) -%>
            return new Node[] { <%= node.semantic_fields.grep(Prism::Template::NodeKindField).map { |field| "this.#{field.name}" }.join(', ') %> };
          <%- else -%>
            ArrayList<Node> childNodes = new ArrayList<>();
            <%- node.semantic_fields.each do |field| -%>
              <%- case field -%>
              <%- when Prism::Template::NodeField, Prism::Template::OptionalNodeField -%>
            childNodes.add(this.<%= field.name %>);
              <%- when Prism::Template::NodeListField -%>
            childNodes.addAll(Arrays.asList(this.<%= field.name %>));
              <%- end -%>
            <%- end -%>
            return childNodes.toArray(EMPTY_ARRAY);
          <%- end -%>
        }

        public <T> T accept(AbstractNodeVisitor<T> visitor) {
            return visitor.visit<%= node.name -%>(this);
        }

        @Override
        protected String toString(String indent) {
            StringBuilder builder = new StringBuilder();
            builder.append(this.getClass().getSimpleName());
            if (hasNewLineFlag()) {
                builder.append("[Li]");
            }
            builder.append('\n');
            String nextIndent = indent + "  ";
            <%- if node.fields.any?(Prism::Template::NodeListField) or node.fields.any?(Prism::Template::ConstantListField) -%>
            String nextNextIndent = nextIndent + "  ";
            <%- end -%>
            <%- [*node.flags, *node.fields.grep_v(Prism::Template::LocationField).grep_v(Prism::Template::OptionalLocationField)].each do |field| -%>
            builder.append(nextIndent);
            builder.append("<%= field.name %>: ");
            <%- case field -%>
            <%- when Prism::Template::NodeField -%>
            builder.append(this.<%= field.name %>.toString(nextIndent));
            <%- when Prism::Template::OptionalNodeField -%>
            builder.append(this.<%= field.name %> == null ? "null\n" : this.<%= field.name %>.toString(nextIndent));
            <%- when Prism::Template::NodeListField -%>
            builder.append('\n');
            for (Node child : this.<%= field.name %>) {
                builder.append(nextNextIndent).append(child.toString(nextNextIndent));
            }
            <%- when Prism::Template::StringField -%>
            builder.append('"' + new String(this.<%= field.name %>, StandardCharsets.UTF_8) + '"');
            builder.append('\n');
            <%- when Prism::Template::ConstantField -%>
            builder.append('"').append(this.<%= field.name %>).append('"');
            builder.append('\n');
            <%- when Prism::Template::OptionalConstantField -%>
            builder.append(this.<%= field.name %> == null ? "null" : "\"" + this.<%= field.name %> + "\"");
            builder.append('\n');
            <%- when Prism::Template::ConstantListField -%>
            builder.append('\n');
            for (<%= string_type %> constant : this.<%= field.name %>) {
                builder.append(nextNextIndent).append('"').append(constant).append('"').append('\n');
            }
            <%- when Prism::Template::Flags -%>
            builder.append(flags);
            builder.append('\n');
            <%- when Prism::Template::UInt8Field, Prism::Template::UInt32Field, Prism::Template::IntegerField, Prism::Template::DoubleField -%>
            builder.append(this.<%= field.name %>);
            builder.append('\n');
            <%- else -%>
            <%- raise field.class.name -%>
            <%- end -%>
            <%- end -%>
            return builder.toString();
        }
    }
    <%- end -%>

    public enum ErrorType {
        <%- errors.each do |error| -%>
        <%= error.name %>,
        <%- end -%>
    }

    public static ErrorType[] ERROR_TYPES = ErrorType.values();

    public enum WarningType {
        <%- warnings.each do |warning| -%>
        <%= warning.name %>,
        <%- end -%>
    }

    public static WarningType[] WARNING_TYPES = WarningType.values();
}
// @formatter:on
