/*  Engr 691-6, Special Topics, Software Language Engineering
    Fowler's Email Message Builder DSL using Method Chaining 
      with a Progressive Interface
    H. C. Cunningham
    Version 1:  10 April 2009
    Version 1a: 12 April 2009

123456789012345678901234567890123456789012345678901234567890123456789012345678

This is a Scala reimplementation of Martin Fowler's Email Message
Builder DSL given in the Method Chaining section of his in-progress
DSL book  //http:www.martinfowler.com/dslwip/MethodChaining.html.

The author of this implementation had the C# code for the DSL
processing, but did not have the code for the semantic model.  There
are some guesses at what was intended.  The overall implementation in
Scala can probably be redesigned to take better advantage of Scala
features.

This is a simple example DSL for constructing email messages.  This
internal DSL ensures that the elements of the message are only added
in a particular order: first the to's, then the cc's, then the
subject, and finally the body.

Here is an example usage of the DSL given by Fowler:

  message = MessageBuilder.build
              .to("fowler@acm.org")
              .cc("editor@publisher.com")
              .subject("Error in book")
              .body("Sally Shipton should read Sally Sparrow")

At least one "to" field and the "body" must be given.  However, the
"cc" and "subject" fields are optional. There may be multiple
occurrences of "to" and "cc".

*/

/* The class "MessageBuilder" and the associated interfaces (Scala traits)
   "IMessageBuilderPostBuild", "IMessageBuilderPostTo",
   "IMessageBuilderPostCc", and "IMessageBuilderPostSubject" define an
   Expression Builder parser for the Email Message DSL. It uses the
   Method Chaining approach with Progressive Interfaces to control the
   ordering.

   The companion object "MessageBuilder" provides the factory method
   to start the construction.
*/

object MessageBuilder {
  def build: IMessageBuilderPostBuild = new MessageBuilder
}

class MessageBuilder 
  extends IMessageBuilderPostBuild with IMessageBuilderPostTo 
  with    IMessageBuilderPostCc    with IMessageBuilderPostSubject {
      
  private val content = new Message

  // DSL statment "to".
  def to(arg: String): IMessageBuilderPostTo = {
    content.addTo(new Email(arg))
    this
  }

  // DSL statment "cc".
  def cc(arg: String): IMessageBuilderPostCc = {
    content.addCc(new Email(arg))
    this
  }

  // DSL statment "subject".
  def subject(arg: String): IMessageBuilderPostSubject = {
    content.setSubject(arg)
    this
  }
  
  // DSL statment "body".
  def body(arg: String): Message  = {
    content.setBody(arg)
    content
  }

}

// Interface to accept only "to" messages
trait IMessageBuilderPostBuild {
  def to(arg: String): IMessageBuilderPostTo
}

// Interface to accept only "to", "cc", or "subject" messages
trait IMessageBuilderPostTo extends IMessageBuilderPostBuild {
  def cc(arg: String): IMessageBuilderPostCc 
  def subject(arg: String): IMessageBuilderPostSubject
}

// Interface to accept only "cc" or "subject" messages
trait IMessageBuilderPostCc {
  def cc(arg: String): IMessageBuilderPostCc
  def subject(arg: String): IMessageBuilderPostSubject
}

// Interface to accept only "body"
trait IMessageBuilderPostSubject {
  def body(arg: String): Message
}


/* The Semantic Model consists of the classes "Message" (for holding
   the message) and "Email" for validating and holding the email
   addresses.
*/

class Message {
  private var tos: List[Email] = Nil
  private var ccs: List[Email] = Nil
  private var subject: String  = null
  private var body: String     = null

  def addTo(to: Email)        { tos     = tos ::: List(to) }
  def addCc(cc: Email)        { ccs     = ccs ::: List(cc) }
  def setSubject(sub: String) { subject = sub              }
  def setBody(bod: String)    { body    = bod              }

  override def toString = 
    tos.mkString("To:  ", ",", "\n") + ccs.mkString("Cc:  ", ",", "\n") +
    "Subject:  " + subject + "\n"    + body +"\n"
}

class Email(arg: String) {
  // full implementation should validate the email address
  override def toString = arg
}


/* 
   Code to test the DSL processing partially.  
*/

object EmailProgressive {

  def main(args: Array[String]) {
    println("\nEmail message builder DSL test beginning.\n")

    val message = MessageBuilder.build
                    .to("fowler@acm.org")
                    .cc("editor@publisher.com")
                    .subject("Error in book")
                    .body("Sally Shipton should read Sally Sparrow")

    println(message)    
    println("Email message builder DSL test ending.")
  }
}
