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}

  queue.each{|i|
    op = ir[i].op
    arg_types = ir[i].args.map{|a|types[a]}
    ways = select_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
  }

  types
end

def select_ways_by_type(ways, arg_types)
  matchedness = ways.map{|way| spec_matches(way.arg_types, arg_types).sort.reverse }
  best_match = matchedness.min
  return [] if 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)
  x = ways.map{|way|
    excess = excess_ranks(way, arg_ranks, arg_types)
    [ excess.map{|e| [-e, 0].max }.sort.reverse, # only care about when rank too low
      excess.map{|e| [e, 0].max }.sort.reverse, # but still prefer no excessive rank (for low
      way.mod_arg_ranks.sort # finally look at spec ranks since empty type never has excess
    ]
  }
  xmin_at = (0...x.size).min_by{|i| x[i] }
  xmin = x[xmin_at]
  effects = (0...x.size).filter{|i| x[i] == xmin }.map{|i|
    way = ways[i]
    [way.impl, calc_ret_rank(way, arg_ranks, arg_types)] }
  raise "internal error, ambiguous op %p" % [ways.map(&:name)*" "] if effects.uniq.size > 1
  ways[xmin_at]
end

def find_way(node, ways, arg_types, arg_ranks)
  ways = select_ways_by_type(ways, arg_types)
  raise IogiiError.new "op is not defined for base types: " + arg_types.map{|t|type_to_str(t)}*" ", node if ways.empty?
  find_way_by_rank(ways, arg_types, arg_ranks)
end

def find_valid_way(node, arg_types, arg_ranks)
  way = find_way(node, node.op.ways, arg_types, arg_ranks)
  excess = excess_ranks(way, arg_ranks, arg_types)
  excess.each.with_index{|e,i|
    raise IogiiError.new "arg #{i+1} rank too low and op does not promote (see #{Site}types.html#promotion )", node if e < 0 && (!way.promote || way.promote == NotFirst && i==0)
  }
  raise IogiiError.new "arg ranks too low and Filter op cannot promote BOTH args, doing so would could create rank invariant violations", node if way.name == "filter" && way.mod > 0 && excess.all?{|e|e < 0}

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

  way
end

def solve_ranks(ir,types)
  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}

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

    way = find_way(ir[i], ir[i].op.ways, arg_types, arg_ranks)

    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", 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
  }
  ranks
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
