package io.github.timwspence.cats.stm

import cats.effect.{ContextShift, IO, Timer}
import org.scalatest.matchers.should.Matchers
import org.scalatest.funsuite.AsyncFunSuite 

import scala.concurrent.ExecutionContext

class TVarTest extends AsyncFunSuite with Matchers {
  implicit override def executionContext: ExecutionContext = ExecutionContext.Implicits.global

  implicit val timer: Timer[IO] = IO.timer(executionContext)

  implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)

  test("Get returns current value") {
    val prog: STM[String] = for {
      tvar  <- TVar.of("hello")
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "hello"
    }
  }

  test("Set changes current value") {
    val prog: STM[String] = for {
      tvar  <- TVar.of("hello")
      _     <- tvar.set("world")
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "world"
      value shouldBe "world"
    }
  }

  test("Modify changes current value") {
    val prog: STM[String] = for {
      tvar  <- TVar.of("hello")
      _     <- tvar.modify(_.toUpperCase)
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "HELLO"
    }
  }

  test("Pending transaction is removed on success") {
    val tvar = TVar.of("foo").commit[IO].unsafeRunSync

    val prog: STM[String] = for {
      _     <- tvar.modify(_.toUpperCase)
      value <- tvar.get
    } yield value

    for (value <- prog.commit[IO].unsafeToFuture) yield {
      value shouldBe "FOO"

      tvar.value shouldBe "FOO"
      tvar.pending.get.isEmpty shouldBe true
    }
  }

  test("Pending transaction is removed on failure") {
    val tvar = TVar.of("foo").commit[IO].unsafeRunSync

    val prog: STM[String] = for {
      _     <- tvar.modify(_.toUpperCase)
      _     <- STM.abort[String](new RuntimeException("boom"))
      value <- tvar.get
    } yield value

    for (_ <- prog.commit[IO].attempt.unsafeToFuture) yield {
      tvar.value shouldBe "foo"

      tvar.pending.get.isEmpty shouldBe true
    }
  }
}