/*
   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.p5

import cc.drx._

import processing.core._
import processing.core.PConstants._
// import processing.core.PApplet._
// import P5._

//TODO implement the core interface to draw the included draw implicits that have been used for a long time
class DrawContextP5(val g:PGraphics) extends DrawContext{
  import Style._

  override def !(p:Default.type):Unit      = {
      //g.background(White)
      super.!(p)
      //--canvas/context
      g.rectMode(CORNERS)    //draw rect from NW to SE
      g.ellipseMode(CENTER)   //specify an ellipse by x, y radius ??
      g.noStroke
      g.noFill
  }

  //--query
  def size:Vec = Vec(g.width, g.height)
  def stroke:Option[Color] = ???
  def fill:Option[Color] = ???
  def weight:Double = ???

  //--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      = g.background(p.c)
  def !(p:Fill):Unit            = g.fill(p.c)
  def !(p:Stroke):Unit          = g.stroke(p.c)
  def !(p:FillNone.type):Unit   = g.noFill
  def !(p:StrokeNone.type):Unit = g.noStroke
  def !(p:Weight):Unit          = g.strokeWeight(p.value)
  def !(p:Font):Unit            = g.textFont(pFont(p), p.size)

  //--cache to auto load and keep resources, this removes the initialization requirement and implementation details to the surface drawn context
  def textSize(text:Text,font:Font):Vec = Vec(textWidth(font,text.value), font.size) //FIXME properly set the y size
  /**compute the width of represented string*/
  def textWidth(f:Font,str:String):Double = {
    val p = pFont(f) //store to prevent re-calling the def
    (0d /: str){case (a,c) => a + p.width(c)}*f.size
  }
  def pFont(font:Font) = fontCache(font)
  override def save(file:File):Try[File] = Try{g save file.path; file} //TODO thow an error if the file could not be saved...
  private val fontCache:Cache[Style.Font,PFont] = Cache{font:Style.Font =>
      import java.awt.{Font => JFont}
      def mkFont(in: => Input) = Try{JFont.createFont(JFont.TRUETYPE_FONT, in.is).deriveFont(JFont.PLAIN,font.size)}
      val jFont = mkFont(File(font.name).in) orElse
                  mkFont(Input.resource(File(font.name)).get) getOrElse  //get is wrapped in the try for the failure to get
                  new JFont(font.name, JFont.PLAIN, font.size.round.toInt)
      new PFont(jFont, true) //use smoothing by default
  }
  private val svgCache = Cache{xml:String =>
    new PShapeSVG(processing.data.XML.parse(xml))
  }
  private val imgCache = Cache{f:File =>
    val bytes =
      if(f.isFile) f.in.toByteArray
      else Input.resource(f).map{_.toByteArray}.getOrElse(Array.empty[Byte])
    val pImg = new PImage(new javax.swing.ImageIcon(bytes).getImage)
    pImg.format = ARGB
    pImg
  }

  def emptyCache() = {  //TODO or use the word clear
     fontCache.empty
     svgCache.empty
     imgCache.empty
  }

  //--Alignment
  def !(p:Align):Unit = g.textAlign(
    p.horz match { case Left => LEFT case Center  =>  CENTER  case Right  => RIGHT  },
    p.vert match { case Top  => TOP  case Midline =>  CENTER  case Bottom => 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.rad)

  //-- shapes `s`
  def !(p:Path):Unit = {
    val head:Vec = p.vertices.head.last
    val rest:Iterable[Vertex] = p.vertices.tail
    g.beginShape()
    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.vertex(x,y)
      case BezierVertex(ca,cb,b) => g.bezierVertex(ca.x,ca.y,   cb.x,cb.y,   b.x,b.y)
    }
    if(p.isClosed) g.endShape(CLOSE) else g.endShape
  }

  def !(s:Circ):Unit    = g.ellipse(s.c.x , s.c.y , s.r*2   , s.r*2)
  def !(s:Line):Unit    = g.line(s.a.x,s.a.y, s.b.x,s.b.y)
  def !(s:Rect):Unit    = g.rect(s.a.x    , s.a.y , s.b.x , s.b.y) //assuming rectMode(CORNERS)
  def !(s:Text):Unit    = g.text(s.value , s.pos.x , s.pos.y)
  private def shape(f: => Unit):Unit = {g.beginShape(); f; g.endShape(CLOSE)}
  def !(s:Poly):Unit    = shape{s.vertices.foreach{v => g.vertex(v.x, v.y)}}
  def !(tri:Tri):Unit     = shape{
    g.vertex(tri.a.x, tri.a.y)
    g.vertex(tri.b.x, tri.b.y)
    g.vertex(tri.c.x, tri.c.y)
  }
  def style(f: =>Unit):Unit = {g.pushStyle; f; g.popStyle}
  def matrix(f: =>Unit):Unit = {g.pushMatrix; f; g.popMatrix}
  def !(a:Arc):Unit     = style{
    g.noFill
    g.strokeWeight(a.w)
    g.strokeCap(SQUARE)
    val w = a.r*2
    //g.ellipseMode(is set to CENTER as default which means RADIUS ???)
    g.arc(a.c.x,a.c.y,  w,w, a.angle.min.rad, a.angle.max.rad, OPEN)
  }
  def !(e:Ellipse):Unit = {
    if(e.rotation == Angle(0))
      g.ellipse(e.c.x,e.c.y, e.r.x*2,e.r.y*2)
    else matrix{
      g.translate(e.c.x, e.c.y)
      g.rotate(e.rotation.rad)
      g.ellipse(0,0, e.r.x*2,e.r.y*2)
    }
  }
  private def rawImageOf(img:Img):PImage = img match {
    case ImgFile(file, _) => imgCache(file)
    case _ => ???  //TODO Img wrapped Pimage
  }
  def !(img:Img):Unit = {
    val p:PImage = rawImageOf(img)
    val box = Rect(p.width,p.height) fitIn img.box  //fit the picture size including aspect ratio into the drawing box //TODO check if rotations can also work here...
    g.image(p, box.a.x, box.a.y, box.width, box.height)
  }
  def !(svg:Svg):Unit = g.shape(svgCache(svg.xml))
  def !(video:Video):Unit = ???
  def !(html:Html):Unit = ???
  def remote(video:Video):MediaRemote = ???
  // def !(arrow:Arrow):Unit = ???
  def !(star:Star):Unit = shape{for(v <- star.vertices) g.vertex(v.x, v.y)}
  override def !(z:Bezier):Unit = g.bezier(z.a.x, z.a.y,    z.ca.x, z.ca.y,  z.cb.x, z.cb.y,    z.b.x,z.b.y) //general bezier

  def close():Unit = {} //TODO close some elements
}