from pathlib import Path
from slither import Slither
from slither.core.cfg.node import NodeType
from slither.slithir.operations import Assignment, Unpack
from slither.core.expressions import (
    AssignmentOperation,
    TupleExpression,
    NewElementaryType,
    CallExpression,
)


TEST_DATA_DIR = Path(__file__).resolve().parent / "test_data"


def test_ternary_conversions(solc_binary_path) -> None:
    """This tests that true and false sons define the same number of variables that the father node declares"""
    solc_path = solc_binary_path("0.8.0")
    slither = Slither(Path(TEST_DATA_DIR, "ternary_expressions.sol").as_posix(), solc=solc_path)
    contract = next(c for c in slither.contracts if c.name == "C")
    for function in contract.functions:
        vars_declared = 0
        vars_assigned = 0
        for node in function.nodes:
            if node.type in [NodeType.IF, NodeType.IFLOOP]:
                # Iterate over true and false son
                for inner_node in node.sons:
                    # Count all variables declared
                    expression = inner_node.expression
                    if isinstance(
                        expression, (AssignmentOperation, NewElementaryType, CallExpression)
                    ):
                        var_expr = expression.expression_left
                        # Only tuples declare more than one var
                        if isinstance(var_expr, TupleExpression):
                            for expr in var_expr.expressions:
                                if expr is not None:
                                    vars_declared += 1
                        else:
                            vars_declared += 1

                    for ir in inner_node.irs:
                        # Count all variables defined
                        if isinstance(ir, (Assignment, Unpack)):
                            vars_assigned += 1
        assert vars_declared == vars_assigned and vars_assigned != 0


def test_ternary_tuple(solc_binary_path) -> None:
    """
    Test that in the ternary liftings of an assignment of the form `(z, ) = ...`,
    we obtain `z` from an unpack operation in both lifitings
    """
    solc_path = solc_binary_path("0.8.0")
    slither = Slither(Path(TEST_DATA_DIR, "ternary_expressions.sol").as_posix(), solc=solc_path)
    contract = next(c for c in slither.contracts if c.name == "D")
    fn = next(f for f in contract.functions if f.name == "a")

    if_nodes = [n for n in fn.nodes if n.type == NodeType.IF]
    assert len(if_nodes) == 1

    if_node = if_nodes[0]
    assert isinstance(if_node.son_true.expression, AssignmentOperation)
    assert (
        len([ir for ir in if_node.son_true.all_slithir_operations() if isinstance(ir, Unpack)]) == 1
    )
    assert (
        len([ir for ir in if_node.son_false.all_slithir_operations() if isinstance(ir, Unpack)])
        == 1
    )
