def solve_types(ir)
  queue = ir.size.times.to_a

  deps = ir.map{[]}
  queue.each{|i| ir[i].args.each{|a|deps[a]<<i} }
  deps.map!(&:uniq)

  types = ir.map{EmptyType}
  nodes_ways = ir.map{nil}

  queue.each{|i|
    op = ir[i].op
    arg_types = ir[i].args.map{|a|types[a]}
    nodes_ways[i] = ways = filter_ways_by_type(ir[i].op.ways, arg_types)
    ret_types = ways.map{|way| spec_to_ret(way, arg_types) }
    raise "ambiguous %s %p %p %d" % [op.name, arg_types, ret_types, ways.size] if ret_types.uniq.size > 1
    next if ret_types.empty?
    result_type = ret_types[0]
    if result_type > types[i]
      types[i] = result_type
      queue.concat(deps[i])
    elsif result_type < types[i]
      raise "type invariant has been violated"
    end
  }

  ir.size.times{|i|
    if nodes_ways[i].empty?
      arg_types = ir[i].args.map{|a|types[a]}
      raise IogiiError.new "op is not defined for base types: " + arg_types.map{|t|type_to_str(t)}*" ", ir[i]
    end
  }

  [types, nodes_ways]
end

def filter_ways_by_type(ways, arg_types)
  if ways.map(&:name) == ["chunkWhen", "range"] && arg_types.include?(IntType) && arg_types.include?(CharType)
    # allowing it to be range and coercing would violate type invariant
    return ways.filter{|way| way.name == "chunkWhen" }
  end
  matchedness = ways.map{|way| spec_matches(way.arg_types, arg_types).sort.reverse }
  best_match = matchedness.min
  return [] if !best_match || best_match[0] == :m4_no_match
  ways.filter.with_index{|way,i| matchedness[i] == best_match }
end

def find_way_by_rank(ways, arg_types, arg_ranks, str)
  if ways.size == 0
    raise
  elsif ways.size == 1
    ways[0]
  elsif ways.size == 2
    commas = str.count(',')
    overload,nonoverload = ways.partition{|way| way.stars.any? }.map{|part| part[0] }

    # these would have a return rank that violates rank invariant
    return nonoverload if commas > 0 && NoCommaOverloads.include?(str[0])

    excess = excess_ranks(overload, arg_ranks, arg_types)
    if excess.zip(overload.stars, overload.arg_ranks).any?{|e,s,r|
        s && e <= s + (r >= 0 ? commas : 0) }
      overload
    else
      nonoverload
    end
  else
    raise
  end
end

def assert_valid_way(way, node, arg_types, arg_ranks)
  excess = excess_ranks(way, arg_ranks, arg_types)
  excess.each.with_index{|e,i|
    raise IogiiError.new "arg#{i+1 if arg_ranks.size>1} rank too low and op does not promote
Expecting: #{type_and_rank_to_str(way.arg_types[i],way.mod_arg_ranks[i])}, Found: #{type_and_rank_to_str(arg_types[i],arg_ranks[i])}
(see #{Site}types.html#promotion )", node if e < 0 && (!way.promote || way.promote == NotFirst && i==0)
  }

  if !way.can_mod? && node.token && node.token.str[-1] == ","
      simpler_op = lookup_op(node.token.str[0...-1])
      simpler_way = find_way_by_rank(filter_ways_by_type(simpler_op.ways, arg_types), arg_types, arg_ranks, node.token.str[0...-1]) if simpler_op
      raise IogiiError.new "extra , used", node if simpler_way == way
  end

  way
end

def solve_ranks(ir, types, nodes_ways)
  queue = ir.size.times.to_a

  deps = ir.map{[]}
  queue.each{|i|ir[i].args.each{|a|deps[a]<<i}}
  deps.map!(&:uniq)

  ranks = ir.map{0}
  nodes_way = ir.map{nil}

  queue.each{|i|
    arg_types = ir[i].args.map{|a|types[a]}
    arg_ranks = ir[i].args.map{|a|ranks[a]}

    nodes_way[i] = way = find_way_by_rank(nodes_ways[i], arg_types, arg_ranks, ir[i].token ? ir[i].token.str : "")

    result_rank = calc_ret_rank(way, arg_ranks, arg_types)
    if result_rank > 100 # todo distinguish between this and legitimate cases (which for all practical purposes will never exist)
      raise IogiiError.new "cannot construct the infinite type
(the rank of values in the circular part of this program would need to be infinite, resulting from something like rank = rank + 1)", ir[i]
    elsif result_rank > ranks[i]
      ranks[i] = result_rank
      queue.concat(deps[i])
    elsif result_rank < ranks[i]
      raise "rank invariant has been violated"
    end
  }

  ir.size.times{|i|
    arg_types = ir[i].args.map{|a|types[a]}
    arg_ranks = ir[i].args.map{|a|ranks[a]}
    assert_valid_way(nodes_way[i], ir[i], arg_types, arg_ranks)
  }

  [ranks, nodes_way]
end

def calc_ret_rank(way, arg_ranks, arg_types)
  zip_level(way, arg_ranks, arg_types) + adjust_ab_ranks(way, arg_ranks.zip(coerce?(way.arg_types,arg_types)).map{|r,c| r + (c ? 1 : 0) }, arg_types)[1]
end

def adjust_ab_ranks(way, arg_ranks, arg_types)
  return [way.mod_arg_ranks, way.mod_ret_rank] if !way.arg_types.include?(BSpec)

  zip_level = (arg_ranks.zip(arg_types, way.mod_arg_ranks).map{|r, t, o|
    e = r-o
    e = [0, e].max if t == EmptyType
    e
  } << 0).max

  lower_a = lower_b = nil
  way.arg_ranks.zip(way.arg_types, arg_ranks, arg_types){|sr,st,r,t|
    l = [~sr+zip_level+way.mod-r,way.mod].min
    if st == ASpec && t != EmptyType
      lower_a =  [lower_a || l, l].min
    elsif st == BSpec && t != EmptyType
      lower_b = [lower_b || l, l].min
    end
  }
  lower_a ||= 0
  lower_b ||= 0

  # this causes
  lower_b = 0 if lower_a > 0

  [way.mod_arg_ranks.zip(way.arg_types).map{|r,t|
    r - (t == ASpec ? lower_a : 0) - (t == BSpec ? lower_b : 0)
  },
  way.mod_ret_rank - (way.ret_type == ASpec ? lower_a : 0 ) - (way.ret_type  == BSpec ? lower_b : 0)]
end

def excess_ranks(way, ranks, types)
  ranks = ranks.zip(coerce?(way.arg_types,types)).map{|r,c| r + (c ? 1 : 0) }
  ranks.zip(types, adjust_ab_ranks(way, ranks, types)[0]).map{|r, t, o|
    e = r-o
    e = [0, e].min if !way.vectorize
    e = [0, e].max if t == EmptyType
    e
  }
end

def zip_level(way, ranks, types)
  (excess_ranks(way, ranks, types)<<0).max
end
