from Lib import RiscV
from Lib.Operands import Temporary, Operand, S
from Lib.Statement import Instruction
from Lib.Allocator import Allocator
from typing import List, Dict


class AllInMemAllocator(Allocator):

    def replace(self, old_instr: Instruction) -> List[Instruction]:
        """Replace Temporary operands with the corresponding allocated
        memory location. FP points to the stack."""
        numreg = 1
        before: List[Instruction] = []
        after: List[Instruction] = []
        subst: Dict[Operand, Operand] = {}
        
        old_args = old_instr.args()
        for arg in old_args:
            # We substitute
            subst[arg] = S[numreg]
            numreg += 1
        for arg in old_instr.used():
            # We have to read them from memory
            if(isinstance(arg, Temporary)):
                before.append(RiscV.ld(subst[arg],self._fdata._pool.get_alloced_loc(arg)))
        for arg in old_instr.defined():
            # We have to write them after to memory
            if(isinstance(arg, Temporary)):
                after.append(RiscV.sd(subst[arg],self._fdata._pool.get_alloced_loc(arg)))
            
        
        # TODO (Exercise 7): compute before,after,args.
        # TODO (Exercise 7): iterate over old_args, check which argument
        # TODO (Exercise 7): is a temporary (e.g. isinstance(..., Temporary)),
        # TODO (Exercise 7): and if so, generate ld/sd accordingly. Replace the
        # TODO (Exercise 7): temporary with S[1], S[2] or S[3] physical registers.
        try:
            new_instr = old_instr.substitute(subst)
        except Exception:
            # We have an instruction that doesn't need substitution
            return [old_instr]
        return before + [new_instr] + after

    def prepare(self):
        """Allocate all temporaries to memory.
        Invariants:
        - Expanded instructions can use s2 and s3
          (to store the values of temporaries before the actual instruction).
        """
        self._fdata._pool.set_temp_allocation(
            {temp: self._fdata.fresh_offset()
             for temp in self._fdata._pool.get_all_temps()})
