/*
   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 Parse {
   def apply[A](string:String)(implicit parser:Parsable[A]):A = parser(string)
   def split[A](string:String)(implicit parser:Parsable[A]):Vector[A] = parser.split(string.trim)

   def split[A](string:String,sep:String)(implicit parser:Parsable[A]):Vector[A] = parser.split(string.trim, sep)
   def get[A](string:String)(implicit parser:Parsable[A]):Option[A] = parser.get(string)

   implicit class ParseStringContext(val sc: StringContext) extends AnyVal{
      def p[T](args:Any*)(implicit parser:Parsable[T]):T = parser(sc.s(args:_*))  //first wrap the sc with the normal s interpreter then do the implicit drx string parsing
   }

   /**Split on the highest precedence split character. Parse.split without a split string uses this.
   note: if string contains the follow chars a trailing or leading is required to specify the split character
   */
   def autoSplit(str:String):Array[String] = {
      val sepList = List(";", ",", "&&", "||", "&", "|", " to ", " ")
      val sepPat = sepList.find{str contains _}.getOrElse(sepList.last)  //find the highest precedence split
      val escapeChars = Set('&','|') //custom Regex quote since 2.10 doesn't have this fuction and scalaJS is non standard regex from java
      val sepPatEscaped = sepPat.foldLeft(""){case (s,c) =>
        val e = if(escapeChars contains c) "\\" else ""
        s + e + c.toString
      }
      str.trim.split(sepPatEscaped).map{_.trim}.filter{_ != ""}
   }
}
trait Parsable[+A]{// extends MacroParsable[A]{
   def apply(string:String):A
   def split(string:String):Vector[A] = Parse.autoSplit(string).toVector map apply //toVector since Array from split is invarient but Vector is Covarient like Parsable[+T]
   def split(string:String,sep:String):Vector[A] = (string split sep).toVector map apply //toVector since Array from split is invarient but Vector is Covarient like Parsable[+T]

   def get(string:String):Option[A] = Try{apply(string)}.toOption //TODO return Either or Try with warning info
}
trait ParsableLowPriorityImplicits{
   private val boolKeys:Map[String,Boolean] = Map(
     "true" -> true,   "false" -> false,
     "on"   -> true,   "off"   -> false,
     "yes"  -> true,   "no"   -> false,
     "y"    -> true,   "n"    -> false,
     "1"    -> true,   "0"    -> false,
     "t"    -> true,   "f"    -> false,
     "good" -> true,   "bad"  -> false,
     "+"    -> true,   "-"    -> false
   )
   private val charKeys:Map[String,Char] = Map(
     "max" -> Char.MaxValue,
     "min" -> Char.MinValue
     //Char is unsigned... so +- does not make sense
   )
   private val intKeys:Map[String,Int] = Map(
     "max" -> Int.MaxValue,
     "min" -> Int.MinValue,
     "+"    -> 1,
     "-"    -> -1
   )
   private val longKeys:Map[String,Long] = Map(
     "max" -> Long.MaxValue,
     "+"    -> 1L,
     "min" -> Long.MinValue,
     "-"    -> -1L
   )
   private val floatKeys:Map[String,Float] = Map(
     "nan"  -> Float.NaN,
     "inf"  -> Float.PositiveInfinity,
     "+inf" -> Float.PositiveInfinity,
     "-inf" -> Float.NegativeInfinity,
     "max"  -> Float.MaxValue,
     "min"  -> Float.MinValue,
     "+"    -> 1f,
     "-"    -> -1f
   )
   private val doubleKeys:Map[String,Double] = Map(
     "nan"  -> Double.NaN,
     "inf"  -> Double.PositiveInfinity,
     "+inf" -> Double.PositiveInfinity,
     "-inf" -> Double.NegativeInfinity,
     "max"  -> Double.MaxValue,
     "min"  -> Double.MinValue,
     "e"    -> E,
     "pi"   -> Pi,
     "π"    -> Pi,
     "tau"  -> Tau,
     "τ"    -> Tau,
     "+"    -> 1d,
     "-"    -> -1d
   )

   private val SIPrefix:Map[Char,Double] = Map(
         'E' -> 1E15,
         'T' -> 1E12,
         'G' -> 1E9,
         'M' -> 1E6,  //mega
         'k' -> 1E3,  //kilo
         'm' -> 1E-3, //milli
         'u' -> 1E-6, //micro
         'μ' -> 1E-6, //micro
         'n' -> 1E-9,
         'p' -> 1E-12
   )
   private val intDigits = "-+0123456789".toSet
   private val decDigits = ".-+0123456789Ee".toSet
   private val allDigits = decDigits ++ SIPrefix.keys

   private def getLong(value:String,base:Int, prefixDigits:String,validDigits:String):Option[Long] = {
     def isPrefix = prefixDigits.toSet
     val norm = value.trim.toUpperCase
     val v = if((norm.size > 1) && (norm(0) == '0') && isPrefix(norm(1))) norm drop 1 else norm
     val triggers = prefixDigits.toSet
     val digits = if(triggers(v.head)) Some(v drop 1) else if(triggers(v.last)) Some(v dropRight 1) else None
     val res = digits map {d =>
       val parseString = d filter validDigits.toSet
       java.lang.Long.parseLong(parseString,base)
     }
     // println(s"value:$value base:$base digits:$digits getLong:$res")
     res
   }
   private def getBin(value:String) = getLong(value, 2,   "B", "-+01")
   private def getOct(value:String) = getLong(value, 8,   "O", "-+01234567")
   private def getHex(value:String) = getLong(value,16, "XH#", "-+0123456789ABCDEF")

   private def applySiPrefix(value:String):Option[Double] =
      for(prefix <- value.lastOption;  scale <- SIPrefix.get(prefix) ) yield {
         if(value.size == 1) scale
         else ParsableDouble(value.init)*scale
      }
   /** General number converter with included simple math calculator with simple ^ / * + - precedence */
   private def mkNumber[T](value:String, fromString:String => T, fromDouble:Double => T, fromLong:Long => T):T = {
      lazy val lcValue = value.toLowerCase
      //math ops of - and + require a space so they are not interpreted as prefix
      if(value endsWith "%") fromDouble(ParsableDouble((value dropRight 1).trim)/100.0)
      else if(value endsWith "dB") fromDouble(ParsableDouble((value dropRight 2).trim).dB)
      else if(value contains "- ") fromDouble(value.split("""\- """) map {s => ParsableDouble(s)} reduce (_ - _))
      else if(value contains "+ ") fromDouble(value.split("""\+ """) map {s => ParsableDouble(s)} reduce (_ + _))
      else if(value contains '*') fromDouble(value.split("""\*+""") map {s => ParsableDouble(s)} reduce (_ * _))
      else if(value contains '/') fromDouble(value.split("""\/+""") map {s => ParsableDouble(s)} reduce (_ / _))
      else if(value contains '^') fromDouble(value.split("""\^+""") map {s => ParsableDouble(s)} reduce (_ ** _))
      else if(doubleKeys contains lcValue) fromDouble(doubleKeys(lcValue))
      else{
        getHex(value).map{fromLong} orElse
        getOct(value).map{fromLong} orElse
        getBin(value).map{fromLong} orElse
        applySiPrefix(value).map{fromDouble} getOrElse {
           //this mutable implementation was much cleaner to read/faster but harder to debug than an immutable solution
           var isInt = true
           var foundDecimal = false
           var clean = ""
           for(c <- value){
              if( intDigits doesNotContain c)       isInt = false
              val skip = c == '.' && foundDecimal
              if( decDigits.contains(c) && !skip)   clean += c
              if( c == '.')                         foundDecimal = true
           }
           if(isInt) fromString(clean) //more preformant for integers
           else      fromDouble(clean.toDouble)
        }
     }
   }

   implicit object ParsableSymbol extends Parsable[Symbol]{
      def apply(v:String):Symbol = Symbol(v) //Symbol wrapper
   }
   implicit object ParsableRegex extends Parsable[Regex]{
      def apply(v:String):Regex = v.r //pass it with the scala regex generator
   }
   //-- numeric parse types
   // using def mkNumber[T](value:String,    fromString:String => T,     fromDouble:Double => T,    fromLong:Long => T):T
   implicit object ParsableChar extends Parsable[Char]{
                                                                                 //fromString     fromDouble, fromLong
      def apply(v:String):Char= charKeys.getOrElse(v.toLowerCase,    mkNumber(v,  _.toInt.toChar, _.toChar,  _.toChar) )
   }
   implicit object ParsableInt extends Parsable[Int]{
      def apply(v:String):Int = intKeys.getOrElse(v.toLowerCase,     mkNumber(v,  _.toInt,        _.toInt,   _.toInt) )
   }
   implicit object ParsableLong extends Parsable[Long]{
      def apply(v:String):Long = longKeys.getOrElse(v.toLowerCase,   mkNumber(v, _.toLong,        _.toLong,  _.toLong) )
   }
   implicit object ParsableFloat extends Parsable[Float]{
      def apply(v:String):Float = floatKeys.getOrElse(v.toLowerCase, mkNumber(v, _.toFloat,       _.toFloat, _.toFloat) )
   }
   implicit object ParsableDouble extends Parsable[Double]{
      def apply(v:String):Double = mkNumber(v,_.toDouble, _.toDouble, _.toDouble)  //internally checks the keys TODO why 
   }
   implicit object ParsableBoolean extends Parsable[Boolean]{
      def apply(v:String):Boolean = boolKeys.getOrElse(v.toLowerCase, v.toBoolean)
   }
   //--drx helpers types are all included in their own object files
}
object Parsable extends ParsableLowPriorityImplicits{
   implicit object ParsableString extends Parsable[String]{
      def apply(v:String):String = v //pass it as is
   }
}