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

/**static helper methods for Lerp use*/
object Lerp{
  def pulse(period:Time):Double = ((Date.s*tau/period.s).sin + 1)/2
}
/**Lerp generalizes any Segment, Bound, Scale that can be linear interpolated*/
trait Lerp[A]{
   /**required methods*/
   def lerp(ratio:Double):A

   /**alias for `lerp` or apply*/
   // 2017-07-30("use `lerp` instead since `%` may clobber other features","v0.2.15")
   // def %(ratio:Double):A = lerp(ratio)
   // 2017-07-30("use `lerp` instead since `apply` may clobber other mixed in features","v0.2.15")
   // def apply(ratio:Double):A = lerp(ratio)

   //==== Lerp
   /**convenience of the mid value*/
   def mid:A = lerp(0.5)
   def min:A = lerp(0.0)
   def max:A = lerp(1.0)
   /**alias for min*/
   def start:A = min
   /**alias for max*/
   def end:A   = max
   2017-07-30("use `mid` instead since 0.5 may not be the mean","v0.2.15")
   def mean:A = lerp(0.5)

   def uniform(implicit rand:Rand):A = lerp(rand.uniform(0.0, 1.0)) //get a value uniformly distributed in the bound
   def normal(implicit rand:Rand):A = lerp(rand.normal(0.5,0.5)) //get a normally distributed value centered around the mean with a variance the bound width

   def pulse(period:Time):A = lerp(Lerp pulse period)

   def ramp(t:Time)(implicit t0:Date):A = lerp(t0 ramp t)

   // potential method names since take has scala iterator like meaning for first set of N
   //take: conflicts with scala collectdion meaning of first n elements
   //bag: is short and seems more consitent than sample, and seems like it is more unique, but maybe conflicts histogram bins?
   //sample: feels like some kind of stochastic process
   //taste: is more careful than sample :
   //pick: is spedcfiic
   //draw: stochastic
   //draw: stochastic, and overloaded with stohastic
   //pull: is not as descript

   //-- sequence interpolated
   //take //TODO use a better name since take in scale is front based rather than spread based
   //n [0  1/4   1/2   3/4   1]   dt         i t0 t
   //0 [                      ]   nan
   //1 [         1/2          ]   nan
   //2 [0                    1]   size/1
   //3 [0        1/2         1]   size/2
   //4 [0    1/3      2/3    1]   size/2     
   //n (p =  i/(n-1)
   def take(n:Int):Iterable[A] = if(n <= 0) List() else if(n == 1) List(lerp(0.5)) else (0 to (n-1)).map{i => lerp(i.toDouble/(n-1))}

   2017-07-30("use take(N:Int) for a more scala semantic", "v0.2.4")
   def steps(N:Int):Seq[A] = if(N==0) Seq() else if(N==1) Seq(lerp(0.5)) else (0 to N).map{i => lerp(i.toDouble/N)}
   def zip[B](xs:Iterable[B]) = take(xs.size) zip xs

   def toBound(implicit b:Bound.Boundable[A]):Bound[A] = Bound.apply(min, max)(b)

}
/**LerpInv includes the ability to convert/lookup a specific `type A` value for a interpolation ratio with `ratioOf`*/
//TODO think about matching the ScaleOneWay and Scale form like LerpOneWay and Lerp unless Lerp is more common
trait LerpInv[A] extends Lerp[A]{
   //---required methods
   def ratioOf(a:A):Double

   //---supported methods
   def normalize(xs:Iterable[A]):Iterable[Double] = xs map ratioOf
   def contains(a:A):Boolean = {val p = ratioOf(a); p >= 0  && p <= 1}

   //sat in Bound returns the type
   // def sat(a:A):A = apply(ratioOf(a).sat(0,1))

   /** modulus in a wrapping fashion */
   def mobius(x:A):A = {
      val t = ratioOf(x) % 1.0
      lerp(if(t < 0.0) 1.0 + t else t)
   }

   //---Tick functions (note these interestingly have to have min and max values and dont' work on the more general Lerp and LerpInv methods //TODO remove this comment if the implemented a min max works here
   def ticks(n:Int)(implicit t:Tickable[A]):Iterable[A] = t.ticks(n,this)
   def logTicks(n:Int,base:Double=10)(implicit t:Tickable[A]):Iterable[A] = t.logTicks(n,this,base:Double)
   //could there be an argument that ticks only occur with format or at least most of the time then use tickValues to get the non formated versions
   final def ticksWithFormat(n:Int)(implicit t:Tickable[A], f:Format[A]):Iterable[(A,String)] = {
     val format = t.formatter(n,this)
     this.ticks(n).map{t => (t, format(t))}
   }
   def ticksOn(path:Lerp[Vec], n:Int)(implicit t:Tickable[A]):Iterable[Text] = {
     //interpolate this domain to the range of the line.bound
     this.ticksWithFormat(n).map{case (t,f) => Text(f, path lerp ratioOf(t) ) } //tick values, with resolution formatting
   }
   private def estimateTickCountOn(path:Segment):Int = {
     //-- auto select a spacing based on the max,textsize and orientation by width and hight
     val pathBox = Rect(path.lerp(0),path.lerp(1))
     val spacing:Double = if(pathBox.width > pathBox.height) 60 else 20 //TODO auto set rather than hard coded better auto spacing based on font size and data types...
     path.tickCount(spacing)
   }
   /**auto select tick count based on segment length*/
   def ticksOn(path:Segment)(implicit t:Tickable[A]):Iterable[Text] = {
     val n = estimateTickCountOn(path)
     ticksOn(path,n)(t)
   }

   def -->[B](range:LerpInv[B]) = Scale(this, range)
   def -->[B](range:Lerp[B]) = Scale(this, range)
}