/** File "BALBOADLL.scala" by KWR for CSE250, Spring 2022.
    Requires having ISR.scala and SortedDLL.scala compiled at same level.
    Buffer As List Built Of Arrays.
    Uses native ArrayBuffer class accessed from doubly linked list.

    CLASS INVS: Items are sorted non-descending according to keyComp in list then arrays.
    The last node of the list holds an empty array, thus serving as the end sentinel,
    while every other node holds a nonempty array---so 0 is a valid index for the array.
 */
import scala.collection.mutable.ArrayBuffer

class BALBOADLL[A](keyComp: (A,A) => Int) extends ISR[A] { Outer =>     //uses SortedSLL.scala
   val theList: SortedDLL[ArrayBuffer[A]] = new SortedDLL[ArrayBuffer[A]]((x,y) => keyComp(x(0),y(0)))
   private var _size = 0
   private var rnumArrays = 1
   

   /** Iter adds three methods to standard Scala next() and hasNext for iterators.
       Uses integer indices lind for theList and aind for its constituent arrays.
       End iterator is (theList.length-1, 0), which designates the end sentinel node.
       Using integer indices for the ListBuffer part is *lame* because it means the operations
       are not O(1) time, but if the list part is short they are still fairly quick.
    */
   class Iter(var litr: theList.Iter, var aind: Int) extends Iterator[A] {

      /** Special Scala syntax allows using just parens to return the data item.
       */
      def apply(): A = {
         assert(hasNext, "Attempt to fetch item past end in AIOLI\n" + Outer.diagnosticString)
         //return litr.at.item(aind)
         assert(litr.hasNext && 0 <= aind && aind < litr().length,
                "Out of bounds index aind = " + aind + " in object\n" + Outer.diagnosticString)

         return litr()(aind)
      }

      def next(): A = {
         assert(hasNext, "Attempt to advance past end in AIOLI\n" + Outer.diagnosticString)
         //INV: Control here implies ind is not last index of array, so ind+1 is valid
         val tmp = this()
         if (aind < litr().length-1) { 
            aind += 1
         } else {
            aind = 0
            litr.next()
         }
         return tmp
      }

      def hasNext: Boolean = (litr.hasNext)
      //Note: The CLASS INV that all non-sentinel list indices have nonempty arrays enables this code to be short

      def update(newItem: A) = {
         assert(hasNext, "Attempt to update item past end in AIOLI\n" + Outer.diagnosticString)
         litr()(aind) = newItem
      }

      def equals(other: Iter): Boolean = { litr.equals(other.litr) && aind == other.aind }
   }

   //Public Implementation of ISR Trait---sorting and keyComp don't change this.

   type I = Iter

   def begin: Iter = new Iter(theList.begin, 0)  //always exist, by second CLASS INV
   def end: Iter = new Iter(theList.end, 0)
      
   private def insertBefore(item: A, loc: Iter): Iter = {  //always keep same list
      if (loc.equals(end)) {
         val prevlitr = loc.litr.prev
         prevlitr().append(item)
         _size += 1
         return new Iter(prevlitr, prevlitr().length-1)
      } //else
      loc.litr().insert(loc.aind, item)
      _size += 1
      return new Iter(loc.litr, loc.aind)
   }
   /** REQuires (but doesn't test or enforce) that 
       keyComp(preat.item, item) <= 0 && keyComp(item,preat.next.item) <= 0.
       Safe usage is to call insert(item,findPlace(item)).
    */
   def insert(item: A, loc: Iter) = insertBefore(item, loc)
   def insert(item: A): Iter = {
      if (isEmpty) {
         val litr = theList.insert(new ArrayBuffer(), theList.begin)
         litr().append(item)
         _size += 1
         return new Iter(litr,0)
      } else {
         val itr = findPlace(item)
         return insert(item,itr)
      }
   }

   /** Cannot violate the CLASS INV, so OK to use freely.
    */
   def remove(loc: Iter): A = {
      assert(loc.hasNext, "Attempt to remove past-end item")
      //control here means loc is on a real element
      _size -= 1
      val tmp = loc.litr().remove(loc.aind)
      if (loc.litr().isEmpty) {
         theList.remove(loc.litr)
      }
      return tmp
   }
   def remove(item: A): A = {
      val itr = find(item)
      assert(itr.hasNext, "Attempt to remove non-found item " + item + " in BALBOADLL\n" + diagnosticString)
      return remove(itr)
   }

   private def findPlace(item: A): Iter = {
      var litr = theList.begin
      while (litr.hasNext && keyComp(item, litr.next()(0)) > 0) {
         //
      }
      //now litr is 1 or 2 places beyond where we wish to find.
      var litrprev = litr.prev
      if (!litrprev.equals(theList.begin)) { litrprev = litrprev.prev }
      var left = 0
      var right = litrprev().length
      if (right == left || keyComp(item, litrprev()(left)) < 0) {
         return new Iter(litrprev, left)
      } //else INV: left.item <= item < right.item, with end.item == +infinity
      while (right - left >= 2) {
         val mid = (right + left)/2   //integer division!
         if (keyComp(item, litrprev()(mid)) <= 0) {
            right = mid
         } else {
            left = mid
         }
      }
      //INV: left() is a real item, since the array is nonempty
      if (keyComp(item, litrprev()(left)) == 0) {
         return new Iter(litrprev, left)
      } else if (right == litrprev().length) {
         return new Iter(litr, 0)
      } else { 
         return new Iter(litrprev, right)
      }
   }

   def find(item: A): Iter = {
      val itr = findPlace(item)
      if (isEmpty || (itr.hasNext && keyComp(item, itr()) == 0)) return itr else return end
   }

   def size = _size

   //override def isEmpty = (_size <= 0)

   //override def ++=(other: ISR[A]): Unit   
   //Appending whole sequences  is now majorly dubious given the sortedness
   //invariant, so skip & ignore.

   def diagnosticString = {
      var ret = "Size = " + _size + "\n"
      val litr = theList.begin
      var i = 0
      while (litr.hasNext) {  
         ret += "" + i + "-->" + litr.next().toList + "\n"
         i += 1
      }
      ret += "" + (i) + "-->" + "end sentinel"
      ret
   }


}

   
