/*  Engr 691-6, Special Topics, Software Language Engineering
    Scala XML-Based Parser for Fowler's XML-Based External DSL
      for the State Machine Controller (Secret Panel)
    H. C. Cunningham
    Version 1:  14 April 2009
    Version 2:  16 April 2009 
      Modified for StateMachine changes (setMachine).
    Version 3:   3 May 2009   
      Name changed, separated CommandChannel, comments updated.
    Version 4:  13 May 2009   
      Refactored to use IncrementalStateMachineBuilder.
      Added object XMLMachineBuilder and tracing.

    Note: The version 4 changes require that a <resetEvent name=XXX >
    statement now come after the corresponding <event name=XXX />
    statement.

123456789012345678901234567890123456789012345678901234567890123456789012345678

This is a Scala implementation of Martin Fowler's XML-based DSL for
the State Machine Controller given as a part of the Secret Panel
example in the "An Introductory Example" section of his DSL
in-progress book http://www.martinfowler.com/dslwip/intro.html.

The author of this code did not have Fowler's Java code.  This program
uses Scala's XML and other features.

Here is Fowler's example XML DSL input file:

<stateMachine start ="idle">
    <event name="doorClosed" code="D1CL"/>
    <event name="drawOpened" code="D2OP"/>
    <event name="lightOn" code="L1ON"/>
    <event name="doorOpened" code="D1OP"/>
    <event name="panelClosed" code="PNCL"/>

    <command name="unlockPanel" code="PNUL"/>
    <command name="lockPanel" code="PNLK"/>
    <command name="lockDoor" code="D1LK"/>
    <command name="unlockDoor" code="D1UL"/>

  <state name="idle">
    <transition event="doorClosed" target="active"/>
    <action command="unlockDoor"/>
    <action command="lockPanel"/>
  </state>

  <state name="active">
    <transition event="drawOpened" target="waitingForLight"/>
    <transition event="lightOn" target="waitingForDraw"/>
  </state>

  <state name="waitingForLight">
    <transition event="lightOn" target="unlockedPanel"/>
  </state>

  <state name="waitingForDraw">
    <transition event="drawOpened" target="unlockedPanel"/>
  </state>

  <state name="unlockedPanel">
    <action command="unlockPanel"/>
    <action command="lockDoor"/>    
    <transition event="panelClosed" target="idle"/>
   </state>

  <resetEvent name = "doorOpened"/>
</stateMachine>

*/


import scala.xml._
import java.io._


/* An Expression Builder to parse the DSL and build the State Machine
   using the Semantic Model.

*/

/* The class XMLMachineBuilder implements what is essentially a
   recursive descent parser for the XML-based External DSL for the
   State Machine Controller example. The parser uses Scala's XML
   library to build the XML document tree and then uses Scala's XML
   pattern-matching facilities to recognize the various statements and
   attributes to determine information needed to populate the semantic
   model.

   The singleton object XMLMachineBuilder provides a method to
   load the XML input file and parse it to configure a state machine.
 
*/

object XMLMachineBuilder {

  def loadFile(fileName: String): StateMachine  = {
    var loadNode: Elem = null
    try { loadNode = XML.loadFile(fileName) }
    catch {
      case e: FileNotFoundException => 
        throw new RuntimeException(e.getMessage)
    }
    val builder = new XMLMachineBuilder
    builder.trace("Loaded XML file '" + fileName + "'.")
    builder.parseStateMachine(loadNode)
    val stateMachine = builder.getMachine
    builder.trace("Completed definition of state machine with start state '" 
                 + stateMachine.getStart.name + "'.")
    stateMachine
  }
}

class XMLMachineBuilder extends IncrementalStateMachineBuilder {

  // Parse enclosing <stateMachine> statement (top level)
  def parseStateMachine(node: Node) { 
    node match {

      case <stateMachine>{subNodes @ _*}</stateMachine> =>
        val start = (node \ "@start").toString
        trace("Defining state machine with start state '" + start + "'.")
        addState(start)
        for (s  <- subNodes) parseMachineBody(s)
        finishMachine

      case el => 
        val tr = el.toString.trim // ignore white space
        if (!tr.isEmpty()) syntaxError("Illegal element '"  + tr + "'.")
    }
  }

  // Parse body of <stateMachine> statement (second level)
  private def parseMachineBody(node: Node) {
    node match {

      case <event></event> =>
        val name = (node \ "@name").toString
        val code = (node \ "@code").toString
        trace("..Define event '" + name + "'.")
        addEvent(name,code)

      case <resetEvent></resetEvent> =>
        val name = (node \ "@name").toString
        trace("..Define resetEvent '" + name + "'.")
        addResetEvent(name)

      case <command></command> =>
        val name = (node \ "@name").toString
        val code = (node \ "@code").toString
        trace("..Define command '" + name + "'.")
        addCommand(name,code)

      case <state>{subNodes @ _*}</state> =>
        val name = (node \ "@name").toString
        trace("..Define state '" + name + "'.")
        addState(name)
        for (s <- subNodes) parseStateBody(s)

      case el => 
        val tr = el.toString.trim // ignore white space
        if (!tr.isEmpty()) syntaxError("Illegal element '"  + tr + "'.")
    }
  }

  // Parse body of <state> statement (third level)
  private def parseStateBody(node: Node) {
    node match {

      case <action></action> =>
        val command = (node \ "@command").toString
        trace("....Add action '" + command + "'.")
        addAction(command)

      case <transition></transition> =>
        val event  = (node \ "@event").toString
        val target = (node \ "@target").toString
        trace("....Add transition on '" + event + "' to '" + target + "'.")
        addTransition(event,target)

      case el => 
        val tr = el.toString.trim // ignore white space
        if (!tr.isEmpty()) syntaxError("Illegal element '"  + tr + "'.")
    }
  }

}


/*  Do some limited testing of the Secret Panel XML DSL  */

object StateMachineXMLTest {

  def main(args: Array[String]) {
    println("\nXML State Machine DSL test beginning.\n")

    // Load XML file, create builder, and call parser to build machine
    val stateMachine = XMLMachineBuilder.loadFile("SecretPanel.xml")

    println
    println(stateMachine)

    // Build the controller
    val commandsChannel = new CommandChannel
    val control         = new Controller(stateMachine,commandsChannel)

    println(control.toString)

    // Execute the model
    control.handle('D1CL)
    control.handle('L1ON)
    control.handle('D2OP)
    control.handle('PNCL)

    println("\nCommands output:  " + commandsChannel.getOutput)

    println("\nXML State Machine DSL test ending.\n")
  }
}
