/* % scalac DiningHakkersOnFsm.scala; % scala DiningHakkersOnFsm
 * minor mods made of activator template DiningHakkersOnFsm.scala
 * read http://192.168.17.223/pmateti/Courses/7370/
 * Lectures/Actors+Akka+Scala/dining-philosophers-akka-fsm.html */ 

import akka.actor._, akka.actor.FSM._ , scala.concurrent.duration._

sealed trait ChopstickMessage
object CmsgGrab extends ChopstickMessage
object CmsgRetn extends ChopstickMessage
case class CmsgTaken(chopstick: ActorRef) extends ChopstickMessage
case class CmsgBusy(chopstick: ActorRef) extends ChopstickMessage

sealed trait ChopstickState
case object CSAvailabe extends ChopstickState
case object CSTaken extends ChopstickState

case class IsWith(hakker: ActorRef)

class Chopstick extends Actor with FSM[ChopstickState, IsWith] {
  import context._

  startWith(CSAvailabe, IsWith(system.deadLetters))

  when(CSAvailabe) {
    case Event(CmsgGrab, _) =>
      goto(CSTaken) using IsWith(sender) replying CmsgTaken(self)
  }

  when(CSTaken) {
    case Event(CmsgGrab, currentState) =>
      stay replying CmsgBusy(self)
    case Event(CmsgRetn, IsWith(hakker)) if sender == hakker =>
      goto(CSAvailabe) using IsWith(system.deadLetters)
  }

  initialize
}

sealed trait HakkerMessage
object Think extends HakkerMessage

sealed trait HakkerState
case object Waiting extends HakkerState
case object Thinking extends HakkerState
case object Hungry extends HakkerState
case object WaitForOtherChopstick extends HakkerState
case object FirstChopstickDenied extends HakkerState
case object Eating extends HakkerState

case class Possess(left: Option[ActorRef], right: Option[ActorRef])

class FSMHakker(name: String, left: ActorRef, right: ActorRef) 
extends Actor with FSM[HakkerState, Possess] {

  startWith(Waiting, Possess(None, None))

  when(Waiting) {
    case Event(Think, _) =>
      println( s"$name starts to think" )
      startThinking(5.seconds)
  }

  when(Thinking) {
    case Event(StateTimeout, _) =>
      left ! CmsgGrab
      right ! CmsgGrab
      goto(Hungry)
  }

  when(Hungry) {
    case Event(CmsgTaken(`left`), _) =>
      goto(WaitForOtherChopstick) using Possess(Some(left), None)
    case Event(CmsgTaken(`right`), _) =>
      goto(WaitForOtherChopstick) using Possess(None, Some(right))
    case Event(CmsgBusy(_), _) =>
      goto(FirstChopstickDenied)
  }

  when(WaitForOtherChopstick) {
    case Event(CmsgTaken(`left`), Possess(None, Some(right))) =>
      startEating(left, right)
    case Event(CmsgTaken(`right`), Possess(Some(left), None)) =>
      startEating(left, right)
    case Event(CmsgBusy(chopstick), Possess(leftOption, rightOption)) =>
      leftOption.foreach(_ ! CmsgRetn)
      rightOption.foreach(_ ! CmsgRetn)
      startThinking(10.milliseconds)
  }

  private def startEating(left: ActorRef, right: ActorRef) = {
    println(s"$name has picked up ${left.path.name} and ${right.path.name} and starts to eat")
    goto(Eating)  using
      Possess(Some(left), Some(right)) forMax (5.seconds)
  }

  when(FirstChopstickDenied) {
    case Event(CmsgTaken(secondChopstick), _) =>
      secondChopstick ! CmsgRetn
      startThinking(10.milliseconds)
    case Event(CmsgBusy(chopstick), _) =>
      startThinking(10.milliseconds)
  }

  when(Eating) {
    case Event(StateTimeout, _) =>
      println(s"$name puts chopsticks down and starts to think")
      left ! CmsgRetn
      right ! CmsgRetn
      startThinking(5.seconds)
  }

  initialize

  private def startThinking(duration: FiniteDuration): State = {
    goto(Thinking) using
      Possess(None, None) forMax duration
  }
}

object DiningHakkersOnFsm {

  val acsy = ActorSystem()

  def main(args: Array[String]) {
    val chopsticks =
      for (i <- 0 to 4) yield 
        acsy.actorOf(Props[Chopstick], "C" + i)
    val hakkers = for {
      (name, i) <- List("P0", "P1", "P2", "P3", "P4").zipWithIndex
    } yield 
      acsy.actorOf(
        Props(classOf[FSMHakker], 
              name, chopsticks(i), chopsticks((i + 1) % 5)))
    hakkers.foreach(_ ! Think)
  }
}