"""
    SearchOrder

Abstract type representing the order of processing during a branch and
prune search.

For custom search orders following methods must be implemented:
- `pop!(so::SearchOrder)`: return the next leaf to be processed.
    Return `nothing` when the search is done.
- `push!(so::SearchOrder, leaf::BPNode)`: add a leaf to the set of leaves to be
    processed.

The object is initialized by giving it the root node of the search tree,
as `SearchOrder(root::BPNode)`.
"""
abstract type SearchOrder end

struct DepthFirst{REGION} <: SearchOrder
    working_leaves::Vector{BPNode{REGION}}
end

DepthFirst(root::BPNode) = DepthFirst([root])

struct BreadthFirst{REGION} <: SearchOrder
    working_leaves::Vector{BPNode{REGION}}
end

BreadthFirst(root::BPNode) = BreadthFirst([root])

mutable struct ChangingOrder{REGION} <: SearchOrder
    working_leaves::Vector{BPNode{REGION}}
    next::Int 
end

ChangingOrder(root::BPNode) = ChangingOrder([root], 1)
set_next!(so::ChangingOrder, i::Int) = (so.next = i)

"""
    pop!(::SearchOrder)

Return the next leaf that will be processed.

Can modify the internal state of the SearchOrder.

Must return `nothing` if there is no more data to process.

Must be define for custom search orders.
"""
Base.pop!(so::BreadthFirst) = isempty(so.working_leaves) ? nothing : popfirst!(so.working_leaves)
Base.pop!(so::DepthFirst) = isempty(so.working_leaves) ? nothing : pop!(so.working_leaves)
function Base.pop!(so::ChangingOrder)
    isempty(so.working_leaves) && return nothing
    next = so.working_leaves[so.next]
    deleteat!(so.working_leaves, so.next)
    so.next = 1
    return next
end 

"""
    push!(::SearchOrder, leaf::BPNode)

Add a leaf to the set of nodes that need to be processed.

Can modify the internal state of the SearchOrder.

Must be define for custom search orders.
"""
Base.push!(so::Union{DepthFirst, BreadthFirst, ChangingOrder}, leaf::BPNode) = push!(so.working_leaves, leaf)
