

import scala.language.postfixOps
import scala.reflect.runtime.universe._

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: TypeTag[T], ev2: TypeTag[U], ev3: TypeTag[CC[T]], ev4: TypeTag[CC[U]]) {

    def elements = List(ev1.tpe <:< ev2.tpe, ev2.tpe <:< ev1.tpe)
    def containers = List(ev3.tpe <:< ev4.tpe, ev4.tpe <:< ev3.tpe)

    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
        case x      => throw new MatchError(x)
      }
  }

  def showsCovariance[T, U, CC[_]](implicit ev1: TypeTag[T], ev2: TypeTag[U], ev3: TypeTag[CC[T]], ev4: TypeTag[CC[U]]) =
    new VarianceTester[T, U, CC](CO) showsExpectedVariance

  def showsInvariance[T, U, CC[_]](implicit ev1: TypeTag[T], ev2: TypeTag[U], ev3: TypeTag[CC[T]], ev4: TypeTag[CC[U]]) =
    new VarianceTester[T, U, CC](IN) showsExpectedVariance

  def showsContravariance[T, U, CC[_]](implicit ev1: TypeTag[T], ev2: TypeTag[U], ev3: TypeTag[CC[T]], ev4: TypeTag[CC[U]]) =
    new VarianceTester[T, U, CC](CONTRA) showsExpectedVariance

  def typeCompare[T, U](implicit ev1: TypeTag[T], ev2: TypeTag[U]) = (ev1.tpe <:< ev2.tpe, ev2.tpe <:< ev1.tpe) match {
    case (true, true)   => SAME
    case (true, false)  => SUB
    case (false, true)  => SUPER
    case (false, false) => NONE
  }

  def assertAnyRef[T: TypeTag] = List(
    typeOf[T] <:< typeOf[Any],
    typeOf[T] <:< typeOf[AnyRef],
    !(typeOf[T] <:< typeOf[AnyVal])
  ) foreach (assert(_, "assertAnyRef"))

  def assertAnyVal[T: TypeTag] = List(
    typeOf[T] <:< typeOf[Any],
    !(typeOf[T] <:< typeOf[AnyRef]),
    typeOf[T] <:< typeOf[AnyVal]
  ) foreach (assert(_, "assertAnyVal"))

  def assertSameType[T: TypeTag, U: TypeTag] = assert(typeCompare[T, U] == SAME, "assertSameType")
  def assertSuperType[T: TypeTag, U: TypeTag] = assert(typeCompare[T, U] == SUPER, "assertSuperType")
  def assertSubType[T: TypeTag, U: TypeTag] = assert(typeCompare[T, U] == SUB, "assertSubType")
  def assertNoRelationship[T: TypeTag, U: TypeTag] = assert(typeCompare[T, U] == NONE, "assertNoRelationship")

  def testVariancesVia[T: TypeTag, U: TypeTag] = 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
}
