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

object Draw{
   import Color._

    /**this can be used to reset the draw states to some known state*/
    def defaults(g:PGraphics):PGraphics = {
      //g.background(White)
      g.rectMode(CORNERS)    //draw rect from NW to SE
      g.ellipseMode(CENTER)   //specify an ellipse by x, y radius ??
      g.noStroke
      g.noFill
      g
    }

    private def rawImageOf(img:Img):PImage = img match {
      case ImgFile(file, _) => imgCache(file)
      case _ => ???  //TODO Img wrapped Pimage
    }

    //--utility syntax on processing methods
    implicit class RichPSurface(val surface:PSurface) extends AnyVal{
      def setIcon(img:Img) = surface.setIcon(rawImageOf(img))
    }
    implicit class RichPImage(val img:PImage) extends AnyVal{
      def scale(scale:Double):PImage = {
         img.resize((img.width*scale).round.toInt, (img.height*scale).round.toInt)
         img
      }
    }

    implicit class RichPGraphics(val g:PGraphics) extends AnyVal{
      def !(property:Style.Property):Unit = this ! Style(Set(property))
      def !(shape:Shape):Unit = shape draw g
      def !(shapes:Traversable[Shape]):Unit = shapes foreach {_ draw g}
      def draw(drawFunc: => Unit):PGraphics = {
         g.beginDraw
            g.pushMatrix
            g.pushStyle
               drawFunc
            g.popStyle
            g.popMatrix
         g.endDraw
         g
      }
    }
    /**style here has grown to be a compound of graphics drawing state*/
    implicit class RichStyle(val style:Style) extends AnyVal with Shape{
      import Style._
      def draw(implicit g:PGraphics):Unit = {
        //accumulator variables to collect settings
        var vertAlign:Option[Int] = None  //processing int
        var horzAlign:Option[Int] = None
        var strokeWasSet = false
        var fillWasSet = false
        style foreach {
           case Default => defaults(g)
           case Background(c) => g.background(c)
           case Fill(c) => g.fill(c); fillWasSet = true
           case FillNone => g.noFill; fillWasSet = false
           case Stroke(c) => g.stroke(c); strokeWasSet = true
           case StrokeNone  => g.noStroke; strokeWasSet = false
           case Weight(w) => g.strokeWeight(w)
           case f:Font => f.draw(g) //g.textFont(f.pFont, f.size)
           case Align(horz,vert) => ??? //FIXME need to add the features here
           case x:AlignHorizontal => horzAlign = Some(x match {
              case Left => LEFT
              case Right => RIGHT
              case Center => CENTER
           })
           case x:AlignVertical => vertAlign = Some(x match{
              case Top => TOP
              case Midline => CENTER
              case Bottom => BOTTOM
           })
           case ScaleProperty(t:Vec) =>
             g.scale(t.x,t.y)
           case Translate(t:Vec) =>
             g.translate(t.x,t.y)
           case Rotate(r:Angle) =>
             g.rotate(r.rad)
           case Transform(t,r,s) =>
             g.translate(t.x, t.y)
             g.rotate(r.rad)
             g.scale(s.x,s.y)
        }
        //if neither stroke or fill is set then let the fill or stroke pass through
        if(strokeWasSet && !fillWasSet)   g.noFill
        if(  fillWasSet && !strokeWasSet) g.noStroke

        if(vertAlign.isDefined || horzAlign.isDefined)
           g.textAlign(horzAlign getOrElse CENTER, vertAlign getOrElse CENTER)
      }
      def draw(drawFunc: => Unit)(implicit g:PGraphics):Unit = {
        g.pushMatrix
        g.pushStyle
           draw //draw the style
           drawFunc
        g.popStyle
        g.popMatrix
      }
    }
    /**Generic shape class*/ //TODO can this be brought up to drx instead of drx.p5??
    trait Shape extends Any{
       //---required methods
       @deprecated("use (g ! shape ~ style) or (shape ~ style draw g) instead","v0.1.18")
       def draw(style:Style)(implicit g:PGraphics):Unit = style.draw{draw}
       def draw(implicit g:PGraphics):Unit

       //---derived methods
       //**Attach a style to a shape*/
       def ~(style:Style):StyledShape = StyledShape(this,style)
       //**initialize shape with a style */
       def ~(property:Style.Property):StyledShape = StyledShape(this,Style()~property)

       /** provide a quick automatic way to set a fill or stroke
        * all shapes have a stroke even if they can be filled (but the ClosedShape overrides this to provide a fill color)
        */
       def ~(color:Color):StyledShape =  this ~ Style.Stroke(color)
    }
    /**these are shapes that have an area and default to coloring by area rather than stroke*/
    trait ClosedShape extends Any with Shape {
       override def ~(color:Color):StyledShape =  this ~ Style.Fill(color)
    }

    /*turn a generic shape into a styled *///TODO can this be brought up to drx so StypedShapes are first class and part of containers for other draw contexts
    case class StyledShape(shape:Shape,style:Style) extends Shape{
      def draw(implicit g:PGraphics):Unit = style.draw{shape.draw(g)}(g)

      override def draw(overrideStyle:Style)(implicit g:PGraphics):Unit = overrideStyle.draw{shape.draw(g)}(g)
      override def ~(mergeStyle:Style):StyledShape = StyledShape(shape, style ~ mergeStyle)
      override def ~(addProperty:Style.Property):StyledShape = StyledShape(shape,style~addProperty)
    }
    case class GroupedShape(shapes:Traversable[Shape]) extends Shape{
      def draw(implicit g:PGraphics):Unit = shapes.foreach{_.draw(g)}
    }
    //FIXME TODO this is a temporary class to support the new generic core Shape.Styled class instead of the implicit p5.Draw.StyledShape
    implicit class DrxShapeStyled(val styledShape:cc.drx.Shape.Styled) extends AnyVal with Shape{
      def draw(implicit g:PGraphics):Unit = new DrawContextP5(g) ! styledShape
    }

    @deprecated("This is techincally not a Shape","v0.1.18")
    implicit class RichVec(val vec:Vec) extends AnyVal with Shape{
        @deprecated("use (g ! vec ~ 1) instead","v0.1.18")
        def draw(implicit g:PGraphics):Unit = g.ellipse(vec.x,vec.y,1,1)
        @deprecated("use (g ! vec ~ d) instead","v0.1.18")
        def draw(d:Double=1.0)(implicit g:PGraphics):Unit = g.ellipse(vec.x,vec.y,d,d)
        @deprecated("use (g ! vec ~ string) instead","v0.1.18")
        def draw(text:String)(implicit g:PGraphics):Unit = g.text(text,vec.x,vec.y)
    }

    implicit class RichBezier(val z:Bezier) extends AnyVal with Shape{
        def draw(implicit g:PGraphics):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
    }
    implicit class RichStar(val star:Star) extends AnyVal with ClosedShape{
       def draw(implicit g:PGraphics):Unit = {
          g.beginShape
             for(v <- star.vertices) g.vertex(v.x, v.y)
          g.endShape(CLOSE)
       }
    }
    implicit class RichArrow(val arrow:Arrow) extends AnyVal with Shape{ //not a closed shaped but the head gets the fill color by the stroke
       def draw(implicit g:PGraphics):Unit = {
         val color:Color = Color(g.strokeColor)
         val lineWeight:Double=g.strokeWeight //grab the currently set strokeWeight
         val headSize:Double=lineWeight*8
         val a = arrow.line.a
         val b = arrow.line.b
         val vel = b - a
         if(vel.range != 0){
            //g.strokeWeight(lineWeight)
            g.stroke(color)
            //--triangle vec boid
            val head = b - vel.mag(headSize)
            g.line(a.x,a.y, head.x,head.y)
            val ortho = (vel x Vec.z) mag (headSize/2)
            val right = head + ortho
            val left = head - ortho
            g.noStroke
            g.fill(color)
            g.beginShape
               g.vertex(b.x, b.y)
               g.vertex(right.x, right.y)
               g.vertex(left.x, left.y)
            g.endShape
         }
       }
    }
    implicit class RichLine(val line:Line) extends AnyVal with Shape{
        def a = line.a
        def b = line.b

        def draw(implicit g:PGraphics):Unit = g.line(a.x,a.y, b.x,b.y)
        @deprecated("use (g ! line ~ Style.Weight(weight)) instead","v0.1.18")
        def draw(weight:Double)(implicit g:PGraphics):Unit = {
          g.pushStyle
          g.strokeWeight(weight)
          g.line(a.x,a.y, b.x,b.y)
          g.popStyle
        }

        @deprecated("use g ! Arrow(line) instead","0.2.0-angle")
        def drawArrow(color:Color,lineWeight:Double=1, headSize:Double=8)(implicit g:PGraphics):Unit = {
         val vel = b - a
         if(vel.range != 0)
         {g.pushStyle
            g.strokeWeight(lineWeight)
            g.stroke(color)
            //--triangle vec boid
            val head = b - vel.mag(headSize)
            g.line(a.x,a.y, head.x,head.y)
            val ortho = (vel x Vec.z) mag (headSize/2)
            val right = head + ortho
            val left = head - ortho
            g.noStroke
            g.fill(color)
            g.beginShape
               g.vertex(b.x, b.y)
               g.vertex(right.x, right.y)
               g.vertex(left.x, left.y)
            g.endShape
         g.popStyle
         }
        }
    }

    //TODO use a open and closed path couter-part Path and PathClosed  ShapeClosed
    implicit class RichPath(val path:Path) extends AnyVal with Shape{
        def draw(implicit g:PGraphics):Unit = {
          //TODO we shouldn't require a push and pop style inside this shape
           g.pushStyle
              g.noFill
              g.beginShape
              path.vertices foreach {
                  case v:Vec => g.vertex(v.x, v.y)
                  case v:BezierVertex => g.bezierVertex(v.ca.x,v.ca.y,  v.cb.x,v.cb.y,  v.b.x, v.b.y)
              }
              g.endShape
           g.popStyle
        }
    }

    //TODO deprecate the overly specific shape a Boid like shape
    implicit class RichState(val state:State) extends AnyVal with Shape{
        def pos = state.pos
        def vel = state.vel
        //def acc = state.acc

        def draw(implicit g:PGraphics):Unit = draw(1)
        def draw(size:Double=1)(implicit g:PGraphics):Unit =
          if(vel.range != 0){
            //--triangle vec boid
            val head = pos + (vel mag (size*4))
            val ortho = vel x Vec.z mag size
            val right = pos + ortho
            val left = pos - ortho
            g.beginShape
             g.vertex(head.x, head.y)
             g.vertex(right.x, right.y)
             g.vertex(left.x, left.y)
            g.endShape
          }
    }

    implicit class RichText(val text:Text) extends AnyVal with ClosedShape{
      def draw(implicit g:PGraphics):Unit = g.text(text.value, text.pos.x, text.pos.y)
    }

    //--cache to auto load and keep resources, this removes the initialization requirement and implementation details to the surface drawn context
    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 //the .get is wrapped in the mkFont Try to catch errors
                    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:Array[Byte] =
        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() = {  //or use the word clear
       fontCache.empty
       svgCache.empty
       imgCache.empty
    }
    implicit class RichSvg(val svg:Svg) extends AnyVal with Shape{
      // @deprecated("use svg.draw(g) or g ! svg instead","0.2.13")
      def pShape = svgCache(svg.xml)

      def draw(implicit g:PGraphics):Unit = {
        val p:PShape = svgCache(svg.xml)
        // p.disableStyle
        // val box = Rect(p.width,p.height) fitIn svg.box  //fit the picture size including aspect ratio into the drawing box //TODO check if rotations can also work here...
        //g.shape(p, svg.box.a.x, svg.box.a.y, svg.box.width, svg.box.height)
        // g.fill(Red)
        g.shape(p)
      }
    }
    implicit class RichImg(val img:Img) extends AnyVal with Shape{
        @deprecated("use img.draw(g) or g ! img instead","0.2.13")
        def pImg:PImage = rawImageOf(img)
        def draw(implicit g:PGraphics):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)
        }
    }
    implicit class RichFont(val font:Style.Font) extends AnyVal{
        // @deprecated("use font.draw(g) or g ! font instead","0.2.13")
        def pFont:PFont = fontCache(font)

        /**compute the width of represented character*/
        @deprecated("use font.width(String) instead","0.2.13")
        def width(c:Char):Double = pFont width c
        /**compute the width of represented string*/
        def width(str:String):Double = {
          val p = pFont //store to prevent re-calling the def
          (0d /: str){case (a,c) => a + p.width(c)}*font.size
        }
        def draw(g:PGraphics):Unit = g.textFont(pFont, font.size)
    }

    implicit class RichCirc(val c:Circ) extends AnyVal with ClosedShape{
        def draw(implicit g:PGraphics):Unit = g.ellipse(c.c.x,c.c.y, c.r*2,c.r*2)
    }
    implicit class RichEllipse(val e:Ellipse) extends AnyVal with ClosedShape{
        def draw(implicit g:PGraphics):Unit = {
          if(e.rotation == Angle(0))
             g.ellipse(e.c.x,e.c.y, e.r.x*2,e.r.y*2)
          else{
            g.pushMatrix
              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)
            g.popMatrix
          }
        }
    }
    implicit class RichArc(val a:Arc) extends AnyVal with Shape{
        def draw(implicit g:PGraphics):Unit = {
          g.pushStyle
            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)
          g.popStyle
        }
    }
    implicit class RichRect(val r:Rect) extends AnyVal with ClosedShape{
        def draw(implicit g:PGraphics):Unit = g.rect(r.a.x,r.a.y,  r.b.x,r.b.y) //assuming rectMode(CORNERS)
        @deprecated("dont include styles in the drawing context","v0.2.15")
        def draw(cornerRadius:Double)(implicit g:PGraphics):Unit = g.rect(r.a.x,r.a.y,  r.b.x,r.b.y,  cornerRadius)
    }
    implicit class RichAxes[A,B](val a:Axes[A,B]) extends AnyVal with ClosedShape{
        def draw(implicit g:PGraphics):Unit = {
          //TODO add draw ticks and detect font size and spacings (and get tick stroke color from the set fill color
          a.xTicks foreach {t => g ! (t + 3.x) ~ Style.Top}  //auto center
          a.yTicks foreach {t => g ! (t - 3.x) ~ Style.Right}  //auto midline
        }
    }
    implicit class RichTri(val tri:Tri) extends AnyVal with ClosedShape{
        def draw(implicit g:PGraphics):Unit = {
          g.beginShape()
            g.vertex(tri.a.x, tri.a.y)
            g.vertex(tri.b.x, tri.b.y)
            g.vertex(tri.c.x, tri.c.y)
          g.endShape(CLOSE)
        }
    }
    implicit class RichPoly(val poly:Poly) extends AnyVal with ClosedShape{
        def draw(implicit g:PGraphics):Unit = {
          g.beginShape()
             poly.vertices.foreach{v => g.vertex(v.x, v.y)}
          g.endShape(CLOSE)
        }
    }

}