use dust_lang::*;

#[test]
fn equality_assignment_long() {
    let source = "let a = if 4 == 4 { true } else { false }; a";

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::Boolean)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 0)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(13, 15)
                ),
                (Instruction::jump(1, true), Span(18, 19)),
                (Instruction::load_boolean(0, true, true), Span(20, 24)),
                (Instruction::load_boolean(0, false, false), Span(34, 39)),
                (Instruction::define_local(0, 0, false), Span(4, 5)),
                (Instruction::get_local(1, 0), Span(43, 44)),
                (Instruction::r#return(true), Span(44, 44)),
            ],
            vec![ConcreteValue::Integer(4), ConcreteValue::string("a")],
            vec![Local::new(1, Type::Boolean, false, Scope::default(),)]
        )),
    );

    assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
}

#[test]
fn equality_assignment_short() {
    let source = "let a = 4 == 4 a";

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::Boolean)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 0)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(10, 12)
                ),
                (Instruction::jump(1, true), Span(10, 12)),
                (Instruction::load_boolean(0, true, true), Span(10, 12)),
                (Instruction::load_boolean(0, false, false), Span(10, 12)),
                (Instruction::define_local(0, 0, false), Span(4, 5)),
                (Instruction::get_local(1, 0), Span(15, 16)),
                (Instruction::r#return(true), Span(16, 16)),
            ],
            vec![ConcreteValue::Integer(4), ConcreteValue::string("a")],
            vec![Local::new(1, Type::Boolean, false, Scope::default())]
        )),
    );

    assert_eq!(run(source), Ok(Some(ConcreteValue::Boolean(true))));
}

#[test]
fn if_else_assigment_false() {
    let source = r#"
        let a = if 4 == 3 {
            panic();
            0
        } else {
            1; 2; 3; 4;
            42
        };
        a"#;

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::Integer)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 1)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(22, 24)
                ),
                (Instruction::jump(3, true), Span(27, 28)),
                (
                    Instruction::call_native(0, NativeFunction::Panic, 0),
                    Span(41, 48)
                ),
                (Instruction::load_constant(0, 2, false), Span(62, 63)),
                (Instruction::jump(5, true), Span(129, 130)),
                (Instruction::load_constant(1, 3, false), Span(93, 94)),
                (Instruction::load_constant(2, 4, false), Span(96, 97)),
                (Instruction::load_constant(3, 1, false), Span(99, 100)),
                (Instruction::load_constant(4, 0, false), Span(102, 103)),
                (Instruction::load_constant(5, 5, false), Span(117, 119)),
                (Instruction::r#move(5, 0), Span(129, 130)),
                (Instruction::define_local(5, 0, false), Span(13, 14)),
                (Instruction::get_local(6, 0), Span(139, 140)),
                (Instruction::r#return(true), Span(140, 140)),
            ],
            vec![
                ConcreteValue::Integer(4),
                ConcreteValue::Integer(3),
                ConcreteValue::Integer(0),
                ConcreteValue::Integer(1),
                ConcreteValue::Integer(2),
                ConcreteValue::Integer(42),
                ConcreteValue::string("a")
            ],
            vec![Local::new(6, Type::Integer, false, Scope::default())]
        )),
    );

    assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
}

#[test]
fn if_else_assigment_true() {
    let source = r#"
        let a = if 4 == 4 {
            1; 2; 3; 4;
            42
        } else {
            panic();
            0
        };
        a"#;

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::Integer)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 0)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(22, 24)
                ),
                (Instruction::jump(6, true), Span(27, 28)),
                (Instruction::load_constant(0, 1, false), Span(41, 42)),
                (Instruction::load_constant(1, 2, false), Span(44, 45)),
                (Instruction::load_constant(2, 3, false), Span(47, 48)),
                (Instruction::load_constant(3, 0, false), Span(50, 51)),
                (Instruction::load_constant(4, 4, false), Span(65, 67)),
                (Instruction::jump(2, true), Span(129, 130)),
                (
                    Instruction::call_native(5, NativeFunction::Panic, 0),
                    Span(97, 104)
                ),
                (Instruction::load_constant(5, 5, false), Span(118, 119)),
                (Instruction::r#move(5, 4), Span(129, 130)),
                (Instruction::define_local(5, 0, false), Span(13, 14)),
                (Instruction::get_local(6, 0), Span(139, 140)),
                (Instruction::r#return(true), Span(140, 140)),
            ],
            vec![
                ConcreteValue::Integer(4),
                ConcreteValue::Integer(1),
                ConcreteValue::Integer(2),
                ConcreteValue::Integer(3),
                ConcreteValue::Integer(42),
                ConcreteValue::Integer(0),
                ConcreteValue::string("a")
            ],
            vec![Local::new(6, Type::Integer, false, Scope::default())]
        )),
    );

    assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
}

#[test]
fn if_else_complex() {
    let source = "
        if 1 == 1 {
            1; 2; 3; 4;
        } else {
            1; 2; 3; 4;
        }";

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::None)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 0)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(14, 16)
                ),
                (Instruction::jump(5, true), Span(19, 20)),
                (Instruction::load_constant(0, 0, false), Span(33, 34)),
                (Instruction::load_constant(1, 1, false), Span(36, 37)),
                (Instruction::load_constant(2, 2, false), Span(39, 40)),
                (Instruction::load_constant(3, 3, false), Span(42, 43)),
                (Instruction::jump(4, true), Span(95, 95)),
                (Instruction::load_constant(4, 0, false), Span(74, 75)),
                (Instruction::load_constant(5, 1, false), Span(77, 78)),
                (Instruction::load_constant(6, 2, false), Span(80, 81)),
                (Instruction::load_constant(7, 3, false), Span(83, 84)),
                (Instruction::r#move(7, 3), Span(95, 95)),
                (Instruction::r#return(false), Span(95, 95)),
            ],
            vec![
                ConcreteValue::Integer(1),
                ConcreteValue::Integer(2),
                ConcreteValue::Integer(3),
                ConcreteValue::Integer(4),
            ],
            vec![]
        ))
    );

    assert_eq!(run(source), Ok(None));
}

#[test]
fn if_else_false() {
    let source = "if 1 == 2 { panic(); 0 } else { 42 }";

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::Integer)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 1)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(5, 7)
                ),
                (Instruction::jump(2, true), Span(10, 11)),
                (
                    Instruction::call_native(0, NativeFunction::Panic, 0),
                    Span(12, 19)
                ),
                (Instruction::load_constant(0, 2, true), Span(21, 22)),
                (Instruction::load_constant(1, 3, false), Span(32, 34)),
                (Instruction::r#move(1, 0), Span(36, 36)),
                (Instruction::r#return(true), Span(36, 36)),
            ],
            vec![
                ConcreteValue::Integer(1),
                ConcreteValue::Integer(2),
                ConcreteValue::Integer(0),
                ConcreteValue::Integer(42)
            ],
            vec![]
        )),
    );

    assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
}

#[test]
fn if_else_true() {
    let source = "if 1 == 1 { 42 } else { panic(); 0 }";

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::Integer)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 0)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(5, 7)
                ),
                (Instruction::jump(2, true), Span(10, 11)),
                (Instruction::load_constant(0, 1, false), Span(12, 14)),
                (Instruction::jump(2, true), Span(36, 36)),
                (
                    Instruction::call_native(1, NativeFunction::Panic, 0),
                    Span(24, 31)
                ),
                (Instruction::load_constant(1, 2, false), Span(33, 34)),
                (Instruction::r#move(1, 0), Span(36, 36)),
                (Instruction::r#return(true), Span(36, 36))
            ],
            vec![
                ConcreteValue::Integer(1),
                ConcreteValue::Integer(42),
                ConcreteValue::Integer(0)
            ],
            vec![]
        )),
    );

    assert_eq!(run(source), Ok(Some(ConcreteValue::Integer(42))));
}

#[test]
fn if_false() {
    let source = "if 1 == 2 { panic() }";

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::None)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 1)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(5, 7)
                ),
                (Instruction::jump(1, true), Span(10, 11)),
                (
                    Instruction::call_native(0, NativeFunction::Panic, 0),
                    Span(12, 19)
                ),
                (Instruction::r#return(false), Span(21, 21))
            ],
            vec![ConcreteValue::Integer(1), ConcreteValue::Integer(2)],
            vec![]
        )),
    );

    assert_eq!(run(source), Ok(None));
}

#[test]
fn if_true() {
    let source = "if 1 == 1 { panic() }";

    assert_eq!(
        compile(source),
        Ok(Chunk::with_data(
            None,
            FunctionType {
                type_parameters: None,
                value_parameters: None,
                return_type: Box::new(Type::None)
            },
            vec![
                (
                    *Instruction::equal(true, 0, 0)
                        .set_b_is_constant()
                        .set_c_is_constant(),
                    Span(5, 7)
                ),
                (Instruction::jump(1, true), Span(10, 11)),
                (
                    Instruction::call_native(0, NativeFunction::Panic, 0),
                    Span(12, 19)
                ),
                (Instruction::r#return(false), Span(21, 21))
            ],
            vec![ConcreteValue::Integer(1)],
            vec![]
        )),
    );

    assert_eq!(
        run(source),
        Err(DustError::Runtime {
            error: VmError::NativeFunction(NativeFunctionError::Panic {
                message: None,
                position: Span(12, 19)
            }),
            source
        })
    );
}