//! FIXME: write short doc here

use super::*;

// test expr_literals
// fn foo() {
//     let _ = true;
//     let _ = false;
//     let _ = 1;
//     let _ = 2.0;
//     let _ = b'a';
//     let _ = 'b';
//     let _ = "c";
//     let _ = r"d";
//     let _ = b"e";
//     let _ = br"f";
// }
pub(crate) const LITERAL_FIRST: TokenSet = token_set![
    TRUE_KW,
    FALSE_KW,
    INT_NUMBER,
    FLOAT_NUMBER,
    BYTE,
    CHAR,
    STRING,
    RAW_STRING,
    BYTE_STRING,
    RAW_BYTE_STRING
];

pub(crate) fn literal(p: &mut Parser) -> Option<CompletedMarker> {
    if !p.at_ts(LITERAL_FIRST) {
        return None;
    }
    let m = p.start();
    p.bump_any();
    Some(m.complete(p, LITERAL))
}

// E.g. for after the break in `if break {}`, this should not match
pub(super) const ATOM_EXPR_FIRST: TokenSet =
    LITERAL_FIRST.union(paths::PATH_FIRST).union(token_set![
        T!['('],
        T!['{'],
        T!['['],
        L_DOLLAR,
        T![|],
        T![move],
        T![box],
        T![if],
        T![while],
        T![match],
        T![unsafe],
        T![return],
        T![break],
        T![continue],
        T![async],
        T![try],
        T![loop],
        T![for],
        LIFETIME,
    ]);

const EXPR_RECOVERY_SET: TokenSet = token_set![LET_KW, R_DOLLAR];

pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)> {
    if let Some(m) = literal(p) {
        return Some((m, BlockLike::NotBlock));
    }
    if paths::is_path_start(p) {
        return Some(path_expr(p, r));
    }
    let la = p.nth(1);
    let done = match p.current() {
        T!['('] => tuple_expr(p),
        T!['['] => array_expr(p),
        L_DOLLAR => meta_var_expr(p),
        T![|] => lambda_expr(p),
        T![move] if la == T![|] => lambda_expr(p),
        T![async] if la == T![|] || (la == T![move] && p.nth(2) == T![|]) => lambda_expr(p),
        T![if] => if_expr(p),

        T![loop] => loop_expr(p, None),
        T![box] => box_expr(p, None),
        T![for] => for_expr(p, None),
        T![while] => while_expr(p, None),
        T![try] => try_block_expr(p, None),
        LIFETIME if la == T![:] => {
            let m = p.start();
            label(p);
            match p.current() {
                T![loop] => loop_expr(p, Some(m)),
                T![for] => for_expr(p, Some(m)),
                T![while] => while_expr(p, Some(m)),
                // test labeled_block
                // fn f() { 'label: {}; }
                T!['{'] => {
                    block_expr(p);
                    m.complete(p, EFFECT_EXPR)
                }
                _ => {
                    // test_err misplaced_label_err
                    // fn main() {
                    //     'loop: impl
                    // }
                    p.error("expected a loop");
                    m.complete(p, ERROR);
                    return None;
                }
            }
        }
        T![async] if la == T!['{'] || (la == T![move] && p.nth(2) == T!['{']) => {
            let m = p.start();
            p.bump(T![async]);
            p.eat(T![move]);
            block_expr(p);
            m.complete(p, EFFECT_EXPR)
        }
        T![match] => match_expr(p),
        // test unsafe_block
        // fn f() { unsafe { } }
        T![unsafe] if la == T!['{'] => {
            let m = p.start();
            p.bump(T![unsafe]);
            block_expr(p);
            m.complete(p, EFFECT_EXPR)
        }
        T!['{'] => {
            // test for_range_from
            // fn foo() {
            //    for x in 0 .. {
            //        break;
            //    }
            // }
            block_expr_unchecked(p)
        }
        T![return] => return_expr(p),
        T![continue] => continue_expr(p),
        T![break] => break_expr(p, r),
        _ => {
            p.err_recover("expected expression", EXPR_RECOVERY_SET);
            return None;
        }
    };
    let blocklike = match done.kind() {
        IF_EXPR | WHILE_EXPR | FOR_EXPR | LOOP_EXPR | MATCH_EXPR | BLOCK_EXPR | EFFECT_EXPR => {
            BlockLike::Block
        }
        _ => BlockLike::NotBlock,
    };
    Some((done, blocklike))
}

// test tuple_expr
// fn foo() {
//     ();
//     (1);
//     (1,);
// }
fn tuple_expr(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T!['(']));
    let m = p.start();
    p.expect(T!['(']);

    let mut saw_comma = false;
    let mut saw_expr = false;
    while !p.at(EOF) && !p.at(T![')']) {
        saw_expr = true;
        if !p.at_ts(EXPR_FIRST) {
            p.error("expected expression");
            break;
        }
        expr(p);
        if !p.at(T![')']) {
            saw_comma = true;
            p.expect(T![,]);
        }
    }
    p.expect(T![')']);
    m.complete(p, if saw_expr && !saw_comma { PAREN_EXPR } else { TUPLE_EXPR })
}

// test array_expr
// fn foo() {
//     [];
//     [1];
//     [1, 2,];
//     [1; 2];
// }
fn array_expr(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T!['[']));
    let m = p.start();

    let mut n_exprs = 0u32;
    let mut has_semi = false;

    p.bump(T!['[']);
    while !p.at(EOF) && !p.at(T![']']) {
        n_exprs += 1;

        // test array_attrs
        // const A: &[i64] = &[1, #[cfg(test)] 2];
        if !expr_with_attrs(p) {
            break;
        }

        if n_exprs == 1 && p.eat(T![;]) {
            has_semi = true;
            continue;
        }

        if has_semi || !p.at(T![']']) && !p.expect(T![,]) {
            break;
        }
    }
    p.expect(T![']']);

    m.complete(p, ARRAY_EXPR)
}

// test lambda_expr
// fn foo() {
//     || ();
//     || -> i32 { 92 };
//     |x| x;
//     move |x: i32,| x;
//     async || {};
//     move || {};
//     async move || {};
// }
fn lambda_expr(p: &mut Parser) -> CompletedMarker {
    assert!(
        p.at(T![|])
            || (p.at(T![move]) && p.nth(1) == T![|])
            || (p.at(T![async]) && p.nth(1) == T![|])
            || (p.at(T![async]) && p.nth(1) == T![move] && p.nth(2) == T![|])
    );
    let m = p.start();
    p.eat(T![async]);
    p.eat(T![move]);
    params::param_list_closure(p);
    if opt_fn_ret_type(p) {
        // test lambda_ret_block
        // fn main() { || -> i32 { 92 }(); }
        block_expr(p);
    } else {
        if p.at_ts(EXPR_FIRST) {
            expr(p);
        } else {
            p.error("expected expression");
        }
    }
    m.complete(p, CLOSURE_EXPR)
}

// test if_expr
// fn foo() {
//     if true {};
//     if true {} else {};
//     if true {} else if false {} else {};
//     if S {};
//     if { true } { } else { };
// }
fn if_expr(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T![if]));
    let m = p.start();
    p.bump(T![if]);
    cond(p);
    block_expr(p);
    if p.at(T![else]) {
        p.bump(T![else]);
        if p.at(T![if]) {
            if_expr(p);
        } else {
            block_expr(p);
        }
    }
    m.complete(p, IF_EXPR)
}

// test label
// fn foo() {
//     'a: loop {}
//     'b: while true {}
//     'c: for x in () {}
// }
fn label(p: &mut Parser) {
    assert!(p.at(LIFETIME) && p.nth(1) == T![:]);
    let m = p.start();
    p.bump(LIFETIME);
    p.bump_any();
    m.complete(p, LABEL);
}

// test loop_expr
// fn foo() {
//     loop {};
// }
fn loop_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
    assert!(p.at(T![loop]));
    let m = m.unwrap_or_else(|| p.start());
    p.bump(T![loop]);
    block_expr(p);
    m.complete(p, LOOP_EXPR)
}

// test while_expr
// fn foo() {
//     while true {};
//     while let Some(x) = it.next() {};
//     while { true } {};
// }
fn while_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
    assert!(p.at(T![while]));
    let m = m.unwrap_or_else(|| p.start());
    p.bump(T![while]);
    cond(p);
    block_expr(p);
    m.complete(p, WHILE_EXPR)
}

// test for_expr
// fn foo() {
//     for x in [] {};
// }
fn for_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
    assert!(p.at(T![for]));
    let m = m.unwrap_or_else(|| p.start());
    p.bump(T![for]);
    patterns::pattern(p);
    p.expect(T![in]);
    expr_no_struct(p);
    block_expr(p);
    m.complete(p, FOR_EXPR)
}

// test cond
// fn foo() { if let Some(_) = None {} }
// fn bar() {
//     if let Some(_) | Some(_) = None {}
//     if let | Some(_) = None {}
//     while let Some(_) | Some(_) = None {}
//     while let | Some(_) = None {}
// }
fn cond(p: &mut Parser) {
    let m = p.start();
    if p.eat(T![let]) {
        patterns::pattern_top(p);
        p.expect(T![=]);
    }
    expr_no_struct(p);
    m.complete(p, CONDITION);
}

// test match_expr
// fn foo() {
//     match () { };
//     match S {};
//     match { } { _ => () };
//     match { S {} } {};
// }
fn match_expr(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T![match]));
    let m = p.start();
    p.bump(T![match]);
    expr_no_struct(p);
    if p.at(T!['{']) {
        match_arm_list(p);
    } else {
        p.error("expected `{`")
    }
    m.complete(p, MATCH_EXPR)
}

pub(crate) fn match_arm_list(p: &mut Parser) {
    assert!(p.at(T!['{']));
    let m = p.start();
    p.eat(T!['{']);

    // test match_arms_inner_attribute
    // fn foo() {
    //     match () {
    //         #![doc("Inner attribute")]
    //         #![doc("Can be")]
    //         #![doc("Stacked")]
    //         _ => (),
    //     }
    // }
    attributes::inner_attributes(p);

    while !p.at(EOF) && !p.at(T!['}']) {
        if p.at(T!['{']) {
            error_block(p, "expected match arm");
            continue;
        }

        // test match_arms_commas
        // fn foo() {
        //     match () {
        //         _ => (),
        //         _ => {}
        //         _ => ()
        //     }
        // }
        if match_arm(p).is_block() {
            p.eat(T![,]);
        } else if !p.at(T!['}']) {
            p.expect(T![,]);
        }
    }
    p.expect(T!['}']);
    m.complete(p, MATCH_ARM_LIST);
}

// test match_arm
// fn foo() {
//     match () {
//         _ => (),
//         _ if Test > Test{field: 0} => (),
//         X | Y if Z => (),
//         | X | Y if Z => (),
//         | X => (),
//     };
// }
fn match_arm(p: &mut Parser) -> BlockLike {
    let m = p.start();
    // test match_arms_outer_attributes
    // fn foo() {
    //     match () {
    //         #[cfg(feature = "some")]
    //         _ => (),
    //         #[cfg(feature = "other")]
    //         _ => (),
    //         #[cfg(feature = "many")]
    //         #[cfg(feature = "attributes")]
    //         #[cfg(feature = "before")]
    //         _ => (),
    //     }
    // }
    attributes::outer_attributes(p);

    patterns::pattern_top_r(p, TokenSet::EMPTY);
    if p.at(T![if]) {
        match_guard(p);
    }
    p.expect(T![=>]);
    let blocklike = expr_stmt(p).1;
    m.complete(p, MATCH_ARM);
    blocklike
}

// test match_guard
// fn foo() {
//     match () {
//         _ if foo => (),
//     }
// }
fn match_guard(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T![if]));
    let m = p.start();
    p.bump(T![if]);
    expr(p);
    m.complete(p, MATCH_GUARD)
}

// test block
// fn a() {}
// fn b() { let _ = 1; }
// fn c() { 1; 2; }
// fn d() { 1; 2 }
pub(crate) fn block_expr(p: &mut Parser) {
    if !p.at(T!['{']) {
        p.error("expected a block");
        return;
    }
    block_expr_unchecked(p);
}

fn block_expr_unchecked(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T!['{']));
    let m = p.start();
    p.bump(T!['{']);
    expr_block_contents(p);
    p.expect(T!['}']);
    m.complete(p, BLOCK_EXPR)
}

// test return_expr
// fn foo() {
//     return;
//     return 92;
// }
fn return_expr(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T![return]));
    let m = p.start();
    p.bump(T![return]);
    if p.at_ts(EXPR_FIRST) {
        expr(p);
    }
    m.complete(p, RETURN_EXPR)
}

// test continue_expr
// fn foo() {
//     loop {
//         continue;
//         continue 'l;
//     }
// }
fn continue_expr(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(T![continue]));
    let m = p.start();
    p.bump(T![continue]);
    p.eat(LIFETIME);
    m.complete(p, CONTINUE_EXPR)
}

// test break_expr
// fn foo() {
//     loop {
//         break;
//         break 'l;
//         break 92;
//         break 'l 92;
//     }
// }
fn break_expr(p: &mut Parser, r: Restrictions) -> CompletedMarker {
    assert!(p.at(T![break]));
    let m = p.start();
    p.bump(T![break]);
    p.eat(LIFETIME);
    // test break_ambiguity
    // fn foo(){
    //     if break {}
    //     while break {}
    //     for i in break {}
    //     match break {}
    // }
    if p.at_ts(EXPR_FIRST) && !(r.forbid_structs && p.at(T!['{'])) {
        expr(p);
    }
    m.complete(p, BREAK_EXPR)
}

// test try_block_expr
// fn foo() {
//     let _ = try {};
// }
fn try_block_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
    assert!(p.at(T![try]));
    let m = m.unwrap_or_else(|| p.start());
    // Special-case `try!` as macro.
    // This is a hack until we do proper edition support
    if p.nth_at(1, T![!]) {
        // test try_macro_fallback
        // fn foo() { try!(Ok(())); }
        let path = p.start();
        let path_segment = p.start();
        let name_ref = p.start();
        p.bump_remap(IDENT);
        name_ref.complete(p, NAME_REF);
        path_segment.complete(p, PATH_SEGMENT);
        path.complete(p, PATH);
        let _block_like = items::macro_call_after_excl(p);
        return m.complete(p, MACRO_CALL);
    }

    p.bump(T![try]);
    block_expr(p);
    m.complete(p, EFFECT_EXPR)
}

// test box_expr
// fn foo() {
//     let x = box 1i32;
//     let y = (box 1i32, box 2i32);
//     let z = Foo(box 1i32, box 2i32);
// }
fn box_expr(p: &mut Parser, m: Option<Marker>) -> CompletedMarker {
    assert!(p.at(T![box]));
    let m = m.unwrap_or_else(|| p.start());
    p.bump(T![box]);
    if p.at_ts(EXPR_FIRST) {
        expr(p);
    }
    m.complete(p, BOX_EXPR)
}

/// Expression from `$var` macro expansion, wrapped in dollars
fn meta_var_expr(p: &mut Parser) -> CompletedMarker {
    assert!(p.at(L_DOLLAR));
    let m = p.start();
    p.bump(L_DOLLAR);
    let (completed, _is_block) =
        expr_bp(p, Restrictions { forbid_structs: false, prefer_stmt: false }, 1);

    match (completed, p.current()) {
        (Some(it), R_DOLLAR) => {
            p.bump(R_DOLLAR);
            m.abandon(p);
            it
        }
        _ => {
            while !p.at(R_DOLLAR) {
                p.bump_any()
            }
            p.bump(R_DOLLAR);
            m.complete(p, ERROR)
        }
    }
}
