use std::str::FromStr;
use super::ast::*;
use super::template_string::TemplateString;
use super::lex::*;
use std::collections::BTreeMap;
use lalrpop_util::ErrorRecovery;
use ordered_float::NotNan;
use crate::diagnostic::span;
use crate::path::{PathPrefix, OwnedTargetPath, OwnedValuePath, OwnedSegment};
use crate::parser::ast::Expr;
use crate::value::KeyString;


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

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

    enum Tok<'input> {
        "identifier" => Token::Identifier(<&'input str>),
        "path field" => Token::PathField(<&'input str>),
        "string literal" => Token::StringLiteral(<StringLiteralToken<&'input str>>),
        "raw string literal" => Token::RawStringLiteral(<RawStringLiteralToken<&'input str>>),
        "integer literal" => Token::IntegerLiteral(<i64>),
        "float literal" => Token::FloatLiteral(<NotNan<f64>>),
        "regex literal" => Token::RegexLiteral(<&'input str>),
        "timestamp literal" => Token::TimestampLiteral(<&'input str>),

        "function call" => Token::FunctionCall(<&'input str>),

        "invalid token" => Token::InvalidToken(<char>),

        "reserved identifier" => Token::ReservedIdentifier(<&'input str>),

        LQuery => Token::LQuery,
        RQuery => Token::RQuery,

        "if" => Token::If,
        "else" => Token::Else,
        "null" => Token::Null,
        "true" => Token::True,
        "false" => Token::False,
        "abort" => Token::Abort,
        "return" => Token::Return,

        ";" => Token::SemiColon,
        "\n" => Token::Newline,
        "|" => Token::Pipe,
        "=" => Token::Equals,
        "|=" => Token::MergeEquals,
        "," => Token::Comma,
        "_" => Token::Underscore,
        ":" => Token::Colon,
        "." => Token::Dot,
        "&" => Token::Ampersand,
        "!" => Token::Bang,
        "->" => Token::Arrow,
        "%" => Token::Percent,

        "+" => Token::Operator("+"),
        "*" => Token::Operator("*"),
        "-" => Token::Operator("-"),
        "/" => Token::Operator("/"),
        "<" => Token::Operator("<"),
        "<=" => Token::Operator("<="),
        ">" => Token::Operator(">"),
        ">=" => Token::Operator(">="),
        "==" => Token::Operator("=="),
        "!=" => Token::Operator("!="),
        "|" => Token::Operator("|"),

        "&&" => Token::Operator("&&"),
        "||" => Token::Operator("||"),
        "??" => Token::Operator("??"),

        "[" => Token::LBracket,
        "{" => Token::LBrace,
        "(" => Token::LParen,

        "]" => Token::RBracket,
        "}" => Token::RBrace,
        ")" => Token::RParen,
    }
}

// -----------------------------------------------------------------------------
// parser entry points
// -----------------------------------------------------------------------------

// The main entrypoint into a VRL program.
//
// A program consists of one or more expressions.
pub Program: Program = NonterminalNewline* <RootExprs> => Program(<>);

// -----------------------------------------------------------------------------
// root expressions
// -----------------------------------------------------------------------------

#[inline]
RootExprs: Vec<Node<RootExpr>> = {
    RootExpr => vec![<>],
    (<RootExpr> EndOfExpression)*,
    <v:(<RootExpr> EndOfExpression)+> <e:RootExpr> => {
        let mut v = v;
        v.push(e);
        v
    },
};

RootExpr: Node<RootExpr> = {
    Expr => Node::new(<>.span(), RootExpr::Expr(<>)),

    // Root expressions are allowed to fail. The parser will continue with the
    // next expression in the program.
    Sp<!> => {
        let Node { span, node } = <>;
        let ErrorRecovery { error: source, dropped_tokens } = node;

        let reserved_keyword = dropped_tokens
            .first()
            .cloned()
            .into_iter()
            .find_map(|(start, t, end)| match t {
                Token::ReservedIdentifier(s) => Some((start, s.to_owned(), end)),
                _ => None,
            });

        if let Some((start, keyword, end)) = reserved_keyword {
            let error = Error::ReservedKeyword {
                start,
                keyword: keyword,
                end,
            };

            return Node::new(span, RootExpr::Error(error));
        }

        let source = source
            .map_token(|t| t.map(|s| s.to_owned()))
            .map_error(|e| e.to_string());

        let dropped_tokens = dropped_tokens
            .into_iter()
            .map(|(l, t, r)| (l, t.map(|s| s.to_owned()), r))
            .collect();

        let error = Error::ParseError {
            span,
            source,
            dropped_tokens,
        };

        Node::new(span, RootExpr::Error(error))
    },
};

// -----------------------------------------------------------------------------
// expressions
// -----------------------------------------------------------------------------

#[inline]
Exprs: Vec<Node<Expr>> = {
    Expr => vec![<>],
    <v:(<Expr> EndOfExpression)+> <e:(<Expr>)?> => match e {
        None => v,
        Some(e) => {
            let mut v = v;
            v.push(e);
            v
        }
    }
};


Expr: Node<Expr> = {
    Sp<IfStatement> => Node::new(<>.span(), Expr::IfStatement(<>)),
    Sp<AbortExpr>,
    Sp<ReturnExpr>,
    AssignmentExpr,
};

EndOfExpression: () = {
    "\n"+ => (),
    ";" "\n"* => (),
};

NonterminalNewline: () = "\n";

Ident: Ident = "identifier" => Ident(<>.to_owned());

AbortExpr: Expr = {
    Sp<"abort"> => Expr::Abort(<>.map(|_| Abort { message: None })),
    <n: Sp<"abort">> <message: Expr> => Expr::Abort(n.map(|_| Abort { message: Some(Box::new(message.clone())) })),
}

ReturnExpr: Expr = {
    <ret: Sp<"return">> <expr: Expr> => {
        // let span = span(ret.span().start(), expr.span().end());
        // let ret = Return(Node::new(span, Return(expr)));
        // Expr::Return(ret)
        Expr::Return(
            ret.map(|_| Return { expr: Box::new(expr.clone()) })
        )
    },
};

// An identifier that is allowed to include reserved keywords.
#[inline]
AnyIdent: Ident = {
    "identifier" => Ident(<>.to_owned()),
    "reserved identifier" => Ident(<>.to_owned()),
    "if" => Ident("if".to_owned()),
    "else" => Ident("else".to_owned()),
    "null" => Ident("null".to_owned()),
    "true" => Ident("true".to_owned()),
    "false" => Ident("false".to_owned()),
    "abort" => Ident("abort".to_owned()),
    "return" => Ident("return".to_owned()),
};

// -----------------------------------------------------------------------------
// assignment
// -----------------------------------------------------------------------------

AssignmentExpr: Node<Expr> = {
    Assignment => Node::new(<>.span(), Expr::Assignment(<>)),
    ArithmeticExpr,
};

#[inline]
Assignment: Node<Assignment> = {
    Sp<AssignmentSingle>,
    Sp<AssignmentInfallible>,
};

AssignmentOp: AssignmentOp = {
    "=" => AssignmentOp::Assign,
    "|=" => AssignmentOp::Merge,
}

#[inline]
AssignmentSingle: Assignment = {
    <target: Sp<AssignmentTarget>>
        <op: AssignmentOp>
        NonterminalNewline*
        <expr: Box<Expr>> => Assignment::Single { target, op, expr },
}

#[inline]
AssignmentInfallible: Assignment = {
    <ok: Sp<AssignmentTarget>> ","
        <err: Sp<AssignmentTarget>>
        <op: AssignmentOp>
        NonterminalNewline*
        <expr: Box<Expr>> => Assignment::Infallible{ ok, err, op, expr},
}

#[inline]
AssignmentTarget: AssignmentTarget = {
    "_" => AssignmentTarget::Noop,
    Query => AssignmentTarget::Query(<>),
    Ident => AssignmentTarget::Internal(<>, None),
};

// -----------------------------------------------------------------------------
// arithmetic
// -----------------------------------------------------------------------------

ArithmeticExpr: Node<Expr> = {
    Sp<ErrorCoalesce>,
};

ErrorCoalesce: Expr = {
    Op<ErrorCoalesce, "??", Logical>,
    Logical,
};

Logical: Expr = {
    Op<Logical, "||", Equal>,
    Op<Logical, "&&", Equal>,
    Equal,
};

Equal: Expr = {
    Op<Equal, "!=", Compare>,
    Op<Equal, "==", Compare>,
    Compare,
};

Compare: Expr = {
    Op<Compare, ">=", Merge>,
    Op<Compare, ">", Merge>,
    Op<Compare, "<=", Merge>,
    Op<Compare, "<", Merge>,
    Merge,
};

Merge: Expr = {
    Op<Merge, "|", Add>,
    Add,
}

Add: Expr = {
    Op<Add, "+", Factor>,
    Op<Add, "-", Factor>,
    Factor,
};

Factor: Expr = {
    Op<Factor, "*", Not>,
    Op<Factor, "/", Not>,
    Not,
};

Not: Expr = {
    <bang: Sp<"!">> <expr: Box<Sp<Not>>> => {
        let span = span(bang.span().start(), expr.span().end());
        let not = Unary::Not(Node::new(span, Not(bang.map(|_| ()), expr)));
        Expr::Unary(Node::new(span, not))
    },
    Term,
};

#[inline]
Term: Expr = {
    Sp<Literal> => Expr::Literal(<>),
    Sp<Container> => Expr::Container(<>),
    Sp<Query> => Expr::Query(<>),
    Sp<FunctionCall> => Expr::FunctionCall(<>),
    Sp<Ident> => Expr::Variable(<>),
};

// -----------------------------------------------------------------------------
// query
// -----------------------------------------------------------------------------

pub Query: Query = {
    LQuery <Sp<".">> RQuery => {
        let span = <>.span();
        let target = Node::new(span, QueryTarget::External(PathPrefix::Event));
        let path = Node::new(span, OwnedValuePath::root());

        Query { target, path }
    },
    LQuery <Sp<"%">> RQuery => {
        let span = <>.span();
        let target = Node::new(span, QueryTarget::External(PathPrefix::Metadata));
        let path = Node::new(span, OwnedValuePath::root());

        Query { target, path }
    },
    LQuery <target: Sp<QueryTarget>> <path: Sp<Path>> RQuery => Query { target, path: path.map(OwnedValuePath::from) },
};

#[inline]
QueryTarget: QueryTarget = {
    Ident => QueryTarget::Internal(<>),
    "." => QueryTarget::External(PathPrefix::Event),
    "%" => QueryTarget::External(PathPrefix::Metadata),
    FunctionCall => QueryTarget::FunctionCall(<>),
    Container => QueryTarget::Container(<>),
};

// -----------------------------------------------------------------------------
// path
// -----------------------------------------------------------------------------

pub Field: KeyString = {
    AnyIdent => <>.to_string().into(),
    PathField => <>.to_string().into(),
    String => <>.to_string().into(),
}

#[inline]
Path: OwnedValuePath = PathSegment+ => OwnedValuePath::from(<>);

#[inline]
PathSegment: OwnedSegment = {
    "."? <Field> => OwnedSegment::field(&<>),
    "[" <Integer> "]" => OwnedSegment::index(<> as isize),
};

#[inline]
PathField: Ident = "path field" => Ident(<>.to_owned());

// -----------------------------------------------------------------------------
// function call
// -----------------------------------------------------------------------------

FunctionCall: FunctionCall = {
    <ident: Sp<"function call">> <abort_on_error: "!"?> "("
        NonterminalNewline*
        <arguments: CommaMultiline<Sp<FunctionArgument>>?>
    ")" <closure: Sp<FunctionClosure>?> => {
        let ident = ident.map(|s| Ident(s.to_owned()));
        let abort_on_error = abort_on_error.is_some();
        let arguments = arguments.unwrap_or_default();

        FunctionCall { ident, abort_on_error, arguments, closure }
    },
};

#[inline]
FunctionArgument: FunctionArgument = {
    <ident: (<Sp<AnyIdent>> ":")?> <expr: ArithmeticExpr> => FunctionArgument { <> },
};

#[inline]
FunctionClosure: FunctionClosure = {
    "->" <variables: ClosureVariables> NonterminalNewline* <block: Sp<Block>> =>
        FunctionClosure { variables, block }
};

#[inline]
ClosureVariables: Vec<Node<Ident>> = {
    "||" => vec![],
    "|" <variables: CommaMultiline<ClosureVariable>?> "|" =>
        variables.unwrap_or_default()
};

#[inline]
ClosureVariable: Node<Ident> = {
    Sp<Ident> => <>,
    Sp<"_"> => <>.map(|s| Ident("".to_owned())),
};

// -----------------------------------------------------------------------------
// if statement
// -----------------------------------------------------------------------------

IfStatement: IfStatement =
    "if"
    <predicate: Sp<Predicate>>
    NonterminalNewline*
    <consequent: Sp<Block>>
    <mut alternatives: (Sp<ElseIf>)*>
    <alternative: ("else" NonterminalNewline* <Sp<Block>>)?> => {
        let mut alternative = alternative;
        alternatives.reverse();
        for Node { span, mut node } in alternatives {
            node.else_node = alternative;
            let node = Node::new(span, Expr::IfStatement(Node::new(span, node)));

            alternative = Some(Node::new(node.span(), Block(vec![node])));
        }

        IfStatement { predicate, if_node: consequent, else_node: alternative }
};

#[inline]
ElseIf: IfStatement =
    "else" NonterminalNewline* "if"
    <predicate: Sp<Predicate>>
    NonterminalNewline*
    <consequent: Sp<Block>> => {
    IfStatement { predicate, if_node: consequent, else_node: None }
};

#[inline]
StatementSeparator: () = {
    NonterminalNewline, ";"
};

Predicate: Predicate = {
    Box<ArithmeticExpr> => Predicate::One(<>),
    "(" NonterminalNewline* <v:(<AssignmentExpr> StatementSeparator+)+> <e:(<AssignmentExpr>)?> ")" => {
        let expressions = match e {
            None => v,
            Some(e) => {
                let mut v = v;
                v.push(e);
                v
            }
        };

        Predicate::Many(expressions)
    },
};

// -----------------------------------------------------------------------------
// containers
// -----------------------------------------------------------------------------

Container: Container = {
    Box<Sp<Group>> => Container::Group(<>),
    Sp<Block> => Container::Block(<>),
    Sp<Array> => Container::Array(<>),
    Sp<Object> => Container::Object(<>),
};

#[inline]
Group: Group = "(" <AssignmentExpr> ")" => Group(<>);

Block: Block = "{" NonterminalNewline* <Exprs>  "}" => Block(<>);

#[inline]
Array: Array = {
    "[" NonterminalNewline* "]" => Array(vec![]),
    "[" NonterminalNewline* <CommaMultiline<ArithmeticExpr>> "]" => Array(<>),
};

#[inline]
Object: Object = {
    "{" NonterminalNewline* "}" => Object(BTreeMap::default()),
    "{" NonterminalNewline* <CommaMultiline<(<Sp<ObjectKey>> ":" <ArithmeticExpr>)>> "}" => {
        let object =<>.into_iter().collect::<BTreeMap<_, _>>();
        Object(object)
    },
};

#[inline]
ObjectKey: String = "string literal" => <>.unescape();

// -----------------------------------------------------------------------------
// literals
// -----------------------------------------------------------------------------

pub Literal: Literal = {
    String => Literal::String(<>),
    RawString => Literal::RawString(<>),
    Integer => Literal::Integer(<>),
    Float => Literal::Float(<>),
    Boolean => Literal::Boolean(<>),
    Null => Literal::Null,
    Regex => Literal::Regex(<>),
    Timestamp => Literal::Timestamp(<>),
};

String: TemplateString = Sp<"string literal"> => {
    let Node { span, node } = <>;
    node.template(span)
};

RawString: String = "raw string literal" => <>.unescape();
Integer: i64 = "integer literal";
Float: NotNan<f64> = "float literal";
Boolean: bool = { "true" => true, "false" => false };
Null: () = "null";
Regex: String = "regex literal" => <>.replace("\\'", "'");
Timestamp: String = "timestamp literal" => <>.replace("\\'", "'");

// -----------------------------------------------------------------------------
// macros
// -----------------------------------------------------------------------------

Box<T>: Box<T> = T => Box::new(<>);

Sp<T>: Node<T> = <l: @L> <rule: T> <r: @R> => Node::new(span(l, r), rule);

Op<L, Code, R>: Expr = Sp<(<Sp<L>> <Sp<Code>> NonterminalNewline* <Sp<R>>)> => {
    let op = <>.map(|(lhs, code, rhs)| {
        let (span, code) = code.take();
        let op = match code {
            Token::Operator(s) => Opcode::from_str(s).unwrap(),
            _ => panic!("must always be an operator"),
        };

        Op(Box::new(lhs), Node::new(span, op), Box::new(rhs))
    });

    Expr::Op(op)
};

CommaMultiline<T>: Vec<T> = {
    <T> NonterminalNewline* => vec![<>],
    <v:(<T> "," NonterminalNewline*)+> <e:(<T> NonterminalNewline*)?> => match e {
        None => v,
        Some(e) => {
            let mut v = v;
            v.push(e);
            v
        }
    }
};
