use crate::{bil::Bitvector, intermediate_representation::*};

use super::{create_computation, mock_context, NodeValue};

use mock_context::Context;
use mock_context::StartEnd;

fn mock_program() -> Term<Program> {
    let var = Variable {
        name: String::from("RAX"),
        size: ByteSize::new(8),
        is_temp: false,
    };
    let value = Expression::UnOp {
        op: UnOpType::IntNegate,
        arg: Box::new(Expression::Var(var.clone())),
    };
    let def_term1 = Term {
        tid: Tid::new("def1".to_string()),
        term: Def::Assign {
            var: var.clone(),
            value: value.clone(),
        },
    };
    let def_term2 = Term {
        tid: Tid::new("def2".to_string()),
        term: Def::Assign {
            var: var.clone(),
            value: value.clone(),
        },
    };
    let def_term3 = Term {
        tid: Tid::new("def3".to_string()),
        term: Def::Assign {
            var: var.clone(),
            value: value.clone(),
        },
    };
    let def_term4 = Term {
        tid: Tid::new("def4".to_string()),
        term: Def::Assign {
            var: var.clone(),
            value: value.clone(),
        },
    };
    let def_term5 = Term {
        tid: Tid::new("def5".to_string()),
        term: Def::Assign {
            var: var.clone(),
            value: value.clone(),
        },
    };
    let call_term = Term {
        tid: Tid::new("call".to_string()),
        term: Jmp::Call {
            target: Tid::new("sub2"),
            return_: Some(Tid::new("sub1_blk2")),
        },
    };
    let return_term = Term {
        tid: Tid::new("return".to_string()),
        term: Jmp::Return(Expression::Const(Bitvector::zero(64.into()))), // The return term does not matter
    };
    let jmp = Jmp::Branch(Tid::new("sub1_blk1"));
    let jmp_term = Term {
        tid: Tid::new("jump"),
        term: jmp,
    };
    let sub1_blk1 = Term {
        tid: Tid::new("sub1_blk1"),
        term: Blk {
            defs: vec![def_term1],
            jmps: vec![call_term],
        },
    };
    let sub1_blk2 = Term {
        tid: Tid::new("sub1_blk2"),
        term: Blk {
            defs: vec![def_term5],
            jmps: vec![jmp_term],
        },
    };
    let sub1 = Term {
        tid: Tid::new("sub1"),
        term: Sub {
            name: "sub1".to_string(),
            blocks: vec![sub1_blk1, sub1_blk2],
        },
    };
    let cond_jump = Jmp::CBranch {
        target: Tid::new("sub1_blk1"),
        condition: Expression::Const(Bitvector::from_u8(0)),
    };
    let cond_jump_term = Term {
        tid: Tid::new("cond_jump"),
        term: cond_jump,
    };
    let jump_term_2 = Term {
        tid: Tid::new("jump2"),
        term: Jmp::Branch(Tid::new("sub2_blk2")),
    };
    let sub2_blk1 = Term {
        tid: Tid::new("sub2_blk1"),
        term: Blk {
            defs: vec![def_term2, def_term3],
            jmps: vec![cond_jump_term, jump_term_2],
        },
    };
    let sub2_blk2 = Term {
        tid: Tid::new("sub2_blk2"),
        term: Blk {
            defs: vec![def_term4],
            jmps: vec![return_term],
        },
    };
    let sub2 = Term {
        tid: Tid::new("sub2"),
        term: Sub {
            name: "sub2".to_string(),
            blocks: vec![sub2_blk1, sub2_blk2],
        },
    };
    let program = Term {
        tid: Tid::new("program"),
        term: Program {
            subs: vec![sub1, sub2],
            extern_symbols: Vec::new(),
            entry_points: Vec::new(),
            address_base_offset: 0,
        },
    };
    program
}

#[test]
fn backward_fixpoint() {
    let project = Project {
        program: mock_program(),
        cpu_architecture: String::from("x86"),
        stack_pointer_register: Variable {
            name: String::from("RSP"),
            size: ByteSize::new(8),
            is_temp: false,
        },
        calling_conventions: Vec::new(),
    };

    let mock_con = Context::new(&project);
    let mut computation = create_computation(mock_con.clone(), None);
    computation.set_node_value(
        *mock_con
            .tid_to_node_index
            .get(&(Tid::new("sub1"), Tid::new("sub1_blk1"), StartEnd::Start))
            .unwrap(),
        NodeValue::Value(0),
    );
    computation.compute_with_max_steps(100);

    // The fixpoint values of all 12 BlockStart/BlockEnd nodes are compared with their expected value
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub1"), Tid::new("sub1_blk1"), StartEnd::Start))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        0 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub1"), Tid::new("sub1_blk1"), StartEnd::End))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        1 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub1"), Tid::new("sub1_blk2"), StartEnd::Start))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        1 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub1"), Tid::new("sub1_blk2"), StartEnd::End))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        0 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub2_blk1"), StartEnd::Start))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        4 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub2_blk1"), StartEnd::End))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        2 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub2_blk2"), StartEnd::Start))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        2 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub2_blk2"), StartEnd::End))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        1 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub1_blk1"), StartEnd::Start))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        5 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub1_blk1"), StartEnd::End))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        4 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub1_blk2"), StartEnd::Start))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        6 as u64
    );
    assert_eq!(
        *computation
            .get_node_value(
                *mock_con
                    .tid_to_node_index
                    .get(&(Tid::new("sub2"), Tid::new("sub1_blk2"), StartEnd::End))
                    .unwrap()
            )
            .unwrap()
            .unwrap_value(),
        5 as u64
    );
}
