package io.github.timwspence.cats.stm

import java.util.concurrent.atomic.AtomicReference

import io.github.timwspence.cats.stm.STM.internal._

/**
  * Transactional variable - a mutable memory location
  * that can be read or written to via `STM` actions.
  *
  * Analagous to `cats.effect.concurrent.Ref`.
  */
final class TVar[A] private[stm] (
  private[stm] val id: Long,
  @volatile private[stm] var value: A,
  private[stm] val pending: AtomicReference[Map[TxId, Txn]]
) {

  /**
    * Get the current value as an
    * `STM` action.
    */
  def get: STM[A] = STM { log =>
    val entry = getOrInsert(log)
    TSuccess(entry.unsafeGet[A])
  }

  /**
    * Set the current value as an
    * `STM` action.
    */
  def set(a: A): STM[Unit] = STM { log =>
    val entry = getOrInsert(log)
    TSuccess(entry.unsafeSet(a))
  }

  /**
    * Modify the current value as an
    * `STM` action.
    */
  def modify(f: A => A): STM[Unit] = STM { log =>
    val entry   = getOrInsert(log)
    val updated = f(entry.unsafeGet[A])
    TSuccess(entry.unsafeSet(updated))
  }

  private def getOrInsert(log: TLog): TLogEntry =
    if (log.contains(id))
      log(id)
    else {
      val entry = TLogEntry(this, value)
      log += id -> entry
      entry
    }

}

object TVar {

  def of[A](value: A): STM[TVar[A]] = STM { _ =>
    val id = IdGen.incrementAndGet
    TSuccess(new TVar(id, value, new AtomicReference(Map())))
  }

}