# todo some of this logic is duplicated with infer - unify it

IR2 = Struct.new(:way, :args, :zip_level, :type, :rank)

# IR2 is a simpler graph representation of the program
def to_ir2(ir, ranks, types, ir1_inds, nodes_way)
  ir2ir2 = {}
  ir2_nodes = []
  ir.each.with_index{|node,i|
    arg_types = node.args.map{|a|types[a]}
    arg_ranks = node.args.map{|a|ranks[a]}
    way = nodes_way[i]

    coerces = coerce?(way.arg_types,arg_types)
    zip_level = zip_level(way, arg_ranks, arg_types)
    excess = excess_ranks(way, arg_ranks, arg_types)

    args = node.args.map{|a| ->{ ir2ir2[a] } }

    # implicit coerces
    args = args.zip(coerces, arg_ranks,0..).map{|a,c,r,i|
      if c
        arg_types[i] = CharType
        arg_ranks[i] += 1
        ir2_nodes << IR2.new(Ops["str"].ways[0], [a], r, CharType, r+1)
        ir2_nodes.size - 1
      else
        a
      end
    }

    # implicit promotes
    args = args.zip(excess,0..,arg_ranks, arg_types, way.special_reps).map{|a,e,ind,r,t,sr|
      npromotes = -e - (sr && e < 0 ? 1 : 0)
      arg_ranks[ind] += [npromotes, 0].max
      npromotes.times{|time|
        ir2_nodes << IR2.new(Ops["just"].ways[0], [a], 0, t, r+time+1)
        a = ir2_nodes.size - 1
      }
      a
    }

    # implicit repeats
    args = args.zip(excess,0..,arg_ranks, arg_types, way.special_reps).map{|a,e,ind,r,t,sr|
      nreps = zip_level - [e, 0].max + (sr && e < 0 ? 1 : 0)

      rep_zip_level = [e, 0].max # max is because could be < 0 for special reps
      arg_ranks[ind] += [nreps, 0].max

      nreps.times{|time|
        ir2_nodes << IR2.new(Ops["repeat"].ways[0], [a], rep_zip_level, t, r+time+1)
        a = ir2_nodes.size - 1
      }
      a
    }

    # use identity op to promote type of nil when needed (this lets the engine know correct arg ranks for truthy/default values) (ruby lazylib cheats and uses dynamic rank)
    if way.vectorize # no cases of non vectorizing args taking [a] type
      op_rank = way.mod

      used_ranks = arg_ranks.zip(way.arg_ranks).map{|arg_rank, op_arg_rank|
        arg_rank - zip_level - ~op_arg_rank
      }

      used_ranks.zip(way.arg_types, way.arg_ranks, arg_types, arg_ranks, 0..).each{|used_rank, op_arg_type, op_arg_rank, arg_type, arg_rank, i|
        next if concrete_rank(op_arg_type)
        next if used_rank >= op_rank
        if arg_type == EmptyType
          ir2_nodes << IR2.new(Ops["_id"].ways[0], [args[i]], 0, EmptyType,
            ~op_arg_rank + op_rank + zip_level)
          args[i] = ir2_nodes.size - 1
        else
          # this is ok actually since it will happy due to adjust_ab_types (but empty type is never deficient so never adjusted)
#           raise "internal type error, deficient non empty type"
        end
      }
    end

    # superpad
    if way.name == "superpad"
      a = args[0]
      zip_level.times{|time|
        ir2_nodes << IR2.new(Ops["padDefault"].dup_mod(arg_ranks[0]-time-1).ways[0], [a], time, arg_types[0], arg_ranks[0])
        a = ir2_nodes.size - 1
      }
      ir2ir2[i] = a
    else
      # create node
      ir2_nodes << IR2.new(way, args, zip_level, types[i], ranks[i])
      ir2ir2[i] = ir2_nodes.size - 1
    end
  }
  ir2_nodes.each{|node| node.args.map!{|a| lookup_deferred_ind(a) } }
  print_inds = ir1_inds.map{|i| ir2ir2[i] }
  print_inds.map!{|a| lookup_deferred_ind(a) }

  [ir2_nodes, print_inds]
end

# generate implicit nodes that handle converting all outputs to a single string
def to_ir3(ir2, ir2_inds, pp_sep)
  ir3 = ir2.dup
  ir3 << IR2.new(Ops["nil"].ways[0], [], 0, CharType, 2)
  last_cons = ir3.size - 1
  if pp_sep
    ir3 << IR2.new(new_op("data", "[char]"){ str_to_lazy_list(pp_sep) }.ways[0], [], 0, CharType, 1)
    sep_ind = ir3.size - 1
  end

  ir2_inds.reverse_each{|i|
    if pp_sep
      ir3 << IR2.new(Ops["show"].ways[0], [i], 0, CharType, 1)
      ir3 << IR2.new(Ops["append"].ways[0], [ ir3.size - 1, sep_ind], 0, CharType, 1)
      i = ir3.size - 1
    else
      node = ir2[i]
      if node.type == IntType
        ir3 << node = IR2.new(Ops["str"].ways[0], [i], node.rank, CharType, node.rank+1)
        i = ir3.size-1
      end
      if node.rank == 0
        ir3 << node = IR2.new(Ops["just"].ways[0], [i], 0, CharType, 1); i = ir3.size-1
      elsif node.rank > 2
        ir3 << node = IR2.new(Unwords, [i], node.rank-2, CharType, node.rank-1); i = ir3.size-1
      end
      while node.rank > 1
        ir3 << node = IR2.new(Unlines, [i], node.rank-2, CharType, node.rank-1); i = ir3.size-1
      end
    end
    ir3 << IR2.new(Ops["cons"].ways[0], [last_cons, i], 0, CharType, 2)
    last_cons = ir3.size-1
  }
  ir3 << IR2.new(Ops["concat"].ways[0], [last_cons], 0, CharType, 1)
  [ir3, ir3.size-1]
end

def lookup_deferred_ind(a)
  been = {}
  while Proc===a
    raise IogiiError.new "direct circular dependency" if been[a]
    been[a] = true
    a=a.call
  end
  a
end
