use crate::datadog::grok::ast::*;
use crate::datadog::grok::lexer::*;
use crate::path::{OwnedValuePath, OwnedSegment};
use crate::owned_value_path;
use ordered_float::NotNan;
use crate::value::Value;

grammar<'err, 'input>(input: &'input str);

extern {
    type Location = usize;
    type Error = Error;

    enum Tok<'input> {
        "identifier" => Token::Identifier(<&'input str>),
        "extended identifier" => Token::ExtendedIdentifier(<&'input str>),
        "string literal" => Token::StringLiteral(<String>),
        "integer literal" => Token::IntegerLiteral(<i64>),
        "float literal" => Token::FloatLiteral(<NotNan<f64>>),

        "null" => Token::Null,
        "true" => Token::True,
        "false" => Token::False,

        "," => Token::Comma,
        ":" => Token::Colon,
        "." => Token::Dot,
        "%{" => Token::LRule,
        "}" => Token::RRule,
        "[" => Token::LBracket,
        "]" => Token::RBracket,
        "(" => Token::LParen,
        ")" => Token::RParen,
    }
}

pub GrokFilter: GrokPattern = {
 "%{" <match_fn:FunctionOrRef> <fp:(DestinationAndFilter)?> "}" => GrokPattern{ match_fn, destination: fp },
 "%{" <match_fn:FunctionOrRef> ":" "}" => GrokPattern{ match_fn, destination: None },
}

DestinationAndFilter: Destination = {
    ":" <path:Lookup> <filter_fn:(FilterFn)?> => Destination {path: path, filter_fn},
    ":" <filter_fn:(FilterFn)> => Destination {path: owned_value_path!(), filter_fn: Some(filter_fn)}
}

FilterFn: Function = ":" <FunctionOrRef>;

Lookup: OwnedValuePath = {
  <s: PathSegment> <l: Lookup?> => match l {
    None => owned_value_path!(s),
    Some(mut l) => {
      l.push_front_segment(s);
      l
    }
  }
}

PathSegment: OwnedSegment = {
    "."? <Field> =>  OwnedSegment::field(&<>), // DD path can't start with `.` but the grammar is simpler this way
    "[" <String> "]" => OwnedSegment::field(&<>),
};

Field: String = {
    Identifier => String::from(<>),
    ExtendedIdentifier => String::from(<>),
};

FunctionOrRef: Function = <name:QualifiedName> <args:ArgsList?> => Function { name, args };

QualifiedName: String = <start:Identifier> <end:("." Identifier)*> => {
    let mut name = start.to_owned();
    if !end.is_empty() {
        name.push_str(".");
        let rest: String = end.iter().map(|(t, s)| s.to_string()).collect::<Vec<String>>().join(".");
        name.push_str(rest.as_ref());
    }
    name
};

ArgsList = "(" <CommaList<Arg>> ")";

Arg: FunctionArgument = {
    Literal => FunctionArgument::Arg(<>),
    FunctionOrRef => FunctionArgument::Function(<>),
};

Literal: Value = {
    Integer => Value::Integer(<>),
    Float => Value::Float(<>),
    String => Value::Bytes(<>.into()),
    Boolean => Value::Boolean(<>),
    Null => Value::Null,
};

Integer: i64 = "integer literal";
Float: NotNan<f64> = "float literal";
String: String = "string literal";
ExtendedIdentifier: &'input str = "extended identifier";
Identifier: &'input str = "identifier";
Boolean: bool = { "true" => true, "false" => false };
Null: () = "null";


#[inline]
CommaList<T>: Vec<T> = {
    <mut v:(<T> ",")*> <e:T?> => match e {
        None => v,
        Some(e) => {
            v.push(e);
            v
        }
    }
};
