/*  CSci 658, Software Language Engineering 
    Email Message Builder DSL using Method Chaining 
      with a Progressive Interface
    H. Conrad Cunningham

1234567890123456789012345678901234567890123456789012345678901234567890

2009-04-10: (V1) Original
2009-04-12: (V1a) Minor updates to Scala programming style.
2018-02-02: (V1b) Updated comments. Created compile script.

This is a Scala reimplementation of Martin Fowler's Email Message
Builder DSL given in the Method Chaining Chapter of his book
Domain-Specific Languages.

The author of this code 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.")
  }
}
