module Rupture
  module Sequence
    def first
      seq.first
    end

    def rest
      seq.rest
    end

    def next
      rest.seq
    end

    def second
      rest.first
    end

    def empty?
      not seq
    end

    def not_empty
      self if seq
    end

    def divide
      [first, rest]
    end

    def each
      s = self
      while s = s.seq
        yield s.first
        s = s.rest
      end
    end

    def count
      F.loop(0, self) do |recur, i, s|
        if s.empty?
          i
        elsif s.respond_to?(:size)
          s.size + i
        else
          recur[i.inc, s.rest]
        end
      end
    end

    def conj(item)
      F.cons(item, self)
    end

    def into(coll)
      coll.seq.reduce(:conj, self)
    end

    def reverse
      Seq::Empty.into(self)
    end

    def take(n)
      F.lazy_seq do
        if n.pos? and s = seq
          F.cons(s.first, s.rest.take(n.dec))
        end
      end
    end

    def drop(n)
      F.lazy_seq do
        F.loop(n, seq) do |recur, n, s|
          if s and n.pos?
            recur[n.dec, s.next]
          else
            s
          end
        end
      end
    end

    def take_last(n)
      F.loop(seq, drop(n).seq) do |recur, s, lead|
        if lead
          recur[s.next, lead.next]
        else
          s || Seq::Empty
        end
      end
    end

    def drop_last(n)
      F.map(self, drop(n)) {|x,_| x}
    end

    def last
      take_last(1).first
    end

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

    def take_while(p = nil, &pred)
      pred ||= p
      F.lazy_seq do
        if s = seq
          i = s.first
          F.cons(i, s.rest.take_while(pred)) if pred[i]
        end
      end
    end

    def drop_while(p = nil, &pred)
      pred ||= p
      F.lazy_seq do
        F.loop(seq) do |recur, s|
          if s and pred[s.first]
            recur[s.next]
          else
            s
          end
        end
      end
    end

    def split_with(p = nil, &pred)
      pred ||= p
      [take_while(pred), drop_while(pred)]
    end

    def filter(p = nil, &pred)
      pred ||= p
      F.filter(pred, self)
    end

    def remove(p = nil, &pred)
      pred ||= p
      F.remove(pred, self)
    end

    def separate(p = nil, &pred)
      pred ||= p
      [filter(pred), remove(pred)]
    end

    def sequential?
      true
    end

    def flatten
      sequential = lambda {|x| x.class <= Seq or x.class == Array}
      tree_seq(sequential, :seq).remove(sequential)
    end

    def map(f = nil, &fn)
      fn ||= f
      F.lazy_loop(seq) do |recur, s|
        F.cons(fn[s.first], recur[s.next]) if s
      end
    end

    def map_indexed(f = nil, &fn)
      fn ||= f
      F.lazy_loop(0, seq) do |recur, i, s|
        F.cons(fn[i, s.first], recur[i.inc, s.next]) if s
      end
    end

    def concat(*colls)
      F.concat(self, *colls)
    end

    def mapcat(f = nil, &fn)
      fn ||= f
      F.mapcat(fn, self)
    end

    def reduce(*args, &fn)
      fn ||= args.shift
      Utils.verify_args(args, 0, 1)

      if s = seq
        inject(*args, &fn)
      elsif args.size == 1
        args.first
      else
        fn[] if fn.arity == -1
      end
    end

    def reductions(*args, &fn)
      fn ||= args.shift
      Utils.verify_args(args, 0, 1)
      acc, coll = (args.empty? ? divide : [args.first, self])

      F.lazy_loop(acc, coll) do |recur, acc, coll|
        if coll.seq
          acc = fn[acc, coll.first]
          F.cons(acc, recur[acc, coll.rest])
        end
      end.conj(acc)
    end

    def foldr(*args, &fn)
      fn ||= args.shift
      reverse.reduce(*args) {|a,b| fn[b,a]}
    end

    def tree_seq(branch, children, &f)
      branch   ||= f
      children ||= f
      walk = lambda do |node|
        F.lazy_seq do
          rest = children[node].mapcat(walk) if branch[node]
          F.cons(node, rest)
        end
      end
      walk[self]
    end

    def every?(p = nil, &pred)
      pred ||= p || F[:identity]
      all?(&pred)
    end

    def some(f = nil, &fn)
      fn ||= f || F[:identity]
      F.loop(seq) do |recur, s|
        if s
          if val = fn[s.first]
            val
          else
            recur[s.next]
          end
        end
      end
    end

    def nth(n)
      drop(n.dec).first
    end

    def partition(n, step = n, pad = nil)
      F.lazy_seq do
        if s = seq
          p = take(n)
          if n == p.count
            F.cons(p, drop(step).partition(n, step, pad))
          elsif pad
            F.cons(p.concat(pad).take(n), nil)
          else
            nil
          end
        end
      end
    end

    def partition_all(n, step = n)
      partition(n, step, [])
    end

    def partition_by(f = nil, &fn)
      fn ||= f
      F.lazy_seq do
        if s = seq
          head = s.first
          val  = fn[head]
          run = F.cons(head, s.rest.take_while {|i| val == fn[i]})
          F.cons(run, s.drop(run.count).partition_by(fn))
        end
      end
    end

    def partition_between(f = nil, &fn)
      fn ||= f
      F.lazy_seq do
        if s = seq
          run = F.cons(s.first,
                       s.partition(2,1).take_while {|i| not fn[*i]}.map(:second))
          F.cons(run, s.drop(run.count).partition_between(fn))
        end
      end
    end

    def sort(c = nil, &cmp)
      cmp||= c
      super(&cmp).seq
    end

    def sort_by(f = nil, &fn)
      fn ||= f
      sort {|a,b| fn[a] <=> fn[b]}
    end

    def frequencies
      reduce(Hash.new(0)) do |counts, x|
        counts.update!(x, :inc)
      end
    end

    def doall(n = nil)
      if n
        loop(n, seq) do |recur, n, s|
          recur[n.dec, s.next] if s and n.pos?
        end
      else
        loop(seq) do |recur, s|
          recur[s.next] if s
        end
      end
    end
  end
end
