{
  // SPDX-FileCopyrightText: 2022 Free Mobile
  // SPDX-License-Identifier: AGPL-3.0-only

  package filter

  import (
    "errors"
    "fmt"
    "net/netip"

    "akvorado/common/helpers"
  )
}

Input ← _ expr:Expr _ EOF {
  meta := c.globalStore["meta"].(*Meta)
  return c.compileExpr(toSlice(expr), meta), nil
}

Expr "expression" ← head:(SubExpr / NotExpr / ConditionExpr) rest:( _ ( KW_AND / KW_OR ) _ Expr )* {
  expr := []any{head}
  for _, e := range toSlice(rest) {
    rest := toSlice(e)
    expr = append(expr, strings.ToUpper(toString(rest[1])))
    expr = append(expr, rest[3])
  }
  return expr, nil
}
SubExpr "sub-expression" ← '(' _ expr:Expr _ ')' {
  return []any{"(", expr, ")"}, nil
}
NotExpr "NOT expression" ← KW_NOT _ expr:Expr {
  return []any{"NOT", expr}, nil
}

ConditionExpr "conditional" ←
    ConditionIPExpr
  / ConditionPrefixExpr
  / ConditionMACExpr
  / ConditionStringExpr
  / ConditionBoundaryExpr
  / ConditionUintExpr
  / ConditionArrayUintExpr
  / ConditionASExpr
  / ConditionASPathExpr
  / ConditionCommunitiesExpr
  / ConditionETypeExpr
  / ConditionProtoExpr

ColumnIP ←
 column:[A-Za-z0-9]+ !IdentStart
   &{ return c.columnIsOfType(column, "ip") }
    { return c.acceptColumn() }
ConditionIPExpr "condition on IP" ←
   column:ColumnIP _
   operator:("=" / "!=") _ ip:IP {
     return []any{column, operator, "toIPv6(", quote(ip), ")"}, nil
   }
 / column:ColumnIP _
   operator:"<<" _ subnet:Subnet {
     return []any{column, subnet}, nil
   }
 / column:ColumnIP _
   operator:"!<<" _ subnet:Subnet {
     return []any{column, "NOT", subnet}, nil
   }
 / column:ColumnIP _
   operator:InOperator _ '(' _ value:ListIP _ ')' {
     return []any{column, operator, "(", value, ")"}, nil
   }


ConditionPrefixExpr "condition on prefix" ←
   column:("SrcNetPrefix"i !IdentStart { return c.acceptColumn() }) _
   operator:("=" / "!=") _
   prefix:SourcePrefix {
     switch toString(operator) {
       case "=": return []any{prefix}, nil
       case "!=": return []any{"NOT (", prefix, ")"}, nil
     }
     return "", nil
   }
 / column:("DstNetPrefix"i !IdentStart { return c.acceptColumn() }) _
   operator:("=" / "!=") _
   prefix:DestinationPrefix {
     switch toString(operator) {
       case "=": return []any{prefix}, nil
       case "!=": return []any{"NOT (", prefix, ")"}, nil
     }
     return "", nil
   }

ConditionMACExpr "condition on MAC" ←
   column:("SrcMAC"i !IdentStart { return c.acceptColumn() }
         / "DstMAC"i !IdentStart { return c.acceptColumn() }) _
   operator:("=" / "!=") _ mac:MAC {
       return []any{column, operator, "MACStringToNum(", quote(mac), ")"}, nil
   }

ConditionStringExpr "condition on string" ←
 column:(value:[A-Za-z0-9]+ !IdentStart
           &{ return c.columnIsOfType(value, "string") }
            { return c.acceptColumn() }) _
 rcond:RConditionStringExpr {
  return []any{column, rcond}, nil
}
RConditionStringExpr "condition on string" ←
   operator:("=" / "!=" / LikeOperator ) _ str:StringLiteral {
     return []any{operator, quote(str)}, nil
   }
 / operator:InOperator _ '(' _ value:ListString _ ')' {
  return []any{operator, "(", value, ")"}, nil
   }

ConditionBoundaryExpr "condition on boundary" ←
 column:("InIfBoundary"i !IdentStart { return c.acceptColumn() }
      / "OutIfBoundary"i !IdentStart { return c.acceptColumn() }) _
 operator:("=" / "!=") _
 boundary:("external"i / "internal"i / "undefined"i) {
  return []any{column, operator, quote(strings.ToLower(toString(boundary)))}, nil
}

ConditionUintExpr "condition on integer" ←
 column:(value:[A-Za-z0-9]+ !IdentStart
           &{ return c.columnIsOfType(value, "uint") }
            { return c.acceptColumn() }) _
 operator:("=" / ">=" / "<=" / "<" / ">" / "!=") _
 value:Unsigned64 {
  return []any{column, operator, value}, nil
}

ConditionArrayUintExpr "condition on array of integers" ←
   column:(value:[A-Za-z0-9]+ !IdentStart
           &{ return c.columnIsOfType(value, "array(uint)") }
            { return c.acceptColumn() }) _
   "=" _ value:Unsigned64 {
     return []any{"has(", column, ",", value, ")"}, nil
   }
 / column:(value:[A-Za-z0-9]+ !IdentStart
           &{ return c.columnIsOfType(value, "array(uint)") }
            { return c.acceptColumn() }) _
   "!=" _ value:Unsigned64 {
     return []any{"NOT has(", column, ",", value, ")"}, nil
   }

ConditionASExpr "condition on AS number" ←
 column:("SrcAS"i !IdentStart { return c.acceptColumn() }
       / "DstAS"i !IdentStart { return c.acceptColumn() }
       / "Dst1stAS"i !IdentStart { return c.acceptColumn() }
       / "Dst2ndAS"i !IdentStart { return c.acceptColumn() }
       / "Dst3rdAS"i !IdentStart { return c.acceptColumn() }) _
 rcond:RConditionASExpr {
  return []any{column, rcond}, nil
}
RConditionASExpr "condition on AS number" ←
   operator:("=" / "!=") _ value:ASN { return []any{operator, value}, nil }
 / operator:InOperator _ '(' _ value:ListASN _ ')' {
  return []any{operator, "(", value, ")"}, nil
}

ConditionASPathExpr "condition on AS path" ←
   column:("DstASPath"i !IdentStart { return c.acceptColumn() }) _ "=" _ value:ASN { return []any{"has(", column, ",", value, ")"}, nil }
 / column:("DstASPath"i !IdentStart { return c.acceptColumn() }) _ "!=" _ value:ASN { return []any{"NOT has(", column, ",", value, ")"}, nil }

ConditionCommunitiesExpr "condition on communities" ←
   column:("DstCommunities"i !IdentStart { return c.acceptColumn() }) _ "=" _ value:Community { return []any{"has(", column, ",", value, ")"}, nil }
 / column:("DstCommunities"i !IdentStart { return c.acceptColumn() }) _ "!=" _ value:Community { return []any{"NOT has(", column, ",", value, ")"}, nil }
 / column:("DstCommunities"i !IdentStart { return c.acceptColumn() }) _ "=" _ value:LargeCommunity { return []any{"has(", c.getColumn("DstLargeCommunities"), ",", value, ")"}, nil }
 / column:("DstCommunities"i !IdentStart { return c.acceptColumn() }) _ "!=" _ value:LargeCommunity { return []any{"NOT has(", c.getColumn("DstLargeCommunities"), ",", value, ")"}, nil }

ConditionETypeExpr "condition on Ethernet type" ←
 column:("EType"i !IdentStart { return c.acceptColumn() }) _
 operator:("=" / "!=") _ value:("IPv4"i / "IPv6"i) {
  etypes := map[string]uint16{
    "ipv4": helpers.ETypeIPv4,
    "ipv6": helpers.ETypeIPv6,
   }
   etype := etypes[strings.ToLower(toString(value))]
   return []any{column, operator, etype}, nil
}
ConditionProtoExpr "condition on protocol" ← ConditionProtoIntExpr / ConditionProtoStrExpr
ConditionProtoIntExpr "condition on protocol as integer" ←
 column:("Proto"i !IdentStart { return c.acceptColumn() }) _
 operator:("=" / ">=" / "<=" / "<" / ">" / "!=") _ value:Unsigned8 {
  return []any{column, operator, value}, nil
}
ConditionProtoStrExpr "condition on protocol as string" ←
 column:("Proto"i !IdentStart { return c.acceptColumn() }) _
 operator:("=" / "!=") _ value:StringLiteral {
  return []any{"dictGetOrDefault('protocols', 'name', ", column, ", '???')", operator, quote(value)}, nil
}

IP "IP address" ← [0-9A-Fa-f:.]+ !IdentStart {
  ip, err := netip.ParseAddr(string(c.text))
  if err != nil {
    return "", errors.New("expecting an IP address")
  }
  return ip.String(), nil
}
ListIP "list IP addresses" ←
   head:IP _ ',' _ tail:ListIP { return fmt.Sprintf("toIPv6(%s), %s", quote(head), tail), nil }
 / value:IP { return fmt.Sprintf("toIPv6(%s)", quote(value)), nil }

Subnet "IP subnet" ← [0-9A-Fa-f:.]+ "/" [0-9]+ !IdentStart {
  net, err := netip.ParsePrefix(string(c.text))
  if err != nil {
    return "", errors.New("expecting a subnet")
  }
  if net.Addr().Is6() {
    return fmt.Sprintf("BETWEEN toIPv6('%s') AND toIPv6('%s')", net.Masked().Addr().String(), lastIP(net).String()), nil
  }
  return fmt.Sprintf("BETWEEN toIPv6('::ffff:%s') AND toIPv6('::ffff:%s')", net.Masked().Addr().String(), lastIP(net).String()), nil
}

SourcePrefix "IP prefix" ← [0-9A-Fa-f:.]+ "/" [0-9]+ !IdentStart {
  return c.parsePrefix("Src")
}
DestinationPrefix "IP prefix" ← [0-9A-Fa-f:.]+ "/" [0-9]+ !IdentStart {
  return c.parsePrefix("Dst")
}

MAC "MAC address" ← [0-9A-Fa-f:.]+ !IdentStart {
  hw, err := net.ParseMAC(string(c.text))
  if err != nil {
    return "", errors.New("expecting a MAC address")
  }
  if len(hw) != 6 {
    return "", errors.New("expecting a 6-byte MAC address")
  }
  return hw.String(), nil
}

ASN "AS number" ← "AS"i? value:Unsigned32 !IdentStart {
  return value, nil
}
ListASN "list of AS numbers" ←
   head:ASN _ ',' _ tail:ListASN { return fmt.Sprintf("%s, %s", toString(head), tail), nil }
 / value:ASN { return toString(value), nil }

Community "community" ← value1:Unsigned16 ":" value2:Unsigned16 !IdentStart !":" {
  return (uint32(value1.(uint16)) << 16) + uint32(value2.(uint16)), nil
}
LargeCommunity "large community" ← value1:Unsigned32 ":" value2:Unsigned32 ":" value3:Unsigned32 !IdentStart !":" {
  return fmt.Sprintf("bitShiftLeft(%d::UInt128, 64) + bitShiftLeft(%d::UInt128, 32) + %d::UInt128", value1, value2, value3), nil
}

StringLiteral "quoted string" ← ( '"' DoubleStringChar* '"' / "'" SingleStringChar* "'" ) {
    return string(c.text[1:len(c.text)-1]), nil
} / ( ( '"' DoubleStringChar* ( EOL / EOF ) ) / ( "'" SingleStringChar* ( EOL / EOF ) ) ) {
    return "", errors.New("string literal not terminated")
}
SourceChar ← .
DoubleStringChar ← !( '"' / EOL ) SourceChar
SingleStringChar ← !( "'" / EOL ) SourceChar
ListString "list of strings" ←
   head:StringLiteral _ ',' _ tail:ListString { return fmt.Sprintf("%s, %s", quote(head), tail), nil }
 / value:StringLiteral { return quote(value), nil }

Unsigned8 "unsigned 8-bit integer" ← [0-9]+ !IdentStart {
  v, err := strconv.ParseUint(string(c.text), 10, 8)
  if err != nil {
    return "", errors.New("expecting an unsigned 8-bit integer")
  }
  return uint8(v), nil
}

Unsigned16 "unsigned 16-bit integer" ← [0-9]+ !IdentStart {
  v, err := strconv.ParseUint(string(c.text), 10, 16)
  if err != nil {
    return "", errors.New("expecting an unsigned 16-bit integer")
  }
  return uint16(v), nil
}

Unsigned32 "unsigned 32-bit integer" ← [0-9]+ !IdentStart {
  v, err := strconv.ParseUint(string(c.text), 10, 32)
  if err != nil {
    return "", errors.New("expecting an unsigned 32-bit integer")
  }
  return uint32(v), nil
}

Unsigned64 "unsigned 64-bit integer" ← [0-9]+ !IdentStart {
  v, err := strconv.ParseUint(string(c.text), 10, 64)
  if err != nil {
    return "", errors.New("expecting an unsigned integer")
  }
  return uint64(v), nil
}

LikeOperator "LIKE operators" ←
   KW_LIKE
 / KW_ILIKE
 / KW_UNLIKE
 / KW_IUNLIKE
InOperator "IN operators" ←
   KW_IN
 / KW_NOTIN
KW_AND "AND operator" ← "AND"i !IdentStart { return "AND", nil }
KW_OR "OR operator" ← "OR"i  !IdentStart { return "OR", nil }
KW_NOT "NOT operator" ← "NOT"i !IdentStart { return "NOT", nil }
KW_LIKE "LIKE operator" ← "LIKE"i !IdentStart { return "LIKE", nil }
KW_ILIKE "ILIKE operator" ← "ILIKE"i !IdentStart { return "ILIKE", nil }
KW_IN "IN operator" ← "IN"i !IdentStart { return "IN", nil }
KW_UNLIKE "UNLIKE operator" ← "UNLIKE"i !IdentStart { return "NOT LIKE", nil }
KW_IUNLIKE "IUNLIKE operator" ← "IUNLIKE"i !IdentStart { return "NOT ILIKE", nil }
KW_NOTIN "NOTIN operator" ← "NOTIN"i !IdentStart { return "NOT IN", nil }

SingleLineComment "comment" ← "--" ( !EOL SourceChar )*
MultiLineComment ← "/*" ( !"*/" SourceChar )* ("*/" / EOF {
  return "", errors.New("comment unclosed")
})
IdentStart ← [A-Za-z_]
_ "whitespace" ← ([ \n\r\t] / SingleLineComment / MultiLineComment)*
EOF ← !.
EOL ← '\n'
