/*  Engr 691-6, Special Topics, Software Language Engineering
    Fowler's State Machine Model for the Secret Panel DSL Examples
    H. C. Cunningham
    Version 1: 14 April 2009
    Version 2: 16 April 2009 
      Added methods to get events, commands, and resetEvents from 
      a StateMachine for use with other of Fowler's examples.
    Version 3: 13 May 2009   
      Cleaned up Scala programming style some (changed var's to val's
      where possible, used class parameters without separate attributes,
      added return types on all public features to clearly define interface).

123456789012345678901234567890123456789012345678901234567890123456789012345678

This is a Scala reimplementation of Martin Fowler's State Machine
semantic model (for the Secret Panel Controller) given in the "An
Introductory Example" section of his in-progress DSL book
http://www.martinfowler.com/dslwip/intro.html.

The author of this code had much of the Java code for Fowler's model
from the introductory chapter.  However, there are a few guesses at
what was intended.  Also the integration of the code generation
example from the "Transformer Generation" chapter required further
enhancements of the model's implementation to provide the needed
accessor methods. The overall implementation in Scala can probably be
redesigned to take better advantage of Scala features.

*/


import scala.collection.mutable.Map
import scala.collection.mutable.Set
import scala.collection.mutable.ListBuffer


/* State Machine Event and Command objects.  

   This was changed from Fowler's Java code to use Scala case classes
   for conciseness and convenience.  These do expose the parameters of
   the constructors, but it did not seem to be a huge compromise in
   this application.  It does, however, make the code fragile with
   respect to the way names or codes are represented.
*/

abstract class AbstractEvent
case class Command(name: String, code: Symbol) extends AbstractEvent
case class Event  (name: String, code: Symbol) extends AbstractEvent


/* State machine Transition objects. This was also changed to be a
   case class for the Scala version for conciseness and convenience, with
   the same caveat as above.

   This code overrides the toString method generated by the case class
   mechanism.
*/

case class Transition(source: State, trigger: Event, target: State) {
  override def toString = "Transition(" + source.name + ", " + 
                           trigger.name + ", " + target.name + ")"
}


/* State Machine State objects.  Similar in style to the case classes,
   this exposes the "name" constructor parameter.
*/

class State(val name: String) {

  private var stateMachine: StateMachine = null
  private val actions                    = Set[Command]()
  private val transitions                = Map[Symbol,Transition]()

  // Mutator methods

  // Allow parent machine to register interest in subsequent changes
  def setMachine(mach: StateMachine) { stateMachine =  mach }

  def addAction(command: Command) { 
    actions += command 
    if (stateMachine != null) stateMachine.stateChanged(this)
  }

  def addTransition(event: Event, targetState: State) {
    transitions(event.code) = Transition(this,event,targetState)
    if (stateMachine != null) stateMachine.stateChanged(this)
  }

  // Accessor methods
  def getMachine: StateMachine   = stateMachine

  def getActions: List[Command]  = actions.toList

  def getEvents: List[Event]     = transitions.values.toList.map(_.trigger)

  def getAllTargets: List[State] = transitions.values.toList.map(_.target)

  def getTransitions: List[Transition] = transitions.values.toList

  def hasTransition(eventCode: Symbol): Boolean = 
    transitions.isDefinedAt(eventCode)

  def targetState(eventCode: Symbol): State = transitions(eventCode).target

  def executeActions(commandsChannel: CommandChannel) {
    for (c <- actions) { commandsChannel.send(c.code) }
  }

  override def toString = "State \"" + name + "\" \nwith actions " +
    actions.mkString(", ") + "\nand transitions " +
    transitions.values.toList.mkString(", ")
}


/* Object representing the entire State Machine with a given start
   state.
*/

class StateMachine(startState: State) {

  private val resetEvents = new ListBuffer[Event]

  /* The following were added by Cunningham to support getEvents and
     getCommands methods needed by code generators.  This only considers
     events and actions that are registered on states reachable from the 
     start state.  This might not be sufficient for some purposes.  The
     alternative would be to have methods to set or add these into the model
     during configuration (e.g., buidling from DSL input).

     Deficiency: The current optimization to avoid doing a graph
     traversal for each call of getStates, getEvents, and getCommands
     will not give the correct results if any new transitions or
     states are added after the first call.  As a work-around, the
     capability to have State objects to notify its enclosing machine
     of changes was added.  The way this capability is designed should
     be readdressed.

*/ 

    private var allStates: ListBuffer[State] = null
    private val allEvents                    = Set[Event]() 
    private val allCommands                  = Set[Command]()

  // Mutator methods
  def addResetEvents(events: Event*) { 
    for (e <- events) { resetEvents += e } 
    allStates = null // force a new traversal of state machine graph
  }

  def stateChanged(s: State) { 
    allStates = null;  allEvents.clear; allCommands.clear 
  }

  // NOT USED CURRENTLY
  private def addResetEvent_byAddingTransitions(e: Event) {
    for (s <- getStates)
      if (!s.hasTransition(e.code)) s.addTransition(e, startState)
  }

  // Accessor methods
  def getStart: State = startState

  def getEvents: List[Event] = {
    if (allStates == null) {
      allEvents.clear; allCommands.clear
      gatherForwards(startState)
    }
    allEvents ++= resetEvents
    allEvents.toList
  }

  def getCommands: List[Command] = {
    if (allStates == null) {
      allEvents.clear; allCommands.clear
      gatherForwards(startState)
    }
    allCommands.toList
  }

  /* This was changed from Fowler's design to store the result of the
     graph traversal in an attribute of the object.  It also creates the 
     allEvents and commands attributes.
  */

  def getStates: List[State] = {
    if (allStates == null) {
      allStates = new ListBuffer[State]
      allEvents.clear; allCommands.clear
      gatherForwards(startState)
    }
    allStates.toList
  }

  /*  Traverse the graph of states reachable from the start state via
      transitions to gather the states, events, and commands.
  */
  private def gatherForwards(start: State) {
    if (start == null) return
    if (allStates.contains(start)) return
    else {
      allStates   +=  start
      allEvents   ++= start.getEvents
      allCommands ++= start.getActions
      for (next <- start.getAllTargets) {
        gatherForwards(next)
      }
      return
    }
  }

  def isResetEvent(eventCode: Symbol): Boolean = 
    resetEvents.map(_.code).contains(eventCode)

  def getResetEvents: List[Event] = resetEvents.toList

  override def toString = 
    "STATE MACHINE with START STATE \"" + startState.name + 
    "\" \nREACHABLE STATES \n" + getStates.mkString("\n") + 
    " \nRESET EVENTS \n" + resetEvents.mkString(", ")
}


/* A Controller for the State Machine. */

class Controller(stateMachine: StateMachine, commandsChannel: CommandChannel) {

  private var currentState: State = stateMachine.getStart

  // Mutator methods
  def handle(eventCode: Symbol) {
    if (currentState.hasTransition(eventCode))
      transitionTo(currentState.targetState(eventCode))
    else if (stateMachine.isResetEvent(eventCode))
       transitionTo(stateMachine.getStart)
    else
      println("Error:  Unknown event code " + eventCode)
      // ignore unknown events
  }

  private def transitionTo(target: State) {
    currentState = target
    currentState.executeActions(commandsChannel)
  }

  // Overloaded handle methods added for convenience (not in Fowler)
  def handle(event: Event) { handle(event.code) }

  def handle(events: Seq[Event]) { for (e <- events) handle(e.code) }

  // Accessor Methods
  def getCommandChannel: CommandChannel = commandsChannel

  override def toString = "CONTROLLER for \n" + stateMachine
}
