# encoding: utf-8

module Rouge::Seq
  Empty = Object.new

  class Cons; end

  module ISeq; end

  module ASeq
    include ISeq

    def inspect
      "(#{to_a.map(&:inspect).join " "})"
    end

    def to_s; inspect; end

    def seq; self; end

    def first; raise NotImplementedError; end
    def next; raise NotImplementedError; end

    def more
      s = self.next
      if s.nil?
        Empty
      else
        s
      end
    end

    def cons(o)
      Cons.new(o, self)
    end

    def length
      l = 0
      cursor = self

      while cursor != Empty
        l += 1
        cursor = cursor.more
      end

      l
    end

    alias count length

    def [](i)
      return to_a[i] if i.is_a? Range

      cursor = self

      i += self.length if i < 0

      while i > 0
        i -= 1
        cursor = cursor.more
        return nil if cursor == Empty
      end

      cursor.first
    end

    def ==(seq)
      (seq.is_a?(ISeq) and self.to_a == seq.to_a) or
        (seq.is_a?(::Array) and self.to_a == seq)
    end

    def each(&block)
      return self.enum_for(:each) if block.nil?

      yield self.first

      cursor = self.more
      while cursor != Empty
        yield cursor.first
        cursor = cursor.more
      end

      self
    end

    def map(&block)
      return self.enum_for(:map) if block.nil?

      r = []
      self.each {|e| r << block.call(e)}
      r
    end

    def to_a
      r = []
      self.each {|e| r << e}
      r
    end

    def map(&block)
      Cons[*to_a.map(&block)]
    end
  end

  class << Empty
    include ASeq

    def inspect; "()"; end
    def to_s; inspect; end

    def seq; nil; end
    def first; nil; end
    def next; nil; end

    def each(&block)
      return self.enum_for(:each) if block.nil?
    end
  end

  Empty.freeze

  class Cons
    include ASeq

    def initialize(head, tail)
      if tail and !tail.is_a?(ISeq)
        raise ArgumentError,
            "tail should be an ISeq, not #{tail.inspect} (#{tail.class})"
      end

      @head, @tail = head, tail
    end

    def first; @head; end
    def next; Rouge::Seq.seq @tail; end

    def self.[](*elements)
      return Empty if elements.length.zero?

      head = nil
      (elements.length - 1).downto(0).each do |i|
        head = new(elements[i], head.freeze)
      end

      head.freeze
    end

    attr_reader :head, :tail
  end

  class Array
    include ASeq

    def initialize(array, i)
      @array, @i = array, i
    end

    def first
      @array[@i]
    end

    def next
      if @i + 1 < @array.length
        Array.new(@array, @i + 1)
      end
    end
  end

  class Lazy
    include ISeq

    def initialize(body)
      @body = body
      @realized = false
    end

    def seq
      if @realized
        @result
      else
        @result = Rouge::Seq.seq(@body.call) || Empty
        @realized = true
        @result
      end
    rescue UnknownSeqError
      @realized = true
      @result = Empty
      raise
    end

    def method_missing(sym, *args, &block)
      seq.send(sym, *args, &block)
    end

    def inspect; seq.inspect; end
    def to_s; seq.to_s; end
  end

  UnknownSeqError = Class.new(StandardError)

  def self.seq(form)
    case form
    when ISeq
      form.seq
    when NilClass
      form
    when ::Array
      if form.empty?
        nil
      else
        Rouge::Seq::Array.new(form, 0)
      end
    when Enumerator
      seq(form.to_a)
    else
      raise UnknownSeqError, form.inspect
    end
  end
end

# vim: set sw=2 et cc=80: