using ColorTypes, FixedPointNumbers
using Test

@testset "ColorTypes" begin

@test isempty(detect_ambiguities(ColorTypes, Base, Core))

# Support pre- and post- julia #20288
tformat(x...) = join(string.(x), ", ")

@test ColorTypes.to_top(AGray32(.8)) == ColorTypes.Colorant{FixedPointNumbers.Normed{UInt8,8},2}
@test @inferred(eltype(Color{N0f8})) == N0f8
@test @inferred(eltype(RGB{Float32})) == Float32
@test @inferred(eltype(RGBA{Float64})) == Float64
# @test eltype(RGB) == TypeVar(:T, Fractional)
@inferred(eltype(RGB))      # just test that it doesn't error

@test length(RGB)    == 3
@test length(RGB1)   == 3
@test length(Gray)   == 1
@test length(ARGB)   == 4
@test length(RGB24)  == 3
@test length(ARGB32) == 4
@test length(AGray{Float32}) == 2

# coloralpha/alphacolor for `TransparentColor`s (issue #126)
@test @inferred(coloralpha(RGBA)) == RGBA
@test @inferred(coloralpha(ARGB)) == RGBA
@test @inferred(coloralpha(RGBA{Float32})) == RGBA
@test @inferred(coloralpha(ARGB{Float32})) == RGBA
@test @inferred(coloralpha(AGray)) == GrayA
@test @inferred(coloralpha(GrayA)) == GrayA
@test @inferred(coloralpha(AGray{Float32})) == GrayA
@test @inferred(coloralpha(GrayA{Float32})) == GrayA
@test_throws MethodError coloralpha(ARGB32)
@test @inferred(alphacolor(RGBA)) == ARGB
@test @inferred(alphacolor(ARGB)) == ARGB
@test @inferred(alphacolor(RGBA{Float32})) == ARGB
@test @inferred(alphacolor(ARGB{Float32})) == ARGB
@test @inferred(alphacolor(AGray)) == AGray
@test @inferred(alphacolor(GrayA)) == AGray
@test @inferred(alphacolor(AGray{Float32})) == AGray
@test @inferred(alphacolor(GrayA{Float32})) == AGray
@test @inferred(alphacolor(ARGB32)) == ARGB32

@test @inferred(color_type(RGB{N0f8})) == RGB{N0f8}
@test @inferred(color_type(RGB)) == RGB
@test @inferred(color_type(RGBA{Float32})) == RGB{Float32}
@test @inferred(color_type(GrayA{N0f8})) == Gray{N0f8}
@test @inferred(color_type(RGBA)) == RGB
@test @inferred(color_type(RGB24) ) == RGB24
@test @inferred(color_type(ARGB32)) == RGB24
@test @inferred(color_type(TransparentColor{RGB})) == RGB
@test @inferred(color_type(TransparentColor{RGB,Float64})) == RGB
@test @inferred(color_type(TransparentColor{RGB{Float64},Float64})) == RGB{Float64}
@test color_type(TransparentColor) <: Color
@test Color <: color_type(TransparentColor)
@test_throws MethodError color_type(Colorant{N0f8})

@test @inferred(base_color_type(RGBA{Float32})) == RGB
@test @inferred(base_color_type(ARGB{Float32})) == RGB
@test @inferred(base_color_type(BGR{N0f8})      ) == BGR
@test @inferred(base_color_type(HSV) ) == HSV
@test @inferred(base_color_type(HSVA)) == HSV
@test @inferred(base_color_type(TransparentColor{RGB{Float64},Float64})) == RGB

@test @inferred(base_colorant_type(RGBA{Float32})) == RGBA
@test @inferred(base_colorant_type(ARGB{Float32})) == ARGB
@test @inferred(base_colorant_type(BGR{N0f8})      ) == BGR
@test @inferred(base_colorant_type(HSV) ) == HSV
@test @inferred(base_colorant_type(HSVA)) == HSVA

@testset "floattype" begin
    @test @inferred(floattype(RGBA{Float32})) == RGBA{Float32}
    @test @inferred(floattype(BGR{N0f8})    ) == BGR{Float32}
    @test @inferred(floattype(Gray{N0f8})   ) == Gray{Float32}
    @test @inferred(floattype(N0f8)         ) == Float32
    @test @inferred(floattype(Bool)         ) == Float32
    @test @inferred(floattype(Float32)      ) == Float32
    @test @inferred(floattype(Float64)      ) == Float64

    @test @inferred(floattype(ARGB32)) == ARGB{Float32}
    @test @inferred(floattype(AGray32)) == AGray{Float32}
    @test @inferred(floattype(RGB24)) == RGB{Float32}
    @test @inferred(floattype(Gray24)) == Gray{Float32}
end

@test @inferred(ccolor(Colorant{N0f8,3}, BGR{N0f8})) == BGR{N0f8}

@test @inferred(ccolor(RGB{Float32}, HSV{Float32})) == RGB{Float32}
@test @inferred(ccolor(RGB{N0f8},      HSV{Float32})) == RGB{N0f8}
@test @inferred(ccolor(RGB,          HSV{Float32})) == RGB{Float32}
@test @inferred(ccolor(ARGB{Float32}, HSV{Float32})) == ARGB{Float32}
@test @inferred(ccolor(ARGB{N0f8},      HSV{Float32})) == ARGB{N0f8}
@test @inferred(ccolor(ARGB,          HSV{Float32})) == ARGB{Float32}

@test @inferred(ccolor(Gray{N0f8}, Bool)) === Gray{N0f8}
@test @inferred(ccolor(Gray,     Bool)) === Gray{Bool}
@test @inferred(ccolor(Gray,     Int))  === Gray{N0f8}
# This tests the same thing as the last, but in a user-observable way
let a = Array{Gray}(undef, 1)
    a[1] = Gray(0)
    a[1] = 1
    @test a[1] === Gray(1)
end

@test @inferred(ccolor(RGB,  RGB))  == RGB
@test @inferred(ccolor(Gray, Gray)) == Gray

# Traits for instances (and their constructors)
@test @inferred(eltype(RGB{N0f8}(1,0,0))) == N0f8
@test @inferred(eltype(RGB(1.0,0,0))) == Float64
@test @inferred(eltype(ARGB(1.0,0.8,0.6,0.4))) == Float64
@test @inferred(eltype(RGBA{Float32}(1.0,0.8,0.6,0.4))) == Float32
@test @inferred(eltype(RGB(0x01,0x00,0x00))) == N0f8

@test length(ARGB(1.0,0.8,0.6,0.4)) == 4

@test @inferred(color_type(RGB{N0f8}(1,0,0))) == RGB{N0f8}
@test @inferred(color_type(ARGB(1.0,0.8,0.6,0.4))) == RGB{Float64}
@test @inferred(color_type(RGBA{Float32}(1.0,0.8,0.6,0.4))) == RGB{Float32}

@test @inferred(base_color_type(RGB{N0f8}(1,0,0))) == RGB
@test @inferred(base_color_type(ARGB(1.0,0.8,0.6,0.4))) == RGB
@test @inferred(base_color_type(RGBA{Float32}(1.0,0.8,0.6,0.4))) == RGB

@test @inferred(base_colorant_type(RGB{N0f8}(1,0,0))) == RGB
@test @inferred(base_colorant_type(ARGB(1.0,0.8,0.6,0.4))) == ARGB
@test @inferred(base_colorant_type(RGBA{Float32}(1.0,0.8,0.6,0.4))) == RGBA

@test N0f8 <: ColorTypes.eltypes_supported(RGB(1,0,0))

# Constructors
for val in (0.2, 0.2f0, N0f8(0.2), N4f12(0.2), N0f16(0.2),
            Gray{N0f8}(0.2), Gray{N4f12}(0.2), Gray24(0.2))
    @test isa(Gray(val), Gray)
    @test Gray{N0f8}(val) === Gray{N0f8}(0.2)
    @test Gray{N0f16}(val) === Gray{N0f16}(0.2)
    @test Gray24(val) === Gray24(0.2)
    @test AGray32(val) === AGray32(0.2, 1)
    @test AGray32(val, 0.8) === AGray32(0.2, 0.8)
end
for val in (1.2, 1.2f0, N4f12(1.2), Gray{N4f12}(1.2), 2)
    !isa(val, Int) && @test isa(Gray(val), Gray)
    @test_throws ArgumentError Gray{N0f8}(val)
    @test_throws ArgumentError Gray{N0f16}(val)
    @test_throws ArgumentError Gray24(val) == Gray24(0.2)
    @test_throws ArgumentError GrayA{N0f8}(val)
    @test_throws ArgumentError AGray{N0f8}(val)
    @test_throws ArgumentError AGray32(val)
    @test_throws ArgumentError AGray32(val, 0.8)
end
@test eltype(Gray()) == N0f8
@test Gray(Gray()) == Gray()  # no StackOverflowError
@test eltype(broadcast(Gray, rand(5))) == Gray{Float64}
@test eltype(broadcast(Gray, rand(Float32,5))) == Gray{Float32}

for C in ColorTypes.parametric3
    @test eltype(C{Float32}) == Float32
    et = (C <: AbstractRGB) ? N0f8 : Float32
    @test eltype(C(1,0,0)) == et
    @test color_type(C(1,0,0)) == C{et}
    @test color_type(C) == C
    @test color_type(C{Float32}) == C{Float32}
    @test eltype(C{Float32}(1,0,0)) == Float32
    @test C(C()) == C()  # no StackOverflowError
end

# Specifically test the AbstractRGB types
# This checks that the constructor order is the same, even if the
# storage order is not
for C in filter(T -> T <: AbstractRGB, ColorTypes.color3types)
    c = C(1, 0.5, 0)
    C == RGB24 && continue
    @test red(c)   == c.r == 1
    @test green(c) == c.g == 0.5
    @test blue(c)  == c.b == 0
end

# #80
z = N0f8(0)
@test HSV(z, z, z) === HSV{Float32}(0, 0, 0)

# Check various input types and values
for C in filter(T -> T <: AbstractRGB, ColorTypes.color3types)
    for val1 in (0.2, 0.2f0, N0f8(0.2), N4f12(0.2), N0f16(0.2))
        for val2 in (0.2, 0.2f0, N0f8(0.2), N4f12(0.2), N0f16(0.2))
            c = C(val1,val2,val1)
            @test isa(c, C)
            @test isa(alphacolor(C)(val1,val2,val1), alphacolor(C))
            @test isa(alphacolor(C)(val1,val2,val1,0.2), alphacolor(C))
            @test alphacolor(C)(c) === alphacolor(C)(val1,val2,val1,convert(eltype(c), 1))
            @test alphacolor(C)(c, 0.2) === alphacolor(C)(val1,val2,val1,convert(eltype(c), 0.2))
            @test alphacolor(c, 0.2) === alphacolor(C)(val1,val2,val1,convert(eltype(c), 0.2))
            if C !== RGB24
                @test isa(coloralpha(C)(val1,val2,val1), coloralpha(C))
                @test isa(coloralpha(C)(val1,val2,val1,0.2), coloralpha(C))
                @test C{N0f8}(val1,val2,val1) === C{N0f8}(0.2,0.2,0.2)
                @test C{N0f16}(val1,val2,val1) === C{N0f16}(0.2,0.2,0.2)
                @test alphacolor(C){N0f8}(val1,val2,val1) === alphacolor(C){N0f8}(0.2,0.2,0.2,1)
                @test alphacolor(C){N0f8}(val1,val2,val1,0.2) === alphacolor(C){N0f8}(0.2,0.2,0.2,0.2)
                @test coloralpha(C){N0f8}(val1,val2,val1) === coloralpha(C){N0f8}(0.2,0.2,0.2,1)
                @test coloralpha(C){N0f8}(val1,val2,val1,0.2) === coloralpha(C){N0f8}(0.2,0.2,0.2,0.2)
                @test coloralpha(C)(c) === coloralpha(C){eltype(c)}(val1,val2,val1,1)
                @test coloralpha(C)(c, 0.2) === coloralpha(C)(val1,val2,val1,convert(eltype(c), 0.2))
                @test coloralpha(c, 0.2) === coloralpha(C)(val1,val2,val1,convert(eltype(c), 0.2))
            end
        end
    end
    @test isa(C(1,0,1), C)
    @test C() === C(0,0,0)
    @test isa(alphacolor(C)(1,0,1), alphacolor(C))
    @test alphacolor(C)() === alphacolor(C)(0,0,0,1)
    if C != RGB24
        @test C(1,0,1) === C{N0f8}(1,0,1)
        @test alphacolor(C)(1,0,1) === alphacolor(C){N0f8}(1,0,1,1)
        @test alphacolor(C)(1,0,1,0.2) === alphacolor(C){Float64}(1,0,1,0.2)
        @test coloralpha(C)(1,0,1) === coloralpha(C){N0f8}(1,0,1,1)
        @test coloralpha(C)(1,0,1,0.2) === coloralpha(C){Float64}(1,0,1,0.2)
        @test coloralpha(C)() === coloralpha(C)(0,0,0,1)
    end
    for val in (1.2, 1.2f0, N4f12(1.2), 2)
        if C !== RGB24
            @test_throws ArgumentError C{N0f8}(val,val,val)
            @test_throws ArgumentError C{N0f16}(val,val,val)
            @test_throws ArgumentError alphacolor(C){N0f8}(val,val,val)
            @test_throws ArgumentError coloralpha(C){N0f8}(val,val,val)
            if val != 2
                @test isa(C(val,val,val), C)
                c = C(val,val,val)
                @test_throws ArgumentError alphacolor(C){N0f8}(c)
                @test_throws ArgumentError coloralpha(C){N0f8}(c)
                if C != RGB1 && C != RGB4
                    @test_throws ArgumentError alphacolor(C){N0f8}(c, 0.2)
                    @test_throws ArgumentError coloralpha(C){N0f8}(c, 0.2)
                end
            end
        end
        if C == RGB24 || isa(val, Int)
            @test_throws ArgumentError C(val,val,val)
            @test_throws ArgumentError alphacolor(C)(val,val,val)
        end
    end
end

ret = @test_throws ArgumentError RGB(255, 17, 48)
@test occursin(tformat(255,17,48), ret.value.msg)
@test occursin("0-255", ret.value.msg)
ret = @test_throws ArgumentError RGB(256, 17, 48)
@test occursin(tformat(256,17,48), ret.value.msg)
@test !occursin("0-255", ret.value.msg)

@testset "Test some Gray stuff" begin
    c = Gray(0.8)
    @test gray(c) == real(c) == 0.8
    @test gray(0.8) == 0.8
    c = convert(Gray, 0.8)
    @test c === Gray{Float64}(0.8)

    ac = convert(AGray, c)
    @test ac === AGray{Float64}(0.8, 1.0)
    ac = AGray(c)
    @test ac === AGray{Float64}(0.8, 1.0)
    ac = AGray{Float64}(c)
    @test ac === AGray{Float64}(0.8, 1.0)
    ca = GrayA{Float64}(ac)
    @test ca === GrayA{Float64}(0.8, 1.0)

    c = AGray(0.8)
    @test gray(c) == 0.8
    @test color(c) == Gray(0.8)

    c = convert(Gray, true)
    @test c === Gray{Bool}(true)
    @test gray(c) === true
    @test gray(false) === false
end

# Transparency
@test alphacolor(Gray24(.2), .8) == AGray32(.2,.8)
@test alphacolor(RGB24(1,0,0), .8) == ARGB32(1,0,0,.8)
@test alphacolor(RGB(1,0,0), .8) == ARGB{N0f8}(1,0,0,.8)
@test coloralpha(RGB(1,0,0), .8) == RGBA{N0f8}(1,0,0,.8)
@test alphacolor(RGBA(1,0,0,.8)) == ARGB{Float64}(1,0,0,.8)
@test coloralpha(ARGB(1,0,0,.8)) == RGBA{Float64}(1,0,0,.8)
@test alphacolor(RGBA(1,0,0,.8)) == ARGB{Float64}(1,0,0,.8)
@test coloralpha(ARGB(1,0,0,.8)) == RGBA{Float64}(1,0,0,.8)
for C in setdiff(ColorTypes.parametric3, [RGB1,RGB4])
    for A in (alphacolor(C), coloralpha(C))
        @test eltype(A{Float32}) == Float32
        @test color_type(A) == C
        @test color_type(A{Float32}) == C{Float32}
        @test eltype(A(1,0.8,0.6,0.4)) == Float64
        c = A{Float64}(1,0.8,0.6,0.4)
        @test color_type(c) == C{Float64}
        cc = color(c)
        @test cc == C{Float64}(1,0.8,0.6)
        @test A(cc) == A{Float64}(1,0.8,0.6,1)
        @test A(cc, 0.4)  == c
        @test A(cc, 0x01) == A{Float64}(1,0.8,0.6,1)
        @test A{Float32}(cc, 0x01) == A{Float32}(1,0.8,0.6,1)
        @test C(c         ) == C{Float64}(1,0.8,0.6)
        @test C{Float32}(c) == C{Float32}(1,0.8,0.6)
        @test convert(A, cc) == A{Float64}(1,0.8,0.6,1)
        @test A(cc) === A{Float64}(1,0.8,0.6,1)
        @test A{Float64}(cc) === A{Float64}(1,0.8,0.6,1)
        @test convert(A, cc, 0.4)  == c
        @test convert(A, cc, 0x01) == A{Float64}(1,0.8,0.6,1)
        @test convert(A{Float32}, cc, 0x01) == A{Float32}(1,0.8,0.6,1)
        @test convert(C,          c) == C{Float64}(1,0.8,0.6)
        @test convert(C{Float32}, c) == C{Float32}(1,0.8,0.6)
        @test C{Float32}(c) === C{Float32}(1,0.8,0.6)
    end
    AC, CA = alphacolor(C), coloralpha(C)
    @test AC(CA{Float64}(1,0.8,0.6,0.4)) == AC{Float64}(1,0.8,0.6,0.4)
    @test AC{Float64}(CA{Float64}(1,0.8,0.6,0.4)) == AC{Float64}(1,0.8,0.6,0.4)
    @test CA(AC{Float64}(1,0.8,0.6,0.4)) == CA{Float64}(1,0.8,0.6,0.4)
    @test CA{Float64}(AC{Float64}(1,0.8,0.6,0.4)) == CA{Float64}(1,0.8,0.6,0.4)
end
ac = reinterpret(ARGB32, rand(UInt32))
@test convert(ARGB32, ac) == ac
c = convert(RGB24, ac)
@test convert(RGB24, c) == c

crgb   = convert(RGB, c)
acargb = convert(ARGB, ac)
@test convert(Colorant,       crgb) === crgb
@test convert(Colorant{N0f8},   crgb) === crgb
@test convert(Colorant{Float32},       crgb) === convert(RGB{Float32}, crgb)
@test convert(Color,          crgb) === crgb
@test convert(Color{N0f8},      crgb) === crgb
@test convert(Color{Float32}, crgb) === convert(RGB{Float32}, crgb)
@test_throws ErrorException convert(TransparentColor, crgb)
@test_throws ErrorException convert(TransparentColor{RGB{N0f8}}, crgb)
@test_throws ErrorException convert(TransparentColor{RGB{N0f8},N0f8}, crgb)
@test_throws ErrorException convert(TransparentColor{RGB{N0f8},N0f8,2}, crgb)
@test convert(AlphaColor, crgb) === alphacolor(crgb)
@test convert(AlphaColor{RGB{N0f8}}, crgb) === alphacolor(crgb)
@test convert(AlphaColor{RGB{N0f8},N0f8}, crgb) === alphacolor(crgb)
@test convert(AlphaColor{RGB{N0f8},N0f8,4}, crgb) === alphacolor(crgb)
@test convert(ColorAlpha, crgb) === coloralpha(crgb)
@test convert(ColorAlpha{RGB{N0f8}}, crgb) === coloralpha(crgb)
@test convert(ColorAlpha{RGB{N0f8},N0f8}, crgb) === coloralpha(crgb)
@test convert(ColorAlpha{RGB{N0f8},N0f8,4}, crgb) === coloralpha(crgb)

@test convert(Colorant, acargb) === acargb
@test convert(Colorant{N0f8},   acargb) === acargb
@test convert(Colorant{N0f8,3}, acargb) === crgb
@test convert(TransparentColor, acargb) == acargb
@test convert(Color, acargb) == crgb
@test convert(AlphaColor, acargb) === acargb # issue #126
@test convert(ColorAlpha, acargb) == coloralpha(acargb) # issue #126

h = N0f8(0.5)
@test Gray24(0.5) == Gray24(h)
@test convert(Gray24, 0.5) == Gray24(h)
@test convert(AGray, Gray24(h)) === AGray{N0f8}(h, 1)
@test convert(AGray, Gray24(h), 0.8)  === AGray{N0f8}(h, 0.8)
@test convert(AGray, AGray32(h, 0.8)) === AGray{N0f8}(h, 0.8)
@test AGray32(0.5) == AGray32(h, 1)
@test convert(AGray32, 0.5, 0.8) == AGray32(h, N0f8(0.8))
@test RGB24(0.5) == RGB24(h, h, h)
@test convert(RGB24, 0.5) == RGB24(h, h, h)
@test ARGB32(0.5) == ARGB32(h, h, h, 1)

@test red(c)   == red(ac)
@test green(c) == green(ac)
@test blue(c)  == blue(ac)
ac2 = convert(ARGB32, c)
@test reinterpret(UInt32, ac2) == (c.color | 0xff000000)
@test color(c) == c
@test color(ac) == c
@test alpha(c) == N0f8(1)
@test alpha(ac) == N0f8(ac.color>>24, 0)
@test alpha(ac2) == N0f8(1)
@test reinterpret(UInt32, reinterpret(RGB24,  0xff020304)) == 0xff020304
@test reinterpret(UInt32, reinterpret(ARGB32, 0x01020304)) == 0x01020304
ac3 = convert(RGBA, ac)
@test convert(RGB24, ac3) == c
ac4 = AGray32(.2,.8)
@test alpha(ac4) == .8
@test gray(ac4) == .2

for C in filter(T -> T <: AbstractRGB, ColorTypes.color3types)
    rgb = convert(C, c)
    C == RGB24 && continue
    @test ccolor(Gray24, C) == Gray24
    @test ccolor(AGray32, C) == AGray32
    @test convert(AbstractRGB, c) == c
    @test convert(AbstractRGB{Float64}, rgb) === convert(C{Float64}, c)
    argb = convert(alphacolor(C), ac)
    rgba = convert(coloralpha(C), ac)
    @test rgb.r == red(c)
    @test rgb.g == green(c)
    @test rgb.b == blue(c)
    @test argb.alpha == alpha(ac)
    @test argb.r == red(ac)
    @test argb.g == green(ac)
    @test argb.b == blue(ac)
    @test rgba.alpha == alpha(ac)
    @test rgba.r == red(ac)
    @test rgba.g == green(ac)
    @test rgba.b == blue(ac)
end

@test_throws ErrorException convert(HSV, RGB(1,0,1))
@test_throws ErrorException convert(AHSV, RGB(1,0,1), 0.5)

@test convert(Float64, Gray(.3)) === .3
@test Float64(Gray(0.3)) === 0.3
@test float(Gray(0.3))   === 0.3
x = N0f8(0.3)
@test convert(N0f8, Gray24(0.3)) === x
@test convert(GrayA{N0f8}, .2) == GrayA{N0f8}(.2)
@test convert(AGray{N0f8}, .2) == AGray{N0f8}(.2)
@test Gray{N0f8}(0.37).val           == N0f8(0.37)
@test convert(Gray{N0f8}, 0.37).val  == N0f8(0.37)
@test reinterpret(UInt32, Gray24(reinterpret(N0f8, 0x0d)))           == 0x000d0d0d
@test reinterpret(UInt32, convert(Gray24, reinterpret(N0f8, 0x0d)))  == 0x000d0d0d
@test reinterpret(UInt32, AGray32(reinterpret(N0f8, 0x0d)))          == 0xff0d0d0d
@test reinterpret(UInt32, convert(AGray32, reinterpret(N0f8, 0x0d))) == 0xff0d0d0d
@test reinterpret(UInt32, AGray32(reinterpret(N0f8, 0x0d), reinterpret(N0f8, 0x80))) == 0x800d0d0d
@test convert(Gray{N0f16}, Gray24(reinterpret(N0f8, 0x0d))) == Gray{N0f16}(0.05098)

@test promote(Gray{N0f8}(0.2), Gray24(0.3)) === (Gray{N0f8}(0.2), Gray{N0f8}(0.3))
@test promote(Gray(0.2f0), Gray24(0.3)) === (Gray{Float32}(0.2), Gray{Float32}(N0f8(0.3)))
@test promote(RGB{N0f8}(0.2,0.3,0.4), RGB24(0.3,0.8,0.1)) === (RGB{N0f8}(0.2,0.3,0.4), RGB{N0f8}(0.3,0.8,0.1))
@test promote(RGB{Float32}(0.2,0.3,0.4), RGB24(0.3,0.8,0.1)) === (RGB{Float32}(0.2,0.3,0.4), RGB{Float32}(N0f8(0.3),N0f8(0.8),N0f8(0.1)))

iob = IOBuffer()
cf = RGB{Float32}(0.32218,0.14983,0.87819)
c  = convert(RGB{N0f8}, cf)
show(iob, c)
@test String(take!(iob)) == "RGB{N0f8}(0.322,0.149,0.878)"
c = RGB{N0f16}(0.32218,0.14983,0.87819)
show(iob, c)
@test String(take!(iob)) == "RGB{N0f16}(0.32218,0.14983,0.87819)"
c = RGBA{N0f8}(0.32218,0.14983,0.87819,0.99241)
show(iob, c)
@test String(take!(iob)) == "RGBA{N0f8}(0.322,0.149,0.878,0.992)"
show(IOContext(iob, :compact => true), c)
@test String(take!(iob)) == "RGBA{N0f8}(0.322,0.149,0.878,0.992)"
show(iob, cf)
@test String(take!(iob)) == "RGB{Float32}(0.32218f0,0.14983f0,0.87819f0)"
show(IOContext(iob, :compact => true), cf)
@test String(take!(iob)) == "RGB{Float32}(0.32218,0.14983,0.87819)"
show(iob, Gray24(0.4))
@test String(take!(iob)) == "Gray24(0.4N0f8)"
show(iob, RGB24(0.4,0.2,0.8))
@test String(take!(iob)) == "RGB24(0.4N0f8,0.2N0f8,0.8N0f8)"
show(iob, ARGB32(0.4,0.2,0.8,1.0))
@test String(take!(iob)) == "ARGB32(0.4N0f8,0.2N0f8,0.8N0f8,1.0N0f8)"
summary(iob, Gray{N0f8}[0.2, 0.4, 0.6])
@test String(take!(iob)) == "3-element Array{Gray{N0f8},1} with eltype Gray{Normed{UInt8,8}}"
@test ColorTypes.colorant_string(Union{})   == "Union{}"
@test ColorTypes.colorant_string(RGB{N0f8}) == "RGB"
@test ColorTypes.colorant_string(RGB24)     == "RGB24"
@test ColorTypes.colorant_string(ARGB32)    == "ARGB32"
@test ColorTypes.colorant_string(Gray24)    == "Gray24"
@test ColorTypes.colorant_string(AGray32)   == "AGray32"
@test ColorTypes.colorant_string_with_eltype(Union{})   == "Union{}"
@test ColorTypes.colorant_string_with_eltype(RGB{N0f8}) == "RGB{N0f8}"
@test ColorTypes.colorant_string_with_eltype(RGB24)     == "RGB24"
@test ColorTypes.colorant_string_with_eltype(ARGB32)    == "ARGB32"
@test ColorTypes.colorant_string_with_eltype(Gray24)    == "Gray24"
@test ColorTypes.colorant_string_with_eltype(AGray32)   == "AGray32"

@test oneunit(Gray{N0f8}) == Gray{N0f8}(1)
@test zero(Gray{N0f8}) == Gray{N0f8}(0)

c = Gray(0.8)
@test c == 0.8
show(iob, c)
@test String(take!(iob)) == "Gray{Float64}(0.8)"
show(iob, AGray(0.8))
@test String(take!(iob)) == "AGray{Float64}(0.8,1.0)"
show(iob, AGray32(0.8))
@test String(take!(iob)) == "AGray32{N0f8}(0.8,1.0)"

# if the test below fails, please extend the list of types at the call to
# make_alpha in types.jl (this is the price of making that list explicit)
@test Set(ColorTypes.ctypes) ==
Set([DIN99d, DIN99o, DIN99, HSI, HSL, HSV, LCHab, LCHuv,
     LMS, Lab, Luv, XYZ, YCbCr, YIQ, xyY, BGR, RGB, Gray])

## operations
for T in (Gray{N0f8}, Gray{N2f6}, Gray{N0f16}, Gray{N2f14}, Gray{N0f32}, Gray{N2f30},
          Gray{Float16}, Gray{Float32}, Gray{Float64},
          RGB{N0f8}, RGB{N2f6}, RGB{N0f16}, RGB{N2f14}, RGB{N0f32}, RGB{N2f30},
          RGB{Float16}, RGB{Float32}, RGB{Float64},
          AGray{Float32}, GrayA{Float64},
          RGBA{Float32}, ARGB{N0f16}, RGB1{N0f8}, RGB4{Float64},
          BGR{Float16}, ABGR{N0f32}, BGRA{N2f14},
          Gray, AGray, GrayA,
          RGB, ARGB, RGBA, BGR, ABGR, BGRA, RGB1, RGB4,
          HSV, HSL, Lab, LCHab, YIQ)
    a = rand(T)
    @test all(x->x[2]<=getfield(a,x[1])<=x[3],
                zip(ColorTypes.colorfields(T),gamutmin(T),gamutmax(T)))
    @test isa(a, T)
    a = rand(T, (3, 5))
    if isconcretetype(T)
        @test isa(a, Array{T,2})
    end
    for el in a
        @test all(x->x[2]<=getfield(el,x[1])<=x[3],
                    zip(ColorTypes.colorfields(T),gamutmin(T),gamutmax(T)))
    end
    @test eltype(a) <: T
    @test size(a) == (3,5)
    ap = a'
    @test ap[1,1] == a[1,1]
end
for T in (Gray24, AGray32)
    a = rand(T)
    b = a.color
    @test b&0xff == (b>>8)&0xff == (b>>16)&0xff
    @test isa(a, T)
    a = rand(T,3,5)
    for el in a
        b = el.color
        @test b&0xff == (b>>8)&0xff == (b>>16)&0xff
    end
    @test eltype(a) <: T
    @test size(a) == (3,5)
end

@test eltype(broadcast(RGB, [BGR(1,0,0)])) == RGB{N0f8}
addred(x1::AbstractRGB, x2::AbstractRGB) = red(x1) + red(x2)
@test addred.([RGB(1,0,0)], RGB(1.0,0,0)) == [2]

# colorfields
@test ColorTypes.colorfields(AGray32(.2)) == (:color,:alpha)
@test ColorTypes.colorfields(Gray) == (:val,)
@test ColorTypes.colorfields(RGB1) == (:r, :g, :b)
@test ColorTypes.colorfields(RGB4) == (:r, :g, :b)
@test ColorTypes.colorfields(BGR) == (:r, :g, :b)

# UInt32 comparison
@test reinterpret(UInt32, Gray24()) == 0x00000000
@test reinterpret(UInt32, Gray24(.2)) == 0x00333333
@test reinterpret(UInt32, Gray24(reinterpret(N0f8, 0x23))) == 0x00232323
@test reinterpret(UInt32, AGray32()) == 0xff000000
@test reinterpret(UInt32, AGray32(.2)) == 0xff333333
@test reinterpret(UInt32, convert(AGray32, .2, 0.)) == 0x00333333
@test reinterpret(UInt32, AGray32(reinterpret(N0f8, 0x23))) == 0xff232323
@test reinterpret(UInt32, RGB24()) == 0x00000000
@test reinterpret(UInt32, ARGB32()) == 0xff000000
@test reinterpret(UInt32, ARGB32(1,.2,.3)) == 0xffff334c

@test @inferred(mapc(sqrt, Gray{N0f8}(0.04))) == Gray(sqrt(N0f8(0.04)))
@test @inferred(mapc(sqrt, AGray{N0f8}(0.04, 0.4))) == AGray(sqrt(N0f8(0.04)), sqrt(N0f8(0.4)))
@test @inferred(mapc(sqrt, GrayA{N0f8}(0.04, 0.4))) == GrayA(sqrt(N0f8(0.04)), sqrt(N0f8(0.4)))
@test @inferred(mapc(x->2x, RGB{N0f8}(0.04,0.2,0.3))) == RGB(map(x->2*N0f8(x), (0.04,0.2,0.3))...)
@test @inferred(mapc(sqrt, RGBA{N0f8}(0.04,0.2,0.3,0.7))) == RGBA(map(x->sqrt(N0f8(x)), (0.04,0.2,0.3,0.7))...)
@test @inferred(mapc(x->1.5f0x, RGBA{N0f8}(0.04,0.2,0.3,0.4))) == RGBA(map(x->1.5f0*N0f8(x), (0.04,0.2,0.3,0.4))...)

@test @inferred(mapc(max, Gray{N0f8}(0.2), Gray{N0f8}(0.3))) == Gray{N0f8}(0.3)
@test @inferred(mapc(-, AGray{Float32}(0.3), AGray{Float32}(0.2))) == AGray{Float32}(0.3f0-0.2f0,0.0)
@test @inferred(mapc(min, RGB{N0f8}(0.2,0.8,0.7), RGB{N0f8}(0.5,0.2,0.99))) == RGB{N0f8}(0.2,0.2,0.7)
@test @inferred(mapc(+, RGBA{N0f8}(0.2,0.8,0.7,0.3), RGBA{Float32}(0.5,0.2,0.99,0.5))) == RGBA(0.5f0+N0f8(0.2),0.2f0+N0f8(0.8),0.99f0+N0f8(0.7),0.5f0+N0f8(0.3))
@test @inferred(mapc(+, HSVA(0.1,0.8,0.3,0.5), HSVA(0.5,0.5,0.5,0.3))) == HSVA(0.1+0.5,0.8+0.5,0.3+0.5,0.5+0.3)
@test_throws ArgumentError mapc(min, RGB{N0f8}(0.2,0.8,0.7), BGR{N0f8}(0.5,0.2,0.99))
@test @inferred(mapc(abs, -2)) == 2

@test @inferred(reducec(+, 0.0, Gray(0.3))) === 0.3
@test @inferred(reducec(+, 1.0, Gray(0.3))) === 1.3
@test @inferred(reducec(+, 0, Gray(0.3))) === 0.3
@test @inferred(reducec(+, 0.0, AGray(0.3, 0.8))) === 0.3 + 0.8
@test @inferred(reducec(+, 0.0, RGB(0.3, 0.8, 0.5))) === (0.3 + 0.8) + 0.5
@test @inferred(reducec(+, 0.0, RGBA(0.3, 0.8, 0.5, 0.7))) === ((0.3 + 0.8) + 0.5) + 0.7
@test @inferred(reducec(&, true, Gray(true)))
@test !(@inferred(reducec(&, false, Gray(true))))
@test !(@inferred(reducec(&, true, Gray(false))))

@test @inferred(reducec(+, 0.0, 0.3)) === 0.3
@test @inferred(reducec(+, 0, 0.3)) === 0.3
@test @inferred(reducec(&, true, true))
@test !(@inferred(reducec(&, false, true)))
@test !(@inferred(reducec(&, true, false)))

@test @inferred(mapreducec(x->x^2, +, 0.0, Gray(0.3))) === 0.3^2
@test @inferred(mapreducec(x->x^2, +, 1.0, Gray(0.3))) === 1 + 0.3^2
@test @inferred(mapreducec(x->x^2, +, 0, Gray(0.3))) === 0.3^2
@test @inferred(mapreducec(x->x^2, +, 0.0, AGray(0.3, 0.8))) === 0.3^2 + 0.8^2
@test @inferred(mapreducec(x->x^2, +, 0.0, RGB(0.3, 0.8, 0.5))) === (0.3^2 + 0.8^2) + 0.5^2
@test @inferred(mapreducec(x->x^2, +, 0.0, RGBA(0.3, 0.8, 0.5, 0.7))) === ((0.3^2 + 0.8^2) + 0.5^2) + 0.7^2
@test !(@inferred(mapreducec(x->!x, &, true, Gray(true))))
@test !(@inferred(mapreducec(x->!x, &, false, Gray(true))))
@test @inferred(mapreducec(x->!x, &, true, Gray(false)))
@test !@inferred(mapreducec(x->!x, &, false, Gray(false)))

@test @inferred(mapreducec(x->x^2, +, 0.0, 0.3)) === 0.3^2
@test @inferred(mapreducec(x->x^2, +, 1.0, 0.3)) === 1 + 0.3^2
@test @inferred(mapreducec(x->x^2, +, 0, 0.3)) === 0.3^2
@test !(@inferred(mapreducec(x->!x, &, true, true)))
@test !(@inferred(mapreducec(x->!x, &, false, true)))
@test @inferred(mapreducec(x->!x, &, true, false))
@test !@inferred(mapreducec(x->!x, &, false, false))

@test Gray(0.8) ≈ Gray(0.8 + eps())
@test Gray(0.8) ≈ 0.8 + eps()
@test 0.8 + eps() ≈ Gray(0.8)
@test GrayA(0.8, 0.4) ≈ GrayA(0.8 + eps(), 0.4)
@test RGB(0.2, 0.8, 0.4) ≈ RGB(0.2, 0.8 + eps(), 0.4)
@test RGBA(0.2, 0.8, 0.4, 0.2) ≈ RGBA(0.2, 0.8 + eps(), 0.4, 0.2 - eps())
@test !(Gray(0.8) ≈ Gray(0.6))
@test !(RGB(0.2, 0.8, 0.4) ≈ RGB(0.2, 0.8 + eps(), 0.5))
@test Gray(0.8f0) ≈ Gray(Float64(0.8f0 + eps(0.8f0)))
c = RGB{N0f8}(0.2, 0.8, 0.4)
c1 = mapc(Float32, c)
@test c == c1 && c ≈ c1
c2 = RGB(red(c1), green(c1)-0.1, blue(c1))
@test c ≈ c2 atol=0.11
@test !isapprox(c, c2; atol=0.09)
@test c ≈ convert(RGB4, c)
@test !(c ≈ HSV{Float32}(140.0f0,0.75f0,0.8f0))  # the latter comes from convert when using Colors
@test Gray(0.8N0f8) == Gray24(0.8) && Gray(0.8N0f8) ≈ Gray24(0.8)

# issue #52
@test AGray{BigFloat}(0.5,0.25) == AGray{BigFloat}(0.5,0.25)
@test RGBA{BigFloat}(0.5, 0.25, 0.5, 0.5) == RGBA{BigFloat}(0.5, 0.25, 0.5, 0.5)

for (a, b) in ((Gray(1.0), Gray(1)),
               (GrayA(0.8, 0.6), AGray(0.8, 0.6)),
               (RGB(1, 0.5, 0), BGR(1, 0.5, 0)),
               (RGBA(1, 0.5, 0, 0.8), ABGR(1, 0.5, 0, 0.8)))
    local a, b
    @test a == b
    @test hash(a) == hash(b)
end
for (a, b) in ((RGB(1, 0.5, 0), RGBA(1, 0.5, 0, 0.9)),)
    local a, b
    @test a != b
    @test hash(a) != hash(b)
end
# It's not obvious whether we want these to compare as equal, but
# whatever happens, you want hashing and equality-testing to yield the
# same result
for (a, b) in ((RGB(1, 0.5, 0), RGBA(1, 0.5, 0, 1)),)
    local a, b
    @test (a == b) == (hash(a) == hash(b))
end

### Prevent ambiguous definitions

# Certain types, like Gray24, reinterpret a UInt32 as having a
# particular color meaning. The problem with defining `convert`
# methods is that a UInt32 has a value, e.g., 0 = black and 1 = white.
# Users who want to interpret a UInt32 as a bit pattern should
# explicitly use `reinterpret`.
@testset "bit pattern ambiguities" begin
    @test_throws MethodError convert(UInt32, RGB24(1,1,1))
    @test_throws MethodError convert(UInt32, ARGB32(1,1,1,0))
    @test convert(UInt32, Gray24(1)) == 1
    @test_throws InexactError convert(UInt32, Gray24(0.5))
    @test_throws MethodError convert(UInt32, AGray32(1,0))
    @test !(RGB24(1,1,1) == 0x00ffffff)
    @test !(ARGB32(1,1,1,0) == 0x00ffffff)
    @test Gray24(1) == 1
    @test !(Gray24(1) == 0x00ffffff)
    @test !(AGray32(1,0) == 0x00ffffff)
    @test_throws ArgumentError RGB24(0x00ffffff)
    @test_throws ArgumentError ARGB32(0x00ffffff)
    @test_throws ArgumentError Gray24(0x00ffffff)
    @test_throws ArgumentError AGray32(0x00ffffff)
    @test_throws ArgumentError RGB24[0x00000000,0x00808080]
    @test_throws ArgumentError RGB24[0x00000000,0x00808080,0x00ffffff]
    @test_throws ArgumentError RGB24[0x00000000,0x00808080,0x00ffffff,0x000000ff]
end

### Prevent future commits from unexporting abstract types
@testset "abstract type exports" begin
    dispatcher(::AbstractGray) = 1
    dispatcher(::Color3) = 2
    dispatcher(::TransparentGray) = 3
    dispatcher(Transparent3) = 4
    dispatcher(::TransparentRGB) = 5
    normeddispatcher(::ColorantNormed) = true
    normeddispatcher(::Colorant) = false

    @test dispatcher(Gray(0.2)) == 1
    @test dispatcher(RGB(1.0,1.0,1.0)) == 2
    @test dispatcher(AGray(0.2,0.5)) == 3
    @test dispatcher(alphacolor(rand(HSV))) == 4
    @test dispatcher(ARGB(0.5,1.0,1.0,1.0)) == 5
    @test normeddispatcher(RGB(1.0,1.0,1.0)) == false
    @test normeddispatcher(RGB{N0f8}(1.0,1.0,1.0)) == true
end

@testset "color construction from grayscale" begin
    @test RGB(Gray(0.2), 0.3, 0.4) == RGB(0.2, 0.3, 0.4)
    @test RGB(0.2, Gray(0.3), 0.4) == RGB(0.2, 0.3, 0.4)
    @test RGB(0.2, 0.3, Gray(0.4)) == RGB(0.2, 0.3, 0.4)
    @test RGB(Gray(0.2), Gray(0.3), Gray(0.4)) == RGB(0.2, 0.3, 0.4)
end

end # ColorTypes

nothing


import Adapt

"""
    struct BoundaryCondition{C<:BCType, T}

Container for boundary conditions.
"""
struct BoundaryCondition{C<:BCType, T}
    condition :: T
end

"""
    BoundaryCondition(BC, condition)

Construct a boundary condition of type `BC` with a number or array as a `condition`.

Boundary condition types include `Periodic`, `Flux`, `Value`, `Gradient`, and `NormalFlow`.
"""
BoundaryCondition(BC, condition) = BoundaryCondition{BC, typeof(condition)}(condition)

"""
    BoundaryCondition(BC, condition::Function; parameters=nothing, discrete_form=false)

Construct a boundary condition of type `BC` with a function boundary `condition`.

By default, the function boudnary `condition` is assumed to have the 'continuous form'
`condition(ξ, η, t)`, where `t` is time and `ξ` and `η` vary along the boundary.
In particular:

- On `x`-boundaries, `condition(y, z, t)`.
- On `y`-boundaries, `condition(x, z, t)`.
- On `z`-boundaries, `condition(x, y, t)`.

If `parameters` is not `nothing`, then function boundary conditions have the form
`func(ξ, η, t, parameters)`, where `ξ` and `η` are spatial coordinates varying along
the boundary as explained above.

If `discrete_form=true`, the function `condition` is assumed to have the "discrete form",

    `condition(i, j, grid, clock, model_fields)`,

where `i`, and `j` are indices that vary along the boundary. If `discrete_form=true` and
`parameters` is not `nothing`, the function `condition` is called with

    `condition(i, j, grid, clock, model_fields, parameters)`.
"""
function BoundaryCondition(TBC, condition::Function;
                           parameters = nothing,
                           discrete_form = false,
                           field_dependencies=())

    if discrete_form
        field_dependencies != () && error("Cannot set `field_dependencies` when `discrete_form=true`!")
        condition = DiscreteBoundaryFunction(condition, parameters)
    else
        # Note that the boundary :x and location Center, Center are in general incorrect.
        # These are corrected in the FieldBoundaryConditions constructor.
        condition = ContinuousBoundaryFunction(condition, parameters, field_dependencies)
    end

    return BoundaryCondition{TBC, typeof(condition)}(condition)
end

bctype(bc::BoundaryCondition{C}) where C = C

# Adapt boundary condition struct to be GPU friendly and passable to GPU kernels.
Adapt.adapt_structure(to, b::BoundaryCondition{C, A}) where {C<:BCType, A<:AbstractArray} =
    BoundaryCondition(C, Adapt.adapt(to, parent(b.condition)))

#####
##### Some abbreviations to make life easier.
#####

# These type aliases make dispatching on BCs easier (not exported).
const BC   = BoundaryCondition
const FBC  = BoundaryCondition{<:Flux}
const PBC  = BoundaryCondition{<:Periodic}
const NFBC = BoundaryCondition{<:NormalFlow}
const VBC  = BoundaryCondition{<:Value}
const GBC  = BoundaryCondition{<:Gradient}
const ZFBC = BoundaryCondition{Flux, Nothing} # "zero" flux

# More readable BC constructors for the public API.
    PeriodicBoundaryCondition() = BoundaryCondition(Periodic,   nothing)
      NoFluxBoundaryCondition() = BoundaryCondition(Flux,       nothing)
ImpenetrableBoundaryCondition() = BoundaryCondition(NormalFlow, nothing)

      FluxBoundaryCondition(val; kwargs...) = BoundaryCondition(Flux, val; kwargs...)
     ValueBoundaryCondition(val; kwargs...) = BoundaryCondition(Value, val; kwargs...)
  GradientBoundaryCondition(val; kwargs...) = BoundaryCondition(Gradient, val; kwargs...)
NormalFlowBoundaryCondition(val; kwargs...) = BoundaryCondition(NormalFlow, val; kwargs...)

# Support for various types of boundary conditions
@inline getbc(bc::BC{<:NormalFlow, Nothing}, args...) = 0
@inline getbc(bc::BC{C, <:Number},        args...)                  where C = bc.condition
@inline getbc(bc::BC{C, <:AbstractArray}, i, j, grid, clock, model_fields) where C = @inbounds bc.condition[i, j]
@inline getbc(bc::BC{C, <:Function},      i, j, grid, clock, model_fields) where C = bc.condition(i, j, grid, clock, model_fields)

@inline Base.getindex(bc::BC{C, <:AbstractArray}, i, j) where C = getindex(bc.condition, i, j)


cd("/Users/adrienpajon/Documents/all.nosync/git_repo/pulse_analysis/2020-11-12 xp pulse all seuil/Julia_analysis")

using CSV
using DataFrames
using JLD

# include("functions/prony.jl")
include("functions/pronyAlgo.jl")
include("functions/deleterows.jl")

#using Pyplot
pyplot()

##
Ts_MTiXsens=0.01
Ts_IBS = 0.02
Ts_Xsensor=1/55.2; #correctif dans la pratique on est plus vers 55.2Hz
convertlbpo2Nm = 0.11298482933333

## MTiXsens
#filenameMTiXsens : namefile of each experiment
#filenameMTiXsensID : name ID of each sensor with keys that refers to the position on the body
#sensorXsensNames : keys of each sensor equivalent to their position on the body
# include("imports/Import_filename_MTiXsens.jl")
filenameMTiXsens=load("imports/import_filename_MTiXsens_gel.jld")["filenameMTiXsens"]
sensorXsensNames=load("imports/import_filename_MTiXsens_gel.jld")["sensorXsensNames"]
filenameMTiXsensID=load("imports/import_filename_MTiXsens_gel.jld")["filenameMTiXsensID"]

## Xsensor
#filenameXsensor : namefile of each experiment
# include("imports/Import_filename_Xsensor.jl")
filenameXsensor=load("imports/Import_filename_Xsensor_gel.jld")["filenameXsensor"]

## Instrumented_backseat
#filenameIBS : namefile of each experiment
# include("imports/Import_filename_IBS.jl")
filenameIBS=load("imports/Import_filename_IBS_gel.jld")["filenameIBS"]

println("FileName import finished")

## ##################################################################
#script to read CSV and store in
# MTiXsens
# Xsensor
# IBS
encoderFolderPath="../measures/gel/encoder_file/"
IBSFolderPath="../measures/gel/IBS/"
XsensorFolderPath="../measures/gel/Xsensor/"
MTiXsensFolderPath="../measures/gel/MTiXsens/"
include("scripts/script_read_CSV.jl")

## ##################################################################
#script to extract data from MTiXsens and Xsensor in
# AccFree_MTiXsens
# Xsensor_reduce
include("scripts/script_data_extract.jl")

Treduce=12
Vreduce=Int(Treduce/Ts_IBS)
include("scripts/script_reduce_IBS.jl")

badFileMTiXsens=[43]
filenameMTiXsens=deleterows(filenameMTiXsens,badFileMTiXsens)
# filenameXsensor=deleterows(filenameMTiXsens,badFile)
badFileIBS=[41 63 67]
filenameIBS=deleterows(filenameIBS,badFileIBS)

sync=Dict()

sync[1]=(MTiXsens=0,Xsensor=0,Instrumented_backseat=0)
sync[2]=(MTiXsens=0,Xsensor=0,Instrumented_backseat=0)
#MTiXsens est en Packet counter

## ######
index_test_MTiXsens_1cm=searchAllSynchroMTiXsens(AccFree_MTiXsens,filenameMTiXsens[1:30,:],20,20,2,2,10,100)
index_test_MTiXsens_2cm=searchAllSynchroMTiXsens(AccFree_MTiXsens,filenameMTiXsens[31:60,:],20,50)
index_test_MTiXsens_3cm=searchAllSynchroMTiXsens(AccFree_MTiXsens,filenameMTiXsens[61:90,:],20,30)
index_test_MTiXsens_4cm=searchAllSynchroMTiXsens(AccFree_MTiXsens,filenameMTiXsens[91:end,:],20,30)
# index_test_MTiXsens_dda=searchAllSynchroMTiXsens(AccFree_MTiXsens,filenameMTiXsens[121:end,:],20,30)

index_test_MTiXsens=[index_test_MTiXsens_1cm;
                    index_test_MTiXsens_2cm;
                    index_test_MTiXsens_3cm;
                    index_test_MTiXsens_4cm]#
                    # index_test_MTiXsens_dda]

println(index_test_MTiXsens)
## ####
# index_test_Xsensor=[]
if size(Xsensor_reduce[filenameXsensor[1,1]],2)>6
    m=10
else
    m=4
end
index_test_Xsensor_1cm=searchAllSynchro(Xsensor_reduce,m,filenameXsensor[1:30,:],20,5*10^3)
index_test_Xsensor_2cm=searchAllSynchro(Xsensor_reduce,m,filenameXsensor[31:60,:],20,1*10^4)
index_test_Xsensor_3cm=searchAllSynchro(Xsensor_reduce,m,filenameXsensor[61:90,:],20,1*10^4)
index_test_Xsensor_4cm=searchAllSynchro(Xsensor_reduce,m,filenameXsensor[91:end,:],20,1*10^4)
# index_test_Xsensor=searchAllSynchro(Xsensor_reduce,m,filenameXsensor,20,1*10^4)

index_test_Xsensor=[index_test_Xsensor_1cm;
                    index_test_Xsensor_2cm;
                    index_test_Xsensor_3cm;
                    index_test_Xsensor_4cm]#
                    # index_test_MTiXsens_dda]
println(index_test_Xsensor)

## ####
index_test_IBS_01=searchAllSynchro(IBS,2,filenameIBS[1:49,:],20,2*10^5)
index_test_IBS_02=searchAllSynchro(IBS,2,filenameIBS[50:end,:],20,1*10^6)
index_test_IBS=[index_test_IBS_01;
                    index_test_IBS_02]

index_test_IBS_03=searchAllSynchro(IBS,2,filenameIBS[74,:],20,2*10^6)
index_test_IBS[74]=index_test_IBS_03[1]

index_test_IBS_04=searchAllSynchro(IBS,2,filenameIBS[39,:],20,1.5*10^6)
index_test_IBS[39]=index_test_IBS_04[1]

index_test_IBS.+=(Vreduce-1)

println(index_test_IBS)

## #
# sync = Dict{String, Int64}()
sync=Dict()
sync_=Dict()
# for k=1:length(index_test_MTiXsens)
#     sync[k]=(MTiXsens=index_test_MTiXsens[k]-100,
#                 Xsensor=index_test_Xsensor[k]-55,
#                 IBS=index_test_IBS[k]-50)
#     sync_[k]=(MTiXsens=index_test_MTiXsens[k]-100,Xsensor=1,IBS=1)
# end

sync=(MTiXsens=index_test_MTiXsens.-100,
                Xsensor=index_test_Xsensor.-55,
                IBS=index_test_IBS.-50)
sync_=(MTiXsens=index_test_MTiXsens.-100,Xsensor=index_test_Xsensor.*0 .+1,IBS=index_test_IBS.*0 .+1)

step_max_perturb=400

save("imports/import_synchro_gel.jld", "sync", sync)
save("imports/Import_synchro_perturb_gel.jld", "sync_", sync_,
                                            "step_max_perturb",step_max_perturb)


module BSP

importall ImmutableArrays
import Base.search

include("bsp/Mesh.jl")
include("bsp/Tree.jl")

export Bsp
type Bsp
	tree::Tree.Node
	entities::Vector{Dict{String,String}}
	vertices::Vector{Vector3{Float32}}
	max_lights::Int
end

include("bsp/File.jl")

search(bsp::Bsp, pos::Vector3) = search(bsp.tree, pos)

end


using ArgParse
using YAML
using Distributed

# const IN_SLURM = "SLURM_JOBID" in keys(ENV)
# IN_SLURM && using ClusterManagers
# if IN_SLURM
#     pids = addprocs(SlurmManager(parse(Int, ENV["SLURM_NTASKS"])))
# else
#     pids = addprocs()
# end

@everywhere include("evo/data.jl")

s = ArgParse.ArgParseSettings()

ArgParse.@add_arg_table(
    s,
    "--seed", arg_type=Int, default=0,
    "--id", arg_type=String, default="test",
    "--log", arg_type=String, default="evolution.log",
    "--cfg", arg_type=String, default="cfg/darwin.yaml",
)

args = ArgParse.parse_args(s)
cfg = YAML.load_file(args["cfg"])
cfg["seed"] = args["seed"]
cfg["n_fitness"] = 1

e = Evolution(NeurodevoInd, cfg; id=args["id"], logfile=args["log"])
e.mutation = uniform_mutation
e.evaluation = data_evaluation
e.generation = generation
run!(e)


using MonteCarloX
using Random

include("../examples/contact_process.jl")

"""
    test_gillespie_fast(;verbose=false)

Testing if sampling single event correctly

there is clearly a drift in the precision of sum_rates (needs to be compensated
in system implementation of course
"""
function test_gillespie(;verbose = false)
    return pass
end


module PlasmoPipsNlpInterface

using MathProgBase.SolverInterface
using SparseArrays
using LinearAlgebra
import MPI
import JuMP
import Plasmo.PlasmoModelGraph
#import ..PlasmoModelGraph

include("PipsNlpSolver.jl")
using .PipsNlpSolver
export pipsnlp_solve

mutable struct ModelData
    d    #NLP evaluator
    n::Int
    m::Int
    local_m::Int
    jacnnz::Int
    hessnnz::Int
    firstIeq::Vector{Int}           #row index of equality constraint in 1st stage
    firstJeq::Vector{Int}           #column index of equality constraint in 1st stage
    firstVeq::Vector{Float64}       #coefficient of variable in equality constraint in 1st stage
    secondIeq::Vector{Int}          #row index of equality constraint in 2nd stage
    secondJeq::Vector{Int}          #column index of equality constraint in 2nd stage
    secondVeq::Vector{Float64}
    firstIineq::Vector{Int}
    firstJineq::Vector{Int}
    firstVineq::Vector{Float64}
    secondIineq::Vector{Int}
    secondJineq::Vector{Int}
    secondVineq::Vector{Float64}
    num_eqconnect::Int
    num_ineqconnect::Int
    eqconnect_lb::Vector{Float64}
    eqconnect_ub::Vector{Float64}
    ineqconnect_lb::Vector{Float64}
    ineqconnect_ub::Vector{Float64}
    eq_idx::Vector{Int}
    ineq_idx::Vector{Int}
    firstJeqmat
    secondJeqmat
    firstJineqmat
    secondJineqmat
    linkIeq::Vector{Int}
    linkJeq::Vector{Int}
    linkVeq::Vector{Float64}
    linkIineq::Vector{Int}
    linkJineq::Vector{Int}
    linkVineq::Vector{Float64}
    x_sol::Vector{Float64}
    coreid::Int
    loaded::Bool
    local_unsym_hessnnz::Int
end
ModelData() = ModelData(nothing,0,0,0,0,0,Int[],Int[], Float64[], Int[], Int[], Float64[],Int[],Int[],Float64[], Int[], Int[], Float64[], 0, 0, Float64[],Float64[],Float64[],Float64[], Int[], Int[],nothing, nothing, nothing, nothing, Int[],Int[], Float64[], Int[], Int[], Float64[],Float64[], 0, false, 0)

#Helper function
function getData(m::JuMP.Model)
    if haskey(m.ext, :Data)
        return m.ext[:Data]
    else
        error("This functionality is only available to model with extension data")
    end
end

function pipsnlp_solve(graph::PlasmoModelGraph.AbstractModelGraph,master_index::Int64,children_indices::Vector{Int64})

    if master_index == 0
        master_node = PlasmoModelGraph.ModelNode()
    else
        master_node = PlasmoModelGraph.getnode(graph,master_index)
    end
    children_nodes = [PlasmoModelGraph.getnode(graph,c_index) for c_index in children_indices]

    #need to check that the structure makes sense
    submodels = [PlasmoModelGraph.getmodel(child) for child in children_nodes]
    scen = length(children_nodes)
    #############################

    master = PlasmoModelGraph.getmodel(master_node)
    modelList = [master; submodels]

    #Add ModelData to each model
    for (idx,node) in enumerate(modelList)
        node.ext[:Data] = ModelData()
    end

    master_linear_lb = []
    master_linear_ub = []

    linkconstraints = PlasmoModelGraph.getlinkconstraints(graph) #get all of the link constraints in the graph
    #get arrays of lower and upper bounds for each link constraint
    for con in linkconstraints
        push!(master_linear_lb,con.lb)
        push!(master_linear_ub,con.ub)
    end

    nlinkeq = 0
    nlinkineq = 0
    eqlink_lb = Float64[]
    eqlink_ub = Float64[]
    ineqlink_lb = Float64[]
    ineqlink_ub = Float64[]

    #go through the link constraints
    for c in 1:length(linkconstraints)
        coeffs = linkconstraints[c].terms.coeffs
        vars   = linkconstraints[c].terms.vars
        connect = false     #the constraint connects a single node to the master
        allconnect = false  #the constraint connects subproblems to eachother
        node = nothing

		for (it,ind) in enumerate(coeffs)              #for each coefficient in the constraint
            #if the constraint has a variable from a sub-node
            if (!connect) && (vars[it].m) != master    #if the variable isn't in the master model
                connect = true
                node = vars[it].m                      #the submodel
		    end
            #if the constraint actually involved multiple nodes
		    if connect && (vars[it].m != master) && (vars[it].m != node)
		       allconnect = true
		       break  #don't need to check every variable if this statement runs
		    end
	    end

    	if (connect) && (!allconnect)   #if there's a master child connection only
            # println("Simple Links")
            # println(node.ext)
    	    local_data = getData(node)
    		if master_linear_lb[c] == master_linear_ub[c]  #if it's an equality constraint
                firstIeq = local_data.firstIeq
                firstJeq = local_data.firstJeq
                firstVeq = local_data.firstVeq
                secondIeq = local_data.secondIeq
                secondJeq = local_data.secondJeq
                secondVeq = local_data.secondVeq
                push!(local_data.eqconnect_lb, master_linear_lb[c])
                push!(local_data.eqconnect_ub, master_linear_ub[c])
                local_data.num_eqconnect += 1
                row = local_data.num_eqconnect
                for (it,ind) in enumerate(coeffs)
                    if (vars[it].m) == master
                           	  push!(firstIeq, row)
                          	  push!(firstJeq, vars[it].col)
                          	  push!(firstVeq, ind)
                    elseif (vars[it].m) == node
                           	  push!(secondIeq, row)
                          	  push!(secondJeq, vars[it].col)
                          	  push!(secondVeq, ind)
                    else
                      error("only supports connection between first stage variables and second stage variables from one specific scenario")
                    end
                end
    		else
                firstIineq = local_data.firstIineq
                firstJineq = local_data.firstJineq
                firstVineq = local_data.firstVineq
                secondIineq = local_data.secondIineq
                secondJineq = local_data.secondJineq
                secondVineq = local_data.secondVineq

                push!(local_data.ineqconnect_lb, master_linear_lb[c])
                push!(local_data.ineqconnect_ub, master_linear_ub[c])

                local_data.num_ineqconnect += 1
                row = local_data.num_ineqconnect
                for (it,ind) in enumerate(coeffs)
                    if (vars[it].m) == master
                        push!(firstIineq, row)
                        push!(firstJineq, vars[it].col)
                        push!(firstVineq, ind)
                    elseif (vars[it].m) == node
                        push!(secondIineq, row)
                        push!(secondJineq, vars[it].col)
                        push!(secondVineq, ind)
                    else
                        error("only supports connection between first stage variables and second stage variables from one specific scenario")
                    end
                end
	        end
	    end

	    if (allconnect)
            if master_linear_lb[c] == master_linear_ub[c]
                nlinkeq = nlinkeq + 1
                push!(eqlink_lb, master_linear_lb[c])
                push!(eqlink_ub, master_linear_ub[c])
                row = nlinkeq
                for (it,ind) in enumerate(coeffs)
                    node = vars[it].m
                    # println("Linking constraint")
                    # println(node.ext)
                    # println("Check model list again")
                    # println(modelList[1].ext)
                    local_data = getData(node)
                    linkIeq = local_data.linkIeq
                    linkJeq = local_data.linkJeq
                    linkVeq = local_data.linkVeq
                    push!(linkIeq, row)
                    push!(linkJeq, vars[it].col)
                    push!(linkVeq, ind)
                end
            else
                nlinkineq = nlinkineq + 1
                push!(ineqlink_lb, master_linear_lb[c])
                push!(ineqlink_ub, master_linear_ub[c])
                row = nlinkineq
                for (it,ind) in enumerate(coeffs)
                    node = vars[it].m
                    local_data = getData(node)
                    linkIineq = local_data.linkIineq
                    linkJineq = local_data.linkJineq
                    linkVineq = local_data.linkVineq
                    push!(linkIineq, row)
                    push!(linkJineq, vars[it].col)
                    push!(linkVineq, ind)
                end
            end
        end
    end
    #removeConnection(master, connectid)  #removes the linear constraints from the master model.  I don't add these to the master anyways
    master_data = getData(master)  #this isn't used anywhere?

    if haskey(master.ext, :nlinkeq)
        nlinkeq =  master.ext[:nlinkeq]
    end
    if haskey(master.ext, :nlinkineq)
        nlinkineq =  master.ext[:nlinkineq]
    end
    if haskey(master.ext, :eqlink_lb)
        eqlink_lb = copy(master.ext[:eqlink_lb])
        eqlink_ub = copy(master.ext[:eqlink_lb])
    end
    if haskey(master.ext, :eqlink_ub)
        eqlink_lb = copy(master.ext[:eqlink_ub])
        eqlink_ub = copy(master.ext[:eqlink_ub])
    end
    if haskey(master.ext, :ineqlink_lb)
        ineqlink_lb = copy(master.ext[:ineqlink_lb])
    end
    if haskey(master.ext, :ineqlink_ub)
        ineqlink_ub = copy(master.ext[:ineqlink_ub])
    end

    for (idx,node) in enumerate(modelList)
        local_data = getData(node)
        if haskey(node.ext, :linkIineq)
            local_data.linkIineq =  copy(node.ext[:linkIineq])
            node.ext[:linkIineq] = nothing
        end
        if haskey(node.ext, :linkJineq)
            local_data.linkJineq =  copy(node.ext[:linkJineq])
            node.ext[:linkJineq] = nothing
        end
        if haskey(node.ext, :linkVineq)
            local_data.linkVineq =  copy(node.ext[:linkVineq])
            node.ext[:linkVineq] = nothing
        end
        if haskey(node.ext, :linkIeq)
            local_data.linkIeq =  copy(node.ext[:linkIeq])
            node.ext[:linkIeq] = nothing
        end
        if haskey(node.ext, :linkJeq)
            local_data.linkJeq =  copy(node.ext[:linkJeq])
            node.ext[:linkJeq] = nothing
        end
        if haskey(node.ext, :linkVeq)
            local_data.linkVeq =  copy(node.ext[:linkVeq])
            node.ext[:linkVeq] = nothing
        end
    end

    function str_init_x0(nodeid, x0)
        node = modelList[nodeid+1]
        local_initval = copy(node.colVal)
        if any(isnan,node.colVal)
            local_initval[isnan.(node.colVal)] .= 0
            local_initval = min.(max.(node.colLower,local_initval),node.colUpper)
        end
        original_copy(local_initval,x0)
    end

    function str_prob_info(nodeid,flag, mode,col_lb,col_ub,row_lb,row_ub)
	 	#comm = MPI.COMM_WORLD
        if flag != 1
            node = modelList[nodeid+1]
            local_data = getData(node)
            if(!local_data.loaded)
                local_data.loaded = true
                if (nodeid > 0)
                    if haskey(node.ext, :warmStart)
    	                if node.ext[:warmStart] == true
              	      	   solve(node)
                   	   	end
                    end
            	end

    			nlp_lb, nlp_ub = JuMP.constraintbounds(node)
         		local_data.local_m  = length(nlp_lb)

    			newRowId = Array{Int}(undef,local_data.local_m)
    			eqId = 1
    			ineqId = 1
                for c in 1:local_data.local_m
                    if nlp_lb[c] == nlp_ub[c]
                        push!(local_data.eq_idx, c)
                        newRowId[c] = eqId
                        eqId +=  1
                    else
        				push!(local_data.ineq_idx, c)
        				newRowId[c] = ineqId
        				ineqId += 1
                    end
         		end
                local_data.m  = local_data.local_m + local_data.num_eqconnect + local_data.num_ineqconnect
                local_data.n = node.numCols
                local_data.x_sol = zeros(Float64,local_data.n)
                local_data.firstJeqmat = sparse(local_data.firstIeq, local_data.firstJeq, local_data.firstVeq, local_data.num_eqconnect, master_data.n)
                local_data.secondJeqmat = sparse(local_data.secondIeq, local_data.secondJeq, local_data.secondVeq, local_data.num_eqconnect, local_data.n)
                local_data.firstJineqmat = sparse(local_data.firstIineq, local_data.firstJineq, local_data.firstVineq, local_data.num_ineqconnect, master_data.n)
                local_data.secondJineqmat = sparse(local_data.secondIineq, local_data.secondJineq, local_data.secondVineq, local_data.num_ineqconnect, local_data.n)

                local_data.d = JuMP.NLPEvaluator(node)
                initialize(local_data.d, [:Grad,:Jac, :Hess])
                Ijac, Jjac = jac_structure(local_data.d)
                Ijaceq = Int[]
                Jjaceq = Int[]
                Ijacineq = Int[]
                Jjacineq = Int[]
                jac_eq_index = Int[]
                jac_ineq_index = Int[]
                for i in 1:length(Ijac)
                    c = Ijac[i]
                    if nlp_lb[c] == nlp_ub[c]
                        modifiedrow = newRowId[c]
                        push!(Ijaceq, modifiedrow)
                        push!(Jjaceq, Jjac[i])
                        push!(jac_eq_index, i)
                    else
                        modifiedrow = newRowId[c]
                        push!(Ijacineq, modifiedrow)
                        push!(Jjacineq, Jjac[i])
                        push!(jac_ineq_index,i)
                    end
                end

        		node.ext[:Ijaceq] = Ijaceq
         		node.ext[:Jjaceq] = Jjaceq
                node.ext[:Ijacineq] = Ijacineq
         		node.ext[:Jjacineq] = Jjacineq
         		node.ext[:jac_eq_index] = jac_eq_index
         		node.ext[:jac_ineq_index] = jac_ineq_index
         		Ihess, Jhess = hesslag_structure(local_data.d)

         		Hmap = Bool[]
         		node_Hrows = Int[]
         		node_Hcols = Int[]
         		for i in 1:length(Ihess)
            	    if Jhess[i] <= Ihess[i]
               	        push!(node_Hrows, Ihess[i])
                   		push!(node_Hcols, Jhess[i])
                   		push!(Hmap, true)
            	    else
                        push!(Hmap, false)
                    end
                end
         		val = ones(Float64,length(node_Hrows))
         		mat = sparse(node_Hrows,node_Hcols,val, local_data.n, local_data.n)
         		node.ext[:Hrows] = node_Hrows
         		node.ext[:Hcols] = node_Hcols
         		node.ext[:Hmap] = Hmap
         		local_hessnnz = length(mat.rowval)
         		local_data.local_unsym_hessnnz = length(Ihess)
         		local_data.hessnnz = local_hessnnz
		    end

	 	    if(mode == :Values)
               	nlp_lb, nlp_ub = JuMP.constraintbounds(node)
    			eq_lb=Float64[]
    			eq_ub=Float64[]
    			ineq_lb=Float64[]
    			ineq_ub=Float64[]
    			for i in 1: length(nlp_lb)
    			    if nlp_lb[i] == nlp_ub[i]
    			       push!(eq_lb, nlp_lb[i])
    			       push!(eq_ub, nlp_ub[i])
    			    else
    			       push!(ineq_lb, nlp_lb[i])
    			       push!(ineq_ub, nlp_ub[i])
    			    end
    			end

    			if nodeid !=  0
    			   eq_lb = [eq_lb; local_data.eqconnect_lb]
    			   eq_ub = [eq_ub; local_data.eqconnect_ub]
    			   ineq_lb = [ineq_lb; local_data.ineqconnect_lb]
    			   ineq_ub = [ineq_ub; local_data.ineqconnect_ub]
    			end

    			original_copy([eq_lb;ineq_lb], row_lb)
    			original_copy([eq_ub;ineq_ub], row_ub)
    			original_copy(node.colLower, col_lb)
    			original_copy(node.colUpper, col_ub)
            end
		    return (local_data.n,local_data.m)
		else
		    if(mode == :Values)
                original_copy([eqlink_lb; ineqlink_lb], row_lb)
                original_copy([eqlink_ub; ineqlink_ub], row_ub)
            end
            return (0, nlinkeq + nlinkineq)
		end
    end

    function str_eval_f(nodeid,x0,x1)
    	node = modelList[nodeid+1]
        local_data = getData(node)
        local_d = getData(node).d
        if nodeid ==  0
            local_x = x0
        else
            local_x = x1
        end
        local_scl = (node.objSense == :Min) ? 1.0 : -1.0
        f = local_scl*eval_f(local_d,local_x)
        return f
    end

    function str_eval_g(nodeid,x0,x1,new_eq_g, new_inq_g)
        node = modelList[nodeid+1]
        local_data = getData(node)
        local_d = getData(node).d
        if nodeid ==  0
            local_x = x0
        else
            local_x = x1
        end
        local_g = Array{Float64}(undef,local_data.local_m)
        eval_g(local_d, local_g, local_x)
        new_eq_g[1:end] = [local_g[local_data.eq_idx]; local_data.firstJeqmat*x0+local_data.secondJeqmat*x1]
        new_inq_g[1:end] = [local_g[local_data.ineq_idx]; local_data.firstJineqmat*x0+local_data.secondJineqmat*x1]
	    return Int32(1)
    end

    function str_write_solution(id::Integer, x::Vector{Float64}, y_eq::Vector{Float64}, y_ieq::Vector{Float64})
        node = modelList[id+1]
        local_data = getData(node)
        local_data.x_sol = copy(x)
        if id == 0
            node.objVal = str_eval_f(id, x, nothing)
        else
            node.objVal = str_eval_f(id, nothing, x)
        end
        r = MPI.Comm_rank(comm)
        local_data.coreid = r
    end

    function str_eval_grad_f(rowid,colid,x0,x1,new_grad_f)
    node = modelList[rowid+1]
    if rowid == colid
        local_data = getData(node)
        local_d = getData(node).d
        if colid ==  0
            local_x = x0
        else
            local_x = x1
        end
        local_grad_f = Array{Float64}(undef,local_data.n)
        eval_grad_f(local_d, local_grad_f, local_x)
        local_scl = (node.objSense == :Min) ? 1.0 : -1.0
        #scale!(local_grad_f,local_scl)
        rmul!(local_grad_f,local_scl)
        original_copy(local_grad_f, new_grad_f)
    elseif colid == 0
        new_grad_f[1:end] .= 0
    else
        @assert(false)
    end
    return Int32(1)
    end

    function array_copy(src,dest)
        @assert(length(src)==length(dest))
        for i in 1:length(src)
            dest[i] = src[i]-1
        end
    end

    function original_copy(src,dest)
        @assert(length(src)==length(dest))
        for i in 1:length(src)
            dest[i] = src[i]
        end
    end

    function str_eval_jac_g(rowid,colid,flag, x0,x1,mode,e_rowidx,e_colptr,e_values,i_rowidx,i_colptr,i_values)
        if flag != 1
            node = modelList[rowid+1]
            local_data = getData(node)
            local_m_eq = length(local_data.eq_idx)
            local_m_ineq = length(local_data.ineq_idx)
            if mode == :Structure
                if (rowid == colid)
                    Ieq=[node.ext[:Ijaceq];local_data.secondIeq .+ local_m_eq]
                    Jeq=[node.ext[:Jjaceq];local_data.secondJeq]
                    Veq= ones(Float64, length(Ieq))
                    Iineq=[node.ext[:Ijacineq];local_data.secondIineq .+ local_m_ineq]
                    Jineq=[node.ext[:Jjacineq];local_data.secondJineq]
                    Vineq=ones(Float64, length(Iineq))
                    eqmat = sparse(Ieq, Jeq, Veq, local_m_eq + local_data.num_eqconnect, local_data.n)
                    ineqmat = sparse(Iineq, Jineq, Vineq, local_m_ineq + local_data.num_ineqconnect, local_data.n)
                else
                    eqmat = sparse(local_m_eq .+ local_data.firstIeq, local_data.firstJeq, local_data.firstVeq, local_m_eq .+ local_data.num_eqconnect, master_data.n)
                    ineqmat = sparse(local_m_ineq .+ local_data.firstIineq, local_data.firstJineq, local_data.firstVineq, local_m_ineq .+ local_data.num_ineqconnect, master_data.n)
                end
                return(length(eqmat.rowval), length(ineqmat.rowval))
            else
                if rowid == colid
                    if colid ==  0
                        local_x = x0
                    else
                        local_x = x1
                    end
                    local_values = Array{Float64}(undef,length(node.ext[:Ijaceq])+length(node.ext[:Ijacineq]))
                    eval_jac_g(local_data.d, local_values, local_x)
                    jac_eq_index = node.ext[:jac_eq_index]
                    jac_ineq_index = node.ext[:jac_ineq_index]
                    Ieq=[node.ext[:Ijaceq];local_data.secondIeq .+ local_m_eq]
                    Jeq=[node.ext[:Jjaceq];local_data.secondJeq]
                    Veq=[local_values[jac_eq_index];local_data.secondVeq]
                    Iineq=[node.ext[:Ijacineq];local_data.secondIineq .+ local_m_ineq]
                    Jineq=[node.ext[:Jjacineq];local_data.secondJineq]
                    Vineq=[local_values[jac_ineq_index];local_data.secondVineq]
                    eqmat = sparseKeepZero(Ieq, Jeq, Veq, local_m_eq + local_data.num_eqconnect, local_data.n)
                    ineqmat = sparseKeepZero(Iineq, Jineq, Vineq, local_m_ineq + local_data.num_ineqconnect, local_data.n)
                else
                    eqmat = sparseKeepZero(local_m_eq .+ local_data.firstIeq, local_data.firstJeq, local_data.firstVeq, local_m_eq .+ local_data.num_eqconnect, master_data.n)
                    ineqmat = sparseKeepZero(local_m_ineq .+ local_data.firstIineq, local_data.firstJineq, local_data.firstVineq, local_m_ineq .+ local_data.num_ineqconnect, master_data.n)
                end
                if length(eqmat.nzval) > 0
                    array_copy(eqmat.rowval,e_rowidx)
                    array_copy(eqmat.colptr,e_colptr)
                    original_copy(eqmat.nzval,e_values)
                end
                if length(ineqmat.nzval) > 0
                    array_copy(ineqmat.rowval,i_rowidx)
                    array_copy(ineqmat.colptr,i_colptr)
                    original_copy(ineqmat.nzval, i_values)
                end
            end
        else
            node = modelList[rowid+1]
            local_data = getData(node)
            linkIeq = local_data.linkIeq
            linkJeq = local_data.linkJeq
            linkVeq = local_data.linkVeq
            linkIineq = local_data.linkIineq
            linkJineq = local_data.linkJineq
            linkVineq = local_data.linkVineq

            if mode == :Structure
                return(length(linkVeq), length(linkVineq))
            else
                eqmat = sparse(linkIeq, linkJeq, linkVeq, nlinkeq, local_data.n)
                ineqmat = sparse(linkIineq, linkJineq, linkVineq, nlinkineq, local_data.n)
                if length(eqmat.nzval) > 0
                    array_copy(eqmat.rowval,e_rowidx)
                    array_copy(eqmat.colptr,e_colptr)
                    original_copy(eqmat.nzval,e_values)
                end
                if length(ineqmat.nzval) > 0
                    array_copy(ineqmat.rowval,i_rowidx)
                    array_copy(ineqmat.colptr,i_colptr)
                    original_copy(ineqmat.nzval, i_values)
                end
            end
        end
        return Int32(1)
    end

    function str_eval_h(rowid,colid,x0,x1,obj_factor,lambda,mode,rowidx,colptr,values)
        node = modelList[colid+1]
        local_data = getData(node)
        if mode == :Structure
            if rowid == colid
                node_Hrows  = node.ext[:Hrows]
                node_Hcols = node.ext[:Hcols]
                node_val = ones(Float64,length(node_Hrows))
                mat = sparseKeepZero(node_Hrows,node_Hcols,node_val, local_data.n, local_data.n)
                return length(mat.rowval)
            elseif colid == 0
                node_Hrows  = node.ext[:Hrows]
                node_Hcols = node.ext[:Hcols]
                node_Hrows,node_Hcols = exchange(node_Hrows,node_Hcols)
                node_val = ones(Float64,length(node_Hrows))
                mat = sparseKeepZero(node_Hrows,node_Hcols,node_val, local_data.n, local_data.n)
                return length(mat.rowval)
            else
                return 0
            end
        else
            if rowid==colid
                node_Hmap = node.ext[:Hmap]
                node_Hrows  = node.ext[:Hrows]
                node_Hcols = node.ext[:Hcols]
                if colid ==  0
                    local_x = x0
                else
                    local_x = x1
                end
                local_unsym_values = Array{Float64}(undef,local_data.local_unsym_hessnnz)
                node_val = ones(Float64,length(node_Hrows))
                local_scl = (node.objSense == :Min) ? 1.0 : -1.0
                local_m_eq = length(local_data.eq_idx)
                local_m_ineq = length(local_data.ineq_idx)
                local_lambda = zeros(Float64, local_data.local_m)
                for i in 1:local_m_eq
                    local_lambda[local_data.eq_idx[i]] = lambda[i]
                end
                for i in 1:local_m_ineq
                    local_lambda[local_data.ineq_idx[i]] = lambda[i+local_m_eq]
                end

                eval_hesslag(local_data.d, local_unsym_values, local_x, obj_factor*local_scl, local_lambda)
                local_sym_index=1
                for i in 1:local_data.local_unsym_hessnnz
                    if node_Hmap[i]
                        node_val[local_sym_index] = local_unsym_values[i]
                        local_sym_index +=1
                    end
                end
                node_Hrows,node_Hcols = exchange(node_Hrows,node_Hcols)
                mat = sparseKeepZero(node_Hrows,node_Hcols,node_val, local_data.n, local_data.n)
                array_copy(mat.rowval,rowidx)
                array_copy(mat.colptr,colptr)
                original_copy(mat.nzval,values)
            elseif colid ==	0
                node_Hrows  = node.ext[:Hrows]
                node_Hcols = node.ext[:Hcols]
                node_val = zeros(Float64,length(node_Hrows))
                node_Hrows,node_Hcols = exchange(node_Hrows,node_Hcols)
                mat = sparseKeepZero(node_Hrows,node_Hcols,node_val, local_data.n, local_data.n)
                array_copy(mat.rowval,rowidx)
                array_copy(mat.colptr,colptr)
                original_copy(mat.nzval,values)
            else
            end
        end
        return Int32(1)
    end

    comm = MPI.COMM_WORLD
    if(MPI.Comm_rank(comm) == 0)
        t1 = time()
    end

    #Create FakeModel (The PIPS interface model) and pass all the functions it requires
    model = FakeModel(:Min,0, scen,
    str_init_x0, str_prob_info, str_eval_f, str_eval_g, str_eval_grad_f, str_eval_jac_g, str_eval_h,str_write_solution)
    prob = createProblemStruct(comm, model, true)
    ret = solveProblemStruct(prob)
    root = 0
    r = MPI.Comm_rank(comm)

    if(MPI.Comm_rank(comm) == 0)
        println("Timing Results:")
        println("init_x0  ",prob.t_jl_init_x0)
        println("str_init_x0  ", prob.t_jl_str_prob_info)
        println("eval_f  ", prob.t_jl_eval_f)
        println("eval_g0  ",prob.t_jl_eval_g)
        println("eval_grad_f  ", prob.t_jl_eval_grad_f)
        println("eval_jac  ", prob.t_jl_eval_jac_g)
        println("str_eval_jac  ", prob.t_jl_str_eval_jac_g)
        println("eval_h  ",  prob.t_jl_eval_h)
        println("str_eval_h ",  prob.t_jl_str_eval_h)
        println("eval_write_solution  ",  prob.t_jl_write_solution)
        println("PIPS-NLP time:   ",  time() - t1, " (s)")
    end

    for (idx,node) in enumerate(modelList)  #set solution values for each model
        local_data = getData(node)
        if idx != 1
            coreid = zeros(Int, 1)
            sc = MPI.Reduce(local_data.coreid, MPI.SUM, root, comm)
            if r == root
                coreid[1] = sc
            end
            MPI.Bcast!(coreid, length(coreid), root, comm)
            n = zeros(Int, 1)
            n[1] = local_data.n
            MPI.Bcast!(n,      length(n),      coreid[1], comm)
            if r != coreid[1]
                local_data.n = n[1]
                local_data.x_sol = zeros(Float64,local_data.n)
            end
            MPI.Bcast!(local_data.x_sol, local_data.n, coreid[1], comm)
            node.colVal = local_data.x_sol
        else
            node.colVal = local_data.x_sol
        end
    end

    status = :Unknown
    if ret == 0
        status = :Optimal
        #PlasmoModelGraph._setobjectivevalue(graph,prob.t_jl_eval_f)
    elseif ret == 1
        status = :Not_Finished
    elseif	ret == 2
        status = :Maximum_Iterations_Exceeded
    elseif	ret == 3
        stauts = :Infeasible_Problem_Detected
    elseif ret == 4
        status = :Restoration_needed
    else
        status = :UnknownStatus
    end

    return status
end  #end pips_nlp_solve

#############################################
# Helpers
#############################################
function exchange(a,b)
	 temp = a
         a=b
         b=temp
	 return (a,b)
end

function sparseKeepZero(I::AbstractVector{Ti},
    J::AbstractVector{Ti},
    V::AbstractVector{Tv},
    nrow::Integer, ncol::Integer) where {Tv,Ti<:Integer}
    N = length(I)
    if N != length(J) || N != length(V)
        throw(ArgumentError("triplet I,J,V vectors must be the same length"))
    end
    if N == 0
        return spzeros(eltype(V), Ti, nrow, ncol)
    end

    # Work array
    Wj = Array{Ti}(undef,max(nrow,ncol)+1)
    # Allocate sparse matrix data structure
    # Count entries in each row
    Rnz = zeros(Ti, nrow+1)
    Rnz[1] = 1
    nz = 0
    for k=1:N
        iind = I[k]
        iind > 0 || throw(ArgumentError("all I index values must be > 0"))
        iind <= nrow || throw(ArgumentError("all I index values must be ≤ the number of rows"))
        Rnz[iind+1] += 1
        nz += 1
    end
    Rp = cumsum(Rnz)
    Ri = Array{Ti}(undef,nz)
    Rx = Array{Tv}(undef,nz)

    # Construct row form
    # place triplet (i,j,x) in column i of R
    # Use work array for temporary row pointers
    @simd for i=1:nrow; @inbounds Wj[i] = Rp[i]; end
    @inbounds for k=1:N
        iind = I[k]
        jind = J[k]
        jind > 0 || throw(ArgumentError("all J index values must be > 0"))
        jind <= ncol || throw(ArgumentError("all J index values must be ≤ the number of columns"))
        p = Wj[iind]
        Vk = V[k]
        Wj[iind] += 1
        Rx[p] = Vk
        Ri[p] = jind
    end

    # Reset work array for use in counting duplicates
    @simd for j=1:ncol; @inbounds Wj[j] = 0; end

    # Sum up duplicates and squeeze
    anz = 0
    @inbounds for i=1:nrow
        p1 = Rp[i]
        p2 = Rp[i+1] - 1
        pdest = p1
        for p = p1:p2
            j = Ri[p]
            pj = Wj[j]
            if pj >= p1
                Rx[pj] = Rx[pj] + Rx[p]
            else
                Wj[j] = pdest
                if pdest != p
                    Ri[pdest] = j
                    Rx[pdest] = Rx[p]
                end
                pdest += one(Ti)
            end
        end
        Rnz[i] = pdest - p1
        anz += (pdest - p1)
    end

    # Transpose from row format to get the CSC format
    RiT = Array{Ti}(undef,anz)
    RxT = Array{Tv}(undef,anz)

    # Reset work array to build the final colptr
    Wj[1] = 1
    @simd for i=2:(ncol+1); @inbounds Wj[i] = 0; end
    @inbounds for j = 1:nrow
        p1 = Rp[j]
        p2 = p1 + Rnz[j] - 1
        for p = p1:p2
            Wj[Ri[p]+1] += 1
        end
    end
    RpT = cumsum(Wj[1:(ncol+1)])

    # Transpose
    @simd for i=1:length(RpT); @inbounds Wj[i] = RpT[i]; end
    @inbounds for j = 1:nrow
        p1 = Rp[j]
        p2 = p1 + Rnz[j] - 1
        for p = p1:p2
            ind = Ri[p]
            q = Wj[ind]
            Wj[ind] += 1
            RiT[q] = j
            RxT[q] = Rx[p]
        end
    end

    return SparseMatrixCSC(nrow, ncol, RpT, RiT, RxT)
end

function convert_to_c_idx(indicies)
    for i in 1:length(indicies)
        indicies[i] = indicies[i] - 1
    end
end

end #end module
# function removeConnection(master::JuMP.Model, deleterow)
#     deleteat!(master.linconstr,deleterow)
# end


###
### Particle Filtering and Particle MCMC Samplers.
###

####
#### Generic Sequential Monte Carlo sampler.
####

"""
    SMC(n_particles::Int)

Sequential Monte Carlo sampler.

Note that this method is particle-based, and arrays of variables
must be stored in a [`TArray`](@ref) object.

Usage:

```julia
SMC(1000)
```
"""
mutable struct SMC{space, F} <: InferenceAlgorithm
    n_particles           ::  Int
    resampler             ::  F
    resampler_threshold   ::  Float64
end
function SMC(n_particles::Int, resampler::F, resampler_threshold::Float64, space::Tuple) where {F}
    return SMC{space, F}(n_particles, resampler, resampler_threshold)
end
SMC(n) = SMC(n, resample_systematic, 0.5, ())
SMC(n_particles::Int, ::Tuple{}) = SMC(n_particles)
function SMC(n_particles::Int, space::Symbol...)
    SMC(n_particles, resample_systematic, 0.5, space)
end

function Sampler(alg::SMC, s::Selector)
    info = Dict{Symbol, Any}()
    info[:logevidence] = []
    return Sampler(alg, info, s)
end

function step(model, spl::Sampler{<:SMC}, vi::VarInfo)
    particles = ParticleContainer{Trace}(model)
    vi.num_produce = 0;  # Reset num_produce before new sweep\.
    set_retained_vns_del_by_spl!(vi, spl)
    resetlogp!(vi)

    push!(particles, spl.alg.n_particles, spl, vi)

    while consume(particles) != Val{:done}
      ess = effectiveSampleSize(particles)
      if ess <= spl.alg.resampler_threshold * length(particles)
        resample!(particles,spl.alg.resampler)
      end
    end

    ## pick a particle to be retained.
    Ws, _ = weights(particles)
    indx = randcat(Ws)
    push!(spl.info[:logevidence], particles.logE)

    return particles[indx].vi, true
end

## wrapper for smc: run the sampler, collect results.
function sample(model::Model, alg::SMC)
    spl = Sampler(alg)

    particles = ParticleContainer{Trace}(model)
    push!(particles, spl.alg.n_particles, spl, empty!(VarInfo(model)))

    while consume(particles) != Val{:done}
      ess = effectiveSampleSize(particles)
      if ess <= spl.alg.resampler_threshold * length(particles)
        resample!(particles,spl.alg.resampler)
      end
    end
    w, samples = getsample(particles)
    res = Chain(log(w), samples)
    return res
end

####
#### Particle Gibbs sampler.
####

"""
    PG(n_particles::Int, n_iters::Int)

Particle Gibbs sampler.

Note that this method is particle-based, and arrays of variables
must be stored in a [`TArray`](@ref) object.

Usage:

```julia
PG(100, 100)
```
"""
mutable struct PG{space, F} <: InferenceAlgorithm
  n_particles           ::    Int         # number of particles used
  n_iters               ::    Int         # number of iterations
  resampler             ::    F           # function to resample
end
function PG(n_particles::Int, n_iters::Int, resampler::F, space::Tuple) where F
    return PG{space, F}(n_particles, n_iters, resampler)
end
PG(n1::Int, n2::Int, ::Tuple{}) = PG(n1, n2)
function PG(n1::Int, n2::Int, space::Symbol...)
    PG(n1, n2, resample_systematic, space)
end

const CSMC = PG # type alias of PG as Conditional SMC

function Sampler(alg::PG, s::Selector)
    info = Dict{Symbol, Any}()
    info[:logevidence] = []
    Sampler(alg, info, s)
end

step(model, spl::Sampler{<:PG}, vi::VarInfo, _) = step(model, spl, vi)

function step(model, spl::Sampler{<:PG}, vi::VarInfo)
    particles = ParticleContainer{Trace{typeof(spl), typeof(vi), typeof(model)}}(model)

    vi.num_produce = 0;  # Reset num_produce before new sweep\.
    ref_particle = isempty(vi) ?
                  nothing :
                  forkr(Trace(model, spl, vi))

    set_retained_vns_del_by_spl!(vi, spl)
    resetlogp!(vi)

    if ref_particle == nothing
        push!(particles, spl.alg.n_particles, spl, vi)
    else
        push!(particles, spl.alg.n_particles-1, spl, vi)
        push!(particles, ref_particle)
    end

    while consume(particles) != Val{:done}
        resample!(particles, spl.alg.resampler, ref_particle)
    end

    ## pick a particle to be retained.
    Ws, _ = weights(particles)
    indx = randcat(Ws)
    push!(spl.info[:logevidence], particles.logE)

    return particles[indx].vi, true
end

function sample(  model::Model,
                  alg::PG;
                  save_state=false,         # flag for state saving
                  resume_from=nothing,      # chain to continue
                  reuse_spl_n=0             # flag for spl re-using
                )

    spl = reuse_spl_n > 0 ?
          resume_from.info[:spl] :
          Sampler(alg)
    if resume_from != nothing
        spl.selector = resume_from.info[:spl].selector
    end
    @assert typeof(spl.alg) == typeof(alg) "[Turing] alg type mismatch; please use resume() to re-use spl"

    n = reuse_spl_n > 0 ?
        reuse_spl_n :
        alg.n_iters
    samples = Vector{Sample}()

    ## custom resampling function for pgibbs
    ## re-inserts reteined particle after each resampling step
    time_total = zero(Float64)

    vi = resume_from == nothing ?
        empty!(VarInfo(model)) :
        resume_from.info[:vi]

    pm = nothing
    PROGRESS[] && (spl.info[:progress] = ProgressMeter.Progress(n, 1, "[PG] Sampling...", 0))

    for i = 1:n
        time_elapsed = @elapsed vi, _ = step(model, spl, vi)
        push!(samples, Sample(vi))
        samples[i].value[:elapsed] = time_elapsed

        time_total += time_elapsed

        if PROGRESS[] && spl.selector.tag == :default
            ProgressMeter.next!(spl.info[:progress])
        end
    end

    @info("[PG] Finished with")
    @info("  Running time    = $time_total;")

    loge = exp.(mean(spl.info[:logevidence]))
    if resume_from != nothing   # concat samples
        pushfirst!(samples, resume_from.info[:samples]...)
        pre_loge = exp.(resume_from.logevidence)
        # Calculate new log-evidence
        pre_n = length(resume_from.info[:samples])
        loge = (log(pre_loge) * pre_n + log(loge) * n) / (pre_n + n)
    end
    c = Chain(loge, samples)       # wrap the result by Chain

    if save_state               # save state
        c = save(c, spl, model, vi, samples)
    end

    return c
end

function assume(  spl::Sampler{T},
                  dist::Distribution,
                  vn::VarName,
                  _::VarInfo
                ) where T<:Union{PG,SMC}

    vi = current_trace().vi
    if isempty(getspace(spl.alg)) || vn.sym in getspace(spl.alg)
        if ~haskey(vi, vn)
            r = rand(dist)
            push!(vi, vn, r, dist, spl)
        elseif is_flagged(vi, vn, "del")
            unset_flag!(vi, vn, "del")
            r = rand(dist)
            vi[vn] = vectorize(dist, r)
            setgid!(vi, spl.selector, vn)
            setorder!(vi, vn, vi.num_produce)
        else
            updategid!(vi, vn, spl)
            r = vi[vn]
        end
    else # vn belongs to other sampler <=> conditionning on vn
        if haskey(vi, vn)
            r = vi[vn]
        else
            r = rand(dist)
            push!(vi, vn, r, dist, Selector(:invalid))
        end
        acclogp!(vi, logpdf_with_trans(dist, r, istrans(vi, vn)))
    end
    return r, zero(Real)
end

function assume(  spl::Sampler{A},
                  dists::Vector{D},
                  vn::VarName,
                  var::Any,
                  vi::VarInfo
                ) where {A<:Union{PG,SMC},D<:Distribution}
    error("[Turing] PG and SMC doesn't support vectorizing assume statement")
end

function observe(spl::Sampler{T}, dist::Distribution, value, vi) where T<:Union{PG,SMC}
    produce(logpdf(dist, value))
    return zero(Real)
end

function observe( spl::Sampler{A},
                  ds::Vector{D},
                  value::Any,
                  vi::VarInfo
                ) where {A<:Union{PG,SMC},D<:Distribution}
    error("[Turing] PG and SMC doesn't support vectorizing observe statement")
end

####
#### Particle marginal Metropolis-Hastings sampler.
####

"""
    PMMH(n_iters::Int, smc_alg:::SMC, parameters_algs::Tuple{MH})

Particle independant Metropolis–Hastings and
Particle marginal Metropolis–Hastings samplers.

Note that this method is particle-based, and arrays of variables
must be stored in a [`TArray`](@ref) object.

Usage:

```julia
alg = PMMH(100, SMC(20, :v1), MH(1,:v2))
alg = PMMH(100, SMC(20, :v1), MH(1,(:v2, (x) -> Normal(x, 1))))
```

Arguments:

- `n_iters::Int` : Number of iterations to run.
- `smc_alg:::SMC` : An [`SMC`](@ref) algorithm to use.
- `parameters_algs::Tuple{MH}` : An [`MH`](@ref) algorithm, which includes a
sample space specification.
"""
mutable struct PMMH{space, A<:Tuple} <: InferenceAlgorithm
    n_iters               ::    Int               # number of iterations
    algs                  ::    A                 # Proposals for state & parameters
end
function PMMH(n_iters::Int, algs::A, space::Tuple) where {A <: Tuple}
    return PMMH{space, A}(n_iters, algs)
end
function PMMH(n_iters::Int, smc_alg::SMC, parameter_algs...)
    return PMMH(n_iters, tuple(parameter_algs..., smc_alg), ())
end

PIMH(n_iters::Int, smc_alg::SMC) = PMMH(n_iters, tuple(smc_alg), ())

function Sampler(alg::PMMH, model::Model, s::Selector)
    info = Dict{Symbol, Any}()
    spl = Sampler(alg, info, s)

    alg_str = "PMMH"
    n_samplers = length(alg.algs)
    samplers = Array{Sampler}(undef, n_samplers)

    space = Set{Symbol}()

    for i in 1:n_samplers
        sub_alg = alg.algs[i]
        if isa(sub_alg, Union{SMC, MH})
            samplers[i] = Sampler(sub_alg, model, Selector(Symbol(typeof(sub_alg))))
        else
            error("[$alg_str] unsupport base sampling algorithm $alg")
        end
        if typeof(sub_alg) == MH && sub_alg.n_iters != 1
            @warn("[$alg_str] number of iterations greater than 1 is useless for MH since it is only used for its proposal")
        end
        space = (space..., getspace(sub_alg)...)
    end

    # Sanity check for space
    if !isempty(space)
        @assert issubset(get_pvars(model), space) "[$alg_str] symbols specified to samplers ($space) doesn't cover the model parameters ($(get_pvars(model)))"

        if !(issetequal(get_pvars(model), space))
            @warn("[$alg_str] extra parameters specified by samplers don't exist in model: $(setdiff(space, get_pvars(model)))")
        end
    end

    info[:old_likelihood_estimate] = -Inf # Force to accept first proposal
    info[:old_prior_prob] = 0.0
    info[:samplers] = samplers

    return spl
end

function step(model, spl::Sampler{<:PMMH}, vi::VarInfo, is_first::Bool)
    violating_support = false
    proposal_ratio = 0.0
    new_prior_prob = 0.0
    new_likelihood_estimate = 0.0
    old_θ = copy(vi[spl])

    Turing.DEBUG && @debug "Propose new parameters from proposals..."
    for local_spl in spl.info[:samplers][1:end-1]
        Turing.DEBUG && @debug "$(typeof(local_spl)) proposing $(getspace(local_spl.alg))..."
        propose(model, local_spl, vi)
        if local_spl.info[:violating_support] violating_support=true; break end
        new_prior_prob += local_spl.info[:prior_prob]
        proposal_ratio += local_spl.info[:proposal_ratio]
    end

    if !violating_support # do not run SMC if going to refuse anyway
        Turing.DEBUG && @debug "Propose new state with SMC..."
        vi, _ = step(model, spl.info[:samplers][end], vi)
        new_likelihood_estimate = spl.info[:samplers][end].info[:logevidence][end]

        Turing.DEBUG && @debug "computing accept rate α..."
        is_accept, _ = mh_accept(
          -(spl.info[:old_likelihood_estimate] + spl.info[:old_prior_prob]),
          -(new_likelihood_estimate + new_prior_prob),
          proposal_ratio,
        )
    end

    Turing.DEBUG && @debug "decide whether to accept..."
    if !violating_support && is_accept # accepted
        is_accept = true
        spl.info[:old_likelihood_estimate] = new_likelihood_estimate
        spl.info[:old_prior_prob] = new_prior_prob
    else                      # rejected
        is_accept = false
        vi[spl] = old_θ
    end

    return vi, is_accept
end

function sample(  model::Model,
                  alg::PMMH;
                  save_state=false,         # flag for state saving
                  resume_from=nothing,      # chain to continue
                  reuse_spl_n=0             # flag for spl re-using
                )

    spl = Sampler(alg, model)
    if resume_from != nothing
        spl.selector = resume_from.info[:spl].selector
    end
    alg_str = "PMMH"

    # Number of samples to store
    sample_n = spl.alg.n_iters

    # Init samples
    time_total = zero(Float64)
    samples = Array{Sample}(undef, sample_n)
    weight = 1 / sample_n
    for i = 1:sample_n
        samples[i] = Sample(weight, Dict{Symbol, Any}())
    end

    # Init parameters
    vi = if resume_from == nothing
        vi_ = VarInfo(model)
    else
        resume_from.info[:vi]
    end
    n = spl.alg.n_iters

    # PMMH steps
    accept_his = Bool[]
    PROGRESS[] && (spl.info[:progress] = ProgressMeter.Progress(n, 1, "[$alg_str] Sampling...", 0))
    for i = 1:n
      Turing.DEBUG && @debug "$alg_str stepping..."
      time_elapsed = @elapsed vi, is_accept = step(model, spl, vi, i==1)

      if is_accept # accepted => store the new predcits
          samples[i].value = Sample(vi, spl).value
      else         # rejected => store the previous predcits
          samples[i] = samples[i - 1]
      end

      time_total += time_elapsed
      push!(accept_his, is_accept)
      if PROGRESS[]
        haskey(spl.info, :progress) && ProgressMeter.update!(spl.info[:progress], spl.info[:progress].counter + 1)
      end
    end

    println("[$alg_str] Finished with")
    println("  Running time    = $time_total;")
    accept_rate = sum(accept_his) / n  # calculate the accept rate
    println("  Accept rate         = $accept_rate;")

    if resume_from != nothing   # concat samples
      pushfirst!(samples, resume_from.info[:samples]...)
    end
    c = Chain(-Inf, samples)       # wrap the result by Chain

    if save_state               # save state
      c = save(c, spl, model, vi, samples)
    end

    c
end

####
#### IMCMC Sampler.
####

"""
    IPMCMC(n_particles::Int, n_iters::Int, n_nodes::Int, n_csmc_nodes::Int)

Particle Gibbs sampler.

Note that this method is particle-based, and arrays of variables
must be stored in a [`TArray`](@ref) object.

Usage:

```julia
IPMCMC(100, 100, 4, 2)
```

Arguments:

- `n_particles::Int` : Number of particles to use.
- `n_iters::Int` : Number of iterations to employ.
- `n_nodes::Int` : The number of nodes running SMC and CSMC.
- `n_csmc_nodes::Int` : The number of CSMC nodes.
```

A paper on this can be found [here](https://arxiv.org/abs/1602.05128).
"""
mutable struct IPMCMC{space, F} <: InferenceAlgorithm
  n_particles           ::    Int         # number of particles used
  n_iters               ::    Int         # number of iterations
  n_nodes               ::    Int         # number of nodes running SMC and CSMC
  n_csmc_nodes          ::    Int         # number of nodes CSMC
  resampler             ::    F           # function to resample
end
function IPMCMC(n_particles::Int, n_iters::Int, n_nodes::Int, n_csmc_nodes::Int, resampler::F, space::Tuple) where {F}
    return IPMCMC{space, F}(n_particles, n_iters, n_nodes, n_csmc_nodes, resampler)
end
IPMCMC(n1::Int, n2::Int) = IPMCMC(n1, n2, 32, 16)
IPMCMC(n1::Int, n2::Int, n3::Int) = IPMCMC(n1, n2, n3, Int(ceil(n3/2)))
IPMCMC(n1::Int, n2::Int, n3::Int, n4::Int, ::Tuple{}) = IPMCMC(n1, n2, n3, n4)
function IPMCMC(n1::Int, n2::Int, n3::Int, n4::Int, space::Symbol...)
  IPMCMC(n1, n2, n3, n4, resample_systematic, space)
end

function Sampler(alg::IPMCMC, s::Selector)
  info = Dict{Symbol, Any}()
  spl = Sampler(alg, info, s)
  # Create SMC and CSMC nodes
  samplers = Array{Sampler}(undef, alg.n_nodes)
  # Use resampler_threshold=1.0 for SMC since adaptive resampling is invalid in this setting
  default_CSMC = CSMC(alg.n_particles, 1, alg.resampler, getspace(alg))
  default_SMC = SMC(alg.n_particles, alg.resampler, 1.0, getspace(alg))

  for i in 1:alg.n_csmc_nodes
    samplers[i] = Sampler(default_CSMC, Selector(Symbol(typeof(default_CSMC))))
  end
  for i in (alg.n_csmc_nodes+1):alg.n_nodes
    samplers[i] = Sampler(default_SMC, Selector(Symbol(typeof(default_CSMC))))
  end

  info[:samplers] = samplers

  return spl
end

function step(model, spl::Sampler{<:IPMCMC}, VarInfos::Array{VarInfo}, is_first::Bool)
    # Initialise array for marginal likelihood estimators
    log_zs = zeros(spl.alg.n_nodes)

    # Run SMC & CSMC nodes
    for j in 1:spl.alg.n_nodes
        VarInfos[j].num_produce = 0
        VarInfos[j] = step(model, spl.info[:samplers][j], VarInfos[j])[1]
        log_zs[j] = spl.info[:samplers][j].info[:logevidence][end]
    end

    # Resampling of CSMC nodes indices
    conditonal_nodes_indices = collect(1:spl.alg.n_csmc_nodes)
    unconditonal_nodes_indices = collect(spl.alg.n_csmc_nodes+1:spl.alg.n_nodes)
    for j in 1:spl.alg.n_csmc_nodes
        # Select a new conditional node by simulating cj
        log_ksi = vcat(log_zs[unconditonal_nodes_indices], log_zs[j])
        ksi = exp.(log_ksi .- maximum(log_ksi))
        c_j = wsample(ksi) # sample from Categorical with unormalized weights

        if c_j < length(log_ksi) # if CSMC node selects another index than itself
            conditonal_nodes_indices[j] = unconditonal_nodes_indices[c_j]
            unconditonal_nodes_indices[c_j] = j
        end
    end
    nodes_permutation = vcat(conditonal_nodes_indices, unconditonal_nodes_indices)

    VarInfos[nodes_permutation]
end

function sample(model::Model, alg::IPMCMC)

  spl = Sampler(alg)

  # Number of samples to store
  sample_n = alg.n_iters * alg.n_csmc_nodes

  # Init samples
  time_total = zero(Float64)
  samples = Array{Sample}(undef, sample_n)
  weight = 1 / sample_n
  for i = 1:sample_n
    samples[i] = Sample(weight, Dict{Symbol, Any}())
  end

  # Init parameters
  vi = empty!(VarInfo(model))
  VarInfos = Array{VarInfo}(undef, spl.alg.n_nodes)
  for j in 1:spl.alg.n_nodes
    VarInfos[j] = deepcopy(vi)
  end
  n = spl.alg.n_iters

  # IPMCMC steps
  if PROGRESS[] spl.info[:progress] = ProgressMeter.Progress(n, 1, "[IPMCMC] Sampling...", 0) end
  for i = 1:n
    Turing.DEBUG && @debug "IPMCMC stepping..."
    time_elapsed = @elapsed VarInfos = step(model, spl, VarInfos, i==1)

    # Save each CSMS retained path as a sample
    for j in 1:spl.alg.n_csmc_nodes
      samples[(i-1)*alg.n_csmc_nodes+j].value = Sample(VarInfos[j], spl).value
    end

    time_total += time_elapsed
    if PROGRESS[]
      haskey(spl.info, :progress) && ProgressMeter.update!(spl.info[:progress], spl.info[:progress].counter + 1)
    end
  end

  println("[IPMCMC] Finished with")
  println("  Running time    = $time_total;")

  Chain(0.0, samples) # wrap the result by Chain
end

####
#### Resampling schemes for particle filters
####

# Some references
#  - http://arxiv.org/pdf/1301.4019.pdf
#  - http://people.isy.liu.se/rt/schon/Publications/HolSG2006.pdf
# Code adapted from: http://uk.mathworks.com/matlabcentral/fileexchange/24968-resampling-methods-for-particle-filtering

# Default resampling scheme
function resample(w::AbstractVector{<:Real}, num_particles::Integer=length(w))
    return resample_systematic(w, num_particles)
end

# More stable, faster version of rand(Categorical)
function randcat(p::AbstractVector{T}) where T<:Real
    r, s = rand(T), 1
    for j in eachindex(p)
        r -= p[j]
        if r <= zero(T)
            s = j
            break
        end
    end
    return s
end

function resample_multinomial(w::AbstractVector{<:Real}, num_particles::Integer)
    return rand(Distributions.sampler(Categorical(w)), num_particles)
end

function resample_residual(w::AbstractVector{<:Real}, num_particles::Integer)

    M = length(w)

    # "Repetition counts" (plus the random part, later on):
    Ns = floor.(length(w) .* w)

    # The "remainder" or "residual" count:
    R = Int(sum(Ns))

    # The number of particles which will be drawn stocastically:
    M_rdn = num_particles - R

    # The modified weights:
    Ws = (M .* w - floor.(M .* w)) / M_rdn

    # Draw the deterministic part:
    indx1, i = Array{Int}(undef, R), 1
    for j in 1:M
        for k in 1:Ns[j]
            indx1[i] = j
            i += 1
        end
    end

    # And now draw the stocastic (Multinomial) part:
    return append!(indx1, rand(Distributions.sampler(Categorical(w)), M_rdn))
end

function resample_stratified(w::AbstractVector{<:Real}, num_particles::Integer)

    Q, N = cumsum(w), num_particles

    T = Array{Float64}(undef, N + 1)
    for i=1:N,
        T[i] = rand() / N + (i - 1) / N
    end
    T[N+1] = 1

    indx, i, j = Array{Int}(undef, N), 1, 1
    while i <= N
        if T[i] < Q[j]
            indx[i] = j
            i += 1
        else
            j += 1
        end
    end
    return indx
end

function resample_systematic(w::AbstractVector{<:Real}, num_particles::Integer)

    Q, N = cumsum(w), num_particles

    T = collect(range(0, stop = maximum(Q)-1/N, length = N)) .+ rand()/N
    push!(T, 1)

    indx, i, j = Array{Int}(undef, N), 1, 1
    while i <= N
        if T[i] < Q[j]
            indx[i] = j
            i += 1
        else
            j += 1
        end
    end
    return indx
end