# Prefixed so that they can be compared
EmptyType = :t0_empty # the type of an empty list, it can be anything
IntType = :t1_int
CharType = :t2_char
XSpec = :t3_s_x
ASpec = :t4_s_a
BSpec = :t5_s_b

def type_spec_parts(spec)
  spec.gsub("->"," ").split.map{|s|s[/[^*]+/] } # remove "*"s for quick ref low rank overloads
end

def concrete_rank(spec)
  !(spec == ASpec || spec == BSpec)
end

def concrete_type(spec)
  !(spec == ASpec || spec == BSpec || spec == XSpec)
end

def spec_rank(part)
  t = spec_type(part)
  r = part.scan(/\[|\{/).size
  concrete_rank(t) ? r : ~r
end

def spec_type(part)
  case part.tr('[]{}','')
  when "x"
    XSpec
  when "a"
    ASpec
  when "b"
    BSpec
  when "int"
    IntType
  when "char"
    CharType
  when "", "empty"
    EmptyType
  else raise "unknown arg type %p" % part
  end
end

def is_special_rep(part)
  part[0] == '{'
end

def type_to_str(sym)
  sym.to_s.sub(/^.*_/,'')
end

def type_and_rank_to_str(type,rank)
  r = "["*rank + type_to_str(type) + "]"*rank
  r == "[empty]" ? "[]" : r
end

def spec_matches(specs, args)
  a = same_spec(specs, args, ASpec).max
  b = same_spec(specs, args, BSpec).max
  specs = specs.map{|s| s == ASpec ? a : s == BSpec ? b : s }
  specs.zip(args).map{|s, a|
    case s
      when XSpec
        a == EmptyType ? :m1_defaults : :m0_matches
      when IntType
        case a
          when EmptyType; :m1_defaults
          when IntType; :m0_matches
          when CharType; :m4_no_match
        end
      when CharType
        case a
        when EmptyType; :m2_defaults_char
        when IntType; :m3_coerces
        when CharType; :m0_matches
        end
      when EmptyType
        :m0_matches
      else; raise s.to_s
    end
  }
end

def same_spec(specs, args, target)
  args.filter.with_index{|a,i| specs[i] == target }
end

def spec_to_ret(way, args)
  return IntType if OpsUsingZero.include?(way.name) && way.mod == 0 && args[0] == EmptyType

  if concrete_type(way.ret_type)
    way.ret_type
  else
    t = same_spec(way.arg_types, args,  way.ret_type).max || EmptyType
    way.ret_type == XSpec ? [t, IntType].max : t
  end
end

def coerce?(specs,args)
  specs.zip(args).map{|s,a|
    if s == CharType && a == IntType
      true
    else
      !concrete_type(s) && a == IntType && same_spec(specs, args, s).any?{|o| o == CharType }
    end
  }
end

