object Test
{
  object Variances extends Enumeration {
    val CO, IN, CONTRA = Value
  }
  import Variances.{ CO, IN, CONTRA }
  
  object SubtypeRelationship extends Enumeration {
    val NONE, SAME, SUB, SUPER = Value
  }
  import SubtypeRelationship.{ NONE, SAME, SUB, SUPER }
  
  class VarianceTester[T, U, CC[_]](expected: Variances.Value)(
            implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) {
              
    def elements = List(ev1 <:< ev2, ev2 <:< ev1)
    def containers = List(ev3 <:< ev4, ev4 <:< ev3)

    def isUnrelated = typeCompare[T, U] == NONE
    def isSame = typeCompare[T, U] == SAME
    def isSub = typeCompare[T, U] == SUB
    def isSuper = typeCompare[T, U] == SUPER
        
    def showsCovariance = (elements == containers)
    def showsContravariance = (elements == containers.reverse)
    def showsInvariance = containers forall (_ == isSame)

    def allContainerVariances = List(showsCovariance, showsInvariance, showsContravariance)
    
    def showsExpectedVariance =
      if (isUnrelated) allContainerVariances forall (_ == false)
      else if (isSame) allContainerVariances forall (_ == true)
      else expected match {
        case CO     => showsCovariance && !showsContravariance && !showsInvariance
        case IN     => showsInvariance && !showsCovariance && !showsContravariance
        case CONTRA => showsContravariance && !showsCovariance && !showsInvariance
      }
  }
  
  def showsCovariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
    new VarianceTester[T, U, CC](CO) showsExpectedVariance

  def showsInvariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
    new VarianceTester[T, U, CC](IN) showsExpectedVariance
    
  def showsContravariance[T, U, CC[_]](implicit ev1: Manifest[T], ev2: Manifest[U], ev3: Manifest[CC[T]], ev4: Manifest[CC[U]]) =
    new VarianceTester[T, U, CC](CONTRA) showsExpectedVariance
  
  def typeCompare[T, U](implicit ev1: Manifest[T], ev2: Manifest[U]) = (ev1 <:< ev2, ev2 <:< ev1) match {
    case (true, true)   => SAME
    case (true, false)  => SUB
    case (false, true)  => SUPER
    case (false, false) => NONE
  }
  
  def assertAnyRef[T: Manifest] = List(
    manifest[T] <:< manifest[Any],
    manifest[T] <:< manifest[AnyRef],
    !(manifest[T] <:< manifest[AnyVal])
  ) foreach (assert(_, "assertAnyRef"))
  
  def assertAnyVal[T: Manifest] = List(
    manifest[T] <:< manifest[Any],
    !(manifest[T] <:< manifest[AnyRef]),
    manifest[T] <:< manifest[AnyVal]
  ) foreach (assert(_, "assertAnyVal"))
  
  def assertSameType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SAME, "assertSameType")
  def assertSuperType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SUPER, "assertSuperType")
  def assertSubType[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == SUB, "assertSubType")
  def assertNoRelationship[T: Manifest, U: Manifest] = assert(typeCompare[T, U] == NONE, "assertNoRelationship")
  
  def testVariancesVia[T: Manifest, U: Manifest] = assert(
    typeCompare[T, U] == SUB && 
    showsCovariance[T, U, List] &&
    showsInvariance[T, U, Set],
    "testVariancesVia"
  )
  
  def runAllTests = {
    assertAnyVal[AnyVal]
    assertAnyVal[Unit]
    assertAnyVal[Int] 
    assertAnyVal[Double] 
    assertAnyVal[Boolean]
    assertAnyVal[Char]
  
    assertAnyRef[AnyRef]
    assertAnyRef[java.lang.Object]
    assertAnyRef[java.lang.Integer]
    assertAnyRef[java.lang.Double]
    assertAnyRef[java.lang.Boolean]
    assertAnyRef[java.lang.Character]
    assertAnyRef[String]
    assertAnyRef[scala.List[String]]
    assertAnyRef[scala.List[_]]
        
    // variance doesn't work yet
    // testVariancesVia[String, Any]
    // testVariancesVia[String, AnyRef]

    assertSubType[List[String], List[Any]]
    assertSubType[List[String], List[AnyRef]]
    assertNoRelationship[List[String], List[AnyVal]]
  
    assertSubType[List[Int], List[Any]]
    assertSubType[List[Int], List[AnyVal]]
    assertNoRelationship[List[Int], List[AnyRef]]
    
    // Nothing
    assertSubType[Nothing, Any]
    assertSubType[Nothing, AnyVal]
    assertSubType[Nothing, AnyRef]
    assertSubType[Nothing, String]
    assertSubType[Nothing, List[String]]
    assertSubType[Nothing, Null]
    assertSameType[Nothing, Nothing]
    
    // Null
    assertSubType[Null, Any]
    assertNoRelationship[Null, AnyVal]
    assertSubType[Null, AnyRef]
    assertSubType[Null, String]
    assertSubType[Null, List[String]]
    assertSameType[Null, Null]
    assertSuperType[Null, Nothing]
    
    // Any
    assertSameType[Any, Any]
    assertSuperType[Any, AnyVal]
    assertSuperType[Any, AnyRef]
    assertSuperType[Any, String]
    assertSuperType[Any, List[String]]
    assertSuperType[Any, Null]
    assertSuperType[Any, Nothing]
    
    // Misc unrelated types
    assertNoRelationship[Unit, AnyRef]
    assertNoRelationship[Unit, Int]
    assertNoRelationship[Int, Long]
    assertNoRelationship[Boolean, String]
    assertNoRelationship[List[Boolean], List[String]]
    assertNoRelationship[Set[Boolean], Set[String]]
  }

  def main(args: Array[String]): Unit = runAllTests
}
