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

object Version extends Applicable[String,Version]{
   //puntuation  | space |  [digit look behind] & [letter look ahead] | [letter look behind] & [digit look ahead]
   val sep = """(\s*[\.\_\-\/\,\;]\s*)|\s+|(?<=\d)+(?=\p{L})|(?<=\p{L})+(?=\d)""".r
   implicit object ParsableVersion extends Parsable[Version]{  def apply(v:String):Version = Version(v)  }

   final class VersionStringContext(val sc:StringContext) extends AnyVal{
     def version(args:Any*):Version = Version(sc.s(args:_*))
   }
   def apply(major:Int, minor:Int, patch:Int):Version = Version(s"$major.$minor.$patch")
   def apply(major:Int, minor:Int):Version            = Version(s"$major.$minor")
   def apply(major:Int):Version                       = Version(s"$major")

   //assuming initial release with no previous specified date
   def kryptonInit(init:Date=Date.now):Version = Version(init.krypton take 1)
   // def krypton(last:Date, next:Date):Version = krypton(last.krypton take 8, next)
   def kryptonNext(lastVer:Version, next:Date=Date.now):Version = {
     val nextVer = next.krypton take 8
     val same = lastVer.name lcp nextVer
     Version(same + nextVer.drop(same.size).take(1))
   }
   def kryptonMap(xs:Iterable[Date]):Iterable[Version] = {
     xs.scanLeft(Version("0")){kryptonNext _ }.tail  //pull in the tupled version of krypton
   }
}
/**Version represents a string name that may look like a version, its main feature provides a nice human sorting rather than full string bases sorting*/
case class Version(val name: String) extends AnyVal with Ordered[Version]{
   // def value:String = name //TODO change name to value
   // import scala.math.Ordered.orderingToOrdered  //FIXME is this import really not needed
   override def toString = name

   def /(minor:Int):Version = Version(name + "." + minor)
   def /(minor:String):Version = Version(name + "." + minor)
   def *(alt:String):Version = Version(name + "-" + alt)

   //less than 6 is a convenient size that tracks timesstamps and datestamps in iso form
   def isDigit(s:String):Boolean = s.nonEmpty && (s.size < 6) && s.forall{_.isDigit}

   def compare(that:Version):Int = {
      @tailrec def cmp(a:Seq[String], b:Seq[String]):Int = {
         //println(s"$a , $b")
            //--both non empty
         if(a.nonEmpty && b.nonEmpty){
            //if head is the same keep comparing
            if(a.head == b.head) cmp(a.tail, b.tail)
            //compare by numeric if all digits
            else if(isDigit(a.head) && isDigit(b.head)) a.head.toInt compare b.head.toInt
            //if a only a digit then let 'a' be bigger
            else if(isDigit(a.head)) 1
            //if b only is a digit then let 'b' be bigger
            else if(isDigit(b.head)) -1
            //use the normal string comparison function
            else a.head compare b.head
         }
         //-- 'b' is larger if (implicityly no 'a') && ('b' is a number), 
         //-- 'a' is larger if 'b' contains more
         else if(b.nonEmpty) if(isDigit(b.head)) -1 else 1
         //-- 'a' is larger if (implicitly no 'b') && ('a' is a number), 
         //-- 'b' is larger if 'a' contains more
         else if(a.nonEmpty) if(isDigit(a.head)) 1  else -1
         // otherwise assume equality //false case
         else 0 //both empty so equal
      }
      if(this.name == that.name) 0 else cmp(this.terms, that.terms)
   }
   def terms = (Version.sep split name).toIndexedSeq
   def isSnapshot     = name.toLowerCase contains "snapshot"
   def isFinal        = name forall {d => d.isDigit || d == '.'}
   def isIntermediate = name exists {d => d.isLetter }

   def take(depth:Int):Version = Version(terms take depth mkString ".")
   def similar(that:Version,depth:Int) = this.take(depth) == that.take(depth)
}