text:
using PyCall, Compat
using Compat.Test, Compat.Dates, Compat.Serialization

filter(f, itr) = collect(Iterators.filter(f, itr))
filter(f, d::AbstractDict) = Base.filter(f, d)

PYTHONPATH=get(ENV,"PYTHONPATH","")
PYTHONHOME=get(ENV,"PYTHONHOME","")
PYTHONEXECUTABLE=get(ENV,"PYTHONEXECUTABLE","")
Compat.@info "Python version $pyversion from $(PyCall.libpython), PYTHONHOME=$(PyCall.PYTHONHOME)\nENV[PYTHONPATH]=$PYTHONPATH\nENV[PYTHONHOME]=$PYTHONHOME\nENV[PYTHONEXECUTABLE]=$PYTHONEXECUTABLE"

roundtrip(T, x) = convert(T, PyObject(x))
roundtrip(x) = roundtrip(PyAny, x)
roundtripeq(T, x) = roundtrip(T, x) == x
roundtripeq(x) = roundtrip(x) == x

@pyimport math

struct TestConstruct
    x
end

pymodule_exists(s::AbstractString) = !ispynull(pyimport_e(s))

# default integer type for PyAny conversions
const PyInt = pyversion < v"3" ? Int : Clonglong

@testset "PyCall" begin
    # conversion of NumPy scalars before npy_initialized by array conversions (#481)
    np = pyimport_e("numpy")
    if !ispynull(np) # numpy is installed, so test
        let o = get(pycall(np["array"], PyObject, 1:3), PyObject, 2)
            @test convert(Int32, o) === Int32(3)
            @test convert(Int64, o) === Int64(3)
            @test convert(Float64, o) === Float64(3)
            @test convert(Complex{Int}, o) === 3+0im
        end
    end

    # test handling of type-tuple changes in Julia 0.4
    import PyCall.pyany_toany
    @test pyany_toany(Int) == Int
    @test pyany_toany(PyAny) == Any
    @test pyany_toany(Tuple{Int,PyAny}) == Tuple{Int,Any}
    @test pyany_toany(Tuple{Int,Tuple{PyAny,Int8}}) == Tuple{Int,Tuple{Any,Int8}}
    @test pyany_toany(Tuple{PyAny,Int,Vararg{PyAny}}) == Tuple{Any,Int,Vararg{Any}}

    @test roundtripeq(17)
    @test roundtripeq(0x39)
    @test roundtripeq(true) && roundtripeq(false)
    @test roundtripeq(3.14159)
    @test roundtripeq(1.3+4.5im)
    @test roundtripeq(nothing)
    @test roundtripeq("Hello world")
    @test roundtripeq("Hëllö")
    @test roundtripeq("Hello \0\0\0world")
    @test roundtripeq("Hël\0\0lö")
    @test roundtripeq(Symbol, :Hello)
    @test roundtripeq(C_NULL) && roundtripeq(convert(Ptr{Cvoid}, 12345))
    @test roundtripeq([1,3,4,5]) && roundtripeq([1,3.2,"hello",true])
    @test roundtripeq([1 2 3;4 5 6]) && roundtripeq([1. 2 3;4 5 6])
    @test roundtripeq((1,(3.2,"hello"),true)) && roundtripeq(())
    @test roundtripeq(Int32)
    @test roundtripeq(Dict(1 => "hello", 2 => "goodbye")) && roundtripeq(Dict())
    @test roundtripeq(UInt8[1,3,4,5])
    @test roundtrip(3 => 4) == (3,4)
    @test roundtrip(Pair{Int,Int}, 3 => 4) == Pair(3,4)
    @test eltype(roundtrip([Ref(1), Ref(2)])) == typeof(Ref(1))

    @test pycall(PyObject(x -> x + 1), PyAny, 314158) == 314159
    @test PyObject(x -> x + 1)(314158) == 314159
    @test PyAny(PyObject(3)) == 3
    @test roundtrip(x -> x + 1)(314158) == 314159

    testkw(x; y=0) = x + 2*y
    @test pycall(PyObject(testkw), PyAny, 314157) == 314157
    @test pycall(PyObject(testkw), PyAny, 314157, y=1) == 314159
    @test roundtrip(testkw)(314157) == 314157
    @test roundtrip(testkw)(314157, y=1) == 314159

    # check type stability of pycall with an explicit return type
    @inferred pycall(PyObject(1)[:__add__], Int, 2)

    if PyCall.npy_initialized
        @test PyArray(PyObject([1. 2 3;4 5 6])) == [1. 2 3;4 5 6]
        let A = rand(Int, 2,3,4), B = rand(Bool, 2,3,4)
            @test convert(PyAny, PyReverseDims(A)) == permutedims(A, [3,2,1])
            @test convert(PyAny, PyReverseDims(BitArray(B))) == permutedims(B, [3,2,1])
        end
    end
    @test PyVector(PyObject([1,3.2,"hello",true])) == [1,3.2,"hello",true]
    @test PyDict(PyObject(Dict(1 => "hello", 2 => "goodbye"))) == Dict(1 => "hello", 2 => "goodbye")
    @test roundtripeq(BitArray([true, false, true, true]))

    let d = PyDict(Dict(1 => "hello", 34 => "yes" ))
        @test get(d.o, 1) == "hello"
        set!(d.o, 34, "goodbye")
        @test d[34] == "goodbye"
        @test sort!(keys(Int, d)) == sort!(collect(d.o[:keys]())) == sort!(collect(keys(d))) == [1, 34]
        @test eltype(d) == eltype(typeof(d)) == Pair{Int, String}
    end

    let d = Dict(zip(1:1000, 1:1000)), f
        f(k,v) = iseven(k) # For 0.6
        f(kv) = iseven(kv[1]) # For 0.7
        @test filter(f, d) == filter(f, PyDict(d)) == filter!(f, PyDict(d)) ==
              Dict(zip(2:2:1000, 2:2:1000))
    end

    @test roundtripeq(Any[1 2 3; 4 5 6])
    @test roundtripeq([])
    @test convert(Array{PyAny,1}, PyObject(Any[1 2 3; 4 5 6])) == Any[Any[1,2,3],Any[4,5,6]]
    if PyCall.npy_initialized
        @test roundtripeq(begin A = Array{Int}(undef); A[1] = 3; A; end)
    end
    @test convert(PyAny, PyObject(begin A = Array{Any}(undef); A[1] = 3; A; end)) == 3

    array2py2arrayeq(x) = PyCall.py2array(Float64,PyCall.array2py(x)) == x
    @test array2py2arrayeq(rand(3))
    @test array2py2arrayeq(rand(3,4))
    @test array2py2arrayeq(rand(3,4,5))

    @test roundtripeq(2:10) && roundtripeq(10:-1:2)
    @test roundtrip(2:2.0:10) == convert(Vector{Float64}, 2:2.0:10)

    @test math.sin(3) ≈ sin(3)

    @test collect(PyObject([1,"hello",5])) == [1,"hello",5]

    @test try @eval (@pyimport os.path) catch ex
        if VERSION >= v"0.7.0-DEV.1729"
            ex = (ex::LoadError).error
        end
        isa(ex, ArgumentError)
    end

    @test PyObject("hello") == PyObject("hello")
    @test PyObject("hello") != PyObject("hellö")
    @test PyObject(hash) == PyObject(hash)
    @test PyObject(hash) != PyObject(println)
    @test hash(PyObject("hello")) == hash(PyObject("hello"))
    @test hash(PyObject("hello")) != hash(PyObject("hellö"))
    @test hash(PyObject("hello")) != hash("hellö")
    @test hash(PyObject(hash)) == hash(PyObject(hash))
    @test hash(PyObject(hash)) != hash(PyObject(println))
    @test hash(PyObject(hash)) != hash(hash)

    # issue #92:
    let x = PyVector(PyAny[])
        py"lambda x: x.append(\"bar\")"(x)
        @test x == ["bar"]
    end

    @test roundtripeq(Dates.Date(2012,3,4))
    @test roundtripeq(Dates.DateTime(2012,3,4, 7,8,9,11))
    @test roundtripeq(Dates.Millisecond(typemax(Int32)))
    @test roundtripeq(Dates.Millisecond(typemin(Int32)))
    @test roundtripeq(Dates.Second, Dates.Second(typemax(Int32)))
    @test roundtripeq(Dates.Second, Dates.Second(typemin(Int32)))
    @test roundtripeq(Dates.Day, Dates.Day(999999999)) # max allowed day timedelta
    @test roundtripeq(Dates.Day, Dates.Day(-999999999)) # min allowed day timedelta

    # fixme: is there any nontrivial showable test we can do?
    @test !showable("text/html", PyObject(1))

    # in Python 3, we need a specific encoding to write strings or bufferize them
    # (http://stackoverflow.com/questions/5471158/typeerror-str-does-not-support-the-buffer-interface)
    pyutf8(s::PyObject) = pycall(s["encode"], PyObject, "utf-8")
    pyutf8(s::String) = pyutf8(PyObject(s))

    # IO (issue #107)
    #@test roundtripeq(stdout) # No longer true since #250
    let buf = Compat.IOBuffer(read=false, write=true), obuf = PyObject(buf)
        @test !obuf[:isatty]()
        @test obuf[:writable]()
        @test !obuf[:readable]()
        @test obuf[:seekable]()
        obuf[:write](pyutf8("hello"))
        obuf[:flush]()  # should be a no-op, since there's no flushing IOBuffer
        @test position(buf) == obuf[:tell]() == 5
        let p = obuf[:seek](-2, 1)
            @test p == position(buf) == 3
        end
        let p = obuf[:seek](0, 0)
            @test p == position(buf) == 0
        end
        @test String(take!(buf)) == "hello"
        obuf[:writelines](["first\n", "second\n", "third"])
        @test String(take!(buf)) == "first\nsecond\nthird"
        obuf[:write](b"möre stuff")
        @test String(take!(buf)) == "möre stuff"
        @test isopen(buf) == !obuf[:closed] == true
        obuf[:close]()
        @test isopen(buf) == !obuf[:closed] == false
    end
    let buf = IOBuffer("hello\nagain"), obuf = PyObject(buf)
        @test !obuf[:writable]()
        @test obuf[:readable]()
        @test obuf[:readlines]() == ["hello\n", "again"]
    end
    let buf = IOBuffer("hello\nagain"), obuf = PyObject(buf)
        @test codeunits(obuf[:read](5)) == b"hello"
        @test codeunits(obuf[:readall]()) == b"\nagain"
    end
    let buf = IOBuffer("hello\nagain"), obuf = PyTextIO(buf)
        @test obuf[:encoding] == "UTF-8"
        @test obuf[:read](3) == "hel"
        @test obuf[:readall]() == "lo\nagain"
    end
    let nm = tempname()
        open(nm, "w") do f
            # @test roundtripeq(f)  # PR #250
            pf = PyObject(f)
            @test pf[:fileno]() == fd(f)
            @test pf[:writable]()
            @test !pf[:readable]()
            pf[:write](pyutf8(nm))
            pf[:flush]()
        end
        @test read(nm, String) == nm
    end

    # issue #112
    @test roundtripeq(Array, [1,2,3,4])
    @test roundtripeq(Array{Int8}, [1,2,3,4])

    # conversion of numpy scalars
    pyanycheck(x::Any) = pyanycheck(typeof(x), PyObject(x))
    pyanycheck(T, o::PyObject) = isa(convert(PyAny, o), T)
    @test pyanycheck(PyInt, PyVector{PyObject}(PyObject([1]))[1])
    @test pyanycheck(Float64, PyVector{PyObject}(PyObject([1.3]))[1])
    @test pyanycheck(ComplexF64, PyVector{PyObject}(PyObject([1.3+1im]))[1])
    @test pyanycheck(Bool, PyVector{PyObject}(PyObject([true]))[1])

    # conversions of Int128 and BigInt
    let i = 1234567890123456789 # Int64
        @test PyObject(i) - i == 0
    end
    let i = 12345678901234567890 # Int128
        @test PyObject(i) - i == 0
    end
    let i = BigInt(12345678901234567890), o = PyObject(i) # BigInt
        @test o - i == 0
        @test BigInt(o) == i
        if pyversion >= v"3.2"
            @test PyAny(o) == i == convert(Integer, o)
            @test_throws InexactError Int64(o)
        end
    end

    # bigfloat conversion
    if pymodule_exists("mpmath")
        for x in (big(pi), big(pi) + im/big(pi))
            @test pyanycheck(x)
            # conversion may not be exact since it goes through a decimal string
            @test abs(roundtrip(x) - x) < eps(BigFloat) * 1e3 * abs(x)
        end
    end
    @test convert(BigInt, PyObject(1234)) == 1234

    # buffers
    let b = PyCall.PyBuffer(pyutf8("test string"))
        @test ndims(b) == 1
        @test (length(b),) == (length("test string"),) == (size(b, 1),) == size(b)
        @test stride(b, 1) == 1
        @test PyCall.iscontiguous(b) == true
    end

    let o = PyObject(1+2im)
        @test haskey(o, :real)
        @test :real in keys(o)
        @test o[:real] == 1
    end

    # []-based sequence access
    let a1=[5,8,6], a2=rand(3,4), a3=rand(3,4,5), o1=PyObject(a1), o2=PyObject(a2), o3=PyObject(a3)
        @test [o1[i] for i in eachindex(a1)] == a1
        @test [o1[end-(i-1)] for i in eachindex(a1)] == reverse(a1)
        @test o2[1] == collect(a2[1,:])
        @test length(o1) == length(o2) == length(o3) == 3
        o1[end-1] = 7
        @test o1[2] == 7

        # multiple indices are passed as tuples, but this is apparently
        # only supported by numpy arrays.
        if PyCall.npy_initialized
            @test [o2[i,j] for i=1:3, j=1:4] == a2
            @test [o3[i,j,k] for i=1:3, j=1:4, k=1:5] == a3
            @test o3[2,3] == collect(a3[2,3,:])
            o2[2,3] = 8
            @test o2[2,3] == 8
            o3[2,3,4] = 9
            @test o3[2,3,4] == 9
        end
    end

    # list operations:
    let o = PyObject(Any[8,3])
        @test collect(push!(o, 5)) == [8,3,5]
        @test pop!(o) == 5 && collect(o) == [8,3]
        @test popfirst!(o) == 8 && collect(o) == [3]
        @test collect(pushfirst!(o, 9)) == [9,3]
        @test collect(prepend!(o, [5,4,2])) == [5,4,2,9,3]
        @test collect(append!(o, [1,6,8])) == [5,4,2,9,3,1,6,8]
        @test isempty(empty!(o))
    end
    let o = PyObject(Any[8,3])
        @test collect(append!(o, o)) == [8,3,8,3]
        push!(o, 1)
        @test collect(prepend!(o, o)) == [8,3,8,3,1,8,3,8,3,1]
    end

    # issue #216:
    @test length(collect(pyimport("itertools")[:combinations]([1,2,3],2))) == 3

    # PyNULL and copy!
    let x = PyNULL(), y = copy!(x, PyObject(314159))
        @test Int(x) == Int(y) == 314159
    end
    @test ispynull(PyNULL())
    @test !ispynull(PyObject(3))
    @test ispynull(pydecref(PyObject(3)))

    @test !ispynull(pyimport_conda("inspect", "not a conda package"))
    import Conda
    if PyCall.conda
        # import pyzmq to test PR #294
        let already_installed = "pyzmq" ∈ Conda._installed_packages()
            @test !ispynull(pyimport_conda("zmq", "pyzmq"))
            @test "pyzmq" ∈ Conda._installed_packages()
            if !already_installed
                Conda.rm("pyzmq")
            end
        end
    end

    let x = 7
        py"""
        def myfun(x):
            return x + $x
        """
        @test py"1 + 2" == 3
        @test py"1 + $x" == 8
        @test py"1 + $(x^2)" == 50
        @test py"myfun"(10) == 17
    end

    # issue #352
    let x = "1+1"
        @test py"$x" == "1+1"
        @test py"$$x" == py"$$(x)" == 2
        @test py"7 - $$x - 7" == 0 # evaluates "7 - 1 + 1 - 7"
        @test py"7 - ($$x) - 7" == -2 # evaluates "7 - (1 + 1) - 7"
        @test py"1 + $$(x[1:2]) 3" == 5 # evals 1 + 1+ 3
    end

    # Float16 support:
    if PyCall.npy_initialized
        @test roundtripeq(Float16[17 18 Inf -Inf -0.0 0.0])
        @test isa(roundtrip(Float16[17]), Vector{Float16})
    end

    """
    foobar doc
    """
    foobar(x) = x+1

    # function attributes
    let o = PyObject(foobar)
        @test o[:__name__] == o[:func_name] == string(foobar)
        @test o[:__doc__] == o[:func_doc] == "foobar doc\n"
        @test o[:__module__] == o[:__defaults__] == o[:func_defaults] ==
              o[:__closure__] == o[:func_closure] == nothing
    end

    # issue #345
    let weakdict = pyimport("weakref")["WeakValueDictionary"]
        # (use weakdict for the value, since Python supports
        #  weak references to type objects)
        @test convert(Dict{Int,PyObject}, weakdict(Dict(3=>weakdict))) == Dict(3=>weakdict)
        @test get(weakdict(Dict(3=>weakdict)),3) == weakdict
    end

    # Expose python docs to Julia doc system
    py"""
    def foo():
        "foo docstring"
        return 0
    """
    global foo354 = py"foo"
    # use 'content' since `Text` objects test equality by object identity
    @test @doc(foo354).content == "foo docstring"

    # binary operators
    for b in (4, PyObject(4))
        for op in (+, -, *, /, %, &, |, ^, <<, >>, ⊻)
            let x = op(PyObject(111), b)
                @test isa(x, PyObject)
                @test convert(PyAny, x) == op(111, 4)
            end
            @test convert(PyAny, op(b, PyObject(3))) == op(4, 3)
        end
    end
    @test convert(PyAny, PyObject(3)^4)  == 3^4 # literal integer powers
    @test convert(PyAny, PyObject(3)^0)  == 1   # literal integer powers
    @test convert(PyAny, PyObject(2)^-1) == 0.5 # literal integer powers
    # unary operators
    for op in (+, -, ~, abs)
        let x = op(PyObject(-3))
            @test isa(x, PyObject)
            @test convert(PyAny, x) == op(-3)
        end
    end
    # comparisons
    for x in (3,4,5), y in (3.0,4.0,5.0)
        for op in (<, <=, ==, !=, >, >=, isless, isequal)
            @test op(PyObject(x), PyObject(y)) == op(x, y)
            if op != isequal
                @test op(PyObject(x), y) == op(x, y)
            end
        end
    end

    # updating operators .+= etcetera
    let o = PyObject(Any[1,2]), c = o
        broadcast!(+, o, o, Any[3,4]) # o .+= x doesn't work yet in 0.7
        @test collect(o) == [1,2,3,4]
        @test o.o == c.o # updated in-place
    end

    # more flexible bool conversions, matching Python "truth value testing"
    @test convert(Bool, PyObject(nothing)) === false
    @test convert(Bool, PyObject(0.0)) === false
    @test convert(Bool, PyObject(Any[])) === false
    @test convert(Bool, PyObject(17.3)) === true
    @test convert(Bool, PyObject(Any[0])) === true
    @test convert(Bool, PyVector{PyObject}(PyObject([false]))[1]) === false

    # serialization
    let py_sum_obj = pybuiltin("sum")
        b = IOBuffer()
        serialize(b, py_sum_obj)
        @test py_sum_obj == deserialize(seekstart(b))

        b = IOBuffer()
        serialize(b, PyNULL())
        @test PyNULL() == deserialize(seekstart(b))
    end

    # @pycall macro expands correctly
    _pycall = GlobalRef(PyCall,:pycall)
    @test macroexpand(@__MODULE__, :(@pycall foo(bar)::T)) == :($(_pycall)(foo, T, bar))
    @test macroexpand(@__MODULE__, :(@pycall foo(bar, args...)::T)) == :($(_pycall)(foo, T, bar, args...))
    @test macroexpand(@__MODULE__, :(@pycall foo(bar; kwargs...)::T)) == :($(_pycall)(foo, T, bar; kwargs...))

    # basic @pywith functionality
    fname = tempname()
    try
        @test begin
            @pywith pybuiltin("open")(fname,"w") as f begin
                f[:write]("test")
            end
            open(io->read(io, String), fname)=="test"
        end
    finally
        rm(fname,force=true)
    end

    @test occursin("integer", Base.Docs.doc(PyObject(1)).content)
    @test occursin("no docstring", Base.Docs.doc(PyObject(py"lambda x: x+1")).content)

    let b = rand(UInt8, 1000)
        @test(convert(Vector{UInt8}, pybytes(b)) == b
              == convert(Vector{UInt8}, pybytes(String(copy(b))))
              == convert(Vector{UInt8}, pybytes(codeunits(String(copy(b))))))
    end

    let t = convert(Tuple, PyObject((3,34)))
        @test isa(t, Tuple{PyObject,PyObject})
        @test t == (PyObject(3), PyObject(34))
    end
    for T in (Tuple{Vararg{PyAny}}, NTuple{2,PyInt}, Tuple{PyInt,PyInt}, Tuple{Vararg{PyInt}}, Tuple{PyInt,Vararg{PyInt}})
        let t = convert(T, PyObject((3,34)))
            @test isa(t, Tuple{PyInt,PyInt})
            @test t == (3,34)
        end
    end
    @test_throws BoundsError convert(NTuple{3,Int}, PyObject((3,34)))

    let p = PyCall.pickle(), buf = IOBuffer()
        p[:dump]("hello world", buf)
        p[:dump](314159, buf)
        p[:dump](Any[1,1,2,3,5,8], buf)
        @test p[:load](seekstart(buf)) == "hello world"
        @test p[:load](buf) == 314159
        @test p[:load](buf) == [1,1,2,3,5,8]
    end

    # Test that we can call constructors on the python side
    @test pycall(PyObject(TestConstruct), PyAny, 1).x == 1

    # Test getattr fallback
    @test PyObject(TestConstruct(1))[:x] == 1
    @test_throws KeyError PyObject(TestConstruct(1))[:y]

    # iterating over Julia objects in Python:
    @test py"[x**2 for x in $(PyCall.pyjlwrap_new(1:4))]" ==
          py"[x**2 for x in $(x for x in 1:4)]" ==
          py"[x**2 for x in $(PyCall.jlwrap_iterator(1:4))]" ==
          [1,4,9,16]

    let o = PyObject("foo")
        @test pystr(o) == "foo"
        @test pyrepr(o) == "'foo'"
    end

    # pyfunction
    @test pyfunction(factorial, Int)(3) === PyInt(6)
    @test pyfunction(sin, Complex{Int})(3) === sin(3+0im)
    @test pyfunctionret(factorial, Float64, Int)(3) === 6.0
    @test pyfunctionret(factorial, nothing, Int)(3) === nothing
    @test PyCall.is_pyjlwrap(pycall(pyfunctionret(factorial, Any, Int), PyObject, 3))
    @test pyfunctionret(max, Int, Vararg{Int})(3,4,5) === PyInt(5)

    # broadcasting scalars
    let o = PyObject(3) .+ [1,4]
        @test o isa Vector{PyObject}
        @test o == [4,7]
    end
end

######################################################################
#@pydef tests: type declarations need to happen at top level

# issue #389
@pydef mutable struct EmptyClass
end

# @pywith errors correctly handled
@pydef mutable struct IgnoreError
    function __init__(self, ignore)
        self[:ignore] = ignore
    end
    __enter__(self) = ()
    __exit__(self, typ, value, tb) = self[:ignore]
end

# @pydef example from README
@pydef mutable struct Doubler <: PyCall.builtin[:AssertionError]
    __init__(self, x=10) = (self[:x] = x)
    function my_method(self, arg1::Number)
        return arg1 + 20
    end
    type_str(self, obj::T) where T = string(T)
    x2.get(self) = self[:x] * 2
    function x2.set!(self, new_val)
        self[:x] = new_val / 2
    end
end

@testset "pydef" begin
    d = Doubler(5)
    @test d[:x] == 5
    d[:x2] = 30
    @test d[:x] == 15
    @test d[:type_str](10) == string(PyInt)
    @test PyCall.builtin[:isinstance](d, PyCall.builtin[:AssertionError])

    @test_throws ErrorException @pywith IgnoreError(false) error()
    @test (@pywith IgnoreError(true) error(); true)
end

include("test_pyfncall.jl")


text:
#!NOTE: Only methods for FlattenContinuous given here, so if all flattened, defaults to Real construct_flatten/unflatten
#!NOTE2: need to use unflattentype::UnflattenStrict/UnflattenFlexible instead of ::U so specializes correctly
############################################################################################
function construct_flatten(
    output::Type{T},
    flattentype::FlattenContinuous,
    unflattentype::UnflattenStrict,
    x::Union{I,Array{I}}
) where {T<:AbstractFloat, I<:Integer}
    v = I[]
    flatten_to_Int(x::Union{R,AbstractArray{R}}) where {R<:Real} = v
    unflatten_to_Int(v::Union{R,AbstractVector{R}}) where {R<:Real} = x
    return flatten_to_Int, unflatten_to_Int
end
function construct_flatten(
    output::Type{T},
    flattentype::FlattenContinuous,
    unflattentype::UnflattenFlexible,
    x::Union{I,Array{I}}
) where {T<:AbstractFloat, I<:Integer}
    v = I[]
    flatten_to_Int(x::Union{R,AbstractArray{R}}) where {R<:Real} = v
    unflatten_to_Int(v::Union{R,AbstractVector{R}}) where {R<:Real} = x
    return flatten_to_Int, unflatten_to_Int
end

############################################################################################
#Export
export
    construct_flatten

ext: jl


text:
module FastParzenWindows

    using ProgressMeter, StatsFuns, Random, Printf, SparseArrays, LinearAlgebra, Distributions, MLBase

    include("fastparzenwindows.jl")

    include("softparzen.jl")

    include("partition.jl")

    include("spiraldata.jl")

    include("getmixturemodel.jl")

    include("cvfpw.jl")

    export fpw, spiraldata, cv_fpw

end


text:
# <hide>
function printsum(a)
    println(summary(a), ": ", repr(a))
end
# </hide>

# Type Definitions are probably most similar to tyepdefs in c?
# a simple type with no special constructor functions might look like this
type Person
	name::String
	male::Bool
	age::Float64
	children::Int
end

p = Person("Julia", false, 4, 0)
printsum(p)
#> Person: Person("Julia",false,4.0,0)

people = Person[]
push!(people, Person("Steve", true, 42, 0))
push!(people, Person("Jade", false, 17, 3))
printsum(people)
#> 2-element Array{Person,1}: [Person("Steve",true,42.0,0),Person("Jade",false,17.0,3)]

# types may also contains arrays and dicts
# constructor functions can be defined to easily create objects
type Family
	name::String
	members::Array{String, 1}
	extended::Bool
	# constructor that takes one argument and generates a default
	# for the other two values
	Family(name::String) = new(name, String[], false)
	# constructor that takes two arguements and infers the third
	Family(name::String, members) = new(name, members, length(members) > 3)
end

fam1 = Family("blogs")
println(fam1)
#> Family("blogs",String[],false)
fam2 = Family("jones", ["anna", "bob", "charlie", "dick"])
println(fam2)
#> Family("jones",String["anna","bob","charlie","dick"],true)

max_stars_repo_name: floswald/JuliaByExample

max_issues_repo_name: floswald/JuliaByExample

max_forks_repo_name: floswald/JuliaByExample


text:
function filter_handler(x::Union{Nothing, Dict{String, T} where T})

    if isnothing(x)
        return nothing
    end

    nn = ""
    for (k, v) in x
        nn = string(nn, k, ':', v, ',')
    end
    return nn

end

max_stars_repo_name: naustica/Pitaya

max_issues_repo_name: naustica/Pitaya

max_forks_repo_name: naustica/Pitaya


text:
module AzStorage

using AbstractStorage, AzSessions, AzStorage_jll, Base64, DelimitedFiles, HTTP, LightXML, Serialization, Sockets

# https://docs.microsoft.com/en-us/rest/api/storageservices/common-rest-api-error-codes
const RETRYABLE_HTTP_ERRORS = [
    500, # Internal server error
    503] # Service unavailable

# https://curl.haxx.se/libcurl/c/libcurl-errors.html
const RETRYABLE_CURL_ERRORS = [
    7,  # Failed to connect() to host or proxy.
    28, # Connection timed out.
    55, # Failed sendingnetworkdata.
    56] # Failure with received network data.

# https://docs.microsoft.com/en-us/rest/api/storageservices/versioning-for-the-azure-storage-services
const API_VERSION = "2020-04-08"

function __init__()
    ccall((:curl_init, libAzStorage), Cvoid, (Cint, Cint, Ptr{Clong}, Ptr{Clong}, Cstring),
        length(RETRYABLE_HTTP_ERRORS), length(RETRYABLE_CURL_ERRORS), RETRYABLE_HTTP_ERRORS, RETRYABLE_CURL_ERRORS, API_VERSION)
end

mutable struct AzContainer{A<:AzSessionAbstract} <: Container
    storageaccount::String
    containername::String
    prefix::String
    session::A
    nthreads::Int
    nretry::Int
    verbose::Int
end

function Base.copy(container::AzContainer)
    AzContainer(
        container.storageaccount,
        container.containername,
        container.prefix,
        copy(container.session),
        container.nthreads,
        container.nretry,
        container.verbose)
end

struct AzObject
    container::AzContainer
    name::String
end

"""
    open(container, blobname) -> AzObject

Create a handle to an Azure blob with the name `blobname::String` in the
Azure storage container: `container::AzContainer`.

# Example:
```julia
io = open(AzContainer("mycontainer"; storageaccount="myaccount"), "foo.bin")
write(io, rand(10))
```
"""
function Base.open(container::AzContainer, name)
    mkpath(container)
    AzObject(container, string(name))
end

"""
    joinpath(container, blobname) -> AzObject

Create a handle to an Azure blob with the name `blobname::String` in the
Azure storage container: `container::AzContainer`.

# Example:
```julia
io = joinpath(AzContainer("mycontainer"; storageaccount="myaccount"), "foo.bin")
write(io, rand(10))
```
"""
Base.joinpath(container::AzContainer, name...) = open(container, join(name, '/'))

Base.close(object::AzObject) = nothing

const __OAUTH_SCOPE = "offline_access+openid+https://storage.azure.com/user_impersonation"

"""
    container = AzContainer("containername"; storageaccount="myacccount", kwargs...)

`container` is a handle to a new or existing Azure container in the `myaccount` sorage account.
The storage account must already exist.

# Additional keyword arguments
* `session=AzSession(;lazy=true,scope=$__OAUTH_SCOPE)` user credentials (see AzSessions.jl package).
* `nthreads=Sys.CPU_THREADS` number of system threads that OpenMP will use to thread I/O.
* `nretry=10` number of retries to the Azure service (when Azure throws a retryable error) before throwing an error.
* `verbose=0` verbosity flag passed to libcurl.

# Notes
The container name can container "/"'s.  If this is the case, then the string preceding the first "/" will
be the container name, and the string that remains will be pre-pended to the blob names.  This allows Azure
to present blobs in a pseudo-directory structure.
"""
function AzContainer(containername::AbstractString; storageaccount, session=AzSession(;lazy=true, scope=__OAUTH_SCOPE), nthreads=Sys.CPU_THREADS, nretry=10, verbose=0, prefix="")
    name = split(containername, '/')
    _containername = name[1]
    prefix *= lstrip('/'*join(name[2:end], '/'), '/')
    AzContainer(String(storageaccount), String(_containername), String(prefix), session, nthreads, nretry, verbose)
end

function AbstractStorage.Container(::Type{<:AzContainer}, d::Dict, session=AzSession(;lazy=true, scope=__OAUTH_SCOPE); nthreads = Sys.CPU_THREADS, nretry=10, verbose=0)
    AzContainer(
        d["storageaccount"],
        d["containername"],
        d["prefix"],
        session,
        get(d, "nthreads", nthreads),
        get(d, "nretry", nretry),
        get(d, "verbose", verbose))
end

Base.:(==)(x::AzContainer, y::AzContainer) = x.storageaccount == y.storageaccount && x.containername == y.containername && x.prefix == y.prefix

struct ResponseCodes
    http::Int64
    curl::Int64
end

function isretryable(e::HTTP.StatusError)
    e.status ∈ RETRYABLE_HTTP_ERRORS && (return true)
    false
end
isretryable(e::Base.IOError) = true
isretryable(e::HTTP.IOExtras.IOError) = isretryable(e.e)
isretryable(e::Base.EOFError) = true
isretryable(e::Sockets.DNSError) = Base.uverrorname(e.code) == "EAI_NONAME" ? false : true
isretryable(e) = false

function retrywarn(i, s, e)
    @debug "retry $i, sleeping for $s seconds, e=$e"
end

macro retry(retries, ex::Expr)
    quote
        local r
        for i = 1:$(esc(retries))
            try
                r = $(esc(ex))
                break
            catch e
                (i <= $(esc(retries)) && isretryable(e)) || rethrow(e)
                maximum_backoff = 256
                s = min(2.0^(i-1), maximum_backoff) + rand()
                retrywarn(i, s, e)
                sleep(s)
            end
        end
        r
    end
end

"""
    mkpath(container)

create an Azure container from the handle `container::AzContainer`.  If the container
already exists, then this is a no-op.
"""
function Base.mkpath(c::AzContainer)
    if !iscontainer(c)
        @retry c.nretry HTTP.request(
            "PUT",
            "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)?restype=container",
            Dict(
                "Authorization" => "Bearer $(token(c.session))",
                "x-ms-version" => API_VERSION),
            retry = false)
    end
    nothing
end

# 4000 MiB, there are 2^20 bytes in one MiB
const _MINBYTES_PER_BLOCK = 32 * 2^20
const _MAXBYTES_PER_BLOCK = 4_000 * 2^20
const _MAXBLOCKS_PER_BLOB = 50_000

nblocks_error() = error("data is too large for a block-blob")
function nblocks(nthreads::Integer, nbytes::Integer)
    nblocks = ceil(Int, nbytes/_MAXBYTES_PER_BLOCK + eps(Float64))
    if nblocks < nthreads
        nblocks = clamp(ceil(Int, nbytes/_MINBYTES_PER_BLOCK + eps(Float64)), 1, nthreads)
    end
    nblocks > _MAXBLOCKS_PER_BLOB && nblocks_error()
    nblocks
end

_normpath(s) = Sys.iswindows() ? replace(normpath(s), "\\"=>"/") : normpath(s)

addprefix(c::AzContainer, o) = c.prefix == "" ? o : _normpath("$(c.prefix)/$o")

function writebytes(c::AzContainer, o::AbstractString, data::DenseArray{UInt8}; contenttype="application/octet-stream")
    function writebytes_blob(c, o, data, contenttype)
        @retry c.nretry HTTP.request(
            "PUT",
            "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)/$(addprefix(c,o))",
            Dict(
                "Authorization" => "Bearer $(token(c.session))",
                "x-ms-version" => API_VERSION,
                "Content-Length" => "$(length(data))",
                "Content-Type" => contenttype,
                "x-ms-blob-type" => "BlockBlob"),
            data,
            retry = false,
            verbose = c.verbose)
        nothing
    end

    function putblocklist(c, o, blockids)
        xdoc = XMLDocument()
        xroot = create_root(xdoc, "BlockList")
        for blockid in blockids
            add_text(new_child(xroot, "Uncommitted"), blockid)
        end
        blocklist = string(xdoc)

        @retry c.nretry HTTP.request(
            "PUT",
            "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)/$(addprefix(c,o))?comp=blocklist",
            Dict(
                "x-ms-version" => API_VERSION,
                "Authorization" => "Bearer $(token(c.session))",
                "Content-Type" => "application/octet-stream",
                "Content-Length" => "$(length(blocklist))"),
            blocklist,
            retry = false)
        nothing
    end

    function writebytes_block(c, o, data, _nblocks)
        t = token(c.session)
        l = ceil(Int, log10(_nblocks))
        blockids = [base64encode(lpad(blockid-1, l, '0')) for blockid in 1:_nblocks]
        _blockids = [HTTP.escapeuri(blockid) for blockid in blockids]
        r = ccall((:curl_writebytes_block_retry_threaded, libAzStorage), ResponseCodes,
            (Cstring, Cstring,          Cstring,         Cstring,        Ptr{Cstring}, Ptr{UInt8}, Csize_t,      Cint,       Cint,     Cint,     Cint),
             t,       c.storageaccount, c.containername, addprefix(c,o), _blockids,    data,       length(data), c.nthreads, _nblocks, c.nretry, c.verbose)
        r.http >= 300 && error("writebytes_block: error code $(r.http)")
        r.curl > 0 && error("curl error, code=$(r.curl)")

        putblocklist(c, o, blockids)
    end

    _nblocks = nblocks(c.nthreads, length(data))
    if _nblocks > 1
        writebytes_block(c, o, data, _nblocks)
    else
        writebytes_blob(c, o, data, contenttype)
    end
    nothing
end

"""
    write(container, "blobname", data::AbstractString; contenttype="text/plain")

Write the string `data` to a blob with name `blobname` in `container::AzContainer`.
Optionally, one can specify the content-type of the blob using the `contenttype` keyword argument.
For example: `content-type="text/plain", `content-type="applicaton/json", etc..
"""
Base.write(c::AzContainer, o::AbstractString, data::AbstractString; contenttype="text/plain") =
    writebytes(c, o, transcode(UInt8, data); contenttype=contenttype)

_iscontiguous(data::DenseArray) = isbitstype(eltype(data))
_iscontiguous(data::SubArray) = isbitstype(eltype(data)) && Base.iscontiguous(data)
_iscontiguous(data::AbstractArray) = false

"""
    write(container, "blobname", data::StridedArray)

Write the array `data` to a blob with the name `blobname` in `container::AzContainer`.
"""
function Base.write(c::AzContainer, o::AbstractString, data::AbstractArray{T}) where {T}
    if _iscontiguous(data)
        writebytes(c, o, unsafe_wrap(Vector{UInt8}, convert(Ptr{UInt8}, pointer(data)), length(data)*sizeof(T), own=false); contenttype="application/octet-stream")
    else
        error("AzStorage: `write` is not supported on non-isbits arrays and/or non-contiguous arrays")
    end
end

Base.write(c::AzContainer, o::AbstractString, data) = error("AzStorage: `write` is only suppoted for DenseArray.")

"""
    write(io::AzObject, data)

write data to `io::AzObject`.

# Example
```
io = open(AzContainer("mycontainer";storageaccount="mystorageaccount"), "foo.bin")
write(io, rand(10))
x = read!(io, zeros(10))
```
"""
Base.write(o::AzObject, data) = write(o.container, o.name, data)

"""
    serialize(container, "blobname", data)

Serialize and write `data` to a blob with the name `blobname` in `container::AzContainer`.

# Example
```
container = AzContainer("mycontainer";storageaccount="mystorageaccount")
serialize(container, "foo.bin", (rand(10),rand(20)))
a,b = deserialize(io)
```
"""
function Serialization.serialize(c::AzContainer, o::AbstractString, data)
    io = IOBuffer(;write=true)
    serialize(io, data)
    writebytes(c, o, take!(io); contenttype="application/octet-stream")
end

"""
    serialize(io::AzObject, data)

Serialize and write data to `io::AzObject`.  See serialize(conainer, blobname, data).
"""
Serialization.serialize(o::AzObject, data) = serialize(o.container, o.name, data)

"""
    writedlm(container, "blobname", data, args...; options...)

Write the array `data` to a delimited blob with the name `blobname` in container `container::AzContainer`
"""
function DelimitedFiles.writedlm(c::AzContainer, o::AbstractString, data::AbstractArray, args...; opts...)
    io = IOBuffer(;write=true)
    writedlm(io, data, args...; opts...)
    write(c, o, String(take!(io)))
end

"""
    writedlm(io:AzObject, data, args...; options...)

write the array `data` to `io::AzObject`

# Example
```
io = open(AzContainer("mycontainer";storageaccount="mystorageaccount"), "foo.txt")
writedlm(io, rand(10,10))
x = readdlm(io)
```
"""
function DelimitedFiles.writedlm(o::AzObject, data::AbstractArray, args...; opts...)
     writedlm(o.container, o.name, data, args...; opts...)
end

"""
    readdlm(container, "blobname", args...; options...)

Read the data in a delimited blob with the name `blobname` in container `container::AzContainer`
"""
function DelimitedFiles.readdlm(c::AzContainer, o::AbstractString, args...; opts...)
    io = IOBuffer(;write=true, read=true)
    write(io, read(c, o, String))
    seekstart(io)
    readdlm(io, args...; opts...)
end

"""
    readdlm(io:AzObject, args...; options...)

return the parsed delimited blob from the io object `io::AzObject`

# Example
```
io = open(AzContainer("mycontainer";storageaccount="mystorageaccount"), "foo.txt")
data = readdlm(io)
```
"""
function DelimitedFiles.readdlm(o::AzObject, args...; opts...)
    readdlm(o.container, o.name, args...; opts...)
end

#this is to resolve an function call ambiguity
function DelimitedFiles.readdlm(o::AzObject, delim::AbstractChar, args...; opts...)
    readdlm(o.container, o.name, delim, args...; opts...)
end

nthreads_effective(nthreads::Integer, nbytes::Integer) = clamp(div(nbytes, _MINBYTES_PER_BLOCK), 1, nthreads)

function readbytes!(c::AzContainer, o::AbstractString, data::DenseArray{UInt8}; offset=0)
    function readbytes_serial!(c, o, data, offset)
        @retry c.nretry HTTP.open(
                "GET",
                "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)/$(addprefix(c,o))",
                Dict(
                    "Authorization" => "Bearer $(token(c.session))",
                    "x-ms-version" => API_VERSION,
                    "Range" => "bytes=$offset-$(offset+length(data)-1)"),
                    retry = false,
                    verbose = c.verbose) do io
            read!(io, data)
        end
        nothing
    end

    function readbytes_threaded!(c, o, data, offset, _nthreads)
        t = token(c.session)
        r = ccall((:curl_readbytes_retry_threaded, libAzStorage), ResponseCodes,
            (Cstring, Cstring,          Cstring,         Cstring,        Ptr{UInt8}, Csize_t, Csize_t,      Cint,      Cint,     Cint),
             t,       c.storageaccount, c.containername, addprefix(c,o), data,       offset,  length(data), _nthreads, c.nretry, c.verbose)
        r.http >= 300 && error("readbytes_threaded!: error code $(r.http)")
        r.curl > 0 && error("curl error, code=$(r.curl))")
        nothing
    end

    _nthreads = nthreads_effective(c.nthreads, length(data))
    if _nthreads > 1
        readbytes_threaded!(c, o, data, offset, _nthreads)
    else
        readbytes_serial!(c, o, data, offset)
    end
    data
end

"""
    read(container, "blobname", String)

returns the contents of the blob "blobname" in `container::AzContainer` as a string.
"""
Base.read(c::AzContainer, o::AbstractString, T::Type{String}) = String(readbytes!(c, o, Vector{UInt8}(undef, filesize(c,o))))

"""
    read!(container, "blobname", data; offset=0)

read from the blob "blobname" in `container::AzContainer` into `data::DenseArray`, and
where `offset` specifies a number of bytes in the blob to skip before reading.  This
method returns `data`.  For example,
```
data = read!(AzContainer("foo";storageaccount="bar"), "baz.bin", Vector{Float32}(undef,10))
```
"""
function Base.read!(c::AzContainer, o::AbstractString, data::AbstractArray{T}; offset=0) where {T}
    if _iscontiguous(data)
        _data = unsafe_wrap(Array, convert(Ptr{UInt8}, pointer(data)), length(data)*sizeof(T), own=false)
        readbytes!(c, o, _data; offset=offset*sizeof(T))
    else
        error("AzStorage does not support reading objects of type $T and/or into a non-contiguous array.")
    end
    data
end

"""
    read(object, String)

read a string from `object::AzObject`.

# Example
```
io = open(AzContainer("mycontainer";storageaccount="mystorageaccount"), "foo.txt")
read(io, String)
```
"""
Base.read(o::AzObject, T::Type{String}) = read(o.container, o.name, String)

"""
    read!(object, x; offset=0) -> x

read data from `object::AzObject` into `x::DenseArray`,
and return `x`.  `offset` is an integer that can be
used to specify the first byte in the object to read.

# Example
```
io = open(AzContainer("mycontainer";storageaccount="mystorageaccount"), "foo.txt")
read!(io, Vector{Float64}(undef, 10))
```
"""
Base.read!(o::AzObject, data; offset=0) = read!(o.container, o.name, data; offset=offset)

"""
    deserialize(container, "blobname")

read and deserialize from a blob "blobname" in `container::AzContainer`.

# Example
```
io = open(AzContainer("mycontainer";storageaccount="mystorageaccount"), "foo.bin")
serialize(io, (rand(10),rand(20)))
a,b = deserialize(io)
```
"""
function Serialization.deserialize(c::AzContainer, o::AbstractString)
    io = IOBuffer(readbytes!(c, o, Vector{UInt8}(undef,filesize(c, o))); read=true)
    deserialize(io)
end

"""
    deserialize(object)

read and deserialize a blob `object::AzObject`.  See `deserialize(container, "blobname")`.
"""
Serialization.deserialize(o::AzObject) = deserialize(o.container, o.name)

"""
    readdir(container)

list of objects in a container.
"""
function Base.readdir(c::AzContainer; filterlist=true)
    marker = ""
    names = String[]
    while true
        r = @retry c.nretry HTTP.request(
            "GET",
            "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)?restype=container&comp=list&marker=$marker",
            Dict(
                "Authorization" => "Bearer $(token(c.session))",
                "x-ms-version" => API_VERSION),
            retry = false)
        xroot = root(parse_string(String(r.body)))
        blobs = xroot["Blobs"][1]["Blob"]
        _names = [content(blob["Name"][1]) for blob in blobs]
        if filterlist && c.prefix != ""
            _names = replace.(filter(_name->startswith(_name, c.prefix), _names), _normpath(c.prefix*"/")=>"")
        end
        names = [names; _names]
        marker = content(xroot["NextMarker"][1])
        marker == "" && break
    end
    names
end

"""
    dirname(container)

Returns the name of the Azure container that `container::AzContainer` is a handler to.
"""
function Base.dirname(c::AzContainer)
    local nm
    if c.prefix == ""
        nm = c.containername
    else
        nm = _normpath(c.containername * "/" * c.prefix)
    end
    nm
end

AbstractStorage.session(c::AzContainer) = c.session

function AbstractStorage.scrubsession!(c::AzContainer)
    scrub!(c.session)
    c
end

function AbstractStorage.scrubsession(c::AzContainer)
    _c = copy(c)
    scrub!(_c.session)
    _c
end

AbstractStorage.minimaldict(c::AzContainer) = Dict("storageaccount"=>c.storageaccount, "containername"=>c.containername, "prefix"=>c.prefix)

"""
    isfile(container, "blobname")

Returns true if the blob "blobname" exists in `container::AzContainer`.
"""
Base.isfile(c::AzContainer, object::AbstractString) = object ∈ readdir(c)

"""
    isfile(object::AzObject)

Returns true if the blob corresponding to `object` exists.
"""
Base.isfile(o::AzObject) = isfile(o.container, o.name)

iscontainer(c::AzContainer) = c.containername ∈ containers(storageaccount=c.storageaccount, session=c.session, nretry=c.nretry)

"""
    isdir(container)

Returns true if `container::AzContainer` exists.
"""
function Base.isdir(c::AzContainer)
    if !iscontainer(c)
        return false
    end
    if c.prefix == ""
        return true
    end
    !isempty(readdir(c::AzContainer))
end

"""
    containers(;storageaccount="mystorageaccount", session=AzSession(;lazy=true, scope=__OAUTH_SCOPE))

list all containers in a given storage account.
"""
function containers(;storageaccount, session=AzSession(;lazy=true, scope=__OAUTH_SCOPE), nretry=5)
    marker = ""
    names = String[]
    while true
        r = @retry nretry HTTP.request(
            "GET",
            "https://$storageaccount.blob.core.windows.net/?comp=list&marker=$marker",
            Dict(
                "Authorization" => "Bearer $(token(session))",
                "x-ms-version" => API_VERSION),
            retry = false)
        xroot = root(parse_string(String(r.body)))
        containers = xroot["Containers"][1]["Container"]
        names = [names; [content(container["Name"][1]) for container in containers]]
        marker = content(xroot["NextMarker"][1])
        marker == "" && break
    end
    names
end

"""
    filesize(container, "blobname")

Returns the size of the blob "blobname" that is in `container::AzContainer`
"""
function Base.filesize(c::AzContainer, o::AbstractString)
    r = @retry c.nretry HTTP.request(
        "HEAD",
        "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)/$(addprefix(c,o))",
        Dict(
            "Authorization" => "Bearer $(token(c.session))",
            "x-ms-version" => API_VERSION),
        retry = false)
    n = 0
    for header in r.headers
        if header.first == "Content-Length"
            n = parse(Int, header.second)
        end
    end
    n
end

"""
    filesize(object::AzObject)

Returns the size of the blob corresponding to `object::AzObject`
"""
Base.filesize(o::AzObject) = filesize(o.container, o.name)

"""
    rm(container, "blobname")

remove the blob "blobname" from `container::AzContainer`.
"""
function Base.rm(c::AzContainer, o::AbstractString)
    try
        @retry c.nretry HTTP.request(
            "DELETE",
            "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)/$(addprefix(c,o))",
            Dict(
                "Authorization" => "Bearer $(token(c.session))",
                "x-ms-version" => API_VERSION),
            retry = false)
    catch
        @warn "error removing $(c.containername)/$(addprefix(c,o))"
    end
    nothing
end

"""
    rm(object::AzObject)

remove the blob corresponding to `object::AzObject`
"""
Base.rm(o::AzObject) = rm(o.container, o.name)

"""
    rm(container)

remove `container::AzContainer` and all of its blobs.
"""
function Base.rm(c::AzContainer)
    function _rm(c::AzContainer)
        @retry c.nretry HTTP.request(
            "DELETE",
            "https://$(c.storageaccount).blob.core.windows.net/$(c.containername)?restype=container",
            Dict(
                "Authorization" => "Bearer $(token(c.session))",
                "x-ms-version" => API_VERSION),
            retry = false)
    end

    try
        if c.prefix == ""
            _rm(c)
        else
            for o in readdir(c)
                rm(c, o)
            end
            isempty(readdir(c; filterlist=false)) && _rm(c)
        end
    catch
        @warn "error removing $(c.containername)"
    end

    nothing
end

"""
    cp(container_src, container_dst)

copy `container_src::AzContainer` and its blobs to `container_dst::AzContainer`.
"""
function Base.cp(src::AzContainer, dst::AzContainer)
    mkpath(dst)

    blobs = readdir(src)
    for blob in blobs
        @retry dst.nretry HTTP.request(
            "PUT",
            "https://$(dst.storageaccount).blob.core.windows.net/$(dst.containername)/$(addprefix(dst,blob))",
            Dict(
                "Authorization" => "Bearer $(token(dst.session))",
                "x-ms-version" => API_VERSION,
                "x-ms-copy-source" => "https://$(src.storageaccount).blob.core.windows.net/$(src.containername)/$(addprefix(src,blob))"),
            retry = false)
    end
    nothing
end

export AzContainer, containers, readdlm, writedlm

end


text:
# create_test_datasets.jl
#
# Author: Tom Price
# Date: Dec 2018
#
# obtain data from databases test_1ch and test_2ch to test Julia API
# run after create_test_datasets.sh

# Test_1ch calibration

water_cal_1=
    [20351,13854,16950,18614,19292,21191,19613,21150,21611,17390,21328,23590,24131,20167,19417,25120];
signal_cal_1=
    [2037915,2030879,2356324,2286590,2578814,2660975,2390835,2290655,2419225,2240444,2734095,3069099,
2599044,2354805,2267721,2879192];

calib_1=OrderedDict(
    "water"     => Dict("fluorescence_value" => [water_cal_1,  nothing]),
    "channel_1" => Dict("fluorescence_value" => [signal_cal_1, nothing])
)

open("test_1ch_cal_168.json","w") do f
    JSON.print(f, calib_1)
end

# Test_2ch calibration

cal_water_2=[
    [12095,20829,14218,19162,14613,12937,12487,14240,7543,14187,12778,12404,13275,18710,10472,8520],
    [ 2163, 2058, 2216, 1869, 1890, 2246, 1997, 2104,2287, 1981, 2120, 3471, 1953, 2018, 1956,2196]
]
cal_FAM_2=[
    [79676,104728,95264,90512,109013,103317,92905,100809,82292,115539,93250,106691,113931,134691,78968,80789],
    [35683, 41892,39971,40173, 43931, 44413,43240, 42156,39951, 44385,40652, 40888, 45433, 47371,42637,43064]
]
cal_HEX_2=[
    [14622,21383,15831,21415,18148,16878,15657,17881,11466,17953,16429,15830,17765,22733,14600,12180],
    [41396,48051,45444,44977,50132,51689,49093,47231,43050,49144,44858,43502,48565,47037,48413,47160]
]

calib_2=OrderedDict(
    "water"     => Dict("fluorescence_value" => cal_water_2),
    "channel_1" => Dict("fluorescence_value" => cal_FAM_2),
    "channel_2" => Dict("fluorescence_value" => cal_HEX_2)
)

open("test_2ch_cal_219.json","w") do f
    JSON.print(f, calib_2)
end

# Chaipcr calibration

cal_water_175=[
        [7980,8143,8784,9404,10658,9818,4409,6788,5553,6510,8074,8162,7988,6774,7104,8159],
        [2720,2870,2719,2688, 2659,2655,2473,2560,2685,2577,2651,2695,2657,2645,2895,2613]
    ]
cal_FAM_175=[
        [68328,77769,69792,78364,73407,75765,44114,68583,42381,69996,76600,85065,82071,71601,68981,61379],
        [32592,35283,33302,34717,34300,33866,19168,26253,22305,29974,31753,34080,35184,30651,30342,30266]
    ]
cal_HEX_175=[
        [ 8871, 9694, 9222, 9996,10381,10430, 5740, 8500, 6279, 8338, 9762,10475,10346, 9289, 9528, 8971],
        [29347,34003,32414,33407,32460,31620,18094,24103,19945,27478,29500,31694,33605,28289,28734,27943]
    ]

calib_175=OrderedDict(
    "water"     => Dict("fluorescence_value" => cal_water_175),
    "channel_1" => Dict("fluorescence_value" => cal_FAM_175),
    "channel_2" => Dict("fluorescence_value" => cal_HEX_175)
)

open("chaipcr_cal_175.json","w") do f
    JSON.print(f, calib_175)
end

# Test_1ch optical calibration

oc_1=OrderedDict(
    "calibration_info" => calib_1
)

open("test_1ch_oc_168.json","w") do f
    JSON.print(f, oc_1)
end

# Test_2ch optical calibration

baseline_oc_2=[
    [1485,1448,1492,1509,1482,1484,1487,1483,1493,1484,1474,1494,1502,1508,1487,1500],
    [1917,1932,1930,1951,1918,1947,1959,1943,1948,1941,1951,1937,1947,1947,1950,1953]
]
water_oc_2=[
    [8525,8043,8755,9495,10750,10192,7013,7334,5924,6777,7875,8525,8144,7604,7884,8911],
    [2485,2593,2407,2372, 2328, 2384,2321,2267,2328,2321,2322,2427,2301,2361,2573,2316]
]
FAM_oc_2=[
    [70056,89715,72404,82865,77659,82263,78819,79312,47242,67354,81660,91499,83662,80465,76021,64812],
    [30573,37557,32803,34660,34493,34880,31446,28226,23787,28319,32130,35547,34286,32361,30811,29689]
]
HEX_oc_2=[
  [ 7848, 8121, 8223, 8805, 9573, 9535, 7176, 7193, 6186, 6962, 8166, 8643, 8419, 7705, 7996, 8477],
  [26135,28667,27338,27808,27588,26933,24851,21993,18950,24650,25694,27906,27953,25249,25187,25624]
]

chaipcr_oc_2 = OrderedDict(
    "calibration_info" => OrderedDict(
        #"baseline"  => OrderedDict("fluorescence_value" => baseline_oc_2),
        "water"     => OrderedDict("fluorescence_value" => water_oc_2),
        "channel_1" => OrderedDict("fluorescence_value" => FAM_oc_2),
        "channel_2" => OrderedDict("fluorescence_value" => HEX_oc_2)
    )
)

open("chaipcr_oc_250.json","w") do f
    JSON.print(f, chaipcr_oc_2)
end

# Test_1ch amplification

amp_169=readdlm("amp_169.tsv",'\t',header=true)
raw_amp_1=OrderedDict(
    amp_169[2][1]    => amp_169[1][:,1],
    amp_169[2][2]    => Vector{Integer}(amp_169[1][:,2]),
    amp_169[2][3]    => Vector{Integer}(amp_169[1][:,3]),
    amp_169[2][4]    => Vector{Integer}(amp_169[1][:,4])
)

amp_1=OrderedDict(
  "experiment_id"       => 169,
  "step_id"             => 400,
  "min_reliable_cyc"    => 5,
  "calibration_info"    => calib_1,
  "baseline_cyc_bounds" => Any[],
  "baseline_method"     => "sigmoid",
  "cq_method"           => "Cy0",
  "min_fluomax"         => 4356,
  "min_D1max"           => 472,
  "min_D2max"           => 41,
  "raw_data"            => raw_amp_1
)

open("test_1ch_amp_169.json","w") do f
    JSON.print(f, mc_1)
end

# Test_1ch meltcurve data

mc_170=readdlm("mc_223.tsv",'\t',header=true)
raw_mc_1=OrderedDict(
    mc_170[2][1]    => mc_170[1][:,1],
    mc_170[2][2]    => mc_170[1][:,2],
    mc_170[2][3]    => Vector{Integer}(mc_170[1][:,3]),
    mc_170[2][4]    => Vector{Integer}(mc_170[1][:,4])
)

mc_1=OrderedDict(
    "experiment_id"       => 170,
    "stage_id"            => 219,
    "calibration_info"    => calib_1,
    "channel_nums"        => [1],
    "qt_prob"             => 0.64,
    "max_normd_qtv"       => 0.8,
    "top_N"               => 4,
    "raw_data"            => raw_mc_1
)

open("test_1ch_mc_170.json","w") do f
    JSON.print(f, mc_1)
end

# Test_2ch meltcurve data

mc_223=readdlm("mc_223.tsv",'\t',header=true)
raw_mc_2=OrderedDict(
    mc_223[2][1]    => mc_223[1][:,1],
    mc_223[2][2]    => mc_223[1][:,2],
    mc_223[2][3]    => Vector{Integer}(mc_223[1][:,3]),
    mc_223[2][4]    => Vector{Integer}(mc_223[1][:,4])
)

mc_2=OrderedDict(
    "experiment_id"       => 223,
    "stage_id"            => 311,
    "calibration_info"    => calib_2,
    "channel_nums"        => [1,2],
    "qt_prob"             => 0.64,
    "max_normd_qtv"       => 0.8,
    "top_N"               => 4,
    "raw_data"            => raw_mc_2
)

open("test_2ch_mc_223.json","w") do f
    JSON.print(f, mc_2)
end

# Chaipcr dual channel meltcurve data

mc_189=readdlm("mc_189.tsv",'\t',header=true)
raw_mc_189=OrderedDict(
    mc_189[2][1]    => mc_189[1][:,1],
    mc_189[2][2]    => mc_189[1][:,2],
    mc_189[2][3]    => Vector{Integer}(mc_189[1][:,3]),
    mc_189[2][4]    => Vector{Integer}(mc_189[1][:,4])
)

mc_189=OrderedDict(
    "experiment_id"       => 189,
    "stage_id"            => 290,
    "calibration_info"    => calib_175,
    "channel_nums"        => [1,2],
    "qt_prob"             => 0.64,
    "max_normd_qtv"       => 0.8,
    "top_N"               => 4,
    "raw_data"            => raw_mc_189
)

open("chaipcr_mc_189.json","w") do f
    JSON.print(f, mc_189)
end

# Test_1ch thermal consistency single channel

tc_146=readdlm("tc_146.tsv",'\t',header=true)
raw_tc_1=OrderedDict(
    tc_146[2][1]    => tc_146[1][:,1],
    tc_146[2][2]    => tc_146[1][:,2],
    tc_146[2][3]    => Vector{Integer}(tc_146[1][:,3]),
    tc_146[2][4]    => Vector{Integer}(tc_146[1][:,4])
)

tc_1=OrderedDict(
    "experiment_id"       => 146,
    "stage_id"            => 4,
    "calibration_info"    => calib_1,
    "channel_nums"        => [1],
    "qt_prob"             => 0.64,
    "max_normd_qtv"       => 0.8,
    "top_N"               => 4,
    "raw_data"            => raw_tc_1
)

open("test_1ch_tc_146.json","w") do f
    JSON.print(f, tc_1)
end

# Test_2ch thermal consistency single channel

tc_145=readdlm("tc_145.tsv",'\t',header=true)
raw_tc_2=OrderedDict(
    tc_145[2][1]    => tc_145[1][:,1],
    tc_145[2][2]    => tc_145[1][:,2],
    tc_145[2][3]    => Vector{Integer}(tc_145[1][:,3]),
    tc_145[2][4]    => Vector{Integer}(tc_145[1][:,4])
)

tc_2=OrderedDict(
    "experiment_id"       => 145,
    "stage_id"            => 4,
    "calibration_info"    => calib_2,
    "channel_nums"        => [1,2],
    "qt_prob"             => 0.64,
    "max_normd_qtv"       => 0.8,
    "top_N"               => 4,
    "raw_data"            => raw_tc_2
)

open("test_2ch_tc_145.json","w") do f
    JSON.print(f, tc_2)
end

# Test_1ch thermal performance diagnostic single channel

tpd_126=readdlm("tpd_126.tsv",'\t',header=true)
tpd_1=OrderedDict(
    tpd_126[2][1]    => tpd_126[1][:,1],
    tpd_126[2][2]    => tpd_126[1][:,2],
    tpd_126[2][3]    => tpd_126[1][:,3],
    tpd_126[2][4]    => Vector{Integer}(tpd_126[1][:,4])
)

open("test_1ch_tpd_126.json","w") do f
    JSON.print(f, tpd_1)
end

# Test_2ch thermal performance diagnostic single channel

tpd_131=readdlm("tpd_131.tsv",'\t',header=true)
tpd_2=OrderedDict(
    tpd_131[2][1]    => tpd_131[1][:,1],
    tpd_131[2][2]    => tpd_131[1][:,2],
    tpd_131[2][3]    => tpd_131[1][:,3],
    tpd_131[2][4]    => Vector{Integer}(tpd_131[1][:,4])
)

open("test_2ch_tpd_131.json","w") do f
    JSON.print(f, tpd_2)
end

# Test_1ch optical test single channel

baseline_ot_1 = [1704,1803,1522,1442,1490,1540,1834,1757,1593,1705,1711,1586,1529,1638,1659,1502]
excitation_ot_1=[45213,21030,23819,26412,25405,31761,27095,34442,41152,26695,30389,34168,37144,36466,37692,44756]

ot_1 = OrderedDict(
        "baseline" => OrderedDict(
                "fluorescence_value" => baseline_ot_1
        ),
        "excitation" => OrderedDict(
                "fluorescence_value" => excitation_ot_1
        )
)

open("test_1ch_ot_161.json","w") do f
    JSON.print(f, ot_1)
end

# Test_2ch optical test dual channel

ot_baseline_2=[563  506  559  542  515  531  540  590  480  563  542  571  599  567  519  571;
              1623 1585 1628 1619 1608 1614 1627 1661 1573 1654 1625 1651 1677 1628 1581 1632]
ot_water_2=[15477 19246 11922 18452 15154 11504 11059 13373  7701 20789 16486 13542 14345 18934  9841  8883;
             2739  1887  2194  1650  1800  2068  1813  1876  2205  2076  3899  4153  1914  1919  1723  2080]
ot_FAM_2=[15416 19313 11952 18554 15215 11440 10914 13236  7710 20775 16520 13540 14362 18873 10036  8803;
           2620  1919  2209  1701  1804  1993  1678  1836  2225  2054  3919  4141  1943  1821  1902  2007]
ot_HEX_2=[15438 19277 11992 18699 15215 11518 11005 13508  7650 20786 16532 13541 14324 19017  9784  8718;
           2616  1877  2212  1830  1771  2081  1754  2020  2149  1976  3914  4150  1929  1983  1709  1989]

ot_2 = OrderedDict(
    "baseline" => OrderedDict(
        "fluorescence_value" => transpose(ot_baseline_2)
    ),
    "water" => OrderedDict(
        "fluorescence_value" => transpose(ot_water_2)
    ),
    "FAM" => OrderedDict(
        "fluorescence_value" => transpose(ot_FAM_2)
    ),
    "HEX" => OrderedDict(
        "fluorescence_value" => transpose(ot_HEX_2)
    )
)

open("test_2ch_ot_190.json","w") do f
    JSON.print(f, ot_2)
end

max_stars_repo_name: MakerButt/chaipcr

max_issues_repo_name: MakerButt/chaipcr

max_forks_repo_name: MakerButt/chaipcr


text:
using Plots
using LaTeXStrings

# backend(:pgfplotsx)
# backend(:pyplot)
backend(:gr)

#reset past font scaling
Plots.scalefontsizes()
#scale fonts
Plots.scalefontsizes(1.25)
#Extracting stored varibles

#Add colors
lightblue = colorant"rgb(63%,74%,78%)"
orange = colorant"rgb(85%,55%,13%)"
silver = colorant"rgb(69%,67%,66%)"
rust = colorant"rgb(72%,26%,6%)"

x1 = 0.65
y1 = 0.65

u1 = -0.5
v1 = -0.3

u1_log = u1 / x1
v1_log = v1 / y1

x2 = 0.35
y2 = 0.75

u2 = -0.8
v2 = -0.8

u2_log = u2 / x2
v2_log = v2 / y2

x3 = 0.2
y3 = 0.2

u3 = 0.6
v3 = -0.40

u3_log = u3 / x3
v3_log = v3 / y3

times = 0.0: 0.1:1.0
times = times[2:end]

trajectory_1 = hcat([[x1 + t * u1, y1 + t * v1] for t in times]...)'
derivatives_1 = hcat([[u1, v1] for t in times]...)'

start_1 = Matrix([x1, y1]')
derivatives_start_1 = Matrix([u1, v1]')

trajectory_2 = hcat([[x2 + t * u2, y2 + t * v2] for t in times]...)'
derivatives_2 = hcat([[u2, v2] for t in times]...)'

start_2 = Matrix([x2, y2]')
derivatives_start_2 = Matrix([u2, v2]')

trajectory_3 = hcat([[x3 + t * u3, y3 + t * v3] for t in times]...)'
derivatives_3 = hcat([[u3, v3] for t in times]...)'

start_3 = Matrix([x3, y3]')
derivatives_start_3 = Matrix([u3, v3]')

trajectory_1_log = hcat([exp.([log(x1) + t * u1_log, log(y1) + t * v1_log]) for t in times]...)'

derivatives_1_log = hcat([[u1_log * trajectory_1_log[k, 1], v1_log * trajectory_1_log[k, 2]] for k = 1 : length(times)]...)'

trajectory_2_log = hcat([exp.([log(x2) + t * u2_log, log(y2) + t * v2_log]) for t in times]...)'

derivatives_2_log = hcat([[u2_log * trajectory_2_log[k, 1], v2_log * trajectory_2_log[k, 2]] for k = 1 : length(times)]...)'

trajectory_3_log = hcat([exp.([log(x3) + t * u3_log, log(y3) + t * v3_log]) for t in times]...)'

derivatives_3_log = hcat([[u3_log * trajectory_3_log[k, 1], v3_log * trajectory_3_log[k, 2]] for k = 1 : length(times)]...)'

α = 0.10

plt = scatter(xlims=(0,1), ylims=(0,1), aspect_ratio=:equal, grid=false, thickness_scaling=1.25)

scatter!(plt, start_1[:, 1], start_1[:, 2], color=lightblue, label="")
quiver!(plt, start_1[:, 1], start_1[:, 2], quiver=(α * derivatives_start_1[: , 1], α * derivatives_start_1[:, 2]), color=lightblue, label="")

scatter!(plt, trajectory_1[:, 1], trajectory_1[:, 2], color=silver, label="")
quiver!(plt, trajectory_1[:, 1], trajectory_1[:, 2], quiver=(α * derivatives_1[: , 1], α * derivatives_1[:, 2]), color=silver, label="")

scatter!(plt, trajectory_1_log[:, 1], trajectory_1_log[:, 2], color=rust, label="")
quiver!(plt, trajectory_1_log[:, 1], trajectory_1_log[:, 2], quiver=(α * derivatives_1_log[: , 1], α * derivatives_1_log[:, 2]), color=rust, label="")

scatter!(plt, start_2[:, 1], start_2[:, 2], color=lightblue, label="")
quiver!(plt, start_2[:, 1], start_2[:, 2], quiver=(α * derivatives_start_2[: , 1], α * derivatives_start_2[:, 2]), color=lightblue, label="")

scatter!(plt, trajectory_2[:, 1], trajectory_2[:, 2], color=silver, label="")
quiver!(plt, trajectory_2[:, 1], trajectory_2[:, 2], quiver=(α * derivatives_2[: , 1], α * derivatives_2[:, 2]), color=silver, label="")
scatter!(plt, trajectory_2_log[:, 1], trajectory_2_log[:, 2], color=rust, label="")
quiver!(plt, trajectory_2_log[:, 1], trajectory_2_log[:, 2], quiver=(α * derivatives_2_log[: , 1], α * derivatives_2_log[:, 2]), color=rust, label="")

scatter!(plt, start_3[:, 1], start_3[:, 2], color=lightblue, label="")
quiver!(plt, start_3[:, 1], start_3[:, 2], quiver=(α * derivatives_start_3[: , 1], α * derivatives_start_3[:, 2]), color=lightblue, label="")

scatter!(plt, trajectory_3[:, 1], trajectory_3[:, 2], color=silver, label="")
quiver!(plt, trajectory_3[:, 1], trajectory_3[:, 2], quiver=(α * derivatives_3[: , 1], α * derivatives_3[:, 2]), color=silver, label="")
scatter!(plt, trajectory_3_log[:, 1], trajectory_3_log[:, 2], color=rust, label="")
quiver!(plt, trajectory_3_log[:, 1], trajectory_3_log[:, 2], quiver=(α * derivatives_3_log[: , 1], α * derivatives_3_log[:, 2]), color=rust, label="")

scatter!(plt, [], [], color=lightblue, label=L"\mathrm{initialization}")
scatter!(plt, [], [], color=silver, label=L"\mathrm{primal} \ \mathrm{geometry}")
scatter!(plt, [], [], color=rust, label=L"\mathrm{dual} \ \mathrm{geometry}")

savefig(plt, "./out/figures/dual_geometry.pdf")
run(`pdfcrop --margins '5 5 5 5' ./out/figures/dual_geometry.pdf ./out/figures/dual_geometry.pdf`)

max_stars_repo_name: f-t-s/CMD

max_issues_repo_name: f-t-s/CMD

max_forks_repo_name: f-t-s/CMD


text:
# This file contains the code to allow setting & changing key configs.

keySettingsDefault() = Dict([
    :keyALeft   => SDL2.SDLK_a
    :keyARight  => SDL2.SDLK_d
    :keyBLeft   => SDL2.SDLK_LEFT
    :keyBRight  => SDL2.SDLK_RIGHT
  ])
keySettings = keySettingsDefault()

keyDisplayNames = Dict([
    SDL2.SDLK_RIGHT => "Right Arrow"
    SDL2.SDLK_LEFT => "Left Arrow"
    SDL2.SDLK_UP => "Up Arrow"
    SDL2.SDLK_DOWN => "Down Arrow"

    SDL2.SDLK_a => "A"
    SDL2.SDLK_b => "B"
    SDL2.SDLK_c => "C"
    SDL2.SDLK_d => "D"
    SDL2.SDLK_e => "E"
    SDL2.SDLK_f => "F"
    SDL2.SDLK_g => "G"
    SDL2.SDLK_h => "H"
    SDL2.SDLK_i => "I"
    SDL2.SDLK_j => "J"
    SDL2.SDLK_k => "K"
    SDL2.SDLK_l => "L"
    SDL2.SDLK_m => "M"
    SDL2.SDLK_n => "N"
    SDL2.SDLK_o => "O"
    SDL2.SDLK_p => "P"
    SDL2.SDLK_q => "Q"
    SDL2.SDLK_r => "R"
    SDL2.SDLK_s => "S"
    SDL2.SDLK_t => "T"
    SDL2.SDLK_u => "U"
    SDL2.SDLK_v => "V"
    SDL2.SDLK_w => "W"
    SDL2.SDLK_x => "X"
    SDL2.SDLK_y => "Y"
    SDL2.SDLK_z => "Z"

    SDL2.SDLK_0 => "0"
    SDL2.SDLK_1 => "1"
    SDL2.SDLK_2 => "2"
    SDL2.SDLK_3 => "3"
    SDL2.SDLK_4 => "4"
    SDL2.SDLK_5 => "5"
    SDL2.SDLK_6 => "6"
    SDL2.SDLK_7 => "7"
    SDL2.SDLK_8 => "8"
    SDL2.SDLK_9 => "9"

    SDL2.SDLK_MINUS => "-"
    SDL2.SDLK_EQUALS => "="
    SDL2.SDLK_BACKSPACE => "Delete"
    SDL2.SDLK_DELETE => "Delete"

    SDL2.SDLK_TAB => "Tab"
    SDL2.SDLK_LEFTBRACKET => "["
    SDL2.SDLK_RIGHTBRACKET => "]"
    SDL2.SDLK_BACKSLASH => "\\"
    SDL2.SDLK_SEMICOLON => ";"
    SDL2.SDLK_QUOTE => "'"
    SDL2.SDLK_RETURN => "Return"
    SDL2.SDLK_LSHIFT => "Left Shift"
    SDL2.SDLK_COMMA => ","
    SDL2.SDLK_PERIOD => "."
    SDL2.SDLK_SLASH => "/"
    SDL2.SDLK_RSHIFT => "Right Shift"

    SDL2.SDLK_SPACE => "Space"
  ])

badKeySound = nothing
function tryChangingKeySettingButton(keyControl::Symbol)
    global clickedButton
    e,eventType = nothing,nothing
    # Mark the key button as selected and show it before pausing for input.
    if (length(sceneStack) >= 1)
        clickedButton = buttons[keyControl]
        render(sceneStack[end], renderer, win)
        SDL2.RenderPresent(renderer)
        clickedButton = nothing
    end
    while eventType != SDL2.KEYDOWN
        e, _ = pollEvent!()
        eventType = getEventType(e)
    end
    keySym = getKeySym(e)
    tryChangingKeySettingButton(keyControl,keySym)
    # Write new key settings!
    write_prefs_backup()
end
function tryChangingKeySettingButton(keyControl::Symbol, keySym)
    # If it's the same key, we're all done.
    if keySym == keySettings[keyControl]
        return
    end
    if !haskey(keyDisplayNames, keySym)
        # If this isn't a valid key, error
        audioEnabled && SDL2.Mix_PlayChannel( Int32(-1), badKeySound, Int32(0) )
    elseif keySym in values(keySettings)
        #  or if it's already being used somewhere else, swap them.
        reverse_keySettings = Dict(value => key for (key, value) in keySettings)
        prevKey = reverse_keySettings[keySym]

        prevSym = keySettings[keyControl]
        keySettings[prevKey] = prevSym
        buttons[prevKey].text = keyDisplayNames[prevSym]
        keySettings[keyControl] = keySym
        buttons[keyControl].text = keyDisplayNames[keySym]
    else
        keySettings[keyControl] = keySym
        buttons[keyControl].text = keyDisplayNames[keySym]
    end

    if keySettings == keySettingsDefault()
        buttons[:bResetDefaultKeys].enabled = false
    else
        buttons[:bResetDefaultKeys].enabled = true
    end
end

_pp = nothing
prefspath = nothing
prefsfile = nothing
function write_prefs_backup()
    if prefsfile != nothing
        write(prefsfile, serializeKeySettings()*"\n")
    end
end
function load_prefs_backup()
    if prefsfile != nothing && isfile(prefsfile)
        f = open(prefsfile)
        loadKeySettings(readline(f))
    end
end
function init_prefspath()
    global _pp, prefspath, prefsfile
      _pp = SDL2.GetPrefPath(kBUNDLE_ORGANIZATION, kSAFE_GAME_NAME)
      if _pp != Cstring(C_NULL)
          prefspath = unsafe_string(_pp)
          prefsfile = joinpath(prefspath, "settings.txt");
      end
    return prefspath
end

function loadKeySettings(prefs::String)
    p_vals = map((v)->parse(UInt32,v), split(prefs))
    tryChangingKeySettingButton(:keyALeft, p_vals[1])
    tryChangingKeySettingButton(:keyARight, p_vals[2])
    tryChangingKeySettingButton(:keyBLeft, p_vals[3])
    tryChangingKeySettingButton(:keyBRight, p_vals[4])
end
function serializeKeySettings()
    join([keySettings[:keyALeft],
          keySettings[:keyARight],
          keySettings[:keyBLeft],
          keySettings[:keyBRight],
         ], " ")
end

function resetDefaultKeys()
    for (key,val) in keySettingsDefault()
        tryChangingKeySettingButton(key,val)
    end
    # Write new key settings!
    write_prefs_backup()
end

max_stars_repo_name: NHDaly/pongClone

max_issues_repo_name: NHDaly/pongClone

max_forks_repo_name: NHDaly/pongClone


text:
function S1Point(arg0::jdouble)
    return S1Point((jdouble,), arg0)
end

function distance(::Type{S1Point}, arg0::S1Point, arg1::S1Point)
    return jcall(S1Point, "distance", jdouble, (S1Point, S1Point), arg0, arg1)
end

function distance(obj::S1Point, arg0::Point)
    return jcall(obj, "distance", jdouble, (Point,), arg0)
end

function equals(obj::S1Point, arg0::Object)
    return jcall(obj, "equals", jboolean, (Object,), arg0)
end

function get_alpha(obj::S1Point)
    return jcall(obj, "getAlpha", jdouble, ())
end

function get_space(obj::S1Point)
    return jcall(obj, "getSpace", Space, ())
end

function get_vector(obj::S1Point)
    return jcall(obj, "getVector", Vector2D, ())
end

function hash_code(obj::S1Point)
    return jcall(obj, "hashCode", jint, ())
end

function is_na_n(obj::S1Point)
    return jcall(obj, "isNaN", jboolean, ())
end


text:
################################################################################
#  Copyright 2021, Arpan Koirala, Tom Van Acker                                #
################################################################################
# StochasticPowerModels.jl                                                     #
# An extention package of PowerModels.jl for Stochastic (Optimal) Power Flow   #
# See http://github.com/timmyfaraday/StochasticPowerModels.jl                  #
################################################################################

# variables
""
function variable_bus_voltage(pm::AbstractACRModel; nw::Int=nw_id_default, aux::Bool=true, aux_fix::Bool=false, bounded::Bool=true, report::Bool=true, kwargs...)
    _PM.variable_bus_voltage_real(pm, nw=nw, bounded=bounded, report=report; kwargs...)
    _PM.variable_bus_voltage_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...)

    if aux
        variable_bus_voltage_squared(pm, nw=nw, bounded=bounded, report=report, aux_fix=aux_fix; kwargs...)
    else
        if nw == nw_id_default
            variable_bus_voltage_expectation(pm, nw=nw, bounded=bounded, report=report, aux_fix=aux_fix; kwargs...)
            variable_bus_voltage_variance(pm, nw=nw, bounded=bounded, report=report, aux_fix=aux_fix; kwargs...)
        end
    end
end

""
function variable_gen_power(pm::AbstractACRModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true, kwargs...)
    _PM.variable_gen_power_real(pm, nw=nw, bounded=bounded, report=report; kwargs...)
    _PM.variable_gen_power_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...)
end

""
function variable_branch_power(pm::AbstractACRModel; nw::Int=nw_id_default, bounded::Bool=true, report::Bool=true, kwargs...)
    _PM.variable_branch_power_real(pm, nw=nw, bounded=bounded, report=report; kwargs...)
    _PM.variable_branch_power_imaginary(pm, nw=nw, bounded=bounded, report=report; kwargs...)
end

"" # this should be renamed to more accurately reflect the variable, i.e., voltage drop
function variable_branch_current(pm::AbstractACRModel; nw::Int=nw_id_default, aux::Bool=true, bounded::Bool=true, report::Bool=true, aux_fix::Bool=false, kwargs...)
    #variable_branch_voltage_drop_real(pm, nw=nw, bounded=bounded, report=report; kwargs...)
    #variable_branch_voltage_drop_img(pm, nw=nw, bounded=bounded, report=report; kwargs...)

    if aux
        variable_branch_series_current_squared(pm, nw=nw, bounded=bounded, report=report, aux_fix=aux_fix; kwargs...)
    else
        if nw == nw_id_default
            variable_branch_series_current_expectation(pm, nw=nw, bounded=bounded, report=report, aux_fix=aux_fix; kwargs...)
            variable_branch_series_current_variance(pm, nw=nw, bounded=bounded, report=report, aux_fix=aux_fix; kwargs...)
        end
    end
end

# general constraints
""
function constraint_bus_voltage_ref(pm::AbstractACRModel, n::Int, i::Int)
    vr = _PM.var(pm, n, :vr, i)
    vi = _PM.var(pm, n, :vi, i)

    vn = ifelse(n == 1, 1.0, 0.0)

    JuMP.@constraint(pm.model, vr == vn)
    JuMP.@constraint(pm.model, vi == 0.0)
end

function constraint_power_balance(pm::AbstractACRModel, n::Int, i::Int, bus_arcs, bus_gens, bus_pd, bus_qd, bus_gs, bus_bs)
    vr = _PM.var(pm, n, :vr, i)
    vi = _PM.var(pm, n, :vi, i)

    p    = _PM.get(_PM.var(pm, n),    :p, Dict()); _PM._check_var_keys(p, bus_arcs, "active power", "branch")
    q    = _PM.get(_PM.var(pm, n),    :q, Dict()); _PM._check_var_keys(q, bus_arcs, "reactive power", "branch")

    pg   = _PM.get(_PM.var(pm, n),   :pg, Dict()); _PM._check_var_keys(pg, bus_gens, "active power", "generator")
    qg   = _PM.get(_PM.var(pm, n),   :qg, Dict()); _PM._check_var_keys(qg, bus_gens, "reactive power", "generator")

    JuMP.@constraint(pm.model,
        sum(p[a] for a in bus_arcs)
        ==
        sum(pg[g] for g in bus_gens)
        - sum(pd for pd in values(bus_pd))
        - sum(gs for gs in values(bus_gs))*(vr^2 + vi^2)
    )
    JuMP.@constraint(pm.model,
        sum(q[a] for a in bus_arcs)
        ==
        sum(qg[g] for g in bus_gens)
        - sum(qd for qd in values(bus_qd))
        + sum(bs for bs in values(bus_bs))*(vr^2 + vi^2)
    )
end

""
function constraint_branch_voltage(pm::AbstractACRModel, i::Int; nw::Int=nw_id_default)
    branch = _PM.ref(pm, nw, :branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]

    vbdr  = _PM.var(pm, nw, :vbdr, i)
    vbdi  = _PM.var(pm, nw, :vbdi, i)

    vr_fr = _PM.var(pm, nw, :vr, f_bus)
    vr_to = _PM.var(pm, nw, :vr, t_bus)
    vi_fr = _PM.var(pm, nw, :vi, f_bus)
    vi_to = _PM.var(pm, nw, :vi, t_bus)

    JuMP.@constraint(pm.model,  vbdr == (vr_fr-vr_to))
    JuMP.@constraint(pm.model,  vbdi == (vi_fr-vi_to))
end

# galerkin projection
""
function constraint_gp_bus_voltage_squared(pm::AbstractACRModel, n::Int, i, T2, T3)
    vs  = _PM.var(pm, n, :vs, i)
    vr  = Dict(nw => _PM.var(pm, nw, :vr, i) for nw in _PM.nw_ids(pm))
    vi  = Dict(nw => _PM.var(pm, nw, :vi, i) for nw in _PM.nw_ids(pm))

    JuMP.@constraint(pm.model,  T2.get([n-1,n-1]) * vs
                                ==
                                sum(T3.get([n1-1,n2-1,n-1]) *
                                    (vr[n1] * vr[n2] + vi[n1] * vi[n2])
                                    for n1 in _PM.nw_ids(pm), n2 in _PM.nw_ids(pm))
                    )
end

function constraint_gp_power_branch_to(pm::AbstractACRModel, n::Int, f_bus, t_bus, f_idx, t_idx, g, b, g_to, b_to, tr, ti, tm, T2, T3)
    p_to = _PM.var(pm, n, :p, t_idx)
    q_to = _PM.var(pm, n, :q, t_idx)

    vr_fr = Dict(nw => _PM.var(pm, nw, :vr, f_bus) for nw in _PM.nw_ids(pm))
    vr_to = Dict(nw => _PM.var(pm, nw, :vr, t_bus) for nw in _PM.nw_ids(pm))
    vi_fr = Dict(nw => _PM.var(pm, nw, :vi, f_bus) for nw in _PM.nw_ids(pm))
    vi_to = Dict(nw => _PM.var(pm, nw, :vi, t_bus) for nw in _PM.nw_ids(pm))

    JuMP.@constraint(pm.model,  p_to * T2.get([n-1,n-1])
                                ==
                                sum(T3.get([n1-1,n2-1,n-1]) *
                                    ((g + g_to) * (vr_to[n1] * vr_to[n2] + vi_to[n1] * vi_to[n2]) +
                                     (-g * tr - b * ti) / tm^2 * (vr_fr[n1] * vr_to[n2] + vi_fr[n1] * vi_to[n2]) +
                                     (-b * tr + g * ti) / tm^2 * (-(vi_fr[n1] * vr_to[n2] - vr_fr[n1] * vi_to[n2]))
                                    )
                                    for n1 in _PM.nw_ids(pm), n2 in _PM.nw_ids(pm))
                    )

    JuMP.@constraint(pm.model,  q_to * T2.get([n-1,n-1])
                                ==
                                sum(T3.get([n1-1,n2-1,n-1]) *
                                    (-(b + b_to) * (vr_to[n1] * vr_to[n2] + vi_to[n1] * vi_to[n2]) -
                                     (-b * tr + g * ti) / tm^2 * (vr_fr[n1] * vr_to[n2] + vi_fr[n1] * vi_to[n2]) +
                                     (-g * tr - b * ti) / tm^2 * (-(vi_fr[n1] * vr_to[n2] - vr_fr[n1] * vi_to[n2]))
                                    )
                                    for n1 in _PM.nw_ids(pm), n2 in _PM.nw_ids(pm))
                    )
end

""
function constraint_gp_power_branch_from(pm::AbstractACRModel, n::Int,f_bus, t_bus, f_idx, t_idx, g, b, g_fr, b_fr, tr, ti, tm, T2, T3)
    p_fr = _PM.var(pm, n, :p, f_idx)
    q_fr = _PM.var(pm, n, :q, f_idx)

    vr_fr = Dict(nw => _PM.var(pm, nw, :vr, f_bus) for nw in _PM.nw_ids(pm))
    vr_to = Dict(nw => _PM.var(pm, nw, :vr, t_bus) for nw in _PM.nw_ids(pm))
    vi_fr = Dict(nw => _PM.var(pm, nw, :vi, f_bus) for nw in _PM.nw_ids(pm))
    vi_to = Dict(nw => _PM.var(pm, nw, :vi, t_bus) for nw in _PM.nw_ids(pm))

    JuMP.@constraint(pm.model,  p_fr * T2.get([n-1,n-1])
                                ==
                                sum(T3.get([n1-1,n2-1,n-1]) *
                                    ((g + g_fr) / tm^2 * (vr_fr[n1] * vr_fr[n2] + vi_fr[n1] * vi_fr[n2]) +
                                     (-g * tr + b * ti) / tm^2 * (vr_fr[n1] * vr_to[n2] + vi_fr[n1] * vi_to[n2]) +
                                     (-b * tr - g * ti) / tm^2 * (vi_fr[n1] * vr_to[n2] - vr_fr[n1] * vi_to[n2])
                                    )
                                for n1 in _PM.nw_ids(pm), n2 in _PM.nw_ids(pm))
                    )

    JuMP.@constraint(pm.model,  q_fr * T2.get([n-1,n-1])
                                ==
                                sum(T3.get([n1-1,n2-1,n-1]) *
                                    (-(b + b_fr) / tm^2 * (vr_fr[n1] * vr_fr[n2] + vi_fr[n1] * vi_fr[n2]) -
                                     (-b * tr - g * ti) / tm^2 * (vr_fr[n1] * vr_to[n2] + vi_fr[n1] * vi_to[n2]) +
                                     (-g * tr + b * ti) / tm^2 * (vi_fr[n1] * vr_to[n2] - vr_fr[n1] * vi_to[n2])
                                    )
                                for n1 in _PM.nw_ids(pm), n2 in _PM.nw_ids(pm))
                    )
end

""
function constraint_gp_current_squared(pm::AbstractACRModel, n::Int, i, T2, T3)
    css = _PM.var(pm, n, :css, i)

    branch = _PM.ref(pm, n, :branch, i)
    g, b  = _PM.calc_branch_y(branch)

    vbdr = Dict(nw => _PM.var(pm, nw, :vbdr, i) for nw in _PM.nw_ids(pm))
    vbdi = Dict(nw => _PM.var(pm, nw, :vbdi, i) for nw in _PM.nw_ids(pm))

    JuMP.@constraint(pm.model,  T2.get([n-1,n-1]) * css
                                ==
                                (g^2 + b^2) * sum(  T3.get([n1-1,n2-1,n-1]) *
                                                    (vbdr[n1] * vbdr[n2] + vbdi[n1] * vbdi[n2])
                                                    for n1 in _PM.nw_ids(pm), n2 in _PM.nw_ids(pm))
                    )
end

# chance constraints
""
function constraint_bus_voltage_cc_limit(pm::AbstractACRModel, i, vmin, vmax, λmin, λmax, T2, T4)
    ntws = _PM.nw_ids(pm)

    ve  = _PM.var(pm, nw_id_default, :ve, i)
    vv  = _PM.var(pm, nw_id_default, :vv, i)

    vr  = Dict(n => _PM.var(pm, n, :vr, i) for n in ntws)
    vi  = Dict(n => _PM.var(pm, n, :vi, i) for n in ntws)

    T44 = Dict((n1,n2,n3,n4) => T4.get([n1-1,n2-1,n3-1,n4-1]) for n1 in ntws, n2 in ntws, n3 in ntws, n4 in ntws)

    # expectation
    JuMP.@constraint(pm.model, ve == sum((vr[n]^2 + vi[n]^2) * T2.get([n-1,n-1]) for n in ntws))
    # 'variance'
    JuMP.@NLconstraint(pm.model, ve^2 + vv^2
                                 ==
                                 sum(
                                    (vr[n1] * vr[n2] * vr[n3] * vr[n4] +
                                     2 * vr[n1] * vr[n2] * vi[n3] * vi[n4] +
                                     vi[n1] * vi[n2] * vi[n3] * vi[n4]
                                    ) *
                                    T44[(n1,n2,n3,n4)]
                                    for n1 in ntws, n2 in ntws, n3 in ntws, n4 in ntws
                                 )
                    )
    # chance constraint bounds
    JuMP.@constraint(pm.model,  vmin^2
                                <=
                                ve - λmin * vv
                    )
    JuMP.@constraint(pm.model,  ve + λmax * vv                                  # not used by Muhlpfordt
                               <=
                               vmax^2
                   )
end

""
function constraint_bus_voltage_squared_cc_limit(pm::AbstractACRModel, i, vmin, vmax, λmin, λmax, T2, mop)
    vs  = [_PM.var(pm, n, :vs, i) for n in sorted_nw_ids(pm)]

    # bounds on the expectation
    JuMP.@constraint(pm.model, vmin^2 <= _PCE.mean(vs, mop))
    JuMP.@constraint(pm.model, _PCE.mean(vs, mop) <= vmax^2)
    # chance constraint bounds
    JuMP.@constraint(pm.model,  _PCE.var(vs, T2)
                                <=
                               ((_PCE.mean(vs, mop) - vmin^2) / λmin)^2
                    )
    JuMP.@constraint(pm.model,  _PCE.var(vs, T2)
                               <=
                                ((vmax^2 - _PCE.mean(vs, mop)) / λmax)^2
                    )
end

""
function constraint_gen_power_real_cc_limit(pm::AbstractACRModel, g, pmin, pmax, λmin, λmax, T2, mop)
    pg  = [_PM.var(pm, nw, :pg, g) for nw in sorted_nw_ids(pm)]

     # bounds on the expectation
     JuMP.@constraint(pm.model,  pmin <= _PCE.mean(pg, mop))
     JuMP.@constraint(pm.model,  _PCE.mean(pg, mop) <= pmax)
     # chance constraint bounds
     JuMP.@constraint(pm.model,  _PCE.var(pg, T2)
                                 <=
                                ((_PCE.mean(pg, mop) - pmin) / λmin)^2
                   )
     JuMP.@constraint(pm.model,  _PCE.var(pg, T2)
                                 <=
                                 ((pmax - _PCE.mean(pg, mop)) / λmax)^2
                   )
end

""
function constraint_gen_power_imaginary_cc_limit(pm::AbstractACRModel, g, qmin, qmax, λmin, λmax, T2, mop)
    qg  = [_PM.var(pm, nw, :qg, g) for nw in sorted_nw_ids(pm)]

    # bounds on the expectation
    JuMP.@constraint(pm.model,  qmin <= _PCE.mean(qg, mop))
    JuMP.@constraint(pm.model,  _PCE.mean(qg, mop) <= qmax)
    # chance constraint bounds
    JuMP.@constraint(pm.model,  _PCE.var(qg,T2)
                                <=
                                ((_PCE.mean(qg,mop) - qmin) / λmin)^2
                    )
    JuMP.@constraint(pm.model,  _PCE.var(qg,T2)
                               <=
                                ((qmax - _PCE.mean(qg,mop)) / λmax)^2
                    )
end

""
function constraint_branch_series_current_cc_limit(pm::AbstractACRModel, b, cmax, λmax, T2, T4, gs, bs)
    ntws = _PM.nw_ids(pm)

    cse  = _PM.var(pm, nw_id_default, :cse, b)
    csv  = _PM.var(pm, nw_id_default, :csv, b)

    vbdr = Dict(n => _PM.var(pm, n, :vbdr, b) for n in ntws)
    vbdi = Dict(n => _PM.var(pm, n, :vbdi, b) for n in ntws)

    T44 = Dict((n1,n2,n3,n4) => T4.get([n1-1,n2-1,n3-1,n4-1]) for n1 in ntws, n2 in ntws, n3 in ntws, n4 in ntws)

    # expectation
    JuMP.@constraint(pm.model, cse == (gs^2+bs^2) * sum((vbdr[n]^2 + vbdi[n]^2) * T2.get([n-1,n-1]) for n in ntws))
    # 'variance'
    JuMP.@NLconstraint(pm.model, cse^2 + csv^2
                                 ==
                                 (gs^2+bs^2)^2 * sum(
                                    (vbdr[n1] * vbdr[n2] * vbdr[n3] * vbdr[n4] +
                                     2 * vbdr[n1] * vbdr[n2] * vbdi[n3] * vbdi[n4] +
                                     vbdi[n1] * vbdi[n2] * vbdi[n3] * vbdi[n4]
                                    ) *
                                    T44[(n1,n2,n3,n4)]
                                    for n1 in ntws, n2 in ntws, n3 in ntws, n4 in ntws
                                 )
                    )
    # chance constraint bounds
    JuMP.@constraint(pm.model,  cse + λmax * csv
                                <=
                                cmax^2
                    )
end

""
function constraint_branch_series_current_squared_cc_limit(pm::AbstractACRModel, b, cmax, λcmax, T2, mop)
    css = [_PM.var(pm, nw, :css, b) for nw in sorted_nw_ids(pm)]

    # bound on the expectation
    JuMP.@constraint(pm.model,  _PCE.mean(css, mop) <= cmax^2)
    # chance constraint bounds
    JuMP.@constraint(pm.model,  _PCE.var(css,T2)
                                <=
                                ((cmax^2 - _PCE.mean(css,mop)) / λcmax)^2
                    )
end

""
function sol_data_model!(pm::AbstractACRModel, solution::Dict)
    _PM.apply_pm!(_sol_data_model_acr!, solution)
end

""
function _sol_data_model_acr!(solution::Dict)
    if haskey(solution, "bus")
        for (i, bus) in solution["bus"]
            if haskey(bus, "vr") && haskey(bus, "vi")
                bus["vm"] = hypot(bus["vr"], bus["vi"])
                bus["va"] = atan(bus["vi"], bus["vr"])
            end
        end
    end
end

""
function  expression_branch_voltage_drop_real(pm::AbstractACRModel, n::Int, b::Int, f_bus, t_bus)
    vr_fr = _PM.var(pm, n, :vr, f_bus)
    vr_to = _PM.var(pm, n, :vr, t_bus)
    _PM.var(pm, n, :vbdr)[b]=vr_fr-vr_to
end

""
function  expression_branch_voltage_drop_img(pm::AbstractACRModel, n::Int, b::Int, f_bus, t_bus)
    vi_fr = _PM.var(pm, n, :vi, f_bus)
    vi_to = _PM.var(pm, n, :vi, t_bus)
    _PM.var(pm, n, :vbdi)[b] = (vi_fr-vi_to)
end


text:
push!(LOAD_PATH, expanduser(".")) # Assumed to be ran from the repl folder.

#include("RadioPropagation.jl")
using RadioPropagation
using Plots

frequency_ghz = collect(1:1000);

attenuation_db_km = RadioPropagation.atmospheric_attenuation_db_per_km.( frequency_ghz );

plot1 = plot(	frequency_ghz, attenuation_db_km,
	 			xlabel 	= "Frequency [GHz]",
				ylabel 	= "One-way loss [dB/km]",
				title  	= "Atmospheric Attenuation.",
				#ylims 	= (),
				label 	= "Standard",
				yaxis=:log,
				dpi=300
				)

attenuation_db_km = RadioPropagation.atmospheric_attenuation_db_per_km.( frequency_ghz, 288.15, 0 );

plot!( 	frequency_ghz, attenuation_db_km,
	 	label = "Dry air",
	 	yaxis=:log,
	 	)

savefig("figures/attenuation_example")

max_stars_repo_name: ErikBuer/RadioPropagation

max_issues_repo_name: ErikBuer/RadioPropagation

max_forks_repo_name: ErikBuer/RadioPropagation


text:
function ComputeIntrinsicAlignmentGrid!(CosmologicalGrid::CosmologicalGrid,
    LensingFunction::WLWeightFunction, ConvolvedDensity::AbstractConvolvedDensity,
    BackgroundQuantities::BackgroundQuantities,
    Cosmology::AbstractCosmology)
    c_0 = 2.99792458e5 #TODO: find a package containing the exact value of
                       #physical constants involved in calculations
    input_data = readdlm(joinpath(
        dirname(@__FILE__),"..","input_files","scaledmeanlum-E2Sa.txt"), Float64)
    z = input_data[:,1]
    lum = input_data[:,2]
    spl = Dierckx.Spline1D(z, lum, k = 2)
    lumgrid = spl.(CosmologicalGrid.ZArray)
    for zidx in 1:length(CosmologicalGrid.ZArray)
        for iidx in 1:length(ConvolvedDensity.ZBinArray)-1
            LensingFunction.IntrinsicAlignmentArray[iidx, zidx] =
            - BackgroundQuantities.HZArray[zidx] / c_0 *
            ConvolvedDensity.DensityGridArray[iidx, zidx] *
            LensingFunction.IntrinsicAlignmentModel.𝓐IA *
            LensingFunction.IntrinsicAlignmentModel.𝓒IA * Cosmology.ΩM *
            ( (1 + CosmologicalGrid.ZArray[zidx]) ^
            LensingFunction.IntrinsicAlignmentModel.ηIA ) *
            (lumgrid[zidx] ^ LensingFunction.IntrinsicAlignmentModel.βIA ) /
            BackgroundQuantities.DZArray[zidx]
        end
    end
end


text:
"""
    klein_gordon

The Klein–Gordon equation is often written in natural units:
```latex
psi_tt - psi_xx + m^2 psi = 0
```
Here it is solved on a finite interval, with zero essential boundary conditions.
Only the real part of psi is solved for.
"""
module klein_gordon

using LinearAlgebra
using MeshCore.Exports
using MeshSteward.Exports
using Elfel.Exports
using PlotlyJS

L = 2.0
m2 = 10.0 # coefficient m^2
N = 150;# number of subdivisions along the length of the domain

# Boundary and initial conditions
Fbc(x) = 0.0
Fic(x) = 0.0
Vic(x) = 1.0
tend = 8.0
dt = 0.02

function genmesh()
    return attach!(Mesh(), L2block(L, N))
end

function assembleM(Psih)
    function integrate!(am, geom, elit, qpit)
        nedof = ndofsperel(elit)
        me = LocalMatrixAssembler(nedof, nedof, 0.0)
        for el in elit
            init!(me, eldofs(el), eldofs(el))
            for qp in qpit
                Jac, J = jacjac(el, qp)
                gradN = bfungrad(qp, Jac)
                JxW = J * weight(qp)
                N = bfun(qp)
                for j in 1:nedof
                    for i in 1:nedof
                        me[i, j] += (N[i] * N[j]) * (JxW)
                    end
                end
            end
            assemble!(am, me)
        end
        return am
    end

    elit = FEIterator(Psih)
    qpit = QPIterator(Psih, (kind = :Gauss, order = 2))
    geom = geometry(Psih.mesh)
    am = start!(SysmatAssemblerSparse(0.0), ndofs(Psih), ndofs(Psih))

    integrate!(am, geom, elit, qpit)

    return finish!(am)
end

function assembleK(Psih)
    function integrate!(am, geom, elit, qpit)
        nedof = ndofsperel(elit)
        me = LocalMatrixAssembler(nedof, nedof, 0.0)
        for el in elit
            init!(me, eldofs(el), eldofs(el))
            for qp in qpit
                Jac, J = jacjac(el, qp)
                x = location(el, qp)
                gradN = bfungrad(qp, Jac)
                JxW = J * weight(qp)
                N = bfun(qp)
                for j in 1:nedof
                    for i in 1:nedof
                        me[i, j] += (gradN[i][1] * gradN[j][1]) * (JxW)
                    end
                end
            end
            assemble!(am, me)
        end
        return am
    end

    elit = FEIterator(Psih)
    qpit = QPIterator(Psih, (kind = :Gauss, order = 2))
    geom = geometry(Psih.mesh)
    am = start!(SysmatAssemblerSparse(0.0), ndofs(Psih), ndofs(Psih))

    integrate!(am, geom, elit, qpit)

    return finish!(am)
end

function assembleD(Psih, m2)
    function integrate!(am, geom, elit, qpit, m)
        nedof = ndofsperel(elit)
        me = LocalMatrixAssembler(nedof, nedof, 0.0)
        for el in elit
            init!(me, eldofs(el), eldofs(el))
            for qp in qpit
                Jac, J = jacjac(el, qp)
                x = location(el, qp)
                gradN = bfungrad(qp, Jac)
                JxW = J * weight(qp)
                N = bfun(qp)
                for j in 1:nedof
                    for i in 1:nedof
                        me[i, j] += (N[i] * N[j]) * (m2 * JxW)
                    end
                end
            end
            assemble!(am, me)
        end
        return am
    end

    elit = FEIterator(Psih)
    qpit = QPIterator(Psih, (kind = :Gauss, order = 2))
    geom = geometry(Psih.mesh)
    am = start!(SysmatAssemblerSparse(0.0), ndofs(Psih), ndofs(Psih))

    integrate!(am, geom, elit, qpit, m2)

    return finish!(am)
end

function run()
    mesh = genmesh()
    Psih = FESpace(Float64, mesh, FEH1_L2())
    ir = baseincrel(mesh)
    bdvl = connectedv(boundary(mesh));
    locs = geometry(mesh)
    for i in bdvl
        setebc!(Psih, 0, i, 1, Fbc(locs[i]...))
    end
    numberdofs!(Psih)
    @show nu = nunknowns(Psih)
    xs = [locs[i][1] for i in 1:length(locs)]
    M = assembleM(Psih)
    K = assembleK(Psih)
    D = assembleD(Psih, m2)
    Psi0 = fill(0.0, ndofs(Psih))
    V0 = fill(0.0, ndofs(Psih))
    for i in 1:length(locs)
        n = dofnum(Psih, 0, i, 1)
        Psi0[n] = Fic(locs[i][1])
        V0[n] = Vic(locs[i][1])
    end
    for i in bdvl
        n = dofnum(Psih, 0, i, 1)
        V0[n] = 0.0
    end
    V1 = fill(0.0, ndofs(Psih))
    L1 = fill(0.0, ndofs(Psih))
    L0 = fill(0.0, ndofs(Psih))
    # Plots
    layout = Layout(;width=700, height=700, xaxis = attr(title="x", range=[0.0, L]), yaxis = attr(title = "Psi", range=[-1.0, 1.0]))
    sigdig = n -> round(n*1000)/1000
    function updateplot(pl, t, xs, Psis)
        curv = scatter(;x=xs, y=Psis, mode="lines", name = "Sol", line_color = "rgb(155, 15, 15)")
        plots = cat(curv; dims = 1)
        pl.plot.layout["title"] = "t = $(sigdig(t))"
        react!(pl, plots, pl.plot.layout)
        sleep(0.12)
    end
    function solve!(V1, M, K, D, L0, L1, Psi0, V0, dt, nu)
        # This assumes zero EBC
        R = (L1 + L0)/2 + (1/dt)*(M*V0) - K*(Psi0 + (dt/4)*V0) - D*(Psi0 + (dt/4)*V0)
        V1[1:nu] = ((1/dt)*M + (dt/4)*(K + D))[1:nu, 1:nu] \ R[1:nu]
        return V1
    end
    pl = 0
    step = 0
    t = 0.0
    while t < tend
        if mod(step, 10) == 0
            Psis = [Psi0[dofnum(Psih, 0, i, 1)] for i in 1:nshapes(ir.right)]
            if step == 0
                curv = scatter(;x=xs, y=Psis, mode="lines", name = "Sol", line_color = "rgb(155, 15, 15)")
                plots = cat(curv; dims = 1)
                pl = plot(plots, layout)
                display(pl)
            end
            updateplot(pl, t, xs, Psis)
        end
        solve!(V1, M, K, D, L0, L1, Psi0, V0, dt, nu)
        Psi1 = Psi0 + dt/2*(V1 + V0)
        t = t + dt
        step = step + 1
        copyto!(V0, V1)
        copyto!(Psi0, Psi1)
    end
    # scattersysvec!(Psih, Psi0)
    # makeattribute(Psih, "Psi", 1)
    # vtkwrite("klein_gordon-Psi", baseincrel(mesh), [(name = "Psi",)])
end

end

using .klein_gordon
klein_gordon.run()