# ------------------------------------
# set initial conditions
# ------------------------------------
function set_initQbase(Qbase, cellxmax, cellymax, restart_file, init_rho, init_u, init_v, init_p, init_T,
                    specific_heat_ratio, out_file_front, out_ext, out_dir, restartnum, Rd, nval)

    restart_check = 0
    try Qbase = setup_restart_value(Qbase, cellxmax, cellymax, out_dir, restart_file, nval)
        println("Restart "*restart_file)
        restart_check = 2
    catch
        restart_check = 1
    end

    if restart_check == 1
        Qbase = setup_init_value(Qbase, cellxmax, cellymax, init_rho, init_u, init_v, init_p)
        println("Start Initial condition")
        restartnum = 0
        output_result(0, Qbase, cellxmax, cellymax, specific_heat_ratio, out_file_front, out_ext, out_dir, Rd, nval)
    end

    return Qbase, restartnum
end

function setup_init_value(Qbase, cellxmax, cellymax, init_rho, init_u, init_v, init_p)
    for j in 1:cellymax
        for i in 1:cellxmax
            Qbase[i,j,1] = init_rho
            Qbase[i,j,2] = init_u
            Qbase[i,j,3] = init_v
            Qbase[i,j,4] = init_p
        end
    end
    return Qbase
end

function setup_restart_value(Qbase, cellxmax, cellymax, out_dir, restart_file, nval)
    skipnum = 1
    fff = []
    open("result/"*restart_file, "r") do f
        fff = read(f, String)
    end
    fff = split(fff,"\n", keepempty = false)

    for i in 1+skipnum:length(fff)
        fff[i] = replace(fff[i]," \r" => "")
    end

    k = 1
    for i in 2:cellxmax-1
        for j in 2:cellymax-1
            temp = split(fff[k+skipnum]," ")
            for l in 1:nval
                Qbase[i,j,l] = parse(Float64,temp[l])
            end
            k = k+1
        end
    end
    return Qbase
end

# ------------------------------------
# find out if results were diverge
# ------------------------------------
function check_divrege(Qbase, cellxmax, cellymax, Rd, fwrite)
    ite = 0
    for j in 2:cellymax-1
        for i in 2:cellxmax-1
            T = Qbase[i,j,4]/(Qbase[i,j,1]*Rd)
            if T < 0 || isequal(T, NaN) == true
                open( fwrite, "a" ) do f
                    ai  = @sprintf("%4.0f", i)
                    aj  = @sprintf("%4.0f", j)
                    rho = @sprintf("%4.0e", Qbase[i,j,1])
                    p   = @sprintf("%4.0e", Qbase[i,j,4])

                    write(f, "\n")
                    write(f, " diverge ")
                    write(f, "\n")
                    write(f, " i = "*ai)
                    write(f, "\n")
                    write(f, " j = "*aj)
                    write(f, "\n")
                    write(f, " rho = "*rho)
                    write(f, "\n")
                    write(f, " p = "*p)
                    write(f, "\n")

                end
                ite = 1
            end
        end
    end

    if ite == 1
        println("\n")
        println("\n")
        println(" T<0 ")
        println(" diverge ")
        println("\n")
        throw(UndefVarError(:x))
    end
end

# ------------------------------------
# quicksort
# ------------------------------------
function quicksort(list, first, last, num_cell_data, standard_num)
    # list = zezros(m,n)
    x = list[Int(floor((first+last)/2)),standard_num]
    i = first
    j = last

    while true
        while list[i,standard_num] < x
            i = i+1
        end
        while x < list[j,standard_num]
            j = j-1
        end

        if (i >= j)
            break
        else
            for k in 1:num_cell_data
                t = list[i,k]
                list[i,k] = list[j,k]
                list[j,k] = t
            end
            i = i+1
            j = j-1
        end
    end

    if (first < i - 1)
        list = quicksort(list, first, i - 1,num_cell_data,standard_num)
    end
    if (j + 1 < last)
        list = quicksort(list, j + 1, last,num_cell_data,standard_num)
    end
    return list
end


#InspectDR: Support for pan/zoom
#-------------------------------------------------------------------------------

#==Constants
===============================================================================#
const PAN_STEPRATIO = 0.25 #Percentage of current extents
const ZOOM_STEPRATIO = 2.0 #How much to zoom in/out with mousewheel + keybindings

#==Drawing functions
===============================================================================#
function selectionbox_draw(ctx::CairoContext, selbb::BoundingBox, graphbb::BoundingBox, hallowed::Bool, vallowed::Bool)
	xmin = selbb.xmin; xmax = selbb.xmax
	ymin = selbb.ymin; ymax = selbb.ymax
	if !hallowed
		xmin = graphbb.xmin
		xmax = graphbb.xmax
	end
	if !vallowed
		ymin = graphbb.ymin
		ymax = graphbb.ymax
	end

	Cairo.save(ctx) #-----
	Cairo.set_source(ctx, COLOR_BLACK)
	setlinestyle(ctx, :dash, 1.0)
	Cairo.rectangle(ctx, BoundingBox(xmin, xmax, ymin, ymax))
	Cairo.stroke(ctx)
	Cairo.restore(ctx) #-----

	nothing
end

function selectionbox_draw(ctx::CairoContext, w::PlotWidget)
	if !w.sel.enabled; return; end
	selectionbox_draw(ctx, w.sel.bb, w.graphbb, w.hallowed, w.vallowed)
end

#==Switching to h/v lock (or releasing both)
===============================================================================#
function locdir_h(pwidget::PlotWidget)
	pwidget.hallowed = true
	pwidget.vallowed = false
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end
function locdir_v(pwidget::PlotWidget)
	pwidget.hallowed = false
	pwidget.vallowed = true
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end
function locdir_any(pwidget::PlotWidget)
	pwidget.hallowed = true
	pwidget.vallowed = true
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end

#==Scale/position widget control
===============================================================================#
function scalectrl_enabled(pwidget::PlotWidget)
	return Gtk.GAccessor.sensitive(pwidget.w_xscale)
end
function scalectrl_enabled(pwidget::PlotWidget, v::Bool)
	Gtk.GAccessor.sensitive(pwidget.w_xscale, v)
	Gtk.GAccessor.sensitive(pwidget.w_xpos, v)
end
#Apply current scale/position scrollbar values to plot extents:
function scalectrl_apply(pwidget::PlotWidget)
	xscale = getproperty(pwidget.xscale, :value, Int)
	xpos = getproperty(pwidget.xpos, :value, Float64)

	#Use transformed coordinate system:
	ext_full = rescale(getextents_full(pwidget.src), pwidget.src.axes)
	span = ext_full.xmax - ext_full.xmin
	center = (ext_full.xmax + ext_full.xmin) / 2
	vspan = span/xscale #Visible span
	xmin = center + span*xpos - vspan/2
	xmax = xmin + vspan

	#Update extents & redraw
	ext_new = PExtents2D(xmin, xmax, DNaN, DNaN)
	ext = merge(getextents_xfrm(pwidget.src), ext_new)
	setextents_xfrm(pwidget.src, ext)
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end

#==Basic zoom control
===============================================================================#
#Zoom to bounding box (in device coordinates):
function zoom(pwidget::PlotWidget, bb::BoundingBox)
	p1 = Point2D(bb.xmin, bb.ymin)
	p2 = Point2D(bb.xmax, bb.ymax)

	ext = getextents_xfrm(pwidget.src)
	xf = Transform2D(ext, pwidget.graphbb)
	p1 = ptmap_rev(xf, p1)
	p2 = ptmap_rev(xf, p2)

	setextents_xfrm(pwidget.src, PExtents2D(
		min(p1.x, p2.x), max(p1.x, p2.x),
		min(p1.y, p2.y), max(p1.y, p2.y)),
		pwidget.hallowed, pwidget.vallowed
	)
	scalectrl_enabled(pwidget, false) #Scroll bar control no longer valid
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end

#Zoom in/out @ point (pt in plot coordinates)
function zoom(pwidget::PlotWidget, ext::PExtents2D, pt::Point2D, ratio::Float64)
	xspan = ext.xmax - ext.xmin
	yspan = ext.ymax - ext.ymin
	Δx = pt.x - ext.xmin
	Δy = pt.y - ext.ymin
	xmin = pt.x-ratio*Δx
	ymin = pt.y-ratio*Δy

	setextents_xfrm(pwidget.src, PExtents2D(
		xmin, xmin + ratio*xspan,
		ymin, ymin + ratio*yspan
	))
	scalectrl_enabled(pwidget, false) #Scroll bar control no longer valid
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end

#Zoom in/out, centered on current extents
function zoom(pwidget::PlotWidget, ratio::Float64)
	ext = getextents_xfrm(pwidget.src)
	pt = Point2D((ext.xmin+ext.xmax)/2, (ext.ymin+ext.ymax)/2)
	zoom(pwidget, ext, pt, ratio)
end
zoom_out(pwidget::PlotWidget, stepratio::Float64=ZOOM_STEPRATIO) =
	zoom(pwidget, stepratio)
zoom_in(pwidget::PlotWidget, stepratio::Float64=ZOOM_STEPRATIO) =
	zoom(pwidget, 1/stepratio)

#Zoom in/out around specified device coordinates:
function zoom(pwidget::PlotWidget, x::Float64, y::Float64, ratio::Float64)
	pt = Point2D(x, y)
	ext = getextents_xfrm(pwidget.src)
	xf = Transform2D(ext, pwidget.graphbb)
	pt = ptmap_rev(xf, pt)
	zoom(pwidget, ext, pt, ratio)
end
zoom_out(pwidget::PlotWidget, x::Float64, y::Float64, stepratio::Float64=ZOOM_STEPRATIO) =
	zoom(pwidget, x, y, stepratio)
zoom_in(pwidget::PlotWidget, x::Float64, y::Float64, stepratio::Float64=ZOOM_STEPRATIO) =
	zoom(pwidget, x, y, 1/stepratio)

function zoom_xfull(pwidget::PlotWidget)
	#TODO
end
function zoom_full(pwidget::PlotWidget, hallowed::Bool=true, vallowed::Bool=true)
	setextents(pwidget.src, PExtents2D(), hallowed, vallowed) #Reset desired extents
	if hallowed
		scalectrl_enabled(pwidget, false) #Suppress updates from setproperty!
		setproperty!(pwidget.xscale, :value, Int(1))
		setproperty!(pwidget.xpos, :value, Float64(0))
		scalectrl_enabled(pwidget, true)
		scalectrl_apply(pwidget)
	else
		render(pwidget)
		Gtk.draw(pwidget.canvas)
	end
end
zoom_hfull(pwidget::PlotWidget) = zoom_full(pwidget, true, false)
zoom_vfull(pwidget::PlotWidget) = zoom_full(pwidget, false, true)

#==Box-zoom control
===============================================================================#
function boxzoom_setstart(pwidget::PlotWidget, x::Float64, y::Float64)
	locdir_any(pwidget)
	pwidget.sel.enabled = true
	pwidget.sel.bb = BoundingBox(x, x, y, y)
end
function boxzoom_cancel(pwidget::PlotWidget)
	pwidget.sel.enabled = false
	Gtk.draw(pwidget.canvas)
end
function boxzoom_complete(pwidget::PlotWidget, x::Float64, y::Float64)
	pwidget.sel.enabled = false
	bb = pwidget.sel.bb
	pwidget.sel.bb = BoundingBox(bb.xmin, x, bb.ymin, y)
	zoom(pwidget, pwidget.sel.bb)
end
#Set end point of boxzoom area:
function boxzoom_setend(pwidget::PlotWidget, x::Float64, y::Float64)
	bb = pwidget.sel.bb
	pwidget.sel.bb = BoundingBox(bb.xmin, x, bb.ymin, y)
	Gtk.draw(pwidget.canvas)
end

#==Basic pan control
===============================================================================#
function pan_xratio(pwidget::PlotWidget, panstepratio::Float64)
	ext = getextents_xfrm(pwidget.src)
	panstep = panstepratio*(ext.xmax-ext.xmin)
	setextents_xfrm(pwidget.src, PExtents2D(
		ext.xmin+panstep, ext.xmax+panstep,
		ext.ymin, ext.ymax
	))
	scalectrl_enabled(pwidget, false) #Scroll bar control no longer valid
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end
function pan_yratio(pwidget::PlotWidget, panstepratio::Float64)
	ext = getextents_xfrm(pwidget.src)
	panstep = panstepratio*(ext.ymax-ext.ymin)
	setextents_xfrm(pwidget.src, PExtents2D(
		ext.xmin, ext.xmax,
		ext.ymin+panstep, ext.ymax+panstep
	))
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end

pan_left(pwidget::PlotWidget) = pan_xratio(pwidget, -PAN_STEPRATIO)
pan_right(pwidget::PlotWidget) = pan_xratio(pwidget, PAN_STEPRATIO)
pan_up(pwidget::PlotWidget) = pan_yratio(pwidget, PAN_STEPRATIO)
pan_down(pwidget::PlotWidget) = pan_yratio(pwidget, -PAN_STEPRATIO)

#==Mouse-pan control
===============================================================================#
#Δy/Δy: in device coordinates
function mousepan_delta(pwidget::PlotWidget, ext::PExtents2D, Δx::Float64, Δy::Float64)
	#Convert to plot coordinates:
	xf = Transform2D(ext, pwidget.graphbb)
	Δvec = vecmap_rev(xf, Point2D(-Δx, -Δy))

	setextents_xfrm(pwidget.src, ext) #Restore original extents before overwriting
	setextents_xfrm(pwidget.src, PExtents2D(
		ext.xmin+Δvec.x, ext.xmax+Δvec.x,
		ext.ymin+Δvec.y, ext.ymax+Δvec.y),
		pwidget.hallowed, pwidget.vallowed
	)
	scalectrl_enabled(pwidget, false) #Scroll bar control no longer valid
	render(pwidget)
	Gtk.draw(pwidget.canvas)
end
function mousepan_setstart(pwidget::PlotWidget, x::Float64, y::Float64)
	locdir_any(pwidget)
	pwidget.sel.bb = BoundingBox(x, x, y, y) #Tracks start/end pos
	pwidget.sel.ext_start = getextents_xfrm(pwidget.src)
end
function mousepan_cancel(pwidget::PlotWidget)
	mousepan_delta(pwidget, pwidget.sel.ext_start, 0.0, 0.0)
end
function mousepan_complete(pwidget::PlotWidget, x::Float64, y::Float64)
	#Already panned.
end
#Set new point of mousepan operation:
function mousepan_move(pwidget::PlotWidget, x::Float64, y::Float64)
	bb = pwidget.sel.bb
	bb = BoundingBox(bb.xmin, x, bb.ymin, y)
	pwidget.sel.bb = bb
	Δx = bb.xmax-bb.xmin; Δy = bb.ymax-bb.ymin
	mousepan_delta(pwidget, pwidget.sel.ext_start, Δx, Δy)
end

#Last line


# using MPI
import MPI

MPI.Init()
rank = MPI.Comm_rank(MPI.COMM_WORLD)
size = MPI.Comm_size(MPI.COMM_WORLD)

manager = MPI.start_main_loop(MPI.MPI_TRANSPORT_ALL)

println("Hello from rank $rank")
println("Size $size")

@everywhere function print_cpus()
    run(pipeline(`ps -eo pid,psr,pcpu,cmd`, `grep julia`))
    run(`hostname`)
    # run(`echo $LD_LIBRARY_PATH`)
end

println("Before map")
print_cpus()

@everywhere function pcpus(mat)
    println("In map")
    print_cpus()
    svd(mat)
end

M = [rand(100,100) for i=1:size]

pmap(pcpus, M)

println("After map.")
print_cpus()

MPI.stop_main_loop(manager)


@testset "Parallel processing" begin

    W = addprocs(2)
    @everywhere using GigaSOM

    som = initGigaSOM(pbmc8_data, 10, 10, seed = 1234)

    @testset "Check SOM dimensions" begin
        @test size(som.codes) == (100, 10)
        @test som.xdim == 10
        @test som.ydim == 10
        @test som.numCodes == 100
    end

    som = trainGigaSOM(som, pbmc8_data, epochs = 2, rStart = 6.0)

    winners = mapToGigaSOM(som, pbmc8_data)

    embed = embedGigaSOM(som, pbmc8_data)

    @testset "Check results" begin
        codes = som.codes
        @test size(codes) == (100, 10)

        dfCodes = DataFrame(codes, :auto)
        rename!(dfCodes, Symbol.(antigens))
        dfEmbed = DataFrame(embed, :auto)
        CSV.write(genDataPath * "/parallelDfCodes.csv", dfCodes)
        CSV.write(genDataPath * "/parallelWinners.csv", winners)
        CSV.write(genDataPath * "/parallelEmbedded.csv", dfEmbed)

        # load the ref data
        refParallelDfCodes = CSV.File(refDataPath * "/refParallelDfCodes.csv") |> DataFrame
        refParallelWinners = CSV.File(refDataPath * "/refParallelWinners.csv") |> DataFrame
        refParallelEmbedded =
            CSV.File(refDataPath * "/refParallelEmbedded.csv") |> DataFrame

        # load the generated data
        parallelDfCodes = CSV.File(genDataPath * "/parallelDfCodes.csv") |> DataFrame
        parallelDfCodesTest = first(parallelDfCodes, 10)
        parallelWinners = CSV.File(genDataPath * "/parallelWinners.csv") |> DataFrame
        parallelWinnersTest = first(parallelWinners, 10)
        parallelEmbedded = CSV.File(genDataPath * "/parallelEmbedded.csv") |> DataFrame
        parallelEmbeddedTest = first(parallelEmbedded, 10)

        # test the generated data against the reference data
        @test refParallelWinners == parallelWinnersTest
        @test Matrix{Float64}(refParallelDfCodes) ≈ Matrix{Float64}(parallelDfCodesTest)
        @test Matrix{Float64}(refParallelEmbedded) ≈ Matrix{Float64}(parallelEmbeddedTest) atol =
            1e-4
    end

    rmprocs(W)

end


# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
# Since these FMAs can increase the performance of many numerical algorithms,
# we need to opt-in explicitly.
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
@muladd begin

# The methods below are specialized on the mortar type
# and called from the basic `create_cache` method at the top.
function create_cache(mesh::P4estMesh{3}, equations, mortar_l2::LobattoLegendreMortarL2, uEltype)
  # TODO: Taal compare performance of different types
  fstar_threaded = [Array{uEltype, 4}(undef, nvariables(equations), nnodes(mortar_l2), nnodes(mortar_l2), 4)
                    for _ in 1:Threads.nthreads()]

  fstar_tmp_threaded = [Array{uEltype, 3}(undef, nvariables(equations), nnodes(mortar_l2), nnodes(mortar_l2))
                         for _ in 1:Threads.nthreads()]
  u_threaded          = [Array{uEltype, 3}(undef, nvariables(equations), nnodes(mortar_l2), nnodes(mortar_l2))
                         for _ in 1:Threads.nthreads()]

  (; fstar_threaded, fstar_tmp_threaded, u_threaded)
end

#     index_to_start_step_3d(index::Symbol, index_range)
#
# Given a symbolic `index` and an `indexrange` (usually `eachnode(dg)`),
# return `index_start, index_step_i, index_step_j`, i.e., a tuple containing
# - `index_start`,   an index value to begin a loop
# - `index_step_i`,  an index step to update during an `i` loop
# - `index_step_j`,  an index step to update during a `j` loop
# The resulting indices translate surface indices to volume indices.
#
# !!! warning
#     This assumes that loops using the return values are written as
#
#     i_volume_start, i_volume_step_i, i_volume_step_j = index_to_start_step_3d(symbolic_index_i, index_range)
#     j_volume_start, j_volume_step_i, j_volume_step_j = index_to_start_step_3d(symbolic_index_j, index_range)
#     k_volume_start, k_volume_step_i, k_volume_step_j = index_to_start_step_3d(symbolic_index_k, index_range)
#
#     i_volume, j_volume, k_volume = i_volume_start, j_volume_start, k_volume_start
#     for j_surface in index_range
#       for i_surface in index_range
#         # do stuff with `(i_surface, j_surface)` and `(i_volume, j_volume, k_volume)`
#
#         i_volume += i_volume_step_i
#         j_volume += j_volume_step_i
#         k_volume += k_volume_step_i
#       end
#       i_volume += i_volume_step_j
#       j_volume += j_volume_step_j
#       k_volume += k_volume_step_j
#     end
@inline function index_to_start_step_3d(index::Symbol, index_range)
  index_begin = first(index_range)
  index_end   = last(index_range)

  if index === :begin
    return index_begin, 0, 0
  elseif index === :end
    return index_end, 0, 0
  elseif index === :i_forward
    return index_begin, 1, index_begin - index_end - 1
  elseif index === :i_backward
    return index_end, -1, index_end + 1 - index_begin
  elseif index === :j_forward
    return index_begin, 0, 1
  else # if index === :j_backward
    return index_end, 0, -1
  end
end

# Extract the two varying indices from a symbolic index tuple.
# For example, `surface_indices((:i_forward, :end, :j_forward)) == (:i_forward, :j_forward)`.
@inline function surface_indices(indices::NTuple{3, Symbol})
  i1, i2, i3 = indices
  index = i1
  (index === :begin || index === :end) && return (i2, i3)

  index = i2
  (index === :begin || index === :end) && return (i1, i3)

  # i3 in (:begin, :end)
  return (i1, i2)
end

function prolong2interfaces!(cache, u,
                             mesh::P4estMesh{3},
                             equations, surface_integral, dg::DG)
  @unpack interfaces = cache
  index_range = eachnode(dg)

  @threaded for interface in eachinterface(dg, cache)
    # Copy solution data from the primary element using "delayed indexing" with
    # a start value and two step sizes to get the correct face and orientation.
    # Note that in the current implementation, the interface will be
    # "aligned at the primary element", i.e., the indices of the primary side
    # will always run forwards.
    primary_element = interfaces.neighbor_ids[1, interface]
    primary_indices = interfaces.node_indices[1, interface]

    i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], index_range)
    j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], index_range)
    k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], index_range)

    i_primary = i_primary_start
    j_primary = j_primary_start
    k_primary = k_primary_start
    for j in eachnode(dg)
      for i in eachnode(dg)
        for v in eachvariable(equations)
          interfaces.u[1, v, i, j, interface] = u[v, i_primary, j_primary, k_primary, primary_element]
        end
        i_primary += i_primary_step_i
        j_primary += j_primary_step_i
        k_primary += k_primary_step_i
      end
      i_primary += i_primary_step_j
      j_primary += j_primary_step_j
      k_primary += k_primary_step_j
    end

    # Copy solution data from the secondary element using "delayed indexing" with
    # a start value and two step sizes to get the correct face and orientation.
    secondary_element = interfaces.neighbor_ids[2, interface]
    secondary_indices = interfaces.node_indices[2, interface]

    i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_indices[1], index_range)
    j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_indices[2], index_range)
    k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d(secondary_indices[3], index_range)

    i_secondary = i_secondary_start
    j_secondary = j_secondary_start
    k_secondary = k_secondary_start
    for j in eachnode(dg)
      for i in eachnode(dg)
        for v in eachvariable(equations)
          interfaces.u[2, v, i, j, interface] = u[v, i_secondary, j_secondary, k_secondary, secondary_element]
        end
        i_secondary += i_secondary_step_i
        j_secondary += j_secondary_step_i
        k_secondary += k_secondary_step_i
      end
      i_secondary += i_secondary_step_j
      j_secondary += j_secondary_step_j
      k_secondary += k_secondary_step_j
    end
  end

  return nothing
end

function calc_interface_flux!(surface_flux_values,
                              mesh::P4estMesh{3},
                              nonconservative_terms,
                              equations, surface_integral, dg::DG, cache)
  @unpack neighbor_ids, node_indices = cache.interfaces
  @unpack contravariant_vectors = cache.elements
  index_range = eachnode(dg)

  @threaded for interface in eachinterface(dg, cache)
    # Get element and side information on the primary element
    primary_element  = neighbor_ids[1, interface]
    primary_indices  = node_indices[1, interface]
    primary_direction = indices2direction(primary_indices)

    i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], index_range)
    j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], index_range)
    k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], index_range)

    i_primary = i_primary_start
    j_primary = j_primary_start
    k_primary = k_primary_start

    # Get element and side information on the secondary element
    secondary_element = neighbor_ids[2, interface]
    secondary_indices = node_indices[2, interface]
    secondary_direction = indices2direction(secondary_indices)
    secondary_surface_indices = surface_indices(secondary_indices)

    # Get the surface indexing on the secondary element.
    # Note that the indices of the primary side will always run forward but
    # the secondary indices might need to run backwards for flipped sides.
    i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1], index_range)
    j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2], index_range)
    i_secondary = i_secondary_start
    j_secondary = j_secondary_start

    for j in eachnode(dg)
      for i in eachnode(dg)
        # Get the normal direction from the primary element.
        # Note, contravariant vectors at interfaces in negative coordinate direction
        # are pointing inwards. This is handled by `get_normal_direction`.
        normal_direction = get_normal_direction(primary_direction, contravariant_vectors,
                                                i_primary, j_primary, k_primary,
                                                primary_element)

        calc_interface_flux!(surface_flux_values, mesh, nonconservative_terms, equations,
                             surface_integral, dg, cache,
                             interface, normal_direction,
                             i, j, primary_direction, primary_element,
                             i_secondary, j_secondary, secondary_direction, secondary_element)

        # Increment the primary element indices
        i_primary += i_primary_step_i
        j_primary += j_primary_step_i
        k_primary += k_primary_step_i
        # Increment the secondary element surface indices
        i_secondary += i_secondary_step_i
        j_secondary += j_secondary_step_i
      end
      # Increment the primary element indices
      i_primary += i_primary_step_j
      j_primary += j_primary_step_j
      k_primary += k_primary_step_j
      # Increment the secondary element surface indices
      i_secondary += i_secondary_step_j
      j_secondary += j_secondary_step_j
    end
  end

  return nothing
end

# Inlined function for interface flux computation for conservative flux terms
@inline function calc_interface_flux!(surface_flux_values,
                                      mesh::P4estMesh{3},
                                      nonconservative_terms::Val{false}, equations,
                                      surface_integral, dg::DG, cache,
                                      interface_index, normal_direction,
                                      primary_i_node_index, primary_j_node_index,
                                      primary_direction_index, primary_element_index,
                                      secondary_i_node_index, secondary_j_node_index,
                                      secondary_direction_index, secondary_element_index)
  @unpack u = cache.interfaces
  @unpack surface_flux = surface_integral

  u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_i_node_index, primary_j_node_index, interface_index)

  flux_ = surface_flux(u_ll, u_rr, normal_direction, equations)

  for v in eachvariable(equations)
    surface_flux_values[v, primary_i_node_index, primary_j_node_index,
                        primary_direction_index, primary_element_index] = flux_[v]
    surface_flux_values[v, secondary_i_node_index, secondary_j_node_index,
                        secondary_direction_index, secondary_element_index] = -flux_[v]
  end
end

# Inlined function for interface flux computation for flux + nonconservative terms
@inline function calc_interface_flux!(surface_flux_values,
                                      mesh::P4estMesh{3},
                                      nonconservative_terms::Val{true}, equations,
                                      surface_integral, dg::DG, cache,
                                      interface_index, normal_direction,
                                      primary_i_node_index, primary_j_node_index,
                                      primary_direction_index, primary_element_index,
                                      secondary_i_node_index, secondary_j_node_index,
                                      secondary_direction_index, secondary_element_index)
  @unpack u = cache.interfaces
  surface_flux, nonconservative_flux = surface_integral.surface_flux

  u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_i_node_index, primary_j_node_index, interface_index)

  flux_ = surface_flux(u_ll, u_rr, normal_direction, equations)

  # Compute both nonconservative fluxes
  # In general, nonconservative fluxes can depend on both the contravariant
  # vectors (normal direction) at the current node and the averaged ones.
  # However, both are the same at watertight interfaces, so we pass the
  # `normal_direction` twice.
  noncons_primary   = nonconservative_flux(u_ll, u_rr, normal_direction, normal_direction, equations)
  noncons_secondary = nonconservative_flux(u_rr, u_ll, normal_direction, normal_direction, equations)

  # Store the flux with nonconservative terms on the primary and secondary elements
  for v in eachvariable(equations)
    # Note the factor 0.5 necessary for the nonconservative fluxes based on
    # the interpretation of global SBP operators coupled discontinuously via
    # central fluxes/SATs
    surface_flux_values[v, primary_i_node_index, primary_j_node_index,
                        primary_direction_index, primary_element_index] = flux_[v] + 0.5 * noncons_primary[v]
    surface_flux_values[v, secondary_i_node_index, secondary_j_node_index,
                        secondary_direction_index, secondary_element_index] = -(flux_[v] + 0.5 * noncons_secondary[v])
  end
end

function prolong2boundaries!(cache, u,
                             mesh::P4estMesh{3},
                             equations, surface_integral, dg::DG)
  @unpack boundaries = cache
  index_range = eachnode(dg)

  @threaded for boundary in eachboundary(dg, cache)
    # Copy solution data from the element using "delayed indexing" with
    # a start value and two step sizes to get the correct face and orientation.
    element      = boundaries.neighbor_ids[boundary]
    node_indices = boundaries.node_indices[boundary]

    i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], index_range)
    j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], index_range)
    k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], index_range)

    i_node = i_node_start
    j_node = j_node_start
    k_node = k_node_start
    for j in eachnode(dg)
      for i in eachnode(dg)
        for v in eachvariable(equations)
          boundaries.u[v, i, j, boundary] = u[v, i_node, j_node, k_node, element]
        end
        i_node += i_node_step_i
        j_node += j_node_step_i
        k_node += k_node_step_i
      end
      i_node += i_node_step_j
      j_node += j_node_step_j
      k_node += k_node_step_j
    end
  end

  return nothing
end

function calc_boundary_flux!(cache, t, boundary_condition, boundary_indexing,
                             mesh::P4estMesh{3},
                             equations, surface_integral, dg::DG)
  @unpack boundaries = cache
  @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements
  @unpack surface_flux = surface_integral
  index_range = eachnode(dg)

  @threaded for local_index in eachindex(boundary_indexing)
    # Use the local index to get the global boundary index from the
    # pre-sorted list
    boundary = boundary_indexing[local_index]

    # Get information on the adjacent element, compute the surface fluxes,
    # and store them
    element       = boundaries.neighbor_ids[boundary]
    node_indices  = boundaries.node_indices[boundary]
    direction     = indices2direction(node_indices)

    i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], index_range)
    j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], index_range)
    k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], index_range)

    i_node = i_node_start
    j_node = j_node_start
    k_node = k_node_start
    for j in eachnode(dg)
      for i in eachnode(dg)
        # Extract solution data from boundary container
        u_inner = get_node_vars(boundaries.u, equations, dg, i, j, boundary)

        # Outward-pointing normal direction (not normalized)
        normal_direction = get_normal_direction(direction, contravariant_vectors,
                                                i_node, j_node, k_node, element)

        # Coordinates at boundary node
        x = get_node_coords(node_coordinates, equations, dg,
                            i_node, j_node, k_node, element)

        flux_ = boundary_condition(u_inner, normal_direction, x, t, surface_flux, equations)

        # Copy flux to element storage in the correct orientation
        for v in eachvariable(equations)
          surface_flux_values[v, i, j, direction, element] = flux_[v]
        end

        i_node += i_node_step_i
        j_node += j_node_step_i
        k_node += k_node_step_i
      end
      i_node += i_node_step_j
      j_node += j_node_step_j
      k_node += k_node_step_j
    end
  end
end

function prolong2mortars!(cache, u,
                          mesh::P4estMesh{3}, equations,
                          mortar_l2::LobattoLegendreMortarL2,
                          surface_integral, dg::DGSEM)
  @unpack fstar_tmp_threaded = cache
  @unpack neighbor_ids, node_indices = cache.mortars
  index_range = eachnode(dg)

  @threaded for mortar in eachmortar(dg, cache)
    # Copy solution data from the small elements using "delayed indexing" with
    # a start value and two step sizes to get the correct face and orientation.
    small_indices = node_indices[1, mortar]

    i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], index_range)
    j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], index_range)
    k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], index_range)

    for position in 1:4
      i_small = i_small_start
      j_small = j_small_start
      k_small = k_small_start
      element = neighbor_ids[position, mortar]
      for j in eachnode(dg)
        for i in eachnode(dg)
          for v in eachvariable(equations)
            cache.mortars.u[1, v, position, i, j, mortar] = u[v, i_small, j_small, k_small, element]
          end
          i_small += i_small_step_i
          j_small += j_small_step_i
          k_small += k_small_step_i
        end
        i_small += i_small_step_j
        j_small += j_small_step_j
        k_small += k_small_step_j
      end
    end

    # Buffer to copy solution values of the large element in the correct orientation
    # before interpolating
    u_buffer = cache.u_threaded[Threads.threadid()]
    # temporary buffer for projections
    fstar_tmp = fstar_tmp_threaded[Threads.threadid()]

    # Copy solution of large element face to buffer in the
    # correct orientation
    large_indices = node_indices[2, mortar]

    i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_indices[1], index_range)
    j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_indices[2], index_range)
    k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d(large_indices[3], index_range)

    i_large = i_large_start
    j_large = j_large_start
    k_large = k_large_start
    element = neighbor_ids[5, mortar]
    for j in eachnode(dg)
      for i in eachnode(dg)
        for v in eachvariable(equations)
          u_buffer[v, i, j] = u[v, i_large, j_large, k_large, element]
        end
        i_large += i_large_step_i
        j_large += j_large_step_i
        k_large += k_large_step_i
      end
      i_large += i_large_step_j
      j_large += j_large_step_j
      k_large += k_large_step_j
    end

    # Interpolate large element face data from buffer to small face locations
    multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, :, mortar),
                            mortar_l2.forward_lower,
                            mortar_l2.forward_lower,
                            u_buffer,
                            fstar_tmp)
    multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, :, mortar),
                            mortar_l2.forward_upper,
                            mortar_l2.forward_lower,
                            u_buffer,
                            fstar_tmp)
    multiply_dimensionwise!(view(cache.mortars.u, 2, :, 3, :, :, mortar),
                            mortar_l2.forward_lower,
                            mortar_l2.forward_upper,
                            u_buffer,
                            fstar_tmp)
    multiply_dimensionwise!(view(cache.mortars.u, 2, :, 4, :, :, mortar),
                            mortar_l2.forward_upper,
                            mortar_l2.forward_upper,
                            u_buffer,
                            fstar_tmp)
  end

  return nothing
end

function calc_mortar_flux!(surface_flux_values,
                           mesh::P4estMesh{3},
                           nonconservative_terms, equations,
                           mortar_l2::LobattoLegendreMortarL2,
                           surface_integral, dg::DG, cache)
  @unpack neighbor_ids, node_indices = cache.mortars
  @unpack contravariant_vectors = cache.elements
  @unpack fstar_threaded, fstar_tmp_threaded = cache
  index_range = eachnode(dg)

  @threaded for mortar in eachmortar(dg, cache)
    # Choose thread-specific pre-allocated container
    fstar = fstar_threaded[Threads.threadid()]
    fstar_tmp = fstar_tmp_threaded[Threads.threadid()]

    # Get index information on the small elements
    small_indices = node_indices[1, mortar]
    small_direction = indices2direction(small_indices)

    i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], index_range)
    j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], index_range)
    k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], index_range)

    for position in 1:4
      i_small = i_small_start
      j_small = j_small_start
      k_small = k_small_start
      element = neighbor_ids[position, mortar]
      for j in eachnode(dg)
        for i in eachnode(dg)
          # Get the normal direction on the small element.
          # Note, contravariant vectors at interfaces in negative coordinate direction
          # are pointing inwards. This is handled by `get_normal_direction`.
          normal_direction = get_normal_direction(small_direction, contravariant_vectors,
                                                  i_small, j_small, k_small, element)

          calc_mortar_flux!(fstar, mesh, nonconservative_terms, equations,
                            surface_integral, dg, cache,
                            mortar, position, normal_direction,
                            i, j)

          i_small += i_small_step_i
          j_small += j_small_step_i
          k_small += k_small_step_i
        end
        i_small += i_small_step_j
        j_small += j_small_step_j
        k_small += k_small_step_j
      end
    end

    # Buffer to interpolate flux values of the large element to before
    # copying in the correct orientation
    u_buffer = cache.u_threaded[Threads.threadid()]

    mortar_fluxes_to_elements!(surface_flux_values,
                               mesh, equations, mortar_l2, dg, cache,
                               mortar, fstar, u_buffer, fstar_tmp)
  end

  return nothing
end

# Inlined version of the mortar flux computation on small elements for conservation fluxes
@inline function calc_mortar_flux!(fstar,
                                   mesh::P4estMesh{3},
                                   nonconservative_terms::Val{false}, equations,
                                   surface_integral, dg::DG, cache,
                                   mortar_index, position_index, normal_direction,
                                   i_node_index, j_node_index)
  @unpack u = cache.mortars
  @unpack surface_flux = surface_integral

  u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, i_node_index, j_node_index, mortar_index)

  flux = surface_flux(u_ll, u_rr, normal_direction, equations)

  # Copy flux to buffer
  set_node_vars!(fstar, flux, equations, dg, i_node_index, j_node_index, position_index)
end

# Inlined version of the mortar flux computation on small elements for conservation fluxes
# with nonconservative terms
@inline function calc_mortar_flux!(fstar,
                                   mesh::P4estMesh{3},
                                   nonconservative_terms::Val{true}, equations,
                                   surface_integral, dg::DG, cache,
                                   mortar_index, position_index, normal_direction,
                                   i_node_index, j_node_index)
  @unpack u = cache.mortars
  surface_flux, nonconservative_flux = surface_integral.surface_flux

  u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, i_node_index, j_node_index, mortar_index)

  # Compute conservative flux
  flux = surface_flux(u_ll, u_rr, normal_direction, equations)

  # Compute nonconservative flux and add it to the flux scaled by a factor of 0.5 based on
  # the interpretation of global SBP operators coupled discontinuously via
  # central fluxes/SATs
  noncons = nonconservative_flux(u_ll, u_rr, normal_direction, normal_direction, equations)
  flux_plus_noncons = flux + 0.5 * noncons

  # Copy to buffer
  set_node_vars!(fstar, flux_plus_noncons, equations, dg, i_node_index, j_node_index, position_index)
end

@inline function mortar_fluxes_to_elements!(surface_flux_values,
                                            mesh::P4estMesh{3}, equations,
                                            mortar_l2::LobattoLegendreMortarL2,
                                            dg::DGSEM, cache, mortar, fstar, u_buffer, fstar_tmp)
  @unpack neighbor_ids, node_indices = cache.mortars
  index_range = eachnode(dg)

  # Copy solution small to small
  small_indices   = node_indices[1, mortar]
  small_direction = indices2direction(small_indices)

  for position in 1:4
    element = neighbor_ids[position, mortar]
    for j in eachnode(dg), i in eachnode(dg)
      for v in eachvariable(equations)
        surface_flux_values[v, i, j, small_direction, element] = fstar[v, i, j, position]
      end
    end
  end

  # Project small fluxes to large element.
  multiply_dimensionwise!(
    u_buffer,
    mortar_l2.reverse_lower, mortar_l2.reverse_lower,
    view(fstar, .., 1),
    fstar_tmp)
  add_multiply_dimensionwise!(
    u_buffer,
    mortar_l2.reverse_upper, mortar_l2.reverse_lower,
    view(fstar, .., 2),
    fstar_tmp)
  add_multiply_dimensionwise!(
    u_buffer,
    mortar_l2.reverse_lower, mortar_l2.reverse_upper,
    view(fstar, .., 3),
    fstar_tmp)
  add_multiply_dimensionwise!(
    u_buffer,
    mortar_l2.reverse_upper, mortar_l2.reverse_upper,
    view(fstar, .., 4),
    fstar_tmp)

  # The flux is calculated in the outward direction of the small elements,
  # so the sign must be switched to get the flux in outward direction
  # of the large element.
  # The contravariant vectors of the large element (and therefore the normal
  # vectors of the large element as well) are four times as large as the
  # contravariant vectors of the small elements. Therefore, the flux needs
  # to be scaled by a factor of 4 to obtain the flux of the large element.
  u_buffer .*= -4

  # Copy interpolated flux values from buffer to large element face in the
  # correct orientation.
  # Note that the index of the small sides will always run forward but
  # the index of the large side might need to run backwards for flipped sides.
  large_element = neighbor_ids[5, mortar]
  large_indices  = node_indices[2, mortar]
  large_direction = indices2direction(large_indices)
  large_surface_indices = surface_indices(large_indices)

  i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_surface_indices[1], index_range)
  j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_surface_indices[2], index_range)

  # Note that the indices of the small sides will always run forward but
  # the large indices might need to run backwards for flipped sides.
  i_large = i_large_start
  j_large = j_large_start
  for j in eachnode(dg)
    for i in eachnode(dg)
      for v in eachvariable(equations)
        surface_flux_values[v, i_large, j_large, large_direction, large_element] = u_buffer[v, i, j]
      end
      i_large += i_large_step_i
      j_large += j_large_step_i
    end
    i_large += i_large_step_j
    j_large += j_large_step_j
  end

  return nothing
end

function calc_surface_integral!(du, u,
                                mesh::P4estMesh{3},
                                equations,
                                surface_integral::SurfaceIntegralWeakForm,
                                dg::DGSEM, cache)
  @unpack boundary_interpolation = dg.basis
  @unpack surface_flux_values = cache.elements

  # Note that all fluxes have been computed with outward-pointing normal vectors.
  # Access the factors only once before beginning the loop to increase performance.
  # We also use explicit assignments instead of `+=` to let `@muladd` turn these
  # into FMAs (see comment at the top of the file).
  factor_1 = boundary_interpolation[1,          1]
  factor_2 = boundary_interpolation[nnodes(dg), 2]
  @threaded for element in eachelement(dg, cache)
    for m in eachnode(dg), l in eachnode(dg)
      for v in eachvariable(equations)
        # surface at -x
        du[v, 1,          l, m, element] = (
          du[v, 1,          l, m, element] + surface_flux_values[v, l, m, 1, element] * factor_1)

        # surface at +x
        du[v, nnodes(dg), l, m, element] = (
          du[v, nnodes(dg), l, m, element] + surface_flux_values[v, l, m, 2, element] * factor_2)

        # surface at -y
        du[v, l, 1,          m, element] = (
          du[v, l, 1,          m, element] + surface_flux_values[v, l, m, 3, element] * factor_1)

        # surface at +y
        du[v, l, nnodes(dg), m, element] = (
          du[v, l, nnodes(dg), m, element] + surface_flux_values[v, l, m, 4, element] * factor_2)

        # surface at -z
        du[v, l, m, 1,          element] = (
          du[v, l, m, 1,          element] + surface_flux_values[v, l, m, 5, element] * factor_1)

        # surface at +z
        du[v, l, m, nnodes(dg), element] = (
          du[v, l, m, nnodes(dg), element] + surface_flux_values[v, l, m, 6, element] * factor_2)
      end
    end
  end

  return nothing
end

end # @muladd


"""
Returns a Dict corresponding to the results of querying `service`, where

## Parameters
* `service`: one of the Google Maps API (e.g. `geocode` or `directions`)

## Keyword Arguments
Often corresponds to parameters that are passed to the REST-ful API.
"""
function requestjson(
        service::String,
        api::String = "https://maps.googleapis.com/maps/api/",
        ext::String = "/json";
        kwargs...
    )
    #result = Requests.get(string(api,service,ext); query = Dict(kwargs))
    result = HTTP.request("GET",string(api,service,ext); query = Dict(kwargs))
    #resultjson = JSON.parse(Requests.readstring(result))
    resultjson = JSON.parse(String(result.body))
    #info("REQUEST:{\"uri\":\"", result.request.value.uri, "\", ",
    #              "\"status\": \"", Requests.statuscode(result), "\"}")
    #Requests.statuscode(result) == 400 && warn("ERROR:",resultjson["error"])
    resultjson
end

function requestimage(service::String; kwargs...)
    result = Requests.get(
        "https://maps.googleapis.com/maps/api/$service"; query = Dict(kwargs)
    )
    info("REQUEST:{\"uri\":\"", result.request.value.uri, "\", ",
                  "\"status\": \"", Requests.statuscode(result), "\"}")
    ImageMagick.readblob(result.data)
end

"""
Decodes a sequence of `lonlats` encoded in the polyline algorithm format.

See https://developers.google.com/maps/documentation/utilities/polylinealgorithm
for details.
"""
function decodepolyline(polyline::Vector{UInt8})
    function f(polyline, index)
        x = shift = 0
        while true
            @inbounds block = polyline[index] - 63 # 63 == Int('?')
            x |= (block & 0x1f) << shift
            index += 1; shift += 5
            block >= 0x20 || break
        end
        index, (x & 1) == 1 ? ~(x >> 1) : (x >> 1)
    end

    index = 1; currlat = currlon = 0
    lonlat = Tuple{Float64,Float64}[]
    polylength = length(polyline)
    while index < polylength
        index, dlat = f(polyline, index)
        index, dlon = f(polyline, index)
        currlat += dlat; currlon += dlon
        push!(lonlat, (currlon / 1e5, currlat / 1e5))
    end
    lonlat
end

decodepolyline(s::String) = decodepolyline(Vector{UInt8}(s))


"""
	write(io::IO, arg::ARG)
	write(filename::AbstractString, arg::ARG)

Write `arg` using the extended Newick format.
"""
write(filename::AbstractString, arg::ARG) = write(filename, extended_newick(arg))
write(io::IO, arg::ARG) = write(io, extended_newick(arg))

#=
Strategy
(i) If the global root is shared
Go recursively down this root and write a newick.
If a hybrid node is met for the first time, store it in a list of hybrid nodes met
If it is met for the second time, stop recursing down
(ii) none of the roots are shared
feed the function with each root in turn (and the list of hybrids)
(iii) One of the root is shared, not the other one
start with the non shared one. should work allright
=#
function extended_newick(arg::ARG)
	hybrids = Dict()

	if arg.roots[1] == arg.roots[2]
		# (i)
		str = extended_newick!(arg.roots[1], nothing, hybrids)
		return str * ";"
	else
		if !isshared(arg.roots[1]) && !isshared(arg.roots[2])
			#(ii)
			str1 = extended_newick!(arg.roots[1], nothing, hybrids)
			str2 = extended_newick!(arg.roots[2], nothing, hybrids)
			return "($(str1),$(str2))GlobalRoot:0.;"
		elseif isshared(arg.roots[1])
			#(iii)
			str = extended_newick!(arg.roots[2], nothing, hybrids)
			return str * ";"
		elseif isshared(arg.roots[2])
			str = extended_newick!(arg.roots[1], nothing, hybrids)
			return str * ";"
		end
	end
end

function extended_newick!(a_r::ARGNode, anc, hybrids::Dict)
	# println("$(label(a_r))")
	nwk = ""
	# Dealing with children of a_r
	if !isleaf(a_r) && !haskey(hybrids, label(a_r))
		nwk *= "("
		for c in children(a_r)
			nwk *= extended_newick!(c, a_r, hybrids)
			nwk *= ","
		end
		nwk = nwk[1:end-1] # Removing trailing ','
		nwk *= ")"
	end
	# Adding a_r to hybrid if necessary
	if ishybrid(a_r) && !haskey(hybrids, label(a_r))
		i = length(hybrids) +1
		hybrids[label(a_r)] = "#H$(i)"
	end
	# Writing a_r itself
	a_r_label = if ishybrid(a_r)
		label(a_r) * hybrids[label(a_r)]
	else
		label(a_r)
	end
	nwk *= a_r_label
	nwk *= nwk_node_data(a_r, anc)

	return nwk
end

"""
	nwk_node_data(n::ARGNode, a)

Return a string of the form "[&segments=0,1]:$(time)".

Different cases to choose from:
	(i) Node is shared, not a hybrid, not the root of only one tree
  its two times should be the same
  [&segments={0,1}]
	(ii) Node is not shared
  It should only have one color
  [&segments={c}]
	(iii) Node is shared and a hybrid
  We need to know what the color of the branch we arrived from is
  [&segments={c}]
	(iv) Node is the root of one tree and not of the other
  If `c` is the non-root color
  [&segments={c}]
"""
function nwk_node_data(n::ARGNode, a)
	if isshared(n) && !ishybrid(n) && !is_partial_root(n)
		# (i)
		τ1 = branch_length(n)[1]
		τ2 = branch_length(n)[2]
		@assert (ismissing(τ1) && ismissing(τ2)) || isapprox(τ1, τ2)
		return nwk_node_data(τ1, 1, 2)

	elseif isshared(n) && ishybrid(n)
		#(iii)
		ec = edgecolor(a, n)
		if ec[1] && !ec[2]
			return nwk_node_data(branch_length(n)[1], 1)
		elseif !ec[1] && ec[2]
			return nwk_node_data(branch_length(n)[2], 2)
		elseif ec[1] && ec[2]
			@assert isapprox(branch_length(n)[1], branch_length(n)[2])
			return nwk_node_data(branch_length(n)[1], 1, 2)
		end

	elseif isshared(n) && isroot(n)
		# (iv)
		for c in 1:2
			if !isroot(n, c)
				@assert isroot(n, othercolor(c))
				return nwk_node_data(branch_length(n)[c], c)
			end
		end
		@error "$n"
	elseif !isshared(n)
		# (ii)
		for c in 1:2
			if hascolor(n, c)
				@assert !hascolor(n, othercolor(c))
				return nwk_node_data(branch_length(n)[c], c)
			end
		end
		@error "No color for node $n"
	else
		@error "Unclear state for node $n"
	end
end

function nwk_node_data(τ, c::Int)
	ismissing(τ) ? "[&segments={$(c-1)}]" : "[&segments={$(c-1)}]:$(τ)"
end
function nwk_node_data(τ, c1::Int, c2::Int)
	clr = "$(min(c1,c2)-1),$(max(c1,c2)-1)"
	ismissing(τ) ? "[&segments={$(clr)}]" : "[&segments={$(clr)}]:$(τ)"
end


"""
    DecisionRef <: AbstractVariableRef

Holds a reference to the model, the stage the decision is taken in, and the corresponding MOI.VariableIndex.
"""
struct DecisionRef <: JuMP.AbstractVariableRef
    model::JuMP.Model
    index::MOI.VariableIndex
end
is_decision_type(::Type{DecisionRef}) = true

# Getters (model) #
# ========================== #
function get_decisions(model::JuMP.Model, s::Integer = 1)::Decisions
    !haskey(model.ext, :decisions) && return IgnoreDecisions()
    N = length(model.ext[:decisions])
    1 <= s <= N || error("Stage $s not in range 1 to $N.")
    return model.ext[:decisions][s]
end

function all_decisions(model::JuMP.Model, s::Integer = 1)
    decisions = get_decisions(model, s)::Decisions
    return all_decisions(decisions)
end

function all_known_decisions(model::JuMP.Model, s::Integer = 2)
    decisions = get_decisions(model, s)::Decisions
    return all_known_decisions(decisions)
end
"""
    all_decision_variables(model::JuMP.Model)

Returns a stage-wise list of all decisions currently in the `model`. The decisions are
ordered by creation time.
"""
function all_decision_variables(model::JuMP.Model)
    haskey(model.ext, :decisions) || error("No decisions in model.")
    N = length(model.ext[:decisions])
    return ntuple(Val{N}()) do stage
        return all_decision_variables(model, stage)
    end
end
"""
    all_decision_variables(model::JuMP.Model, stage::Integer)

Returns a list of all decisions currently in the `model` at stage `stage`. The decisions are
ordered by creation time.
"""
function all_decision_variables(model::JuMP.Model, stage::Integer)
    haskey(model.ext, :decisions) || error("No decisions in model.")
    decisions = get_decisions(model, stage)::Decisions
    return map(all_decisions(decisions)) do index
        DecisionRef(model, index)
    end
end
"""
    all_known_decision_variables(model::JuMP.Model)

Returns a stage-wise list of all known decisions currently in the `model`. The decisions are
ordered by creation time.
"""
function all_known_decision_variables(model::JuMP.Model)
    haskey(model.ext, :decisions) || error("No decisions in model.")
    N = length(model.ext[:decisions])
    return ntuple(Val{N}()) do s
        return all_known_decision_variables(model, s)
    end
end
"""
    all_known_decision_variables(model::JuMP.Model, stage::Integer)

Returns a stage-wise list of all known decisions currently in the `model` at stage `stage`. The decisions are
ordered by creation time.
"""
function all_known_decision_variables(model::JuMP.Model, stage::Integer)
    haskey(model.ext, :decisions) || error("No decisions in model.")
    decisions = get_decisions(model, stage)::Decisions
    return map(all_known_decisions(decisions)) do index
        DecisionRef(model, index)
    end
end
"""
    num_decisions(model::JuMP.Model, stage::Integer = 1)

Return the number of decisions in `model` at stage `stage`. Defaults to the first stage.
"""
function num_decisions(model::JuMP.Model, stage::Integer = 1)
    decisions = get_decisions(model, stage)::Decisions
    return num_decisions(decisions)
end
"""
    num_known_decisions(model::JuMP.Model, stage::Integer = 2)

Return the number of known decisions in `model` at stage `stage`. Defaults to the second stage.
"""
function num_known_decisions(model::JuMP.Model, stage::Integer = 2)
    stage > 1 || error("No decisions can be known in the first stage.")
    decisions = get_decisions(model, stage - 1)::Decisions
    return num_known_decisions(decisions)
end

# Getters (refs) #
# ========================== #
function stage(dref::DecisionRef)
    haskey(dref.model.ext, :stage_map) || error("No decisions in model.")
    return dref.model.ext[:stage_map][index(dref)]
end

function get_decisions(dref::DecisionRef)::Decisions
    s = stage(dref)
    s == 0 && return IgnoreDecisions()
    return get_decisions(dref.model, s)
end
"""
    decision(dref::DecisionRef)

Return the internal `Decision` associated with `dref`.
"""
function decision(dref::DecisionRef)
    decisions = get_decisions(dref)::Decisions
    return decision(decisions, index(dref))
end
"""
    state(dref::DecisionRef)

Return the `DecisionState` of `dref`.
"""
function state(dref::DecisionRef)
    return decision(dref).state
end

# Setters #
# ========================== #
function take_decisions!(model::JuMP.Model, drefs::Vector{DecisionRef}, vals::AbstractVector)
    # Check that all given decisions are in model
    map(dref -> check_belongs_to_model(dref, model), drefs)
    # Check decision length
    length(drefs) == length(vals) || error("Given decision of length $(length(vals)) not compatible with number of decision variables $(length(drefs)).")
    # Update decisions
    for (dref, val) in zip(drefs, vals)
        d = decision(dref)
        # Update state
        d.state = Taken
        # Update value
        d.value = val
        # Update
        update_decision_state!(dref, Taken)
    end
    return nothing
end

function untake_decisions!(model::JuMP.Model, drefs::Vector{DecisionRef})
    # Check that all given decisions are in model
    map(dref -> check_belongs_to_model(dref, model), drefs)
    # Update decisions
    need_update = false
    for dref in drefs
        d = decision(dref)
        if state(d) == Taken
            need_update |= true
            # Update state
            d.state = NotTaken
            update_decision_state!(dref, NotTaken)
        end
    end
    return nothing
end

# JuMP variable interface #
# ========================== #
function MOI.get(model::JuMP.Model, attr::MOI.AbstractVariableAttribute,
                 dref::DecisionRef)
    check_belongs_to_model(dref, model)
    if MOI.is_set_by_optimize(attr)
        return JuMP._moi_get_result(backend(model), attr, index(dref))
    else
        return MOI.get(backend(model), attr, index(dref))
    end
end

function MOI.set(model::Model, attr::MOI.AbstractVariableAttribute,
                 dref::DecisionRef, value)
    check_belongs_to_model(dref, model)
    MOI.set(backend(model), attr, index(dref), value)
    return nothing
end

JuMP.name(dref::DecisionRef) = MOI.get(owner_model(dref), MOI.VariableName(), dref)::String

function JuMP.set_name(dref::DecisionRef, name::String)
    return MOI.set(owner_model(dref), MOI.VariableName(), dref, name)
end

function decision_by_name(model::Model, name::String)
    index = MOI.get(backend(model), MOI.VariableIndex, name)
    if index isa Nothing
        return nothing
    else
        return DecisionRef(model, index)
    end
end

JuMP.index(dref::DecisionRef) = dref.index

function JuMP.value(dref::DecisionRef; result::Int = 1)::Float64
    if state(dref) == NotTaken
        return MOI.get(owner_model(dref), MOI.VariablePrimal(result), dref)
    end
    # If decision has been taken or is known
    # the value can be fetched directly
    return decision(dref).value
end

function JuMP.is_fixed(dref::DecisionRef)
    if state(dref) == NotTaken
        return false
    end
    return true
end

function JuMP.unfix(dref::DecisionRef)
    if state(dref) == NotTaken
        # Nothing to do, just return
        return nothing
    end
    d = decision(dref)
    # Update state
    d.state = NotTaken
    # Update objective and constraints
    update_decision_state!(dref, NotTaken)
    return nothing
end

"""
    fix(dref::DecisionRef, val::Number)

Fix the decision associated with `dref` to `val`.

See also [`unfix`](@ref).
"""
function JuMP.fix(dref::DecisionRef, val::Number)
    d = decision(dref)
    if state(dref) == NotTaken
        # Update state
        d.state = Taken
        # Update value
        d.value = val
    else
        # Just update value
        d.value = val
    end
    # Modify decision state
    update_decision_state!(dref, d.state)
    return nothing
end

function JuMP.delete(model::JuMP.Model, dref::DecisionRef)
    if model !== owner_model(dref)
        error("The decision you are trying to delete does not " *
              "belong to the model.")
    end
    # First delete any SingleDecision constraints
    for S in [MOI.GreaterThan{Float64}, MOI.LessThan{Float64}, MOI.EqualTo{Float64}, MOI.ZeroOne, MOI.Integer]
        ci = CI{SingleDecision,S}(index(dref).value)
        if MOI.is_valid(backend(model), ci)
            MOI.delete(backend(model), ci)
        end
    end
    # Remove SingleDecisionSet constraint
    ci = CI{MOI.SingleVariable,SingleDecisionSet{Float64}}(index(dref).value)
    MOI.delete(backend(model), ci)
    # Delete the variable corresponding to the decision
    MOI.delete(backend(model), index(dref))
    # Remove the decision
    remove_decision!(get_decisions(dref), index(dref))
    return nothing
end

function JuMP.delete(model::JuMP.Model, drefs::Vector{DecisionRef})
    isempty(drefs) && return nothing
    if any(model !== owner_model(dref) for dref in drefs)
        error("A decision you are trying to delete does not " *
              "belong to the model.")
    end
    # First delete any SingleDecision constraints
    for dref in drefs
        for S in [MOI.GreaterThan{Float64}, MOI.LessThan{Float64}, MOI.EqualTo{Float64}, MOI.ZeroOne, MOI.Integer]
            ci = CI{SingleDecision,S}(index(dref).value)
            if MOI.is_valid(backend(model), ci)
                MOI.delete(backend(model), ci)
            end
        end
    end
    # Delete the variables corresponding to the decision
    MOI.delete(backend(model), index.(drefs))
    # Remove any MultipleDecisionSet constraint
    for ci in MOI.get(backend(model), MOI.ListOfConstraintIndices{MOI.VectorOfVariables, MultipleDecisionSet{Float64}}())
        f = MOI.get(backend(model), MOI.ConstraintFunction(), ci)::MOI.VectorOfVariables
        if all(f.variables .== index.(drefs))
            # This is the constraint
            MOI.delete(backend(model), ci)
            break
        end
    end
    # Remove any SingleDecisionSet constraints
    for dref in drefs
        ci = CI{MOI.SingleVariable,SingleDecisionSet{Float64}}(index(dref).value)
        if MOI.is_valid(backend(model), ci)
            MOI.delete(backend(model), ci)
        end
    end
    # Remove the decisions
    map(dref -> remove_decision!(get_decisions(dref), index(dref)), drefs)
    return nothing
end

JuMP.owner_model(dref::DecisionRef) = dref.model

struct DecisionNotOwned <: Exception
    dref::DecisionRef
end

function JuMP.check_belongs_to_model(dref::DecisionRef, model::AbstractModel)
    if owner_model(dref) !== model
        throw(DecisionNotOwned(dref))
    end
end

function JuMP.is_valid(model::Model, dref::DecisionRef)
    return model === owner_model(dref)
end

function JuMP.has_lower_bound(dref::DecisionRef)
    if state(dref) == Known
        return false
    end
    ci = MOI.ConstraintIndex{SingleDecision, MOI.GreaterThan{Float64}}(index(dref).value)
    return MOI.is_valid(backend(owner_model(dref)), ci)
end
function JuMP.LowerBoundRef(dref::DecisionRef)
    moi_lb =  MOI.ConstraintIndex{SingleDecision, MOI.GreaterThan{Float64}}
    ci = moi_lb(index(dref).value)
    return ConstraintRef{JuMP.Model, moi_lb, ScalarShape}(owner_model(dref),
                                                          ci,
                                                          ScalarShape())
end
function JuMP.set_lower_bound(dref::DecisionRef, lower::Number)
    new_set = MOI.GreaterThan(convert(Float64, lower))
    if has_lower_bound(dref)
        ci = MOI.ConstraintIndex{SingleDecision, MOI.GreaterThan{Float64}}(index(dref).value)
        MOI.set(backend(owner_model(dref)), MOI.ConstraintSet(), ci, new_set)
    else
        MOI.add_constraint(backend(owner_model(dref)), SingleDecision(index(dref)), new_set)
    end
    return nothing
end
function JuMP.delete_lower_bound(dref::DecisionRef)
    JuMP.delete(owner_model(dref), LowerBoundRef(dref))
end
function JuMP.lower_bound(dref::DecisionRef)
    if !has_lower_bound(dref)
        error("Decision $(dref) does not have a lower bound.")
    end
    cset = MOI.get(owner_model(dref), MOI.ConstraintSet(),
                   LowerBoundRef(dref))::MOI.GreaterThan{Float64}
    return cset.lower
end

function JuMP.has_upper_bound(dref::DecisionRef)
    if state(dref) == Known
        return false
    end
    ci = MOI.ConstraintIndex{SingleDecision, MOI.LessThan{Float64}}(index(dref).value)
    return MOI.is_valid(backend(owner_model(dref)), ci)
end
function JuMP.UpperBoundRef(dref::DecisionRef)
    moi_ub =  MOI.ConstraintIndex{SingleDecision, MOI.LessThan{Float64}}
    ci = moi_ub(index(dref).value)
    return ConstraintRef{JuMP.Model, moi_ub, ScalarShape}(owner_model(dref),
                                                          ci,
                                                          ScalarShape())
end
function JuMP.set_upper_bound(dref::DecisionRef, lower::Number)
    new_set = MOI.LessThan(convert(Float64, lower))
    if has_upper_bound(dref)
        ci = MOI.ConstraintIndex{SingleDecision, MOI.LessThan{Float64}}(index(dref).value)
        MOI.set(backend(owner_model(dref)), MOI.ConstraintSet(), ci, new_set)
    else
        MOI.add_constraint(backend(owner_model(dref)), SingleDecision(index(dref)), new_set)
    end
    return nothing
end
function JuMP.delete_upper_bound(dref::DecisionRef)
    JuMP.delete(owner_model(dref), UpperBoundRef(dref))
end
function JuMP.upper_bound(dref::DecisionRef)
    if !has_upper_bound(dref)
        error("Decision $(dref) does not have a upper bound.")
    end
    cset = MOI.get(owner_model(dref), MOI.ConstraintSet(),
                   UpperBoundRef(dref))::MOI.LessThan{Float64}
    return cset.upper
end

function JuMP.is_integer(dref::DecisionRef)
    if state(dref) == Known
        return false
    end
    ci = MOI.ConstraintIndex{SingleDecision, MOI.Integer}(index(dref).value)
    return MOI.is_valid(backend(owner_model(dref)), ci)
end
function JuMP.set_integer(dref::DecisionRef)
    if is_integer(dref)
        return nothing
    elseif is_binary(dref)
        error("Cannot set the decision $(dref) to integer as it is already binary.")
    else
        MOI.add_constraint(backend(owner_model(dref)), SingleDecision(index(dref)), MOI.Integer())
    end
end
function JuMP.unset_integer(dref::DecisionRef)
    JuMP.delete(owner_model(dref), IntegerRef(dref))
    return nothing
end
function JuMP.IntegerRef(dref::DecisionRef)
    moi_int =  MOI.ConstraintIndex{SingleDecision, MOI.Integer}
    ci = moi_int(index(dref).value)
    return ConstraintRef{JuMP.Model, moi_int, ScalarShape}(owner_model(dref),
                                                           ci,
                                                           ScalarShape())
end

function JuMP.is_binary(dref::DecisionRef)
    if state(dref) == Known
        return false
    end
    ci = MOI.ConstraintIndex{SingleDecision, MOI.ZeroOne}(index(dref).value)
    return MOI.is_valid(backend(owner_model(dref)), ci)
end
function JuMP.set_binary(dref::DecisionRef)
    if is_binary(dref)
        return nothing
    elseif is_integer(dref)
        error("Cannot set the decision $(dref) to binary as it is already integer.")
    else
        MOI.add_constraint(backend(owner_model(dref)), SingleDecision(index(dref)), MOI.ZeroOne())
    end
end
function JuMP.unset_binary(dref::DecisionRef)
    JuMP.delete(owner_model(dref), BinaryRef(dref))
    return nothing
end
function JuMP.BinaryRef(dref::DecisionRef)
    moi_bin =  MOI.ConstraintIndex{SingleDecision, MOI.ZeroOne}
    ci = moi_bin(index(dref).value)
    return ConstraintRef{JuMP.Model, moi_bin, ScalarShape}(owner_model(dref),
                                                           ci,
                                                           ScalarShape())
end

function JuMP.start_value(dref::DecisionRef)
    return MOI.get(owner_model(dref), MOI.VariablePrimalStart(), dref)
end
function JuMP.set_start_value(dref::DecisionRef, value::Number)
    MOI.set(owner_model(dref), MOI.VariablePrimalStart(), dref, Float64(value))
end

function Base.hash(dref::DecisionRef, h::UInt)
    return hash(objectid(owner_model(dref)), hash(dref.index, h))
end

JuMP.isequal_canonical(d::DecisionRef, other::DecisionRef) = isequal(d, other)
function Base.isequal(dref::DecisionRef, other::DecisionRef)
    return owner_model(dref) === owner_model(other) && dref.index == other.index
end

Base.iszero(::DecisionRef) = false
Base.copy(dref::DecisionRef) = DecisionRef(dref.model, dref.index)
Base.broadcastable(dref::DecisionRef) = Ref(dref)

# JuMP copy interface #
# ========================== #
function JuMP.copy_extension_data(decisions::NTuple{N,Decisions}, dest::Model, src::Model) where N
    new_decisions = ntuple(Val{N}()) do _
        Decisions()
    end
    for s in 1:N
        for dref in all_decision_variables(src, s)
            set_decision!(new_decisions[s], index(dref), decision(dref))
        end
        # TODO
        for kref in all_known_decision_variables(src, s)
            set_known_decision!(new_decisions[s], index(kref), decision(kref))
        end
    end
    return new_decisions
end

function Base.getindex(reference_map::JuMP.ReferenceMap, dref::DecisionRef)
    return DecisionRef(reference_map.model,
                       reference_map.index_map[index(dref)])
end


#!/bin/env julia

#Base.compilecache(GP)

#include("./GP.jl")
import GP
#using GP

if length(ARGS) < 2
    println("give me input file names")
    exit(0)
end

input = ARGS

@time GP.process(input[1])
@time GP.process(input[2])


type RacosClassification
    solution_space
    sample_region
    label
    positive_solution
    negative_solution
    x_positive
    uncertain_bit

    function RacosClassification(dim, positive, negative; ub=1)
        solution_space = dim
        sample_region = []
        label = []
        # solution
        positive_solution = positive
        negative_solution = negative
        x_positive = Nullable()
        uncertain_bit = ub

        regions = dim.regions
        for i in 1:dim.size
            if dim.types[i] == true
                temp = [convert(Float64, regions[i][1]), convert(Float64, regions[i][2])]
            else
                temp = [regions[i][1], regions[i][2]]
            end
            push!(sample_region, temp)
            push!(label, false)
        end
        return new(solution_space, sample_region, label, positive_solution,
          negative_solution, x_positive, uncertain_bit)
    end
end

function reset_classifier(classifier)
    regions = classfier.solution_space.regions
    for i in 1:classifier.solution_space.size
        classifier.sample_region[i][0] = regions[i][0]
        classifier.sample_region[i][1] = regions[i][1]
        classifier.label[i] = false
    end
    classifier.x_positive = Nullable()
end

# This algos always works, whether discrete or continuous, we always use this function.
function mixed_classification(classifier)
    classifier.x_positive = classifier.positive_solution[rand(rng,
      1:length(classifier.positive_solution))]
    len_negative = length(classifier.negative_solution)
    index_set = Array(1:classifier.solution_space.size)
    types = classifier.solution_space.types
    while len_negative > 0
        pos = rand(rng, 1:length(index_set))
        k = index_set[pos]
        x_pos_k = classifier.x_positive.x[k]
        # continuous
        if types[k] == true
            x_negative = classifier.negative_solution[rand(rng, 1:len_negative)]
            x_neg_k = x_negative.x[k]
            if x_pos_k < x_neg_k
                r = rand_uniform(rng, x_pos_k, x_neg_k)
                if r < classifier.sample_region[k][2]
                    classifier.sample_region[k][2] = r
                    i = 1
                    while i <= len_negative
                        if classifier.negative_solution[i].x[k] >= r
                            itemp = classifier.negative_solution[i]
                            classifier.negative_solution[i] = classifier.negative_solution[len_negative]
                            classifier.negative_solution[len_negative] = itemp
                            len_negative -= 1
                        else
                            i += 1
                        end
                    end
                end
            else
                r = rand_uniform(rng, x_neg_k, x_pos_k)
                if r > classifier.sample_region[k][1]
                    classifier.sample_region[k][1] = r
                    i = 1
                    while i <= len_negative
                        if classifier.negative_solution[i].x[k] <= r
                            itemp = classifier.negative_solution[i]
                            classifier.negative_solution[i] = classifier.negative_solution[len_negative]
                            classifier.negative_solution[len_negative] = itemp
                            len_negative -= 1
                        else
                            i += 1
                        end
                    end
                end
            end
            # discrete
        else
            delete = 0
            i = 1
            while i <= len_negative
                if classifier.negative_solution[i].x[k] != x_pos_k
                    delete += 1
                    itemp = classifier.negative_solution[i]
                    classifier.negative_solution[i] = classifier.negative_solution[len_negative]
                    classifier.negative_solution[len_negative] = itemp
                    len_negative -= 1
                else
                    i += 1
                end
            end
            if delete != 0
                splice!(index_set, pos)
            end
            if length(index_set) == 0
                push!(index_set, k)
            end
        end
    end
    set_uncertain_bit!(classifier, index_set)
end

function set_uncertain_bit!(classifier, iset)
    index_set = iset
    for i in 1:classifier.uncertain_bit
        pos = rand(rng, 1:length(index_set))
        index = index_set[pos]
        classifier.label[index] = true
        splice!(index_set, pos)
    end
end

function rand_sample(classifier)
    x = []
    for i in 1:classifier.solution_space.size
        if classifier.label[i] == true
            if classifier.solution_space.types[i] == true
                push!(x, rand_uniform(rng, classifier.sample_region[i][1], classifier.sample_region[i][2]))
            else
                push!(x, rand(rng, classifier.sample_region[i][1]:classifier.sample_region[i][2]))
            end
        else
            push!(x, classifier.x_positive.x[i])
        end
    end
    return x
end

# for dubugging
function print_neg(classifier)
    zoolog("------print neg------")
    for x in classifier.negative_solution
        sol_print(x)
    end
end

function print_pos(classifier)
    zoolog("------print pos------")
    for x in classifier.positive_solution
        sol_print(x)
    end
end

function print_sample_region(classifier)
    zoolog("------print sample region------")
    zoolog(classifier.sample_region)
end


@testset "Default continuous post-operator" begin
    prob, dt = motor_homog()
    sol = solve(prob, tspan=dt);
    @test sol.alg isa GLGM06
    @test sol.alg.static == Val{false}()
    @test setrep(sol) == Zonotope{Float64,Array{Float64,1},Array{Float64,2}}

    # static case
    sol = solve(prob, tspan=dt, static=true);
    @test sol.alg isa GLGM06
    @test sol.alg.static == Val{true}()
    @test setrep(sol) == Zonotope{Float64,SArray{Tuple{8},Float64,1,8},SArray{Tuple{8,8},Float64,2,64}}

    # if the static kwarg is passed outside the algorithm => it is ignored
    sol = solve(prob, tspan=dt, alg=GLGM06(δ=1e-2), static=true);
    @test setrep(sol) == Zonotope{Float64,Array{Float64,1},Array{Float64,2}}
end

@testset "Flowpipe interface" begin
    p = @ivp(x' = -x, x(0) ∈ 0..1)
    δ = 0.1
    sol = solve(p, tspan=(0.0, 1.0), GLGM06(δ=δ))
    F = flowpipe(sol)

    # iteration
    @test set(F, 1) == set(F[1])
    @test set(F, length(F)) == set(F[end])

    # time span methods
    @test _isapprox(tspan(sol), 0.0 .. 1.0)
    @test tstart(F) == 0.0
    @test tend(F) ≈ 1.0
    @test tstart(F[1]) == 0.0
    @test tend(F[1]) ≈ 0.1
    @test tstart(F[end]) ≈ 0.9
    @test tend(F[end]) ≈ 1.0

    # time span for flowpipe slices
    @test tstart(F[1:3]) ≈ 0.0
    @test tend(F[1:3]) ≈ 3δ
    @test tspan(F[1:3]) ≈ 0.0 .. 3δ

    @test tstart(F, 1:3) ≈ 0.0  # same, more efficient
    @test tend(F, 1:3) ≈ 3δ
    @test tspan(F, 1:3) ≈ 0.0 .. 3δ

    @test tstart(sol[1:3]) ≈ 0.0
    @test tend(sol[1:3]) ≈ 3δ
    @test tspan(sol[1:3]) ≈ 0.0 .. 3δ

    # set representation
    @test setrep(F) <: Zonotope
    N = Float64
    ZT = Zonotope{N, Vector{N}, Matrix{N}}
    @test setrep(F) == ZT
    @test rsetrep(F) == ReachSet{N, ZT}

    # callable behavior
    @test F(0..1) == F[1:end]
    @test F(0.05) == F[1]
    @test F(1.0) == F[end]
    @test F(0.05 .. 0.15) == F[1:2]

    # time interval *not* strictly contained in flowpipe returns a subset
    @test tspan(F(0.05 .. 1.05)) == tspan(F)

    # time interval totally outside the flowpipe returns an error
    @test_throws ArgumentError F(2 .. 3)

    # test that in the border of the time transition we get two reachsets
    F = flowpipe(sol)
    @test F(0.1) == F[1:2]

    # dimension
    @test dim(F) == 1

    # different ways to input the time span or the time horizon
    sol = solve(p, tspan=(0.0, 1.0))
    sol = solve(p, (0.0, 1.0))
    sol = solve(p, tspan=[0.0, 1.0])
    sol = solve(p, [0.0, 1.0])
    sol = solve(p, T=1.0)
    sol = solve(p, 1.0)
end

@testset "Solution interface: initial states" begin
    # interval initial condition
    p = @ivp(x' = -x, x(0) ∈ 0..1)
    solve(p, T=1.0)

    # interval initial condition
    p = @ivp(x' = -x, x(0) ∈ Interval(0, 1))
    solve(p, T=1.0)

    # deterministic initial condition, scalar for one-dimensional matrix (TODO)
    p = InitialValueProblem(@system(x' = -x), 0.5)
    solve(p, T=1.0)

    # deterministic initial condition, vector
    p = InitialValueProblem(@system(x' = -x), [0.5])
    solve(p, T=1.0)
end

@testset "Solution interface: time span" begin
    p = @ivp(x' = -x, x(0) ∈ 0..1)
    Δt = 0.0 .. 2.0

    # only time horizon given
    sol = solve(p, T=2.0)
    @test _isapprox(tspan(sol), Δt)

    # both time horizon and time span given => error
    @test_throws ArgumentError solve(p, T=1.0, tspan=(0.0, 2.0))

    # time span given as a tuple
    sol = solve(p, tspan=(0.0, 2.0))
    @test _isapprox(tspan(sol), Δt)

    # time span given as an interval
    sol = solve(p, tspan=0.0 .. 2.0)
    @test _isapprox(tspan(sol), Δt)

    # time span given as a LazySets interval
    sol = solve(p, tspan=Interval(0.0, 2.0))
    @test _isapprox(tspan(sol), Δt)

    # time span given as a vector
    sol = solve(p, tspan=[0.0, 2.0])
    @test _isapprox(tspan(sol), Δt)
end

@testset "Concrete projection" begin
    prob, tspan = motor_homog()
    sol = solve(prob, tspan=tspan, GLGM06(δ=0.01), static=false)

    project(sol, (1, 3))
    project(sol, [1, 3])
    project(sol, vars=[1, 3])
    project(sol, vars=(1, 3))

    F = flowpipe(sol)
    project(F, (1, 3))
    project(F, [1, 3])
    project(F, vars=[1, 3])
    project(F, vars=(1, 3))

    R = sol[end]
    project(R, (1, 3))
    project(R, [1, 3])
    project(R, vars=(1, 3))
    project(R, vars=[1, 3])

    # TODO concrete projection of sets with static array fields
end

# TODO:
# eachindex(F)

#=
TESTS

using LazySets, Revise, ReachabilityAnalysis
N = Float64
X = Hyperrectangle(N[0, 0], N[1, 1])
R = ReachSet(X, 0 .. 1)
set(R) ==
tstart(R) == 0.0
tend(R) == 1.0
tspan(R) == 1.0
dim(R) == 2
overapproximate(project(X, [1]), Interval) == Interval(-1, 1)
=#


"""
Abstract model type. All models are subtypes of this one, see *e.g.* [`AbstractAModel`](@ref)
"""
abstract type AbstractModel end

"""
Abstract structure that defines component models, which are structures that lists the processes that can be simulated for a given component.
For example [`LeafModels`](@ref) is a concrete implementation of an `AbstractComponentModel` for leaves.
"""
abstract type AbstractComponentModel <: AbstractModel end

"""
Assimilation (photosynthesis) abstract model. All photosynthesis models must be a subtype of
this.
"""
abstract type AbstractAModel <: AbstractModel end

"""
Stomatal conductance abstract model. All stomatal conductance models must be a subtype of
this.

An AbstractGsModel subtype struct must implement at least a g0 field.
"""
abstract type AbstractGsModel <: AbstractModel end

"""
Light interception abstract struct. All light interception models must be a subtype of this.
"""
abstract type AbstractInterceptionModel <: AbstractModel end

"""
Energy balance abstract struct. All energy balance models must be a subtype of this.
"""
abstract type AbstractEnergyModel <: AbstractModel end


"""
    abstract type AbstractColumn{A,T}

Packs together diagonal and offdiagonal elements of an operator at selected address.

See [`Column`](@ref) for a default implementation.

# Interface

* [`diagonal_element`](@ref)
* [`get_offdiagonal`](@ref)
* [`num_offdiagonals`](@ref)
* [`offdiagonals`](@ref) (optional)
"""
abstract type AbstractColumn{A,T} end

offdiagonals(col::AbstractColumn) = ColOffdiagonals(col)

"""
    Column

Default implementation of [`AbstractColumn`](@ref). Defined in terms of
[`diagonal_element`](@ref), [`get_offdiagonal`](@ref), and [`num_offdiagonals`](@ref)
of the operator.
"""
struct Column{A,T,O<:AbstractOperator{T},M} <: AbstractColumn{A,T}
    operator::O
    address::A
    map::M
end
diagonal_element(col::Column) = diagonal_element(col.operator, col.address, col.map)
get_offdiagonal(col::Column, i) = get_offdiagonal(col.operator, col.address, col.map, i)
num_offdiagonals(col::Column) = num_offdiagonals(col.operator, col.address, col.map)

Base.show(io::IO, col::Column) = print(io, "column(", col.operator, ", ", col.address, ")")

function column(op::AbstractOperator, add::A) where {A}
    map = OccupiedModeMap(add)
    return Column(op, add, map)
end

struct ColOffdiagonals{A,T,C<:AbstractColumn{A,T}} <: AbstractVector{Tuple{A,T}}
    column::C
end
Base.size(od::ColOffdiagonals) = (num_offdiagonals(od.column),)
Base.getindex(od::ColOffdiagonals, i) = get_offdiagonal(od.column, i)

"""
    CompositeColumn

Column for an operator with a [`CompositeFS`](@ref) address.
"""
struct CompositeColumn{N,A,T,O<:AbstractOperator{T},M} <: AbstractColumn{A,T}
    operator::O
    address::A
    maps::M
end
function column(op::AbstractOperator{T}, add::A) where {N,A<:CompositeFS{N},T}
    maps = map(OccupiedModeMap, add.components)
    return CompositeColumn{N,A,T,typeof(op),typeof(maps)}(op, add, maps)
end

Base.show(io::IO, col::CompositeColumn) = print(io, "column($(col.operator), $(col.address))")

# NOTE: the following @generated functions take care of calling the operator on all
# combinations of addresses in the `CompositeFS`. This way, it's enough for operators to
# define the 6-argument get_offdiagonal.
function get_offdiagonal(col::CompositeColumn, chosen)
    return get_offdiagonal(CompositeAction(col.operator), col, chosen)
end
@generated function get_offdiagonal(::A, col::CompositeColumn{N}, chosen) where {A,N}
    expr = quote
        orig_chosen = chosen # for error at the end
        add = col.address
        adds = col.address.components
        maps = col.maps
        op = col.operator
    end
    # Per-component terms.
    for i in 1:N
        expr = quote
            $expr
            num = num_offdiagonals(op, adds[$i], maps[$i])
            if chosen ≤ num
                new_add_i, value = get_offdiagonal(op, adds[$i], maps[$i], chosen, $i)
                new_add = update_component(add, new_add_i, Val($i))
                return new_add, value
            else
                chosen -= num
            end
        end
    end
    # Cross-component terms.
    if A ≢ NoCompositeAction
        for i in 1:N, j in 1:N
            i == j && continue
            j > i && A === OneWayCompositeAction && continue
            expr = quote
                $expr
                num = num_offdiagonals(op, adds[$i], adds[$j], maps[$i], maps[$j])
                if chosen ≤ num
                    new_add_i, new_add_j, value = get_offdiagonal(
                        op, adds[$i], adds[$j], maps[$i], maps[$j], chosen, ($i, $j)
                    )
                    new_add = update_component(add, new_add_i, new_add_j, Val($i), Val($j))
                    return new_add, value
                else
                    chosen -= num
                end
            end
        end
    end

    return quote
        $expr
        throw(BoundsError(col, orig_chosen))
    end
end

function num_offdiagonals(col::CompositeColumn)
    return num_offdiagonals(CompositeAction(col.operator), col)
end
@generated function num_offdiagonals(::A, col::CompositeColumn{N}) where {A,N}
    expr = quote
        adds = col.address.components
        maps = col.maps
        op = col.operator
        result = 0
    end
    for i in 1:N
        expr = quote
            $expr
            result += num_offdiagonals(op, adds[$i], maps[$i])
        end
    end
    if A ≢ NoCompositeAction
        for i in 1:N, j in 1:N
            i == j && continue
            j > i && A === OneWayCompositeAction && continue
            expr = quote
                $expr
                result += num_offdiagonals(op, adds[$i], adds[$j], maps[$i], maps[$j])
            end
        end
    end
    return quote
        $expr
        return result
    end
end

function diagonal_element(col::CompositeColumn)
    return diagonal_element(CompositeAction(col.operator), col)
end
@generated function diagonal_element(::A, col::CompositeColumn{N,<:Any,T}) where {A,N,T}
    expr = quote
        adds = col.address.components
        maps = col.maps
        op = col.operator
        result = zero($T)
    end
    for i in 1:N
        expr = quote
            $expr
            result += diagonal_element(op, adds[$i], maps[$i], $i)
        end
    end
    if A ≢ NoCompositeAction
        for i in 1:N, j in 1:N
            i == j && continue
            j > i && A === OneWayCompositeAction && continue
            expr = quote
                $expr
                result += diagonal_element(
                    op, adds[$i], adds[$j], maps[$i], maps[$j], ($i,$j)
                )
            end
        end
    end
    return quote
        $expr
        return result
    end
end