# Lar = LinearAlgebraicRepresentation

function interior_to_f(triangle,f,V,FV,EV,FE)
	v1,v2,v3 = triangle
	u = V[:,v2]-V[:,v1]
	v = V[:,v3]-V[:,v1]
	w = cross(u,v)
	T = [1. 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]; T[1:3,4] = -V[:,v1]
	R = [1. 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1]; R[1:3,1:3] = [u v w]; R = R'
	mapping = R * T

	trianglepoints = [[V[:,v1] V[:,v2] V[:,v3]]; ones(3)']
	pts = mapping * trianglepoints
	points2D = [pts[r,:] for r = 1:size(pts,1)
				if !(all(pts[r,:].==0) || all(pts[r,:].==1) )]
	p2D = hcat(points2D...)'
	checkpoint = 0.4995 .* p2D[:,1] + 0.4995 .* p2D[:,2] + 0.001 .* p2D[:,3]

	cellpoints = [V[:,FV[f]]; ones(length(FV[f]))' ]
	points = mapping * cellpoints
	verts2D = [points[r,:] for r = 1:size(points,1)
				if !(all(points[r,:].==0) || all(points[r,:].==1) )]
	P2D = hcat(verts2D...)'

	vdict = Dict(collect(zip(FV[f], 1:length(FV[f]))))
	celledges = [[vdict[v] for v in EV[e]] for e in FE[f]]

	out = pointInPolygonClassification(P2D,celledges)(checkpoint)
	if out=="p_in"
		return true
	else
		return false
	end
end

function ordering(triangles,V)
	normals = []
	v1,v2,v3 = triangles[1]
	if v1>v2 v1,v2 = v2,v1 end
	e3 = normalize(V[:,v2]-V[:,v1])
	e1 = normalize(V[:,v3]-V[:,v1])
	e2 = normalize(cross(e1,e3))
	basis = [e1 e2 e3]
	transform = inv(basis)

	angles = []
	for (v1,v2,v3) in triangles
		w1 = normalize(V[:,v3]-V[:,v1])
		w2 = transform * w1
		w3 = cross([0,0,1],w2)
		push!(normals,w3)
	end
	for k=1:length(normals)
		angle = atan(normals[k][2],normals[k][1])
		push!(angles,angle)
	end
	#pairs = sort(collect(zip(angles,1:length(triangles))))
	pairs = sort([(angle,k) for (k,angle) in enumerate(angles)])
	order = [k for (angle,k) in pairs]
	return order
end

function ord(hinge::Int64, bd1::AbstractSparseVector{Int64,Int64}, V::Array{Float64,2},
FV::Array{Array{Int64,1},1}, EV::Array{Array{Int64,1},1}, FE::Array{Array{Int64,1},1})
	cells = SparseArrays.findnz(bd1)[1]
	triangles = []

	function area(v1,v2,v3)
		u = V[:,v2]-V[:,v1]
		v = V[:,v3]-V[:,v1]
		out = norm(cross(u,v)) # actually, to be divided by two
		return out
	end

	for f in cells
		v1,v2 = EV[hinge]
		index = findfirst(v -> (area(v1,v2,v)≠0), FV[f])
		v3 = FV[f][index]

		# test if [v1,v2,v3] interior to f
		while true
			if interior_to_f([v1, v2, v3],f,V,FV,EV,FE)
				push!(triangles, [v1,v2,v3])
				break
			else
				index = findnext(v -> (area(v1,v2,v)≠0), FV[f], index+1)
				v3 = FV[f][index]
			end
		end
	end
	order = ordering(triangles,V)
	return [cells[index] for index in order]
end

function mynext(cycle, pivot)
	len = length(cycle)
	ind = findall(x -> x==pivot, cycle)[1]
	nextIndex = ind==len ? 1 : ind+1
	return cycle[nextIndex][1]
end

function myprev(cycle, pivot)
	len = length(cycle)
	ind = findall(x->x==pivot, cycle)[1]
	nextIndex = ind==1 ? len : ind-1
	return cycle[nextIndex][1]
end

function build_copFC(rV, rcopEV, rcopFE)
#function build_copFC(V,FV,EV,copFE)

	# G&F -> Pao data structures
	V = convert(Lar.Points, rV')
	EV = cop2lar(rcopEV)
	fe = cop2lar(rcopFE)
	fv = [collect(Set(cat(EV[e] for e in fe[f]))) for f=1:length(fe)]
	FV = convert(Lar.Cells, fv)
	copFE = rcopFE

	copEF = copFE'
	FE = [SparseArrays.findnz(copFE[k,:])[1] for k=1:size(copFE,1)]
	# Initializations
	m,n = size(copEF)
	marks = zeros(Int,n);
	I = Int64[]; J = Int64[]; W = Int8[];
	jcol = 0
	choose(marks) = findfirst(x -> x<2, marks)

	# Main loop (adding one copFC's column stepwise)
	while sum(marks) < 2n
		# select a (d−1)-cell, "seed" of the column extraction
		σ = choose(marks)
		if marks[σ] == 0
			cd1 = sparsevec([σ], Int8[1], n)
		elseif marks[σ] == 1
			cd1 = sparsevec([σ], Int8[-1], n)
		end
		# compute boundary cd2 of seed cell
		cd2 = copEF * cd1
		# loop until (boundary) cd2 becomes empty
		while nnz(cd2)≠0
			corolla = sparsevec([], Int8[], n)
			# for each “hinge” τ cell
			for τ ∈ (.*)(SparseArrays.findnz(cd2)...)
				#compute the  coboundary
				tau = sparsevec([abs(τ)], [sign(τ)], m)
				bd1 = transpose(transpose(tau) * copEF)
				cells2D = SparseArrays.findnz(bd1)[1]
				# compute the  support
				pivot = intersect(cells2D, SparseArrays.findnz(cd1)[1])[1]
				# compute the new adj cell
				fan = ord(abs(τ),bd1,V,FV,EV,FE) # ord(pivot,bd1)
				if τ > 0
					adj = mynext(fan,pivot)
				elseif τ < 0
					adj = myprev(fan,pivot)
				end
				# orient adj
				if copEF[abs(τ),adj] ≠ copEF[abs(τ),pivot]
					corolla[adj] = cd1[pivot]
				else
					corolla[adj] = -(cd1[pivot])
				end
			end
			# insert corolla cells in current cd1
			for (k,val) in zip(SparseArrays.findnz(corolla)...)
				cd1[k] = val
			end
			# compute again the boundary of cd1
			cd2 = copEF * cd1
		end
		for σ ∈ SparseArrays.findnz(cd1)[1]
			# update the counters of used cells
			marks[σ] += 1
		end
		# append a new column to [∂d+]
		# copFC += cd1
		rows, vals = SparseArrays.findnz(cd1)
		jcol += 1
		append!(I,rows)
		append!(J,[ jcol for k=1:nnz(cd1) ])
		append!(W,vals)
	end
	copFC = sparse(I,J,W)
	return copFC
end

"""
    bbox(vertices::Points)

The axis aligned bounding box of the provided set of n-dim `vertices`.

The box is returned as the couple of `Points` of the two opposite corners of the box.
"""
function bbox(vertices::Points)
    minimum = mapslices(x->min(x...), vertices, dims=1)
    maximum = mapslices(x->max(x...), vertices, dims=1)
    minimum, maximum
end

"""
    bbox_contains(container, contained)

Check if the axis aligned bounding box `container` contains `contained`.

Each input box must be passed as the couple of `Points` standing on the opposite corners of the box.
"""
function bbox_contains(container, contained)
    b1_min, b1_max = container
    b2_min, b2_max = contained
    all(map((i,j,k,l)->i<=j<=k<=l, b1_min, b2_min, b2_max, b1_max))
end

"""
    face_area(V::Points, EV::Cells, face::Cell)

The area of `face` given a geometry `V` and an edge topology `EV`.
"""
function face_area(V::Points, EV::Cells, face::Cell)
    return face_area(V, build_copEV(EV), face)
end

function face_area(V::Points, EV::ChainOp, face::Cell)
    function triangle_area(triangle_points::Points)
        ret = ones(3,3)
        ret[:, 1:2] = triangle_points
        return .5*det(ret)
    end

    area = 0

    fv = buildFV(EV, face)

    verts_num = length(fv)
    v1 = fv[1]

    for i in 2:(verts_num-1)

        v2 = fv[i]
        v3 = fv[i+1]

        area += triangle_area(V[[v1, v2, v3], :])
    end

    return area
end

"""
    skel_merge(V1::Points, EV1::ChainOp, V2::Points, EV2::ChainOp)

Merge two **1-skeletons**
"""
function skel_merge(V1::Points, EV1::ChainOp, V2::Points, EV2::ChainOp)
	V = [V1; V2]
    EV = blockdiag(EV1,EV2)
    return V, EV
end

# function coppie_duplicate(A)
# 	t=()
# 	for i in 1:size(A,1)
# 		t= (t...,(A[i,1],A[i,2]))
# 	end
# 	return [y[i] for y in unique(t), i in 1:2]
# end
"""
    skel_merge(V1::Points, EV1::ChainOp, FE1::ChainOp, V2::Points, EV2::ChainOp, FE2::ChainOp)

Merge two **2-skeletons**
"""
function skel_merge(V1::Lar.Points, EV1::Lar.ChainOp, FE1::Lar.ChainOp,
					V2::Lar.Points, EV2::Lar.ChainOp, FE2::Lar.ChainOp)
    FE = blockdiag(FE1,FE2)
    V, EV = skel_merge(V1, EV1, V2, EV2)
    return V, EV, FE
end

"""
    delete_edges(todel, V::Points, EV::ChainOp)

Delete edges and remove unused vertices from a **2-skeleton**.

Loop over the `todel` edge index list and remove the marked edges from `EV`.
The vertices in `V` which remained unconnected after the edge deletion are deleted too.
"""
function delete_edges(todel, V::Points, EV::ChainOp)
    tokeep = setdiff(collect(1:EV.m), todel)
    EV = EV[tokeep, :]

    vertinds = 1:EV.n
    todel = Array{Int64, 1}()
    for i in vertinds
        if length(EV[:, i].nzind) == 0
            push!(todel, i)
        end
    end

    tokeep = setdiff(vertinds, todel)
    EV = EV[:, tokeep]
    V = V[tokeep, :]

    return V, EV
end

"""
    buildFV(EV::Cells, face::Cell)

The list of vertex indices that expresses the given `face`.

The returned list is made of the vertex indices ordered following the traversal order to keep a coherent face orientation.
The edges are need to understand the topology of the face.

In this method the input face must be expressed as a `Cell`(=`SparseVector{Int8, Int}`) and the edges as `Cells`.
"""
function buildFV(EV::Cells, face::Cell)
    return buildFV(build_copEV(EV), face)
end

"""
    buildFV(copEV::ChainOp, face::Cell)

The list of vertex indices that expresses the given `face`.

The returned list is made of the vertex indices ordered following the traversal order to keep a coherent face orientation.
The edges are need to understand the topology of the face.

In this method the input face must be expressed as a `Cell`(=`SparseVector{Int8, Int}`) and the edges as `ChainOp`.
"""
function buildFV(copEV::ChainOp, face::Cell)
    startv = -1
    nextv = 0
    edge = 0

    vs = Array{Int64, 1}()

    while startv != nextv
        if startv < 0
            edge = face.nzind[1]
            startv = copEV[edge,:].nzind[face[edge] < 0 ? 2 : 1]
            push!(vs, startv)
        else
            edge = setdiff(intersect(face.nzind, copEV[:, nextv].nzind), edge)[1]
        end
        nextv = copEV[edge,:].nzind[face[edge] < 0 ? 1 : 2]
        push!(vs, nextv)

    end

    return vs[1:end-1]
end

"""
    buildFV(copEV::ChainOp, face::Array{Int, 1})

The list of vertex indices that expresses the given `face`.

The returned list is made of the vertex indices ordered following the traversal order to keep a coherent face orientation.
The edges are need to understand the topology of the face.

In this method the input face must be expressed as a list of vertex indices and the edges as `ChainOp`.
"""
function buildFV(copEV::ChainOp, face::Array{Int, 1})
    startv = face[1]
    nextv = startv

    vs = []
    visited_edges = []

    while true
        curv = nextv
        push!(vs, curv)

        edge = 0

        for edgeEx in copEV[:, curv].nzind
            nextv = setdiff(copEV[edgeEx, :].nzind, curv)[1]
            if nextv in face && (nextv == startv || !(nextv in vs)) && !(edgeEx in visited_edges)
                edge = edgeEx
                break
            end
        end

        push!(visited_edges, edge)

        if nextv == startv
            break
        end
    end

    return vs
end

"""
    build_copFE(FV::Cells, EV::Cells)

The signed `ChainOp` from 1-cells (edges) to 2-cells (faces)
"""
function build_copFE(FV::Lar.Cells, EV::Lar.Cells)
	copFE = Lar.u_coboundary_1(FV, EV) # unsigned
	faceedges = [findnz(copFE[f,:])[1] for f=1:size(copFE,1)]

	f_edgepairs = Array{Array{Int64,1}}[]
	for f=1:size(copFE,1)
		edgepairs = Array{Int64,1}[]
		for v in FV[f]
			push!(edgepairs, [e for e in faceedges[f] if v in EV[e]])
		end
		push!(f_edgepairs, edgepairs)
	end
	for f=1:size(copFE,1)
		for (e1,e2) in f_edgepairs[f]
			v = intersect(EV[e1], EV[e2])[1]
			copFE[f,e1] = EV[e1][2]==v ? 1 : -1
			copFE[f,e2] = EV[e2][1]==v ? 1 : -1
		end
	end
	return copFE
end

"""
    build_copEV(EV::Cells, signed=true)

The signed (or not) `ChainOp` from 0-cells (vertices) to 1-cells (edges)
"""
function build_copEV(EV::Cells, signed=true)
    setValue = [-1, 1]
    if signed == false
        setValue = [1, 1]
    end

    maxv = max(map(x->max(x...), EV)...)
    copEV = spzeros(Int8, length(EV), maxv)

    for (i,e) in enumerate(EV)
        e = sort(collect(e))
        copEV[i, e] = setValue
    end

    return copEV
end

"""
    build_cops(edges::Cells, faces::Cells)

The vertices-edges and edges-faces chain operators (`copEV::ChainOp`, `copFE::ChainOp`)
"""
function build_cops(edges::Cells, faces::Cells)
    copEV = build_copEV(edges)
    FV = Cells(map(x->buildFV(copEV,x), faces))
    copFE = build_copFE(FV, edges)

    return [copEV, copFE]
end

"""
    vin(vertex, vertices_set)

Checks if `vertex` is one of the vertices inside `vertices_set`
"""
function vin(vertex, vertices_set)
    for v in vertices_set
        if vequals(vertex, v)
            return true
        end
    end
    return false
end

"""
    vequals(v1, v2)

Check the equality between vertex `v1` and vertex `v2`
"""
function vequals(v1, v2)
    err = 10e-8
    return length(v1) == length(v2) && all(map((x1, x2)->-err < x1-x2 < err, v1, v2))
end

function vcycle( copEV::Lar.ChainOp, copFE::Lar.ChainOp, f::Int64 )
	edges,signs = SparseArrays.findnz(copFE[f,:])
	vpairs = [s>0 ? SparseArrays.findnz(copEV[e,:])[1] :
					reverse(SparseArrays.findnz(copEV[e,:])[1])
				for (e,s) in zip(edges,signs)]
	a = [pair for pair in vpairs if length(pair)==2]
	function mycat(a::Lar.Cells)
		out=[]
		for cell in a append!(out,cell) end
		return out
	end
	vs = collect(Set(mycat(a)))
	vdict = Dict(zip(vs,1:length(vs)))
	edges = [[vdict[pair[1]], vdict[pair[2]]] for pair in vpairs if length(pair)==2]
	return vs, edges
end

"""
    triangulate(model::LARmodel)

Full constrained Delaunnay triangulation of the given 3-dimensional `LARmodel`
"""
function triangulate(model::LARmodel)
    V, topology = model
    cc = build_cops(topology...)
    return triangulate(V, cc)
end

"""
    triangulate(V::Points, cc::ChainComplex)

Full constrained Delaunnay triangulation of the given 3-dimensional model (given with topology as a `ChainComplex`)
"""
function triangulate(V::Lar.Points, cc::Lar.ChainComplex)
	copEV, copFE = cc[1:2]

    triangulated_faces = Array{Any, 1}(undef, copFE.m)

    for f in 1:copFE.m
        if f % 10 == 0
            print(".")
        end

        edges_idxs = copFE[f, :].nzind
        edge_num = length(edges_idxs)
        edges = zeros(Int64, edge_num, 2)

        #fv = Lar.buildFV(copEV, copFE[f, :])
        fv, edges = Lar.vcycle(copEV, copFE, f)
		if fv ≠ []
			vs = V[fv, :]
	        v1 = LinearAlgebra.normalize(vs[2, :] - vs[1, :])
	        v2 = [0, 0, 0]
	        v3 = [0, 0, 0]
	        err = 1e-8
	        i = 3
	        while -err < LinearAlgebra.norm(v3) < err
	            v2 = LinearAlgebra.normalize(vs[i, :] - vs[1, :])
	            v3 = LinearAlgebra.cross(v1, v2)
	            i = i % size(vs,1) + 1
	        end
	        M = reshape([v1; v2; v3], 3, 3)
	        vs = (vs*M)[:, 1:2]
			v = convert(Lar.Points, vs'[1:2,:])
			vmap = Dict(zip(fv,1:length(fv))) # vertex map
			mapv = Dict(zip(1:length(fv),fv)) # inverse vertex map

			trias = Lar.triangulate2d(v,edges)
			triangulated_faces[f] = [[mapv[v] for v in tria] for tria in trias]
		end
    end

    return triangulated_faces
end

"""
    point_in_face(point, V::Points, copEV::ChainOp)

Check if `point` is inside the area of the face bounded by the edges in `copEV`
"""
function point_in_face(point, V::Points, copEV::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

        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)
            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)
                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

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

"""
    lar2obj(V::Points, cc::ChainComplex)

Triangulated OBJ string representation of the model passed as input.

Use this function to export LAR models into OBJ

# Example

```julia
	julia> cube_1 = ([0 0 0 0 1 1 1 1; 0 0 1 1 0 0 1 1; 0 1 0 1 0 1 0 1],
	[[1,2,3,4],[5,6,7,8],[1,2,5,6],[3,4,7,8],[1,3,5,7],[2,4,6,8]],
	[[1,2],[3,4],[5,6],[7,8],[1,3],[2,4],[5,7],[6,8],[1,5],[2,6],[3,7],[4,8]] )

	julia> cube_2 = Lar.Struct([Lar.t(0,0,0.5), Lar.r(0,0,pi/3), cube_1])

	julia> V, FV, EV = Lar.struct2lar(Lar.Struct([ cube_1, cube_2 ]))

	julia> V, bases, coboundaries = Lar.chaincomplex(V,FV,EV)

	julia> (EV, FV, CV), (copEV, copFE, copCF) = bases, coboundaries

	julia> FV # bases[2]
	18-element Array{Array{Int64,1},1}:
	 [1, 3, 4, 6]
	 [2, 3, 5, 6]
	 [7, 8, 9, 10]
	 [1, 2, 3, 7, 8]
	 [4, 6, 9, 10, 11, 12]
	 [5, 6, 11, 12]
	 [1, 4, 7, 9]
	 [2, 5, 11, 13]
	 [2, 8, 10, 11, 13]
	 [2, 3, 14, 15, 16]
	 [11, 12, 13, 17]
	 [11, 12, 13, 18, 19, 20]
	 [2, 3, 13, 17]
	 [2, 13, 14, 18]
	 [15, 16, 19, 20]
	 [3, 6, 12, 15, 19]
	 [3, 6, 12, 17]
	 [14, 16, 18, 20]

	julia> CV # bases[3]
	3-element Array{Array{Int64,1},1}:
	 [2, 3, 5, 6, 11, 12, 13, 14, 15, 16, 18, 19, 20]
	 [2, 3, 5, 6, 11, 12, 13, 17]
	 [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 17]

	julia> copEV # coboundaries[1]
	34×20 SparseMatrixCSC{Int8,Int64} with 68 stored entries: ...

	julia> copFE # coboundaries[2]
	18×34 SparseMatrixCSC{Int8,Int64} with 80 stored entries: ...

	julia> copCF # coboundaries[3]
	4×18 SparseMatrixCSC{Int8,Int64} with 36 stored entries: ...

	objs = Lar.lar2obj(V'::Lar.Points, [coboundaries...])

	open("./two_cubes.obj", "w") do f
    	write(f, objs)
	end
```
"""
function lar2obj(V::Lar.Points, cc::Lar.ChainComplex)
    copEV, copFE, copCF = cc
	if size(V,2) > 3
		V = convert(Lar.Points, V') # out V by rows
	end
    obj = ""
    for v in 1:size(V, 1)
        obj = string(obj, "v ",
    	round(V[v, 1], digits=6), " ",
    	round(V[v, 2], digits=6), " ",
    	round(V[v, 3], digits=6), "\n")
    end

    print("Triangulating")
    triangulated_faces = Lar.triangulate(V, cc[1:2])
    println("DONE")

    for c in 1:copCF.m
        obj = string(obj, "\ng cell", c, "\n")
        for f in copCF[c, :].nzind
            triangles = triangulated_faces[f]
            for tri in triangles
                #t = copCF[c, f] > 0 ? tri : tri[end:-1:1]
				t = tri
                obj = string(obj, "f ", t[1], " ", t[2], " ", t[3], "\n")
            end
        end
    end

    return obj
end

"""
	lar2obj(V::Lar.Points, TV::Lar.Cells)::String
```
```
"""
function lar2obj(V::Lar.Points, TV::Lar.Cells)
	obj = ""
    for v in 1:size(V, 1)
        obj = string(obj, "v ",
    	round(V[1,v], digits=6), " ",
    	round(V[2,v], digits=6), " ",
    	round(V[3,v], digits=6), "\n")
	end
	for tri in TV
		#t = copCF[c, f] > 0 ? tri : tri[end:-1:1]
		obj = string(obj, "f ", tri[1], " ", tri[2], " ", tri[3], "\n")
	end
end

"""
    obj2lar(path)

Read OBJ file at `path` and create a 2-skeleton as `Tuple{Points, ChainComplex}` from it.

This function does care about eventual internal grouping inside the OBJ file.
"""
function obj2lar(path)
    vs = Array{Float64, 2}(undef, 0, 3)
    edges = Array{Array{Array{Int, 1}, 1}, 1}()
    faces = Array{Array{Array{Int, 1}, 1}, 1}()
	push!(edges, Array{Array{Int, 1}, 1}[])
	push!(faces, Array{Array{Int, 1}, 1}[])
	g = 1

    open(path, "r") do fd
        for line in eachline(fd)
            elems = split(line)
            if length(elems) > 0
                if elems[1] == "v"
					# parse and store a vertex
                    x = parse(Float64, elems[2])
                    y = parse(Float64, elems[3])
                    z = parse(Float64, elems[4])
                    vs = [vs; x y z]

				elseif elems[1] == "f"
                    # Ignore the vertex tangents and normals
                    v1 = parse(Int, split(elems[2], "/")[1])
                    v2 = parse(Int, split(elems[3], "/")[1])
                    v3 = parse(Int, split(elems[4], "/")[1])

                    e1 = sort([v1, v2])
                    e2 = sort([v2, v3])
                    e3 = sort([v3, v1])

					push!(edges[g], e1)
					push!(edges[g], e2)
					push!(edges[g], e3)

                    push!(faces[g], sort([v1, v2, v3]))

				elseif elems[1] == "g"
					#start a new group of edges and faces
					#println(line)
					g += 1
					push!(edges, Array{Array{Int, 1}, 1}[])
					push!(faces, Array{Array{Int, 1}, 1}[])
				end
            end
        end
    end

	#return vs, build_cops(edges, faces)
	return convert(Lar.Points, vs'), edges[2:end], faces[2:end]
end

"""
    binaryRange(n)

Generate the first `n` binary numbers in string padded for max `2^n` length
"""
function binaryRange(n)
    return string.(range(0, length=2^n), base=2, pad=n)
end

function space_arrangement(V::Points, EV::ChainOp, FE::ChainOp, multiproc::Bool=false)

    fs_num = size(FE, 1)
    sp_idx = Lar.Arrangement.spatial_index(V, EV, FE)

    rV = Lar.Points(undef, 0,3)
    rEV = SparseArrays.spzeros(Int8,0,0)
    rFE = SparseArrays.spzeros(Int8,0,0)

    if (multiproc == true)
        in_chan = Distributed.RemoteChannel(()->Channel{Int64}(0))
        out_chan = Distributed.RemoteChannel(()->Channel{Tuple}(0))

        @async begin
            for sigma in 1:fs_num
                put!(in_chan, sigma)
            end
            for p in Distributed.workers()
                put!(in_chan, -1)
            end
        end

        for p in Distributed.workers()
            @async Base.remote_do(
                frag_face_channel, p, in_chan, out_chan, V, EV, FE, sp_idx)
        end

        for sigma in 1:fs_num
            rV, rEV, rFE = skel_merge(rV, rEV, rFE, take!(out_chan)...)
        end

    else

       for sigma in 1:fs_num
           # print(sigma, "/", fs_num, "\r")
           nV, nEV, nFE = Lar.Arrangement.frag_face(
           	V, EV, FE, sp_idx, sigma)
           a,b,c = Lar.skel_merge(
           	rV, rEV, rFE, nV, nEV, nFE)
           rV=a; rEV=b; rFE=c
       end

#		depot_V = Array{Array{Float64,2},1}(undef,fs_num)
#		depot_EV = Array{ChainOp,1}(undef,fs_num)
#		depot_FE = Array{ChainOp,1}(undef,fs_num)
#        for sigma in 1:fs_num
#            print(sigma, "/", fs_num, "\r")
#            nV, nEV, nFE = Arrangement.frag_face( V, EV, FE, sp_idx, sigma)
#            depot_V[sigma] = nV
#            depot_EV[sigma] = nEV
#            depot_FE[sigma] = nFE
#        end
#		rV = vcat(depot_V...)
#		rEV = SparseArrays.blockdiag(depot_EV...)
#		rFE = SparseArrays.blockdiag(depot_FE...)

    end

    rV, rEV, rFE = Lar.Arrangement.merge_vertices(rV, rEV, rFE)

    rCF = Arrangement.minimal_3cycles(rV, rEV, rFE)

    return rV, rEV, rFE, rCF
end

###  2D triangulation
Lar = LinearAlgebraicRepresentation
"""
	obj2lar2D(path::AbstractString)::Lar.LARmodel

Read a *triangulation* from file, given its `path`. Return a `LARmodel` object
"""
function obj2lar2D(path::AbstractString)::Lar.LARmodel
    vs = Array{Float64, 2}(undef, 0, 3)
    edges = Array{Array{Int, 1}, 1}()
    faces = Array{Array{Int, 1}, 1}()

    open(path, "r") do fd
		for line in eachline(fd)
			elems = split(line)
			if length(elems) > 0
				if elems[1] == "v"
					x = parse(Float64, elems[2])
					y = parse(Float64, elems[3])
					z = parse(Float64, elems[4])
					vs = [vs; x y z]
				elseif elems[1] == "f"
					# Ignore the vertex tangents and normals
					v1 = parse(Int, elems[2])
					v2 = parse(Int, elems[3])
					v3 = parse(Int, elems[4])
					append!(edges, map(sort,[[v1,v2],[v2,v3],[v3,v1]]))
					push!(faces, [v1, v2, v3])
				end
				edges = collect(Set(edges))
			end
		end
	end
    return (vs, [edges,faces])::Lar.LARmodel
end

"""
	lar2obj2D(V::Lar.Points,
			cc::Lar.ChainComplex)::String

Produce a *triangulation* from a `LARmodel`. Return a `String` object
"""
function lar2obj2D(V::Lar.Points, cc::Lar.ChainComplex)::String
    @assert length(cc) == 2
    copEV, copFE = cc
    V = [V zeros(size(V, 1))]

    obj = ""
    for v in 1:size(V, 1)
        	obj = string(obj, "v ",
        	round(V[v, 1]; digits=6), " ",
        	round(V[v, 2]; digits=6), " ",
        	round(V[v, 3]; digits=6), "\n")
    end

    #triangulated_faces = triangulate2D(V, cc)
    triangulated_faces = triangulate(V, cc)

	obj = string(obj, "\n")
	for f in 1:copFE.m
		triangles = triangulated_faces[f]
		for tri in triangles
			t = tri
			#t = copCF[c, f] > 0 ? tri : tri[end:-1:1]
			obj = string(obj, "f ", t[1], " ", t[2], " ", t[3], "\n")
		end
	end

    return obj
end

#TODO: finish by using a string as an IObuffer
#"""
#	lar2tria2lar(V::Lar.Points, cc::Lar.ChainComplex)::Lar.LARmodel
#
#Return a triangulated `LARmodel` starting from a stadard LARmodel.
#Useful for colour drawing a complex of non-convex cells.
#
#"""
#function lar2tria2lar(V::Lar.Points, cc::Lar.ChainComplex)::Lar.LARmodel
#	obj = Lar.lar2obj2D(V::Lar.Points, cc::Lar.ChainComplex)
#	vs, (edges,faces) = Lar.obj2lar2D(obj::AbstractString)::Lar.LARmodel
#	return (vs, [edges,faces])::Lar.LARmodel
#end

"""
	triangulate2D(V::Lar.Points,
			cc::Lar.ChainComplex)::Array{Any, 1}

Compute a *CDT* for each face of a `ChainComplex`. Return an `Array` of triangles.
"""
function triangulate2D(V::Lar.Points, cc::Lar.ChainComplex)::Array{Any, 1}
    copEV, copFE = cc
    triangulated_faces = Array{Any, 1}(undef, copFE.m)
    if size(V,2)==2
		V = [V zeros(size(V,1),1)]
	end

	polygons,edgecycles = faces2polygons(copEV, copFE) #new

    for f in 1:copFE.m
        edges_idxs = copFE[f, :].nzind
        edge_num = length(edges_idxs)
        edges = Array{Int64,1}[] #zeros(Int64, edge_num, 2)

		# fv = Lar.buildFV(copEV, copFE[f, :])
		fv = cat(polygons[f])
        vs = V[fv, :]
		edges = cat(edgecycles[f])
        edges = convert(Array{Int64,2}, hcat(edges...)')

		# triangulated_faces[f] = Triangle.constrained_triangulation(
        # 	vs, fv, edges, fill(true, edge_num))
		v = convert(Lar.Points, vs'[1:2,:])
		vmap = Dict(zip(fv,1:length(fv))) # vertex map
		mapv = Dict(zip(1:length(fv),fv)) # inverse vertex map
		ev = [[vmap[e] for e in edges[k,:]] for k=1:size(edges,1)]
		trias = Lar.triangulate2d(v,ev)
		triangulated_faces[f] = [[mapv[v] for v in tria] for tria in trias]

        tV = V[:, 1:2]

        area = Lar.face_area(tV, copEV, copFE[f, :])
        if area < 0
            for i in 1:length(triangulated_faces[f])
                triangulated_faces[f][i] = triangulated_faces[f][i][end:-1:1]
            end
        end
    end

    return triangulated_faces
end

"""
	lar2cop(CV::Lar.Cells)::Lar.ChainOp

Convert an array of array of integer indices to vertices into a sparse matrix.

# Examples

For a single 3D unit cube we get:

```
julia> V,(VV,EV,FV,CV) = Lar.cuboid([1,1,1],true);

julia> Matrix(Lar.lar2cop(EV))
12×8 Array{Int8,2}:
 1  1  0  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  0  1  1
 1  0  1  0  0  0  0  0
 0  1  0  1  0  0  0  0
 0  0  0  0  1  0  1  0
 0  0  0  0  0  1  0  1
 1  0  0  0  1  0  0  0
 0  1  0  0  0  1  0  0
 0  0  1  0  0  0  1  0
 0  0  0  1  0  0  0  1

julia> Matrix(Lar.lar2cop(FV))
6×8 Array{Int8,2}:
 1  1  1  1  0  0  0  0
 0  0  0  0  1  1  1  1
 1  1  0  0  1  1  0  0
 0  0  1  1  0  0  1  1
 1  0  1  0  1  0  1  0
 0  1  0  1  0  1  0  1

julia> Matrix(Lar.lar2cop(CV))
1×8 Array{Int8,2}:
 1  1  1  1  1  1  1  1
```
"""
function lar2cop(CV::Lar.Cells)::Lar.ChainOp
	I = Int64[]; J = Int64[]; Value = Int8[];
	for k=1:size(CV,1)
		n = length(CV[k])
		append!(I, k * ones(Int64, n))
		append!(J, CV[k])
		append!(Value, ones(Int64, n))
	end
	return SparseArrays.sparse(I,J,Value)
end

"""
	cop2lar(cop::Lar.ChainOp)::Lar.Cells

Convert a sparse array of type `ChainOp` into an array of array of type `Cells`.

Notice that `cop2lar` is the inverse function of `lar2cop`. their composition is the identity function.

# Example

```
julia> V,(VV,EV,FV,CV) = Lar.cuboid([1,1,1],true);

julia> Lar.cop2lar(Lar.lar2cop(EV))
12-element Array{Array{Int64,1},1}:
 [1, 2]
 [3, 4]
   ...
 [2, 6]
 [3, 7]
 [4, 8]

julia> Lar.cop2lar(Lar.lar2cop(FV))
6-element Array{Array{Int64,1},1}:
 [1, 2, 3, 4]
 [5, 6, 7, 8]
 [1, 2, 5, 6]
 [3, 4, 7, 8]
 [1, 3, 5, 7]
 [2, 4, 6, 8]

julia> Lar.cop2lar(Lar.lar2cop(CV))
1-element Array{Array{Int64,1},1}:
 [1, 2, 3, 4, 5, 6, 7, 8]
```
"""
function cop2lar(cop::Lar.ChainOp)::Lar.Cells
	[findnz(cop[k,:])[1] for k=1:size(cop,1)]
end

function FV2EVs(copEV::Lar.ChainOp, copFE::Lar.ChainOp)
	EV = [findnz(copEV[k,:])[1] for k=1:size(copEV,1)]
	FE = [findnz(copFE[k,:])[1] for k=1:size(copFE,1)]
	EVs = [[EV[e] for e in fe] for fe in FE]
	return EVs
end

"""
	randomcuboids(n,scale=1.0

Generate the `LAR` model of a collection of `n` random cuboids.
Position, orientation and measure of sides are all random.
"""
function randomcuboids(n,scale=1.0)
	assembly = []
	for k=1:n
		corner = rand(Float64, 2)
		sizes = rand(Float64, 2)
		V,(_,EV,_) = Lar.cuboid(corner,true,corner+sizes)
		center = (corner + corner+sizes)/2
		angle = rand(Float64)*2*pi
		obj = Lar.Struct([ Lar.t(center...), Lar.r(angle),
				Lar.s(scale,scale), Lar.t(-center...), (V,EV) ])
		push!(assembly, obj)
	end
	Lar.struct2lar(Lar.Struct(assembly))
end

function randomcubes(n,scale=1.0)
	assembly = []
	for k=1:n
		corner = rand(Float64, 3)
		sizes = rand(Float64, 3)
		V,(_,EV,FV,_) = Lar.cuboid(corner,true,corner+sizes)
		center = (corner + corner+sizes)/2
		angle1 = rand(Float64)*2*pi
		angle2 = rand(Float64)*2*pi
		obj = Lar.Struct([ Lar.t(center...),
				Lar.r(0,angle2,0),Lar.r(0,0,angle1),
				Lar.s(scale,scale,scale), Lar.t(-center...), (V,EV,FV) ])
		push!(assembly, obj)
	end
	Lar.struct2lar(Lar.Struct(assembly))
end

"""
	compute_FV( copEV::Lar.ChainOp, copFE::Lar.ChainOp )::Lar.Cells

Compute the `FV` array of type `Lar.Cells` from two `Lar.ChainOp`, via
sparse array product.  To be generalized to open 2-manifolds.
"""
function compute_FV( copEV::Lar.ChainOp, copFE::Lar.ChainOp )
	# TODO: generalize for open 2-manifolds
	kFV = (x->div(x,2)).(abs.(copFE) * abs.(copEV)) # works only for closed surfaces
	FV = [SparseArrays.findnz(kFV[k,:])[1] for k=1:size(kFV,1)]
	return FV
end

"""
    triangulate2d(V, EV)

Contrained Delaunay Triangulation of LAR model (V,EV).
Discovery and removal of holes from triangulation, by comparing
original and generated edges. `V` is given by column.
"""
# function triangulate2d(V, EV)
#     # data for Constrained Delaunay Triangulation (CDT)
#     points = convert(Array{Float64,2}, V')
# 	points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
#     edges_list = convert(Array{Int64,2}, hcat(EV...)')
#     edge_boundary = [true for k=1:size(edges_list,1)]
#     triangles = Triangle.constrained_triangulation(points,points_map,edges_list)
#     # edges of the triangulation
#     ev = map(sort,cat([[[u,v], [v,w], [w,u]] for (u,v,w) in triangles]))
#     # remove duplicated edges from triangulation
#     ev_nodups = sort(collect(Set(ev)))
#     ##Plasm.view(Plasm.numbering(0.35)((V,[[[k] for k=1:size(V,2)], ev_nodups])))
#     # dictionary o original edges
# 	edge_dict = Dict(zip(EV,1:length(EV)))
# 	#edge_dict = Dict(zip(ev_nodups,1:length(ev_nodups)))
#
# 	triaedges = Array{Int64,1}(undef,0)
# 	for (u,v) in ev
# 		if haskey(edge_dict, [u,v])
# 			push!(triaedges, edge_dict[[u,v]])
# 		elseif haskey(edge_dict, [v,u])
# 			push!(triaedges, edge_dict[[v,u]])
# 		end
# 	end
#
#     # subdivide original edges between inner and outer
#     counters = zeros(size(edges_list,1))
#     for e in triaedges
#         counters[e]+=1
#     end
#     # compute inner triangles
#     inneredges = Array{Array{Int64,1},1}()
#     for (k,value) in enumerate(counters)
#        if value==2
#            push!(inneredges, EV[k], reverse(EV[k]))
#        end
#     end
#     # compute hole(s): wheater all (some?) triangle edges are inneredges
#     holes = Array{Array{Int64,1},1}()
#     for (k,(u,v,w)) in enumerate(triangles)
#         triangle = [[u,v],[v,w],[w,u]]
#         if setdiff(triangle,inneredges)==[]
#             push!(holes, triangles[k])
#         end
#     end
#     triangles = [tria for tria in triangles if !(tria in holes)]
#     return triangles
# end
function triangulate2d(V, EV)
    # data for Constrained Delaunay Triangulation (CDT)
    points = convert(Array{Float64,2}, V')
	points_map = Array{Int64,1}(collect(1:1:size(points)[1]))
    edges_list = convert(Array{Int64,2}, hcat(EV...)')
    edge_boundary = [true for k=1:size(edges_list,1)] ## dead code !!
    trias = Triangle.constrained_triangulation(points,points_map,edges_list)
	innertriangles = Array{Int64,1}[]
	for (u,v,w) in trias
		point = (points[u,:]+points[v,:]+points[w,:])./3
		copEV = Lar.lar2cop(EV)
		inner = Lar.point_in_face(point, points::Lar.Points, copEV::Lar.ChainOp)
		if inner
			push!(innertriangles,[u,v,w])
		end
	end
    return innertriangles
end

function blockdiag_di_nardo(X::SparseMatrixCSC...)
    num = length(X)
    mX = Int[ size(x, 1) for x in X ]
    nX = Int[ size(x, 2) for x in X ]
    m = sum(mX)
    n = sum(nX)

    Tv = promote_type(map(x->eltype(x.nzval), X)...)
    Ti = isempty(X) ? Int : promote_type(map(x->eltype(x.rowval), X)...)

    colptr = Vector{Ti}(undef, n+1)
    nnzX = Int[ nnz(x) for x in X ]
    nnz_res = sum(nnzX)
    rowval = Vector{Ti}(undef, nnz_res)
    nzval = Vector{Tv}(undef, nnz_res)

    nnz_sofar = 0
    nX_sofar = 0
    mX_sofar = 0
    for i = 1 : num
        colptr[(1 : nX[i] + 1) .+ nX_sofar] = X[i].colptr .+ nnz_sofar
        rowval[(1 : nnzX[i]) .+ nnz_sofar] = X[i].rowval .+ mX_sofar
        nzval[(1 : nnzX[i]) .+ nnz_sofar] = X[i].nzval
        nnz_sofar += nnzX[i]
        nX_sofar += nX[i]
        mX_sofar += mX[i]
    end
    colptr[n+1] = nnz_sofar + 1

    SparseMatrixCSC(m, n, colptr, rowval, nzval)
end


module BoxChopper

export volfrac  # function

const N, P = 1, 2  # negative, positive directions
const NP = (N,P)  # tuple allocation is more efficient than array allocation

const X, Y, Z = 1, 2, 3  # x, y, z coordinates
const XYZ, YZX, ZXY = (X,Y,Z), (Y,Z,X), (Z,X,Y)
const CYC_AXES = (XYZ, YZX, ZXY)

typealias Tuple2{T} NTuple{2,T}
typealias Tuple3{T} NTuple{3,T}

function calc_vcbits(box::Tuple3{Tuple2{Real}}, nout::Tuple3{Real}, nr₀::Real)
    # Calculate the bit array that indicates vertex contained-ness.

    # The bit array is 8 bits (= 1 byte).  The kth bit is 1 if the kth verex is
    # contained in the half space defined by the plane, an 0 otherwise.  The 8 vertices
    # of the box are indexed 1 through 8 in the order of (---), (+--), (-+-), (++-),
    # (--+), (+-+), (-++), (+++), where, e.g., (+--) indicates the vertex at the
    # +x, -y, -z corner.
    nx, ny, nz = nout
    vcbits = 0x00
    bit = 0x01
    for sz = NP, sy = NP, sx = NP
        if nx*box[X][sx] + ny*box[Y][sy] + nz*box[Z][sz] ≤ nr₀
            vcbits |= bit
        end
        bit <<= 1
    end

    return vcbits
end

function isquadsect(vcbits::UInt8)
    # Determine if the vertex contained-ness bit array corresponds to the case where
    # the plane crosses one of the three sets of four parallel edges of the box.

    return vcbits==0x0F || vcbits==~0x0F || vcbits==0x33 || vcbits==~0x33 || vcbits==0x55 || vcbits==~0x55

    # Equivalent to
    # n = count_ones(vcbits)
    # m = count_ones(vcbits & 0x0F)
    # return n==4 && iseven(m)
end

function edgedir_quadsect(vcbits::UInt8)
    # For the cases where the plane crosses a set of four parallel edges of the
    # box, determine which direction those edges lie.

    if vcbits==0x0F || vcbits==~0x0F
        dir = Z
    elseif vcbits==0x33 || vcbits==~0x33
        dir = Y
    else
        assert(vcbits==0x55 || vcbits==~0x55)
        dir = X
    end

    return dir
end

function rvol_quadsect(box::Tuple3{Tuple2{Real}}, nout::Tuple3{Real}, nr₀::Real, vcbits::UInt8)
    # Return the volume fraction for the case where the plane croses a set of four
    # parallel edges.

    w = edgedir_quadsect(vcbits)
    ∆w = box[w][P] - box[w][N]

    ~, u, v = CYC_AXES[w]
    nw, nu, nv = nout[w], nout[u], nout[v]
    mean_cepts = 4nr₀
    for sv = NP, su = NP
        mean_cepts -= nu*box[u][su] + nv*box[v][sv]
    end
    mean_cepts /=  nw * 4∆w

    sw = nw>0 ? N:P  # nw cannot be 0
    return abs(mean_cepts - box[w][sw]/∆w)
end

function rvol_gensect(box::Tuple3{Tuple2{Real}}, nout::Tuple3{Real}, nr₀::Real, vcbits::UInt8)
    # Calculate the volume fraction of most general cases, by cutting out corners
    # from a triangular pyramid.
    # Assume count_ones(vcbits) ≤ 4.  Othewise, call this function with flipped
    # nout, nr₀, vcbits.

    nx, ny, nz = nout
    sx, sy, sz = (nx≥0 ? N:P), (ny≥0 ? N:P), (nz≥0 ? N:P)  # signs of corner
    cx, cy, cz = box[X][sx], box[Y][sy], box[Z][sz]  # corner coordinates
    ∆x, ∆y, ∆z = box[X][P]-box[X][N], box[Y][P]-box[Y][N], box[Z][P]-box[Z][N]  # box edges
    nxcx, nycy, nzcz = nx*cx, ny*cy, nz*cz

    rmax, rmid, rmin =  # (lengths from corner to intercetps) / (box edges)
        abs((nr₀-nycy-nzcz)/nx-cx)/∆x, abs((nr₀-nzcz-nxcx)/ny-cy)/∆y, abs((nr₀-nxcx-nycy)/nz-cz)/∆z

    # Sort rmax, rmin, rmin properly.
    if rmax < rmid; rmax, rmid = rmid, rmax; end
    if rmid < rmin; rmid, rmin = rmin, rmid; end
    if rmax < rmid; rmax, rmid = rmid, rmax; end

    # Calculate the volume of the triangular pyramid, and cut off appropriate corners.
    tmax = 1 - 1/rmax
    rvol_core = 1 + tmax + tmax^2
    if rmid > 1
        tmid = 1 - 1/rmid
        rvol_core -= rmax * tmid^3
    end
    if rmin > 1
        tmin = 1 - 1/rmin
        rvol_core -= rmax * tmin^3
    end

    return rvol_core * rmin * rmid / 6
end

"""
    volfrac(box, nout, r₀)

Returns the volume fraction `rvol = vol(box ⋂ half-space) / vol(box)` that indicates
how much portion of the volume of a box is included the half-space defined by a
plane.  The result is between 0.0 and 1.0, inclusive.

The box needs to be aligned with the Cartesian axes, and is described by `box =
[xn xp; yn yp; zn zp]` that specifies the x-, y-, z-boundaries of the box.  The
half-space is described by the boundary plane.  The boundary plane is described by
the outward normal vector `nout = [nx, ny, nz]` and a point `r₀ = [rx, ry, rz]`
on the plane.  `nout` does not have to be normalized.
"""
function volfrac(box::Tuple3{Tuple2{Real}}, nout::Tuple3{Real}, r₀::Tuple3{Real})
    # Return the volume fraction rvol = vol(box ⋂ half-space) / vol(box).

    nr₀ = nout[X]⋅r₀[X] + nout[Y]⋅r₀[Y] + nout[Z]⋅r₀[Z]
    vcbits = calc_vcbits(box, nout, nr₀)
    nvc = count_ones(vcbits)  # number of vertices contained

    if nvc == 8  # box is inside half-space
        rvol = 1.
    elseif nvc == 0  # box is outside half-space
        rvol = 0.
    elseif isquadsect(vcbits) # plane crosses a set of four parallel edges of box
        rvol = rvol_quadsect(box, nout, nr₀, vcbits)
    elseif nvc ≤ 4 # general cases with nvc ≤ 4
        rvol = rvol_gensect(box, nout, nr₀, vcbits)
    else  # general cases with nvc ≥ 5
        assert(nvc ≥ 5)
        rvol = 1. - rvol_gensect(box, (-nout[1],-nout[2],-nout[3]), -nr₀, ~vcbits)
    end

    return rvol
end

end  # module PlanarBoxDivider


@testset "test_linalg" begin
a = [-3 2;1 1]
b = [0  1]'
c = [1 -1]
r = 3

@test care(a, b, c'*c, r) ≈ [0.5895174372762604 1.8215747248860816; 1.8215747248860823 8.818839806923107]
@test dare(a, b, c'*c, r) ≈ [240.73344504496302 -131.09928700089387; -131.0992870008943 75.93413176750603]

## Test dare method for non invertible matrices
A = [0 1; 0 0];
B0 = [0; 1];
Q = Matrix{Float64}(I, 2, 2);
R0 = 1
X = dare(A, B0, Q, R0);
# Reshape for matrix expression
B = reshape(B0, 2, 1)
R = fill(R0, 1, 1)
@test norm(A'X*A - X - (A'X*B)*((B'X*B + R)\(B'X*A)) + Q) < 1e-14
## Test dare for scalars
A = 1.0;
B = 1.0;
Q = 1.0;
R = 1.0;
X0 = dare(A, B, Q, R);
X = X0[1]
@test norm(A'X*A - X - (A'X*B)*((B'X*B + R)\(B'X*A)) + Q) < 1e-14
# Test for complex matrices
I2 = Matrix{Float64}(I, 2, 2)
@test dare([1.0 im; im 1.0], I2, I2, I2) ≈ [1 + sqrt(2) 0; 0 1 + sqrt(2)]
# And complex scalars
@test dare(1.0, 1, 1, 1) ≈ fill((1 + sqrt(5))/2, 1, 1)
@test dare(1.0im, 1, 1, 1) ≈ fill((1 + sqrt(5))/2, 1, 1)
@test dare(1.0, 1im, 1, 1) ≈ fill((1 + sqrt(5))/2, 1, 1)

## Test gram, ctrb, obsv
a_2 = [-5 -3; 2 -9]
C_212 = ss(a_2, [1; 2], [1 0; 0 1], [0; 0])
C_222 = ss(a_2, [1 0; 0 2], [1 0; 0 1], zeros(2,2))

a_3 = [-0.81  0.47 -0.43  1.58  0.25 -0.40  0.92;
       -0.61 -1.89  0.80 -1.59  2.01  0.98 -0.90;
        0.50 -1.18 -2.12 -1.56 -1.14  0.14 -0.87;
       -1.28  2.12  0.47 -1.16  3.65 -1.23 -1.26;
       -0.24 -0.08  1.58 -3.58 -1.26  1.72 -2.59;
        1.28 -0.96 -1.28 -0.57 -2.43 -2.43 -0.36;
       -0.16  1.53 -0.99  1.47  0.61 -2.22 -3.27]
b_3 =  [ 0.49 -0.80  0.10;
        -0.18  0.70  0.72;
         0.00  0.83  2.58;
         1.42  0.00  0.00;
         0.00  0.22  0.19;
         0.00  0.00 -0.08;
         0.00 -1.15 -1.93]
c_3 = [ 0.00  0.84  0.10  0.30  0.49  1.71  0.00
       -1.79 -0.89 -0.54 -0.60  0.74 -0.19  0.00]
d_3 = [ 1.36  0.96  1.44
       -1.07  0.00 -1.96]
C_732 = ss(a_3,b_3,c_3,d_3);

s = tf("s")
f_C_211 = (s+2)*(s+3)/((s+4)*(s+5))
# biquad passband
omega0 = 52.0; Q = 10
f_C_211_bis = (s/(Q*omega0)) / ((s/omega0)^2 + s/(Q*omega0) + 1)    # Works again in julia 1.0

C_22tf = [0 tf([3,0],[1,1,10]);tf([1,1],[1,5]) tf(2,[1,6])]

da_2 = [0.2 -0.8; -0.8 0.07]
D_221 = ss(da_2, [1 0; 0 2], [1 0], [0 0], 0.005)
D_222 = ss(da_2, [1 0; 0 2], [1 0; 0 1], zeros(2,2), 0.005)

a_4 = [-0.08 0.83 0.0 0.0;-0.83 -0.08 0.0 0.0;0.0 0.0 -0.7 9.0;0.0 0.0 -9.0 -0.7]
b_4 = [1 1;0 0;1 -1;0 0]
c_4 = [0.4 0.0 0.4 0.0;0.6 0.0 1.0 0.0]
d_4 = [0.3 0.0;0.0 -0.15]
D_422 = ss(a_4,b_4,c_4,d_4,0.2)
z = tf("z", 0.05)
D_311 = z^3/((z+0.5)*(z^2-1.4z+0.72801))

C_static = ss([-0.78593 -1.2707;  0.2638 -1.13143])
D_static = ss([0.704473 1.56483; -1.6661 -2.16041], 0.07)

@test gram(C_212,:c) ≈ [0.042016806722689065 0.09663865546218485
    0.09663865546218488 0.24369747899159663]
@test gram(C_212,:o) ≈ [0.09523809523809523 -0.0119047619047619
    -0.011904761904761911 0.059523809523809534]
@test gram(C_222,:c) ≈ [0.11764705882352941 -0.029411764705882346
    -0.02941176470588234 0.21568627450980393]
@test gram(C_222,:o) ≈ [0.09523809523809523 -0.0119047619047619
    -0.011904761904761911 0.059523809523809534]
@test gram(D_221,:c) ≈ [11.895658388619264 -7.526846649259635
    -7.526846649259634 12.517564258299078]
@test gram(D_221,:o) ≈ [3.1039794541519283 -1.7910988952221152
    -1.7910988952221152 2.197919733616834]
@test gram(D_222,:c) ≈ [11.895658388619264 -7.526846649259635
    -7.526846649259634 12.517564258299078]
@test gram(D_222,:o) ≈ [5.301899187768763 -3.2250358337314955
    -3.2250358337314955 4.777830864787395]

@test obsv(C_222) == [1 0; 0 1; -5 -3; 2 -9]
@test ctrb(C_222) == [1 0 -5 -6; 0 2 2 -18]
@test obsv(C_212) == [1 0; 0 1; -5 -3; 2 -9]
@test ctrb(C_212) == [1 -11; 2 -16]
@test norm(C_222) ≈ 0.5773502691896258
@test norm(C_212) ≈ 0.5345224838248488
@test norm(D_222) ≈ 4.940973856125768
@test norm(D_221) ≈ 3.4490083195926426
@test norm(ss([1],[2],[3],[4])) == Inf

#
## Test Hinfinity norm computations
#
# Continuous
Ninf, ω_peak = hinfnorm(ss(-1, 1, 1, 0))
@test Ninf ≈ 1 rtol=1e-6
@test ω_peak ≈ 0 rtol=1e-6

Ninf, ω_peak = hinfnorm(ss(-1, 1, 1, 1))
@test Ninf ≈ 2 rtol=1e-6
@test ω_peak ≈ 0 rtol=1e-6

Ninf, ω_peak = hinfnorm(ss(1.5))
@test Ninf ≈ 1.5 rtol=1e-6
@test ω_peak ≈ 0 rtol=1e-6

tolHinf = 1e-12
@test norm(C_212, Inf, tol=tolHinf) ≈ 0.242535625036333 atol=tolHinf
@test norm(C_222, Inf, tol=tolHinf) ≈ 0.242535625036333 atol=tolHinf
Ninf, ω_peak = hinfnorm(C_732, tol=tolHinf)
@test Ninf ≈ 4.899135403568278 atol=(10*tolHinf)
@test ω_peak ≈ 6.112977387441163 atol=1e-6
@test norm(f_C_211, Inf, tol=tolHinf) ≈ 1.0 atol=(2*tolHinf)
Ninf, ω_peak =  hinfnorm(f_C_211_bis, tol=tolHinf)
@test Ninf ≈ 1.0
@test ω_peak ≈ 52.0

# unstable system
sys = 1/(s-1)
@inferred hinfnorm(sys)
@test norm(sys, Inf, tol=tolHinf) ≈ Inf
Ninf, ω_peak = hinfnorm(sys; tol=tolHinf)
@test Ninf == Inf
@test isnan(ω_peak)
Ninf, ω_peak = linfnorm(sys; tol=tolHinf)
@test Ninf ≈ 1
@test ω_peak ≈ 0

Ninf, ω_peak = hinfnorm(C_static, tol=tolHinf)
@test Ninf ≈ 1.760164138376307 atol=1e-12
@test ω_peak ≈ 0 atol=1e-12
@inferred hinfnorm(C_static, tol=tolHinf)

Ninf, ω_peak = hinfnorm(C_22tf, tol=tolHinf)
@test Ninf ≈ 3.014974550173459 atol=(10*tolHinf)
@test ω_peak ≈ 3.162123338668049 atol=1e-8

# Poles on the imaginary axis
# Only integrator
Ninf, ω_peak = hinfnorm(ss(0.0, 1, 1, 0))
@test Ninf == Inf
@test ω_peak ≈ 0

# Integrator and lag
Ninf, ω_peak = hinfnorm(ss([-2.0 1; 0 0], [0; 1], [1 0], 0))
@test Ninf == Inf
@test ω_peak ≈ 0

# 1/(s^2 + 1)
Ninf, ω_peak = hinfnorm(ss([0 1.0; -1.0 0], [1; 0], [0 1], 0))
@test Ninf == Inf
@test ω_peak ≈ 1

# 1/(s - im), complex coefficients
Ninf, ω_peak = hinfnorm(ss(im, 1, 1, 0))
@test Ninf == Inf
@test ω_peak ≈ 1

# Complex coefficient systems
Ninf, ω_peak = hinfnorm(ss(-1+im, 1, 1, 0))
@test Ninf ≈ 1 rtol=1e-6
@test ω_peak ≈ 1 rtol=1e-3

Ninf, ω_peak = hinfnorm(ss(-1-im, 1, 1, 0))
@test Ninf ≈ 1 rtol=1e-6
@test ω_peak ≈ -1 rtol=1e-3

# 1/(s + 1)/(s - 1)
@test hinfnorm(ss([-1 1; 0 1], [0; 1], [1 0], 0))[1] == Inf
@test linfnorm(ss([-1 1; 0 1], [0; 1], [1 0], 0))[1] ≈ 1 rtol=1e-6

# Second order resonant system with peaks at ±sqrt(2)
G = ss([0 1; -1 -1], [0; 1], [1 0], 0)
Ninf, ω_peak = hinfnorm(G)
@test Ninf ≈ 2/sqrt(3) rtol=1e-6
@test ω_peak ≈ 1/sqrt(2) rtol=1e-3
@test hinfnorm(G, tol=1e-8)[1] ≈ 2/sqrt(3) rtol=1e-8
@test hinfnorm(G, tol=1e-10)[1] ≈ 2/sqrt(3) rtol=1e-10

# Same version as above but frequency shifted by -1, i.e., two peaks at -1 ± sqrt(2)
Ninf, ω_peak = hinfnorm(ss([0 1; -im -(1+2im)], [0; 1], [1 0], 0))
@test Ninf ≈ 2/sqrt(3) rtol=1e-6
@test abs(ω_peak+1) ≈ 1/sqrt(2) rtol=1e-3

# Same version as above but frequency shifted by +1, i.e., two peaks at 1 ± sqrt(2)
Ninf, ω_peak = hinfnorm( ss([0 1; im -1+2im], [0; 1], [1 0], 0))
@test Ninf ≈ 2/sqrt(3) rtol=1e-6
@test abs(ω_peak-1) ≈ 1/sqrt(2) rtol=1e-3

# System with mutiple resonance peaks
A = [-0.1*I        -Diagonal(1:10)
     Diagonal(1:10) zeros(10,10)]

Ninf, ω_peak = hinfnorm(ss(A, 0.1*[ones(10); zeros(10)], [zeros(1,10) ones(1,10)], 0))
@test Ninf ≈ 1.05871265 rtol=1e-5
@test ω_peak ≈ 0.9874 rtol=1e-4

Ninf, ω_peak = hinfnorm(ss(A, 0.1*[ones(4); 2; ones(5); zeros(10)], [zeros(1,10) ones(1,10)], 0))
@test Ninf ≈ 2.00918 rtol=1e-5
@test ω_peak ≈ 4.9981 rtol=1e-4

# Discrete Time
Ninf, ω_peak =  hinfnorm(ss(0.8, 1, 1, 0, 1))
@test Ninf ≈ 5 rtol=1e-6
@test ω_peak ≈ 0

Ninf, ω_peak =  hinfnorm(ss(-0.8, 1, 1, 0, 1))
@test Ninf ≈ 5 rtol=1e-6
@test ω_peak ≈ pi

Ninf, ω_peak = hinfnorm(D_221, tol=tolHinf)
@test Ninf ≈ 17.794697451669421 atol=(20*tolHinf)
@test ω_peak ≈ 0 atol=1e-8

Ninf, ω_peak = linfnorm(D_422, tol=tolHinf)
@test Ninf ≈ 3.360351099392252 atol=(10*tolHinf)
@test ω_peak ≈ 8.320643111730551 atol=1e-6
Ninf, ω_peak = hinfnorm(D_422, tol=tolHinf)
@test Ninf == Inf
@test isnan(ω_peak)

Ninf, ω_peak = hinfnorm(D_311, tol=tolHinf)
@test Ninf ≈ 4.458729529942810 atol=(10*tolHinf)
@test ω_peak ≈ 11.878021287349698 atol=1e-6

Ninf, ω_peak = hinfnorm(D_static, tol=tolHinf)
@test Ninf ≈ 3.205246234285972 atol=1e-12
@test ω_peak ≈ 0 atol=1e-12

# Pole on unit circle
Ninf, ω_peak = hinfnorm(ss(1, 1, 1, 0, 1))
@test Ninf == Inf
@test ω_peak ≈ 0

# Complex coefficients
Ninf, ω_peak =  hinfnorm(ss(0.8*exp(1im), 1, 1, 0, 1))
@test Ninf ≈ 5 rtol=1e-6
@test ω_peak ≈ 1

Ninf, ω_peak =  hinfnorm(ss(0.8*exp(-1im), 1, 1, 0, 1))
@test Ninf ≈ 5 rtol=1e-6
@test ω_peak ≈ -1

sys = hinfnorm(ss(0.8*exp(2.5im), 1, 1, 0, 1))
Ninf, ω_peak =  hinfnorm(ss(0.8*exp(2.5im), 1, 1, 0, 1))
@test Ninf ≈ 5 rtol=1e-6
@test ω_peak ≈ 2.5
Ninf, ω_peak = linfnorm(ss(0.8*exp(2.5im), 1, 1, 0, 1))
@test Ninf ≈ 5 rtol=1e-6
@test ω_peak ≈ 2.5

# Unstable discrete-time system
sys = ss(2, 3, 1, 1, 1)
Ninf, ω_peak = hinfnorm(sys)
@test Ninf == Inf
@test isnan(ω_peak)
Ninf, ω_peak = linfnorm(sys)
@test Ninf ≈ 2.0 rtol=1e-6
@test ω_peak ≈ 0

A = [1  100  10000; .01  1  100; .0001  .01  1]
T, P, B = balance(A)
# The scaling is BLAS dependent. However, the ratio should be the same on all
# machines. We just need to check that T == res * constant
res_diag = [512, 8, 0.0625]
constant = T[1]/res_diag[1]
@test all(diag(T) == res_diag * constant)
@test P == [1 0 0; 0 1 0; 0 0 1]
@test B ≈ [1.0 1.5625 1.220703125; 0.64 1.0 0.78125; 0.8192 1.28 1.0]
end


# Plots a 1D or 2D GPE object.
#
# Keyword arguments:
#   - β: level of confidence interval for ribbon (1D only)
#   - obsv: plot observation points (1D only)
#   - var: plot predicted variance rather than mean (2D only)
@recipe function f(gp::GPE; β=0.95, obsv=true, var=false)
    @assert gp.dim ∈ (1,2)
    if gp.dim == 1
        xlims --> (minimum(gp.x), maximum(gp.x))
        xmin, xmax = plotattributes[:xlims]
        x = range(xmin, stop=xmax, length=100)
        μ, Σ = predict_f(gp, x)
        y = μ
        err = invΦ((1+β)/2)*sqrt.(Σ)

        @series begin
            seriestype := :path
            ribbon := err
            fillcolor --> :lightblue
            color --> :black
            x,y
        end
        if obsv
            @series begin
                seriestype := :scatter
                markershape := :circle
                markercolor := :black
                gp.x', gp.y
            end
        end
    else
        xlims --> (minimum(gp.x[1,:]), maximum(gp.x[1,:]))
        ylims --> (minimum(gp.x[2,:]), maximum(gp.x[2,:]))
        xmin, xmax = plotattributes[:xlims]
        ymin, ymax = plotattributes[:ylims]
        x = range(xmin, stop=xmax, length=50)
        y = range(ymin, stop=ymax, length=50)
        xgrid = repeat(x', 50, 1)
        ygrid = repeat(y, 1, 50)
        μ, Σ = predict_f(gp,[vec(xgrid)';vec(ygrid)'])
        if var
            zgrid  = reshape(Σ,50,50)
        else
            zgrid  = reshape(μ,50,50)
        end
        x, y, zgrid
    end
end


@testset "Locally free class group of group algebras" begin
  G = small_group(8, 4)
  A = AlgAss(AlgGrp(FlintQQ, G))[1]
  O = Order(A, basis(A))
  C = Hecke.locally_free_class_group(O)
  @test C.snf == fmpz[ 2 ]

  G = small_group(10, 2)
  A = AlgAss(AlgGrp(FlintQQ, G))[1]
  O = Order(A, basis(A))
  C = Hecke.locally_free_class_group(O)
  @test C.snf == fmpz[]

  G = small_group(12, 3)
  A = AlgAss(AlgGrp(FlintQQ, G))[1]
  O = Order(A, basis(A))
  C = Hecke.locally_free_class_group(O)
  @test C.snf == fmpz[]
end

@testset "Locally free class group of matrix algebras" begin
  Qx, x = FlintQQ["x"]
  f = x^2 + 47
  K, a = number_field(f, "a")
  A = AlgAss(MatrixAlgebra(K, 2))
  A, _, _ = Hecke.restrict_scalars(A, FlintQQ)
  O = MaximalOrder(A)
  C = Hecke.locally_free_class_group(O) # == class_group(K)
  @test C.snf == fmpz[ 5 ]

  f = x^2 + 26
  K, a = number_field(f, "a")
  A = AlgAss(MatrixAlgebra(K, 2))
  A, _, _ = Hecke.restrict_scalars(A, FlintQQ)
  O = MaximalOrder(A)
  C = Hecke.locally_free_class_group(O) # == class_group(K)
  @test C.snf == fmpz[ 6 ]
end


#solves Ax = b where A is a symmetric tridiagonal matrix
#a is the diagonal of A, c is the subdiagonal of A, b is the rhs,
#d is the diagonal of U, x is the solution
function serial_trid(a, b, c, d, x)
  n = size(a,1)
  d[1] = a[1]
  x[1] = b[1]
  for i = 2:n   # 3 reads and 2 writes
    l = c[i - 1] / d[i - 1]
    d[i] = a[i] - c[i - 1] * l
    x[i] = b[i] - l * x[i - 1]
  end

  x[n] = x[n] / d[n]
  for i = n - 1 : -1 : 1    # 3 reads and 1 write
    x[i] = (x[i] - c[i] * x[i + 1]) / d[i]
  end
end

#solves Ax = b in place, where A is a symmetric tridiagonal matrix
#a is the diagonal of A, overwritten with the diagonal of U
#c is the subdiagonal of A
#b is the rhs, overwritten with the solution
function serial_trid(a, b, c)
  n = size(a,1)
  for i = 2:n
    l = c[i - 1] / a[i - 1]
    a[i] -= c[i - 1] * l
    b[i] -= l * b[i - 1]
  end

  b[n] /= a[n]
  for i = n - 1 : -1 : 1
    b[i] = (b[i] - c[i] * b[i + 1]) / a[i]
  end
end

#compute A*x, where A is a tridiagonal matrix
#a is the diagonal of A
#b is the super diagonal of A
#c is the sub diagonal of A
#x is the column vector
function matrix_vector_product(a, b, c, x)
  n = size(x, 1)
  y = zeros(n)
  for i = 1 : n
    y[i] = a[i] * x[i]
  end
  for i = 2 : n
    y[i] += c[i - 1] * x[i - 1]
  end
  for i = 1 : n - 1
    y[i] += b[i] * x[i + 1]
  end
  y
end


# Copyright (c) 2019 Matthew Help and contributors                               #src
#                                                                                #src
# Permission is hereby granted, free of charge, to any person obtaining a copy   #src
# of this software and associated documentation files (the "Software"), to deal  #src
# in the Software without restriction, including without limitation the rights   #src
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      #src
# copies of the Software, and to permit persons to whom the Software is          #src
# furnished to do so, subject to the following conditions:                       #src
#                                                                                #src
# The above copyright notice and this permission notice shall be included in all #src
# copies or substantial portions of the Software.                                #src
#                                                                                #src
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     #src
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       #src
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    #src
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         #src
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  #src
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE  #src
# SOFTWARE.                                                                      #src

# # N-Queens

# **Originally Contributed by**: Matthew Helm ([with help from @mtanneau on Julia Discourse](https://discourse.julialang.org/t/which-jump-jl-solver-for-this-problem/43350/17?u=mthelm85))

# The N-Queens problem involves placing N queens on an N x N chessboard such
# that none of the queens attacks another. In chess, a queen can move
# vertically, horizontally, and diagonally so there cannot be more than one
# queen on any given row, column, or diagonal.

# ![Four Queens](../../assets/n_queens4.png)

# *Note that none of the queens above are able to attack any other as a result
# of their careful placement.*

using JuMP
import GLPK
import LinearAlgebra

# N-Queens
N = 8

model = Model(GLPK.Optimizer)

# Next, let's create an N x N chessboard of binary values. 0 will represent an
# empty space on the board and 1 will represent a space occupied by one of our
# queens:

@variable(model, x[1:N, 1:N], Bin)

# Now we can add our constraints:

# There must be exactly one queen in a given row/column
for i = 1:N
    @constraint(model, sum(x[i, :]) == 1)
    @constraint(model, sum(x[:, i]) == 1)
end

# There can only be one queen on any given diagonal
for i in -(N-1):(N-1)
    @constraint(model, sum(LinearAlgebra.diag(x, i)) <= 1)
    @constraint(model, sum(LinearAlgebra.diag(reverse(x, dims=1), i)) <= 1)
end

# That's it! We are ready to put our model to work and see if it is able to find
# a feasible solution:

optimize!(model)

# We can now review the solution that our model found:

solution = convert.(Int, value.(x))

# ![Four Queens](../../assets/n_queens.png)


slope = open("./inputs/day3.txt") do f
	readlines(f) |> l -> split.(l, "")
end

function count_trees(Δx, Δy)
	x = 1
	y = 1

	trees = 0

	while y <= length(slope)
		if slope[y][x] == "#"
			trees += 1
		end

		x += Δx
		y += Δy

		if x > length(slope[1])
			x %= length(slope[1])
		end
	end

	return trees
end

part1() = count_trees(3, 1)

part2() = ([(1, 1), (3, 1), (5, 1), (7, 1), (1, 2)] .|> args -> count_trees(args...)) |> prod

println("day 3:\n\tpart 1: ", part1(), "\n\tpart 2: ", part2())


using Test

@testset "CHSH facet regression using relabels" begin

using BellScenario: Degeneracy, LocalPolytope, ConvexPolytope

function chsh_inequalities()
    # positivity
    I_ineq = [0 0 0 0 0 -1 0 0 0]
    # non-trivial CH inequality
    II_ineq = [0 -1 0 -1 0 1 1 1 -1]

    (α_out_relabels, β_out_relabels) = Degeneracy.bipartite_output_relabels((2,2),(2,2),"non-signaling")
    in_relabels = Degeneracy.bipartite_input_relabels((2,2),(2,2),"non-signaling")

    ineqs = []
    # TODO: resolve the 3x redundancy in relabeled inequalities
    for α_out_relabel in α_out_relabels
        for β_out_relabel in β_out_relabels
            for in_relabel in in_relabels
                push!(ineqs, I_ineq*in_relabel*α_out_relabel*β_out_relabel)
                push!(ineqs, II_ineq*in_relabel*α_out_relabel*β_out_relabel)
            end
        end
    end

    unique(ineqs)
end

@testset "verifying polytope" begin

    dir = "./test/regression/files/"

    vertices = LocalPolytope.vertices((2,2),(2,2))
    @test size(vertices) == (16,)
    @test size(vertices[1]) == (8,1)

    constraints = ConvexPolytope.facet_constraints(vertices, dir=dir)
    @test constraints[2] == []
    @test constraints[3] == []

    ineq_matches = chsh_inequalities()

    inequalities = constraints[1]
    issetequal(inequalities, ineq_matches)
end

end


# Run a synthetic benchmark (Jarosch's 2013 benchmark)
using Plots, Printf

@views av(A)    = 0.25*(A[1:end-1,1:end-1].+A[2:end,1:end-1].+A[1:end-1,2:end].+A[2:end,2:end])
@views av_xa(A) = 0.5.*(A[1:end-1,:].+A[2:end,:])
@views av_ya(A) = 0.5.*(A[:,1:end-1].+A[:,2:end])
@views inn(A)   = A[2:end-1,2:end-1]

@views function iceflow()
    # physics
    s2y      = 3600*24*365.25  # seconds to years
    lx, ly   = 30e3, 30e3
    rho_i    = 910.0           # ice density
    g        = 9.81            # gravity acceleration
    npow     = 3.0             # Glen's power law exponent
    a0       = 1.5e-24         # Glen's law enhancement term
    # numerics
    nx, ny   = 100, 100        # numerical grid resolution
    itMax    = 1e5             # number of iteration steps
    nout     = 200             # error check frequency
    tolnl    = 1e-6            # nonlinear tolerance
    epsi     = 1e-4            # small number
    damp     = 0.85            # convergence acceleration
    dtausc   = 1.0/2.0         # iterative dtau scaling
    # derived physics
    a        = 2.0*a0/(npow+2)*(rho_i*g)^npow*s2y
    # derived numerics
    dx, dy   = lx/nx, ly/ny
    xc, yc   = LinRange(dx/2, lx-dx/2, nx), LinRange(dy/2, ly-dy/2, ny)
    (Xc,Yc)  = ([x for x=xc,y=yc], [y for x=xc,y=yc])
    cfl      = max(dx^2,dy^2)/4.1
    # array initialisation
    S        = zeros(nx  , ny  )
    B        = zeros(nx  , ny  )
    M        = zeros(nx  , ny  )
    Err      = zeros(nx  , ny  )
    dSdx     = zeros(nx-1, ny  )
    dSdy     = zeros(nx  , ny-1)
    gradS    = zeros(nx-1, ny-1)
    D        = zeros(nx-1, ny-1)
    qHx      = zeros(nx-1, ny-2)
    qHy      = zeros(nx-2, ny-1)
    dtau     = zeros(nx-2, ny-2)
    ResH     = zeros(nx-2, ny-2)
    dHdt     = zeros(nx-2, ny-2)
    Vx       = zeros(nx-1, ny-1)
    Vy       = zeros(nx-1, ny-1)
    # initial condition (Jarosch's 2013 benchmark)
    H        =  ones(nx  , ny  )
    xm, xmB  = 20e3, 7e3
    M       .= (((npow.*2.0./xm.^(2*npow-1)).*Xc.^(npow-1)).*abs.(xm.-Xc).^(npow-1)).*(xm.-2.0.*Xc)
    M[Xc.>xm]  .= 0.0
    B[Xc.<xmB] .= 500
    # smoothing (Mahaffy, 1976)
    B[2:end-1,2:end-1] .= B[2:end-1,2:end-1] .+ 1.0./4.1.*(diff(diff(B[:,2:end-1], dims=1), dims=1) .+ diff(diff(B[2:end-1,:], dims=2), dims=2))
    S       .= B .+ H
    # iteration loop
    it = 1; err = 2*tolnl
    while err>tolnl && it<itMax
        Err   .= H
        # compute diffusivity
        dSdx  .= diff(S, dims=1)/dx
        dSdy  .= diff(S, dims=2)/dy
        gradS .= sqrt.(av_ya(dSdx).^2 .+ av_xa(dSdy).^2)
        D     .= a*av(H).^(npow+2) .* gradS.^(npow-1)
        # compute flux
        qHx   .= .-av_ya(D).*diff(S[:,2:end-1], dims=1)/dx
        qHy   .= .-av_xa(D).*diff(S[2:end-1,:], dims=2)/dy
        # update ice thickness
        dtau  .= dtausc*min.(1.0, cfl./(epsi .+ av(D)))
        ResH  .= .-(diff(qHx, dims=1)/dx .+ diff(qHy, dims=2)/dy) .+ inn(M)
        dHdt  .= dHdt.*damp .+ ResH
        H[2:end-1,2:end-1] .= max.(0.0, inn(H) .+ dtau.*dHdt)
        # update surface
        S     .= B .+ H
        # error check
        if mod(it, nout)==0
            Err  .= Err .- H
            err   = (sum(abs.(Err))./nx./ny)
            @printf("it = %d, error = %1.2e \n", it, err)
            if isnan(err) error("NaNs") end # safeguard
        end
        it += 1
    end
    # compute velocities
    Vx .= -D./(av(H) .+ epsi).*av_ya(dSdx)
    Vy .= -D./(av(H) .+ epsi).*av_xa(dSdy)
    # visualisation
    FS = 7
    xc, yc = xc./1e3, yc./1e3
    xax = ((xc[1], xc[end]), font(FS, "Courier"))
    yax = ((yc[1], yc[end]), font(FS, "Courier"))
    p1 = heatmap(xc, yc, S' , aspect_ratio=1, xaxis=xax, yaxis=yax, c=:davos, title="Surface elev. [m]", titlefont="Courier", titlefontsize=FS)
    p2 = heatmap(xc, yc, H' , aspect_ratio=1, xaxis=xax, yaxis=yax, c=:davos, title="Ice thickness [m]", titlefont="Courier", titlefontsize=FS)
    # display(plot(p1, p2, size=(400,160), dpi=200))
    plot(p1, p2, size=(400,160), dpi=200)
    !ispath("../output") && mkdir("../output")
    savefig("../output/iceflow_bench_out.png")
    return
end

iceflow()


using jInv.Mesh
using Multigrid
using SparseArrays
using LinearAlgebra
using Test
println("************************************************* GMG RAP for Poisson 2D ******************************************************");

domain = [0.0, 1.0, 0.0, 1.0];
n = [128,128];
Mr = getRegularMesh(domain,n)
G = getNodalGradientMatrix(Mr);
Ar = G'*G;
Ar = Ar + 1e-4*opnorm(Ar,1)*sparse(1.0I,size(Ar,2),size(Ar,2));

levels      = 4;
numCores 	= 8;
maxIter     = 5;
relativeTol = 1e-10;
relaxType   = "Jac-GMRES";
relaxParam  = 0.75;
relaxPre 	= 1;
relaxPost   = 1;
cycleType   ='V';
coarseSolveType = "NoMUMPS";

MG = getMGparam(Float64,Int64,levels,numCores,maxIter,relativeTol,relaxType,relaxParam,relaxPre,relaxPost,cycleType,coarseSolveType,0.5,0.0);

N = size(Ar,2);

b = Ar*rand(N,2);
b = b/norm(b);
x = zeros(N,2);

MGsetup(Ar,Mr,MG,size(b,2),true);

println("****************************** Stand-alone GMG RAP for Poisson: ******************************")
x = solveMG(MG,b,x,true)[1];
println("*******************Outside: ",norm(Ar*x - b)/norm(b))
@test norm(Ar*x - b) < 0.005;
println("****************************** Stand-alone GMG RAP : iterative coarsest ***********************")
coarseSolveType = "GMRES"
MG = getMGparam(Float64,Int64,levels,numCores,maxIter,relativeTol,relaxType,relaxParam,relaxPre,relaxPost,cycleType,coarseSolveType,0.5,0.0);
MGsetup(Ar,Mr,MG,size(b,2),true);
solveMG(MG,b,x,true);
@test norm(Ar*x - b) < 0.001;
println("****************************** GMRES preconditioned with GMG, Jac-GMRES relaxation: ******************************")
x[:] .= 0.0
relaxType   = "Jac-GMRES";
coarseSolveType = "NoMUMPS"
MG = getMGparam(Float64,Int64,levels,numCores,maxIter,relativeTol,relaxType,relaxParam,relaxPre,relaxPost,cycleType,coarseSolveType,0.5,0.0);
MGsetup(Ar,Mr,MG,size(b,2),true);
x = solveGMRES_MG(Ar,MG,b,x,true,10,true)[1];
println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Outside: ",norm(Ar*x - b)/norm(b))
@test norm(Ar*x - b) < 0.001;

println("****************************** 3D Poisson ******************************")

domain = [0.0, 1.0, 0.0, 1.0,0.0,1.0];
n = [32,32,16];
Mr = getRegularMesh(domain,n)
G = getNodalGradientMatrix(Mr);
Ar = G'*G;
Ar = Ar + 1e-4*opnorm(Ar,1)*sparse(1.0I,size(Ar,2),size(Ar,2));

MG = getMGparam(Float64,Int64,levels,numCores,maxIter,relativeTol,relaxType,relaxParam,relaxPre,relaxPost,cycleType,coarseSolveType,0.5,0.0);

N = size(Ar,2);

b = Ar*rand(N,2);
b = b/norm(b);
x = zeros(N,2);

MGsetup(Ar,Mr,MG,size(b,2),true);

println("****************************** Stand-alone 3D GMG RAP Poisson: ******************************")
solveMG(MG,b,x,true);
@test norm(Ar*x - b) < 0.01;

Ar = 0;
b = 0;
x = 0;
Mr = 0;
copySolver(MG);
destroyCoarsestLU(MG);
Multigrid.clear!(MG)
MG = 0;


## External interface

# Main function
"""
    heppcat(Y,k,iters;init=homppca(Y,k))

Estimate probabilistic PCA model for noise that is heteroscedastic across samples.

Inputs are:
+ `Y` : list of matrices (each column is a sample)
+ `k` : number of factors
+ `iters` : number of iterations to run
+ `init`  : initial model (will be modified in-place)
Output is a [`HePPCATModel`](@ref) object.
"""
function heppcat(Y,k,iters;init=homppca(Y,k))
    M = init
    for _ in 1:iters
        updatev!(M,Y,ExpectationMaximization())
        updateF!(M,Y,ExpectationMaximization())
    end
    M
end

# Homoscedastic initialization
"""
    homppca(Y,k)

Estimate probabilistic PCA model for noise that is homoscedastic across samples.

Inputs are:
+ `Y` : list of matrices (each column is a sample)
+ `k` : number of factors
Output is a [`HePPCATModel`](@ref) object.
"""
function homppca(Y,k)
    Yf = reduce(hcat,Y)
    n, L = size(Yf,2), length(Y)
    Uh, s, _ = svd(Yf)
    λh = abs2.(s)./n
    λb = mean(λh[k+1:end])
    HePPCATModel(Uh[:,1:k],λh[1:k] .- λb,I(k),fill(λb,L))
end

# log-likelihood (todo: add constant)
"""
    loglikelihood(M::HePPCATModel,Y)

Log-likelihood of model `M` with respect to data `Y` (dropping constant term).
"""
function loglikelihood(M::HePPCATModel,Y)
    d, k = size(M.U)
    n, L = size.(Y,2), length(Y)
    return 1/2*sum(1:L) do l
        norm(sqrt(Diagonal((M.λ./M.v[l])./(M.λ .+ M.v[l])))*M.U'Y[l])^2 -
        n[l]*sum(log.(M.λ .+ M.v[l])) - n[l]*(d-k)*log(M.v[l]) - norm(Y[l])^2/M.v[l]
    end
end


using ReachabilityAnalysis, Plots

@taylorize function lorenz!(dx, x, params, t)
    local σ = 10.0
    local β = 8.0 / 3.0
    local ρ = 28.0
    dx[1] = σ * (x[2] - x[1])
    dx[2] = x[1] * (ρ - x[3]) - x[2]
    dx[3] = x[1] * x[2] - β * x[3]
    return dx
end

X0 = Hyperrectangle(low=[0.9, 0.0, 0.0], high=[1.1, 0.0, 0.0])
prob = @ivp(x' = lorenz!(x), dim=3, x(0) ∈ X0);

alg = TMJets(abstol=1e-15, orderT=10, orderQ=2, maxsteps=50_000);

sol = solve(prob, T=10.0, alg=alg);

solz = overapproximate(sol, Zonotope);

plot(solz, vars=(0, 1), xlab="t", ylab="x")

plot(solz(0.0 .. 1.5), vars=(0, 1), xlab="t", ylab="x", lw=0.0)
plot!(x -> 20.0, c=:red, xlims=(0.0, 1.5), lab="")

ρ([1.0, 0.0, 0.0], solz(0.0 .. 1.5))

plot(solz, vars=(0, 2), xlab="t", ylab="y")

-ρ([0.0, -1.0, 0.0], solz)

ρ([0.0, 1.0, 0.0], solz)

plot(solz, vars=(0, 3), xlab="t", ylab="z")

plot(solz, vars=(1, 3))


function vikor(decisionMat::DataFrame, weights::Array{Float64,1}; v::Float64=0.5)::VikorResult
    w = unitize(weights)

    nalternatives, ncriteria = size(decisionMat)

    col_max = colmaxs(decisionMat)
    col_min = colmins(decisionMat)

    A = similar(decisionMat)

    for i in 1:nalternatives
        for j in 1:ncriteria
            @inbounds A[i, j] = abs((col_max[j] - decisionMat[i, j]) / (col_max[j] - col_min[j]))
        end
    end

    weightedA = w * A

    s = Array{Float64,1}(undef, nalternatives)
    r = similar(s)
    q = similar(s)

    for i in 1:nalternatives
        s[i] = sum(weightedA[i,:])
        r[i] = maximum(weightedA[i,:])
    end

    smin = minimum(s)
    smax = maximum(s)
    rmin = minimum(r)
    rmax = maximum(r)
    q = v .* ((s .- smin ./ (smax .- smin))) + (1 - v) .* ((r .- rmin ./ (rmax .- rmin)))

    best_index = sortperm(q) |> first

    result = VikorResult(
        decisionMat,
        w,
        weightedA,
        best_index,
        q
    )
    return result
end


## File contains the fitting funcs

using JuMP
using Gurobi
using Distributions

include("BertrandNash.jl")  #file generates data

function setUpFitting()
	m = Model(solver=GurobiSolver(OutputFlag=false))
	@defVar(m, vthetas1[1:3])
	@defVar(m, vthetas2[1:3])
	# @addConstraint(m, vthetas1[2])
	# @addConstraint(m, vthetas2[1])
	@defVar(m, vintercept1 >= 0)
	@defVar(m, vintercept2 >= 0)
	#normalization choice
	@addConstraint(m, vthetas1[1] <= -1.)
	@addConstraint(m, vthetas2[2] <= -1.)
	return m, vthetas1, vthetas2, vintercept1, vintercept2
end

#adds the constraints to the modl for the residual for this observation
function addResids(m, thetas1, thetas2, intercept1, intercept2, prices_t, GDP_t, pmax)
	@defVar(m, y[1:2] >= 0)
	@defVar(m, resid >= 0)
	@defVar(m, d[1:2] >=0)  #doesn't seem to work if we insist these are positive...
	@addConstraint(m, d[1] == prices_t[1] * thetas1[1] + prices_t[2] * thetas1[2] + GDP_t * thetas1[3] + intercept1 )
	@addConstraint(m, d[2] == prices_t[1] * thetas2[1] + prices_t[2] * thetas2[2] + GDP_t * thetas2[3] + intercept2 )
	@addConstraint(m, y[1] >= d[1] + prices_t[1] * thetas1[1])
	@addConstraint(m, y[2] >= d[2] + prices_t[2] * thetas2[2])
	@addConstraint(m, pmax * (y[1] + y[2]) - prices_t[1] * d[1] - prices_t[2] * d[2] -
					prices_t[1]^2 * thetas1[1] - prices_t[2]^2 * thetas2[2] <= resid )
	return resid
end

#calc an out of sample residual.  may be a better way to do this.
function calcResid(fitTheta1, fitTheta2, fitIntercept1, fitIntercept2, prices, GDP, pmax)
	m, vthetas1, vthetas2, vintercept1, vintercept2 = setUpFitting()
	for i = 1:3
		@addConstraint(m, vthetas1[i] == fitTheta1[i])
		@addConstraint(m, vthetas2[i] == fitTheta2[i])
	end
	@addConstraint(m, vintercept1 == fitIntercept1)
	@addConstraint(m, vintercept2 == fitIntercept2)
	resid = addResids(m, vthetas1, vthetas2, vintercept1, vintercept2, prices, GDP, pmax)
	@setObjective(m, Min, resid)
	status = solve(m);
	if status != :Optimal
		println(prices, status)
		error(status)
	end

	return getObjectiveValue(m)
end


include("helper.jl")

include("chebyshevtransform.jl")

include("lyap.jl")
include("constrained_lyap.jl")

include("bary.jl")
include("clenshaw.jl")
include("ultraspherical.jl")
include("fourier.jl")

include("hesseneigs.jl")

include("LowRankMatrix.jl")
include("AlmostBandedMatrix.jl")
include("BandedBlockBandedMatrix.jl")
include("RaggedMatrix.jl")


function update!(s::Int,smax::Int,data::Array{Float64,1},nodes::Array{Float64,1},w::Float64,t::Float64,r::Float64)
    x = rand(data)
    dist = distance(nodes,x)
    bmu = bestMatchingUnit(dist)
    θ = neighbourhood(bmu,s,smax,nodes,w,t,r)
    for i in 1:length(nodes)
        nodes[i] += θ[i]*dist[i]
    end
end

function SOM(n::Int,smax::Int,data::Array{Float64,1};
            w::Float64=0.1,     # width scale of the Gaussian neighbourhood
            t::Float64=0.001,    # decay rate
            r::Float64=0.001)    # shrink rate

    nodes = maxentropy(n,data)

    w = maxspread(data)/length(nodes)*w

    for s in 1:smax
        update!(s,smax,data,nodes,w,t,r)
    end

    return nodes
end


# -*- coding: utf-8 -*-
# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .jl
#       format_name: light
#       format_version: '1.3'
#       jupytext_version: 0.8.6
#   kernelspec:
#     display_name: Julia 1.0.3
#     language: julia
#     name: julia-1.0
# ---

module DiscreteTimeSEIRD
using DifferentialEquations

function seird(du, u, parms, t)
    S,E,Ir,Id,R,D = u
    β,δ,γ,Γ,μ,ϵ,ω = parms
    I = Ir + Id
    N = S + E + I + R + D
    dS = S - β*S*I/N + ω*R
    dE = E + β*S*I/N - γ*E + ϵ
    dIr = Ir + γ*(1-μ)E - γ*Ir + ϵ
    dId = Id + γ*μ*E - Γ*Id + ϵ
    dR = R + γ*Ir - ω*R
    dD = D + Γ*Id
    du = [dS, dE, dIr, dId, dR, dD]
end

end


function get_sensors_ring(; num_pts=180, φ_inicial=0, φ_final=2π, kR=1, θ=π/2)
    ϕ_range = range(φ_inicial, φ_final; length=num_pts)
    θ_range = ones(length(ϕ_range)) .* θ

    x = kR .* cos.(ϕ_range) .* sin.(θ_range)
    y = kR .* sin.(ϕ_range) .* sin.(θ_range)
    z = kR .* cos.(θ_range)
    return Array(transpose([x y z]))
end


# demo sum-of-squares RKHS.

using LinearAlgebra
import Random

import PyPlot
import Interpolations
import Distributions

import SignalTools

include("../src/misc/declarations.jl")

include("../src/RKHS/RKHS.jl")
include("../src/RKHS/kernel.jl")
include("../src/RKHS/interpolators.jl")

include("../src/warp_map/Rieszanalysis.jl")
include("../src/misc/utilities.jl")

PyPlot.close("all")

Random.seed!(25)

fig_num = 1

##### regression

# use spline (better conditioned) for large N.
#θ = Spline34KernelType(0.2)
#θ = Spline34KernelType(0.6)
#θ = Spline34KernelType(0.8)
θ = Spline34KernelType(1.1)

# use this kernel when doing investigation on the eigenfunctions that are associated
#   with the kernel.
#θ = GaussianKernel1DType(1.1)
#θ = GaussianKernel1DType(0.8)

μ = 1e-6
#μ = 1.0
#μ = 0.0

#N = 14
#N = 50
N = 500

x_range = LinRange(-π,2.34*π,N)
x_range = LinRange(-π-4, 2.34*π+4,N)

X = collect( [x_range[n]] for n = 1:N )
coordwrapper = xx->convert2itpindex(xx, X[1], X[end], [length(X)])

### specify oracle function.

K = 3
mixture_weights = collect( sqrt(n) for n = 1:K)
mixture_weights = mixture_weights./sum(mixture_weights)

normal_dists = collect( Distributions.Normal(randn(), 2*rand()) for n = 1:K )

# mixture distribution.
mix_normal_dist = Distributions.MixtureModel(normal_dists, mixture_weights)

f = xx->sinc(xx)*xx^3
#f = xx->10*Distributions.pdf(mix_normal_dist,xx)
y = f.(x_range)

### SDP
max_iterations = 50000
#max_iterations = 5000

# Make the Convex.jl module available
using Convex
using SCS
using SDPA

# Generate random problem data

I_mat = Matrix{Float64}(LinearAlgebra.I, N, N)
K = constructkernelmatrix(X, θ)
Kp = K.^2
#G = K'*K
G = K

M_mat = Kp'*Kp + μ*Kp
L_mat = cholesky(M_mat).U
N_mat = -2*Kp'*y
zeros_N = zeros(Float64,N,N)
zeros_1N = zeros(Float64,1,N)

# Create a (column vector) variable of size n x 1.
t = Variable()
α = Variable(N)

diag_α = diagm(0=>α)

# constraint.
# constraint = ([ I_mat L*α zeros_n;
#                 α'*L' t+2*dot(Kp*α,y) zeros_1n;
#                 zeros_n zeros_1n' G*diag_α+diag_α*G] ⪰ 0)
#
# problem = minimize(t+1-1, constraint)

# f is the square of a function.
#constraints = collect( (G*α)[i] >= 0 for i = 1:N )
constraints = collect( α[i] >= 0 for i = 1:N )
obj_expr = ( sumsquares(Kp*α-y) + μ*quadform(α, Kp) )
problem = minimize(obj_expr, constraints)

# f is the sum of the squares of functions, but each q_i isn't an inf sum.
# constraints = collect( (dot(G[i,:],α) >= 0) for i = 1:N )
# obj_expr = ( dot(Kp*α-y, Kp*α-y) + μ*dot(α, Kp*α) )
# problem = minimize(obj_expr, constraints[1])

# The problem is to minimize ||Ax - b||^2 subject to x >= 0
# This can be done by: minimize(objective, constraints)

# Solve the problem by calling solve!
#@time solve!(problem, SCSSolver(max_iters = max_iterations))
@time solve!(problem, SCS.Optimizer(max_iters = max_iterations))
#@time solve!(problem, SDPASolver(MaxIteration = max_iterations))

# Check the status of the problem
problem.status # :Optimal, :Infeasible, :Unbounded etc.
println("status: ", problem.status)

# Get the optimum value
problem.optval

α_star = vec(α.value)

θ_sos = SOSKernelType(θ)
η = RKHSProblemType(α_star, X, θ_sos, μ)

### experiment: modify α_star.
#clamp!(α_star, -0.1, Inf)
#α_star[7] = -0.1
### end experimentation.

# query.
Nq = 1000
xq_range = LinRange(-π-4, 2.34*π+4, Nq)
xq = collect( [xq_range[n]] for n = 1:Nq )

f_xq = f.(xq_range) # generating function.

yq = Vector{Float64}(undef, Nq)
query!(yq,xq,η)

# Visualize regression result.
PyPlot.figure(fig_num)
fig_num += 1

PyPlot.plot(X, y, ".", label = "observed")
PyPlot.plot(xq, yq, "x", label = "query - RKHS")

PyPlot.plot(xq, f_xq, label = "true")

title_string = "SOS-RKHS demo"
PyPlot.title(title_string)
PyPlot.legend()

println("All (numerically) strictly positive values in α_star: ", all(α_star.>0))
println("minimum(α_star) = ", minimum(α_star))
println("All (numerically) strictly positive values in yq: ", all(yq.>0))
println("minimum(yq) = ", minimum(yq))

U = G*diagm(0=>α_star)
s,Q_U = eigen(U)

# verify Sylvester's law: congruent sym real matrices have same eigenvalue signs.
min_val, l_select = findmin(yq)
k_evals_l = collect( α_star[i]*evalkernel(xq[l_select], X[i], θ) for i = 1:N )
yq_l = sum(k_evals_l)
println("yq[l_select]-yq_l = ", yq[l_select]-yq_l)


include("spharm.jl")

function test_main()

  Ng = 5
  lmax = 2

  Gvec = zeros(3,Ng)

  for ig = 1:Ng
    Gvec[:,ig] = [1.3, 1.1, 2.1]*(ig-1)
  end

  ylm = zeros( Float64, Ng, (lmax+1)^2 )

  ilm = 0
  for l = 0:lmax
    @printf("l = %d\n", l)
    # m = 0
    ilm = ilm + 1
    ylm[:,ilm] = spharm( l, 0, Gvec )
    println(ilm)
    println(ilm, " ", l, " ", 0)
    # m > 0 and m < 0
    for m = 1:l
      ilm = ilm + 1
      ylm[:,ilm] = spharm( l, m, Gvec )*(-1.0)^m
      println(ilm, " ", l, " ", m)
      #
      ilm = ilm + 1
      ylm[:,ilm] = spharm( l, -m, Gvec )*(-1.0)^m
      println(ilm)
      println(ilm, " ", l, " ", -m)
    end
  end

  for lm = 1:(lmax+1)^2
    @printf("\n")
    @printf("lm = %5d\n", lm)
    for ig = 1:Ng
      @printf("%5d %18.10f\n", ig, ylm[ig,lm])
    end
  end

end

test_main()


@testset "Non-commutative Monomial" begin
    Mod.@ncpolyvar x
    X_ = constantmonomial(typeof(x))
    @test nvariables(X_) == 0
    @test isempty(X_.vars)
    @test isempty(X_.z)
    X0 = constantmonomial(x)
    for Y in [x^0, X0]
        @test nvariables(Y) == 1
        @test variables(Y)[1] == x
        @test collect(exponents(Y)) == [0]
    end
    for X in [X_, X0]
        for Y in [monomial(x), x^1, x * X, X * x]
            @test nvariables(Y) == 1
            @test variables(Y)[1] == x
            @test collect(exponents(Y)) == [1]
        end
    end
    for X in [X_, X0]
        for Y in [x^2, X * x^2, x^2 * X]
            @test nvariables(Y) == 1
            @test variables(Y)[1] == x
            @test collect(exponents(Y)) == [2]
        end
    end
    @testset "Issue #148" begin
        Mod.@ncpolyvar x y z
        m = x * y^4 * x^2
        @test 3 == @inferred degree(m, x)
        @test 4 == @inferred degree(m, y)
        @test 0 == @inferred degree(m, z)
    end
    @testset "Issue #71 of DynamicPolynomials" begin
        Mod.@ncpolyvar x y
        @test x^0 * y == y * x^0
    end
end
@testset "Non-commutative MonomialVector" begin
    Mod.@ncpolyvar x y
    X = emptymonovec(typeof(x))
    @test iszero(nvariables(X))
    @test isempty(variables(X))
    X = emptymonovec(x)
    @test nvariables(X) == 1
    @test variables(X)[1] == x
    X = monovec([y, x])
    @test X[1] == x
    @test X[2] == y
    # `variables` may return `[x, y, x, y]` if the polynomial has, e.g., the monomial `x * y * x * y`.
    X = monomials([y, x, y, x, y], 0:2)
    Y = monomials([x, y], 2)
    for M in [X, Y]
        @test variables(X)[1] == x
        @test variables(X)[2] == y
        @test variables(X)[3] == x
        @test collect(exponents(X[1])) == [2, 0, 0]
        @test collect(exponents(X[2])) == [1, 1, 0]
        @test collect(exponents(X[3])) == [0, 2, 0]
        @test collect(exponents(X[4])) == [0, 1, 1]
    end
    @test collect(exponents(X[5])) == [1, 0, 0]
    @test collect(exponents(X[6])) == [0, 1, 0]
    @test collect(exponents(X[7])) == [0, 0, 0]
    X0 = [x^3, x^2*y, x*y^2, x*y*x, y^3, y^2*x, y*x^2, y*x*y]
    X1 = monomials([x, y], 3)
    X2 = monovec(X0)
    Z = [[3,0,0,0],[2,1,0,0],[1,2,0,0],[1,1,1,0],[0,3,0,0],[0,2,1,0],[0,1,2,0],[0,1,1,1]]
    @test X1 isa AbstractVector{<:AbstractMonomial}
    @test X2 isa AbstractVector{<:AbstractMonomial}
    @test length(X1) == length(X2) == length(Z)
    @test issorted(X1, rev=true)
    @test issorted(X2, rev=true)
    @test X1 == X0
    for i in 1:length(Z)
        @test variables(X1[i])[1] == variables(X2[i])[1] == x
        @test variables(X1[i])[2] == variables(X2[i])[2] == y
        @test variables(X1[i])[3] == variables(X2[i])[3] == x
        @test variables(X1[i])[4] == variables(X2[i])[4] == y
        @test collect(exponents(X1[i])) == collect(exponents(X2[i])) == Z[i]
    end
end


# This file is derived from parts of Julia's math tests from https://github.com/JuliaLang/julia/blob/master/test/math.jl

# Test math functions. We compare to BigFloat instead of hard-coding
# values, assuming that BigFloat has an independent and independently
# tested implementation.
for T in (Float32, Float64)
    x = T(1//3)
    y = T(1//2)
    yi = 4
    # Test random values
    @test xacos(x) ≈ acos(big(x))
    @test xacos_u1(x) ≈ acos(big(x))
    @test xacosh(1+x) ≈ acosh(big(1+x))
    @test xasin(x) ≈ asin(big(x))
    @test xasin_u1(x) ≈ asin(big(x))
    @test xasinh(x) ≈ asinh(big(x))
    @test xatan(x) ≈ atan(big(x))
    @test xatan_u1(x) ≈ atan(big(x))
    @test xatan2(x,y) ≈ atan2(big(x),big(y))
    @test xatan2_u1(x,y) ≈ atan2(big(x),big(y))
    @test atanh(x) ≈ atanh(big(x))
    @test xatanh(x) ≈ atanh(big(x))
    @test xcbrt(x) ≈ cbrt(big(x))
    @test xcos(x) ≈ cos(big(x))
    @test xcos_u1(x) ≈ cos(big(x))
    @test xcosh(x) ≈ cosh(big(x))
    @test xexp(x) ≈ exp(big(x))
    @test xexp10(x) ≈ exp10(big(x))
    @test xexp2(x) ≈ exp2(big(x))
    @test xexpm1(x) ≈ expm1(big(x))
    @test xlog(x) ≈ log(big(x))
    @test xlog_u1(x) ≈ log(big(x))
    @test xlog10(x) ≈ log10(big(x))
    @test xlog1p(x) ≈ log1p(big(x))
    @test xsin(x) ≈ sin(big(x))
    @test xsin_u1(x) ≈ sin(big(x))
    @test xsinh(x) ≈ sinh(big(x))
    @test xtan(x) ≈ tan(big(x))
    @test xtan_u1(x) ≈ tan(big(x))
    @test tanh(x) ≈ tanh(big(x))

    # Test special values
    @test isequal(xacos(T(1)), T(0))
    @test isequal(xacosh(T(1)), T(0))
    @test_approx_eq_eps xasin(T(1)) T(pi)/2 eps(T)
    @test_approx_eq_eps xasin_u1(T(1)) T(pi)/2 eps(T)
    @test_approx_eq_eps xatan(T(1)) T(pi)/4 eps(T)
    @test_approx_eq_eps xatan_u1(T(1)) T(pi)/4 eps(T)
    @test_approx_eq_eps xatan2(T(1),T(1)) T(pi)/4 eps(T)
    @test_approx_eq_eps xatan2_u1(T(1),T(1)) T(pi)/4 eps(T)
    @test isequal(xcbrt(T(0)), T(0))
    @test isequal(xcbrt(T(1)), T(1))
    @test isequal(xcbrt(T(1000000000)), T(1000))
    @test isequal(xcos(T(0)), T(1))
    @test isequal(xcos_u1(T(0)), T(1))
    @test_approx_eq_eps xcos(T(pi)/2) T(0) eps(T)
    @test_approx_eq_eps xcos_u1(T(pi)/2) T(0) eps(T)
    @test isequal(xcos(T(pi)), T(-1))
    @test isequal(xcos_u1(T(pi)), T(-1))
    @test_approx_eq_eps xexp(T(1)) T(e) 10*eps(T)
    @test isequal(xexp10(T(1)), T(10))
    @test isequal(xexp2(T(1)), T(2))
    @test isequal(xexpm1(T(0)), T(0))
    @test_approx_eq_eps xexpm1(T(1)) T(e)-1 10*eps(T)
    @test isequal(xlog(T(1)), T(0))
    @test isequal(xlog_u1(T(1)), T(0))
    @test_approx_eq_eps xlog(T(e)) T(1) eps(T)
    @test_approx_eq_eps xlog_u1(T(e)) T(1) eps(T)
    @test isequal(xlog10(T(1)), T(0))
    @test isequal(xlog10(T(10)), T(1))
    @test isequal(xlog1p(T(0)), T(0))
    @test_approx_eq_eps xlog1p(T(e)-1) T(1) eps(T)
    @test isequal(xsin(T(0)), T(0))
    @test isequal(xsin_u1(T(0)), T(0))
    @test isequal(xsin(T(pi)/2), T(1))
    @test isequal(xsin_u1(T(pi)/2), T(1))
    @test_approx_eq_eps xsin(T(pi)) T(0) eps(T)
    @test_approx_eq_eps xsin_u1(T(pi)) T(0) eps(T)
    @test isequal(xtan(T(0)), T(0))
    @test isequal(xtan_u1(T(0)), T(0))
    @test_approx_eq_eps xtan(T(pi)/4) T(1) eps(T)
    @test_approx_eq_eps xtan_u1(T(pi)/4) T(1) eps(T)

    # Test inverses
    @test xacos(xcos(x)) ≈ x
    @test xacos_u1(xcos_u1(x)) ≈ x
    @test xacosh(xcosh(x)) ≈ x
    @test xasin(xsin(x)) ≈ x
    @test xasin_u1(xsin_u1(x)) ≈ x
    @test xcbrt(x)^3 ≈ x
    @test xcbrt_u1(x)^3 ≈ x
    @test xcbrt_u1(x^3) ≈ x
    @test xasinh(xsinh(x)) ≈ x
    @test xatan(xtan(x)) ≈ x
    @test xatan_u1(xtan_u1(x)) ≈ x
    @test xatan2(x,y) ≈ xatan(x/y)
    @test xatan2_u1(x,y) ≈ xatan_u1(x/y)
    @test xatanh(xtanh(x)) ≈ x
    @test xcos(xacos(x)) ≈ x
    @test xcos_u1(xacos_u1(x)) ≈ x
    @test xcosh(xacosh(1+x)) ≈ 1+x
    @test xexp(log(x)) ≈ x
    @test xexp10(log10(x)) ≈ x
    @test xexp2(log2(x)) ≈ x
    @test xexpm1(log1p(x)) ≈ x
    @test xlog(xexp(x)) ≈ x
    @test xlog10(xexp10(x)) ≈ x
    @test xlog1p(xexpm1(x)) ≈ x
    @test xsin(xasin(x)) ≈ x
    @test xsin_u1(xasin_u1(x)) ≈ x
    @test xsinh(xasinh(x)) ≈ x
    @test xtan(xatan(x)) ≈ x
    @test xtan_u1(xatan_u1(x)) ≈ x
    @test xtanh(xatanh(x)) ≈ x

    # Test some properties
    @test xcosh(x) ≈ (xexp(x)+xexp(-x))/2
    @test xcosh(x)^2-xsinh(x)^2 ≈ 1
    @test xsin(x)^2+xcos(x)^2 ≈ 1
    @test xsin_u1(x)^2+xcos_u1(x)^2 ≈ 1
    @test xsinh(x) ≈ (xexp(x)-xexp(-x))/2
    @test xtan(x) ≈ xsin(x)/xcos(x)
    @test xtan_u1(x) ≈ xsin_u1(x)/xcos_u1(x)
    @test xtanh(x) ≈ xsinh(x)/xcosh(x)

    #Edge cases
    @test isinf(xlog(zero(T)))
    @test isnan(xlog(convert(T,NaN)))
    @test isinf(xlog1p(-one(T)))
    @test isnan(xlog1p(convert(T,NaN)))
	@test_throws DomainError xlog(-one(T))
	@test_throws DomainError xlog1p(convert(T,-2.0))

end


include( "../src/bootstrapPosteriorCorEstimation.jl" )

using DataFrames
using Distributions
using Random
using Statistics
using Test
using Optim
using MultivariateStats

function distortVector( vec::Vector{Float64}, pct::Float64, σ::Float64 )
  res          = deepcopy( vec );
  len          = size( vec, 1 );
  indices      = rand( 1:1:len, round( Int64, pct*len ) );
  gaussUniDist = Normal( 0.0, σ )
  for i in indices
    res[i] = vec[i] + rand( gaussUniDist );
  end
  return res;
end

println( "running bootstrap correlation algorithm unit tests." );
predMatTraining, predMatEval, yTraining, yEval, errMatTraining = makeupPredictions();
posterior = bootstrapPosteriorCorEstimation( predMatTraining, yTraining, 64, 10000 );
@test all( posterior .>= 0.0 );
@test all( posterior .<= 1.0 );
@test all( sum( posterior ) ≈ 1.0 );

nSamples    = 10000;
X           = rand( Normal( 0.0, 1.5 ), nSamples );
t_1         = Float64[ ( 0.3 * x^3 + 4.0 ) for x in X ];
t_2         = Float64[ ( 0.2 * x^2 + 0.1 * x ) for x in X ];
t_3         = Float64[ ( 0.5 * x^3 + 3.4 * x^2 + 1.5 * x + 0.95 ) for x in X ];
TMat        = hcat( t_1, t_2, t_3 );
predictions = Vector{ Matrix{Float64} }();
mat1 = hcat( distortVector( t_1, 0.05, 1.85 ),
             distortVector( t_2, 0.30, 0.75 ),
             distortVector( t_3, 0.25, 0.5 ) );
push!( predictions, mat1 );

mat2 = hcat( distortVector( t_1, 0.35, 1.25 ),
             distortVector( t_2, 0.10, 0.95 ),
             distortVector( t_3, 0.45, 0.35 ) );
push!( predictions, mat2 );

mat3 = hcat( distortVector( t_1, 1.35, 2.25 ),
             distortVector( t_2, 0.03, 0.07 ),
             distortVector( t_3, 0.25, 5.0 ) );
push!( predictions, mat3  );
nrRuns         = 5000;
samplingFactor = 0.4;
posterior      = bootstrapPosteriorCorEstimation( predictions, TMat, samplingFactor, nrRuns );
@test all( posterior .>= 0.0 );
@test all( posterior .<= 1.0 );
@test all( sum( posterior ) ≈ 1.0 );


"Generate the non-linear system for a lower principal representation for odd n."
function LowerPrincipalOdd(s::Dictionary, moments = compute_moments(s))
    n = length(s) - 1
    @assert isodd(n)
    l = (n+1) >> 1
    QuadRuleFreePoints(QuadRuleData(s, l, moments))
end

"Generate the non-linear system for an upper principal representation for odd n."
function UpperPrincipalOdd(s::Dictionary, moments = compute_moments(s))
    n = length(s) - 1
    @assert isodd(n)
    l = ((n+1) >> 1) + 1
    QuadRuleFixedPoints(QuadRuleData(s, l, moments), [1, l], [supportleft(s), supportright(s)])
end

"Generate the non-linear system for a lower principal representation for even n."
function LowerPrincipalEven(s::Dictionary, moments = compute_moments(s))
    n = length(s) - 1
    @assert iseven(n)
    l = (n >> 1) + 1
    QuadRuleFixedPoints(QuadRuleData(s, l, moments), [1], [supportleft(s)])
end

"Generate the non-linear system for an upper principal representation for even n."
function UpperPrincipalEven(s::Dictionary, moments = compute_moments(s))
    n = length(s) - 1
    @assert iseven(n)
    l = (n >> 1) + 1
    QuadRuleFixedPoints(QuadRuleData(s, l, moments), [l], [supportright(s)])
end

# The fixed root is in K1 = [a,t1].
function CanonicalRepresentationOdd_K1(s::Dictionary, xstar, moments = compute_moments(s))
    n = length(s) - 1
    @assert isodd(n)
    l = ((n+1) >> 1) + 1
    QuadRuleFixedPoints(QuadRuleData(s, l, moments), [1, l], [xstar, supportright(s)])
end

# The fixed root is in J1 = [t1,s2].
function CanonicalRepresentationOdd_J1(s::Dictionary, xstar, moments = compute_moments(s))
    n = length(s) - 1
    @assert isodd(n)
    l = ((n+1) >> 1) + 1
    QuadRuleFixedPoints(QuadRuleData(s, l, moments), [1, 2], [supportleft(s), xstar])
end

# The fixed root is in J1 = [a,s1].
function CanonicalRepresentationEven_J1(s::Dictionary, xstar, moments = compute_moments(s))
    n = length(s) - 1
    @assert iseven(n)
    l = (n >> 1) + 1
    QuadRuleFixedPoints(QuadRuleData(s, l, moments), [1], [xstar])
end

# The fixed root is in K1 = [s1,t2].
function CanonicalRepresentationEven_K1(s::Dictionary, xstar, moments = compute_moments(s))
    n = length(s) - 1
    @assert iseven(n)
    l = (n >> 1) + 2
    QuadRuleFixedPoints(QuadRuleData(s, l, moments), [1, 2, l], [supportleft(s), xstar, supportright(s)])
end


# xn = a11xn_1 + a12xn_2 + a13xn_3 (mod m1)
# Yn = a21Yn-i + a22Yn-2 + a23 (mod m2)
# zn = xn - Yn (mod m1)
# un = zn/m1
# Period : 2^191

import Dates

a11 = 0
a12 = 1403580
a13 = -810728
m1  = 2^32 - 209
a21 = 527612
a22 = 0
a23 = -1370589
m2  = 2^32 - 22853

function usage()
    println("Usage: julia " * PROGRAM_FILE * " <lower-limit> <upper-limit> <how-many>")
    exit(1)
end

macro assert(ex, msgs...)
    msg_body = isempty(msgs) ? ex : msgs[1]
    msg      = string(msg_body)
    return :($ex ? nothing : throw(AssertionError($msg)))
end

function validate_args()
    ll = nothing
    ul = nothing
    n  = nothing
    try
        ll = parse(Int64,ARGS[1])
        ul = parse(Int64,ARGS[2])
        n  = parse(Int64,ARGS[3])
        if n > ul-ll+1
            error("LimitsMismatch")
        end
        if ll > ul
            error("LimitsMismatch")
        end
    catch e
        println(e)
        exit(1)
    end
    return (ll,ul,n)
end

ticks() = (return convert(UInt64,Dates.datetime2unix(Dates.unix2datetime(time()))*1000);)

function generate_initial_xs()
    x1 = 2096367628361 + Int(ticks())
    x2 = 1638267496885 + Int(ticks())
    x3 = 6377396958484 + Int(ticks())
    y1 = 9542607296802 + Int(ticks())
    y2 = 7716177293560 + Int(ticks())
    return (x1,x2,x3,y1,y2)
end

function generate_next(xn_1,xn_2,xn_3,Yn_1,Yn_2,m1,m2)
    xn = mod(a11 * xn_1 + a12 * xn_2 + a13 * xn_3, m1)
    Yn = mod(a21 * Yn_1 + a22 * Yn_2 + a23, m2)
    zn = mod(xn - Yn, m1)
    un = zn/m1
    return (xn,Yn,un)
end

function squeeze_range(ll,ul,r,float_mode)
    val = ll + (ul - ll + 1) * r
    if float_mode
        return val
    end
    return trunc(Int,val)
end

function main()
    try
        @assert(length(ARGS) == 3 , "")
    catch
        usage()
    end
    ll, ul, n          = validate_args()
    x0, x1, x2, y1, y2 = generate_initial_xs()
    i = 0
    d = Dict()
    while i<n
        xn,Yn,r = generate_next(x0,x1,x2,y1,y2,m1,m2)
        ran_num = squeeze_range(ll,ul,r,false)
        x0 = x1
        x1 = x2
        x2 = xn
        y1 = y2
        y2 = Yn
        if !(string(ran_num) in keys(d))
            println(ran_num)
            d[string(ran_num)] = nothing
            i=i+1
        end
    end
end

main()


export generate_tensor_form,
       multivariate_tensor

using Base.Cartesian

"""
    generate_tensor_form(B::ImplicitForm{N}; algorithm::String="loop") where {N}

Compute the Bernstein expansion in tensorial form given its implicit form.

### Input

- `B`         -- Bernstein expansion in implicit form
- `algorithm` -- (optional, default: `"loop"`) the implementation used for this
                 calculation, valid options are:

    * `"loop"`
    * `"kron"`
"""
function generate_tensor_form(B::ImplicitForm{N}; algorithm::String="loop") where {N}
    # unpack fields
    v = VectorOfArray(B.array) # FIXME : move to type?
    generate_tensor_form(v, algorithm=algorithm)
end

function generate_tensor_form(v::VectorOfArray{N, D, T}; algorithm::String="loop") where {N, D, T}
    if algorithm == "loop"
        # preallocate output array
        A = Array{N, length(v)}(Tuple(length(vi) for vi in v))
        _loop_tensor!(A, v)
    elseif algorithm == "kron"
        # preallocate output array
        A = Array{N, 1}() # FIXME: array size matters?
        _kron_tensor!(A, v)
    else
        error("the tensorial expansion algorithm $algorithm is unknown")
    end
    return A
end

"""
```
    _kron_tensor!(B::ImplicitForm{N}) where {N}
```

Compute the Bernstein expansion in tensorial form given its implicit form, using
the Kronecker power.

### Input

- `B` -- Bernstein expansion in implicit form

### Output

A one-dimensional array of the same numeric type as the given implicit form.

### Algorithm

This implementation uses Julia's Kronecker product `kron` function in a loop.
"""
function _kron_tensor!(A::AbstractArray{T, N}, v::VectorOfArray) where {T, N}
    A = v[1] # FIXME: this is not using the given A
    @inbounds for i in 2:length(v)
        A = kron(A, v[i])
    end
end

"""
```
    _loop_tensor!(A::AbstractArray{T, N}, v::VectorOfArray) where {T, N}
```

Compute the Bernstein expansion in tensorial form given its implicit form, using
a nested loop.

### Input

- `B` -- Bernstein expansion in implicit form

### Output

A multi-dimensional array containing the coefficients.

### Algorithm

This implementation uses Julia's `Base.Cartesian` to generate a set of nested
loops to compute the element ``A[i_1, …, i_n]``.
"""
@generated function _loop_tensor!(A::AbstractArray{T, N}, v::VectorOfArray) where {T, N}
    quote
    cache = Vector{Float64}($N-2)
    @inbounds for $(Symbol(:i_, N)) in eachindex(v[$N])
                  # FIXME: split me in several lines!
                  @nloops $(N-1) i i -> eachindex(v[i]) d -> d == 1 ? nothing : d == $N-1 ? cache[d-1] = v[i_{d}, d] * v[i_{d+1}, d+1] : cache[d-1] = cache[d] * v[i_{d}, d] begin (@nref $N A i) = v[i_1, 1] * cache[1] end
              end
    end
end

"""
    multivariate_tensor(k::Vector{Int64}, l::Vector{Int64},
                          low::Vector{T}, high::Vector{T};
                          algorithm::String="loop")

Compute the Bernstein coefficients of a multivariate monomial in the tensor form.

### Input

- `k`    -- vector of degrees for each monomial
- `l`    -- vector of degrees of the Bernstein polynomial for each monomial
- `low`  -- the lower bounds of the interval where the Bernstein coefficients are computed
- `high` -- the upper bounds of the interval the Bernstein coefficients are computed
- `algorithm` -- (optional, default: `"loop"`) the implementation used for this
                 calculation, valid options are:

    * `"loop"`
    * `"kron"`
"""
function multivariate_tensor(k::Vector{Int64}, l::Vector{Int64},
                             low::Vector{T}, high::Vector{T};
                             algorithm::String="loop") where {T}

    n = length(low)
    berncoeffs = VectorOfArray([univariate(k[i], l[i], low[i], high[i]) for i in 1:n])
    generate_tensor_form(berncoeffs, algorithm=algorithm)
end


module Quadratures

# Dependencies of this module

using QuadGK: gauss
using Gridap
using Gridap.Helpers
using StaticArrays

# Functionality provided by this module

export Quadrature
export TensorProductQuadrature
export coordinates
export weights

# Abstract types and interfaces

"""
Abstract type representing a quadrature rule on a Polytope in a space of D dimensions
"""
abstract type Quadrature{D} end

coordinates(::Quadrature{D} where D)::Array{Point{D,Float64},1} = @abstractmethod

weights(::Quadrature)::Array{Float64,1} = @abstractmethod

# Concrete structs

"""
Tensor product quadrature rule (nodes and weights) on a hyper cube [-1,1]^D
"""
struct TensorProductQuadrature{D} <: Quadrature{D}
  coords::Array{Point{D,Float64},1}
  weights::Array{Float64,1}
end

function TensorProductQuadrature(;orders::NTuple{D,Int}) where D
    @assert D == length(orders)
    npoints = [ ceil(Int,(orders[i]+1.0)/2.0) for i in 1:D ]
    quads = [ gauss( eltype(Point{D,Float64}), npoints[i] ) for i in 1:D ]
    for i in 1:D
      quads[i][1] .+= 1; quads[i][1] .*= 1.0/2.0
      quads[i][2] .*= 1.0/2.0
    end
    (coords, weights) = _tensor_product(quads,npoints,Val(D))
    TensorProductQuadrature{D}(coords,weights)
end

coordinates(self::TensorProductQuadrature) = self.coords

weights(self::TensorProductQuadrature) = self.weights

# Helpers

function _tensor_product(quads,npoints,::Val{D}) where D
  @assert length(quads) == D
  @assert length(npoints) == D
  cis = CartesianIndices(tuple(npoints...))
  n = prod(npoints)
  coords = Vector{Point{D,Float64}}(undef,n)
  weights = Vector{Float64}(undef,n)
  _tensor_product!(quads,coords,weights,cis,D)
  (coords, weights)
end

function _tensor_product!(quads,coords,weights,cis,D)
  p = zero(MVector{D,Float64})
  lis = LinearIndices(cis)
  for ci in cis
    p[:] = 0.0
    w = 1.0
    for d in 1:D
      xi = quads[d][1][ci[d]]
      wi = quads[d][2][ci[d]]
      p[d] = xi
      w *= wi
    end
    li = lis[ci]
    coords[li] = p
    weights[li] = w
  end
end

end # module Quadratures