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

// import scala.language.dynamics

trait StringMap{
   //-----required interfaces
   /** the key get interface to parse as you would like*/
   def getString(key:String):Option[String]  //TODO maybe needs a different name to support backward compatibility

   /** list of the available keys (the sort order provides listing features)*/
   def keys:List[String]

   //----default interfaces get and split can be overriden for optimzation
   def get[T](key:String)
     (implicit parser:Parsable[T]):Option[T] =
      (this getString key map parser.apply)
   def get[A,B](keyA:String,keyB:String)
     (implicit parserA:Parsable[A], parserB:Parsable[B]):Option[(A,B)] =
       for(a <- this getString keyA; b <- this getString keyB) yield ( parserA(a), parserB(b) )
   def get[A,B,C](keyA:String,keyB:String,keyC:String)
     (implicit parserA:Parsable[A], parserB:Parsable[B], parserC:Parsable[C]):Option[(A,B,C)] =
       for(a <- this getString keyA; b <- this getString keyB; c <- this getString keyC) yield
         ( parserA(a), parserB(b), parserC(c) )
   def get[A,B,C,D](keyA:String,keyB:String,keyC:String,keyD:String)
     (implicit parserA:Parsable[A], parserB:Parsable[B], parserC:Parsable[C], parserD:Parsable[D]):Option[(A,B,C,D)] =
       for(a <- this getString keyA; b <- this getString keyB; c <- this getString keyC; d <- this getString keyD) yield
         ( parserA(a), parserB(b), parserC(c), parserD(d) )

   /** split */
   def split[T](key:String,sep:String)(implicit parser:Parsable[T]):Vector[T]
      = getString(key) map { parser.split(_,sep) } getOrElse Vector[T]()
   def split[T](key:String)(implicit parser:Parsable[T]):Vector[T]
      = getString(key) map { parser.split(_) } getOrElse Vector[T]()

   /**default as a path separator of '.' but this is configurable (over-ridable) */
   def mkPath(keys:Seq[String]):String = keys.map{_.replace("/",".")}.mkString(".")
   2017-07-30("use mkPath with a list of Keys instead", "dj")
   def mkPath(parent:String,child:String):String = parent + "." + child.replace("/",".")

   // final def apply[T](key:String)(implicit parser:Parsable[T]):Option[T] = (this get key).get
   // 2017-07-30("dangerous so, insead of this(key) use more explicit this.get(key) .as[Int] use direct this.get[Int].get","v0.2.13")

   //----derived classes, but overidable (not final)
   def toList:List[(String,String)] = for(k <- keys; v <- getString(k)) yield (k,v)

   //----derived classes 
   final def toMap:Map[String,String] = toList.toMap
   /**dangerous option, either use modadic or defaults with getOrElse*/
   final def apply[T](key:String)(implicit parser:Parsable[T]):T = {
      val o = get(key)
      if(o.isEmpty) throw Exception.NoSuchKey(key) else o.get
   }

   // def split[T](key:String)(implicit parser:Parsable[T]):Vector[T] = splitWithSep(key, """\s+""")
   final def contains(key:String):Boolean = getString(key).isDefined

   def /(name:String):StringMap.Scoped = StringMap.Scoped(this, name) //FIXME conform to the path notation
   def /(symbol:Symbol):StringMap.Scoped = this / symbol.name

   def mergeWith(that:StringMap):StringMap = StringMap.Merged(this,that)

   /*where the default specifies the parse type*/
   final def getOrElse[T](key:String,default:T)(implicit parser:Parsable[T]):T =
     this get key getOrElse default
}
object StringMap{
  def apply(map:Map[String,String]):StringMap = new StringMap{
    private lazy val sortedKeys = map.keys.toList.sorted
    def getString(key:String) = map.get(key)
    def keys = sortedKeys
  }
  def collect(pf: PartialFunction[String,String]):StringMap = new StringMap{
    def getString(key:String) = pf lift key
    def keys = Nil
  }

  case class Merged(a:StringMap,b:StringMap) extends StringMap{
    def getString(key:String) = a.getString(key) orElse b.getString(key)
    def keys = a.keys ++ b.keys
  }

  /**scoped to lazily build up a key and provide nice syntax for defaults*/
  case class Scoped(ctx:StringMap, key:String) extends StringMap{
     override def /(name:String) = Scoped(ctx, ctx.mkPath(List(key,name)) )
     def getString(subkey:String):Option[String] = ctx get ctx.mkPath(List(key,subkey))

     def keys:List[String] = { //TODO add a cache here ..
       val pre = key + "."
       val preSize = pre.size
       ctx.keys.filter(_ startsWith pre).map{_ drop preSize}.toList
     }

     def get[T](implicit parser:Parsable[T]):Option[T] = ctx get key
     2017-07-30("dangerous so, insead of this.as[Int] use direct this.get[Int].get","v0.2.13")
     def as[T](implicit parser:Parsable[T]):T = ctx.get(key).get //Note: dangerous
     def split[T](implicit parser:Parsable[T]):Vector[T] = ctx.split(key) //autosplit
     override def split[T](sep:String)(implicit parser:Parsable[T]):Vector[T] = ctx.split(key,sep) //specify a split

     /** A syntax sugar that coincides nicely with the stringMap key lookup to simultaneously provide a default and implicit value to type inferencing magic*/ //TODO switch this to be macro based like || is now
     def |[T](default:T)(implicit parser:Parsable[T]):T = ctx.getOrElse(key,default)
     def getOrElse[T](default:T)(implicit parser:Parsable[T]):T = ctx.getOrElse(key,default)
  }


/*
  /** provides A syntax sugar like: kson.field.name.as[String] instead of kson[String]("field.name") */
  trait StringMapDynamic extends StringMap with Dynamic{
     def selectDynamic(name:String):StringMapScoped = this / name //this guy is dangerous and a number of runtime bugs that are normally caught by the compiler happened
  }
*/

  /**Cached provides a nice way to make a fast lookup and parse inline*/
  trait Cached extends StringMap{

    //---data cache
    private val cache       = TrieMap.empty[String,Any]
    private val stringCache = TrieMap.empty[String,String]
    private val splitCache  = TrieMap.empty[String,Any]
    private val nullCache   = TrieMap.empty[String,None.type]

    //-----required interfaces
    def getStringNoCache(key:String):Option[String]

    //-----optimized interfaces
    /**specialized get method for strings so parsing is not required (this also provides a cache mechanism without type encoding)*/
    final def getString(key:String):Option[String] = {
      if(stringCache contains key) Some(stringCache(key))
      else if (nullCache contains key) None
      else {
         val res = getStringNoCache(key) //this is the single line of logic wrapped by the caching logic
         if(res.isDefined) stringCache(key) = res.get
         else nullCache(key) = None
         res
      }
    }

    /**get a key while parsing it via to a viable type inference value and caching it as so*/
    final override def get[T](key:String)(implicit parser:Parsable[T]):Option[T] = {
      if(cache contains key) Some(cache(key).asInstanceOf[T])
      else if (nullCache contains key) None
      else {
         // val got = getFromPath(key,0)//TODO init with zero but can this be used elsewhere?
         val got = getStringNoCache(key)
         val res = got map parser.apply   //this is the single line of logic wrapped by the caching logic

         if(res.isDefined) cache(key) = res.get
         else nullCache(key) = None
         res
      }
    }

    private def splitToCache[T](key:String, splitter:String => Vector[T]):Vector[T] = {
      if(splitCache contains key) splitCache(key).asInstanceOf[Vector[T]]
      else if (nullCache contains key) Vector[T]()
      else {
         val res:Vector[T] = getString(key) map { splitter } getOrElse Vector[T]()
         splitCache(key) = res
         res
      }
    }
    final override def split[T](key:String)(implicit parser:Parsable[T]):Vector[T] =
      splitToCache(key, parser.split(_))
    final override def split[T](key:String,sep:String)(implicit parser:Parsable[T]):Vector[T] =
      splitToCache(key, parser.split(_,sep))
/*
    //override because this is a fast cached implementation
    final override def split[T](key:String,sep:String=Parse.defaultSplitSep)(implicit parser:Parsable[T]):Vector[T] = {
      if(splitCache contains key) splitCache(key).asInstanceOf[Vector[T]]
      else if (nullCache contains key) Vector[T]()
      else {
         val res:Vector[T] = getString(key) map { parser.split(_,sep) } getOrElse Vector[T]()
         splitCache(key) = res
         res
      }
    }
*/
  }
}