package semaine07;
/** Space on which we can draw lines.
 */
abstract class Canvas {

  /** Frame that represents the drawable area of the output device */
  val frame: Frame;

  /** Draw a line in device coordinates*/
  def plotLine(x1: Double, y1: Double, x2: Double, y2: Double): Unit;

  /** Draw a line in logical coordinates*/
  def drawLine(v1: Vector, v2: Vector): Unit = {
    val _v1 = frame.relPoint(v1);
    val _v2 = frame.relPoint(v2);
    plotLine(_v1.x, _v1.y, _v2.x, _v2.y);
  }

  /** Draw a segment of the picture.*/
  def drawSegment(frm: Frame)(v1: Vector, v2: Vector): Unit = {
    val _v1 = frm.relPoint(v1);
    val _v2 = frm.relPoint(v2);
    drawLine(_v1, _v2);
  }

  /** Draw a list of segments on the picture.*/
  def drawSegments(frm: Frame)(segments: List[Pair[Vector, Vector]]): Unit =
    segments match {
      case Nil => ()
      case Pair(p1, p2) :: tail =>
        drawSegment(frm)(p1, p2);
        drawSegments(frm)(tail)
    }

  /** Draw a list of continuous segments on the picture.*/
  def drawPolySegment(frm: Frame)(points: List[Vector]) : Unit =
    points match {
      case p1 :: p2 :: ps =>
        drawSegment(frm)(p1, p2);
        drawPolySegment(frm)(p2 :: ps)
      case _ => ()
    }

  /** updates the contents of the output device*/
  def repaint = ();

  /** Add the last touch to the picture.*/
  def close : Unit;
}

////////////////////////////////////////////////////////////////////////////////

/** Provides display drawing capabilities. The width and height determine
 *  the real size of the window.
 */
class Screen(initWidth: Int, initHeight: Int) extends Canvas {
  val frame = new Frame(new Vector(0, 0),
                        new Vector(1.0, 0),
                        new Vector(0, 1.0));

  // a placeholder for the coordinates of the two pints of a line
  case class Line(x1: Double, y1: Double, x2: Double, y2: Double)

  val lines = new collection.mutable.ArrayBuffer[Line];

  private var shouldExit = false
  private var waitingForClose = false

  def close  = {
    waitingForClose = true;
    while (!shouldExit) {
      try { Thread.sleep(300) }
      catch { case _: InterruptedException => () }
    }
    screen.dispose()
  }

  override def repaint = screen.repaint();

  val screen = new java.awt.Frame("Painter") {
    import java.awt.{Dimension, Graphics}
    import java.awt.event.{ComponentAdapter, ComponentEvent,WindowAdapter, WindowEvent}
    addWindowListener(new WindowAdapter() {
      override def windowClosing(e: WindowEvent): Unit = {
        shouldExit = true;
        if (!waitingForClose)
	  System.out.println("Terminate the application with Ctrl-C");
      }
    });
    addComponentListener(new ComponentAdapter() {
      override def componentResized(e: ComponentEvent): Unit = repaint()
    })
    setSize(new Dimension(initWidth, initHeight))
    setVisible(true)

    private def round(x: Double): Int = Math.floor(x + 0.5).toInt

    override def paint(g: Graphics): Unit = {
      val height = getSize().height - insets.top - insets.bottom - 2
      val width = getSize().width - insets.left - insets.right - 2
      for (val line <- lines)
        g.drawLine(round(line.x1 * width) + insets.left + 1,
                   round(line.y1 * height) + insets.top + 1,
                   round(line.x2 * width) + insets.left + 1,
                   round(line.y2 * height) + insets.top + 1)
    }
  }

  val insets = screen.getInsets();

  def plotLine(x1: Double, y1: Double, x2: Double, y2: Double): Unit =
    lines += Line(x1, 1.0 - y1, x2, 1.0 - y2);
}

//############################################################################

/** Two-dimensional vector.
 */
class Vector (val x: Double, val y: Double) {
  def +(that: Vector): Vector = new Vector(x + that.x, y + that.y);
  def *(scalar: Double): Vector = new Vector(x * scalar, y * scalar);
  def -(that: Vector): Vector = new Vector(x - that.x, y - that.y);
  def /(scalar: Double): Vector = new Vector(x / scalar, y / scalar);
  def norm: Double = Math.sqrt(x * x + y * y);
  override def toString() = "(" + x + "," + y + ")";
}

/** Frame.
 */
class Frame (val origin: Vector, val edgeX: Vector, val edgeY: Vector) {

  /** The vector v in the absolute reference frame. */
  def relVector(v: Vector): Vector = (edgeX * v.x) + (edgeY * v.y);

  /** The point p in the absolute reference frame. */
  def relPoint(p: Vector): Vector = origin + relVector(p);

  /** The frame f in the absolute reference frame. */
  def relFrame(f: Frame): Frame =
    new Frame(relPoint(f.origin), relVector(f.edgeX), relVector(f.edgeY));

  def / (f: Frame): Frame = relFrame(f);
}

object Painter {

  /** Define the type of a painter as a function that takes a frame
   *  draws itself onto it and returns nothing
   */
  type Painter = Frame => Unit;

  val canvas: Canvas = new Screen(800,800);

  /** A painter that paints nothing */
  def blank: Painter = frame => {}

  /** A painter that draws a unitary horizontal line */
  def line: Painter = frame => canvas.drawSegment(frame)(bottomLeft, bottomRight);

  /** A painter that draws a triangle */
  def triangle: Painter = frame =>
    canvas.drawPolySegment(frame)(List(bottomLeft, bottomRight,
                                       topLeft, bottomLeft));

  // the four corners of the unit frame
  val bottomLeft = new Vector(0.0, 0.0);
  val bottomRight = new Vector(1.0, 0.0);
  val topLeft = new Vector(0.0, 1.0);
  val topRight = new Vector(1.0, 1.0);

  // the basic vectors
  val absVector = new Vector(1.0, 0.0);
  val ordVector = new Vector(0.0, 1.0);

  /** Compose a painter that draws p1 on the left of p2
  */
  def beside(p1: Painter, p2: Painter) : Painter = {
    // ... ŕ compléter ...
  }


  /** Compose a painter that draws p1 below p2
  */
  // ... ŕ compléter ...

  /** Return a painter which paints the Sierpinski triangle of depth n */
  // ... ŕ compléter ...

  /** Return a painter which paints the von Koch flake of depth n */
  // ... ŕ compléter ...

  def main(args: Array[String]): Unit = {
    beside(sierpinski(7), koch (5))(new Frame(bottomLeft, absVector, ordVector))
    canvas.repaint

    // Don't forget to close the canvas!
    canvas.close
  }
}
