#!/usr/bin/ruby -w

# TODO create a LazyEvaluationArray to handle infinite lists

class Array
  def head
    self[0]
  end
  
  # last() is already defined
  
  def tail
    self[1..-1]
  end

  def init
    self[0..-2]
  end

  def null
    size == 0
  end

  # TODO ++, map, filter, concat, length, !!

  # TODO unified syntax for foldl and foldl1 (default params?)
  # TODO passing a block to fold (see reduce)
  # foldl (+) 0 [a,b,c] => 0 + (a + (b + c))
  def foldl op, val
    each{ |elem|
      val = val.send(op, elem)
    }
    val
  end

  # foldl1 (+) [a,b,c] => a + (b + c)
  def foldl1 op
    val = head
    tail.each{ |elem|
      val = val.send(op, elem)
    }
    val
  end

  # scanl (+) 0 [a,b,c] => [0, a + 0, b + (a + 0), c + (b + (a + 0))]
  # scanl (+) 0 [1..10] => [0,1,3,6,10,15,21,28,36,45,55]
  def scanl op, val
    res = [val]
    each{ |elem|
      res << res.last.send(op, elem)
    }
    res
  end

  def scanl1 op
    res = [head]
    tail.each{ |elem|
      res << res.last.send(op, elem)
    }
    res
  end

  # foldr (+) 0 [a,b,c] => a + (b + (c + 0))
  def foldr op, val
    reverse.foldl op, val # XXX is this correct???
  end

  # TODO scanr[1]

  def iterate fn, n # fn is a proc
    # XXX problem: infinite list => closures that keep yielding results
    raise "inf"
    res = [n]
    loop {
      #res << apply(&fn, res.last)
    }
    res
  end

  # expressed otherwise in Ruby:
  # repeat
  # replicate

  def cycle
    raise "inf"
  end

  def take n
    self[0..(n-1)]
  end

  def drop n
    self[n..-1]
  end

  def split_at n
    [take(n), drop(n)]
  end

  def take_while
    res = []
    each{ |elem|
      break unless yield(elem)
      res << elem
    }
    res
  end

  def drop_while
    res = []
    each{ |elem|
      next if yield(elem)
      res << elem
    }
    res
  end

  def span
    # in terms of take_while and drop_while???
  end

  def break
  end

  # TODO span break lines words unwords unlines reverse and or any all elem
  # not_elem lookup sum product maximum minimum zip zip3 unzip unzip3

  def zip other
    res = []
    each_index{ |i|
      res << [self[i], other[i]]
    }
    res
  end
end

a = (1..10).to_a
p a.head                          #=> 1
p a.last                          #=> 10
p a.tail                          #=> [2, 3, 4, 5, 6, 7, 8, 9, 10]
p a.init                          #=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
p a.foldl(:+, 0)                  #=> 55
p a.foldl(:*, 1)                  #=> 3628800
p a.foldl1(:+)                    #=> 55
p a.foldl1(:*)                    #=> 3628800
p a.scanl(:+, 0)                  #=> [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
p a.scanl1(:+)                    #=> [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
p a.foldr(:+, 0)
p a.take(4)                       #=> [1, 2, 3, 4]
p a.drop(4)                       #=> [5, 6, 7, 8, 9, 10]
p a.split_at(7)                   #=> [[1, 2, 3, 4, 5, 6, 7], [8, 9, 10]]
p a.take_while{|i| i < 5}         #=> [1, 2, 3, 4]
p a.drop_while{|i| i < 5}         #=> [5, 6, 7, 8, 9, 10]

p a.zip(a.reverse)

# FIXME "{take,split_at} 0" gives weird stuff
# take 0 must give [] see hugs
# split_at 0 must give [[],[1,2,3,4,5,6,7,8,9,10]]
# 11.times{|i| p a.take(i) }
# 11.times{|i| p a.split_at(i) }
