///|
///  {args passed in stack }
///  ---------- fp
///     reg_stack
///  { callee-saved registers }
///  { spilled registers }
///  { temporary space for registers }
///  { ra, fp saved by callee }
///  ---------- fp'
///     var_stack
///  { local variables }
///  { function parameters passed on stack }
///  ---------- sp
pub(all) struct Function {
  mod : Module

  // Function Attributes
  name : String
  params : Array[Operand]
  body : Array[BasicBlock]
  mut var_stack_size : Int64
  mut reg_stack_size : Int64
  terminal_blocks : Array[BasicBlock] // BasicBlocks that end with ret instruction

  // LLVM
  mut llvm_func : @IR.Function?
  is_external : Bool
  is_variadic : Bool

  // Value Map
  value_map : Map[&@IR.Value, Operand]
  bbmap : Map[String, BasicBlock] // Map from label to BasicBlock
  mut vreg_cnt : Int
  mut vfreg_cnt : Int

  // spilled_count
  mut spilled_count : Int
}

///|
pub fn Function::new(
  mod : Module,
  name : String,
  is_external : Bool,
  is_variadic? : Bool = false,
) -> Function {
  Function::{
    mod,
    name,
    params: Array::new(),
    body: Array::new(),
    var_stack_size: 0,
    reg_stack_size: 0,
    terminal_blocks: Array::new(),
    llvm_func: None,
    is_external,
    is_variadic,
    value_map: Map::new(),
    bbmap: Map::new(),
    vreg_cnt: 0,
    vfreg_cnt: 0,
    spilled_count: 0,
  }
}

///|
pub fn Function::inst_iter(self : Self) -> Iter[Instruction] {
  letrec f = yield_ => {
    for bb in self.body {
      for inst in bb.insts {
        guard yield_(inst) is IterContinue else { return IterEnd }
      }
    }
    IterContinue
  }

  Iter::new(f)
}

///|
fn Function::set_params_by_patterns(
  self : Self,
  arg_patterns : Array[ArgPattern],
) -> Unit {
  self.params.clear()
  let { num_arg_regs, num_arg_fregs, .. } = self.mod.arch_config
  let mut areg_cnt = 0
  let mut fareg_cnt = 0
  let mut spill_cnt = 0L
  letrec push_param: (Operand) -> Unit = p => self.params.push(p)

  for arg_pat in arg_patterns {
    match arg_pat {
      I if areg_cnt < num_arg_regs => {
        IRegister(AReg(areg_cnt)) |> push_param
        areg_cnt += 1
      }
      I => {
        let fp_offset = spill_cnt * 8
        Mem(FramePtr, fp_offset) |> push_param
        spill_cnt += 1
      }
      F if fareg_cnt < num_arg_fregs => {
        FRegister(FAReg(fareg_cnt)) |> push_param
        fareg_cnt += 1
      }
      F => {
        let fp_offset = spill_cnt * 8
        Mem(FramePtr, fp_offset) |> push_param
        spill_cnt += 1
      }
    }
  }
}

///|
#callsite(autofill(loc))
pub fn Function::append_basic_block(
  self : Self,
  label : String,
  loc~ : SourceLoc,
) -> BasicBlock {
  if self.bbmap.contains(label) {
    let msg =
      $|Fatal Errpr happened in \{loc}
      #|Duplicate basic block label: \{label} in function \{self.name}
    println(msg)
    panic()
  }
  let bb = BasicBlock::new(self, label)
  self.body.push(bb)
  bb
}

///|
pub fn Function::get_param(self : Self, idx : Int) -> Operand? {
  self.params.get(idx)
}

///|
fn Function::extend_var_stack(
  self : Self,
  size : Int64,
  align : Int64,
) -> Int64 {
  self.var_stack_size = (self.var_stack_size + size + align - 1) / align * align
  self.var_stack_size
}

///|
fn Function::extend_reg_stack(
  self : Self,
  size : Int64,
  align : Int64,
) -> Int64 {
  self.reg_stack_size = (self.reg_stack_size + size + align - 1) / align * align
  self.reg_stack_size
}

///|
pub fn Function::new_virtual_reg(self : Self) -> IRegister {
  let vreg = IRegister::VReg(self.vreg_cnt)
  self.vreg_cnt += 1
  vreg
}

///|
pub fn Function::new_virtual_freg(self : Self) -> FRegister {
  let vfreg = FRegister::VFReg(self.vfreg_cnt)
  self.vfreg_cnt += 1
  vfreg
}

///|
pub fn Function::keeped_treg1(self : Self) -> IRegister {
  let num_tregs = self.mod.arch_config.num_temp_regs
  TReg(num_tregs - 1)
}

///|
pub fn Function::keeped_ftreg1(self : Self) -> FRegister {
  let num_tfregs = self.mod.arch_config.num_temp_fregs
  FTReg(num_tfregs - 1)
}

///|
pub fn Function::keeped_treg2(self : Self) -> (IRegister, IRegister) {
  let num_tregs = self.mod.arch_config.num_temp_regs
  (TReg(num_tregs - 1), TReg(num_tregs - 2))
}

///|
pub fn Function::keeped_ftreg2(self : Self) -> (FRegister, FRegister) {
  let num_tfregs = self.mod.arch_config.num_temp_fregs
  (FTReg(num_tfregs - 1), FTReg(num_tfregs - 2))
}

///|
pub fn Function::bind_llvm_value_to_register(
  self : Self,
  val : &@IR.Value,
  reg : IRegister,
) -> Unit {
  self.value_map.set(val, Operand::IRegister(reg))
}

///|
pub fn Function::bind_llvm_value_to_fregister(
  self : Self,
  val : &@IR.Value,
  freg : FRegister,
) -> Unit {
  self.value_map.set(val, Operand::FRegister(freg))
}

///|
pub fn Function::bind_llvm_value_to_mem(
  self : Self,
  val : &@IR.Value,
  base : IRegister,
  offset : Int64,
) -> Unit {
  self.value_map.set(val, Operand::MemLoc(base, offset))
}

///|
pub fn Function::get_operand_from_llvm_value(
  self : Self,
  val : &@IR.Value,
) -> Operand? {
  if val.tryAsConstantEnum() is Some(c) {
    let operand = match c {
      ConstantInt(c) => Imm(c.getValueAsInt64())
      ConstantFP(c) => FImm(c.getValue())
      ConstantPointerNull(_) => Imm(0)
      _ => {
        let err_msg =
          $|Fatal Error happened in Function::get_operand_from_llvm_value
          $|Unsupported constant value: \{val}
        println(err_msg)
        panic()
      }
    }
    return Some(operand)
  }
  if val.tryAsGlobalValue() is Some(gv) {
    let gv_name = gv.getName().unwrap()
    let label = Label(gv_name)
    return Some(label)
  }
  self.value_map.get(val)
}

///|
pub fn Function::has_inst_with_opcode(self : Self, opcode : OpCode) -> Bool {
  for inst in self.inst_iter() {
    if inst.opcode == opcode {
      return true
    }
  }
  false
}

///|
pub fn Function::collect_insts_with_opcode(
  self : Self,
  opcode : OpCode,
) -> Array[Instruction] {
  let insts = Array::new()
  for inst in self.inst_iter() {
    if inst.opcode == opcode {
      insts.push(inst)
    }
  }
  insts
}

///|
pub fn Function::collect_blocks_with_opcode(
  self : Self,
  opcode : OpCode,
) -> Array[BasicBlock] {
  let blocks = Array::new()
  for bb in self.body {
    for inst in bb.insts {
      if inst.opcode == opcode {
        blocks.push(bb)
        break
      }
    }
  }
  blocks
}

///|
pub fn Function::get_entry_block(self : Self) -> BasicBlock? {
  self.body.get(0)
}

///|
pub fn Function::contains_virtual_reg(self : Self) -> Bool {
  for bb in self.body {
    if bb.contains_virtual_reg() {
      return true
    }
  }
  false
}

///|
pub impl Show for Function with output(self, logger) {
  logger.write_string("func \{self.name}(")
  let pstr = self.params.map(p => "\{p}").join(", ")
  logger.write_string(pstr)
  logger.write_string(") {\n")
  for i, bb in self.body {
    logger.write_object(bb)
    if i != self.body.length() - 1 {
      logger.write_string("\n")
    }
  }
  logger.write_string("}\n")
}