module Pacer
  module Core
    module Graph
      module PathRoute
        # Transform raw paths to a tree:
        # [a b c]
        # [a b d]
        # [a e f]
        # [a e g]
        # -- becomes --
        # [a [b [c]
        #       [d]]
        #    [e [f]
        #       [g]]]

        # The default comparator block is { |prev, current| prev == current }
        def tree(&block)
          wrapped.chain_route transform: :path_tree, element_type: :array, compare: block
        end
      end
    end
  end

  module Transform
    module PathTree
      attr_accessor :compare

      protected

      def attach_pipe(end_pipe)
        pipe = PathTreePipe.new compare
        pipe.setStarts end_pipe if end_pipe
        pipe
      end

      class PathTreePipe < Pacer::Pipes::RubyPipe
        def initialize(compare = nil)
          super()
          self.building_path = nil
          self.prev_path = nil
          self.compare = compare || proc { |a, b| a == b }
        end

        # NOTE: doesn't handle variable length paths yet...
        def processNextStart()
          while true
            path = starts.next
            if building_path
              if compare.call path.first, building_path.first
                add_path path
              else
                return next_path(path)
              end
            else
              next_path(path)
            end
          end
        rescue Pacer::EmptyPipe, java.util.NoSuchElementException
          if building_path
            r = building_path
            self.building_path = nil
            r
          else
            raise EmptyPipe.instance
          end
        end

        private

        attr_accessor :building_path, :prev_path, :compare

        def make(path)
          path.reverse.inject(nil) do |inner, e|
            if inner
              [e, inner]
            else
              [e]
            end
          end
        end

        def add_path(path)
          working = building_path
          (1..path.length - 1).each do |pos|
            current = path[pos]
            prev = prev_path[pos]
            if compare.call current, prev
              working = working.last
            else
              if pos < path.length
                working << make(path[pos..-1])
              else
                working << [current]
              end
              break
            end
          end
          self.prev_path = path
        end

        def next_path(path)
          finished = building_path
          self.building_path = make path
          self.prev_path = path
          finished
        end
      end
    end
  end
end

