IR = Struct.new(:op, :args, :token, :virtual) # virtual is just used for op stats for now

def to_ir(ast_nodes, registers, ast_inds, input_op)
  nodes = []
  ast2ir = {}
  ast_nodes.size.times{|ast_ind| to_ir_h(ast_nodes, ast_ind, nodes, ast2ir, registers, input_op) }
  ast2ir.each{|k,v|
    been = {}
    while AstInd === v
      raise IogiiError.new "direct circular dependency", ast_nodes[v.ind] if been[v.ind]
      been[v.ind] = true
      ast2ir[k] = v = ast2ir[v.ind]
    end
  }
  ir_inds = ast_inds.map{|i| ast2ir[i] }
  nodes.each{|node| node.args = node.args.map{|a| IrInd===a ? a.ind : ast2ir[a] } }
  [nodes, ir_inds]
end

# ir args are assumed to be ast_inds unless this
IrInd = Struct.new(:ind); def ir_ind a; IrInd.new(a); end

# ast2ind is assumed to be ir inds unless this
AstInd = Struct.new(:ind)

def to_ir_h(ast_nodes, ast_ind, nodes, ast2ir, registers, input_op)
  ast_node = ast_nodes[ast_ind]
  case ast_node.type
    when :InputNode
      nodes << IR.new(input_op.value, [], ast_node.token)
      ast2ir[ast_ind] = nodes.size - 1
    when :VarNode
      ast2ir[ast_ind] = AstInd.new registers[ast_node.token.str]
    when :DataNode
      nodes << IR.new(ast_node.token.data_impl, [], ast_node.token)
      ast2ir[ast_ind] = nodes.size - 1
    when :VirtualDuped0
      ast2ir[ast_ind] = AstInd.new ast_node.token.duped_value.args[0]
    when :VirtualDuped1
      ast2ir[ast_ind] = AstInd.new ast_node.token.duped_value.args[1]
    when :ImplicitValue
      ast2ir[ast_ind] = AstInd.new(ast_node.impl)
    when :IdentityNode
      ast2ir[ast_ind] = AstInd.new ast_node.args[0]
    when :ArgNode
      ast2ir[ast_ind] = AstInd.new ast_node.impl
    when :OpNode
      op = lookup_op(ast_node.token.str)
      nodes << IR.new(op, ast_node.args, ast_node.token)
      ast2ir[ast_ind] = nodes.size - 1
    when :MatchedEndBracket
      # handled by matching left bracket of BlockAst
    when :UnmatchedEndBracket
      ast2ir[ast_ind] = AstInd.new ast_node.args[0]
    when :LoopOp, :MDupOp
      op = lookup_op(ast_node.token.str)
      mod = op.ways[0].mod
      args = ast_node.args
      token = ast_node.token
      block = ast_node.impl
      block_ret = ast_nodes[block].args[0]

      if op.name == "mdup"
        ast2ir[block] = AstInd.new block_ret
        ast2ir[ast_ind] = AstInd.new args[0]
      elsif op.name == "mpeek"
        ast2ir[block] = AstInd.new block_ret
        ast2ir[ast_ind] = AstInd.new args[0]
      elsif op.name == "iterate"
        nodes << IR.new(Ops["cons"].dup_mod(mod), [block_ret, args[0]], token, true)
        ast2ir[block] = nodes.size - 1
        nodes << IR.new(Ops["superpad"].dup_mod(mod), [ir_ind(nodes.size-1)], token, true)
        ast2ir[ast_ind] = nodes.size - 1
      elsif op.name == "iterate0"
        nodes << IR.new(Ops["consDefault"].dup_mod(mod), [block_ret], token, true)
        ast2ir[block] = AstInd.new block_ret
        nodes << IR.new(Ops["superpad"].dup_mod(mod), [ir_ind(nodes.size-1)], token, true)
        ast2ir[ast_ind] = nodes.size - 1
      elsif op.name == "foldr"
        nodes << IR.new(Ops["pad"].dup_mod(mod), [block_ret, args[0]], token, true)
        pad_ind = nodes.size - 1
        nodes << IR.new(Ops["superpad"].dup_mod(mod), [ir_ind(nodes.size-1)], token, true)

        nodes << IR.new(Ops["tail"].dup_mod(mod), [ir_ind(nodes.size-1)], token, true)
        ast2ir[ast_ind] = nodes.size - 1
        nodes << IR.new(Ops["head"].dup_mod(mod), [ir_ind(pad_ind)], token, true)
        ast2ir[block] = nodes.size - 1
      elsif op.name == "foldr0"
        nodes << IR.new(Ops["superpad"].dup_mod(mod), [block_ret], token, true)
        nodes << IR.new(Ops["padDefault"].dup_mod(mod), [ir_ind(nodes.size-1)], token, true)
        nodes << IR.new(Ops["tail"].dup_mod(mod), [ir_ind(nodes.size-1)], token, true)
        ast2ir[ast_ind] = nodes.size - 1
        nodes << IR.new(Ops["head"].dup_mod(mod), [block_ret], token, true)
        ast2ir[block] = nodes.size - 1
      else raise
      end
    else raise "unhandled node type %p" % ast_node.type
  end
end
