package io.chrisdavenport.linebacker

import cats.effect._
import cats.effect.concurrent.Semaphore
import cats.implicits._
import java.util.concurrent.ExecutorService
import scala.concurrent.ExecutionContext

trait Linebacker[F[_]] {

  def blockingContext: ExecutionContext

  /**
   * Attempts to Run the Given `F[A]` on the blocking pool.
   * Then shifts back to the F to the Context Shift
   * Requires Implicit ContextShift Available
   */
  def blockContextShift[A](fa: F[A])(implicit cs: ContextShift[F]): F[A] =
    cs.evalOn(blockingContext)(fa)

  /**
   * Same Method as blockContextShift but significantly shorter.
    **/
  final def blockCS[A](fa: F[A])(implicit cs: ContextShift[F]): F[A] =
    blockContextShift(fa)
}

object Linebacker {
  def apply[F[_]](implicit ev: Linebacker[F]): Linebacker[F] = ev

  def fromExecutorService[F[_]](es: ExecutorService): Linebacker[F] = new Linebacker[F] {
    def blockingContext = ExecutionContext.fromExecutorService(es)
  }
  def fromExecutionContext[F[_]](ec: ExecutionContext): Linebacker[F] = new Linebacker[F] {
    def blockingContext = ec
  }

  def bounded[F[_]: Concurrent](lb: Linebacker[F], bound: Long): F[Linebacker[F]] = 
    Semaphore[F](bound).map(new BoundedLinebacker(lb, _))

  private class BoundedLinebacker[F[_]: Concurrent](lb: Linebacker[F], s: Semaphore[F]) extends Linebacker[F]{
    def blockingContext: ExecutionContext = lb.blockingContext
    override def blockContextShift[A](fa: F[A])(implicit cs: ContextShift[F]): F[A] =
      s.withPermit(lb.blockContextShift(fa))
  }
}