///|
/// Opaque function-owned SSA value handle.
pub struct Value {
  priv owner : Ref[Unit]
  priv id : Int
}

///|
fn Value::new(owner : Ref[Unit], id : Int) -> Value {
  { owner, id }
}

///|
pub impl Eq for Value with fn equal(self, other) {
  physical_equal(self.owner, other.owner) && self.id == other.id
}

///|
pub impl Hash for Value with fn hash_combine(self, hasher) {
  hasher.combine(self.id)
}

///|
pub impl Debug for Value with fn to_repr(self) {
  Repr::literal("v\{self.id}")
}

///|
/// Opaque function-owned basic-block handle.
pub struct Block {
  priv owner : Ref[Unit]
  priv id : Int
}

///|
fn Block::new(owner : Ref[Unit], id : Int) -> Block {
  { owner, id }
}

///|
pub impl Eq for Block with fn equal(self, other) {
  physical_equal(self.owner, other.owner) && self.id == other.id
}

///|
pub impl Hash for Block with fn hash_combine(self, hasher) {
  hasher.combine(self.id)
}

///|
pub impl Debug for Block with fn to_repr(self) {
  Repr::literal("block\{self.id}")
}

///|
/// Opaque function-owned instruction handle.
pub struct Instruction {
  priv owner : Ref[Unit]
  priv id : Int
}

///|
fn Instruction::new(owner : Ref[Unit], id : Int) -> Instruction {
  { owner, id }
}

///|
pub impl Eq for Instruction with fn equal(self, other) {
  physical_equal(self.owner, other.owner) && self.id == other.id
}

///|
pub impl Hash for Instruction with fn hash_combine(self, hasher) {
  hasher.combine(self.id)
}

///|
pub impl Debug for Instruction with fn to_repr(self) {
  Repr::literal("inst\{self.id}")
}

///|
/// Opaque function-owned address-visible stack object handle.
pub struct StackObject {
  priv owner : Ref[Unit]
  priv id : Int
}

///|
fn StackObject::new(owner : Ref[Unit], id : Int) -> StackObject {
  { owner, id }
}

///|
pub impl Eq for StackObject with fn equal(self, other) {
  physical_equal(self.owner, other.owner) && self.id == other.id
}

///|
pub impl Hash for StackObject with fn hash_combine(self, hasher) {
  hasher.combine(self.id)
}

///|
pub impl Debug for StackObject with fn to_repr(self) {
  Repr::literal("stack\{self.id}")
}

///|
pub struct Edge {
  target : Block
  arguments : Array[Value]
} derive(Debug, Eq)

///|
fn Edge::new(target : Block, arguments : Array[Value]) -> Edge {
  { target, arguments: arguments.copy() }
}

///|
fn Edge::copy(self : Edge) -> Edge {
  Edge::new(self.target, self.arguments)
}

///|
pub struct SwitchCase {
  bits : UInt64
  edge : Edge
} derive(Debug, Eq)

///|
fn SwitchCase::new(bits : UInt64, edge : Edge) -> SwitchCase {
  { bits, edge }
}

///|
fn SwitchCase::copy(self : SwitchCase) -> SwitchCase {
  SwitchCase::new(self.bits, self.edge.copy())
}

///|
pub(all) enum Terminator {
  Jump(Edge)
  Branch(Value, Edge, Edge)
  Switch(Value, Array[SwitchCase], Edge)
  Return(Array[Value])
  TailCall(SemanticCall, Array[Value])
  NoReturnCall(SemanticCall, Array[Value])
  Trap(TrapReason)
} derive(Debug, Eq)

///|
fn Terminator::copy(self : Terminator) -> Terminator {
  match self {
    Jump(edge) => Jump(edge.copy())
    Branch(condition, true_edge, false_edge) =>
      Branch(condition, true_edge.copy(), false_edge.copy())
    Switch(index, cases, default_edge) =>
      Switch(index, cases.map(case => case.copy()), default_edge.copy())
    Return(values) => Return(values.copy())
    TailCall(call, operands) => TailCall(call.copy(), operands.copy())
    NoReturnCall(call, operands) => NoReturnCall(call.copy(), operands.copy())
    Trap(reason) => Trap(reason)
  }
}

///|
pub struct TerminatorMetadata {
  source : SourceLocation?
  live_gc_roots : Array[Value]
} derive(Debug, Eq)

///|
pub fn TerminatorMetadata::empty() -> TerminatorMetadata {
  { source: None, live_gc_roots: [] }
}

///|
pub fn TerminatorMetadata::new(
  source : SourceLocation?,
  live_gc_roots : Array[Value],
) -> TerminatorMetadata {
  { source, live_gc_roots: live_gc_roots.copy() }
}

///|
pub struct TerminatorRecord {
  kind : Terminator
  metadata : TerminatorMetadata
} derive(Debug, Eq)

///|
fn TerminatorRecord::new(
  kind : Terminator,
  metadata : TerminatorMetadata,
) -> TerminatorRecord {
  {
    kind: kind.copy(),
    metadata: {
      source: metadata.source,
      live_gc_roots: metadata.live_gc_roots.copy(),
    },
  }
}

///|
fn TerminatorRecord::copy(self : TerminatorRecord) -> TerminatorRecord {
  TerminatorRecord::new(self.kind, self.metadata)
}

///|
pub struct InstructionMetadata {
  source : SourceLocation?
  live_gc_roots : Array[Value]
  stack_map : StackMapMetadata?
} derive(Debug, Eq)

///|
pub fn InstructionMetadata::empty() -> InstructionMetadata {
  { source: None, live_gc_roots: [], stack_map: None }
}

///|
pub struct StackMapMetadata {
  id : Int
  argument_root_count : Int
} derive(Debug, Eq)

///|
pub fn StackMapMetadata::new(
  id : Int,
  argument_root_count~ : Int,
) -> StackMapMetadata {
  { id, argument_root_count }
}

///|
pub fn InstructionMetadata::new(
  source : SourceLocation?,
  live_gc_roots : Array[Value],
  stack_map? : StackMapMetadata,
) -> InstructionMetadata {
  { source, live_gc_roots: live_gc_roots.copy(), stack_map }
}

///|
priv enum ValueDefinition {
  FunctionParameter(Int)
  BlockParameter(Block, Int)
  InstructionResult(Instruction, Int)
  RemovedInstructionResult(Instruction, Int)
}

///|
priv struct ValueData {
  ty : ValueType
  mut definition : ValueDefinition
}

///|
priv struct BlockData {
  parameters : Array[Value]
  instructions : Array[Instruction]
  mut terminator : TerminatorRecord?
}

///|
priv struct InstructionData {
  operation : Operation
  operands : Array[Value]
  results : Array[Value]
  metadata : InstructionMetadata
  mut alive : Bool
  mut parent : Block?
}

///|
priv struct StackObjectData {
  size : Int
  alignment : Int
}

///|
/// A target-neutral semantic machine function.
///
/// Storage is private and canonical. Public queries return handles or
/// snapshots; construction and transformation go through owner-aware seams.
pub struct Function {
  priv owner : Ref[Unit]
  priv name : String
  priv protocol : CallProtocol
  priv signature : Signature
  priv parameters : Array[Value]
  priv values : Array[ValueData]
  priv blocks : Array[BlockData]
  priv instructions : Array[InstructionData]
  priv stack_objects : Array[StackObjectData]
}

///|
fn Function::new(
  name : String,
  protocol : CallProtocol,
  signature : Signature,
) -> Function {
  let function = {
    owner: Ref(()),
    name,
    protocol,
    signature: Signature::new(signature.params, signature.results),
    parameters: [],
    values: [],
    blocks: [],
    instructions: [],
    stack_objects: [],
  }
  for index, ty in signature.params {
    let value = function.allocate_value(ty, FunctionParameter(index))
    function.parameters.push(value)
  }
  function.allocate_block([]) |> ignore
  function
}

///|
fn Function::owns_value(self : Function, value : Value) -> Bool {
  physical_equal(self.owner, value.owner) &&
  value.id >= 0 &&
  value.id < self.values.length()
}

///|
fn Function::owns_block(self : Function, block : Block) -> Bool {
  physical_equal(self.owner, block.owner) &&
  block.id >= 0 &&
  block.id < self.blocks.length()
}

///|
fn Function::owns_instruction(
  self : Function,
  instruction : Instruction,
) -> Bool {
  physical_equal(self.owner, instruction.owner) &&
  instruction.id >= 0 &&
  instruction.id < self.instructions.length()
}

///|
fn Function::owns_stack_object(self : Function, object : StackObject) -> Bool {
  physical_equal(self.owner, object.owner) &&
  object.id >= 0 &&
  object.id < self.stack_objects.length()
}

///|
fn Function::allocate_stack_object(
  self : Function,
  size : Int,
  alignment : Int,
) -> StackObject {
  let object = StackObject::new(self.owner, self.stack_objects.length())
  self.stack_objects.push({ size, alignment })
  object
}

///|
fn Function::allocate_value(
  self : Function,
  ty : ValueType,
  definition : ValueDefinition,
) -> Value {
  let value = Value::new(self.owner, self.values.length())
  self.values.push({ ty, definition })
  value
}

///|
fn Function::allocate_block(
  self : Function,
  parameter_types : Array[ValueType],
) -> Block {
  let block = Block::new(self.owner, self.blocks.length())
  let parameters : Array[Value] = []
  for index, ty in parameter_types {
    parameters.push(self.allocate_value(ty, BlockParameter(block, index)))
  }
  self.blocks.push({ parameters, instructions: [], terminator: None })
  block
}

///|
fn Function::append_instruction(
  self : Function,
  block : Block,
  operation : Operation,
  operands : Array[Value],
  result_types : Array[ValueType],
  metadata : InstructionMetadata,
) -> Array[Value] {
  let instruction = Instruction::new(self.owner, self.instructions.length())
  let results : Array[Value] = []
  for index, ty in result_types {
    results.push(self.allocate_value(ty, InstructionResult(instruction, index)))
  }
  self.instructions.push({
    operation: operation.copy(),
    operands: operands.copy(),
    results,
    metadata: {
      source: metadata.source,
      live_gc_roots: metadata.live_gc_roots.copy(),
      stack_map: metadata.stack_map,
    },
    alive: true,
    parent: Some(block),
  })
  self.blocks[block.id].instructions.push(instruction)
  results
}

///|
pub fn Function::name(self : Function) -> String {
  self.name
}

///|
pub fn Function::protocol(self : Function) -> CallProtocol {
  self.protocol
}

///|
pub fn Function::signature(self : Function) -> Signature {
  Signature::new(self.signature.params, self.signature.results)
}

///|
pub fn Function::parameters(self : Function) -> Array[Value] {
  self.parameters.copy()
}

///|
pub fn Function::blocks(self : Function) -> Array[Block] {
  Array::makei(self.blocks.length(), index => Block::new(self.owner, index))
}

///|
pub fn Function::value_count(self : Function) -> Int {
  self.values.length()
}

///|
pub fn Function::value_index(self : Function, value : Value) -> Int? {
  if self.owns_value(value) {
    Some(value.id)
  } else {
    None
  }
}

///|
pub fn Function::block_count(self : Function) -> Int {
  self.blocks.length()
}

///|
pub fn Function::block_index(self : Function, block : Block) -> Int? {
  if self.owns_block(block) {
    Some(block.id)
  } else {
    None
  }
}

///|
pub fn Function::instruction_count(self : Function) -> Int {
  let mut count = 0
  for data in self.instructions {
    if data.alive {
      count += 1
    }
  }
  count
}

///|
pub fn Function::stack_objects(self : Function) -> Array[StackObject] {
  Array::makei(self.stack_objects.length(), index => {
    StackObject::new(self.owner, index)
  })
}

///|
pub fn Function::stack_object_size(
  self : Function,
  object : StackObject,
) -> Int? {
  if self.owns_stack_object(object) {
    Some(self.stack_objects[object.id].size)
  } else {
    None
  }
}

///|
pub fn Function::stack_object_alignment(
  self : Function,
  object : StackObject,
) -> Int? {
  if self.owns_stack_object(object) {
    Some(self.stack_objects[object.id].alignment)
  } else {
    None
  }
}

///|
pub fn Function::instruction_index(
  self : Function,
  instruction : Instruction,
) -> Int? {
  if self.owns_instruction(instruction) &&
    self.instructions[instruction.id].alive {
    Some(instruction.id)
  } else {
    None
  }
}

///|
pub fn Function::value_type(self : Function, value : Value) -> ValueType? {
  if self.owns_value(value) {
    Some(self.values[value.id].ty)
  } else {
    None
  }
}

///|
pub fn Function::block_parameters(
  self : Function,
  block : Block,
) -> Array[Value] {
  if self.owns_block(block) {
    self.blocks[block.id].parameters.copy()
  } else {
    []
  }
}

///|
pub fn Function::block_instructions(
  self : Function,
  block : Block,
) -> Array[Instruction] {
  if self.owns_block(block) {
    self.blocks[block.id].instructions.copy()
  } else {
    []
  }
}

///|
pub fn Function::block_terminator(
  self : Function,
  block : Block,
) -> TerminatorRecord? {
  if self.owns_block(block) {
    match self.blocks[block.id].terminator {
      Some(record) => Some(record.copy())
      None => None
    }
  } else {
    None
  }
}

///|
pub fn Function::instruction_operation(
  self : Function,
  instruction : Instruction,
) -> Operation? {
  if self.owns_instruction(instruction) &&
    self.instructions[instruction.id].alive {
    Some(self.instructions[instruction.id].operation.copy())
  } else {
    None
  }
}

///|
pub fn Function::instruction_operands(
  self : Function,
  instruction : Instruction,
) -> Array[Value] {
  if self.owns_instruction(instruction) &&
    self.instructions[instruction.id].alive {
    self.instructions[instruction.id].operands.copy()
  } else {
    []
  }
}

///|
pub fn Function::instruction_results(
  self : Function,
  instruction : Instruction,
) -> Array[Value] {
  if self.owns_instruction(instruction) &&
    self.instructions[instruction.id].alive {
    self.instructions[instruction.id].results.copy()
  } else {
    []
  }
}

///|
pub fn Function::instruction_metadata(
  self : Function,
  instruction : Instruction,
) -> InstructionMetadata? {
  if self.owns_instruction(instruction) &&
    self.instructions[instruction.id].alive {
    let metadata = self.instructions[instruction.id].metadata
    Some({
      source: metadata.source,
      live_gc_roots: metadata.live_gc_roots.copy(),
      stack_map: metadata.stack_map,
    })
  } else {
    None
  }
}