text:
module HangingLamp

using ..Ahorn, Maple

function hangingLampFinalizer(entity::Maple.HangingLamp)
    nx, ny = Int.(entity.data["nodes"][1])
    y = Int(entity.data["y"])

    entity.data["height"] = max(abs(ny - y), 8)

    delete!(entity.data, "nodes")
end

const placements = Ahorn.PlacementDict(
    "Hanging Lamp" => Ahorn.EntityPlacement(
        Maple.HangingLamp,
        "line",
        Dict{String, Any}(),
        hangingLampFinalizer
    )
)

Ahorn.minimumSize(entity::Maple.HangingLamp) = 0, 8
Ahorn.resizable(entity::Maple.HangingLamp) = false, true

function Ahorn.selection(entity::Maple.HangingLamp)
    x, y = Ahorn.position(entity)
    height = get(entity.data, "height", 16)

    return Ahorn.Rectangle(x + 1, y, 7, height)
end

function Ahorn.render(ctx::Ahorn.Cairo.CairoContext, entity::Maple.HangingLamp, room::Maple.Room)
    Ahorn.drawImage(ctx, "objects/hanginglamp", 1, 0, 0, 0, 7, 3)

    height = get(entity.data, "height", 16)
    for i in 0:4:height - 11
        Ahorn.drawImage(ctx, "objects/hanginglamp", 1, i + 3, 0, 8, 7, 4)
    end

    Ahorn.drawImage(ctx, "objects/hanginglamp", 1, height - 8, 0, 16, 7, 8)
end

end


text:
using CUDA.CUSPARSE

using LinearAlgebra
using SparseArrays
using SparseArrays: nonzeroinds

@test CUSPARSE.version() isa VersionNumber

m = 25
n = 35
k = 10
blockdim = 5

@testset "array" begin
    x = sprand(m,0.2)
    d_x = CuSparseVector(x)
    @test length(d_x) == m
    @test size(d_x)   == (m,)
    @test size(d_x,1) == m
    @test size(d_x,2) == 1
    @test ndims(d_x)  == 1
    CUDA.@allowscalar begin
        @test Array(d_x[:])        == x[:]
        @test d_x[firstindex(d_x)] == x[firstindex(x)]
        @test d_x[div(end, 2)]     == x[div(end, 2)]
        @test d_x[end]             == x[end]
        @test Array(d_x[firstindex(d_x):end]) == x[firstindex(x):end]
    end
    @test_throws BoundsError d_x[firstindex(d_x) - 1]
    @test_throws BoundsError d_x[end + 1]
    @test nnz(d_x)    == nnz(x)
    @test Array(nonzeros(d_x)) == nonzeros(x)
    @test Array(nonzeroinds(d_x)) == nonzeroinds(x)
    @test nnz(d_x)    == length(nonzeros(d_x))
    x = sprand(m,n,0.2)
    d_x = CuSparseMatrixCSC(x)
    @test length(d_x) == m*n
    @test size(d_x)   == (m,n)
    @test size(d_x,1) == m
    @test size(d_x,2) == n
    @test size(d_x,3) == 1
    @test ndims(d_x)  == 2
    CUDA.@allowscalar begin
        @test Array(d_x[:])        == x[:]
        @test d_x[firstindex(d_x)] == x[firstindex(x)]
        @test d_x[div(end, 2)]     == x[div(end, 2)]
        @test d_x[end]             == x[end]
        @test d_x[firstindex(d_x), firstindex(d_x)] == x[firstindex(x), firstindex(x)]
        @test d_x[div(end, 2), div(end, 2)]         == x[div(end, 2), div(end, 2)]
        @test d_x[end, end]        == x[end, end]
        @test Array(d_x[firstindex(d_x):end, firstindex(d_x):end]) == x[:, :]
        for i in 1:size(x, 2)
            @test Array(d_x[:, i]) == x[:, i]
        end
    end
    @test_throws BoundsError d_x[firstindex(d_x) - 1]
    @test_throws BoundsError d_x[end + 1]
    @test_throws BoundsError d_x[firstindex(d_x) - 1, firstindex(d_x) - 1]
    @test_throws BoundsError d_x[end + 1, end + 1]
    @test_throws BoundsError d_x[firstindex(d_x) - 1:end + 1, :]
    @test_throws BoundsError d_x[firstindex(d_x) - 1, :]
    @test_throws BoundsError d_x[end + 1, :]
    @test_throws BoundsError d_x[:, firstindex(d_x) - 1:end + 1]
    @test_throws BoundsError d_x[:, firstindex(d_x) - 1]
    @test_throws BoundsError d_x[:, end + 1]
    @test nnz(d_x)    == nnz(x)
    @test Array(nonzeros(d_x)) == nonzeros(x)
    @test nnz(d_x)    == length(nonzeros(d_x))
    @test !issymmetric(d_x)
    @test !ishermitian(d_x)
    @test_throws ArgumentError size(d_x,0)
    @test_throws ArgumentError CUSPARSE.CuSparseVector(x)
    y = sprand(k,n,0.2)
    d_y = CuSparseMatrixCSC(y)
    @test_throws ArgumentError copyto!(d_y,d_x)
    d_y = CuSparseMatrixCSR(d_y)
    d_x = CuSparseMatrixCSR(d_x)
    @test_throws ArgumentError copyto!(d_y,d_x)
    CUDA.@allowscalar begin
        for i in 1:size(y, 1)
          @test d_y[i, :] ≈ y[i, :]
        end
    end
    d_y = CuSparseMatrixBSR(d_y, blockdim)
    d_x = CuSparseMatrixBSR(d_x, blockdim)
    @test_throws ArgumentError copyto!(d_y,d_x)
    x = sprand(m,0.2)
    d_x = CuSparseVector(x)
    @test size(d_x, 1) == m
    @test size(d_x, 2) == 1
    @test_throws ArgumentError size(d_x, 0)
    y = sprand(n,0.2)
    d_y = CuSparseVector(y)
    @test_throws ArgumentError copyto!(d_y,d_x)
    x = sprand(m,m,0.2)
    d_x = Symmetric(CuSparseMatrixCSC(x + transpose(x)))
    @test issymmetric(d_x)
    x = sprand(ComplexF64, m, m, 0.2)
    d_x = Hermitian(CuSparseMatrixCSC(x + x'))
    @test ishermitian(d_x)
    x = sprand(m,m,0.2)
    d_x = UpperTriangular(CuSparseMatrixCSC(x))
    @test istriu(d_x)
    @test !istril(d_x)
    d_x = LowerTriangular(CuSparseMatrixCSC(x))
    @test !istriu(d_x)
    @test istril(d_x)
end

@testset "construction" begin
    @testset for elty in [Int32, Int64, Float32, Float64, ComplexF32, ComplexF64]
        @testset "vector" begin
            x = sprand(elty,m, 0.2)
            d_x = CuSparseVector(x)
            @test collect(d_x) == collect(x)
            @test similar(d_x) isa CuSparseVector{elty}
            @test similar(d_x, Float32) isa CuSparseVector{Float32}
        end

        @testset "CSC" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSC(x)
            @test collect(d_x) == collect(x)
            @test similar(d_x) isa CuSparseMatrixCSC{elty}
            @test similar(d_x, Float32) isa CuSparseMatrixCSC{Float32}
        end

        @testset "CSR" begin
            x = sprand(elty,m,n, 0.2)
            d_x  = CuSparseMatrixCSR(x)
            @test collect(d_x) == collect(x)
            @test similar(d_x) isa CuSparseMatrixCSR{elty}
            @test similar(d_x, Float32) isa CuSparseMatrixCSR{Float32}
        end

        @testset "BSR" begin
            x = sprand(elty,m,n, 0.2)
            d_x  = CuSparseMatrixBSR(x, blockdim)
            @test collect(d_x) == collect(x)
            @test similar(d_x) isa CuSparseMatrixBSR{elty}
            @test similar(d_x, Float32) isa CuSparseMatrixBSR{Float32}
        end

        @testset "BSR" begin
            x = sprand(elty,m,n, 0.2)
            d_x  = CuSparseMatrixCOO(x)
            @test collect(d_x) == collect(x)
            @test similar(d_x) isa CuSparseMatrixCOO{elty}
            @test similar(d_x, Float32) isa CuSparseMatrixCOO{Float32}
        end
    end
end

if capability(device()) >= v"5.3"
@testset "construction f16" begin
    @testset for elty in [Float16, ComplexF16]
        @testset "CSC" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSC(x)
            @test collect(d_x) == collect(x)
        end

        @testset "CSR" begin
            x = sprand(elty,m,n, 0.2)
            d_x  = CuSparseMatrixCSR(x)
            @test collect(d_x) == collect(x)
        end
    end
end
end

@testset "conversion" begin
    @testset for elty in [Float32, Float64, ComplexF32, ComplexF64]
        @testset "CSC(::CSR)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSR(x)
            d_x = CuSparseMatrixCSC(d_x)
            @test collect(d_x) == collect(x)
        end

        @testset "CSR(::CSC)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSC(x)
            d_x = CuSparseMatrixCSR(d_x)
            @test collect(d_x) == collect(x)
        end

        @testset "BSR(::CSR)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSR(x)
            d_x = CuSparseMatrixBSR(d_x, blockdim)
            @test collect(d_x) == collect(x)
        end
        # CSR(::BSR) already covered by the non-direct collect

        @testset "BSR(::Dense)" begin
            x = rand(elty,m,n)
            d_x = CuArray(x)
            d_x = CuSparseMatrixBSR(d_x)
            @test collect(d_x) ≈ x
        end

        @testset "COO(::CSR)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSR(x)
            d_x = CuSparseMatrixCOO(d_x)
            @test collect(d_x) == collect(x)
        end
        # CSR(::COO) already covered by the non-direct collect

        @testset "Dense(::CSR)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSR(x)
            h_x = collect(d_x)
            @test h_x ≈ Array(x)
        end

        @testset "Dense(::CSC)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSC(x)
            h_x = collect(d_x)
            @test h_x ≈ Array(x)
        end

        @testset "CSC(::Dense)" begin
            x = rand(elty,m,n)
            d_x = CuArray(x)
            d_x = CuSparseMatrixCSC(d_x)
            h_x = collect(d_x)
            @test h_x ≈ sparse(x)

            d_x_dense = CuMatrix(d_x)
            @test h_x == collect(d_x_dense)
            h_x_dense = Array(d_x)
            @test h_x == h_x_dense
        end

        @testset "CSR(::Dense)" begin
            x = rand(elty,m,n)
            d_x = CuArray(x)
            d_x = CuSparseMatrixCSR(d_x)
            h_x = collect(d_x)
            @test h_x ≈ sparse(x)

            d_x_dense = CuMatrix(d_x)
            @test h_x == collect(d_x_dense)
            h_x_dense = Array(d_x)
            @test h_x == h_x_dense
        end
    end
end

if capability(device()) >= v"5.3"
@testset "conversion f16" begin
    @testset for elty in [Float16, ComplexF16]
        @testset "CSC(::CSR)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSR(x)
            d_x = CuSparseMatrixCSC(d_x)
            @test collect(d_x) == collect(x)
        end

        @testset "CSR(::CSC)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSC(x)
            d_x = CuSparseMatrixCSR(d_x)
            @test collect(d_x) == collect(x)
        end

        @testset "Dense(::CSR)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSR(x)
            h_x = collect(d_x)
            @test h_x ≈ Array(x)
        end

        @testset "Dense(::CSC)" begin
            x = sprand(elty,m,n, 0.2)
            d_x = CuSparseMatrixCSC(x)
            h_x = collect(d_x)
            @test h_x ≈ Array(x)
        end

        @testset "CSC(::Dense)" begin
            x = rand(elty,m,n)
            d_x = CuArray(x)
            d_x = CuSparseMatrixCSC(d_x)
            h_x = collect(d_x)
            @test h_x ≈ sparse(x)
        end

        @testset "CSR(::Dense)" begin
            x = rand(elty,m,n)
            d_x = CuArray(x)
            d_x = CuSparseMatrixCSR(d_x)
            h_x = collect(d_x)
            @test h_x ≈ sparse(x)
        end
    end
end
end

@testset "bsric02" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        A = rand(elty, m, m)
        A += adjoint(A)
        A += m * Diagonal{elty}(I, m)

        @testset "bsric02!" begin
            d_A = CuSparseMatrixCSR(sparse(tril(A)))
            d_A = CuSparseMatrixBSR(d_A, blockdim)
            d_A = CUSPARSE.ic02!(d_A,'O')
            h_A = SparseMatrixCSC(CuSparseMatrixCSR(d_A))
            Ac = sparse(Array(cholesky(Hermitian(A))))
            h_A = transpose(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
            d_A = CuSparseMatrixCSR(sparse(tril(rand(elty,m,n))))
            d_A = CuSparseMatrixBSR(d_A, blockdim)
            @test_throws DimensionMismatch CUSPARSE.ic02!(d_A,'O')
        end

        @testset "bsric02" begin
            d_A = CuSparseMatrixCSR(sparse(tril(A)))
            d_A = CuSparseMatrixBSR(d_A, blockdim)
            d_B = CUSPARSE.ic02(d_A,'O')
            h_A = SparseMatrixCSC(CuSparseMatrixCSR(d_B))
            Ac = sparse(Array(cholesky(Hermitian(A))))
            h_A = transpose(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
        end
    end
end

@testset "bsrilu02" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        A = rand(elty,m,m)
        A += transpose(A)
        A += m * Diagonal{elty}(I, m)
        @testset "bsrilu02!" begin
            d_A = CuSparseMatrixCSR(sparse(A))
            d_A = CuSparseMatrixBSR(d_A, blockdim)
            d_A = CUSPARSE.ilu02!(d_A,'O')
            h_A = SparseMatrixCSC(CuSparseMatrixCSR(d_A))
            pivot = VERSION >= v"1.7-" ? NoPivot() : Val(false)
            Alu = lu(Array(A), pivot)
            Ac = sparse(Alu.L*Alu.U)
            h_A = adjoint(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
            d_A = CuSparseMatrixCSR(sparse(rand(elty,m,n)))
            d_A = CuSparseMatrixBSR(d_A, blockdim)
            @test_throws DimensionMismatch CUSPARSE.ilu02!(d_A,'O')
        end

        @testset "bsrilu02" begin
            d_A = CuSparseMatrixCSR(sparse(A))
            d_A = CuSparseMatrixBSR(d_A, blockdim)
            d_B = CUSPARSE.ilu02(d_A,'O')
            h_A = SparseMatrixCSC(CuSparseMatrixCSR(d_B))
            pivot = VERSION >= v"1.7-" ? NoPivot() : Val(false)
            Alu = lu(Array(A),pivot)
            Ac = sparse(Alu.L*Alu.U)
            h_A = adjoint(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
        end
    end
end

@testset "bsrsv2" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        @testset "bsrsv2!" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                X = rand(elty,m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_A = CuSparseMatrixCSR(sparse(A))
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                d_X = CUSPARSE.sv2!('N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_X)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                d_X = CUDA.rand(elty,n)
                @test_throws DimensionMismatch CUSPARSE.sv2!('N','U',diag,alpha,d_A,d_X,'O')
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSR(A)
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                @test_throws DimensionMismatch CUSPARSE.sv2!('N','U',diag,alpha,d_A,d_X,'O')
            end
        end

        @testset "bsrsv2" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                Al = (unit_diag ? tril(A, -1) + I : tril(A))
                X = rand(elty,m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_Al = CuSparseMatrixCSR(sparse(Al))
                d_Al = CuSparseMatrixBSR(d_Al, blockdim)
                d_Y = CUSPARSE.sv2('N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = Al\(alpha * X)
                @test Y ≈ h_Y
                d_A = CuSparseMatrixCSR(sparse(A))
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                d_Y = CUSPARSE.sv2('N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                UA = (unit_diag ? UnitUpperTriangular(d_A) : UpperTriangular(d_A))
                d_Y = UA\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ A\X
                #=d_Y = UpperTriangular(d_A)'\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ A'\X=#
                d_Y = transpose(UA)\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ transpose(A)\X
                LA = (unit_diag ? UnitLowerTriangular(d_A) : LowerTriangular(d_A))
                d_Y = LA\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ Al\X
                #=d_Y = LowerTriangular(d_A)'\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ A'\X=#
                d_Y = transpose(LA)\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ transpose(Al)\X
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSR(A)
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                @test_throws DimensionMismatch CUSPARSE.sv2('N','U',diag,alpha,d_A,d_X,'O')
            end
        end
    end
end

@testset "bsrsm2" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        @testset "bsrsm2!" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                X = rand(elty,m,m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_A = CuSparseMatrixCSR(sparse(A))
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                d_X = CUSPARSE.sm2!('N','N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_X)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                d_X = CUDA.rand(elty,n,n)
                @test_throws DimensionMismatch CUSPARSE.sm2!('N','N','U',diag,alpha,d_A,d_X,'O')
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSR(A)
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                @test_throws DimensionMismatch CUSPARSE.sm2!('N','N','U',diag,alpha,d_A,d_X,'O')
            end
        end

        @testset "bsrsm2" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                Al = (unit_diag ? tril(A, -1) + I : tril(A))
                X = rand(elty,m, m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_Al = CuSparseMatrixCSR(sparse(Al))
                d_Al = CuSparseMatrixBSR(d_Al, blockdim)
                d_Y = CUSPARSE.sm2('N','N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = Al\(alpha * X)
                @test Y ≈ h_Y
                d_A = CuSparseMatrixCSR(sparse(A))
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                d_Y = CUSPARSE.sm2('N','N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                UA = (unit_diag ? UnitUpperTriangular(d_A) : UpperTriangular(d_A))
                d_Y = UA\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ A\X
                #=d_Y = UpperTriangular(d_A)'\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ A'\X=#
                d_Y = transpose(UA)\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ transpose(A)\X
                LA = (unit_diag ? UnitLowerTriangular(d_A) : LowerTriangular(d_A))
                d_Y = LA\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ Al\X
                #=d_Y = LowerTriangular(d_A)'\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ A'\X=#
                d_Y = transpose(LA)\d_X
                h_Y = collect(d_Y)
                @test h_Y ≈ transpose(Al)\X
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSR(A)
                d_A = CuSparseMatrixBSR(d_A, blockdim)
                @test_throws DimensionMismatch CUSPARSE.sm2('N','N','U',diag,alpha,d_A,d_X,'O')
            end
        end
    end
end

@testset "ilu02" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        @testset "csr" begin
            A = rand(elty,m,m)
            A += transpose(A)
            A += m * Diagonal{elty}(I, m)
            d_A = CuSparseMatrixCSR(sparse(A))
            d_B = CUSPARSE.ilu02(d_A,'O')
            h_A = SparseMatrixCSC(d_B)
            pivot = VERSION >= v"1.7-" ? NoPivot() : Val(false)
            Alu = lu(Array(A),pivot)
            Ac = sparse(Alu.L*Alu.U)
            h_A = adjoint(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
        end
        @testset "csc" begin
            A = rand(elty,m,m)
            A += transpose(A)
            A += m * Diagonal{elty}(I, m)
            d_A = CuSparseMatrixCSC(sparse(A))
            d_B = CUSPARSE.ilu02(d_A,'O')
            h_A = SparseMatrixCSC(d_B)
            pivot = VERSION >= v"1.7-" ? NoPivot() : Val(false)
            Alu = lu(Array(A),pivot)
            Ac = sparse(Alu.L*Alu.U)
            h_A = adjoint(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
        end
    end
end

@testset "ic2" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        @testset "csr" begin
            A   = rand(elty, m, m)
            A  += adjoint(A)
            A  += m * Diagonal{elty}(I, m)
            d_A = CuSparseMatrixCSR(sparse(tril(A)))
            d_B = CUSPARSE.ic02(d_A, 'O')
            h_A = SparseMatrixCSC(d_B)
            Ac  = sparse(Array(cholesky(Hermitian(A))))
            h_A = transpose(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
            A   = rand(elty,m,n)
            d_A = CuSparseMatrixCSR(sparse(tril(A)))
            @test_throws DimensionMismatch CUSPARSE.ic02(d_A, 'O')
        end
        @testset "csc" begin
            A   = rand(elty, m, m)
            A  += adjoint(A)
            A  += m * Diagonal{elty}(I, m)
            d_A = CuSparseMatrixCSC(sparse(tril(A)))
            d_B = CUSPARSE.ic02(d_A, 'O')
            h_A = SparseMatrixCSC(d_B)
            Ac  = sparse(Array(cholesky(Hermitian(A))))
            h_A = transpose(h_A) * h_A
            @test rowvals(h_A) ≈ rowvals(Ac)
            @test reduce(&, isfinite.(nonzeros(h_A)))
            A   = rand(elty,m,n)
            d_A = CuSparseMatrixCSC(sparse(tril(A)))
            @test_throws DimensionMismatch CUSPARSE.ic02(d_A, 'O')
        end
    end
end

@testset "cssv" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        @testset "csrsv2" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                Al = (unit_diag ? tril(A, -1) + I : tril(A))
                X = rand(elty,m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_Al = CuSparseMatrixCSR(sparse(Al))
                d_Y = CUSPARSE.sv2('N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = Al\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sv2('T','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(Al)\(alpha * X)
                @test Y ≈ h_Y
                d_A = CuSparseMatrixCSR(sparse(A))
                d_Y = CUSPARSE.sv2('N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sv2('T','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(A)\(alpha * X)
                UA = (unit_diag ? UnitUpperTriangular(d_A) : UpperTriangular(d_A))
                d_y = UA\d_X
                h_y = collect(d_y)
                y = A\X
                @test y ≈ h_y
                d_y = transpose(UA)\d_X
                h_y = collect(d_y)
                y = transpose(A)\X
                @test y ≈ h_y
                #=d_y = UpperTriangular(d_A)'\d_X
                h_y = collect(d_y)
                y = A'\X
                @test y ≈ h_y=#
                LA = (unit_diag ? UnitLowerTriangular(d_A) : LowerTriangular(d_A))
                d_y = LA\d_X
                h_y = collect(d_y)
                y = Al\X
                @test y ≈ h_y
                d_y = transpose(LA)\d_X
                h_y = collect(d_y)
                y = transpose(Al)\X
                @test y ≈ h_y
                #=d_y = LowerTriangular(d_A)'\d_X
                h_y = collect(d_y)
                y = A'\X
                @test y ≈ h_y=#
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSR(A)
                @test_throws DimensionMismatch CUSPARSE.sv2('N','U',diag,alpha,d_A,d_X,'O')
            end
        end

        @testset "cscsv2" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                Al = (unit_diag ? tril(A, -1) + I : tril(A))
                X = rand(elty,m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_Al = CuSparseMatrixCSC(sparse(Al))
                d_Y = CUSPARSE.sv2('N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = Al\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sv2('T','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(Al)\(alpha * X)
                @test Y ≈ h_Y
                d_A = CuSparseMatrixCSC(sparse(A))
                d_Y = CUSPARSE.sv2('N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sv2('T','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(A)\(alpha * X)
                @test Y ≈ h_Y
                UA = (unit_diag ? UnitUpperTriangular(d_A) : UpperTriangular(d_A))
                d_y = UA\d_X
                h_y = collect(d_y)
                y = A\X
                @test y ≈ h_y
                d_y = transpose(UA)\d_X
                h_y = collect(d_y)
                y = transpose(A)\X
                @test y ≈ h_y
                LA = (unit_diag ? UnitLowerTriangular(d_A) : LowerTriangular(d_A))
                d_y = LA\d_X
                h_y = collect(d_y)
                y = Al\X
                @test y ≈ h_y
                d_y = transpose(LA)\d_X
                h_y = collect(d_y)
                y = transpose(Al)\X
                @test y ≈ h_y
                #=d_y = UpperTriangular(d_A)'\d_X
                h_y = collect(d_y)
                y = A'\X
                @test y ≈ h_y=#
                # shouldn't work for now bc sv2 has no way to do conj...
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSC(A)
                @test_throws DimensionMismatch CUSPARSE.sv2('N','U',diag,alpha,d_A,d_X,'O')
            end
        end
    end
end

@testset "cssm" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        @testset "csrsm2" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                Al = (unit_diag ? tril(A, -1) + I : tril(A))
                X = rand(elty,m,m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_Al = CuSparseMatrixCSR(sparse(Al))
                d_Y = CUSPARSE.sm2('N','N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = Al\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sm2('T','N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(Al)\(alpha * X)
                @test Y ≈ h_Y
                d_A = CuSparseMatrixCSR(sparse(A))
                d_Y = CUSPARSE.sm2('N','N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sm2('T','N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(A)\(alpha * X)
                @test Y ≈ h_Y
                UA = (unit_diag ? UnitUpperTriangular(d_A) : UpperTriangular(d_A))
                d_y = UA\d_X
                h_y = collect(d_y)
                y = A\X
                @test y ≈ h_y
                d_y = transpose(UA)\d_X
                h_y = collect(d_y)
                y = transpose(A)\X
                @test y ≈ h_y
                #=d_y = UpperTriangular(d_A)'\d_X
                h_y = collect(d_y)
                y = A'\X
                @test y ≈ h_y=#
                LA = (unit_diag ? UnitLowerTriangular(d_A) : LowerTriangular(d_A))
                d_y = LA\d_X
                h_y = collect(d_y)
                y = Al\X
                @test y ≈ h_y
                d_y = transpose(LA)\d_X
                h_y = collect(d_y)
                y = transpose(Al)\X
                @test y ≈ h_y
                #=d_y = LowerTriangular(d_A)'\d_X
                h_y = collect(d_y)
                y = A'\X
                @test y ≈ h_y=#
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSR(A)
                @test_throws DimensionMismatch CUSPARSE.sm2('N','N','U',diag,alpha,d_A,d_X,'O')
            end
        end

        @testset "cscsm2" begin
            for unit_diag ∈ (false, true)
                diag = unit_diag ? 'U' : 'N'
                A = rand(elty,m,m)
                A = (unit_diag ? triu(A, 1) + I : triu(A))
                Al = (unit_diag ? tril(A, -1) + I : tril(A))
                X = rand(elty,m,m)
                alpha = rand(elty)
                d_X = CuArray(X)
                d_Al = CuSparseMatrixCSC(sparse(Al))
                d_Y = CUSPARSE.sm2('N','N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = Al\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sm2('T','N','L',diag,alpha,d_Al,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(Al)\(alpha * X)
                @test Y ≈ h_Y
                d_A = CuSparseMatrixCSC(sparse(A))
                d_Y = CUSPARSE.sm2('N','N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = A\(alpha * X)
                @test Y ≈ h_Y
                d_Y = CUSPARSE.sm2('T','N','U',diag,alpha,d_A,d_X,'O')
                h_Y = collect(d_Y)
                Y = transpose(A)\(alpha * X)
                @test Y ≈ h_Y
                UA = (unit_diag ? UnitUpperTriangular(d_A) : UpperTriangular(d_A))
                d_y = UA\d_X
                h_y = collect(d_y)
                y = A\X
                @test y ≈ h_y
                d_y = transpose(UA)\d_X
                h_y = collect(d_y)
                y = transpose(A)\X
                @test y ≈ h_y
                LA = (unit_diag ? UnitLowerTriangular(d_A) : LowerTriangular(d_A))
                d_y = LA\d_X
                h_y = collect(d_y)
                y = Al\X
                @test y ≈ h_y
                d_y = transpose(LA)\d_X
                h_y = collect(d_y)
                y = transpose(Al)\X
                @test y ≈ h_y
                #=d_y = UpperTriangular(d_A)'\d_X
                h_y = collect(d_y)
                y = A'\X
                @test y ≈ h_y=#
                # shouldn't work for now bc sv2 has no way to do conj...
                A = sparse(rand(elty,m,n))
                d_A = CuSparseMatrixCSC(A)
                @test_throws DimensionMismatch CUSPARSE.sm2('N','N','U',diag,alpha,d_A,d_X,'O')
            end
        end
    end
end

@testset "axpyi" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        @testset "axpyi!" begin
            x = sparsevec(rand(1:m,k), rand(elty,k), m)
            y = rand(elty,m)
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            alpha = rand(elty)
            d_y = CUSPARSE.axpyi!(alpha,d_x,d_y,'O')
            #compare
            h_y = collect(d_y)
            y[nonzeroinds(x)] += alpha * nonzeros(x)
            @test h_y ≈ y
        end

        @testset "axpyi" begin
            x = sparsevec(rand(1:m,k), rand(elty,k), m)
            y = rand(elty,m)
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            alpha = rand(elty)
            d_z = CUSPARSE.axpyi(alpha,d_x,d_y,'O')
            #compare
            h_z = collect(d_z)
            z = copy(y)
            z[nonzeroinds(x)] += alpha * nonzeros(x)
            @test h_z ≈ z
            d_z = CUSPARSE.axpyi(d_x,d_y,'O')
            #compare
            h_z = collect(d_z)
            z = copy(y)
            z[nonzeroinds(x)] += nonzeros(x)
            @test h_z ≈ z
        end
    end
end

@testset "gthr and gthrz" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        x = sparsevec(rand(1:m,k), rand(elty,k), m)
        y = rand(elty,m)
        @testset "gthr!" begin
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            d_y = CUSPARSE.gthr!(d_x,d_y,'O')
            h_x = collect(d_x)
            @test h_x ≈ SparseVector(m,nonzeroinds(x),y[nonzeroinds(x)])
        end

        @testset "gthr" begin
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            d_z = CUSPARSE.gthr(d_x,d_y,'O')
            h_z = collect(d_z)
            @test h_z ≈ SparseVector(m,nonzeroinds(x),y[nonzeroinds(x)])
        end

        @testset "gthrz!" begin
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            d_x,d_y = CUSPARSE.gthrz!(d_x,d_y,'O')
            h_x = collect(d_x)
            h_y = collect(d_y)
            @test h_x ≈ SparseVector(m,nonzeroinds(x),y[nonzeroinds(x)])
            #y[nonzeroinds(x)] = zero(elty)
            #@test h_y ≈ y
        end

        @testset "gthrz" begin
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            d_z,d_w = CUSPARSE.gthrz(d_x,d_y,'O')
            h_w = collect(d_w)
            h_z = collect(d_z)
            @test h_z ≈ SparseVector(m,nonzeroinds(x),y[nonzeroinds(x)])
            #y[nonzeroinds(x)] = zero(elty)
            #@test h_w ≈ y
        end
    end
end

@testset "mv!" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        A = sparse(rand(elty,m,n))
        x = rand(elty,n)
        y = rand(elty,m)
        alpha = rand(elty)
        beta = rand(elty)
        @testset "$(typeof(d_A))" for d_A in [CuSparseMatrixCSR(A),
                                              CuSparseMatrixCSC(A),
                                              CuSparseMatrixBSR(A, blockdim)]
            d_x = CuArray(x)
            d_y = CuArray(y)
            @test_throws DimensionMismatch CUSPARSE.mv!('T',alpha,d_A,d_x,beta,d_y,'O')
            @test_throws DimensionMismatch CUSPARSE.mv!('N',alpha,d_A,d_y,beta,d_x,'O')
            CUSPARSE.mv!('N',alpha,d_A,d_x,beta,d_y,'O')
            h_z = collect(d_y)
            z = alpha * A * x + beta * y
            @test z ≈ h_z
            #if d_A isa CuSparseMatrixCSR
            #    @test d_y' * (d_A * d_x) ≈ (d_y' * d_A) * d_x
            #end
        end
    end

    if capability(device()) >= v"5.3"
    @testset for elty in [Float16,ComplexF16]
        A = sparse(rand(elty,m,n))
        x = rand(elty,n)
        y = rand(elty,m)
        alpha = rand(elty)
        beta = rand(elty)
        @testset "$(typeof(d_A))" for d_A in [CuSparseMatrixCSR(A),
                                              CuSparseMatrixCSC(A)]
            d_x = CuArray(x)
            d_y = CuArray(y)
            @test_throws DimensionMismatch CUSPARSE.mv!('T',alpha,d_A,d_x,beta,d_y,'O')
            @test_throws DimensionMismatch CUSPARSE.mv!('N',alpha,d_A,d_y,beta,d_x,'O')
            CUSPARSE.mv!('N',alpha,d_A,d_x,beta,d_y,'O')
            h_z = collect(d_y)
            z = alpha * A * x + beta * y
            @test z ≈ h_z
            if d_A isa CuSparseMatrixCSR
                @test d_y' * (d_A * d_x) ≈ (d_y' * d_A) * d_x
            end
        end
    end
    end
end

@testset "mm!" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        A = sparse(rand(elty,m,k))
        B = rand(elty,k,n)
        C = rand(elty,m,n)
        alpha = rand(elty)
        beta = rand(elty)
        @testset "$(typeof(d_A))" for d_A in [CuSparseMatrixCSR(A),
                                              CuSparseMatrixCSC(A),
                                              CuSparseMatrixBSR(A, blockdim)]
            d_B = CuArray(B)
            d_C = CuArray(C)
            mm! = if CUSPARSE.version() < v"10.3.1" && d_A isa CuSparseMatrixCSR
                CUSPARSE.mm2!
            else
                CUSPARSE.mm!
            end
            @test_throws DimensionMismatch mm!('N','T',alpha,d_A,d_B,beta,d_C,'O')
            @test_throws DimensionMismatch mm!('T','N',alpha,d_A,d_B,beta,d_C,'O')
            if CUSPARSE.version() < v"10.3.1" && d_A isa CuSparseMatrixCSR
                # A^T is not supported with B^T
                @test_throws ArgumentError mm!('T','T',alpha,d_A,d_B,beta,d_C,'O')
            else
                @test_throws DimensionMismatch mm!('T','T',alpha,d_A,d_B,beta,d_C,'O')
            end
            @test_throws DimensionMismatch mm!('N','N',alpha,d_A,d_B,beta,d_B,'O')
            mm!('N','N',alpha,d_A,d_B,beta,d_C,'O')
            h_D = collect(d_C)
            D = alpha * A * B + beta * C
            @test D ≈ h_D
        end
    end
    if CUSPARSE.version() >= v"11.1.1"
        @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
            @testset for (pA, pB, pC) in [(0.1, 0.1, 0.8), (0.8, 0.8, 0.1), (0.4, 0.8, 0.5)]
                A = sprand(elty,m,k, pA)
                B = sprand(elty,k,n, pB)
                C = sprand(elty,m,n, pC)
                alpha = rand(elty)
                beta  = rand(elty)
                @testset "$(typeof(d_A))" for (d_A, d_B, d_C) in [(CuSparseMatrixCSR(A),
                                                                   CuSparseMatrixCSR(B),
                                                                   CuSparseMatrixCSR(C))] # for now only supports CSR
                    mm! = CUSPARSE.mm!
                    @test_throws ArgumentError mm!('N','T',alpha,d_A,d_B,beta,d_C,'O')
                    @test_throws ArgumentError mm!('T','N',alpha,d_A,d_B,beta,d_C,'O')
                    @test_throws ArgumentError mm!('T','T',alpha,d_A,d_B,beta,d_C,'O')
                    @test_throws DimensionMismatch mm!('N','N',alpha,d_A,d_B,beta,d_B,'O')
                    D = alpha * A * B + beta * C
                    d_C = mm!('N','N',alpha,d_A,d_B,beta,d_C,'O')
                    h_C = SparseMatrixCSC(d_C)
                    @test D ≈ h_C
                end
            end
        end
    end

    @testset "issue 493" begin
        x = cu(rand(20))
        cu(sprand(Float32,10,10,0.1)) * @view(x[1:10])
    end
end

@testset "sctr" begin
    @testset for elty in [Float32,Float64,ComplexF32,ComplexF64]
        x = sparsevec(rand(1:m,k), rand(elty,k), m)
        y = zeros(elty,m)
        @testset "sctr!" begin
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            d_y = CUSPARSE.sctr!(d_x,d_y,'O')
            h_y = collect(d_y)
            y[nonzeroinds(x)]  += nonzeros(x)
            @test h_y ≈ y
        end
        y = zeros(elty,m)

        @testset "sctr" begin
            d_x = CuSparseVector(x)
            d_y = CUSPARSE.sctr(d_x,'O')
            h_y = collect(d_y)
            y = zeros(elty,m)
            y[nonzeroinds(x)]  += nonzeros(x)
            @test h_y ≈ y
        end
    end
end

@testset "roti" begin
    @testset for elty in [Float32,Float64]
        x = sparsevec(rand(1:m,k), rand(elty,k), m)
        y = rand(elty,m)
        @testset "roti!" begin
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            angle = rand(elty)
            d_x,d_y = CUSPARSE.roti!(d_x,d_y,cos(angle),sin(angle),'O')
            h_x = collect(d_x)
            h_y = collect(d_y)
            z = copy(x)
            w = copy(y)
            y[nonzeroinds(x)] = cos(angle)*w[nonzeroinds(z)] - sin(angle)*nonzeros(z)
            @test h_x ≈ SparseVector(m,nonzeroinds(x),cos(angle)*nonzeros(z) + sin(angle)*w[nonzeroinds(z)])
            @test h_y ≈ y
        end

        @testset "roti" begin
            d_x = CuSparseVector(x)
            d_y = CuArray(y)
            angle = rand(elty)
            d_z,d_w = CUSPARSE.roti(d_x,d_y,cos(angle),sin(angle),'O')
            h_w = collect(d_w)
            h_z = collect(d_z)
            z = copy(x)
            w = copy(y)
            w[nonzeroinds(z)] = cos(angle)*y[nonzeroinds(x)] - sin(angle)*nonzeros(x)
            @test h_z ≈ SparseVector(m,nonzeroinds(z), cos(angle)*nonzeros(x) + sin(angle)*y[nonzeroinds(x)])
            @test h_w ≈ w
        end
    end
end

ext: jl


text:
# test to see if the likelihood is correctly calculated
# and if the networks are correctly estimated
# Claudia August 2015

# -------------------5taxon tree------------------

if !isdefined(:individualtest) individualtest = false; end

if(individualtest)
    include("../src/types.jl")
    include("../src/functions.jl")
end

if isdefined(:PhyloNetworks)
    PhyloNetworks.CHECKNET || error("need CHECKNET==true in PhyloNetworks to test snaq in test_correctLik.jl")
else
    CHECKNET || error("need CHECKNET==true to test snaq in test_correctLik.jl")
end

#df = readtable("Tree_output.txt")
df=DataFrame(t1=["6","6","10","6","6"],
             t2=["7","7","7","10","7"],
             t3=["4","10","4","4","4"],
             t4=["8","8","8","8","10"],
             CF1234=[0.2729102510259939, 0.3967750546426937, 0.30161247267865315, 0.24693940689390592, 0.2729102510259939],
             CF1324=[0.45417949794801216, 0.30161247267865315, 0.30161247267865315, 0.5061211862121882, 0.45417949794801216],
             CF1423=[0.2729102510259939, 0.30161247267865315, 0.3967750546426937, 0.24693940689390592, 0.2729102510259939])
d = readTableCF(df)

# starting tree:
tree = "((6,4),(7,8),10);"
currT = readTopologyLevel1(tree);
#printEdges(currT)

extractQuartet!(currT,d)
calculateExpCFAll!(d)
lik = logPseudoLik(d)

@test lik ≈ 193.7812623319291
println("passed tree example")

#estTree = optTopRun1!(currT,d,0,5454) # issue with printCounts, TravisCI?
#@test estTree.loglik ≈ 0.0 atol=1e-8
println("passed optTopRun1! on tree")

# ------------------5taxon network 1 hybridization: Case H-----------------
# starting topology: Case G
tree = "((((6:0.1,4:1.5)1:0.2,(7)11#H1)5:0.1,(11#H1,8)),10:0.1);" # Case G
currT = readTopologyLevel1(tree);
# real network: Case H
df=DataFrame(t1=["6","6","10","6","6"],t2=["7","7","7","10","7"],t3=["4","10","4","4","4"],t4=["8","8","8","8","10"],CF1234=[0.13002257237728915, 0.36936019721747243, 0.34692592933269173, 0.12051951084152591, 0.11095702789935982], CF1324=[0.7399548552454217, 0.28371387344983595, 0.28371387344983595, 0.7589609783169482, 0.7780859442012804],CF1423=[0.13002257237728915, 0.34692592933269173, 0.36936019721747243, 0.12051951084152591, 0.11095702789935982])
d = readTableCF(df)

extractQuartet!(currT,d)
calculateExpCFAll!(d)
lik = logPseudoLik(d)

@test lik ≈ 50.17161079450669
println("passed computation of likelihood")

estNet = optTopRun1!(currT, 0.01,75, d,1, 1e-5,1e-6,1e-3,1e-4,
                     false,true,Int[], 5454, STDOUT,false,0.3, STDOUT)
# topology, likAbs,Nfail, data,hmax, fRel,fAbs,xRel,xAbs,
# verbose,closeN,numMoves, seed, logfile,writelog,probST,sout)
@test estNet.loglik ≈ 0.002165 atol=5.0e-6
# 0.00216 < estNet.loglik < 0.00217 || Base.error("not correct estimated network")
println("passed estimation of net")

@testset "snaq! in serial and in parallel" begin
  n1 = snaq!(currT, d, hmax=1, runs=2, Nfail=10, seed=1234,
             ftolRel=1e-2,ftolAbs=1e-2,xtolAbs=1e-2,xtolRel=1e-2)
  addprocs(1)
  @everywhere using PhyloNetworks
  n2 = snaq!(currT, d, hmax=1, runs=2, Nfail=10, seed=1234,
             ftolRel=1e-2,ftolAbs=1e-2,xtolAbs=1e-2,xtolRel=1e-2)
  rmprocs(workers())
  @test writeTopology(n1, round=true)==writeTopology(n2, round=true)
  @test n1.loglik == n2.loglik
  rm("snaq.out")
  rm("snaq.networks")
  rm("snaq.log") # .log and .err should be git-ignored, but still
  rm("snaq.err")
end


text:
# Faraday constant (C/mol)
const F = 96485
# Temperature (Kelvin)
const T = 310
# Ideal gas constant (kJ / (K*mol))
const R = 8314.46
# Unit voltage for Nernst potential (mV)
const RT_F = (R * T) / F
# Scaling factor of unit voltage for Nernst potential(1/mV)
const F_RT = F / (R * T)

# Dissociation constants (units in mM)
const KWATER = 1E-14 * 1E3 * 1E3
const KA_PI = 1.78E-7 * 1E3
const KA_ATP = 3.31E-7 * 1E3
const KA_ADP = 4.17E-7 * 1E3
const KMG_ATP = 6.46E-5 * 1E3
const KMG_ADP = 5.62E-4 * 1E3
const KA_SUC = 6.3E-6 * 1E3
const KA_H2O = 1E-14 * 1E3

# Hydroxide ion
_oh(h) = KWATER / h

# Michaelis-Menton reaction rates and Hills function
_mm(x, k) = x / (x + k)
_mm_reci(x, k) = (x + k) / x
_hills(x, k, n) = x^n / (x^n + k^n)
_hills_reci(x, k, n) = (x^n + k^n) / x^n

#=
Get propotions of AXP from dissociation constants
Returns AXPn-, HAXP, MgAXP, poly (the denominator)
Scalar version
=#
_poly(h, KA) = one(h) + h / KA
function _breakdown_axp(axp, h, mg, KA, KMG)
    poly = _poly(h, KA) + mg / KMG
    axpn = axp / poly
    haxp = axpn * h / KA
    mgaxp = axp - axpn - haxp
    return (axpn, haxp, mgaxp, poly)
end

breakdown_atp(atp, h, mg) = _breakdown_axp(atp, h, mg, KA_ATP, KMG_ATP)
breakdown_adp(adp, h, mg) = _breakdown_axp(adp, h, mg, KA_ADP, KMG_ADP)

# Binding polynomial of phosphate
_pi_poly(h) = _poly(h, KA_PI)
_H2PO4(pi_t, h) = pi_t * _mm(h, KA_PI)

# Binding Polynomial of succinate
_suc_poly(h) = _poly(h, KA_SUC)
# Binding Polynomial of water
_h2o_poly(h) = _poly(h, KA_H2O)

# Nernst potentials (in mV)
_nernst(x_out, x_in, z=1) = RT_F / z * log(x_out / x_in)
_ena(na_o, na_i) = _nernst(na_o, na_i, 1)
_enak(k_o, na_o, k_i, na_i, P_NA_K) = _nernst(na_o * P_NA_K + k_o, na_i * P_NA_K + k_i, 1)
_ek(k_o, k_i) = _nernst(k_o, k_i, 1)
_eca(ca_o, ca_i) = _nernst(ca_o, ca_i, 2)
_esox(sox_i, sox_m) = _nernst(sox_i, sox_m, -1)

# Inverse function of the Nernst equation
# ra stands for Ratio of (electrochemical) Activity
_ra(v) = exp(v * F_RT)

# Proton motive force
_ΔμH(dpsi, h_i, h_m) = dpsi + _nernst(h_i, h_m, 1)
_ΔμH(dpsi, ΔpH) = dpsi - 2.303 * RT_F * ΔpH

# Capacitance of palsma membrane (μF/cm²)
const CM = 1
const CM_INV = inv(CM)
# Capacitance of mitochondrial inner membrane (mV/ms)
const CM_MITO = 1.812E-3
const CM_MITO_INV = inv(CM_MITO)

# Simple diffusion rate
_vdiff(a, b, c_diff) = c_diff * (a - b)

# GHK flux equation
_ghk(zvfrt, ezvfrtm1, px, x_i, x_o, z=1) = px * z * zvfrt * F * ((ezvfrtm1 + 1) * x_i - x_o) / ezvfrtm1


text:
#
# types.jl -
#
# Type definitions for Julia interface to scientific cameras.
#
#------------------------------------------------------------------------------
#
# This file is part of the `ScientificCameras.jl` package which is licensed
# under the MIT "Expat" License.
#
# Copyright (C) 2017, Éric Thiébaut.
#

"""
`TimeoutError` is used to throw a timeout exception.
"""
struct TimeoutError <: Exception; end

"""

Concrete types derived from abstract type `ScientificCamera` are used to
uniquely identify the different camera models or different combinations of
camera model and frame grabber.

"""
abstract type ScientificCamera end

"""

`ROI` is a structure to store a region of interest (ROI), its fields are:

- `xsub`:   Horizontal size of macro-pixels (in pixels).
- `ysub`:   Vertical size of macro-pixels (in pixels).
- `xoff`:   Horizontal offset in pixels of the ROI relative to the sensor.
- `yoff`:   Vertical offset in pixels of the ROI relative to the sensor.
- `width`:  Width in macro-pixels of the ROI.
- `height`: Height in macro-pixels of the ROI.

All fields are integer valued (of type `Int`).  A ROI is constructed by:

    ROI(xsub, ysub, xoff, yoff, width, height)

or

    ROI(xoff, yoff, width, height)

which assumes macro-pixels of same size as pixels, or

    ROI(width, height)

which assumes macro-pixels of same size as pixels and zero offsets.  Note that
for efficiency reasons, there is no testing of the validity of the settings but
you may use `checkroi` for that.

See also: [`getroi`](@ref), [`setroi!`](@ref), [`resetroi!`](@ref),
          [`checkroi`](@ref).

"""
mutable struct ROI
    xsub::Int
    ysub::Int
    xoff::Int
    yoff::Int
    width::Int
    height::Int
    ROI(xsub::Integer, ysub::Integer, xoff::Integer, yoff::Integer, width::Integer, height::Integer) =
        new(xsub, ysub, xoff, yoff, width, height)
    ROI(xoff::Integer, yoff::Integer, width::Integer, height::Integer) =
        new(1, 1, xoff, yoff, width, height)
    ROI(width::Integer, height::Integer) =
        new(1, 1, 0, 0, width, height)
end

# Custom exception to report errors for non-extended methods.
struct NotImplementedException <: Exception
   sym::Symbol
end

#------------------------------------------------------------------------------
# PIXEL FORMATS
#
# Pixel formats are defined in a separate module so that it is easier to import
# all pixel formats with:
#
#    using ScientificCameras.PixelFormats

module PixelFormats

export
    PixelFormat,
    Monochrome,
    ColorFormat,
    RGB,
    BGR,
    XRGB,
    XBGR,
    RGBX,
    BGRX,
    BayerFormat,
    BayerRGGB,
    BayerGRBG,
    BayerBGGR,
    BayerBGGR,
    YUV422

"""

`ScientificCameras.PixelFormat{N}` is the super-type of the various pixel
formats and is parameterized by `N` the number of bits per pixel.

Actual pixel formats are concrete sub-types of `PixelFormat{N}`.  The type
hierarchy is:

    PixelFormat{N} (abstract)
     |- Monochrome{N} (concrete)
     `- ColorFormat{N} (abstract)
         |- RGB{N} (concrete)
         |- BGR{N} (concrete)
         |- XRGB{N} (concrete)
         |- XBGR{N} (concrete)
         |- RGBX{N} (concrete)
         |- BGRX{N} (concrete)
         |- BayerFormat{N} (abstract)
         |   |- BayerRGGB{N} (concrete)
         |   |- BayerGRBG{N} (concrete)
         |   |- BayerGBRG{N} (concrete)
         |   `- BayerBGGR{N} (concrete)
         `- YUV422 (concrete)
       (etc.)

Note that *concrete* types (the leaves of the above tree) are all singletons.
This system forbids to have concrete definitions which provide an equivalent
Julia *bits*, that is *plain data*, type.  This is not really an isssue since
pixel formats are just meant to describe the pixel format used by a camera, not
to provide Julia equivalent bits types.  To get the equivalent bits type (when
it exists), call:

    equivalentbitstype(format)

which returns `Nothing` when there is no possible exact equivalence.

See also: [`equivalentbitstype`](@ref), [`bitsperpixel`](@ref),
          [`ScientificCameras.Monochrome`](@ref),
          [`ScientificCameras.ColorFormat`](@ref).

"""
abstract type PixelFormat{N} end

"""

`ScientificCameras.Monochrome{N}` is a monochrome pixel format where each pixel
is encoded with `N` bits.

See also: [`ScientificCameras.PixelFormat`](@ref).

"""
struct Monochrome{N} <: PixelFormat{N}; end

"""

`ScientificCameras.ColorFormat{N}` is the super-type of colored pixel formats
where each pixel is encoded with `N` bits.

See also: [`ScientificCameras.PixelFormat`](@ref).

"""
abstract type ColorFormat{N} <: PixelFormat{N} end

"""

`ScientificCameras.BayerFormat{N}` is a colored pixel format where the color is
encoded on 4 pixels (1 red, 2 green and 1 blue) each with `N` bits.  There are
several *concrete* sub-types:

- `BayerRGGB{N}` is Bayer format with the mask `['R' 'G'; 'G' 'B']`.
- `BayerGRBG{N}` is Bayer format with the mask `['G' 'R'; 'B' 'G']`.
- `BayerGBRG{N}` is Bayer format with the mask `['G' 'B'; 'R' 'G']`.
- `BayerBGGR{N}` is Bayer format with the mask `['B' 'G'; 'G' 'R']`.

See also: [`ScientificCameras.PixelFormat`](@ref),
          [`ScientificCameras.ColorFormat`](@ref).

"""
abstract type BayerFormat{N} <: ColorFormat{N} end

# Concrete sub-types.
struct BayerRGGB{N} <: BayerFormat{N}; end
struct BayerGRBG{N} <: BayerFormat{N}; end
struct BayerGBRG{N} <: BayerFormat{N}; end
struct BayerBGGR{N} <: BayerFormat{N}; end

"""

`ScientificCameras.RGB{N}` is a packed pixel color format where each pixel
counts `N` bits and encodes the red, green and blue levels (in that order).

See also: [`ScientificCameras.ColorFormat`](@ref),
          [`ScientificCameras.BGR`](@ref).

"""
struct RGB{N} <: ColorFormat{N}; end

"""

`ScientificCameras.BGR{N}` is a packed pixel color format where each pixel
counts `N` bits and encodes the blue, green and red levels (in that order).

See also: [`ScientificCameras.ColorFormat`](@ref),
          [`ScientificCameras.RGB`](@ref).

"""
struct BGR{N} <: ColorFormat{N}; end

"""

`ScientificCameras.XRGB{N}` is a packed pixel color format where each pixel
counts `N` bits and encodes some padding (the "X") followed by the red, green
and blue levels (in that order).

`ScientificCameras.XBGR{N}`, `ScientificCameras.RGBX{N}` and
`ScientificCameras.BGRX{N}` are similar packed pixel color formats with
different ordering of the components in memory.

See also: [`ScientificCameras.ColorFormat`](@ref),
          [`ScientificCameras.BGR`](@ref).

"""
struct XRGB{N} <: ColorFormat{N}; end
struct XBGR{N} <: ColorFormat{N}; end
struct RGBX{N} <: ColorFormat{N}; end
struct BGRX{N} <: ColorFormat{N}; end

@doc @doc(XRGB) XBGR
@doc @doc(XRGB) RGBX
@doc @doc(XRGB) BGRX

"""

`YUV422` is a 32-bit packed color format which encodes Y, U and V color
components in a macro pixel with 4 bytes U, Y0, V and Y1 (in that order) where
Y0 and Y1 are the lowest and highest significant bytes of Y.

See http://www.fourcc.org/yuv.php and
https://stackoverflow.com/questions/8561185/yuv-422-yuv-420-yuv-444

The equivalent Julia bits type is given by `Y422BitsType`.

See also: [`bitsperpixel`](@ref), [`equivalentbitstype`](@ref),
          [`ScientificCameras.PixelFormat`](@ref).

"""
struct YUV422 <: ColorFormat{32}; end

struct PixelFormatID
    id::UInt32
end

const _MONOCHROME = UInt32( 1)
const _RGB        = UInt32( 2)
const _BGR        = UInt32( 3)
const _XRGB       = UInt32( 4)
const _XBGR       = UInt32( 5)
const _RGBX       = UInt32( 6)
const _BGRX       = UInt32( 7)
const _BAYER_RGGB = UInt32( 8)
const _BAYER_GRBG = UInt32( 9)
const _BAYER_GBRG = UInt32(10)
const _BAYER_BGGR = UInt32(11)
const _YUV422     = UInt32(12)

_encode(layout::UInt32, nbits::Integer) =
    PixelFormatID(layout | (UInt32(nbits) << 8))

encode(::Type{Monochrome{N}}) where {N} = _encode(_MONOCHROME, N)
encode(::Type{RGB{N}})        where {N} = _encode(_RGB, N)
encode(::Type{BGR{N}})        where {N} = _encode(_BGR, N)
encode(::Type{XRGB{N}})       where {N} = _encode(_XRGB, N)
encode(::Type{XBGR{N}})       where {N} = _encode(_XBGR, N)
encode(::Type{RGBX{N}})       where {N} = _encode(_RGBX, N)
encode(::Type{BGRX{N}})       where {N} = _encode(_BGRX, N)
encode(::Type{BayerRGGB{N}})  where {N} = _encode(_BAYER_RGGB, N)
encode(::Type{BayerGRBG{N}})  where {N} = _encode(_BAYER_GRBG, N)
encode(::Type{BayerGBRG{N}})  where {N} = _encode(_BAYER_GBRG, N)
encode(::Type{BayerBGGR{N}})  where {N} = _encode(_BAYER_BGGR, N)
encode(::Type{YUV422})                  = _encode(_YUV422, 32)

bitsperpixel(fmt::PixelFormatID) = Int(fmt.id >> 8)

function decode(fmt::PixelFormatID) :: Type{<:PixelFormat}
    N = bitsperpixel(fmt)
    c = (fmt.id & UInt32(0xff))
    return (c == _MONOCHROME ? Monochrome{N} :
            c == _RGB        ? RGB{N}        :
            c == _BGR        ? BGR{N}        :
            c == _XRGB       ? XRGB{N}       :
            c == _XBGR       ? XBGR{N}       :
            c == _RGBX       ? RGBX{N}       :
            c == _BGRX       ? BGRX{N}       :
            c == _BAYER_RGGB ? BayerRGGB{N}  :
            c == _BAYER_GRBG ? BayerGRBG{N}  :
            c == _BAYER_GBRG ? BayerGBRG{N}  :
            c == _BAYER_BGGR ? BayerBGGR{N}  :
            c == _YUV422 && N == 32 ? YUV422 :
            error("invalid pixel format identifier"))
end

end # module PixelFormats

# Import all pixel formats in the main module.
using .PixelFormats

#------------------------------------------------------------------------------
# EQUIVALENT BITS TYPES FOR PIXEL FORMATS
#
# Like pixel formats, bits types are defined in a separate module so that it is
# easier to import all pixel formats with:
#
#    using ScientificCameras.BitsTypes

module BitsTypes

export
    RGB24BitsType,
    BGR24BitsType,
    XRGB32BitsType,
    XBGR32BitsType,
    RGBX32BitsType,
    BGRX32BitsType,
    YUV422BitsType

# Note that whatever the ordering of values in memory, the constructors use
# always the same order: R, G, B and, possibly, X (which defaults to 0).
struct RGB24BitsType
    r::UInt8
    g::UInt8
    b::UInt8
    RGB24BitsType(r, g, b) = new(r, g, b)
end

struct BGR24BitsType
    b::UInt8
    g::UInt8
    r::UInt8
    BGR24BitsType(r, g, b) = new(b, g, r)
end

struct XRGB32BitsType
    x::UInt8
    r::UInt8
    g::UInt8
    b::UInt8
    XRGB32BitsType(r, g, b, x = zero(UInt8)) = new(x, r, g, b)
end

struct XBGR32BitsType
    x::UInt8
    b::UInt8
    g::UInt8
    r::UInt8
    XBGR32BitsType(r, g, b, x = zero(UInt8)) = new(x, b, g, r)
end

struct RGBX32BitsType
    r::UInt8
    g::UInt8
    b::UInt8
    x::UInt8
    RGBX32BitsType(r, g, b, x = zero(UInt8)) = new(r, g, b, x)
end

struct BGRX32BitsType
    b::UInt8
    g::UInt8
    r::UInt8
    x::UInt8
    BGRX32BitsType(r, g, b, x = zero(UInt8)) = new(b, g, r, x)
end

struct YUV422BitsType
    U  :: UInt8
    Y0 :: UInt8
    V  :: UInt8
    Y1 :: UInt8
end

end # module BitsTypes

# Import all bits types in the main module.
using .BitsTypes


text:
println("Starting...")
using DataFrames
using Gadfly
using TextAnalysis
using IJulia
using SQLite
using XGBoost
println("Ok!")

max_stars_repo_name: Kaggle/docker-julia

max_issues_repo_name: AI-For-Rural/docker-julia

max_forks_repo_name: AI-For-Rural/docker-julia


text:
"""
Redirect all data written to stdout by a function to log events.
"""
function redirect_stdout_to_log(func::Function)
    path, io = mktemp()
    close(io)

    try
        open(path, "w") do out
            redirect_stdout(out) do
                func()
            end
        end
    finally
        try
            for line in eachline(path)
                if !isempty(line)
                    @info line
                end
            end
        finally
            rm(path)
        end
    end
end

# The code below is better than the above function because it will output log events as
# they occur instead of when the function completes.  It can be enabled whenever we stop
# supporting Julia 1.2.  Threads.@spawn requires Julia 1.3.

#=
function redirect_stdout_to_log(func::Function)
    orig_stdout = Base.stdout
    (read_pipe, write_pipe) = redirect_stdout()
    redirector = StdoutRedirector(read_pipe, write_pipe, true)

    task = Threads.@spawn _redirect(redirector)
    while !istaskstarted(task)
        sleep(0.1)
    end

    try
        func()
    finally
        redirect_stdout(orig_stdout)
        redirector.enabled = false
        print(redirector.write_pipe, "\n")
        close(redirector.read_pipe)
        close(redirector.write_pipe)
        wait(task)
    end
end

mutable struct StdoutRedirector
    read_pipe::Base.PipeEndpoint
    write_pipe::Base.PipeEndpoint
    enabled::Bool
end

function _redirect(redirector::StdoutRedirector)
    while redirector.enabled
        line = readline(redirector.read_pipe)
        _log_line(line)
    end

    # There may still be data in the pipe.
    if !eof(redirector.read_pipe)
        for line in eachline(redirector.read_pipe)
            _log_line(line)
        end
    end
end

function _log_line(data)
    line = strip(data)
    if !isempty(line)
        @info line
    end
end
=#


text:
abstract type Field end

struct Byr<:Field
  year
end

function isyearbetween(v, min, max)
  contains(v, r"^\d{4}$") && min <= parse(Int, v) <= max
end

function isvalid(byr::Byr)
  isyearbetween(byr.year, 1920, 2002)
end

struct Eyr<:Field
  year
end

function isvalid(eyr::Eyr)
  isyearbetween(eyr.year, 2020, 2030)
end

struct Iyr<:Field
  year
end

function isvalid(iyr::Iyr)
  isyearbetween(iyr.year, 2010, 2020)
end

struct Hgt<:Field
  height
end

function isvalid(hgt::Hgt)
  (endswith(hgt.height, "cm") && 150 <= parse(Int, hgt.height[1:end-2]) <= 193) || (endswith(hgt.height, "in") && 59 <= parse(Int, hgt.height[1:end-2]) <= 76)
end

struct Hcl<:Field
  colour
end

function isvalid(hcl::Hcl)
  contains(hcl.colour, r"^#[0-9a-f]{6}$")
end

struct Ecl<:Field
  colourcode
end

const EYE_COLOURS = ["amb", "blu", "brn", "gry", "grn", "hzl", "oth"]
function isvalid(ecl::Ecl)
  in(ecl.colourcode, EYE_COLOURS)
end

struct Pid<:Field
  id
end

function isvalid(pid::Pid)
  contains(pid.id, r"^\d{9}$")
end

struct Cid<:Field
  id
end

function isvalid(cid::Cid)
  true
end

struct Passport
  fields::Vector{Field}
  Passport(s) = new(eval.([Expr(:call, Symbol(uppercasefirst(a[1])), a[2]) for a in split.(split(s, "\n"), ":")]))
end

passports = Passport.(split(replace(strip(read("a4.txt", String)), " "=>"\n"), "\n\n"))

const REQUIRED = [:Byr, :Eyr, :Iyr, :Hgt, :Hcl, :Ecl, :Pid]
function hasrequiredfields(p::Passport)
  fieldtypes = nameof.(typeof.(p.fields))
  all(r -> in(r, fieldtypes), REQUIRED)
end

part1 = count(hasrequiredfields, passports)
println("$part1")

function isvalid(passport::Passport)
  hasrequiredfields(passport) && all(isvalid, passport.fields)
end

part2 = count(isvalid, passports)
println("$part2")


text:
import Base: show

const HighResolutionTimestamp = UInt
const EmptyTimestamp = HighResolutionTimestamp(0x0)

make_timestamp()::HighResolutionTimestamp  = time_ns()

mutable struct DataTestEvent
    data
    timestamp  :: HighResolutionTimestamp
    is_checked :: Bool

    DataTestEvent(data) = new(data, make_timestamp(), false)
end

mutable struct ErrorTestEvent
    err
    timestamp  :: HighResolutionTimestamp
    is_checked :: Bool

    ErrorTestEvent(err) = new(err, make_timestamp(), false)
end

mutable struct CompleteTestEvent
    timestamp  :: HighResolutionTimestamp
    is_checked :: Bool

    CompleteTestEvent() = new(make_timestamp(), false)
end

const TestActorEvent = Union{DataTestEvent, ErrorTestEvent, CompleteTestEvent}

timestamp(event::TestActorEvent)       = event.timestamp
mark_as_checked(event::TestActorEvent) = event.is_checked = true
is_checked(event::TestActorEvent)      = event.is_checked

struct TestActor <: Actor{Any}
    allowed_type :: Type
    data         :: Vector{TestActorEvent}
    errors       :: Vector{TestActorEvent}
    completes    :: Vector{TestActorEvent}
    created_at   :: HighResolutionTimestamp
    condition    :: Condition

    TestActor(type::Type{L}) where L = begin
        return new(L, Vector{TestActorEvent}(), Vector{TestActorEvent}(), Vector{TestActorEvent}(), make_timestamp(), Condition())
    end
end

on_next!(actor::TestActor,  d) = begin push!(data(actor),      DataTestEvent(deepcopy(d)));    notify(actor.condition, false) end
on_error!(actor::TestActor, e) = begin push!(errors(actor),    ErrorTestEvent(e));   yield(); notify(actor.condition, true) end
on_complete!(actor::TestActor) = begin push!(completes(actor), CompleteTestEvent()); yield(); notify(actor.condition, true) end

Base.show(io::IO, actor::TestActor) = print(io, "TestActor()")

# Creation operator

test_actor(::Type{L}) where L = TestActor(L)

# Actor factory

test_actor() = TestActorFactory()

struct TestActorFactory <: AbstractActorFactory end

create_actor(::Type{L}, factory::TestActorFactory) where L = test_actor(L)

# Utility functions
isreceived(actor::TestActor)  = length(data(actor))      !== 0
isfailed(actor::TestActor)    = length(errors(actor))    !== 0
iscompleted(actor::TestActor) = length(completes(actor)) !== 0

created_at(actor::TestActor) = actor.created_at
condition(actor::TestActor)  = actor.condition

data(actor::TestActor)      = actor.data
errors(actor::TestActor)    = actor.errors
completes(actor::TestActor) = actor.completes

data_timestamps(actor::TestActor)     = map(e -> timestamp(e), data(actor))
error_timestamps(actor::TestActor)    = map(e -> timestamp(e), errors(actor))
complete_timestamps(actor::TestActor) = map(e -> timestamp(e), completes(actor))

last_data_timestamp(actor::TestActor)     = begin ts = data_timestamps(actor); lastindex(ts) === 0 ? EmptyTimestamp : ts[end] end
last_error_timestamp(actor::TestActor)    = begin ts = error_timestamps(actor); lastindex(ts) === 0 ? EmptyTimestamp : ts[end] end
last_complete_timestamp(actor::TestActor) = begin ts = complete_timestamps(actor); lastindex(ts) === 0 ? EmptyTimestamp : ts[end] end

# Check for actor validness

check_data_equals(actor::TestActor, candidate)  = map(e -> e.data, data(actor))  == candidate || throw(DataEventsEqualityFailedException())
check_error_equals(actor::TestActor, candidate) = map(e -> e.err, errors(actor)) == [ candidate ] || throw(ErrorEventEqualityFailedException())

function check_isvalid(actor::TestActor)

    # Verifying nothing messed up inner structure of the test actor itself
    # --------------------------------------------------------------------

    # Data events should be of type DataTestEvent
    all(e -> e isa DataTestEvent, data(actor)) || throw(DataEventIncorrectTypeException())

    # Error events should be of type DataErrorEvent
    all(e -> e isa ErrorTestEvent, errors(actor)) || throw(ErrorEventIncorrectTypeException())

    # Complete events should be of type DataCompleteEvent
    all(e -> e isa CompleteTestEvent, completes(actor)) || throw(CompleteEventIncorrectTypeException())

    # --------------------------------------------------------------------

    # Actor cannot receive multiple errors events
    isfailed(actor) && length(errors(actor)) !== 1 && throw(MultipleErrorEventsException())

    # Actor cannot receive multiple complete events
    iscompleted(actor) && length(completes(actor)) !== 1 && throw(MultipleCompleteEventsException())

    # Actor cannot have error and complete events simultaneously
    if isfailed(actor) && iscompleted(actor)
        errorts    = last_error_timestamp(actor)
        completets = last_complete_timestamp(actor)
        errorts > completets ? throw(ErrorAfterCompleteEventException()) : throw(CompleteAfterErrorEventException())
    end

    # Actor cannot have next events after error event
    isfailed(actor) && last_error_timestamp(actor) < last_data_timestamp(actor) && throw(NextAfterErrorEventException())

    # Actor cannot have next events after complete event
    iscompleted(actor) && last_complete_timestamp(actor) < last_data_timestamp(actor) && throw(NextAfterCompleteEventException())

    # Timestamps of all data must be an increasing sequence
    issorted(data(actor), lt = Base.isless, by = e -> timestamp(e)) || throw(DataEventIncorrectTimestampsOrderException())

    # Actor must receive data only with allowed type
    all(e -> e.data isa actor.allowed_type, data(actor)) || throw(UnacceptableNextEventDataTypeException())

    return true
end

struct DataEventIncorrectTypeException            <: Exception end
struct ErrorEventIncorrectTypeException           <: Exception end
struct CompleteEventIncorrectTypeException        <: Exception end
struct DataEventIncorrectTimestampsOrderException <: Exception end
struct MultipleErrorEventsException               <: Exception end
struct MultipleCompleteEventsException            <: Exception end
struct ErrorAfterCompleteEventException           <: Exception end
struct CompleteAfterErrorEventException           <: Exception end
struct NextAfterErrorEventException               <: Exception end
struct NextAfterCompleteEventException            <: Exception end
struct UnacceptableNextEventDataTypeException     <: Exception end
struct DataEventsEqualityFailedException          <: Exception end
struct ErrorEventEqualityFailedException          <: Exception end

# Test stream values helpers

test_on_source(source::S, test; maximum_wait::Float64 = 60000.0, actor = nothing, check_timings = true) where S = test_on_source(as_subscribable(S), source, test, maximum_wait, actor, check_timings)

test_on_source(::InvalidSubscribableTrait,      source, test, maximum_wait, actor, check_timings)         = throw(InvalidSubscribableTraitUsageError(source))
test_on_source(::SimpleSubscribableTrait{T},    source, test, maximum_wait, actor, check_timings) where T = _test_on_source(T, source, test, maximum_wait, actor, check_timings)
test_on_source(::ScheduledSubscribableTrait{T}, source, test, maximum_wait, actor, check_timings) where T = _test_on_source(T, source, test, maximum_wait, actor, check_timings)

function _test_on_source(::Type{T}, source, test, maximum_wait, actor, check_timings) where T
    actor = actor === nothing ? test_actor(T) : actor

    is_completed = false

    task = @task begin
        try
            subscribe!(source |> safe() |> catch_error((err, obs) -> begin error!(actor, err); never(subscribable_extract_type(obs)) end), actor)
        catch err
            error!(actor, err)
        end
    end

    wakeup = @task begin
        timedwait(() -> is_completed, maximum_wait / MILLISECONDS_IN_SECOND; pollint = 0.5)
        if !is_completed
            notify(condition(actor), TestOnSourceTimedOutException(), error = true)
        end
    end

    schedule(task)
    schedule(wakeup)

    current_test = test

    if current_test !== nothing
        while !wait(condition(actor))
            test_against(actor, current_test, check_timings)

            current_test = get_next(current_test)

            if current_test === nothing
                break
            end
        end
    end

    yield()

    if current_test !== nothing
        test_against(actor, current_test, check_timings)
    end

    yield()

    test_against(actor, TestActorEveryStepVerificationTest(), check_timings)

    is_completed = true

    return true
end

struct TestActorEmptyTest end

get_next(::TestActorEmptyTest) = nothing

test_against(actor::TestActor, test::TestActorEmptyTest, check_timings::Bool) = begin end

struct TestActorEveryStepVerificationTest end

get_next(::TestActorEveryStepVerificationTest) = nothing

function test_against(actor::TestActor, test::TestActorEveryStepVerificationTest, check_timings::Bool)
    check_isvalid(actor)

    all(e -> e.is_checked, data(actor)) || throw(TestActorStreamUncheckedData(actor))
    all(e -> e.is_checked, errors(actor)) || throw(TestActorStreamUncheckedError(actor))
    all(e -> e.is_checked, completes(actor)) || throw(TestActorStreamUncheckedCompletion(actor))
end

struct TestActorErrorTest
    err
    time_passed :: Int

    TestActorErrorTest(err, time_passed::Int = -1) = new(err, time_passed)
end

get_next(::TestActorErrorTest) = nothing

time_passed(test::TestActorErrorTest)                        = test.time_passed
time_event(actor::TestActor, test::TestActorErrorTest)       = timestamp(Base.first(errors(actor)))
time_to_compare(actor::TestActor, test::TestActorErrorTest)  = length(data(actor)) === 0 ? created_at(actor) : timestamp(data(actor)[ end ])

function test_against(actor::TestActor, test::TestActorErrorTest, check_timings::Bool)
    check_isvalid(actor)

    if !isfailed(actor)
        throw(TestActorStreamMissingExpectedErrorException(test.err))
    end

    if test.err !== nothing
        actual   = Base.first(errors(actor)).err
        expected = test.err

        if actual != expected
            throw(TestActorStreamIncorrectExpectedErrorException(actual, expected))
        end
    end

    if is_checked(Base.last(errors(actor)))
        throw(TestActorStreamErrorDoubleCheckException())
    end

    if check_timings
        __check_time_passed(actor, test)
    end

    foreach((e) -> mark_as_checked(e), errors(actor))

    test_against(actor, TestActorEveryStepVerificationTest(), check_timings)

    return true
end

struct TestActorCompleteTest
    time_passed :: Int

    TestActorCompleteTest(time_passed::Int = -1) = new(time_passed)
end

get_next(::TestActorCompleteTest) = nothing

time_passed(test::TestActorCompleteTest)                        = test.time_passed
time_event(actor::TestActor, test::TestActorCompleteTest)       = timestamp(Base.first(completes(actor)))
time_to_compare(actor::TestActor, test::TestActorCompleteTest)  = length(data(actor)) === 0 ? created_at(actor) : timestamp(data(actor)[ end ])

function test_against(actor::TestActor, test::TestActorCompleteTest, check_timings::Bool)
    check_isvalid(actor)

    if !iscompleted(actor)
        throw(TestActorStreamMissingExpectedCompletionException())
    end

    if is_checked(Base.last(completes(actor)))
        throw(TestActorStreamCompletionDoubleCheckException())
    end

    if check_timings
        __check_time_passed(actor, test)
    end

    foreach((e) -> mark_as_checked(e), completes(actor))

    test_against(actor, TestActorEveryStepVerificationTest(), check_timings)

    return true
end

struct TestActorStreamValuesTest
    starts_from :: Int
    time_passed :: Int
    expected    :: Vector{Any}
    after_test  :: Any
    next        :: Any
end

get_next(test::TestActorStreamValuesTest) = test.next

time_passed(test::TestActorStreamValuesTest)                        = test.time_passed
time_event(actor::TestActor, test::TestActorStreamValuesTest)       = timestamp(data(actor)[ test.starts_from ])
time_to_compare(actor::TestActor, test::TestActorStreamValuesTest)  = test.starts_from === 1 ? created_at(actor) : timestamp(data(actor)[ test.starts_from - 1 ])

function test_against(actor::TestActor, test::TestActorStreamValuesTest, check_timings::Bool)
    check_isvalid(actor)

    actual   = map(e -> e.data, data(actor))[ test.starts_from:end ]
    expected = test.expected

    if actual != expected
        throw(TestActorStreamIncorrectStreamValuesException(actual, expected))
    end

    if check_timings
        __check_time_passed(actor, test)
    end

    foreach((e) -> mark_as_checked(e), data(actor)[ test.starts_from:end ])

    test_against(actor, test.after_test, check_timings)
    test_against(actor, TestActorEveryStepVerificationTest(), check_timings)

    return true
end

function __check_time_passed(actor::TestActor, test)
    if time_passed(test) > 0
        t_expected   = UInt(time_passed(test) * NANOSECONDS_IN_MILLISECOND)
        t_event      = time_event(actor, test)
        t_to_compare = time_to_compare(actor, test)

        if !(t_expected * 0.8 < (t_event - t_to_compare) < t_expected * 10.0)
            throw(TestActorStreamIncorrectStreamTimePassedException((t_event - t_to_compare) / NANOSECONDS_IN_MILLISECOND, time_passed(test)))
        end
    elseif time_passed(test) < 0
        t_expected   = 250 * NANOSECONDS_IN_MILLISECOND # Hardcoded 250ms here, TODO
        t_event      = time_event(actor, test)
        t_to_compare = time_to_compare(actor, test)

        if (t_event - t_to_compare) > t_expected
            throw(TestActorStreamSignificantDelayTimePassedException((t_event - t_to_compare) / NANOSECONDS_IN_MILLISECOND))
        end
    end
end

__add_starts_from(test::Nothing, count::Int) = nothing
__add_starts_from(test::TestActorEveryStepVerificationTest, count::Int) = test
__add_starts_from(test::TestActorErrorTest, count::Int) = test
__add_starts_from(test::TestActorCompleteTest, count::Int) = test

function __add_starts_from(left::TestActorStreamValuesTest, count::Int)
    return TestActorStreamValuesTest(left.starts_from + count, left.time_passed, left.expected, left.after_test, __add_starts_from(left.next, count))
end

function __test_connect(left::TestActorStreamValuesTest, right::TestActorStreamValuesTest)
    return TestActorStreamValuesTest(left.starts_from, left.time_passed, left.expected, left.after_test, __add_starts_from(right, length(left.expected)))
end

function __test_connect(left::Int, right::TestActorStreamValuesTest)
    return TestActorStreamValuesTest(right.starts_from, left, right.expected, right.after_test, right.next)
end

function __test_connect(left::Int, right::TestActorErrorTest)
    return TestActorErrorTest(right.err, left)
end

function __test_connect(left::Int, right::TestActorCompleteTest)
    return TestActorCompleteTest(left)
end

function __test_connect(left::TestActorStreamValuesTest, right::TestActorErrorTest)
    return TestActorStreamValuesTest(left.starts_from, left.time_passed, left.expected, left.after_test, right)
end

function __test_connect(left::TestActorStreamValuesTest, right::TestActorCompleteTest)
    return TestActorStreamValuesTest(left.starts_from, left.time_passed, left.expected, left.after_test, right)
end

macro ts(expr)

    function process_arg_values!(values, tests, arg)
        push!(values, arg)
    end

    function process_arg_values!(values, tests, arg::Symbol)
        if arg === :nothing
            push!(values, nothing)
        elseif arg === :e
            push!(tests, TestActorErrorTest(nothing))
        elseif arg === :c
            push!(tests, TestActorCompleteTest())
        else
            push!(values, eval(arg))
            # error("Invalid usage of @ts macro in process_arg_values!(arg = $arg::$(typeof(arg)))")
        end
    end

    function process_arg_values!(values, tests, arg::Expr)
        if arg.head === :call && arg.args[1] === Symbol(":")
            push!(values, arg.args[2]:arg.args[3]...)
        elseif arg.head === :vect
            push!(values, collect(arg.args))
        elseif arg.head === :tuple
            push!(values, (arg.args..., ))
        elseif arg.head === :call
            if arg.args[1] === :e
                push!(tests, TestActorErrorTest(length(arg.args) === 2 ? arg.args[2] : nothing))
            elseif arg.args[2] === :c
                push!(tests, TestActorCompleteTest())
            else
                push!(values, eval(arg))
            end
        else
            push!(values, eval(arg))
            # return arg
            # error("Invalid usage of @ts macro in process_arg_values!(arg = $arg::$(typeof(arg)))")
        end
    end

    function lookup_tree(expr::Expr)
        if expr.head === :call && expr.args[1] === :~
            return Expr(:call, :__test_connect, lookup_tree(expr.args[2]), lookup_tree(expr.args[3]))
        elseif expr.head === :call && expr.args[1] === :e
            return Expr(:call, :TestActorErrorTest, length(expr.args) === 2 ? expr.args[2] : nothing)
        elseif expr.head === :vect
            tests  = []
            values = []

            for arg in expr.args
                process_arg_values!(values, tests, arg)
            end

            if length(tests) > 1
                error("Invalid usage of @ts macro: extra e and/or c markers in values array")
            end

            return Expr(:call, :TestActorStreamValuesTest, 1, -1, values, length(tests) === 1 ? Base.first(tests) : TestActorEmptyTest(), nothing)
        end

        error("Invalid usage of @ts macro")
    end

    function lookup_tree(expr::Int)
        return expr
    end

    function lookup_tree(symbol::Symbol)
        if symbol === :c
            return Expr(:call, :TestActorCompleteTest)
        end

        if symbol === :e
            return Expr(:call, :TestActorErrorTest, nothing)
        end

        error("Invalid usage of @ts macro")
    end

    return lookup_tree(expr)
end

macro ts()
    return :nothing
end

struct TestActorStreamIncorrectStreamValuesException <: Exception
    actual
    expected
end

function Base.show(io::IO, exception::TestActorStreamIncorrectStreamValuesException)
    print(io, "Incorrect values in the stream: expected -> $(exception.expected), actual -> $(exception.actual)")
end

struct TestActorStreamIncorrectStreamTimePassedException <: Exception
    actual
    expected
end

function Base.show(io::IO, exception::TestActorStreamIncorrectStreamTimePassedException)
    print(io, "Incorrect time passed in the stream: expected -> >$(exception.expected)ms, actual -> $(exception.actual)ms")
end

struct TestActorStreamSignificantDelayTimePassedException
    delay
end

function Base.show(io::IO, exception::TestActorStreamSignificantDelayTimePassedException)
    print(io, "Significant delay time passed between some emissions in the stream: ~$(exception.delay)ms")
end

struct TestActorStreamMissingExpectedErrorException <: Exception
    err
end

function Base.show(io::IO, exception::TestActorStreamMissingExpectedErrorException)
    print(io, "Stream did not send an error while expected to fail with: ", exception.err)
end

struct TestActorStreamIncorrectExpectedErrorException <: Exception
    actual
    expected
end

function Base.show(io::IO, exception::TestActorStreamIncorrectExpectedErrorException)
    print(io, "Stream sent an incorrect error: expected -> $(exception.expected), actual -> $(exception.actual)")
end

struct TestActorStreamMissingExpectedCompletionException <: Exception end

function Base.show(io::IO, exception::TestActorStreamMissingExpectedCompletionException)
    print(io, "Stream did not send a complete event while expected to do so")
end

struct TestOnSourceTimedOutException <: Exception end

function Base.show(io::IO, ::TestOnSourceTimedOutException)
    print(io, "TestOnSourceTimedOutException()")
end

struct TestActorStreamUncheckedData <: Exception
    actor :: TestActor
end

function Base.show(io::IO, exception::TestActorStreamUncheckedData)
    print(io, "Some data hasn't been checked, ensure test values contains this values: ", map(e -> e.data, filter(e -> e.is_checked, data(exception.actor))))
end

struct TestActorStreamUncheckedError <: Exception
    actor :: TestActor
end

function Base.show(io::IO, exception::TestActorStreamUncheckedError)
    print(io, "Stream sends error event, but it hasn't been checked. Ensure test values contains error marker 'e' with err = ", Base.first(errors(exception.actor)).err, " on a right time scale.")
end

struct TestActorStreamUncheckedCompletion <: Exception
    actor :: TestActor
end

function Base.show(io::IO, exception::TestActorStreamUncheckedCompletion)
    print(io, "Stream sends complete event, but it hasn't been checked. Ensure test values contains complete marker 'c' on a right time scale.")
end

struct TestActorStreamErrorDoubleCheckException <: Exception end

function Base.show(io::IO, exception::TestActorStreamErrorDoubleCheckException)
    print(io, "Error event has been checked twice, ensure there is no mistake in the test stream values")
end

struct TestActorStreamCompletionDoubleCheckException <: Exception end

function Base.show(io::IO, ::TestActorStreamCompletionDoubleCheckException)
    print(io, "Complete event has been checked twice, ensure there is no mistake in the test stream values")
end