/**
   * Compare to see if the argument <code>argType</code> passed to the method matches the type of
   * the corresponding parameter. The simplest case is when both are equal.
   *
   * <p>This is a conservative comparison - returns true if it cannot decide. If the parameter type
   * is a type variable (e.g. <code>T</code>) then we don't know enough (yet) to decide if they do
   * not match so return true.
   *
   * @param ignoreBaseType TODO
   */
  private IncompatibleTypes compareTypes(
      Type expectedType, Type actualType, boolean ignoreBaseType) {
    // XXX equality not implemented for GenericObjectType
    // if (parmType.equals(argType)) return true;
    if (expectedType == actualType) return IncompatibleTypes.SEEMS_OK;
    // Compare type signatures instead
    String expectedString = GenericUtilities.getString(expectedType);
    String actualString = GenericUtilities.getString(actualType);
    if (expectedString.equals(actualString)) return IncompatibleTypes.SEEMS_OK;

    if (expectedType.equals(Type.OBJECT)) return IncompatibleTypes.SEEMS_OK;
    // if either type is java.lang.Object, then automatically true!
    // again compare strings...

    String objString = GenericUtilities.getString(Type.OBJECT);

    if (expectedString.equals(objString)) {
      return IncompatibleTypes.SEEMS_OK;
    }

    // get a category for each type
    TypeCategory expectedCat = GenericUtilities.getTypeCategory(expectedType);
    TypeCategory argCat = GenericUtilities.getTypeCategory(actualType);
    if (actualString.equals(objString) && expectedCat == TypeCategory.TYPE_VARIABLE) {
      return IncompatibleTypes.SEEMS_OK;
    }
    if (ignoreBaseType) {
      if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
        GenericObjectType parmGeneric = (GenericObjectType) expectedType;
        GenericObjectType argGeneric = (GenericObjectType) actualType;
        return compareTypeParameters(parmGeneric, argGeneric);
      }
      return IncompatibleTypes.SEEMS_OK;
    }

    if (actualType.equals(Type.OBJECT) && expectedCat == TypeCategory.ARRAY_TYPE)
      return IncompatibleTypes.ARRAY_AND_OBJECT;

    // -~- plain objects are easy
    if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PLAIN_OBJECT_TYPE)
      return IncompatibleTypes.getPriorityForAssumingCompatible(expectedType, actualType, false);

    if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PLAIN_OBJECT_TYPE)
      return IncompatibleTypes.getPriorityForAssumingCompatible(
          (GenericObjectType) expectedType, actualType);
    if (expectedCat == TypeCategory.PLAIN_OBJECT_TYPE && argCat == TypeCategory.PARAMETERIZED)
      return IncompatibleTypes.getPriorityForAssumingCompatible(
          (GenericObjectType) actualType, expectedType);

    // -~- parmType is: "? extends Another Type" OR "? super Another Type"
    if (expectedCat == TypeCategory.WILDCARD_EXTENDS || expectedCat == TypeCategory.WILDCARD_SUPER)
      return compareTypes(
          ((GenericObjectType) expectedType).getExtension(), actualType, ignoreBaseType);

    // -~- Not handling type variables
    if (expectedCat == TypeCategory.TYPE_VARIABLE || argCat == TypeCategory.TYPE_VARIABLE)
      return IncompatibleTypes.SEEMS_OK;

    // -~- Array Types: compare dimensions, then base type
    if (expectedCat == TypeCategory.ARRAY_TYPE && argCat == TypeCategory.ARRAY_TYPE) {
      ArrayType parmArray = (ArrayType) expectedType;
      ArrayType argArray = (ArrayType) actualType;

      if (parmArray.getDimensions() != argArray.getDimensions())
        return IncompatibleTypes.ARRAY_AND_NON_ARRAY;

      return compareTypes(parmArray.getBasicType(), argArray.getBasicType(), ignoreBaseType);
    }
    // If one is an Array Type and the other is not, then they
    // are incompatible. (We already know neither is java.lang.Object)
    if (expectedCat == TypeCategory.ARRAY_TYPE ^ argCat == TypeCategory.ARRAY_TYPE) {
      return IncompatibleTypes.ARRAY_AND_NON_ARRAY;
    }

    // -~- Parameter Types: compare base type then parameters
    if (expectedCat == TypeCategory.PARAMETERIZED && argCat == TypeCategory.PARAMETERIZED) {
      GenericObjectType parmGeneric = (GenericObjectType) expectedType;
      GenericObjectType argGeneric = (GenericObjectType) actualType;

      // base types should be related
      {
        IncompatibleTypes result =
            compareTypes(parmGeneric.getObjectType(), argGeneric.getObjectType(), ignoreBaseType);
        if (!result.equals(IncompatibleTypes.SEEMS_OK)) return result;
      }
      return compareTypeParameters(parmGeneric, argGeneric);

      // XXX More to come
    }
    // If one is a Parameter Type and the other is not, then they
    // are incompatible. (We already know neither is java.lang.Object)
    if (false) {
      // not true. Consider class Foo extends ArrayList<String>
      if (expectedCat == TypeCategory.PARAMETERIZED ^ argCat == TypeCategory.PARAMETERIZED) {
        return IncompatibleTypes.SEEMS_OK; // fix this when we know what
        // we are doing here
      }
    }

    // -~- Wildcard e.g. List<*>.contains(...)
    if (expectedCat == TypeCategory.WILDCARD) // No Way to know
    return IncompatibleTypes.SEEMS_OK;

    // -~- Non Reference types
    // if ( parmCat == TypeCategory.NON_REFERENCE_TYPE ||
    // argCat == TypeCategory.NON_REFERENCE_TYPE )
    if (expectedType instanceof BasicType || actualType instanceof BasicType) {
      // this should not be possible, compiler will complain (pre 1.5)
      // or autobox primitive types (1.5 +)
      throw new IllegalArgumentException(
          "checking for compatibility of " + expectedType + " with " + actualType);
    }

    return IncompatibleTypes.SEEMS_OK;
  }