public Binding balance(
      final PsiType x,
      final PsiType y,
      final Balancer balancer,
      final HashSet<Constraint> constraints) {
    final int indicator =
        (x instanceof PsiTypeVariable ? 1 : 0) + (y instanceof PsiTypeVariable ? 2 : 0);

    switch (indicator) {
      case 0:
        if (x instanceof PsiWildcardType || y instanceof PsiWildcardType) {
          final PsiType xType = x instanceof PsiWildcardType ? ((PsiWildcardType) x).getBound() : x;
          final PsiType yType = y instanceof PsiWildcardType ? ((PsiWildcardType) y).getBound() : y;

          switch ((x instanceof PsiWildcardType ? 1 : 0) + (y instanceof PsiWildcardType ? 2 : 0)) {
            case 1:
              if (((PsiWildcardType) x).isExtends()) {
                /* ? extends T1, T2 */
                return null;
              } else {
                /* ? super T1, T2 */
                if (xType != null && !"java.lang.Object".equals(xType.getCanonicalText())) {
                  return null;
                }
                return create();
              }

            case 2:
              if (((PsiWildcardType) y).isExtends()) {
                /* T1, ? extends T2 */
                if (yType instanceof PsiTypeVariable) {
                  final PsiTypeVariable beta = myFactory.create();

                  if (constraints != null) {
                    constraints.add(new Subtype(beta, yType));
                    if (x != null) {
                      constraints.add(new Subtype(x, yType));
                    }
                  }

                  return create();
                } else {
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(xType, yType));
                  }

                  return balance(xType, yType, balancer, constraints);
                }
              } else {
                /* T1, ? super T2 */
                if (yType instanceof PsiTypeVariable) {
                  final PsiTypeVariable beta = myFactory.create();

                  if (constraints != null) {
                    if (x != null) constraints.add(new Subtype(x, beta));
                    constraints.add(new Subtype(yType, beta));
                  }

                  return create();
                } else {
                  if (constraints != null && yType != null && xType != null) {
                    constraints.add(new Subtype(yType, xType));
                  }

                  return balance(xType, yType, balancer, constraints);
                }
              }

            case 3:
              switch ((((PsiWildcardType) x).isExtends() ? 0 : 1)
                  + (((PsiWildcardType) y).isExtends() ? 0 : 2)) {
                case 0: /* ? super T1, ? super T2 */
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(yType, xType));
                  }
                  return balance(xType, yType, balancer, constraints);

                case 1: /* ? extends T1, ? super T2 */
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(xType, yType));
                  }
                  return balance(xType, yType, balancer, constraints);

                case 2: /* ? super T1, ? extends T2*/
                  return null;

                case 3: /* ? extends T1, ? extends T2*/
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(xType, yType));
                  }
                  return balance(xType, yType, balancer, constraints);
              }
          }

          return create();
        } else if (x instanceof PsiArrayType || y instanceof PsiArrayType) {
          final PsiType xType =
              x instanceof PsiArrayType ? ((PsiArrayType) x).getComponentType() : x;
          final PsiType yType =
              y instanceof PsiArrayType ? ((PsiArrayType) y).getComponentType() : y;

          return balance(xType, yType, balancer, constraints);
        } else if (x instanceof PsiClassType && y instanceof PsiClassType) {
          final PsiClassType.ClassResolveResult resultX = Util.resolveType(x);
          final PsiClassType.ClassResolveResult resultY = Util.resolveType(y);

          final PsiClass xClass = resultX.getElement();
          final PsiClass yClass = resultY.getElement();

          if (xClass != null && yClass != null) {
            final PsiSubstitutor ySubst = resultY.getSubstitutor();

            PsiSubstitutor xSubst =
                TypeConversionUtil.getClassSubstitutor(yClass, xClass, resultX.getSubstitutor());
            if (xSubst == null) return null;

            Binding b = create();

            for (final PsiTypeParameter aParm : xSubst.getSubstitutionMap().keySet()) {
              final PsiType xType = xSubst.substitute(aParm);
              final PsiType yType = ySubst.substitute(aParm);

              final Binding b1 =
                  unify(
                      xType,
                      yType,
                      new Unifier() {
                        public Binding unify(final PsiType x, final PsiType y) {
                          return balance(x, y, balancer, constraints);
                        }
                      });

              if (b1 == null) {
                return null;
              }

              b = b.compose(b1);
            }

            return b;
          }
        } else if (y instanceof Bottom) {
          return create();
        } else {
          return null;
        }
        break;

      case 1:
        return balancer.varType((PsiTypeVariable) x, y);

      case 2:
        return balancer.typeVar(x, (PsiTypeVariable) y);

      case 3:
        return balancer.varVar((PsiTypeVariable) x, (PsiTypeVariable) y);
    }

    return null;
  }