{
    package generator

    import (
        "math"
        "strings"
    )

    func asString(values []interface{}) string {
        var builder strings.Builder
        for _, value := range values {
            bites := value.([]byte)
            builder.Write(bites)
        }
        return builder.String()
    }
}

EOF = !.
NewLine = [\n\r]
Space = [  \t]

LineComment = `//` comment:[^\n\r]* (NewLine / EOF)
{
    return strings.Trim(asString(comment.([]interface{})), `  `), nil
}

BlockComment = `/*` comment:(([^*] / ('*' !'/'))*) `*/`
{
    return string(c.text), nil
}

LineEnding = NewLine / EOF / ';' / &'}'

RestOfLine = Space* BlockComment? (LineComment / LineEnding)

Spacey = Space / NewLine / BlockComment / LineComment


CustomIdentifier = first:[\pL]i rest:[\pL0-9_]i*
{
    firstStr := string(first.([]byte))
    if rest == nil {
        return firstStr, nil
    } else {
        return firstStr + asString(rest.([]interface{})), nil
    }
}


// Type identifiers

TypeIdentifier = TypeIdentifierBaseArraySuffix / TypeIdentifierArrayPrefix / TypeIdentifierMapPrefixArraySuffix / TypeCustomIdentifierArraySuffix

TypeIdentifierBase = name:(`bool` / `byte` / `uint8` / `int8` / `uint16` / `int16` / `uint32` / `int32` / `uint64` / `int64` / `float32` / `float64` / `string` / `guid` / `date`)
{
    return NewBaseType(string(name.([]byte)))
}

TypeIdentifierBaseArraySuffix = tipe:TypeIdentifierBase arrays:`[]`*
{
    return NewArrayOfType(tipe.(BaseType), arrays)
}

TypeIdentifierArrayPrefix = `array[` Spacey* of:TypeIdentifier Spacey* `]`
{
    return ArrayType{Of: of.(Type)}, nil
}

TypeIdentifierMapPrefixArraySuffix = `map[` Spacey* key:TypeIdentifier Spacey* `,` Spacey* value:TypeIdentifier Spacey* `]` arrays:`[]`*
{
    return NewArrayOfType(MapType{Key: key.(Type), Value: value.(Type)}, arrays)
}

TypeCustomIdentifierArraySuffix = name:CustomIdentifier arrays:`[]`*
{
    return NewArrayOfType(NewCustomType(name.(string)), arrays)
}


// Literal Values

LiteralValue = HexInteger / DecimalNumber / Integer / FloatSpecial / String / Bool

HexInteger = `0x` HexDigit+
{
    return strconv.ParseInt(string(c.text), 0, 64)
}

DecimalNumber = Integer '.' DecimalDigit+ Exponent?
{
    return strconv.ParseFloat(string(c.text), 64)
}

Integer = ('-' NonZeroDecimalDigit DecimalDigit*
{
    return strconv.ParseInt(string(c.text), 0, 64)
}) / NonNegativeInteger

NonNegativeInteger = ('0' { return uint64(0), nil }) / (sign:'+'? NonZeroDecimalDigit DecimalDigit* {
    if sign == nil {
        return strconv.ParseUint(string(c.text), 0, 64)
    } else {
        return strconv.ParseUint(string(c.text[1:]), 0, 64)
    }
})

Exponent = 'e'i [+-]? DecimalDigit+

FloatSpecial = (sign:[+-]? `inf`i
{
    if sign == nil || (sign.([]byte))[0] == '+' {
        return math.Inf(1), nil
    } else {
        return math.Inf(-1), nil
    }
}) / (`nan`i
{
    return math.NaN(), nil
})

String = '"' ( !EscapedChar . / '\\' EscapeSequence )* '"'
{
    c.text = bytes.Replace(c.text, []byte(`\/`), []byte(`/`), -1)
    return strconv.Unquote(string(c.text))
}

EscapedChar = [\x00-\x1f"\\]

EscapeSequence = SingleCharEscape / UnicodeEscape

SingleCharEscape = ["\\/bfnrt]

UnicodeEscape = 'u' HexDigit HexDigit HexDigit HexDigit

DecimalDigit = [0-9]

NonZeroDecimalDigit = [1-9]

HexDigit = [0-9a-f]i

Bool = `true` { return true, nil } / `false` { return false, nil }


// Const

Const = `const`i Spacey+ tipe:TypeIdentifierBase Spacey+ name:CustomIdentifier Spacey* '=' Spacey* value:LiteralValue
{
    return NewConst(c, tipe.(BaseType), name.(string), value)
}


// Enum

Flags = '[' Spacey* `flags`i Spacey* ']'

Deprecated = '[' Spacey* `deprecated`i Spacey* reason:( '(' Spacey* String Spacey* ')' Spacey* )? ']'
{
    return NewDeprecated(c, reason)
}

Enum = flags:(Flags Spacey*)?
    `enum`i Spacey+ name:CustomIdentifier tipe:(Spacey* ':' Spacey* TypeIdentifierBase)? Spacey+
    '{' fields:EnumField+ Spacey* '}'
{
    return NewEnum(c, flags, tipe, name.(string), fields)
}

EnumField = Spacey* deprecated:(Deprecated Spacey*)? name:CustomIdentifier Spacey* '=' Spacey* value:(HexInteger / Integer) RestOfLine
{
    valueInt64 := asInt64(value)
    return NewEnumField(c, deprecated, name.(string), valueInt64)
}


// Struct

Opcode = '[' Spacey* `opcode`i Spacey* number:( '(' Spacey* (String / HexInteger / Integer) Spacey* ')' Spacey* )? ']'
{
    return NewOpcode(c, number)
}

Readonly = `readonly`i

Struct = opcode:(Opcode Spacey*)?
    readonly:(Readonly Spacey+)?
    `struct`i Spacey+ name:CustomIdentifier Spacey*
    '{' fields:StructField* Spacey* '}'
{
    return NewStruct(c, opcode, readonly, name.(string), fields)
}

StructField = Spacey* tipe:TypeIdentifier Spacey+ name:CustomIdentifier RestOfLine
{
    return NewStructField(c, tipe.(Type), name.(string))
}


// Message

Message = opcode:(Opcode Spacey*)?
    `message`i Spacey+ name:CustomIdentifier Spacey*
    '{' fields:MessageField* Spacey* '}'
{
    return NewMessage(c, opcode, name.(string), fields)
}

MessageField = Spacey* deprecated:(Deprecated Spacey*)? index:NonNegativeInteger Spacey* `->` Spacey* tipe:TypeIdentifier Spacey+ name:CustomIdentifier RestOfLine
{
    return NewMessageField(c, deprecated, index.(uint64), tipe.(Type), name.(string))
}


// Union

Union = opcode:(Opcode Spacey*)?
    `union`i Spacey+ name:CustomIdentifier Spacey*
    '{' fields:UnionField+ Spacey* '}'
{
    return NewUnion(c, opcode, name.(string), fields)
}

UnionField = Spacey* index:NonNegativeInteger Spacey* `->` Spacey* content:(Struct / Message) RestOfLine
{
    return NewUnionField(c, index.(uint64), content)
}


// Import

Import = `import`i Spacey+ path:String
{
    return NewImport(c, path.(string))
}


// Root

Root = (Spacey* (
    (ipt:Import #{
        c.state[`file`].(*File).AddImport(ipt.(*Import))
        return nil
    }) / (konst:Const #{
        c.state[`file`].(*File).AddConst(konst.(*Const))
        return nil
    }) / (enum:Enum #{
        c.state[`file`].(*File).AddEnum(enum.(*Enum))
        return nil
    }) / (strukt:Struct #{
        c.state[`file`].(*File).AddStruct(strukt.(*Struct))
        return nil
    }) / (message:Message #{
        c.state[`file`].(*File).AddMessage(message.(*Message))
        return nil
    }) / (union:Union #{
        c.state[`file`].(*File).AddUnion(union.(*Union))
        return nil
    })) RestOfLine)* Spacey* EOF {
  return c.state[`file`], nil
}
