module Pacer
  module Core
    module Route
      def branch(&block)
        route = chain_route transform: ::Pacer::Transform::Branch
        route.add_block! &block
      end

      def cond(where, &block)
        route = chain_route transform: ::Pacer::Transform::Branch
        route.add_cond! where, &block
      end
    end
  end

  class Wrappers::VertexWrapper
    def branch(&block)
      v.branch &block
    end
  end

  class Wrappers::EdgeWrapper
    def branch(&block)
      e.branch &block
    end
  end

  module Transform
    module Branch
      import com.tinkerpop.pipes.branch.CopySplitPipe
      import com.tinkerpop.pipes.branch.ExhaustMergePipe
      import com.tinkerpop.pipes.branch.FairMergePipe

      attr_accessor :branches, :split_pipe_class, :merge_pipe_class, :conds

      def after_initialize
        super
        self.branches ||= []
        self.conds ||= []
        self.split_pipe_class ||= CopySplitPipe
        self.merge_pipe_class ||= FairMergePipe
      end

      def clone(conf = {})
        back.chain_route({transform: Branch,
                          branches: branches.clone,
                          element_type: element_type,
                          conds: conds.clone,
                          extensions: extensions.clone,
                          split_pipe_class: split_pipe_class,
                          merge_pipe_class: merge_pipe_class
                         }.merge(conf))
      end

      def branch(&block)
        route = clone
        route.add_block! &block
      end

      def cond(where, &block)
        route = clone
        route.add_cond! where, &block
      end

      def otherwise(&block)
        where = "(#{ conds.compact.map { |c| "not (#{c})" }.join " and " })"
        cond where, &block
      end

      def merge
        route = clone
        route.merge_pipe_class = FairMergePipe
        route.chain_route transform: MergedBranch
      end

      def merge_exhaustive
        route = clone
        route.merge_pipe_class = ExhaustMergePipe
        route.chain_route transform: MergedBranch
      end

      def add_block!(&block)
        will_clone = false
        branch = block.call(Pacer::Route.empty(back))
        branches.push branch
        conf = {}

        exts = branches.
          map { |b| b.extensions }.
          map { |a| a ? a.to_set : Set[] }.
          reduce { |r, e| r.intersection(e) }
        if exts.to_a != config[:extensions]
          conf[:extensions] = exts.to_a
        end

        types = branches.map { |b| b.element_type }.to_set
        if types.length == 1
          type = types.first
        elsif types.difference([:vertex, :edge, :mixed]).empty?
          type = :mixed
        elsif types.difference([:path, :array]).empty?
          type = :array
        else
          type = :object
        end
        if type != element_type
          conf[:element_type] = type
        end
        if conf.empty?
          self
        else
          clone conf
        end
      end

      def add_cond!(where, &block)
        conds[branches.length] = where
        add_block! do |r|
          block.call r.where(where)
        end
      end

      def attach_pipe(end_pipe)
        branch_pipes = branches.map { |b| Pacer::Route.pipeline(b) }
        split = split_pipe_class.new branch_pipes
        merge = merge_pipe_class.new branch_pipes
        merge.setStarts split
        if end_pipe
          split.setStarts end_pipe
          merge
        else
          Pacer::Pipes::BlackboxPipeline.new split, merge
        end
      end
    end

    module MergedBranch
      # This just exists so that if I merge and then branch again,
      # the new branches will be against the merged source.
    end
  end
end

