/*
   Copyright 2010 Aaron J. Radke

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
package cc.drx

import scala.collection.JavaConverters._

// object Sketch{
//   def apply(gf:DrawContext => Unit) = new Sketch(gf)
// }
// class Sketch(gf:DrawContext => Unit)

import javafx.scene.image.{Image => FXImage}

object ImgFX{
  def loadFXImage(file:File):Option[FXImage] =
    Img.findInput(file,List("png","jpg"))
       .map{in => new FXImage(in.is)} //TODO do we need to close this stream?
}
case class ImgFX(img:FXImage, box:Rect=Rect(100,100)) extends Img{
  def moveTo(thatBox:Rect):ImgFX = copy(box = thatBox)
  def draw(implicit g:DrawContext) = g ! this
}
trait NodeFX{
  import javafx.scene.{Group,Scene,Node}
  def node:Node
  private var init = false

  def draw(box:Rect, scale:Double, g:DrawContext):Unit = {
    //--load it up on the first frame
    if(!init){
      for(scene <- g.asInstanceOf[DrawContextFX].scene){
        scene.getRoot().asInstanceOf[Group].getChildren.add(0,node) //Note: this sends the video to the back TODO make configurable
      }
      init = true
    }

    //--adjust the size and location on every frame!
    // view.setX(box.a.x) //translate may work just as well and be generic to the node
    // view.setY(box.a.y)
    // val localBox = boxOf(node)
    // val s = 1d //box.width/localBox.width //FIXME TODO choose height or width based on size
    // val s = 2d
    // node.setScaleX(scale)//pivot point is the center of the object so not useful
    // node.setScaleY(scale)
    val translateTransform = new javafx.scene.transform.Translate(box.a.x, box.a.y)
    val scaleTransform = new javafx.scene.transform.Scale(scale,scale,0d,0d) //pivot around origin
    node.getTransforms.setAll(translateTransform, scaleTransform)


    // Log(scale, box, localBox)
    // node.setScaleX(scale)
    // node.setScaleY(scale)
    // val box = rawSize fitIn img.box  //fit the picture size including aspect ratio into the drawing box //TODO check if rotations can also work here...
    ()
  }

  def visible:Boolean = node.isVisible  //TODO check if these work
  def visible_=(v:Boolean) = node.setVisible(v)

}
class VideoFX(file:File) extends MediaRemote with NodeFX{
  import javafx.scene.media.{Media => FXMedia,_}
  //--media
  lazy val media  = new FXMedia(file.url.toString)
  //--player and view(node)
  lazy val player = new MediaPlayer(media)
  lazy val node   = new MediaView(player)

  //--media remote methods
  def play:Unit = player.play
  def stop:Unit = player.stop
  def pause:Unit = player.pause
  def isPlaying:Boolean = player.getStatus == MediaPlayer.Status.PLAYING
  def cursor:Time = Time(player.getCurrentTime)
  def cursor_=(t:Time):Unit = player.seek(t)
  def speedup:Double = player.getCurrentRate
  /** note changing the rate during playback causes some blocking but not terrible and only if changed*/
  def speedup_=(v:Double):Unit = player.setRate(v)

  //--cached fields
  private var _length:Option[Time] = None
  def length:Option[Time] = {
    if(_length.isEmpty)
        _length = media.getDuration.noneIf{d => d.isUnknown|| d.isIndefinite}.map{Time.apply}
    _length
  }
  private var _size:Option[Vec] = None
  def size:Option[Vec] = {
    if(_size.isEmpty){
      val w = media.getWidth
      val h = media.getHeight
      if(w == 0 || h == 0) _size = Some(Vec(w,h))
    }
    _size
  }

  def snapshot:Img = ImgFX(DrawContextFX.snapshot(node))
  def snapshot(size:Vec):Img = ImgFX(DrawContextFX.snapshot(node,size))

  override def draw(box:Rect, scale:Double, g:DrawContext):Unit = {
    super.draw(box,scale,g)
    node.setFitHeight(box.height)
    node.setFitWidth(box.width)
    ()
  }

}
case class WebFX(file:File) extends NodeFX{
  import javafx.scene.web.{WebView, WebEngine}
  val node = new WebView
  val engine = node.getEngine
  val content:String = Img.findInput(file,List("html","svg"))
                          .map{_.toString}
                          .getOrElse(s"<html><body>${file.canon.path}</body></html>")
  engine.loadContent(content)

  // def size:Vec = Vec(node.getMeasuredWidth,node.getMeasuredHeight)
  // engine.load(url)
}


object DrawContextFX{
  //-- constructors
  def apply(c:javafx.scene.canvas.Canvas) = new DrawContextFX(c.getGraphicsContext2D)
  def apply(gc:javafx.scene.canvas.GraphicsContext) = new DrawContextFX(gc)
  //TODO other constructors

  //-- helper utilities
  //
  import javafx.scene.Node
  private def nodeBox(node:Node):Rect = {
    val b = node.getBoundsInLocal()
    Rect(Vec(b.getMinX, b.getMinY), Vec(b.getMaxX, b.getMaxY))
  }
  private def nodeSize(node:Node):Vec = {
    val b = node.getBoundsInLocal()
    Vec(b.getWidth, b.getHeight)
  }

  private def nodeSizeInParent(node:Node):Vec = {
    val b = node.getBoundsInParent
    Vec(b.getWidth, b.getHeight)
  }
  def snapshot(node:Node):FXImage = snapshot(node, nodeSizeInParent(node), false)
  def snapshot(node:Node, size:Vec):FXImage = snapshot(node, size, true)
  private def snapshot(node:Node, size:Vec, useOptimalScale:Boolean):FXImage = {
    val sp = new javafx.scene.SnapshotParameters()
    sp.setFill(javafx.scene.paint.Color.TRANSPARENT) //enable background transparency
    if(useOptimalScale){
      val orgSize = nodeSizeInParent(node)
      val scale:Double = (size.x/orgSize.x)  min  (size.y/orgSize.y)
      sp.setTransform( new javafx.scene.transform.Scale(scale,scale))
    }
    val wi = new javafx.scene.image.WritableImage(size.x.round.toInt, size.y.round.toInt)
    node.snapshot(sp, wi)
    wi
  }
  def save(img:FXImage, file:File):Try[File] = {
    file.mkParents.flatMap{f => Try{
        javax.imageio.ImageIO.write(
          javafx.embed.swing.SwingFXUtils.fromFXImage(img,null),
          f.ext, f.file
        )
        file
      }
    }
  }
  def save(node:Node, file:File):Try[File] = save(snapshot(node), file)

}
// javafx.scene.canvas.GraphicsContext
//
class DrawContextFX(val g:javafx.scene.canvas.GraphicsContext, val scene:Option[javafx.scene.Scene]=None) extends DrawContext{
  import javafx.scene.text.{Font => FXFont, TextAlignment}
  import javafx.geometry.VPos
  import scala.language.implicitConversions
  import Style._

  private var _stroke:Option[Color] = None
  private var _fill:Option[Color] = None
  private var _weight:Double = 1d

  //TODO move to protected so only the drawing contexts can use these lookup values
  def stroke:Option[Color] = _stroke
  def fill:Option[Color] = _fill
  def weight:Double = _weight

  override def !(p:Default.type):Unit      = {
      //g.background(White)
      super.!(p)
      //--canvas/context
      _stroke = None
      _fill = None
  }

  private implicit def colorToFxColor(c:Color) = javafx.scene.paint.Color.rgb(c.r,c.g,c.b,c.a.toDouble/255)

  private def canvas = g.getCanvas
  def size:Vec = Vec(canvas.getWidth, canvas.getHeight)

  //--required properties `p`
  // note this argument based dispatching is applied at compile time and does not need runtime pattern matching
  //---coloring
  def !(p:Background):Unit      = {
    val s = screen
    g.clearRect(s.a.x, s.a.y, s.width, s.height) //always clear the screen but only draw a background color if visible
    if(!p.c.isFullyTransparent){ //fully transparent is essentially a null case so dont' use anything in that case
      g.setFill(p.c) //use the background color
      g.fillRect(s.a.x, s.a.y, s.width, s.height)
    }
  }
  def !(p:Fill):Unit            = {_fill   = Some(p.c); g.setFill(p.c)}
  def !(p:Stroke):Unit          = {_stroke = Some(p.c); g.setStroke(p.c)}
  def !(p:Weight):Unit          = {_weight=p.value; g.setLineWidth(p.value)}
  def !(p:FillNone.type):Unit   = _fill = None  //TODO do we need to call noFill here?
  def !(p:StrokeNone.type):Unit = _stroke = None  //TODO do we need to call noStroke here?
  def !(p:Font):Unit            = g.setFont(fontCache(p).fxFont)

  def style(f: =>Unit):Unit = {g.save; f; g.restore}

  //--cache to auto load and keep resources, this removes the initialization requirement and implementation details to the surface drawn context
  /**compute the width of represented string*/
  def textSize(text:Text,font:Font):Vec = fontCache(font).sizeOf(text)
  class FontFX(val font:Font){
    lazy val fxFont = Try{FXFont.font(font.name, font.size)} getOrElse FXFont.getDefault
    //private lazy val metrics = new FontMetrics(fxFont) awt mechanism
    def sizeOf(text:Text):Vec = { //FIXME  make this box work right
      import javafx.scene.text.{Text => FXText,TextBoundsType}
      val txt = new FXText(text.value)
      txt.setFont(fxFont)
      // txt.setBoundsType(TextBoundsType.VISUAL) //LOGICAL, VISUAL, LOGICAL_VERTICAL_CENTER

      //--use intersect to find size https://stackoverflow.com/a/32291954/622016
      // import javafx.scene.shape.{Rectangle,Shape}
      // val b = txt.getBoundsInLocal
      // val clip = new Rectangle(b.getMinX, b.getMinY, b.getWidth, b.getHeight)
      // val shape = Shape.intersect(txt, clip)
      //--report size
      DrawContextFX.nodeSize(txt)
    }
  }
  private val fontCache:Cache[Style.Font,FontFX] = Cache{font:Style.Font =>
    new FontFX(font)
  }
  private val svgCache = Cache{xml:String =>
    //new PShapeSVG(processing.data.XML.parse(xml))
    ???
  }
  private val webCache = Cache{f:File =>
    new WebFX(f)
  }
  private val imgCache = Cache{f:File =>
    ImgFX.loadFXImage(f).get //FIXME don't use the dangerous get (offer an alternative)
  }
  private val videoCache = Cache{f:File => new VideoFX(f) }

  //--video
  def remote(video:Video):MediaRemote = videoCache(video.file)
  def !(video:Video):Unit = videoCache(video.file).draw(video.box, 1d, this)
  def !(html:Html):Unit = webCache(html.file).draw(html.box, html.scale, this)

  def emptyCache() = {  //TODO or use the word clear
     fontCache.empty
     webCache.empty
     svgCache.empty //TODO remove and reference webCache instead
     imgCache.empty
     videoCache.empty
  }

  def close():Unit = {
    for(v <- videoCache.values) v.stop //stop all media from playing
    // emptyCache(0
  }

  //--Alignment
  def !(p:Align):Unit = {
    g.setTextAlign(p.horz match { case Left => TextAlignment.LEFT case Center  =>  TextAlignment.CENTER  case Right  => TextAlignment.RIGHT  })
    g.setTextBaseline(p.vert match { case Top  => VPos.TOP  case Midline =>  VPos.CENTER  case Bottom => VPos.BOTTOM })
  }

  //--transform (use affine matrix instead?)
  def !(p:Translate):Unit     = g.translate(p.t.x,p.t.y)
  def !(p:ScaleProperty):Unit = g.scale(p.t.x,p.t.y)
  def !(p:Rotate):Unit        = g.rotate(p.r.deg) //fx strangely specified in degrees instead of rad

  //-- shapes `s`
  def !(p:Path):Unit = path{
    val head:Vec = p.vertices.head.last
    val rest:Iterable[Vertex] = p.vertices.tail
    g.moveTo(head.x, head.y)
    rest.foreach{ //TODO it would be nice if these didn't require pattern match but to make something working (run with it for now and optimize/generalize later)
      case Vec(x,y,_)            => g.lineTo(x,y)
      case BezierVertex(ca,cb,b) => g.bezierCurveTo(ca.x,ca.y,   cb.x,cb.y,   b.x,b.y)
    }
    if(p.isClosed) g.closePath()
  }

  def !(s:Circ):Unit    = ellipse(s.c, Vec(s.r,s.r))
  def !(s:Line):Unit    = {
    if(_stroke.isDefined) g.strokeLine(s.a.x,s.a.y, s.b.x,s.b.y)
  }
  def !(s:Rect):Unit    = {
    if(_fill.isDefined)   g.fillRect(  s.a.x , s.a.y , s.width   , s.height)
    if(_stroke.isDefined) g.strokeRect(s.a.x , s.a.y , s.width   , s.height)
  }
  def !(s:Text):Unit    = {
    if(_fill.isDefined)   g.fillText(  s.value, s.pos.x , s.pos.y)
    if(_stroke.isDefined) g.strokeText(s.value, s.pos.x , s.pos.y)
  }

  private def path(f: =>Unit):Unit = {
    g.beginPath()
    f
    if(_fill.isDefined)   g.fill()
    if(_stroke.isDefined) g.stroke()
  }
  def !(s:Poly):Unit    = path{
    val head = s.vertices.head
    g.moveTo(head.x, head.y)
    s.vertices.tail.foreach{v => g.lineTo(v.x, v.y)}
    g.closePath()
  }
  def !(tri:Tri):Unit     = path{
    g.moveTo(tri.a.x, tri.a.y)
    g.lineTo(tri.b.x, tri.b.y)
    g.lineTo(tri.c.x, tri.c.y)
    g.lineTo(tri.a.x, tri.a.y)
    g.closePath()
  }
  def !(a:Arc):Unit     = {
    g.save
      g.setLineWidth(a.w)
      // TODO g.setStrokeCap(SQUARE) is already default in FX
      val w = a.r*2
      //g.ellipseMode(is set to CENTER as default which means RADIUS ???)
      g.strokeArc(a.c.x,a.c.y,  w,w,  a.angle.min.deg,a.angle.max.deg,  javafx.scene.shape.ArcType.OPEN)
    g.restore
  }
  private def ellipse(c:Vec, r:Vec):Unit = {
    if(_fill.isDefined)   g.fillOval(  c.x-r.x , c.y-r.y , r.x*2   , r.y*2)  //fx oval is upper left referenced and width and height
    if(_stroke.isDefined) g.strokeOval(c.x-r.x , c.y-r.y , r.x*2   , r.y*2)
  }
  def !(e:Ellipse):Unit = {
    if(e.rotation == Angle(0)) ellipse(e.c, e.r)
    else{
      g.save
        g.translate(e.c.x, e.c.y)
        g.rotate(e.rotation.deg)
        ellipse(Vec.zero, e.r)
      g.restore
    }
  }
  private def rawImageOf(img:Img):FXImage = img match {
    case ImgFile(file, _) => imgCache(file)
    case ImgFX(img,_) => img   //raw wrapped image
    case _ => ???  //TODO Img wrapped Pimage
  }

  def !(img:Img):Unit = {
    val raw:FXImage = rawImageOf(img)
    val rawSize = Rect(raw.getWidth,raw.getHeight)
    val box = rawSize fitIn img.box  //fit the picture size including aspect ratio into the drawing box //TODO check if rotations can also work here...
    g.drawImage(raw, box.a.x, box.a.y, box.width, box.height)
  }
  def !(svg:Svg):Unit = ???//g.shape(svgCache(svg.xml)) 
  // def !(arrow:Arrow):Unit = ???
  def !(star:Star):Unit = ???//shape{for(v <- star.vertices) g.vertex(v.x, v.y)}
  override def !(z:Bezier):Unit = path{
    g.moveTo(z.a.x, z.a.y)
    g.bezierCurveTo(z.ca.x, z.ca.y,  z.cb.x, z.cb.y,    z.b.x,z.b.y) //general bezier
  }

  //--sketch 
  //initialize a draw context for a sketch operations with a specific draw function on events with this concrete draw context and let it run //TODO add a stop sketch func on the return type
  //this should only be called once  on a draw context so maybe move it to a constructor?
  def sketchWith(draw:DrawContext => Unit):Unit = sketchWith(draw, None)
  def sketchWith(frameRate:Frequency)(draw:DrawContext => Unit):Unit = sketchWith(draw, Some(frameRate.hz.round.toInt))
  def sketchWith(draw:DrawContext => Unit, maxFPS:Option[Int]):Unit = {
    canvas.setFocusTraversable(true) //this is not on by default and needed for key events https://stackoverflow.com/a/24127625/622016
    val listener = Java.InvalidationListener{_ => draw(this)}
    canvas.widthProperty  addListener listener
    canvas.heightProperty addListener listener

    def update():Unit = {draw(this); frameFinished()}

    //--mouse events
    //root:works scene:works canvas:works (mouse moved at least)
    // getX:The entire window getSceneX:works getScreenX:Entire monitor window
    def handleMouse(f:Vec => Unit) = Java.EventHandler{e:javafx.scene.input.MouseEvent =>
      f(Vec(e.getX, e.getY))
      update()
    }
    canvas.setOnMouseMoved    ( handleMouse ( this mouseMoved _ ) )
    canvas.setOnMouseClicked  ( handleMouse ( this mouseClicked _ ) )
    canvas.setOnMousePressed  ( handleMouse ( this mousePressed _ ) )
    canvas.setOnMouseReleased ( handleMouse ( this mouseReleased _ ) )
    canvas.setOnMouseDragged  ( handleMouse ( this mouseDragged _ ) )
    //--touch events
    def handleTouch(f:List[Vec] => Unit) = Java.EventHandler{e:javafx.scene.input.TouchEvent =>
      f(e.getTouchPoints.asScala.toList map {p => Vec(p.getX, p.getY)})
      update()
    }
    canvas.setOnTouchMoved    ( handleTouch ( this touchMoved _ ) )
    canvas.setOnTouchPressed  ( handleTouch ( this touchPressed _ ) )
    canvas.setOnTouchReleased ( handleTouch ( this touchReleased _ ) )
    //--scroll events
    def handleScroll(f:Vec => Unit) = Java.EventHandler{e:javafx.scene.input.ScrollEvent =>
      f(Vec(e.getDeltaX, e.getDeltaY))
      update()
    }
    canvas.setOnScroll( handleScroll ( this scrollMoved _ ) )

    // https://stackoverflow.com/a/32597817/622016
    import javafx.scene.input.DragEvent
    def handleDrag(f:String => Unit) = Java.EventHandler{e:javafx.scene.input.DragEvent =>
      val db = e.getDragboard
      val res = if(db.hasFiles) Some(db.getFiles.asScala.map{f => File(f).path}.mkString(";"))
                else if(db.hasString) Some(db.getString)
                else None

      e.getEventType match {
        case DragEvent.DRAG_OVER =>
          if(e.getGestureSource != canvas){
            for(content <- res) {
              e.acceptTransferModes(javafx.scene.input.TransferMode.ANY:_*) // TransferMode.COPY_OR_MOVE:_*)
              f(content)
            }
          }
        case DragEvent.DRAG_DROPPED =>
          for(content <- res) f(content)
          e.setDropCompleted(res.isDefined)
        case _ =>
      }
      e.consume
    }
    canvas.setOnDragDropped( handleDrag( this dragDropped _) )
    canvas.setOnDragOver( handleDrag( this dragOver _) )

    //--keyboard events
    def handleKey(f:Keyboard.KeyCode => Unit) = Java.EventHandler{e:javafx.scene.input.KeyEvent =>
      val fxCode = e.getCode
      val drxCode = Keyboard.KeyCode.fromFX(fxCode)
      // Log(fxCode,drxCode)
      f(drxCode)
      e.consume() //TODO is this consume required??
      update()
    }
    canvas.setOnKeyPressed    (handleKey ( this keyPressed _ ) )
    canvas.setOnKeyReleased   (handleKey ( this keyReleased _ ) )
    // TODO canvas.setOnKeyReleased???
    // TODO canvas.setOnKeyTyped ???

    //--scroll events
    // TODO canvas.setOnScroll

    //--init draw without any events
    update()

    //--setup animation frame rate
    //https://stackoverflow.com/a/30151889/622016
    // if framerate is defined then launch it in a repeated loop
    for(fps <- maxFPS if fps > 0){
      // (1.s/fps) repeat update() //Note bad things happen when using the scheduled context execution to do the animations
      val frameNanos = (1.s/fps).ns.toLong
      val timer = new javafx.animation.AnimationTimer{
        private var last:Long = 0L
        override def handle(now:Long):Unit = {
          if(now - last > frameNanos){
            last = now //update the last time //before the update call
            update() //do the drawing update
          }
        }
      }
      timer.start()
    }
  }

  override def save(img:Img, file:File):Try[File] = img match {
    case ImgFX(img,_) => DrawContextFX.save(img, file)
    case _ => ???
  }

  override def save(file:File):Try[File] = DrawContextFX.save(canvas, file)
  override def snapshot(size:Vec):Img = ImgFX(DrawContextFX.snapshot(canvas,size))
  override def snapshot:Img = ImgFX(DrawContextFX.snapshot(canvas))
}

class SketchApp {
  //--override-able
  def size:Vec = Vec(600,400)
  def title:String = this.toString.takeWhile(_ != '$').reverse.takeWhile(_ != '.').reverse //"cc.drx.SketchApp"
  def icon:ImgFile = if(Img(title).toFX.isDefined) Img(title) else Img("drx.png")
  def maxFPS:Option[Int] = Some(60)
  def transparent:Boolean = false
  def clearEachFrame:Boolean = true
  def background:Color = Transparent

  protected[drx] var _args:Array[String] = Array()
  def args:Array[String] = _args

  //--sketch function
  final def onDraw(f:DrawContext => Unit):Unit = DrawContextFXApp := f

  //--init function
  // final def onInit(f:DrawContext => Unit):Unit = DrawContextFXApp.onInit(f)

  //--implemented
  final def main(args:Array[String]):Unit = {DrawContextFXApp.launch(this, args)} //singleton launcher..

  //--settings helper functions for JavaFX (could exist in an object)..
  final def applySettings(stage:javafx.stage.Stage):Unit = {
    if(transparent) stage.initStyle(javafx.stage.StageStyle.TRANSPARENT) //transparent backgrounds
    icon.toFX.foreach(stage.getIcons.add) //if icon file or resource exists add it new javafx.scene.image.Image("path to image... or resourse as stream"))
    stage.setTitle(title)
  }
  final def applySettings(scene:javafx.scene.Scene):Unit = {
    if(transparent) scene.setFill(null)
  }

}

object DrawContextFXApp{
  import javafx.application.{Application=>FXApplication}

  private var _app:SketchApp = new SketchApp //initial settings without any override

  def main(args:Array[String]):Unit = {_app._args = args; FXApplication.launch(classOf[DrawApp], args: _*)}
  def launch:Unit = main(Array())
  def launch(app:SketchApp):Unit = {_app=app; main(Array())}
  def launch(app:SketchApp,args:Array[String]):Unit = {_app=app; main(args)}

  def sketchWith(f:DrawContext => Unit):Unit = { this += f; launch}

  private var drawFunctions:List[DrawContext => Unit] = List()

  def +=(f:DrawContext => Unit) = drawFunctions = f :: drawFunctions
  def :=(f:DrawContext => Unit) = drawFunctions = List(f)

  // private var _init:DrawContext => Unit = {_ => ()}  //empty init function
  // def onInit(f:DrawContext => Unit) = _init = f

  def draw(g:DrawContext):Unit = {
    //--by default the each frame is clearEachFrame is drawn (clear the buffer), or do it just the first time
    if(_app.clearEachFrame || g.frame == 0) g ! Style.Background(_app.background)
    //--draw all specified drawFunctions
    for(f <- drawFunctions) f(g)
  }

  class DrawApp extends FXApplication{

    final override def start(stage:javafx.stage.Stage):Unit = {


      //--canvas
      val canvas = new javafx.scene.canvas.Canvas(_app.size.x.toInt, _app.size.y.toInt)

      //--root
      val root = new javafx.scene.Group()
      root.getChildren.add(canvas)

      //--scene
      val scene = new javafx.scene.Scene(root, _app.size.x.toInt, _app.size.y.toInt)

      // bind the scene size to the canvas
      canvas.widthProperty  bind scene.widthProperty
      canvas.heightProperty bind scene.heightProperty

      //--make a drawcontext and apply a sketch redraw context and initiate a sketch with listeners for redraw events
      //
      // val ctx = new DrawContextFX(c.getGraphicsContext2D)
      val ctx = new DrawContextFX(canvas.getGraphicsContext2D, Some(scene))
      ctx.sketchWith(DrawContextFXApp.draw, _app.maxFPS)

      //--setup app shutdown events
      //https://stackoverflow.com/a/20374691/622016
      stage setOnCloseRequest Java.EventHandler{e:javafx.stage.WindowEvent =>
        ctx.close()
        javafx.application.Platform.exit()
        System.exit(0)
      }

      //--TODO let the scene be dragged/moved via the ctx execution function???

      //--stage settings
      _app applySettings stage

      //--scene settings
      _app applySettings scene

      //--add the scene and launch the stage with the show command
      stage.setScene(scene)
      stage.show()

    }
  }
}