module Pacer
  module Routes
    module RouteOperations
      # Arity 2 uses custom sort logic. Arity 1 uses sort_by logic.
      def sort_section(section = nil, opts = {}, &block)
        if not block
          chain_route opts.merge(transform: :sort_section, section: section)
        elsif block.arity == 2
          chain_route opts.merge(transform: :sort_section, custom_sort_block: block, section: section)
        else
          chain_route opts.merge(transform: :sort_section, sort_by_block: block, section: section)
        end
      end

      # Deprecated: use sort_section
      def custom_sort_section(section = nil, &block)
        chain_route transform: :sort_section, custom_sort_block: block, section: section
      end
    end
  end


  module Transform
    module SortSection
      # VisitsSection module provides:
      #  section=
      #  section_visitor
      include Pacer::Visitors::VisitsSection

      attr_accessor :sort_by_block
      attr_accessor :custom_sort_block
      attr_accessor :nil_value

      def nil_sort_value(value)
        @nil_value = nil_value
        self
      end

      protected

      def attach_pipe(end_pipe)
        if custom_sort_block
          wrapper = Pacer::Wrappers::WrapperSelector.build graph, element_type, extensions
          pipe = CustomSortPipe.new(self, section_visitor, custom_sort_block, graph, wrapper)
        else # sort_by_block
          pf = Pacer::Wrappers::WrappingPipeFunction.new self, sort_by_block if sort_by_block
          pipe = SortBySectionPipe.new(self, section_visitor, pf, nil_value)
        end
        pipe.setStarts end_pipe if end_pipe
        pipe
      end

      class SortBySectionPipe < Pacer::Pipes::RubyPipe
        attr_reader :pf_1, :pf_2, :to_sort, :to_emit, :section, :route, :nil_value
        attr_reader :getPathToHere

        def initialize(route, visitor_pipe, pipe_function, nil_value)
          super()
          @to_emit = []
          @visitor_pipe = visitor_pipe
          @route = route
          @to_sort = []
          @paths = []
          @nil_value = nil_value
          if visitor_pipe
            visitor_pipe.visitor = self
          else
            on_element nil
          end
          if pipe_function
            if pipe_function.arity == 1
              @pf_1 = pipe_function
              visitor_pipe.use_on_element = false
            else
              @pf_2 = pipe_function
            end
          else
            visitor_pipe.use_on_element = false
          end
        end

        def setStarts(starts)
          super
          enablePath(true) if pf_2
        end

        def processNextStart
          if pathEnabled
            while to_emit.empty?
              to_sort << [starts.next, starts.getCurrentPath]
            end
          else
            while to_emit.empty?
              to_sort << [starts.next, nil]
            end
          end
          raise EmptyPipe.instance if to_emit.empty?
          element, @getPathToHere = to_emit.shift
          element
        rescue EmptyPipe, java.util.NoSuchElementException
          if to_emit.empty?
            raise EmptyPipe.instance
          else
            after_element
            retry
          end
        end

        def on_element(element)
          @section_element = element
        end

        def after_element
          if to_sort.any?
            if pf_1
              if nil_value
                sorted = to_sort.sort_by do |element, path|
                  v = pf_1.call element
                  if v.nil?
                    nil_value
                  else
                    v
                  end
                end
              else
                sorted = to_sort.sort_by do |element, path|
                  pf_1.call element
                end
              end
            elsif pf_2
              if nil_value
                sorted = to_sort.sort_by do |element, path|
                  v = pf_2.call_with_args element, @section_element, pf_2.wrap_path(path)
                  if v.nil?
                    nil_value
                  else
                    v
                  end
                end
              else
                sorted = to_sort.sort_by do |element, path|
                  pf_2.call_with_args element, @section_element, pf_2.wrap_path(path)
                end
              end
            else
              sorted = to_sort.sort_by do |element, path|
                if element.nil?
                  nil_value
                else
                  element
                end
              end
            end
            to_emit.concat sorted
            @to_sort.clear
          end
        end
      end

      class CustomSortPipe < SortBySectionPipe
        attr_reader :sort_block

        def initialize(route, visitor_pipe, sort_block, graph, wrapper)
          super route, visitor_pipe, nil, nil
          @sort_block = sort_block
          @graph = graph
          @wrapper = wrapper
        end

        def after_element
          if to_sort.any?
            to_sort.map! { |e| [ @wrapper.new(@graph, e.first), e.last ] }
            sorted = to_sort.sort { |a, b| @sort_block.call a.first, b.first }
            if route.element_type == :vertex || route.element_type == :edge
              to_emit.concat sorted.map { |e| [ e.first.element, e.last ] }
            else
              to_emit.concat sorted
            end
            @to_sort.clear
          end
        end
      end
    end
  end
end
