
package dbmodel

  /** Synchronizable collection, Version 11.1
   *
   *  Endow collections with synchronizability, via a simple buffering 
   *  functionality that supports shifting items into the buffer,
   *  discarding items, and rewinding to the first non-discarded items. 
   *
   *  Also, when iterators are associated with resources like files,
   *  we need to be able to release these resources when we are done
   *  with using these iterators.
   *
   *  Wong Limsoon
   *  12 May 2023
   */
  

  object Synchronizable:

    import scala.collection.mutable.Builder
    import scala.collection.BufferedIterator
    import scala.util.Using
    import dbmodel.RESOURCE.Resource
    import dbmodel.Predicates.{ Pred, and }
    


    trait CBI[B] extends BufferedIterator[B], Resource:

      /** Functions needed for implementing Synchrony iterator
       */

      def atEnd: Boolean    /** Reached the end?     
                             */

      def shift(): Unit     /** Move to next item, shift into buffer if needed
                             */

      def discard(): Unit   /** Discard current item, then move to next item
                             */

      def rewind(): Unit    /** Rewind to first undiscarded item in buffer
                             */

      def b: B              /** Current item
                             */

      def isVec = false     /** It a vector underlying this iterator?
                             */

      def bs(): Vector[B]  /** Return and discard current buffer.
                             */

      /** Functions for realising BufferedIterator
       */

      def hasNext: Boolean = !isEmpty

      def head: B = b

      def next(): B = { val tmp = b; discard(); shift(); tmp }


      /** Autoclose this [[CBI]] when at end
       */

      override def isEmpty = { val tmp = atEnd; if tmp then close(); tmp }


      /** Functions for using this [[CBI]] and propagating its [[close]]
       *  function to their output. Although one can invoke all
       *  [[BufferedIterator]] methods on [[CBI]], it is best doing so
       *  via [[done { _.m }]], [[doit { _.m }]], etc. if you want to be
       *  sure that the [[CBI]] is closed after the operation.
       */

      def done[C](f: CBI[B] => C): C = Using.resource(this)(f)

      def doit[C]: (CBI[B] => IterableOnce[C]) => CBI[C] = CBI.doit(this)

      def step[C]: (CBI[B] => C) => CBI[C] = CBI.step(this)

      def condStep[C]: (CBI[B] => Boolean, CBI[B] => C) => CBI[C] = 
        CBI.condStep(this)

      override def filter(f: B => Boolean): CBI[B] = CBI.filter(this)(f) 

      override def withFilter(f: B => Boolean): CBI[B] = CBI.filter(this)(f)

      override def map[C](f: B => C): CBI[C] = CBI.map(this)(f)

      override def flatMap[C](f: B => IterableOnce[C]): CBI[C] =
        CBI.flatMap(this)(f)

      override def foreach[U](f: B => U): Unit = CBI.foreach(this)(f)


      /** Function for convenience of converting between [[CBI]]
       * and other collection types.
       */

      def cbi: this.type = this



    object CBI:

      /** Construct [[CBI]] from [[items]],
       *  where [[items]] is completely in memory.  
       *
       *  @require [[items]] is sorted according to [[key]].
       */

      def apply[B](items: Iterable[B]): CBI[B] = new CBI[B] {
        val buf: Vector[B] = items.toVector
        val mx = buf.length
        val hs: Array[Boolean] = Array.fill(mx)(true)
        var p = 0
        var q = 0
        def shift() = if p < mx then p += 1
        def rewind() = { while (q < p) && !hs(q) do { q += 1}; p = q }
        def discard() = hs(p) = false
        def atEnd = p >= mx
        def b = buf(p)
        def bs() = if isEmpty then Vector() else { p = mx; q = mx; buf }
        override def isVec = true
      }

     
      /** Construct [[CBI]] from [[items]].
       *  [[items]] may not be completely in memory, e.g., an iterator.
       *  Access and buffer a chunk of [[items]] at a time.  
       *
       *  @require [[items]] is sorted according to [[key]].
       */

      def apply[B](items: IterableOnce[B]): CBI[B] = items match
        case vec: Iterable[B] => CBI(vec)
        case _ => try items.asInstanceOf[CBI[B]] catch _ => new CBI[B] {

          items match { case r: Resource => use(r); case _ => }

          val it = items.iterator

          val mx = 1024

          var buf: Vector[B] = 
            val tmp = Vector.newBuilder[B]
            var j = 0
            while j < mx && it.hasNext do { tmp += it.next(); j += 1 }
            tmp.result

          var bx = buf.length
          var hs: Array[Boolean] = Array.fill(bx)(true)
          var p = 0
          var q = 0

          def b = buf(p)

          def discard() = hs(p) = false

          def rewind() = { while (q < p) && !hs(q) do { q += 1 }; p = q }

          def atEnd = p >= bx && !it.hasNext
  
          def shift() = 
            p += 1 
            while p < bx && !hs(p) do p += 1 
            if p >= bx then
              val tmp = Vector.newBuilder[B]
              var j = -1
              for i <- q to p - 1 do if hs(i) then { tmp += buf(i); j += 1 }
              q = 0
              p = j
              j = 0
              while j < mx && it.hasNext do { tmp += it.next(); j += 1 }
              buf = tmp.result
              bx = buf.length
              hs = Array.fill(bx)(true)
              p += 1
  
          def bs() = 
            if isEmpty then Vector()
            else { val tmp = buf; p = bx - 1; q = bx; shift(); tmp }  
        }


      /** More constructors for [[CBI]]
       */
  
      def emptyCBI[B]: CBI[B] = CBI[B](Vector())

      def apply[B](items: B*): CBI[B] = CBI[B](items)

      def apply[B](items: IterableOnce[B], cl: () => Unit): CBI[B] =
        CBI[B](items).use(Resource.closeable(cl))


      /** Functional forms of some [[CBI]] methods.
       */

      def doit[B,C](cbi: CBI[B])(f: CBI[B] => IterableOnce[C]): CBI[C] =
        val it = f(cbi)
        // next line loops forever if use() is used instead of userev()
        try it.asInstanceOf[CBI[C]].userev(cbi) 
        catch _ => CBI(it).use(cbi)
  

      def step[B,C](cbi: CBI[B])(f: CBI[B] => C): CBI[C] = 
        if cbi.isVec then
          val tmp = Vector.newBuilder[C]
          while cbi.hasNext do tmp += f(cbi)
          cbi.close()
          CBI[C](tmp.result)
        else
          val it = new Iterator[C] {
            def hasNext = cbi.hasNext
            def next() = f(cbi)
          }
          CBI[C](it).use(cbi)


      def condStep[B,C]
              (cbi: CBI[B])
              (c: CBI[B]=> Boolean, f: CBI[B] => C)
          : CBI[C] = 
        def more = { while cbi.hasNext && !c(cbi) do cbi.next(); cbi.hasNext }
        if cbi.isVec then
          val tmp = Vector.newBuilder[C]
          while more do tmp += f(cbi)
          cbi.close()
          CBI[C](tmp.result)
        else
          val it = new Iterator[C] {
            def hasNext = more
            def next() = f(cbi)
          }
          CBI[C](it).use(cbi)


      def filter[B](cbi: CBI[B])(f: B => Boolean): CBI[B] = 
        if cbi.isVec then
          val tmp = Vector.newBuilder[B]
          while cbi.hasNext 
          do if f(cbi.head) then tmp += cbi.next() else cbi.next() 
          cbi.close()
          CBI[B](tmp.result)
        else
          val it = cbi collect { case x if f(x) => x }
          CBI(it).use(cbi)


      def map[B,C](cbi: CBI[B])(f: B => C): CBI[C] =
        if cbi.isVec then
          val tmp = Vector.newBuilder[C]
          while cbi.hasNext do tmp += f(cbi.next())
          cbi.close()
          CBI[C](tmp.result)
        else
          val it = cbi collect { case x => f(x) }
          CBI[C](it).use(cbi)


      def flatMap[B,C](cbi: CBI[B])(f: B => IterableOnce[C]): CBI[C] =
        var acc: CBI[C] = CBI.emptyCBI
        val clacc = Resource.closeable { () => acc.close() }
        val it = new Iterator[C] {
          def next() = acc.next()
          def hasNext =
            while !acc.hasNext && cbi.hasNext do  
              val it = f(cbi.next())
              acc = CBI(it)
            acc.hasNext
        }
        CBI(it).use(clacc, cbi)


      def foreach[B,U](cbi: CBI[B])(f: B => U): Unit =
        Using.resource(cbi) { it => while it.hasNext do f(it.next()) }

    end CBI



    /** Endow [[IterableOnce]] objects with a method to turn
     *  themselves into closeable iterators [[CBI]].
     */

    extension [B](it: IterableOnce[B])
      def cbi: CBI[B] = CBI(it)


  end Synchronizable



