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

trait Format[-A]{ //contra-variant means
   def apply(x:A):String = x.toString
   def %(x:A) = apply(x)
   def gain:Double = 1.0 //TODO need to try to remove this so things are not so Formats are not so coupled with Ticks gains
}
trait FormatGeneric{
   /**This Supper trait is used as a lower order precedence for the less specific implicit matches
     * It is also used for cases where type information has been lost and can be found at runtime try to use a more specific formatter
     */
   implicit object FormatAny extends Format[Any]   {
     override def apply(x:Any):String = x match {
        case v:String            => Format.FormatString(v)
        case v:Double            => Format.FormatDouble(v)
        case v:BaseValue[_]      => Format.FormatBaseValue(v)
        case v:Symbol            => Format.FormatSymbol(v)
        case v:Vec               => Format.FormatVec(v)
        case v:Tuple2[Any,Any]   => apply(v._1) + ":" + apply(v._2)
        case v:Iterable[Any]  => v map apply mkString " "
        case v:Array[Any]        => v map apply mkString " "
        case _                   => x.toString
     }
   }
}
object Format extends FormatGeneric{
   /** easily construct a format object*/
   def apply[A](f:A => String):Format[A] = new Format[A]{
     override def apply(a:A):String = f(a)
   }
   def apply[A](x:A)(implicit f:Format[A]):String = f(x)
   def apply[A,B](x:(A,B))(implicit fA:Format[A], fB:Format[B]):String = fA(x._1) +":"+fB(x._2)


   def kv[A,B](x:(A,B))(implicit fA:Format[A], fB:Format[B]):String = {
      val k = fA(x._1)
      val v = fB(x._2)
      if(k.unQuote == v) v else k+":"+v
   }
   def kvColor[A,B](x:(A,B))(implicit fA:Format[A], fB:Format[B]):String = {
     val k = fA(x._1)
     val v = fB(x._2)
     //TODO make the colors configurable
     if(k.unQuote == v) Yellow.ansi(v)
     else               Blue.ansi(k) + Red.ansi(":") + v
   }

   def print[A](x:A)(implicit f:Format[A]):Unit = Console.print(f(x))
   def println[A](x:A)(implicit f:Format[A]):Unit = Console.println(f(x))
   def timetag(zone:String)    = new Numeric((x:Double) => Date(x).format("yyyy-MM-dd_HHmmss-z",zone), 1)
   def utc = new Numeric((x:Double) => Date(x).format("yyyy-MM-dd HH:mm:ss z","UTC"), 1)
   //def timetag = new ScaleFormat((x:Double) => new Date(x.toLong).format("yyyy-MM-dd_HHmmss-z","PST"), 1)
   def degree   = new Numeric(formatter(3,"°", 1),   1)
   def rad2deg  = new Numeric(formatter(3,"°", 1),   180.0/math.Pi)
   def ft2in    = new Numeric(formatter(3,"in",1),  12)
   def number   = new Numeric(formatter(3,"",  1),    1)
   def negative = new Numeric(formatter(3,"", -1), -1)

   private def formatter(precision:Int=3,unit:String="", gain:Double=1.0) = (d:Double) => s"%.${precision}g${unit}" format (d*gain)

   class Numeric(val formatter:Double => String, override val gain:Double=1.0) extends Format[Double]{
      override def apply(x:Double):String = formatter(x*gain)
   }

   //--^^^ above here is the older function based formatters
   //--new type class like form
   implicit object FormatU4 extends Format[U4]{ override def apply(x:U4) = "0x%01x" format x.value } //4bits
   implicit object FormatByte extends Format[Byte]{ override def apply(x:Byte) = "0x%02x" format x } //8bits
   implicit object FormatShort extends Format[Short]{ override def apply(x:Short) = "0x%04x" format x } //16bits
   //TODO add binary number formatting and the nibble type

   implicit object FormatDouble extends Format[Double]{ override def apply(x:Double) = "%.3g" format x } //TODO make this smarter for different scale numbers and pretty printing of integers
   implicit object FormatBaseValue extends Format[BaseValue[_]]{ override def apply(x:BaseValue[_]) = x.nice } //TODO make this smarter for different scale numbers and pretty printing of integers
   implicit object FormatSymbol extends Format[Symbol]{ override def apply(x:Symbol) = x.name }
   implicit object FormatVec extends Format[Vec]{ override def apply(v:Vec) = {
      val two = Format(v.x)+" "+Format(v.y)
      if(v.is2d) two else two+" "+Format(v.z)
   }}
   implicit object FormatUtm extends Format[Utm]{ override def apply(utm:Utm) = utm.format } //TODO this should live in the Utm object, but it is nesseary here to prevent the FormatAny in the Generic from cloberring it

   implicit object FormatString extends Format[String]{
     private lazy val visible = Map('\t' -> "\\t", '\n' -> "\\n", ' '-> "<sp>").withDefault(_.toString)
     override def apply(x:String) = {
       if(x == "") "<empty>"
       else if (x.forall(_.isWhitespace) ) x map visible mkString ""
       else x
     }
   }

   // private val KeyPat = """([^:\s]+)(\s+:)""".r
   private val KeyPat = """(\w+)(\s*)(:)""".r //TODO make this a more complex key match
   def pp[A](x:A)(implicit f:Format[A]):Unit = Console println KeyPat.replaceAllIn(f(x), m =>
       Color.Blue.ansi(m group 1) + (m group 2) + Color.Red.ansi(m group 3)
   )

   final class FormatStringContext(val sc:StringContext) extends AnyVal{
      private def p(i:Int) = sc.parts(i)  //interestingly this method must be private simply because the ParseStringContext p will otherwise conflict
      type F[A] = Format[A]
      def format[A]        ():String                                                                      = p(0)
      def format[A]        (a:A)                (implicit fA:F[A]):String                                 = p(0) + fA(a) + p(1)
      def format[A,B]      (a:A,b:B)            (implicit fA:F[A],fB:F[B]):String                         = p(0) + fA(a) + p(1) + fB(b) + p(2)
      def format[A,B,C]    (a:A,b:B,c:C)        (implicit fA:F[A],fB:F[B],fC:F[C]):String                 = p(0) + fA(a) + p(1) + fB(b) + p(2) + fC(c) + p(3)
      def format[A,B,C,D]  (a:A,b:B,c:C,d:D)    (implicit fA:F[A],fB:F[B],fC:F[C],fD:F[D]):String         = p(0) + fA(a) + p(1) + fB(b) + p(2) + fC(c) + p(3) + fD(d) + p(4)
      def format[A,B,C,D,E](a:A,b:B,c:C,d:D,e:E)(implicit fA:F[A],fB:F[B],fC:F[C],fD:F[D],fE:F[E]):String = p(0) + fA(a) + p(1) + fB(b) + p(2) + fC(c) + p(3) + fD(d) + p(4) + fE(e) + p(5)
   }
}