import scala.math.Ordering
import scala.reflect.ClassTag

trait Sam { def apply(x: Int): String }
trait SamP[U] { def apply(x: Int): U }

class OverloadedFun[T](x: T) {
  def foo(f: T => String): String = f(x)
  def foo(f: Any => T): T = f("a")

  def poly[U](f: Int => String): String = f(1)
  def poly[U](f: Int => U): U = f(1)

  def polySam[U](f: Sam): String = f(1)
  def polySam[U](f: SamP[U]): U = f(1)

  // check that we properly instantiate java.util.function.Function's type param to String
  def polyJavaSam(f: String => String) = 1
  def polyJavaSam(f: java.util.function.Function[String, String]) = 2
}

class StringLike(xs: String) {
  def map[A](f: Char => A): Array[A] = ???
  def map(f: Char => Char): String = ???
}

object Test {
  val of = new OverloadedFun[Int](1)

  of.foo(_.toString)

  of.poly(x => x / 2 )
  of.polySam(x => x / 2 )
  of.polyJavaSam(x => x)

  val sl = new StringLike("a")
  sl.map(_ == 'a')  // : Array[Boolean]
  sl.map(x => 'a')  // : String
}

object sorting {
  def stableSort[K: ClassTag](a: Seq[K], f: (K, K) => Boolean): Array[K] = ???
  def stableSort[L: ClassTag](a: Array[L], f: (L, L) => Boolean): Unit = ???

  stableSort(??? : Seq[Boolean], (x: Boolean, y: Boolean) => x && !y)
}

// trait Bijection[A, B] extends (A => B) {
//   def andThen[C](g: Bijection[B, C]): Bijection[A, C] = ???
//   def compose[T](g: Bijection[T, A]) = g andThen this
// }

object SI10194 {
  trait X[A] {
    def map[B](f: A => B): Unit
  }

  trait Y[A] extends X[A] {
    def map[B](f: A => B)(implicit ordering: Ordering[B]): Unit
  }

  trait Z[A] extends Y[A]

  (null: Y[Int]).map(x => x.toString) // compiled
  (null: Z[Int]).map(x => x.toString) // didn't compile
}
