"""
CAP, SSA Intro, Elimination and Optimisations
Functions to convert a CFG out of SSA Form.
"""

from typing import cast, List, Set, Tuple
from Lib.Errors import MiniCInternalError
from Lib import RiscV
from Lib.Graphes import DiGraph
from Lib.CFG import Block, BlockInstr, CFG
from Lib.Operands import (
    Register, DataLocation,
    Temporary)
from Lib.Statement import AbsoluteJump
from Lib.Terminator import BranchingTerminator, Return
from Lib.PhiNode import PhiNode


def generate_moves_from_phis(phis: List[PhiNode], parent: Block) -> List[BlockInstr]:
    """
    `generate_moves_from_phis(phis, parent)` builds a list of move instructions
    to be inserted in a new block between `parent` and the block with phi nodes
    `phis`.

    This is an helper function called during SSA exit.
    """
    moves: List[BlockInstr] = []
    plabel = parent.get_label()
    for phi in phis:
        if(plabel in phi.used()):
            moves.append(RiscV.mv(phi.var,phi.used()[plabel]))
    return moves


def exit_ssa(cfg: CFG, is_smart: bool) -> None:
    """
    `exit_ssa(cfg)` replaces phi nodes with move instructions to exit SSA form.

    `is_smart` is set to true when smart register allocation is enabled (Lab 5b).
    """
    for b in cfg.get_blocks():
        phis = cast(List[PhiNode], b._phis)  # Use cast for Pyright
        b._phis = []  # Remove all phi nodes in the block
        blabel = b.get_label()
        parents: List[Block] = b.get_in().copy()  # Copy as we modify it by adding blocks
        for p in parents:
            moves = generate_moves_from_phis(phis, p)
            if(len(moves)==0):
                continue
            # Creating the block
            ilabel = cfg.fdata.fresh_label(p.get_label().name+"_to_"+b.get_label().name)
            i = Block(ilabel,moves,AbsoluteJump(blabel))
            # Add the block
            cfg.add_block(i)
            # Changing the jumps
            ot = p.get_terminator()
            if(isinstance(ot,BranchingTerminator)):
                if(ot.label_then == blabel):
                    ot.label_then = ilabel
                if(ot.label_else == blabel):
                    ot.label_else = ilabel
            elif(isinstance(ot,AbsoluteJump)):
                assert(ot.label == blabel)
                ot = AbsoluteJump(ilabel)
            elif(isinstance(ot,Return)):
                raise MiniCInternalError("Malformed CFG, cannot have a return in a parent block")
            else:
                raise MiniCInternalError("I don't know of this terminator type:",type(ot))
            p.set_terminator(ot) # This instruction might be useless
            # Moving from p -> b to p -> i -> b
            cfg.remove_edge(p,b)
            cfg.add_edge(p,i)
            cfg.add_edge(i,b)

