import java.awt.{Dimension, Container, Graphics, Color, Font}
import java.awt.event._
import javax.swing._
import java.util.Random

/****************************************************************/
/************** Random Gui Plumbing Code ************************/
/****************************************************************/

class MyFrame extends JFrame {
  val canvas : JPanel         = new JPanel () 
  val contentPane : Container = getContentPane ()
  val p  : JPanel             = new JPanel ()
  
  def addButton(c: Container, title: String, a: ActionListener) = {
    val b : JButton = new JButton(title)
    c.add(b)
    b.addActionListener(a)
  }

  setSize(600, 400)
  setTitle("Screen Saver")
  contentPane.add(canvas, "Center")
  contentPane.add(p, "South")
     
  addWindowListener(new WindowAdapter() {
    override def windowClosing(e: WindowEvent) {
      System.exit(0)
    }
  })

  addButton(p, "Close", new ActionListener() {
    def actionPerformed(evt: ActionEvent) {
      canvas.setVisible(false)
      System.exit(0)
    }
  })
}

/****************************************************************/
/************** Random Gui code *********************************/
/****************************************************************/


object ScreenSaver {
  
  val delta = 1 

  def clear(box: JPanel) {
    val g = box.getGraphics()
    val d = box.getSize()
    g.clearRect(0, 0, d.width, d.height)
    g.dispose()
  }

  def run(box: JPanel, points: List[Point]) {
    val g = box.getGraphics()
    g.setXORMode(box.getBackground())
    try {
      while (true) {
        val d = box.getSize()
        for (p <- points) {
          p.draw(g)
          p.move(d, delta)
          p.collide(d)
        }
        Thread.sleep(10)
        clear(box)
      }
    } catch { 
        case e: InterruptedException => 
          println("Goodbye!")
    }
    g.dispose()
  }

  val points = List( new Dot("p1", 50, 50)      // Uses default constructor 
                   , new Dot("p2")              // Uses aux constructor
                                                // with Bouncing

                   , new ColorDotV1("p3", Color.red)
                   , new Box("p4", 20, 40)
                   , new ColorBoxV1("p5", Color.pink, 70, 30) 
                   , new ColorDot("p6", Color.blue)                     // with Bouncing
                   , new ColorBox("p7", Color.green, 40, 40)
                   , new BouncingColorDot("p8", Color.magenta) 
                   , new BouncingColorBox("p9", Color.orange, 60, 100)
                   )

  def main(args: Array[String]) = {
    val frame = new MyFrame 
    frame.setVisible(true)
    run(frame.canvas, points)
  }
}

/***************************************************************************/
/*************** Random Number Generator ***********************************/
/***************************************************************************/

object Rand { 
  val rand = new Random(System.currentTimeMillis())

  def apply(lo: Int, hi: Int): Int = {
    require (hi > lo)
    val rng = hi - lo
    lo + rand.nextInt(rng)
  }

  def apply(hi: Int): Int = {
    apply(0, hi)
  }

  def next() = apply(1000)
  
  def hasNext = true
}

/***********************************************************************/
/****************** Abstract Class for ScreenSaver Widget **************/
/***********************************************************************/

abstract class Point {
  
  // Must be filled in by (concrete) sub-classes
  val name : String
  var x    : Int
  var y    : Int
  
  // May be overridden by sub-classes
  var xVel = Rand(1, 5) 
  var yVel = Rand(1, 5) 

  // Must be filled in by (concrete) sub-classes
  def xDim : Int
  def yDim : Int
  def render(g: Graphics): Unit
  def move(d: Dimension, delta: Int) 
  
  // Can be overridden by sub-classes 
  def collide(d: Dimension) {
    hitWall(x, 0, d.width - xDim) map {(v:Int) => 
      x    = v 
      xVel = -xVel 
    }
    hitWall(y, 0, d.height - yDim) map {(v:Int) => 
      y    = v 
      yVel = -yVel 
    } 
  }

  // Cannot be overridden by sub-classes
  final def draw(g: Graphics) {
    val c = g.getColor()    // save old color
    render(g)               // render object
    g.setColor(c)           // restore old color
  }

  private def hitWall(v: Int, lo: Int, hi: Int) : Option[Int] = {
    if (v <= lo) { Some(lo) } else 
    if (v >= hi) { Some(hi) } else 
    None 
  }
 
  // Crappy variant of hitWall
  //if (x < 0) {
  //  x  = 0
  //  xVel = -xVel
  //}
  //if (x + xDim >= d.width) {
  //  x = d.width - xDim 
  //  xVel = -xVel
  //}
  //if (y < 0) {
  //  y = 0
  //  yVel = -yVel
  //}
  //if (y + yDim >= d.height) {
  //  y = d.height - yDim 
  //  yVel = -yVel
  //}

}


/***************************************************************************/
/******************* 1st concrete widget: Black Dot ************************/
/***************************************************************************/

class Dot(val name: String, x0: Int, y0: Int) extends Point {
  
  // Filled in by "constructor"
  var x     = x0 
  var y     = y0 
  def xDim  = 20 // radius 
  def yDim  = 20 // radius 
  
  def render(g: Graphics) {
    g.fillOval(x, y, xDim, yDim)
  }
 
  override def move(d: Dimension, delta: Int) {
    x += (delta * xVel)
    y += (delta * yVel)
  }

  // Auxiliary constructor, invokes "real" constructor
  def this(n: String) = 
    this(n, Rand(0, 100), Rand(0, 100))
}

/***************************************************************************/
/******************* 2nd concrete widget: ColoredDot ***********************/
/***************************************************************************/

class ColorDotV1(name: String, color: Color) extends Dot(name) {
  override def render(g: Graphics) { 
    g.setColor(color) //"color" in scope from ctor params
    super.render(g)
  }
}

/***************************************************************************/
/******************* 3nd concreted widget: Box *****************************/
/***************************************************************************/

class Box(name: String, h: Int, w: Int) extends Dot(name) {
  
  override def yDim = w 
  override def xDim = h

  override def render(g: Graphics) {
    g.fillRect(x, y, h, w)
  }
}

/***************************************************************************/
/******************* 4nd concreted widget: ColoredBox **********************/
/***************************************************************************/

class ColorBoxV1(name: String, color: Color, h: Int, w: Int) 
extends Box(name, h: Int, w: Int) {
  
  override def render(g: Graphics) {
    g.setColor(color) //"color" in scope from constructor params
    super.render(g)
  }
  // Yikes. DRY! How to "reuse" color-ish-ness?
  // Try: Multi-Inherit from Box & ColorDot ? 
  // Fail: Need to BOTH ColorDot.render + Box.render
  //       Multi-inherit gives you one or other (with problems) not both 
}

/***************************************************************************/
/******************* A Trait: Color ****************************************/
/***************************************************************************/

trait Colored /* HIDE */ extends Point /* HIDE */ {
  
  // Will be defined by mixin-target class
  val color: Color
  
  abstract override def render(g: Graphics) {
    g.setColor(color)     
    super.render(g) // Will invoke render of mixin-target
  }

}

/***************************************************************************/
/******************* ColoredDot and ColoredBox Done Right! *****************/
/***************************************************************************/

class ColorDot(name: String, val color: Color) 
  extends Dot(name) 
  with Colored

class ColorBox(name: String, val color: Color, h: Int, w: Int) 
  extends Box(name, h, w) 
  with Colored

/***************************************************************************/
/******************* Another Trait: Bouncing *******************************/
/***************************************************************************/

trait Bouncing extends Point {
  
  private var spring = 0

  private def shootUp() {
    spring = ((spring + 1) % 500)
    if (spring == 0 && yVel <= 0) { 
      yVel = - Rand(15, 25) 
    }
  }

  abstract override def move(d: Dimension, delta: Int) {
    shootUp() 
    yVel += delta
    super.move(d, delta)
  }
}

/***************************************************************************/
/******************* Bouncing Objects! *************************************/
/***************************************************************************/

class BouncingColorDot(name: String, val color: Color) 
  extends Dot(name) with Colored with Bouncing

class BouncingColorBox(name: String, val color: Color, h: Int, w: Int) 
  extends Box(name, h, w) with Colored with Bouncing
