/*  Engr 691-6, Special Topics, Software Language Engineering
    Fowler's C Code Generator for the State Machine (Secret Panel DSLs)
    H. Conrad Cunningham
    Version 1:  15 April 2009
    Version 1a: 13 May 2009   Separated out mock class CommmandChannel.

123456789012345678901234567890123456789012345678901234567890123456789012345678

This is a Scala implementation of Martin Fowler's C code generator
from the "Transformer Generation" chapter of his DSL book work in
progress http://www.martinfowler.com/dslwip/TransformerGeneration.html.

The technique also uses the Model-Aware Generation pattern.  The
boilerplate framework code for the state machine is in the Fowler
chapter on Model-Aware Generation.  (At present, the framework code
has not been redeveloped to allow the output of this code generator to
execute.)

*/


/* Class to transform the State Machine Semantic Model's data into C
   code that uses the the C framework for state machines.

   Because of the way the getEvents and getCommands methods of the
   StateMachine class were implemented, the events and commands may be
   tnerated in a different order than they were input.  The
   implementation of the State Machine model methods should be checked.

*/

import java.io._

class StaticC_Generator(machine: StateMachine) {

  def generate(output: Writer) {
      output.write(header)
      generateEvents(output)
      generateCommands(output)
      generateStateDeclarations(output)
      generateStateBodies(output)
      generateResetEvents(output)
      output.write(footer)
  }

  private val header = "#include \"sm.h\"\n" +
                       "#include \"sm-pop.h\"\n" +
                       "\nvoid build_machine() {\n";

  private val footer = "}"

  private def generateEvents(output: Writer) {
    for (e <- machine.getEvents)
      output.write("  declare_event(\"" + e.name + 
                   "\", \"" + e.code.name + "\");\n") 
    output.write("\n")
  }

  private def generateCommands(output: Writer) {
    for (c <- machine.getCommands)
      output.write("  declare_command(\"" + c.name + 
                   "\", \"" + c.code.name + "\");\n")
    output.write("\n")
  }

  private def generateStateDeclarations(output: Writer) {
    for (s <- machine.getStates)
      output.write("  declare_state(\"" + s.name + "\");\n")
    output.write("\n")
  }

  private def generateStateBodies(output: Writer) {
    for (s <- machine.getStates) {
      output.write("  /* body for " + s.name + " state */\n")
      for (c <- s.getActions) {
        output.write("  declare_action(\"" + s.name + 
                     "\", \"" + c.name + "\");\n")
      }
      for (t <- s.getTransitions) {
        output.write("  declare_transition(\"" + t.source.name + 
                     "\", \"" + t.trigger.name + 
                     "\", \"" + t.target.name + "\");\n")
      }
      output.write("\n")
    }
  }

  private def generateResetEvents(output: Writer) {
    output.write("  /* reset event transitions */\n")
    for (e <- machine.getResetEvents)
      for (s <- machine.getStates)
        if (!s.hasTransition(e.code)) {
          output.write("  declare_transition(\"" + s.name + 
                       "\", \"" + e.name + 
                       ", \"" + machine.getStart.name + "\");\n")
        }
  }

}


/* Do some limited testing of the State Machine Semantic model.

   Much of the hardcoded test here is the configuration example code
   from Fowler's book.  
*/

object StaticC_GeneratorTest {

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

    // Beginning of Folwer's example configuration code
    val doorClosed  = Event("doorClosed",  'D1CL)
    val drawOpened  = Event("drawOpened",  'D2OP)
    val lightOn     = Event("lightOn",     'L1ON)
    val doorOpened  = Event("doorOpened",  'D1OP)
    val panelClosed = Event("panelClosed", 'PNCL)

    val unlockPanelCmd = Command("unlockPanel", 'PNUL)
    val lockPanelCmd   = Command("lockPanel",   'PNLK)
    val lockDoorCmd    = Command("lockDoor",    'D1LK)
    val unlockDoorCmd  = Command("unlockDoor",  'D1UL)

    val idle                 = new State("idle")
    val activeState          = new State("active")
    val waitingForLightState = new State("waitingForLight")
    val waitingForDrawState  = new State("waitingForDraw")
    val unlockedPanelState   = new State("unlockedPanel")

    val machine = new StateMachine(idle)

    idle.addTransition(doorClosed, activeState)
    idle.addAction(unlockDoorCmd)
    idle.addAction(lockPanelCmd)
    idle.setMachine(machine)

    activeState.addTransition(drawOpened, waitingForLightState)
    activeState.addTransition(lightOn, waitingForDrawState)
    activeState.setMachine(machine)

    waitingForLightState.addTransition(lightOn, unlockedPanelState)
    waitingForLightState.setMachine(machine)

    waitingForDrawState.addTransition(drawOpened, unlockedPanelState)
    waitingForDrawState.setMachine(machine)

    unlockedPanelState.addAction(unlockPanelCmd)
    unlockedPanelState.addTransition(panelClosed, idle)
    unlockedPanelState.addAction(lockDoorCmd)
    unlockedPanelState.setMachine(machine)

    machine.addResetEvents(doorOpened)


    // Try the execution of the model
    val commandsChannel = new CommandChannel
    val control = new Controller(machine,commandsChannel)

    println(control.toString)

    control.handle('D1CL)
    control.handle('L1ON)
    control.handle('D2OP)
    control.handle('PNCL)
    println("\nCommands output:  " + commandsChannel.getOutput)

    // Now try code generation
    println("\nBegin code generation.\n")
    val codegen = new StaticC_Generator(machine)
    val outfile = new FileWriter("output.c")

    codegen.generate(outfile)
    outfile.close()
    println("\nEnd code generation.\n")

    println("\nState Machine test ending.")
  }
}
