Lar = LinearAlgebraicRepresentation
#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE - PART 1 METHODS
#    frag_edge
#    intersect_edges
#    merge_vertices!
#-------------------------------------------------------------------------------

"""
	frag_edge(
		model::CAGD.Model,
		edge_idx::Int,
		bigPI::Array{Array{Int64,1},1}
	)::Tuple{Lar.Points, Lar.ChainOp}

Splits the `edge_idx`-th edge of the Edge Topology `model.T[1]`.

This method splits the `edge_idx`-th edge between the Edges in the Topology
`EV = model.T[1]` into several parts by confronting it with the others that
intersect its bounding box (see also [`CAGD.intersect_edges`](@ref)).
Returns a set made of the detected vertices over the edge (with redundancies)
and the associated cochain (without redundancies).

See also:
 - [`CAGD.planar_arrangement_1`](@ref)
 - [`CAGD.frag_edge_channel`](@ref)
It uses: [`CAGD.intersect_edges`](@ref)
---
# Examples
```jldoctest
julia> model = CAGD.Model(hcat([
	[1.0, 0.0], [0.0, 1.0], [0.0, 0.5], [0.5, 1.0], [1.0, 1.0]
]...));
julia> CAGD.addModelCells!(model, 1, [[1, 2], [2, 5], [3, 4], [4, 5]])
julia> bigPI = CAGD.spaceIndex(model);
julia> CAGD.frag_edge(model, 1, bigPI)[1]
4×2 Array{Float64,2}:
 1.0   0.0
 0.0   1.0
 0.25  0.75
 0.0   1.0
julia> Matrix(CAGD.frag_edge(model, 1, bigPI)[2])
2×4 Array{Int8,2}:
 1  0  1  0
 0  1  1  0
```
"""
function frag_edge(
		model::CAGD.Model,
		edge_idx::Int,
		bigPI::Array{Array{Int64,1},1}
	)::Tuple{Lar.Points, Lar.ChainOp}
	V = convert(Lar.Points, model.G')
	EV = model.T[1]
	alphas = Dict{Float64, Int}()
	edge = EV[edge_idx, :]
	verts = V[edge.nzind, :]
	for i in bigPI[edge_idx]
		if i != edge_idx # && edge_idx in bigPI[i]
			intersection = CAGD.intersect_edges(V, edge, EV[i, :])
			for (point, alpha) in intersection
				verts = [verts; point]
				alphas[alpha] = size(verts, 1)
			end
		end
	end
	alphas[0.0], alphas[1.0] = [1, 2]
	alphas_keys = sort(collect(keys(alphas)))
	edge_num = length(alphas_keys)-1
	verts_num = size(verts, 1)
	ev = SparseArrays.spzeros(Int8, edge_num, verts_num)
	for i in 1:edge_num
		ev[i, alphas[alphas_keys[i]]] = 1
		ev[i, alphas[alphas_keys[i+1]]] = 1
	end
	return verts, ev
end

"""
	intersect_edges(
		V::Lar.Points,
		edge1::Lar.Cell,
		edge2::Lar.Cell
	)::Array{Tuple{Lar.Points, Float64}, 1}

Finds the intersection points (if there exist) between the two given edges.

Compute the new points over `edge1` given by the intersection with `edge2`.
For each intersection point it evaluates the scalar corresponding to the
relative distance from the first vertex of the edge.
Note that vertices of `edge1` are therefore never detected (see example 2).

See also: [`CAGD.frag_edge`](@ref)
---
# Examples
```jldoctest
# Cross
julia> V = [1.0 0.0; 0.0 1.0; 0.0 0.5; 0.5 1.0];
julia> copEV = Lar.coboundary_0([[1, 2], [3, 4]]);
julia> CAGD.intersect_edges(V, copEV[1, :], copEV[2, :])
1-element Array{Tuple{Array{T,2} where T,Float64},1}:
 ([0.25 0.75], 0.75)
```

```jldoctest
# Collinear
julia> V = [1.0 0.0; 0.0 1.0; 0.75 0.25; 0.5 0.5];
julia> copEV = Lar.coboundary_0([[1, 2], [3, 4]]);
julia> CAGD.intersect_edges(V, copEV[1, :], copEV[2, :])
2-element Array{Tuple{Array{T,2} where T,Float64},1}:
 ([0.75 0.25], 0.25)
 ([0.5 0.5], 0.5)
julia> CAGD.intersect_edges(V, copEV[2, :], copEV[1, :])
0-element Array{Tuple{Array{T,2} where T,Float64},1}
```
"""
function intersect_edges(
		V::Lar.Points,
		edge1::Lar.Cell,
		edge2::Lar.Cell
	)::Array{Tuple{Lar.Points, Float64}, 1}

	err = 10e-8

	x1, y1, x2, y2 = vcat(map(c->V[c, :], edge1.nzind)...)
	x3, y3, x4, y4 = vcat(map(c->V[c, :], edge2.nzind)...)
	ret = Array{Tuple{Lar.Points, Float64}, 1}()

	v1 = [x2-x1, y2-y1]
	v2 = [x4-x3, y4-y3]
	v3 = [x3-x1, y3-y1]
	ang1 = dot(LinearAlgebra.normalize(v1), LinearAlgebra.normalize(v2))
	ang2 = dot(LinearAlgebra.normalize(v1), LinearAlgebra.normalize(v3))
	parallel = 1-err < abs(ang1) < 1+err
	colinear = parallel && (1-err < abs(ang2) < 1+err || -err < norm(v3) < err)
	if colinear
		o = [x1 y1]
		v = [x2 y2] - o
		alpha = 1/dot(v,v')
		ps = [x3 y3; x4 y4]
		for i in 1:2
			a = alpha*dot(v',(reshape(ps[i, :], 1, 2)-o))
			if 0 < a < 1
				push!(ret, (ps[i:i, :], a))
			end
		end
	elseif !parallel
		denom = (v2[2])*(v1[1]) - (v2[1])*(v1[2])
		a = ((v2[1])*(-v3[2]) - (v2[2])*(-v3[1])) / denom
		b = ((v1[1])*(-v3[2]) - (v1[2])*(-v3[1])) / denom

		if -err < a < 1+err && -err <= b <= 1+err
			p = [(x1 + a*(x2-x1))  (y1 + a*(y2-y1))]
			push!(ret, (p, a))
		end
	end
	return ret
end

"""
merge_vertices!(
	model::CAGD.Model,
	[ edge_map::Array{Array{Int64,1},1} = Array{Array{Int64,1},1}() ],
	[ err::Float64 = 1e-4 ]
)::Nothing

Modify `model` by compacting its points closer than `err` in a single one.

This method check one at time each vertex ``v`` in `model.G` and identifies
each other vertex within `err` with ``v`` itself.
The edges cochain `model.T[1]` is coherently modified (multiple edges between
two vertices are not allowed).
If an `edge_map` is given in input (this could be usefull during the planar
arrangement), then also the map is coherently modified and given back in output.

See also: [`CAGD.planar_arrangement_1`](@ref)
---
# Examples
```jldoctest
julia> model = CAGD.Model([
	0.5 0.0 0.5 1.0 0.5 1.0
	0.5 0.0 0.5 1.0 0.5 1.0
]);
julia> CAGD.addModelCells!(model, 1, [[1, 4], [3, 2], [5, 6], [1, 6], [5, 3]]);
julia> CAGD.merge_vertices!(model)

julia> model.G
2×3 Array{Float64,2}:
 0.5  0.0  1.0
 0.5  0.0  1.0

julia> Matrix(model.T[1])
2×3 Array{Int8,2}:
 1  0  1
 1  1  0
```
"""
function merge_vertices!(
		model::CAGD.Model,
		edge_map::Array{Array{Int64,1},1} = Array{Array{Int64,1},1}(),
		err::Float64 = 1e-4
	)::Nothing
	V = convert(Lar.Points, model.G')
	EV = model.T[1]
	vertsnum = size(V, 1)
	edgenum = size(EV, 1)
	newverts = zeros(Int, vertsnum)
	# KDTree constructor needs an explicit array of Float64
	V = Array{Float64,2}(V)
	kdtree = KDTree(permutedims(V))

	# merge congruent vertices
	todelete = []
	i = 1
	for vi in 1:vertsnum
		if !(vi in todelete)
			nearvs = Lar.inrange(kdtree, V[vi, :], err)
			newverts[nearvs] .= i
			nearvs = setdiff(nearvs, vi)
			todelete = union(todelete, nearvs)
			i = i + 1
		end
	end
	nV = V[setdiff(collect(1:vertsnum), todelete), :]

	# merge congruent edges
	edges = Array{Tuple{Int, Int}, 1}(undef, edgenum)
	oedges = Array{Tuple{Int, Int}, 1}(undef, edgenum)
	for ei in 1:edgenum
		v1, v2 = EV[ei, :].nzind
		edges[ei] = Tuple{Int, Int}(sort([newverts[v1], newverts[v2]]))
		oedges[ei] = Tuple{Int, Int}(sort([v1, v2]))
	end
	nedges = union(edges)
	nedges = filter(t->t[1]!=t[2], nedges)
	nedgenum = length(nedges)
	nEV = spzeros(Int8, nedgenum, size(nV, 1))
	# maps pairs of vertex indices to edge index
	etuple2idx = Dict{Tuple{Int, Int}, Int}()
	# builds `edge_map`
	for ei in 1:nedgenum
		nEV[ei, collect(nedges[ei])] .= 1
		etuple2idx[nedges[ei]] = ei
	end
	if !isempty(edge_map)
		for i in 1:length(edge_map)
			row = edge_map[i]
			row = map(x->edges[x], row)
			row = filter(t->t[1]!=t[2], row)
			row = map(x->etuple2idx[x], row)
			edge_map[i] = row
		end
	end
	# return new vertices and new edges
	model.G = convert(Lar.Points, nV')
	model.T[1] = nEV
	model.T[2] = SparseArrays.spzeros(Int8, 0, size(model, 1, 1))
	return
end

#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE - CLEAN DECOMPOSITION
#-------------------------------------------------------------------------------

"""
	cleandecomposition(
		model::CAGD.Model,
		sigma::Union{Lar.Chain, Array{Int,1}},
		[ edge_map::Array{Array{Int64,1},1} = Array{Array{Int64,1},1}() ]
	)

This function clears the `model` from all edges outside ``σ``.

This function take an arranged-edge `model` and a `Lar.Chain` ``σ`` made of
edges indices of the 1-chains `model.T[1]`.
The method drops from the Topology all the edges that are outside ``σ`` and
gives back the dropped model.
If an `edge_map` is given as an input it is also purged from the edges outside
w.r.t. ``σ`` and it is given as an output as well.

See also: [`CAGD.planar_arrangement`](@ref)
---
# Examples
```jldoctest
# Nested Triangles
julia> model = CAGD.Model([
	0.0  2.0  4.0  1.0  3.0  2.0  2.0 -2.0  6.0
	0.0  0.0  0.0  1.5  1.5  3.0 -3.0  3.0  3.0
]);
julia> CAGD.addModelCells!(model, 1, [
	[1, 3], [1, 6], [3, 6], [2, 4], [2, 5], [4, 5], [7, 8], [7, 9], [8, 9]
]);

julia> σ = SparseArrays.sparse([1; 1; 1; 0; 0; 0; 0; 0; 0]);

julia> model = CAGD.cleandecomposition(model, convert(Lar.Chain, σ))

julia> model.G
2×6 Array{Float64,2}:
 0.0  2.0  4.0  1.0  3.0  2.0
 0.0  0.0  0.0  1.5  1.5  3.0

julia> Matrix(model.T[1])
6×6 Array{Int8,2}:
 1  0  1  0  0  0
 1  0  0  0  0  1
 0  0  1  0  0  1
 0  1  0  1  0  0
 0  1  0  0  1  0
 0  0  0  1  1  0
```
"""
function cleandecomposition(
		model::CAGD.Model,
		sigma::Union{Lar.Chain, Array{Int,1}},
		edge_map::Array{Array{Int64,1},1} = Array{Array{Int64,1},1}()
	)::Union{CAGD.Model, Tuple{CAGD.Model, Array{Array{Int64,1},1}}}

	model = deepcopy(model)
	edge_map = deepcopy(edge_map)
	# Model point extraction to use Lar.point_in_face                           ##
	V = convert(Lar.Points, model.G')

	if issparse(sigma)
		sigma = sigma.nzind
	end
	todel = Array{Int,1}()
	sigma_edges = model.T[1][sigma, :]
	for e in 1 : model.T[1].m
		# Since the model is already arranged an edge can only be a sigma-edge,
		#  an intersection-free innner edge w.r.t. sigma face (not to purge),
		#  or an intersection-free outer edge (has to be deleted).
		if !(e in sigma)
			v1, v2 = CAGD.getModelCellVertices(model, 1, e)
			centroid = .5*(v1 + v2)

			if ! CAGD.point_in_face(centroid, V, sigma_edges)
				push!(todel, e)
			end
		end
	end

	CAGD.deleteModelCells!(model, 1, todel)
	CAGD.modelPurge!(model)

	if isempty(edge_map)
		return model
	end

	# Edges in edge_map must be updated too according to the same approach
	#  edges are deleted from models.
	for i in reverse(todel)
		for row in edge_map

			filter!(x->x!=i, row)

			for j in 1:length(row)
				if row[j] > i
					row[j] -= 1
				end
			end
		end
	end
	return model, edge_map
end

#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE - BICONNECTED COMPONENTS
#-------------------------------------------------------------------------------

"""
	biconnected_components(EV::Lar.ChainOp)

Compute sets of 2-cells on the same 3-cells biconnected components.

The method evaluates the 2-cells ``σ_i`` wich lies on the same 3-cells
biconnected component ``χ_j`` and gives back an array that contains an array
of ``σ_i`` for each ``j``.
Do note that if the cochain `EV` contains more copies of the same 2-cell then
it will be considered like a 3-cell.

Intermediate part of Planar Arrangement's algorithmic pipeline.

See also:
 - [`CAGD.planar_arrangement`](@ref) for the complete pipeline.
 - [`CAGD.planar_arrangement_2`](@ref).
---

# Examples
```jldoctest
julia> copEV = SparseArrays.sparse(Array{Int8, 2}([
		[1 1 0 0 0 0] #1 -> 1,2  |
		[1 0 1 0 0 0] #2 -> 1,3  |
		[1 0 0 1 0 0] #3 -> 1,4   |
		[1 0 0 0 1 0] #4 -> 1,5   |
		[1 0 0 0 0 1] #5 -> 1,6
		[0 1 1 0 0 0] #6 -> 2,3  |
		[0 0 0 1 1 0] #7 -> 4,5   |
	]));
julia> CAGD.biconnected_components(copEV)
2-element Array{Array{Int64,1},1}:
 [2, 6, 1]
 [4, 7, 3]
```

```jldoctest
julia> copEV = SparseArrays.sparse(Array{Int8, 2}([
		[1 1 0] #1 -> 1,2  |
		[1 1 0] #2 -> 1,2  |
		[1 0 1] #3 -> 1,2
	]));
julia> CAGD.biconnected_components(copEV)
1-element Array{Array{Int64,1},1}:
 [2, 1]
```
"""
function biconnected_components(EV::Lar.ChainOp)

	ps = Array{Tuple{Int, Int, Int}, 1}()
	es = Array{Tuple{Int, Int}, 1}()
	todel = Array{Int, 1}()
	visited = Array{Int, 1}()
	bicon_comps = Array{Array{Int, 1}, 1}()
	hivtx = 1

	function an_edge(point) # TODO: fix bug
		# error? : BoundsError: attempt to access 0×0 SparseMatrix ...
		edges = setdiff(EV[:, point].nzind, todel)
		if length(edges) == 0
			edges = [false]
		end
		edges[1]
	end

	function get_head(edge, tail)
		setdiff(EV[edge, :].nzind, [tail])[1]
	end

	function v_to_vi(v)
		i = findfirst(t->t[1]==v, ps)
		# seems findfirst changed from 0 to Nothing
		if typeof(i) == Nothing
			return false
		elseif i == 0
			return false
		else
			return ps[i][2]
		end
	end

	push!(ps, (1,1,1))
	push!(visited, 1)
	exit = false
	while !exit
		edge = an_edge(ps[end][1])
		if edge != false
			tail = ps[end][2]
			head = get_head(edge, ps[end][1])
			hi = v_to_vi(head)
			if hi == false
				hivtx += 1
				push!(ps, (head, hivtx, ps[end][2]))
				push!(visited, head)
			else
				if hi < ps[end][3]
					ps[end] = (ps[end][1], ps[end][2], hi)
				end
			end
			push!(es, (edge, tail))
			push!(todel, edge)
		else
			if length(ps) == 1
				found = false
				pop!(ps)
				for i in 1:size(EV,2)
					if !(i in visited)
						hivtx = 1
						push!(ps, (i, hivtx, 1))
						push!(visited, i)
						found = true
						break
					end
				end
				if !found
					exit = true
				end

			else
				if ps[end][3] == ps[end-1][2]
					edges = Array{Int, 1}()
					while true
						edge, tail = pop!(es)
						push!(edges, edge)
						if tail == ps[end][3]
							if length(edges) > 1
								push!(bicon_comps, edges)
							end
							break
						end
					end

				else
					if ps[end-1][3] > ps[end][3]
						ps[end-1] = (ps[end-1][1], ps[end-1][2], ps[end][3])
					end
				end
				pop!(ps)
			end
		end
	end
	bicon_comps = sort(bicon_comps, lt=(x,y)->length(x)>length(y))
	return bicon_comps
end

function monoconnected_components(EV::Lar.ChainOp)::Array{Array{Int,1},1}
	mono = Array{Array{Int,1},1}()
	tocheck = collect(1 : EV.m)
	while(!isempty(tocheck))
		vs = Array{Int,1}()
		vs_i = Array{Int,1}()
		vs_o = Array{Int,1}()
		evs = [tocheck[1]]
		evs_i = [tocheck[1]]
		evs_o = Array{Int,1}()

		while(!isempty(vs_i) || !isempty(evs_i))
			if !isempty(evs_i)
				e = evs_i[1]
				push!(evs_o, e);
				push!(vs, EV[e, :].nzind...)
			end
			vs_i = setdiff(vs, vs_o)
			if !isempty(vs_i)
				v = vs_i[1]
				push!(vs_o, v);
				push!(evs, EV[:, v].nzind...)
			end
			evs_i = setdiff(evs, evs_o)
		end

		push!(mono, unique(evs))
	end
	return mono
end

#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE - REMOVE MAPPING DANGLING EDGES
#-------------------------------------------------------------------------------

"""
	remove_mapping_dangling_edges(
		edge_map::Array{Array{Int64,1},1},
		bicon_comps::Array{Array{Int64,1},1}
	)::Array{Array{Int64,1},1}

Remove from the edge_map the edges not belonging to a biconnected component.

This utility function deletes from the `edge_map` all the edges that do not
belongs to a biconnected component by ordinatelly recompatting the indices of
the remaning edges.

See also: [`CAGD.planar_arrangement`](@ref).
"""
function remove_mapping_dangling_edges(
		edge_map::Array{Array{Int64,1},1},
		bicon_comps::Array{Array{Int64,1},1}
	)::Array{Array{Int64,1},1}

	edge_map = deepcopy(edge_map)

	edges = sort(union(bicon_comps...))
								  #size(model.T[1, 1]) == max(edge_map...)
	todel = sort(setdiff(collect(1:max(max(edge_map...)...)), edges))

	for i in reverse(todel)
		for row in edge_map

			filter!(x->x!=i, row)

			for j in 1:length(row)
				if row[j] > i
					row[j] -= 1
				end
			end
		end
	end

	return edge_map
end

#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE - PART 2 METHODS
#    componentgraph (aka TGW) from tgw2.jl
#    cell_merging
#-------------------------------------------------------------------------------

"""
	cell_merging(n, containment_graph, V, EVs, boundaries, shells, shell_bboxes)

Cells composing for the Topological Gift Wrapping algorithm.

This is the online part of the TGW algorithm.

See also: [`CAGD.planar_arrangement_2`](@ref).
"""
function cell_merging(
		n, containment_graph, V, EVs, boundaries, shells, shell_bboxes
	)
	function bboxes(V::Lar.Points, indexes::Lar.ChainOp)
		boxes = Array{Tuple{Any, Any}}(undef, indexes.n)
		for i in 1:indexes.n
			v_inds = indexes[:, i].nzind
			boxes[i] = Lar.bbox(V[v_inds, :])
		end
		boxes
	end
	# initialization
	sums = Array{Tuple{Int, Int, Int}}(undef, 0)
	# assembling child components with father components
	for father in 1:n
		if sum(containment_graph[:, father]) > 0
			father_bboxes = bboxes(
				V, abs.(EVs[father]')*abs.(boundaries[father]')
			)
			for child in 1:n
				if containment_graph[child, father] > 0
					child_bbox = shell_bboxes[child]
					for b in 1:length(father_bboxes)
						if Lar.bbox_contains(father_bboxes[b], child_bbox)
							push!(sums, (father, b, child))
							break
						end
					end
				end
			end
		end
	end
	# offset assembly initialization
	EV = vcat(EVs...)
	edgenum = size(EV, 1)
	facenum = sum(map(x->size(x,1), boundaries))
	FE = spzeros(Int8, facenum, edgenum)
	shells2 = spzeros(Int8, length(shells), edgenum)
	r_offsets = [1]
	c_offset = 1
	# submatrices construction
	for i in 1:n
		min_row = r_offsets[end]
		max_row = r_offsets[end] + size(boundaries[i], 1) - 1
		min_col = c_offset
		max_col = c_offset + size(boundaries[i], 2) - 1
		FE[min_row:max_row, min_col:max_col] = boundaries[i]
		shells2[i, min_col:max_col] = shells[i]
		push!(r_offsets, max_row + 1)
		c_offset = max_col + 1
	end
	# offsetting assembly of component submatrices
	for (f, r, c) in sums
		FE[r_offsets[f]+r-1, :] += shells2[c, :]
	end

	return EV, FE
end

#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE - PART 1
#-------------------------------------------------------------------------------

"""
	planar_arrangement_1(
		model::CAGD.Model,
		[sigma::Lar.Chain = spzeros(Int8, 0)],
		[return_edge_map::Bool = false]
	)

First part of arrangement's algorithmic pipeline.

This function computes the pairwise intersection between each edge of a given
2D cellular complex 1-skeleton. The computation is speeded up via the evaluation
of the Spatial Index. See [`CAGD.spaceindex`](@ref).

See also: [`CAGD.planar_arrangement`](@ref) for the complete pipeline.
It uses:
 - [`CAGD.frag_edges_channel`](@ref)
 - [`CAGD.frag_edges`](@ref)
 - [`CAGD.merge_vertices!`](@ref)
---
# Examples
```jldoctest
julia> model = CAGD.Model([
	0.0 0.5 0.0 0.5 0.3 1.0 0.3 1.0;
	0.0 0.0 1.0 1.0 0.5 0.5 1.0 1.0
]);
julia> CAGD.addModelCells!(model, 1, [
	[1, 2], [3, 4], [1, 3], [2, 4], [5, 6], [7, 8], [5, 7], [6, 8]
]);
julia> (model, edge_map) =
	CAGD.planar_arrangement_1(model, spzeros(Int8, 0), true);
julia> model.G
2×9 Array{Float64,2}:
 0.0  0.5  0.0  0.5  0.3  0.5  0.3  1.0  1.0
 0.0  0.0  1.0  1.0  1.0  0.5  0.5  0.5  1.0

julia> Matrix(model.T[1])
11×9 Array{Int8,2}:
 1  1  0  0  0  0  0  0  0
 0  0  1  1  0  0  0  0  0
 0  0  0  1  1  0  0  0  0
 1  0  1  0  0  0  0  0  0
 0  1  0  0  0  1  0  0  0
 0  0  0  0  1  1  0  0  0
 0  0  0  0  0  1  1  0  0
 0  0  0  0  0  1  0  1  0
 0  0  0  0  1  0  0  0  1
 0  0  0  1  0  0  1  0  0
 0  0  0  0  0  0  0  1  1

julia> edge_map
8-element Array{Array{Int64,1},1}:
 [1]
 [2, 3]
 [4]
 [5, 6]
 [7, 8]
 [3, 9]
 [10]
 [11]

```
"""
function planar_arrangement_1(
		model::CAGD.Model,
		sigma::Lar.Chain = spzeros(Int8, 0),
		return_edge_map::Bool = false
	)::Union{
		CAGD.Model,
		Tuple{CAGD.Model, Array{Array{Int64,1},1}},
		Tuple{CAGD.Model, Array{Int,1}},
		Tuple{CAGD.Model, Array{Int,1}, Array{Array{Int64,1},1}}
	}
	# data structures initialization
	# V = convert(Lar.Points, model.G')
	# copEV = model.T[1]
	edgenum = size(model, 1, 1)
	edge_map = Array{Array{Int, 1}, 1}(undef,edgenum)
	rV = Lar.Points(zeros(0, 2))
	rEV = SparseArrays.spzeros(Int8, 0, 0)
	finalcells_num = 0

	# spaceindex computation
	bigPI = CAGD.spaceIndex(model, 1)

	# sequential (iterative) processing of edge fragmentation
	for i in 1:edgenum
		v, ev = CAGD.frag_edge(model, i, bigPI)
		newedges_nums = map(x->x+finalcells_num, collect(1:size(ev, 1)))
		edge_map[i] = newedges_nums
		finalcells_num += size(ev, 1)
		rV = convert(Lar.Points, rV)
		rV, rEV = Lar.skel_merge(rV, rEV, v, ev)
	end

	# merging of close vertices and edges (2D congruence)
	model = CAGD.Model(convert(Lar.Points, rV'))
	CAGD.addModelCells!(model, 1, rEV)
	CAGD.merge_vertices!(model, edge_map)

	if isempty(sigma)
		if return_edge_map
			return model, edge_map
		else
			return model
		end
	end

	# Sigma update by mapping each of its edges into newly born edges
	new_sigma = Array{Int,1}()
	map(i->new_sigma=union(new_sigma, edge_map[i]), sigma.nzind)
	if return_edge_map
		return model, new_sigma, edge_map
	else
		return model, new_sigma
	end
end

#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE - PART 2
#-------------------------------------------------------------------------------

"""
	planar_arrangement_2(
		model::CAGD.Model,
		bicon_comps::{Array{Array,1},1}
	)::CAGD.Model

Second part of arrangement's algorithmic pipeline.

This function is the complete Topological Gift Wrapping (TGW) algorithm that
is firstly locally used in order to decompose the 2-cells and then globally
to generate the 3-cells of the arrangement of the ambient space ``E^3``.

During this process each dangling 1-cell is removed.
Do note that the isolated 2-cells are not removed by this procedure.

See also: [`CAGD.planar_arrangement`](@ref) for the complete pipeline.
It uses:
 - [`CAGD.biconnected_components`](@ref)
 - [`CAGD.componentgraph`](@ref)
 - [`CAGD.cell_merging`](@ref)
---

# Examples
```jldoctest
# Triforce
julia> model = CAGD.Model([
	0.0 0 2 0 0 1 4 4 3 2 4 4
	0.0 2 0 3 4 4 2 0 0 4 4 2
]);

julia> CAGD.addModelCells!(model, 1, [
	[ 1,  2], [ 2,  3], [ 3,  1],
	[ 4,  5], [ 5,  6], [ 6,  7], [ 7,  8], [ 8,  9], [ 9, 4],
	[10, 11], [11, 12], [12, 10]
])

julia> bicon_comps = CAGD.biconnected_components(model.T[1])
3-element Array{Array{Int64,1},1}:
 [9, 8, 7, 6, 5, 4]
 [3, 2, 1]
 [12, 11, 10]

julia> model = CAGD.planar_arrangement_2(model, bicon_comps);

julia> Matrix(model.T[2])
3×12 Array{Int8,2}:
 -1  -1  -1  -1  -1  1   0   0  0   0   0  0
  0   0   0   0   0  0  -1  -1  1   0   0  0
  0   0   0   0   0  0   0   0  0  -1  -1  1
```
"""
function planar_arrangement_2(
		model::CAGD.Model,
		bicon_comps::Array{Array{Int64,1},1}
	)::CAGD.Model

	#==
	test to remove
	@assert isequal(
		bicon_comps,
		CAGD.biconnected_components(model.T[1])
	)
	==#

	# component graph
	n, containment_graph, V, EVs, boundaries, shells, shell_bboxes =
		CAGD.componentgraph(
			convert(Lar.Points, model.G'), model.T[1], bicon_comps
		)

	copEV, FE = CAGD.cell_merging(
	   	n, containment_graph, V, EVs, boundaries, shells, shell_bboxes
	)

	model = CAGD.Model(convert(Lar.Points, V'))
	CAGD.addModelCells!(model, 1, copEV)
	CAGD.addModelCells!(model, 2, FE)
	CAGD.modelPurge!(model)
	return model
end

#-------------------------------------------------------------------------------
#   PLANAR ARRANGEMENT PIPELINE
#-------------------------------------------------------------------------------

"""
	planar_arrangement(
		model::CAGD.Model,
		[sigma::Lar.Chain],
		[return_edge_map::Bool]
	)::Union{CAGD.Model, Tuple{CAGD.Model, Array{Array{Int, 1}, 1}}}

Compute the arrangement on the given cellular complex 1-skeleton in 2D.

A cellular complex is arranged when the intersection of every possible pair
of cell of the complex is empty and the union of all the cells is the
whole Euclidean space.
The methods default input/output is a `CAGD.Model` with at least Geometry and
1-cell Topoly.
Mutiprocessing is by default disabled.

It uses:
 - [`CAGD.planar_arrangement_1`](@ref)
 - [`CAGD.cleandecomposition`](@ref)
 - [`CAGD.biconnected_components`](@ref)
 - [`CAGD.remove_mapping_dangling_edges`](@ref)
 - [`CAGD.planar_arrangement_2`](@ref)

## Additional arguments:
 - `sigma::Chain`: if specified, the method return only cells contained inside
		this cell. _Default_: empty cell.
 - `return_edge_map::Bool`: builds an `edge_map` which maps the input edges
		to the output's. _Defaults_: `false`.
"""
function planar_arrangement(
		model::CAGD.Model,
		sigma::Lar.Chain = spzeros(Int8, 0),                                    ## TEST
		return_edge_map::Bool = false
	)::Union{CAGD.Model, Tuple{CAGD.Model, Array{Array{Int, 1}, 1}}}

	# Check dimensional condition
	@assert length(model) == 2

	#planar_arrangement_1
	if isempty(sigma)
		model, edge_map =
			CAGD.planar_arrangement_1(model, sigma, true)
	else
		model, sigma, edge_map =
			CAGD.planar_arrangement_1(model, sigma, true)
	end

#   print("PLANAR_ARRANGEMENT_1 COMPLETE\n")

	# cleandecomposition
	if !isempty(sigma)
		model, edge_map = CAGD.cleandecomposition(model, sigma, edge_map)
#       print("DECOMPOSITION CLEANED\n")
	end

	# generation of biconnected components
	bicon_comps = CAGD.biconnected_components(model.T[1])
	#mocon_comps = CAGD.monoconnected_components(model.T[1])

#	print("BICONNECTED COMPONENTS FOUND\n")

	if isempty(bicon_comps)
		println("No biconnected components found.\n")
		if (return_edge_map)
			return (model(), nothing)
		else
			return model()
		end
	end

	# Removing Dangling edges from edge map
	if return_edge_map
		@assert size(model, 1, 1) == max((edge_map...)...)                           # CTR
		edge_map = CAGD.remove_mapping_dangling_edges(edge_map, bicon_comps)
	end

#	print("DANGLING EDGES REMOVED\n")

	# Planar_arrangement_2
	model = CAGD.planar_arrangement_2(model, bicon_comps)

#	print("PLANAR ARRANGEMENT 2 COMPLETE\n")

	if size(model, 0, 2) - size(model, 1, 1) + size(model, 2, 1) != 1
		@show size(model, 0, 2) - size(model, 1, 1) + size(model, 2, 1)
	end

	if return_edge_map
		 return model, edge_map
	else
		 return model
	end
end

"""
	point_in_face(point, V::Points, copEV::ChainOp)
Check if `point` is not outside the area of the face bounded by the edges in `copEV`
"""
function point_in_face(point, V::Lar.Points, copEV::Lar.ChainOp)

	function pointInPolygonClassification(V,EV)

		function crossingTest(new, old, status, count)
			if status == 0
				status = new
				return status, (count + 0.5)
			else
				if status == old
					return 0, (count + 0.5)
				else
					return 0, (count - 0.5)
				end
			end
		end

		# Outputs a tileCoder that takes as input a point and classify its
		#  octant position with respect to the supercrescent list N-S-E-W:
		#  1001 | 0001 | 0101
		#  -----+------+-----
		#  1000 | 0000 | 0100
		#  -----+------+-----
		#  0101 | 0010 | 0110
		function setTile(box)
			tiles = [[9,1,5],[8,0,4],[10,2,6]]
			b1,b2,b3,b4 = box
			function tileCode(point)
				x,y = point
				code = 0
				if y>b1 code=code|1 end
				if y<b2 code=code|2 end
				if x>b3 code=code|4 end
				if x<b4 code=code|8 end
				return code
			end
			return tileCode
		end

		function pointInPolygonClassification0(pnt)
			# Set the tile coder w.r.t. the point we are checking
			x,y = pnt
			xmin,xmax,ymin,ymax = x,x,y,y
			tilecode = setTile([ymax,ymin,xmax,xmin])
			count,status = 0,0

			for k in 1:EV.m
				edge = EV[k,:]
				p1, p2 = V[edge.nzind[1], :], V[edge.nzind[2], :]
				(x1,y1),(x2,y2) = p1,p2
				c1,c2 = tilecode(p1),tilecode(p2)
				# For each edge we identify via:
				#  - xor: the different sides
				#  -  or: all the direction involved
				#  - and: the cardinal direction (if unique)
				c_edge, c_un, c_int = xor(c1, c2), c1|c2, c1&c2

				if (c_edge == 0) & (c_un == 0) return "p_on"
				elseif (c_edge == 12) & (c_un == c_edge) return "p_on"
				elseif c_edge == 3
					if c_int == 0 return "p_on"
					elseif c_int == 4 count += 1 end
				elseif c_edge == 15
					x_int = ((y-y2)*(x1-x2)/(y1-y2))+x2
					if x_int > x count += 1
					elseif x_int == x return "p_on" end
				elseif (c_edge == 13) & ((c1==4) | (c2==4))
						status, count = crossingTest(1,2,status,count)
				elseif (c_edge == 14) & ((c1==4) | (c2==4))
						status, count = crossingTest(2,1,status,count)
				elseif c_edge == 7 count += 1
				elseif c_edge == 11 count = count
				elseif c_edge == 1
					if c_int == 0 return "p_on"
					elseif c_int == 4
						status, count = crossingTest(1,2,status,count)
					end
				elseif c_edge == 2
					if c_int == 0 return "p_on"
					elseif c_int == 4
						status, count = crossingTest(2,1,status,count)
					end
				elseif (c_edge == 4) & (c_un == c_edge) return "p_on"
				elseif (c_edge == 8) & (c_un == c_edge) return "p_on"
				elseif c_edge == 5
					if (c1==0) | (c2==0) return "p_on"
					else
						status, count = crossingTest(1,2,status,count)
					end
				elseif c_edge == 6
					if (c1==0) | (c2==0) return "p_on"
					else
						status, count = crossingTest(2,1,status,count)
					end
				elseif (c_edge == 9) & ((c1==0) | (c2==0)) return "p_on"
				elseif (c_edge == 10) & ((c1==0) | (c2==0)) return "p_on"
				end
			end

			if (round(count)%2)==1
				return "p_in"
			else
				return "p_out"
			end
		end
		return pointInPolygonClassification0
	end

	ret = pointInPolygonClassification(V, copEV)(point)
	return "p_in" == ret || ret == "p_on"
end


using OhMyJulia
using DataFrames
using PyCall
using NaturalSort
using Fire
using JSON
@pyimport gurobipy as grb

include("path.jl")
include("weight.jl")

#const data_list = ["abilene", "gscale", "Cernet", "Cesnet201006", "Chinanet", "Esnet", "Garr200112",
#                   "Grnet", "Ibm", "jelly16", "Nordu1997", "Sprint", "Tinet"]
const data_list = ["Cernet" , "Globalcenter" , "Goodnet" , "Gridnet" , "gscale" , "Janetbackbone" ,
"Rediris" , "Renater2010" , "Sinet" , "Sprint" , "SprintINET" , "Surfnet" ,
"SwitchL3" , "TataNld" , "Tinet" , "Uninett2011" , "Uunet" , "VtlWavenet2011" , "Xeex" , "Xspedius"]

const algo_list = ["Ecmp", "Edksp", "Ksp", "Custom", "Raeke", "Vlb"]

function read_topo(file)
    nodes, edges = Set{String}(), String[]
    for line in eachline(file) @when (m = match(r"(s\d+) -> (s\d+)", line)) != nothing
        push!(nodes, m[1], m[2])
        push!(edges, "$(m[1]) $(m[2])")
    end
    nodes, edges
end

function read_demand(hosts, demands)
    results = []
    hosts = readlines(hosts)
    n = length(hosts)
    for line in eachline(demands)
        line = map(x->parse(f64, x), split(line, ' '))
        demand = Dict{String, Float64}()
        for i in 1:n, j in 1:n @when i != j
            demand["$(hosts[i]) $(hosts[j])"] = line[(i-1) * n + j] / 2^20 # to Megabyte
        end
        push!(results, demand)
    end
    results
end

function read_budget(hosts, budgets)
    Dict(zip(map(x->replace(x, 'h', 's'), readlines(hosts)),
             map(x->parse(Int, x),        readlines(budgets))))
end

function write_scheme(io, scheme, x=Dict())
    for (pair, paths) in scheme
        println(io, pair, ':')
        x[pair] = xx = Dict()
        for (path, w) in paths
            p = map(x->split(x, ' '), path)
            println(io, join(map(car, p) ++ cadr(p[end]), " -> "), " @ ", w)
            xx[join(map(car, p) ++ cadr(p[end]), " -> ")] = w
        end
        println(io)
    end
end

function optimal_mcf(nodes, edges, demand)
    tpair(pair) = split(replace(pair, 'h', 's'), ' ')

    le = Dict(e => Dict() for e in edges)

    conn = Dict(n => (
        [s for s in nodes if "$s $n" in edges],
        [t for t in nodes if "$n $t" in edges]
    ) for n in nodes)

    m = grb.Model("optimal_mcf")
    m[:setParam]("OutputFlag", false)
    Z = m[:addVar](name="Z")

    for (pair, d) in demand
        u, v = tpair(pair)
        luv = Dict(e => (le[e][pair] = m[:addVar](name="l_$(e)_$(u)_$(v)")) for e in edges)
        m[:update]()

        # constraint 1: influx == outflux for core nodes
        for n in nodes @when n ∉ (u, v)
            ti = [luv["$u $n"] for u in car(conn[n])]
            to = [luv["$n $v"] for v in cadr(conn[n])]

            m[:addConstr](py"sum($ti) == sum($to)")
        end

        # constraint 2: demands are fulfilled
        ti = [luv["$x $v"] for x in car(conn[v])]
        to = [luv["$v $x"] for x in cadr(conn[v])]
        m[:addConstr](py"sum($ti) - sum($to) == $d")

        ti = [luv["$x $u"] for x in car(conn[u])]
        to = [luv["$u $x"] for x in cadr(conn[u])]
        m[:addConstr](py"sum($to) - sum($ti) == $d")
    end

    # constraint 3: minimize maximum link utilization
    for (edge, flows) in le
        flows = collect(values(flows))
        m[:addConstr](py"sum($flows) <= 1000 * $Z")
    end

    m[:setObjective](Z)
    m[:optimize]()

    json = Dict("Z" => m[:getObjective]()[:getValue]())

    for (edge, flows) in le, (pair, flow) in flows
        json["$pair $edge"] = flow[:X]
    end

    json
end

function single_failure(nodes, edges, demand, scheme)
    rev(x) = join(reverse(split(x, ' ')), ' ')

    stat(edges, scheme) = begin
        m1_scheme, Z = minimize_maximum_link_utilization(nodes, edges, demand, scheme)
        m2_scheme, Zs = minimize_maximum_link_utilization_then_maximize_throuput(nodes, edges, demand, scheme)
        Z, sum(d / Zs[pair] for (pair, d) in demand)
    end

    json = Dict()

    for e in edges @when (<)(split(e, ' ')...)
        edges_f = filter(x->x ∉ (e, rev(e)), edges)
        scheme_f = Dict()
        for (pair, paths) in scheme
            paths_f = [(path, weight) for (path, weight) in paths if e ∉ path && rev(e) ∉ path]
            # no need to renormalize since we will overwrite them anyway
            isempty(paths_f) && @goto disconnected
            scheme_f[pair] = paths_f
        end

        json[e] = stat(edges_f, scheme_f)
        continue

        @label disconnected
        json[e] = "some pairs have no path"
    end

    json
end

@main function main(data; optimal::Bool=false)
    @assert data in data_list
    io, process = open(`yates -budget 16 $data`)
    path_sets = parse_schemes(io)
    nodes, edges = read_topo("data/$data.dot")
    demands = read_demand("data/$data.hosts", "data/$data.demands")
    budgets = read_budget("data/$data.hosts", "data/$data.budgets")

    for (i, demand) in enumerate(demands)
        if optimal
            open("results/$data-$i-optimal-mcf.json", "w") do fout
                JSON.print(fout, optimal_mcf(nodes, edges, demand), 2)
            end
            continue
        end

        for algo in algo_list, (name, select) in (("program", select_program),
                                                  ("greedy", select_greedy),
                                                  ("hardnop", select_hard_nop)) @when length(nodes) < 16 || name != "greedy"
            json = Dict()

            open("results/$data-$i-$algo-$name.result", "w") do fout
                tic()
                raw_scheme = select(nodes, edges, path_sets[algo], budgets)
                time1 = toq()

                any(isempty, values(raw_scheme)) && return println(fout, "cannot solve")

                tic()
                m1_scheme, Z = minimize_maximum_link_utilization(nodes, edges, demand, raw_scheme)
                time2 = toq()

                tic()
                m2_scheme, Zs = minimize_maximum_link_utilization_then_maximize_throuput(nodes, edges, demand, raw_scheme)
                time3 = toq()

                println(fout, "number of nodes (switch): ", length(nodes))
                json["number of nodes (switch)"] = length(nodes)

                println(fout, "number of edges (core): ", length(edges))
                json["number of edges (core)"] = length(edges)

                println(fout, "number of demand pairs: ", length(demand))
                json["number of demand pairs"] = length(demand)

                println(fout, "average budgets: ", mean(values(budgets)))
                json["average budgets"] = mean(values(budgets))

                println(fout)

                println(fout, "time elapsed during path selection: ", time1, 's')
                json["time elapsed during path selection"] = time1

                println(fout, "time elapsed during first step: ", time2, 's')
                json["time elapsed during first step"] = time2

                println(fout, "time elapsed during first + second step: ", time3, 's')
                json["time elapsed during first + second step"] = time3

                println(fout)

                println(fout, "Z: ", Z)
                json["Z"] = Z

                println(fout, "total throuput before the second step:", sum(values(demand)) / max(Z, 1))
                json["total throuput before the second step"] = sum(values(demand)) / max(Z, 1)

                total_throuput = sum(d / Zs[pair] for (pair, d) in demand)
                println(fout, "total throuput after the second step:", total_throuput)
                json["total throuput after the second step"] = total_throuput

                println(fout)

                println(fout, "number of path through each node:")
                json["number of path through each node"] = x = Dict()

                node_dict = Dict(n => 0 for n in nodes)
                for (pair, paths) in m2_scheme, (path, weight) in paths, edge in path
                    n = split(edge, ' ') |> car
                    if car(n) == 's'
                        node_dict[n] += 1
                    end
                end
                for (node, number) in sort(collect(node_dict), lt=natural, by=car)
                    println(fout, "  ", node, ": ", number)
                    x[node] = number
                end

                println(fout, "  average: ", sum(values(node_dict)) / length(node_dict))
                x["average"] = sum(values(node_dict)) / length(node_dict)

                println(fout)

                println(fout, "number of path, total flow, and link utiliaztion of each edge:")
                json["number of path, total flow, and link utiliaztion of each edge"] = x = Dict()

                edge_dict = Dict(e => f64[] for e in edges)
                for (pair, paths) in m2_scheme, (path, weight) in paths, edge in path @when 'h' ∉ edge
                    push!(edge_dict[edge], demand[pair] * weight / Zs[pair])
                end
                for (edge, flows) in sort(collect(edge_dict), lt=natural, by=car)
                    println(fout, "  ", edge, ": ", length(flows), ", ", sum(flows), ", ", sum(flows) / 10, '%')
                    x[edge] = [length(flows), sum(flows), sum(flows) / 10]
                end

                println(fout, "  average: ", sum(length(v) for v in values(edge_dict)) / length(edge_dict),
                                       ", ", sum(sum(v) for v in values(edge_dict)) / length(edge_dict),
                                       ", ", sum(sum(v) for v in values(edge_dict)) / length(edge_dict) / 10, '%')
                x["average"] = [sum(length(v) for v in values(edge_dict)) / length(edge_dict),
                                sum(sum(v) for v in values(edge_dict)) / length(edge_dict),
                                sum(sum(v) for v in values(edge_dict)) / length(edge_dict) / 10]

                println(fout)

                println(fout, "number of path and demand ratio of each st pair:")
                json["number of path and demand ratio of each st pair"] = x = Dict()

                pair_dict = Dict(pair => length(paths) for (pair, paths) in m2_scheme)
                for (pair, nflows) in sort(collect(pair_dict), lt=natural, by=car)
                    println(fout, "  ", pair, ": ", nflows, ", ", 1 / Zs[pair] * 100, '%')
                    x[pair] = [nflows, 1 / Zs[pair] * 100]
                end
                println(fout, "  average: ", sum(values(pair_dict)) / length(pair_dict),
                                       ", ", sum(d / Zs[pair] for (pair, d) in demand) / sum(values(demand)) * 100, '%')
                x["average"] = [sum(values(pair_dict)) / length(pair_dict),
                                sum(d / Zs[pair] for (pair, d) in demand) / sum(values(demand)) * 100]

                println(fout)

                println(fout, "Z, total throuput, and throuput ratio at single edge failure:")
                json["Z, total throuput, and throuput ratio at single edge failure"] = x = Dict()

                edge_dict = single_failure(nodes, edges, demand, raw_scheme)
                for (edge, res) in sort(collect(edge_dict), lt=natural, by=car)
                    if res isa String
                        println(fout, "  ", edge, ": ", res)
                        x[edge] = res
                    else
                        Z, tt = res
                        println(fout, "  ", edge, ": ", Z, ", ", tt, ", ", tt / total_throuput * 100, '%')
                        x[edge] = Z, tt, tt / total_throuput * 100
                    end
                end
                for r in (90, 95, 99, 99.9, 99.99)
                    println(fout, "  P(T > $r%): ", mean(p isa Tuple && p[3] >= r for (e, p) in x if !startswith(e, 'P')))
                    x["P(T > $r%)"] = mean(p isa Tuple && p[3] >= r for (e, p) in x if !startswith(e, 'P'))
                end
                println(fout)

                println(fout, "path set:")
                json["path set"] = x = Dict()
                write_scheme(fout, path_sets[algo], x)
                println(fout)

                println(fout, "paths selected:")
                json["paths selected"] = x = Dict()
                write_scheme(fout, raw_scheme, x)
                println(fout)

                println(fout, "paths after the first step (minimize maximum link utilization):")
                json["paths after the first step (minimize maximum link utilization)"] = x = Dict()
                write_scheme(fout, m1_scheme, x)
                println(fout)

                println(fout, "paths after the second step (maximize total throuput):")
                json["paths after the second step (maximize total throuput)"] = x = Dict()
                write_scheme(fout, m2_scheme, x)
            end

            open("results/$data-$i-$algo-$name.json", "w") do fout
                JSON.print(fout, json, 2)
            end
        end
    end
end


mutable struct Tree <: AbstractAgent
    id::Int
    pos::Tuple{Int,Int}
    status::Bool  # true is green and false is burning
end

function model_initiation(; f = 0.02, d = 0.8, p = 0.01, griddims=(100,100), seed = 111)
    Random.seed!(seed)
    space = GridSpace(griddims, moore = true)
    properties = Dict(:f => f, :d => d, :p => p)
    forest = AgentBasedModel(Tree, space; properties = properties)

    ## create and add trees to each node with probability d,
    ## which determines the density of the forest
    for node in nodes(forest)
        if rand() ≤ forest.d
            add_agent!(node, forest, true)
        end
    end
    return forest
end

function forest_step!(forest)
  for node in nodes(forest, by = :random)
    nc = get_node_contents(node, forest)
    ## the cell is empty, maybe a tree grows here
    if length(nc) == 0
        rand() ≤ forest.p && add_agent!(node, forest, true)
    else
      tree = forest[nc[1]] # by definition only 1 agent per node
      if tree.status == false  # if it is has been burning, remove it.
        kill_agent!(tree, forest)
      else
        if rand() ≤ forest.f  # the tree ignites spontaneously
          tree.status = false
        else  # if any neighbor is on fire, set this tree on fire too
          for cell in node_neighbors(node, forest)
            neighbors = get_node_contents(cell, forest)
            length(neighbors) == 0 && continue
            if any(n -> !forest.agents[n].status, neighbors)
              tree.status = false
              break
            end
          end
        end
      end
    end
  end
end


import Base: iterate, length

"""
    ordered_edges(dag)

Iterator of edges of a dag, ordered in Chickering order:

    Perform a topological sort on the NODES
    while there are unordered EDGES in g
        Let y be the lowest ordered NODE that has an unordered EDGE incident into it
        Let x be the highest ordered NODE for which x => y is not ordered
        return x => y
    end
"""
ordered_edges(g) = OrderedEdges(g)

struct OrderedEdges
    g
    ys::Vector{Int}
    yp::Vector{Int}
    n::Int
    m::Int

    function OrderedEdges(g::DiGraph)
        ys = topological_sort_by_dfs(g)
        yp = sortperm(ys)
        new(g, ys, yp, nv(g), ne(g))
    end
end

length(iter::OrderedEdges) = iter.m

function iterate(iter::OrderedEdges, state=(0, 0, 0, Int[]))
    i, j, k, xs  = state
    k >= iter.m && return nothing
    g, ys, yp = iter.g, iter.ys, iter.yp
    while i == 0
        j = j + 1
        xs = sort(inneighbors(g, ys[j]), by=x->yp[x])
        i = length(xs)
    end
    Edge(xs[i], ys[j]), (i - 1, j, k + 1, xs)
end

# came in handy while prototyping
function chickering_order(g)
    outp = Pair{Int,Int}[]
    ys = topological_sort_by_dfs(g)
    yp = sortperm(ys)
    n = nv(g)
    i = 0 # src index
    j = 0 # dst index
    xs = Int[]
    while true
        while i == 0
            j = j + 1
            if j > n
                @goto ende
            end
            xs = sort(inneighbors(g, ys[j]), by=x->yp[x])
            i = length(xs)
        end
        x, y = xs[i], ys[j]
        push!(outp, x=>y)
        i -= 1
    end
    @label ende
    outp
end

@enum Status reversible=0 compelled=1 unknown=2
"""
    cpdag(skel::DiGraph)

Reference: M. Chickering: Learning equivalence classes of Bayesian
network structures. Journal of Machine Learning Research 2 (2002).
M. Chickering: A Transformational Characterization of Equivalent Bayesian Network Structures. (1995).

Note that the edge order defined there is already partly encoded into the
representation of a DiGraph.
"""
function cpdag(skel::DiGraph)
    g = copy(skel)
    ys = topological_sort_by_dfs(g)
    yp = sortperm(ys)
    n = nv(g)
    m = ne(g)
    label = Dict(zip(map(Pair, edges(g)), repeated(unknown)))

    i = 0 # src index
    j = 0 # dst index
    xs = Int[]
    while true
        while i == 0
            j = j + 1
            if j > n
                @goto ende
            end
            xs = sort(inneighbors(g, ys[j]), by=x->yp[x])
            i = length(xs)
        end

        x, y = xs[i], ys[j]

        if label[x => y] == unknown
            for w in inneighbors(g, x)
                label[w => x] == compelled || continue
                if !has_edge(g, w => y)
                    for z in inneighbors(g, y)
                        label[z => y] = compelled
                    end
                    @goto next
                else
                    label[w => y] = compelled
                end
            end

            lbl = reversible
            for z in inneighbors(g, y)
                if x == z || has_edge(g, z => x)
                    continue
                end
                lbl = compelled
            end
            for z in inneighbors(g, y)
                if label[z => y] == unknown
                    label[z => y] = lbl
                end
            end
        end
        @label next

        i = i - 1
    end
    @label ende
    for e in collect(edges(g))
        x, y = Tuple(e)
        if label[x=>y] == reversible
            add_edge!(g, y => x)
        end
    end
    g
end


########################################################################
# Atoms of the nuclear-norm ball.
########################################################################
"""
    NucBallAtom(m, n, u, v)

Atom in the NucBall of dimension m×n.
"""
mutable struct NucBallAtom{T1<:Int64, T2<:Vector{Float64}} <: AbstractAtom
    m::T1
    n::T1
    u::T2
    v::T2
    function NucBallAtom(u::Vector{Float64}, v::Vector{Float64})
        m = length(u)
        n = length(v)
        new{Int64, Vector{Float64}}(m, n, u, v)
    end
end

"""
    *(M::AbstractLinearOp, a::NucBallAtom) -> Y

Multiply a nuclear-norm atom by a linear map, and return the matrix `Y`.
"""
Base.:(*)(M::LinearOp, a::NucBallAtom{Int64, Vector{Float64}}) = M*vec(a.u*a.v')
LinearAlgebra.mul!(Ma::Vector{Float64}, M::LinearOp, a::NucBallAtom{Int64, Vector{Float64}}) = mul!(Ma, M, vec(a.u*a.v'))
Base.vec(a::NucBallAtom{Int64, Vector{Float64}}) = vec(a.u*a.v')
Base.length(a::NucBallAtom{Int64, Vector{Float64}}) = a.m * a.n
Base.size(a::NucBallAtom{Int64, Vector{Float64}}) = a.m, a.n

########################################################################
# Nuclear-norm ball.
########################################################################
"""
    NucBall(m, n, maxrank=min(m,n))

Atomic set defined by nuclear-norm ball for the space of `m`-by-`n` matrices.
The atomic set takes two optional parameters:

`maxrank` is the maximum dimension of the face exposed by a vector.
"""
struct NucBall{T<:Int64} <: AbstractAtomicSet
    m::T
    n::T
    maxrank::T
    function NucBall(m::Int64, n::Int64, maxrank::Int64)
        m ≥ 1 || throw(DomainError(m,"m must be a positive integer"))
        n ≥ 1 || throw(DomainError(n,"n must be a positive integer"))
        min(m,n) ≥ maxrank ≥ 0 || throw(DomainError(maxrank,"maxrank must be ≥ 0"))
        new{Int64}(m, n, maxrank)
    end
end
NucBall(m::Int64, n::Int64; maxrank=min(m, n)) = NucBall(m, n, maxrank)

"""
    gauge(A::NucBall, x::Matrix)

Gives the nuclear-norm of `x`.
"""
gauge(::NucBall{Int64}, x::Matrix{Float64}) = sum(svdvals(x))
gauge(A::NucBall{Int64}, x::Vector{Float64}) = gauge(A, reshape(x, A.m, A.n))

"""
    support(A::NucBall, z::AbstractMatrix)

Gives the largest singular value of `z`.
"""
function support(A::NucBall{Int64}, z::AbstractMatrix{Float64})
    r = A.maxrank
    if r == 0
        s = [0]
    else
        ~, s, ~ = svds(z, nsv=1)[1]
    end
    return s[1]
end

support(A::NucBall{Int64}, z::AbstractVector{Float64}) = support(A, reshape(z, A.m, A.n))

"""
    expose!(A::NucBall, z::Matrix, a::NucBallAtom)

Gives the top singular vectors of `z`.
"""
function expose!(::NucBall{Int64}, z::AbstractMatrix{Float64}, a::NucBallAtom{Int64, Vector{Float64}})
    u, ~, v = svds(z, nsv=1)[1]
    a.u .= u[:]
    a.v .= v[:]
    return nothing
end
expose!(A::NucBall{Int64}, z::AbstractVector{Float64}, a::NucBallAtom{Int64, Vector{Float64}}) = expose!(A, reshape(z, A.m, A.n), a)

function expose(A::NucBall{Int64}, z::AbstractMatrix{Float64})
    m, n = size(A)
    a = NucBallAtom(zeros(m), zeros(n))
    expose!(A, z, a)
    return a
end
expose(A::NucBall{Int64}, z::AbstractVector{Float64}) = expose(A, reshape(z, A.m, A.n))

Base.length(A::NucBall{Int64}) = A.m*A.n
Base.size(A::NucBall{Int64}) = A.m, A.n
rank(A::NucBall{Int64}) = A.maxrank
atom_name(A::NucBall{Int64}) = "nuclear-norm ball"
atom_description(A::NucBall{Int64}) = "{ uv^T | u ∈ ℝᵐ, v ∈ ℝⁿ, ||u||₂ = ||v||₂ ≤ 1 }"
atom_parameters(A::NucBall{Int64}) = "m = $(A.m), n = $(A.n), maxrank = $(A.maxrank)"

########################################################################
# Face of the nuclear-norm ball.
########################################################################

mutable struct NucBallFace{T1<:Int64, T2<:Matrix{Float64}, T3<:Adjoint{Float64, Matrix{Float64}}} <: AbstractFace
    m::T1
    n::T1
    r::T1
    U::T2
    V::T3
    function NucBallFace(U::Matrix{Float64}, V::Adjoint{Float64, Matrix{Float64}})
        m = size(U, 1)
        n = size(V, 1)
        r = size(U, 2)
        new{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}(m, n, r, U, V)
    end
end

"""
    face(A, z) -> F

Return a face `F` of the atomic set `A` exposed by the vector `z`. The dimension of
the exposed face is limited by `k=maxrank(A)`. If the dimension is being limited
by `k`, then the routine returns a set of rank `k` atoms that define the subset
of the exposed face.
"""
function face!(A::NucBall{Int64}, z::AbstractMatrix{Float64}, F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}})
    r = A.maxrank
    if r == 0
        U = zeros(A.m, 1)
        V = zeros(1, A.n)'
    elseif r < min(A.m, A.n) - 1
        # Arpack won't accept nsv too large
        U, _, V = svds(z, nsv=r)[1]
        F.U .= U
        F.V .= V
    else
        Fa = svd(z)
        F.U .= Fa[1].U[:, 1:r]
        F.V .= Fa[1].V[:, 1:r]
    end
    return nothing
end
face!(A::NucBall{Int64}, z::AbstractVector{Float64}, F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = face!(A, reshape(z, A.m, A.n), F)

function face(A::NucBall{Int64}, z::AbstractMatrix{Float64})
    m, n, r = A.m, A.n, max(A.maxrank, 1)
    U = zeros(m, r)
    V = zeros(r, n)'
    F = NucBallFace(U, V)
    face!(A, z, F)
    return F
end
face(A::NucBall{Int64}, z::AbstractVector{Float64}) = face(A, reshape(z, A.m, A.n))

"""
Given a face and a set of weights, reveal the corresponding
point on the face.
"""
Base.:(*)(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}, S::Matrix{Float64}) = vec(F.U*S*F.V')
Base.:(*)(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}, c::Vector{Float64}) = F*reshape(c, F.r, F.r)

"""
    *(M::LinearOp, F::NucBallFace)

Return a LinearMap `L` whose forward and adjoing operators
are defined as

    L*p = M*vec(U*matrix(p)*V')
    L'*q = vec(U'*matrix(M'*q)*V)

where the `n`-by-`k` matrices `U` and `V` are the left and right
singular vectors that define the face `F` of the nuclear-norm ball.
"""
function Base.:(*)(M::LinearOp, F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}})
    m, n, r = F.m, F.n, F.r
    f = p->M*vec(F.U*reshape(p, r, r)*F.V')
    fc = q->vec(F.U'*reshape(M'*q, m, n)*F.V)
    return LinearMap(f, fc, size(M, 1), r*r)
end

Base.:(*)(λ::Real, F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = λ*vec(F)
Base.length(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = F.m*F.n
Base.size(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = F.m, F.n
rank(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = F.r^2
vec(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = I(F.m*F.n)*F
face_name(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = "face of nuclear-norm ball"
face_parameters(F::NucBallFace{Int64, Matrix{Float64}, Adjoint{Float64, Matrix{Float64}}}) = "m = $(F.m), n = $(F.n), rank = $(rank(F))"


using LinearAlgebra, Statistics, BenchmarkTools, Interpolations, Parameters, Plots, QuantEcon, Roots, Optim, Random, IterTools, BasicInterpolators

function K!(Kg, g, grid, β, ∂u∂c, f, f′, shocks)
# This function requires the container of the output value as argument Kg
    # Construct radial basis functions
    #g_func = RBFInterpolator(grid, vec(g), 1)
    g_func = LinearInterpolation(vec(grid), vec(g), extrapolation_bc=Line())
    # solve for updated consumption value
    for i in 1:length(grid[:,1])
        y=grid[i,:]
        function h(c) # implies c is similar object to grid, this is not compatible with our root finding method currently
            vals = ∂u∂c.(g_func.(sum(f.(y - c), dims=2)  * shocks)) .* f′(y - c) .* shocks
            return ∂u∂c(c) - β * mean(vals)
        end
        Kg[i] = find_zero(h, (1e-10, y[1] .- 1e-10)) # would need to make this N (or at least 2-dimensional for a test)
    end
    return Kg
end

# The following function does NOT require the container of the output value as argument
K(g, grid, β, ∂u∂c, f, f′, shocks) = K!(similar(g), g, grid, β, ∂u∂c, f, f′, shocks)

# returns a cartesian product matrix that is compatible with the RBF interpolation
function cartProd(gridMin,gridMax,gridSize, numSec)
    # creates a vector of all the ranges we need
    ranges = fill(LinRange(gridMin,gridMax,gridSize),numSec)
    # initialise empty array
    grid = Array{Float64}(undef,gridSize^numSec,numSec)
    for (index, p) in enumerate(product(ranges...))
        # coordinates in each dimension are across rows
        grid[index, :] = collect(p)
    end
    return grid
end

function verify_true_policy(m, shocks, c_star)
    # compute (Kc_star)
    @unpack grid, β, ∂u∂c, f, f′ = m
    c_star_new = K(c_star, grid, β, ∂u∂c, f, f′, shocks)

    #compare
    return (c_star-c_star_new)./c_star
end

function check_convergence(m, shocks, c_star, g_init; n_iter = 15)
    @unpack grid, β, ∂u∂c, f, f′ = m
    g = g_init;
    for i in 1:n_iter
        new_g = K(g, grid, β, ∂u∂c, f, f′, shocks)
        g = new_g
    end
    #compare
    return (c_star-g)./c_star
end

Model=@with_kw (α = 0.65,                            # Productivity parameter
                β = 0.95,                            # Discount factor
                γ = 1.0,                             # Risk aversion
                μ = 0.0,                             # First parameter in lognorm(μ, σ)
                s = 0.1,                             # Second parameter in lognorm(μ, σ)
                numSec = 1,
                gridMin = 1e-6,                     # Smallest grid point
                gridMax = 4.0,                      # Largest grid point
                gridSize = 20,                     # Number of grid points
                grid = cartProd(gridMin, gridMax, gridSize, numSec), # Grid
                u = (c, γ = γ) -> isoelastic(c, γ),  # utility function
                ∂u∂c = c -> c^(-γ),                  # u′
                f = k -> k^α,                        # production function
                f′ = k -> α * k^(α - 1),             # f′
                )

m = Model();

Random.seed!(42) # for reproducible results.
shock_size = 250 # number of shock draws in Monte Carlo integral
shocks = collect(exp.(m.μ .+ m.s * randn(shock_size))); # generate shocks

c_star = sum((1 - m.α * m.β) .* m.grid, dims=2) # true policy (c_star)
verify_true_policy(m, shocks, c_star)

isoelastic(c, γ) = isone(γ) ? log(c) : (c^(1 - γ) - 1) / (1 - γ)

check_convergence(m, shocks, c_star, m.grid, n_iter = 15)


module AssemblersTests

using Test
using Gridap
using SparseArrays
using SparseMatricesCSR

for T in [SparseMatrixCSC, SparseMatrixCSR, SymSparseMatrixCSR]
    model = CartesianDiscreteModel(domain=(0.0,1.0,0.0,1.0), partition=(2,2))

    tags = [1,2,3,4,6,5]

    order = 1
    fespace = H1ConformingFESpace(Float64,model,order,tags)

    assem = SparseMatrixAssembler(T,fespace,fespace)

    trian = Triangulation(model)

    quad = CellQuadrature(trian,degree=2)

    basis = CellBasis(fespace)

    a(v,u) = varinner(∇(v), ∇(u))

    bfun(x) = x[2]

    b(v) = varinner(v,CellField(trian,bfun))

    mmat = integrate(a(basis,basis),trian,quad)

    bvec = integrate(b(basis),trian,quad)

    vec = assemble(assem, bvec)

    mat = assemble(assem, mmat)

    x = mat \ vec

    assemble!(vec,assem, bvec)

    assemble!(mat,assem, mmat)

    x2 = mat \ vec

    @test x ≈ x2

    @test vec ≈ [0.0625, 0.125, 0.0625]

    @test mat[1, 1]  ≈  1.333333333333333
    @test mat[2, 1]  ≈ -0.33333333333333
    @test mat[1, 2]  ≈ -0.33333333333333
    @test mat[2, 2]  ≈ 2.666666666666666
    @test mat[3, 2]  ≈ -0.33333333333333
    @test mat[2, 3]  ≈ -0.33333333333333
    @test mat[3, 3]  ≈ 1.333333333333333

    cellids = IdentityCellNumber(Int,length(bvec))

    vec2 = assemble(assem, (bvec,cellids))

    @test vec2 == vec

    vec3 = assemble(assem, (bvec,cellids), (bvec,cellids))
    @test vec3 ≈ 2*vec

    assemble!(vec3, assem, (bvec,cellids), (bvec,cellids))
    @test vec3 ≈ 2*vec

    mat2 = assemble(assem, (mmat,cellids,cellids) )

    @test mat2 == mat

    mat3 = assemble(assem, (mmat,cellids,cellids), (mmat,cellids,cellids))
    @test mat3 ≈ 2*mat

    assemble!(mat3, assem, (mmat,cellids,cellids), (mmat,cellids,cellids))
    @test mat3 ≈ 2*mat
end

end # module AssemblersTests


# adjoint-project.jl
# test adjoint consistency for SPECT projector/back-projector on very small case

using SPECTrecon: SPECTplan
using SPECTrecon: project!, backproject!
using SPECTrecon: project, backproject
using LinearMapsAA: LinearMapAA
using LinearAlgebra: dot
using Test: @test, @testset

@testset "adjoint-project-matrix" begin
    T = Float32
    nx = 8; ny = nx
    nz = 6
    nview = 7

    mumap = rand(T, nx, ny, nz)

    px = 5
    pz = 3
    psfs = rand(T, px, pz, ny, nview)
    psfs = psfs .+ mapslices(reverse, psfs, dims = [1, 2]) # symmetrize
    psfs = psfs ./ mapslices(sum, psfs, dims = [1, 2])

    dy = T(4.7952)
    idim = (nx,ny,nz)
    odim = (nx,nz,nview)

    for interpmeth in (:one, :two)
        for mode in (:fast, :mem)
            plan = SPECTplan(mumap, psfs, dy; interpmeth, mode)
            forw! = (y,x) -> project!(y, x, plan)
            back! = (x,y) -> backproject!(x, y, plan)
            A = LinearMapAA(forw!, back!, (prod(odim),prod(idim)); T, idim, odim)
            @test Matrix(A)' ≈ Matrix(A')
        end
    end

    for interpmeth in (:one, :two)
        for mode in (:fast, :mem)
            forw = x -> project(x, mumap, psfs, dy; interpmeth, mode)
            back = y -> backproject(y, mumap, psfs, dy; interpmeth, mode)
            A = LinearMapAA(forw, back, (prod(odim),prod(idim)); T, idim, odim)
            @test Matrix(A)' ≈ Matrix(A')
        end
    end

end

@testset "proj!-adj-test-dot" begin
    T = Float64
    nx = 32; ny = nx
    nz = 20
    nview = 30

    mumap = rand(T, nx, ny, nz)

    px = 5
    pz = 3
    psfs = rand(T, px, pz, ny, nview)
    psfs = psfs .+ mapslices(reverse, psfs, dims = [1, 2])
    psfs = psfs ./ mapslices(sum, psfs, dims = [1, 2])

    dy = T(4.7952)

    plan = SPECTplan(mumap, psfs, dy)
    show(isinteractive() ? stdout : devnull, "text/plain", plan)

    for interpmeth in (:one, :two)
        for mode in (:fast, :mem)
            plan = SPECTplan(mumap, psfs, dy; interpmeth, mode)
            x = randn(T, nx, ny, nz)
            y = randn(T, nx, nz, nview)
            output_x = similar(y)
            output_y = similar(x)
            project!(output_x, x, plan)
            backproject!(output_y, y, plan)
            @test isapprox(dot(y, output_x), dot(x, output_y); rtol = 1e-5)
        end
    end

    for interpmeth in (:one, :two)
        for mode in (:fast, :mem)
            plan = SPECTplan(mumap, psfs, dy; interpmeth, mode)
            x = randn(T, nx, ny, nz)
            y = randn(T, nx, nz, nview)
            output_x = similar(y)
            output_y = similar(x)
            backproject!(output_y, y, plan)
            project!(output_x, x, plan)
            @test isapprox(dot(y, output_x), dot(x, output_y); rtol = 1e-5)
        end
    end

    for interpmeth in (:one, :two)
        for mode in (:fast, :mem)
            x = randn(T, nx, ny, nz)
            y = randn(T, nx, nz, nview)
            output_x = project(x, mumap, psfs, dy; interpmeth, mode)
            output_y = backproject(y, mumap, psfs, dy; interpmeth, mode)
            @test isapprox(dot(y, output_x), dot(x, output_y); rtol = 1e-5)
        end
    end

end


#=
1D Euler equations
=#

### If the Finch package has already been added, use this line #########
using Finch # Note: to add the package, first do: ]add "https://github.com/paralab/Finch.git"

### If not, use these four lines (working from the examples directory) ###
# if !@isdefined(Finch)
#     include("../Finch.jl");
#     using .Finch
# end
##########################################################################

init_finch("FVeuler1d");

useLog("FVeuler1dlog", level=3)

# Configuration setup
domain(1) # 1D
solverType(FV)
timeStepper(RK4)
timeInterval(0.2)

# Mesh
ord = 1 # order used for flux
n = Int(ceil(150/ord)) # number of elements scaled for subdividing for higher orders
mesh(LINEMESH, elsperdim=n) # A uniform 1D mesh of the unit interval.

if ord > 1
    finiteVolumeOrder(ord)
end

# Primitive variables
r = variable("r", location=CELL)
u = variable("u", location=CELL)
p = variable("p", location=CELL)
U = [r, u, p];

# Conserved variables
q1 = variable("q1", location=CELL)
q2 = variable("q2", location=CELL)
q3 = variable("q3", location=CELL)
Q = [q1, q2, q3];

# Transformations between the variable sets
U2Q = variableTransform(U, Q,
(U) -> (
    gamma = 1.4;
    Q = [U[1],
         U[1] * U[2],
         U[3] / (gamma-1) + 0.5 * U[1] * U[2] * U[2]];
    return Q;
))
Q2U = variableTransform(Q, U,
(Q) -> (
    gamma = 1.4;
    U = [Q[1],
         Q[2] / Q[1],
         (gamma-1) * (Q[3] - 0.5 * Q[2] * Q[2] / Q[1])];
    return U;
))

# Shock tube initial conditions
initial(r, "x<0.3 ? 1 : 0.125")
initial(u, "x<0.3 ? 0.75 : 0")
initial(p, "x<0.3 ? 1 : 0.1")
Finch.eval_initial_conditions(); # set U initial conditions
transformVariable(U2Q); # set Q initial conditions

# Boundary conditions
boundary(q1, 1, NO_BC) # equivalent to transmissive bdry
boundary(q2, 1, NO_BC)
boundary(q3, 1, NO_BC)

# The commented function is a compact version for inspection.
# The longer function below that is similar, but arranged to work
# as a callback in the flux function.
#=
function flux_u(U)
    local r = U[1];
    local u = U[2];
    local p = U[3];
    local gamma = 1.4;

    FU = [ r * u,
           p + r * u*u,
           u * (p + p/(gamma-1) + r*u*u*0.5)];

    # Steger-Warming splitting
    c = sqrt(gamma*p/r)
    M = u/c
    if M < -1
        Fm = FU;
        Fp = zeros(3);
    elseif M > 1
        Fm = zeros(3);
        Fp = FU;
    elseif M >= -1 && M < 0
        Fp = r*(u+c)/(2*gamma) .* [1, (u+c), (u+c)^2/2+(3-gamma)/(gamma-1)*c^2/2];
        Fm = FU - Fp;
    else # 0 < M < 1
        Fm = r*(u-c)/(2*gamma) .* [1, (u-c), (u-c)^2/2+(3-gamma)/(gamma-1)*c^2/2];
        Fp = FU - Fm;
    end

    return (Fm, Fp);
end
=#
function flux_u2(r1,u1,p1, r2,u2,p2, comp, normal)
    gamma = 1.4;
    # The normal points from cell 1 to cell 2
    # So "left" is currently cell 1
    if normal < 0
        # swap left and right
        tmpr = r1; tmpu = u1; tmpp = p1;
        r1 = r2;   u1 = u2;   p1 = p2;
        r2 = tmpr; u2 = tmpu; p2 = tmpp;
    end
    c1 = sqrt(gamma*p1/r1);
    M1 = u1/c1;
    c2 = sqrt(gamma*p2/r2);
    M2 = u2/c2;

    if comp == 1 # rho
        if M1 < -1
            Fp = 0;
        elseif M1 > 1
            Fp = r1 * u1;
        elseif M1 >= -1 && M1 < 0
            Fp = r1*(u1+c1)/(2*gamma);
        else # 0 < M < 1
            Fp = r1*u1 - r1*(u1-c1)/(2*gamma);
        end

        if M2 < -1
            Fm = r2*u2;
        elseif M2 > 1
            Fm = 0;
        elseif M2 >= -1 && M2 < 0
            Fm = r2*u2 - r2*(u2+c2)/(2*gamma);
        else # 0 < M < 1
            Fm = r2*(u2-c2)/(2*gamma);
        end

    elseif comp == 2 # u
        if M1 < -1
            Fp = 0;
        elseif M1 > 1
            Fp = p1 + r1 * u1*u1;
        elseif M1 >= -1 && M1 < 0
            Fp = r1*(u1+c1)/(2*gamma) * (u1+c1);
        else # 0 < M < 1
            Fp = p1 + r1 * u1*u1 - r1*(u1-c1)/(2*gamma) * (u1-c1);
        end

        if M2 < -1
            Fm = p2 + r2 * u2*u2;
        elseif M2 > 1
            Fm = 0;
        elseif M2 >= -1 && M2 < 0
            Fm = p2 + r2 * u2*u2 - r2*(u2+c2)/(2*gamma) * (u2+c2);
        else # 0 < M < 1
            Fm = r2*(u2-c2)/(2*gamma) * (u2-c2);
        end

    elseif comp == 3 # p
        if M1 < -1
            Fp = 0;
        elseif M1 > 1
            Fp = u1 * (p1 + p1/(gamma-1) + r1*u1*u1*0.5);
        elseif M1 >= -1 && M1 < 0
            Fp = r1*(u1+c1)/(2*gamma) * ((u1+c1)^2/2+(3-gamma)/(gamma-1)*c1^2/2);
        else # 0 < M < 1
            Fp = u1 * (p1 + p1/(gamma-1) + r1*u1*u1*0.5) - (r1*(u1-c1)/(2*gamma) * ((u1-c1)^2/2+(3-gamma)/(gamma-1)*c1^2/2));
        end

        if M2 < -1
            Fm = u2 * (p2 + p2/(gamma-1) + r2*u2*u2*0.5);
        elseif M2 > 1
            Fm = 0;
        elseif M2 >= -1 && M2 < 0
            Fm = u2 * (p2 + p2/(gamma-1) + r2*u2*u2*0.5) - (r2*(u2+c2)/(2*gamma) * ((u2+c2)^2/2+(3-gamma)/(gamma-1)*c2^2/2));
        else # 0 < M < 1
            Fm = r2*(u2-c2)/(2*gamma) * ((u2-c2)^2/2+(3-gamma)/(gamma-1)*c2^2/2);
        end
    end
    return (Fp + Fm) * normal;
end

callbackFunction(flux_u2);

flux(Q, ["flux_u2(left(r),left(u),left(p), right(r),right(u),right(p), $i, normal())" for i in 1:3])
#=
The line above is in a compact form. A verbose form would be:
flux([q1,q2,q3], ["flux_u2(left(r),left(u),left(p), right(r),right(u),right(p), 1, normal())"
                  "flux_u2(left(r),left(u),left(p), right(r),right(u),right(p), 2, normal())"
                  "flux_u2(left(r),left(u),left(p), right(r),right(u),right(p), 3, normal())"])

Note:
To get a reconstructed value for a variable r,
    "r"         -> A centered interpolation
    "left(r)"   -> extrapolated from cells on side 1 of the face (the normal vector points from side 1 to side 2)
    "right(r)"  -> similar, but for side 2
    "central(r)"-> similar to "r"
In all cases, neighborhoods that are truncated by a boundary will simply use fewer cells.
Boundary faces will use one ghost cell identical to the interior cell.
The side1/2 orientation is only tied to the normal vector, so if the physical orientation is needed,
pass the normal vector as well with "normal()".
=#

# For inspecting the generated code, look in "fveuler1dcode.jl"
# If you wish to modify it and reimport, use importCode("fveuler1dcode") instead.
# Importing will override anything previously generated.
exportCode("fveuler1dcode")

# Need to update primitive variables each time conserved variables are changed.
@postStepFunction(
    transformVariable(Q2U)
);

# Solve for the conserved variables because the equations were defined for them.
# Since primitive variables are also updated each step, they are also solved for.
solve(Q)

finalize_finch()

##### Uncomment below to plot

x = Finch.fv_info.cellCenters[:];

using Plots
pyplot();
display(plot([x x x], [r.values[:] u.values[:] p.values[:]], markershape=:circle, label=["r" "u" "p"]))


struct Clock
    hour::Int
    minute::Int

    function Clock(hour, minute)
        if minute < 0
            hour = hour + minute ÷ 60 - 1
            minute = 60 + (minute % 60)
        end
        if hour < 0
            hour = 24 + (hour % 24)
        end
        hour = hour + (minute ÷ 60)
        hour = hour % 24
        minute = minute % 60

        new(hour, minute)
    end
end

using Dates
import Base.+, Base.-, Base.!=, Base.==, Base.show

+(c::Clock, m::Dates.Minute) = Clock(c.hour, c.minute + m.value)
-(c::Clock, m::Dates.Minute) = Clock(c.hour, c.minute - m.value)
==(c1::Clock, c2::Clock) = c1.hour == c2.hour && c1.minute == c2.minute
show(io::IO, c::Clock) = print(io, "\"", (c.hour ÷ 10 == 0 ? "0" : ""), c.hour, ":", (c.minute ÷ 10 == 0 ? "0" : ""), c.minute, "\"")


#================================================================
 This package solves the following problem parallely

 min  c_0^T x + \sum{i = 1}^S c_i^Ty_i
 s.t. b_0 - A_0 x           \in K_0,
      b_i - A_i x - B_i y_i \in K_i, \forall i = 1,...,S
                x           \in C_0,
                x_I         \in Z
                        y_i \in C_i, \forall i = 1,...,S

 where input to the Benders engine is:
 c_all is an array of c_0, c_1, c_2, ... , c_S objective coefficients
 A_all is an array of A_0, A_1, A_2, ... , A_S constraint matrices of master variables
 B_all is an array of      B_1, B_2, ... , B_S constraint matrices of scenario variables (note that scenario variables do not appear at master constraints)
 b_all is an array of b_0, b_1, b_2, ... , b_S right hand side of constraints
 K_all is an array of K_0, K_1, K_2, ... , K_S constraint cones
 C_all is an array of C_0, C_1, C_2, ... , C_S variable cones
 v     is an array of master variable types (i.e. :Bin, :Int, :Cont)

 call with: julia -p <num_threads> <myscript>
================================================================#
using JuMP

# this function loads and solves a conic problem and returns its dual
function loadAndSolveConicProblem(c, A, b, K, C, solver)

    # load conic model
    model = MathProgBase.ConicModel(solver)
    MathProgBase.loadproblem!(model, c, A, b, K, C)

    #println(model)
    #println("process id $(myid()) started")
    #@show c, A, b, K, C

    # solve conic model
    MathProgBase.optimize!(model)
    status = MathProgBase.status(model)

    # return status and dual
    #println("process id $(myid()) status $(status)")
    return status, MathProgBase.getdual(model)
end

function loadMasterProblem(c, A, b, K, C, v, num_scen, solver)
    num_var = length(c)
    # load master problem
    master_model = Model(solver=solver)
    @variable(master_model, x[1:num_var])
    for i = 1:num_var
        setcategory(x[i], v[i])
    end
    # add new objective variables for scenarios
    # we assume the objective is sum of nonnegative terms
    @variable(master_model, θ[1:num_scen] >= 0.0)
    @objective(master_model, :Min, sum(c[i]*x[i] for i in 1:num_var) + sum(θ))
    # add constraint cones
    for (cone, ind) in K
        for i in 1:length(ind)
            if cone == :Zero
                @constraint(master_model, dot(A[ind[i],:], x) == b[ind[i]])
            elseif cone == :NonNeg
                @constraint(master_model, dot(A[ind[i],:], x) <= b[ind[i]])
            elseif cone == :NonPos
                @constraint(master_model, dot(A[ind[i],:], x) >= b[ind[i]])
            elseif cone == :SOC
                @constraint(master_model, norm(b[ind[2:end-1]] - A[ind[2:end-1],:]*x) .<= b[ind[1]] - A[ind[1],:]*x)
                @constraint(master_model, dot(A[ind[1],:], x) <= b[ind[1]])
            else
                error("unrecognized cone $cone")
            end
        end
    end
    # add variable cones
    for (cone, ind) in C
        if cone == :Zero
            for i in ind
                setlowerbound(x[i], 0.0)
                setupperbound(x[i], 0.0)
            end
        elseif cone == :Free
            # do nothing
        elseif cone == :NonNeg
            for i in ind
                setlowerbound(x[i], 0.0)
            end
        elseif cone == :NonPos
            for i in ind
                setupperbound(x[i], 0.0)
            end
        elseif cone == :SOC
            @constraint(master_model, norm(x[ind[2:end-1]]) <= x[ind[1]])
            setlowerbound(x[ind[1]], 0.0)
        else
            error("unrecognized cone $cone")
        end
    end

    #setcategory(master_model, [v;[:Cont for i = 1:num_scen]])
    return master_model, x, θ
end

function addCuttingPlanes(master_model, num_scen, A_all, b_all, output, x, θ, separator, TOL)
    cut_added = false
    # add cutting planes, one per scenario
    for i = 1:num_scen
        #@show typeof(b_all[i+1])
        coef = vec(output[i][2]' * A_all[i+1])
        rhs = vecdot(output[i][2], b_all[i+1])
        #@show size(coef*separator'), size(rhs)
        # add an infeasibility cut
        if output[i][1] == :Infeasible
            # output[i][2] is a ray
            # so alpha * output[i][2] is also valid for any alpha >= 0.
            # Hence output[i][2] might have very large coefficients and alter
            # the numerical accuracy of the master's solver.
            # We scale it to avoid this issue
            scaling = abs(rhs)
            if scaling == 0
              scaling = maximum(coef)
            end
            @constraint(master_model, dot(coef/scaling, x) <= sign(rhs))
            cut_added = true
        # add an optimality cut
        else
            if getvalue(θ[i]) < (dot(coef, separator) - rhs)[1] - TOL
                @constraint(master_model, dot(coef, x) - θ[i] <= rhs)
                cut_added = true
            end
        end
    end
    return cut_added

end

function Benders_pmap(c_all, A_all, B_all, b_all, K_all, C_all, v, master_solver, sub_solver, TOL=1e-5)
    #println("Benders pmap started")
    num_master_var = length(c_all[1])
    num_scen = length(c_all) - 1

    num_bins = zeros(Int, num_scen)
    for i = 1:num_scen
        num_bins[i] = length(b_all[i+1])
    end

    #println("Load master problem")
    (master_model, x, θ) = loadMasterProblem(c_all[1], A_all[1], b_all[1], K_all[1], C_all[1], v, num_scen, master_solver)

    cut_added = true
    separator = zeros(num_master_var)
    objval = Inf
    status = :Infeasible
    while cut_added
        #println("Iteration started")
        status = solve(master_model)
        if status == :Infeasible
            break
        end
        objval = getobjectivevalue(master_model)
        separator = zeros(num_master_var)
        for i = 1:num_master_var
            separator[i] = getvalue(x[i])
        end
        new_rhs = [zeros(num_bins[i]) for i in 1:num_scen]
        for i = 1:num_scen
            new_rhs[i] = b_all[i+1] - A_all[i+1] * separator
        end

        output = pmap(loadAndSolveConicProblem,
            [c_all[i+1] for i = 1:num_scen],
            [B_all[i] for i = 1:num_scen],
            [new_rhs[i] for i = 1:num_scen],
            [K_all[i+1] for i = 1:num_scen],
            [C_all[i+1] for i = 1:num_scen],
            [sub_solver for i = 1:num_scen])

        #@show output
        cut_added = addCuttingPlanes(master_model, num_scen, A_all, b_all, output, x, θ, separator, TOL)
    end
    return status, objval, separator
end