## requirements in this file

using LinearAlgebra

## monocular camera on car tools

include(joinpath(@__DIR__, "CarSlamUtilsCommon.jl"))

##

# find joystick command segment
# get all inputs up to this next pose
function whichJoystickValues(fec, fromT, upToTime::ZonedDateTime)
  # upToTime = msgTime # getTimestamp(getVariable(fec.slam.dfg,addHist[end]))
  cmdTimes = (x->fec.synchronizer.cmdVal[x][2]).(1:length(fec.synchronizer.cmdVal)) .|> nanosecond2datetime
  addHist = getAddHistory(fec.slam.dfg)
  filter!(x->occursin(r"x\d",string(x)), addHist)
  mask = fromT .<= cmdTimes .< upToTime

  idxes = (1:length(fec.synchronizer.cmdVal))[mask]
  # get the actual times from the buffer
  joyValsTuple = fec.synchronizer.cmdVal[idxes]
  # @show typeof(joyValsTuple)
  # @show keys(joyValsTuple[1][3])
  # @show typeof(joyValsTuple[1][3])
  # @show joyValsTuple[1][3].axis
  # (x->x[3][:axis][[2;4]]).(joyValsTuple)
  # (x->x[3]).(joyValsTuple)

  # Tuple{Int,Int,Dict{Symbol, Any}}
  joyValsTuple
end

# for FluxModelsPose2Pose2
# ::AbstractArray{<:Real}
@everywhere function interpToOutx4(indata::AbstractMatrix{<:Real}; outlen=25)
  #
  len = size(indata,1)
  if 0 == len
    return zeros(outlen,4)
  end

  # assume evenly spaced
  idx = 1:len
  # tsLcl = range(indata[1,1],indata[end,1],length=outlen)
  iThrtTemp = DataInterpolations.LinearInterpolation(indata[:,1], idx)
  iSterTemp = DataInterpolations.LinearInterpolation(indata[:,2], idx)
  iVelxTemp = DataInterpolations.LinearInterpolation(indata[:,3], idx)
  iVelyTemp = DataInterpolations.LinearInterpolation(indata[:,4], idx)
  newArr = Matrix{Float64}(undef, outlen, 4)
  lookup = range(1,len, length=outlen)
  for i in 1:outlen
    lid = lookup[i]
    newArr[i, 1] = iThrtTemp(lid)
    newArr[i, 2] = iSterTemp(lid)
    newArr[i, 3] = iVelxTemp(lid)
    newArr[i, 4] = iVelyTemp(lid)
  end
  # currently have no velocity values
  return newArr
end

@everywhere function interpToOutx2(indata::AbstractMatrix{<:Real}; outlen=25)
  #
  @show typeof(indata)
  @show size(indata)
  len = size(indata,1)
  if 0 == len
    return zeros(outlen,2)
  end

  # assume evenly spaced
  idx = 1:len
  # tsLcl = range(indata[1,1],indata[end,1],length=outlen)
  iThrtTemp = DataInterpolations.LinearInterpolation(indata[:,1], idx)
  iSterTemp = DataInterpolations.LinearInterpolation(indata[:,2], idx)
  newArr = Matrix{Float64}(undef, outlen, 2)
  lookup = range(1,len, length=outlen)
  for i in 1:outlen
    lid = lookup[i]
    newArr[i, 1] = iThrtTemp(lid)
    newArr[i, 2] = iSterTemp(lid)
  end
  # currently have no velocity values
  return newArr
end

@everywhere function interpToOutx2(indata::Vector{<:AbstractVector{<:Real}}; outlen=25)
  if length(indata) == 1
    @warn "interpToOutx2 indata is too short, returning zeros"
    return zeros(25,2)
  end
  arr = zeros(length(indata),2)
  for i in 1:length(indata), j in 1:2
    arr[i, j] = indata[i][j]
  end
  interpToOutx2(arr, outlen=outlen)
end

@everywhere function interpToOutx2(indata::Vector{Any}; outlen=25)
  if length(indata) == 0
    return zeros(25,2)
  else
    @show indata
    error("interpToOutx2(indata::Vector{Any} does not know how to work with (see above)")
  end
end

# @everywhere function interpTo25x4Old(lclJD)
#   @warn "Unsure of interpTo25x4Old is doing the right thing (this is the old version)"
#   if 0 == size(lclJD,1)
#     return [zeros(4) for i in 1:25]
#   end
#
#   tsLcl = range(lclJD[1,1],lclJD[end,1],length=25)
#   intrTrTemp = DataInterpolations.LinearInterpolation(lclJD[:,2],lclJD[:,1])
#   intrStTemp = DataInterpolations.LinearInterpolation(lclJD[:,3],lclJD[:,1])
#   newVec = Vector{Vector{Float64}}()
#   for tsL in tsLcl
#     newVal = zeros(4)
#     newVal[1] = intrTrTemp(tsL)
#     newVal[2] = intrStTemp(tsL)
#     push!(newVec, newVal)
#   end
#   # currently have no velocity values
#   return newVec
# end

# Is this a general requirement?
function updateSLAMMono!(fec::FrontEndContainer,
                         WEIRDOFFSET::Dict,
                         syncList::Vector{Symbol}=fec.synchronizer.syncList;
                         useFluxModels::Bool=false,
                          allModels=nothing,
                          naiveFrac::Float64=0.6)
  #
  syncz = fec.synchronizer
  idxL,idxO = findSyncLatestIdx(syncz, syncList=[:leftFwdCam;:camOdo], weirdOffset=WEIRDOFFSET)
  if idxL == 0 || idxO == 0
    return nothing
  end
  # already have a counter
  fec.slam.frameCounter = syncz.camOdo[idxO][1]
  img = syncz.leftFwdCam[idxO][3]
  # @assert img isa Array "img is not an array, $(typeof(syncz.leftFwdCam[idxO]))"

  # calculate delta odo since last pose
  zTi = fec.slam.helpers.lastPoseOdomBuffer
  zT = syncz.camOdo[idxO][3]
  iT = zTi\zT
  # x-fwd, y-right, z-up
  psi = convert(Euler, iT.R).Y  # something not right here?
  DX = [iT.t[1];iT.t[2]; psi]

  # Tigger a new pose or skip out
  if !(0 < abs.(DX[3]) - parsed_args["pose_trigger_rotate"] ||
       0 < norm(DX[1:2]) - parsed_args["pose_trigger_distance"])
    # havent travelled far enough yet
    return nothing
  end
  @show round.(DX, digits=3)
  # psi = convert(Euler, zT.R).Y - convert(Euler, zTi.R).Y |> TU.wrapRad
  # @show convert(Euler, iT.R).Y  # something not right here?

  # reset interpose odometry counter for outside global odo integration process
  fec.slam.helpers.lastPoseOdomBuffer = syncz.camOdo[idxO][3] |> deepcopy

  # add new pose
  prevpose = Symbol("x$(fec.slam.poseCount)")
  fec.slam.poseCount += 1
  newpose = Symbol("x$(fec.slam.poseCount)")
  # take timestamp from image msg
  imgTime = nanosecond2datetime(syncz.leftFwdCam[idxL][2])
  addVariable!(fec.slam.dfg, newpose, Pose2, timestamp=imgTime)

  # previous pose time
  prevPoseT = getTimestamp(getVariable(fec.slam.dfg,prevpose))

  # find joystick values required
  joyVals = whichJoystickValues(fec, prevPoseT, imgTime)
  ## lets store the values while we're here
  addData!( fec.slam.dfg, :default_folder_store, prevpose, :JOYSTICK_CMD_VALS, Vector{UInt8}(JSON2.write( joyVals )), mimeType="application/json/octet-stream" )
  # addDataEntry!( fec.slam.dfg, prevpose, fec.datastore, :JOYSTICK_CMD_VALS, "application/json", Vector{UInt8}(JSON2.write( joyVals ))  )
  io = IOBuffer()
  pngSm = Stream(format"PNG", io)
  FileIO.save(pngSm, img)  # think FileIO is required for this
  addData!( fec.slam.dfg, :default_folder_store, prevpose, :KEYFRAME_IMG, take!(io), mimeType="image/png" )

  baselineOdo = MvNormal(DX, diagm([0.05; 0.05; 0.3].^2))
  pp = if useFluxModels
    # convert to NN required format
    # joyVelVal = catJoyVelData(joyVals, velVal)
    # interpolate joystick values to correct length
      # 2:throttle, 4:steering
    joyAs25x2 = ( x->[ x[3][:axis][2]; x[3][:axis][4] ] ).(joyVals)
      @show typeof(joyAs25x2), size(joyAs25x2)
    joyVals25x2 = interpToOutx2( joyAs25x2 )
    # @show size(joyVals25x2)
    joyVals25 = hcat(joyVals25x2, zeros(25,2))
    # build the factor
    FluxModelsPose2Pose2(allModels, joyVals25, baselineOdo, naiveFrac)
  else
    # TODO add covariance later
    Pose2Pose2(baselineOdo)
  end
  # take timestamp from image msg
  addFactor!(fec.slam.dfg, [prevpose;newpose], pp,
             timestamp=nanosecond2datetime(syncz.camOdo[idxO][2]) )

  # variables that can be initialized / solved
  put!(fec.slam.solveSettings.solvables, [newpose; ls(fec.slam.dfg,newpose)[1]])

  # add any potential landmarks and factors
  tagsL = detector(syncz.leftFwdCam[idxL][3] |> collect)
  # tagsR = detector(syncHdlr[:right][idxR][2])

  @show tagsL |> length

  for tag in tagsL
    @show lmid = Symbol("l$(tag.id)")
    !exists(slam.dfg, lmid) && addVariable!(slam.dfg, lmid, Pose2)
    # pose = homographytopose(tag.H, fx, fy, cx, cy, taglength = 160.)
    pose, err1 = tagOrthogonalIteration(tag, fx, fy, cx, cy, taglength = 0.25)
    cTt = LinearMap(pose[1:3, 1:3])∘Translation((pose[1:3,4])...)
    bRc = Rotations.Quat(1/sqrt(2),0,0,-1/sqrt(2))*Rotations.Quat(1/sqrt(2),-1/sqrt(2),0,0)
    bTt = LinearMap(bRc) ∘ cTt
    # wTb = LinearMap(zT.R.R) ∘ Translation(zT.t...)
    # wTt = wTb ∘ bTt

    ld = LinearAlgebra.cross(bTt.linear*[0;0;1], [0;0;1])
    theta = atan(ld[2],ld[1])
    Dtag = [bTt.translation[1:2,];theta]
    # println("$(tag.id), $(ld[3]), $(round.(Dtag, digits=3))")
    p2l2 = Pose2Pose2(MvNormal(Dtag,diagm([0.1;0.1;0.1].^2)))
    addFactor!(slam.dfg, [newpose;lmid], p2l2, timestamp=imgTime)

    # FIXME should be in FEC
    if parsed_args["vis3d"]
      setobject!(vis[:tags][Symbol("tag_$(tag.id)")][:triad], Triad(0.2))
      tagobj = HyperRectangle(Vec(0.,0,0), Vec(0.3,0.3,0.03))
      setobject!(vis[:tags][Symbol("tag_$(tag.id)")][:plate], tagobj)
      settransform!(vis[:tags][Symbol("tag_$(tag.id)")], bTt) # bTt
    end

    # setobject!(vis[:poses][Symbol(newpose)], Triad(0.5))
    # settransform!(vis[:poses][Symbol(newpose)], wTb)

  end

  # check whether a solve should be trigger
  checkSolveStrideTrigger!(fec.slam)

  nothing
end

function drawLatestImage(fec::FrontEndContainer; syncList=[:leftFwdCam;])
  # get synced images
  idxL = findSyncLatestIdx(fec.synchronizer, weirdOffset=WEIRDOFFSET, syncList=syncList )

  # @info "drawLatestImagePair, $idxL, $idxR, $idxO"
  if idxL == 0
    return nothing
  end

  # Do apriltag detection
  tagsL = detector(fec.synchronizer.leftFwdCam[idxL][3] |> collect)

  # @show poses = (T->homographytopose(T.H, fx, fy, cx, cy, taglength = 160.)).(tagsL)
  imgLt = AprilTags.drawTags(fec.synchronizer.leftFwdCam[idxL][3], K, tagsL) # showImage

  # imgL = syncImgs[:left][idxL][2] .|> Gray
  # imgR = syncImgs[:right][idxR][2] .|> Gray
  # featuresL = Keypoints(fastcorners(imgL, 12, 0.25))
  # featuresR = Features(fastcorners(imgR, 12, 0.25))
  # offsetx = CartesianIndex(0, 5)
  # offsety = CartesianIndex(5, 0)
  # map(m -> draw!(imgLt, LineSegment(m - offsetx, m + offsetx)), featuresL)
  # map(m -> draw!(imgLt, LineSegment(m - offsety, m + offsety)), featuresL)

  # draw both
  if parsed_args["imshow"]
    imshow(canvases[1,1], imgLt)
    Gtk.showall(gui["window"])
  end
  nothing
end

##  MessageHandler

function leftImgHdlr(msgdata, fec::FrontEndContainer)
  # @show "leftImgHdlr", msgdata[2].header.seq
  # @show "leftImgHdlr"
  mtms = getROSPyMsgTimestamp(msgdata[end])
  nsT = Int64(datetime2unix(mtms[1]))*1000_000_000 + mtms[2]

  leftdata = take!(IOBuffer(msgdata[2].data))

  # more direct image from ROS and zed
  nvi = normedview(leftdata)
  # im3 = last(fec.synchronizer.leftFwdCam)[2]
  arr = reshape(nvi, 3, 672,376) # im3
  pv = PermutedDimsArray(arr, (1,3,2))
  pvn = similar(pv)
  pvn[3,:,:] .= pv[1,:,:]
  pvn[2,:,:] .= pv[2,:,:]
  pvn[1,:,:] .= pv[3,:,:]
  ldImg = colorview(RGB, pvn)

  # for compressed images
  # ldImg = ImageMagick.load_(leftdata)
  push!(fec.synchronizer.leftFwdCam, (msgdata[2].header.seq, nsT, ldImg) )
  # img = last(SyncImages[:left])
  nothing
end

function odomHdlr(msgdata, fec::FrontEndContainer, WO, allModels=nothing)
  # @show "odomHdlr", msgdata[2].header.seq
  # @show "odomHdlr"
  mtms = getROSPyMsgTimestamp(msgdata[end])
  nsT = Int64(datetime2unix(mtms[1]))*1000_000_000 + mtms[2]

  pos = msgdata[2].pose.pose.position
  quat = msgdata[2].pose.pose.orientation

  zPi = [pos.x;pos.y;pos.z]
  zQi = TU.Quaternion(quat.w,[quat.x;quat.y;quat.z])

  zTi = SE3(zPi, zQi)
  push!(fec.synchronizer.camOdo, (msgdata[2].header.seq, nsT, zTi) )

  # show leftFwdCam image
  drawLatestImage(fec, syncList=[:leftFwdCam;])

  # update the factor graph object as required
  if allModels == nothing
    updateSLAMMono!(fec, WO, useFluxModels=false )
  else
    updateSLAMMono!(  fec, WO, useFluxModels=true, allModels=allModels, naiveFrac=parsed_args["naive_frac"]  )
  end

  nothing
end

function joystickHdlr(msgdata, fec::FrontEndContainer)
  # @show "joystickHdlr", msgdata[2].header.seq
  mtms = getROSPyMsgTimestamp(msgdata[end])
  nsT = Int64(datetime2unix(mtms[1]))*1000_000_000 + mtms[2]

  data = Dict(:axis => msgdata[2].axes,
              :buttons => msgdata[2].buttons)
  # @show data
  push!(fec.synchronizer.cmdVal, (msgdata[2].header.seq, nsT, data) )

  #
end

#


function KolmogorovSmirnovTest()
    return KolmogorovSmirnovTest(())
end

function KolmogorovSmirnovTest(arg0::jlong)
    return KolmogorovSmirnovTest((jlong,), arg0)
end

function approximate_p(obj::KolmogorovSmirnovTest, arg0::jdouble, arg1::jint, arg2::jint)
    return jcall(obj, "approximateP", jdouble, (jdouble, jint, jint), arg0, arg1, arg2)
end

function bootstrap(obj::KolmogorovSmirnovTest, arg0::Vector{jdouble}, arg1::Vector{jdouble}, arg2::jint)
    return jcall(obj, "bootstrap", jdouble, (Vector{jdouble}, Vector{jdouble}, jint), arg0, arg1, arg2)
end

function bootstrap(obj::KolmogorovSmirnovTest, arg0::Vector{jdouble}, arg1::Vector{jdouble}, arg2::jint, arg3::jboolean)
    return jcall(obj, "bootstrap", jdouble, (Vector{jdouble}, Vector{jdouble}, jint, jboolean), arg0, arg1, arg2, arg3)
end

function cdf(obj::KolmogorovSmirnovTest, arg0::jdouble, arg1::jint)
    return jcall(obj, "cdf", jdouble, (jdouble, jint), arg0, arg1)
end

function cdf(obj::KolmogorovSmirnovTest, arg0::jdouble, arg1::jint, arg2::jboolean)
    return jcall(obj, "cdf", jdouble, (jdouble, jint, jboolean), arg0, arg1, arg2)
end

function cdf_exact(obj::KolmogorovSmirnovTest, arg0::jdouble, arg1::jint)
    return jcall(obj, "cdfExact", jdouble, (jdouble, jint), arg0, arg1)
end

function exact_p(obj::KolmogorovSmirnovTest, arg0::jdouble, arg1::jint, arg2::jint, arg3::jboolean)
    return jcall(obj, "exactP", jdouble, (jdouble, jint, jint, jboolean), arg0, arg1, arg2, arg3)
end

function kolmogorov_smirnov_statistic(obj::KolmogorovSmirnovTest, arg0::RealDistribution, arg1::Vector{jdouble})
    return jcall(obj, "kolmogorovSmirnovStatistic", jdouble, (RealDistribution, Vector{jdouble}), arg0, arg1)
end

function kolmogorov_smirnov_statistic(obj::KolmogorovSmirnovTest, arg0::Vector{jdouble}, arg1::Vector{jdouble})
    return jcall(obj, "kolmogorovSmirnovStatistic", jdouble, (Vector{jdouble}, Vector{jdouble}), arg0, arg1)
end

function kolmogorov_smirnov_test(obj::KolmogorovSmirnovTest, arg0::RealDistribution, arg1::Vector{jdouble})
    return jcall(obj, "kolmogorovSmirnovTest", jdouble, (RealDistribution, Vector{jdouble}), arg0, arg1)
end

function kolmogorov_smirnov_test(obj::KolmogorovSmirnovTest, arg0::RealDistribution, arg1::Vector{jdouble}, arg2::jboolean)
    return jcall(obj, "kolmogorovSmirnovTest", jdouble, (RealDistribution, Vector{jdouble}, jboolean), arg0, arg1, arg2)
end

function kolmogorov_smirnov_test(obj::KolmogorovSmirnovTest, arg0::RealDistribution, arg1::Vector{jdouble}, arg2::jdouble)
    return jcall(obj, "kolmogorovSmirnovTest", jboolean, (RealDistribution, Vector{jdouble}, jdouble), arg0, arg1, arg2)
end

function kolmogorov_smirnov_test(obj::KolmogorovSmirnovTest, arg0::Vector{jdouble}, arg1::Vector{jdouble})
    return jcall(obj, "kolmogorovSmirnovTest", jdouble, (Vector{jdouble}, Vector{jdouble}), arg0, arg1)
end

function kolmogorov_smirnov_test(obj::KolmogorovSmirnovTest, arg0::Vector{jdouble}, arg1::Vector{jdouble}, arg2::jboolean)
    return jcall(obj, "kolmogorovSmirnovTest", jdouble, (Vector{jdouble}, Vector{jdouble}, jboolean), arg0, arg1, arg2)
end

function ks_sum(obj::KolmogorovSmirnovTest, arg0::jdouble, arg1::jdouble, arg2::jint)
    return jcall(obj, "ksSum", jdouble, (jdouble, jdouble, jint), arg0, arg1, arg2)
end

function pelz_good(obj::KolmogorovSmirnovTest, arg0::jdouble, arg1::jint)
    return jcall(obj, "pelzGood", jdouble, (jdouble, jint), arg0, arg1)
end


const BaseRules = OrderedRuleSet(
    "Base",
    ("LowCounts", r -> get(r, :COUNTS, 2001.0) < 2000.0),
    (
        "Maraging",
        r -> (r.FIRSTELM == n"Fe") &&
                 (r.NI > 5) && (r.CO > 2) && sum(r, :FE, :NI, :CO, :MO) > 80.0,
    ),
    ("Quartz-like", r -> r.SI > 80),
    (
        "Biotite",
        r -> (r.K > 5) &&
                 (r.MG + r.FE > 20) &&
                 (r.AL > 10) &&
                 (r.SI > 10) && (r.K + r.MG + r.FE + r.AL + r.SI > 80) && (r.O > 5),
    ),
    (
        "Plagioclaise",
        r -> (r.CA + r.NA > 20) &&
                 (r.AL > 10) && (r.SI > 10) && (r.NA + r.CA + r.AL + r.SI > 80) && (r.O > 5),
    ),
    (
        "Othoclaise",
        r -> (r.K > 5) &&
                 (r.AL > 20) && (r.SI > 20) && (r.O > 10) && (r.K + r.AL + r.SI > 80),
    ),
    ("Dolomite", r -> (r.CA > 40) && (r.MG > 10) && (r.CA + r.MG > 70)),
    ("Olivine", r -> (r.MG + r.FE > 40) && (r.SI > 20) && (r.MG + r.FE + r.SI > 80)),
    (
        "Feldspar",
        r -> (r.K + r.NA + r.CA > 20) &&
                 (r.AL > 10) && (r.SI > 20) && (r.K + r.NA + r.CA + r.AL + r.SI > 80),
    ),
    ("Willemite", r -> (r.ZN > 20) && (r.SI > 20) && (r.ZN + r.SI > 80)),
    (
        "Garnet",
        r -> (r.MG + r.FE + r.MN + r.CA > 10) &&
                 (r.AL > 5) && (r.SI > 10) && (r.MG + r.FE + r.MN + r.CA + r.AL + r.SI > 80),
    ),
    (
        "Al-Cu alloy (2XXX)",
        r -> (r.AL > 60) && (r.SECONDELM == n"Cu") && (r.CU > 3) && (r.AL + r.CU > 90),
    ),
    (
        "Al-Mn alloy (3XXX)",
        r -> (r.AL > 60) && (r.SECONDELM == n"Mn") && (r.MN > 3) && (r.AL + r.MN > 90),
    ),
    (
        "Al-Si alloy (4XXX)",
        r -> (r.AL > 80) && (r.SECONDELM == n"Si") && (r.SI > 1) && (r.AL + r.SI > 90),
    ),
    (
        "Al-Mg alloy (5XXX)",
        r -> (r.FIRSTELM == n"Al") && (r.SECONDELM == n"Mg") && (r.MG > 5) && (r.AL + r.MG > 80),
    ),
    (
        "Al-Si-Mg (6XXX)",
        r -> (r.AL > 90) &&
                 (r.SI > 0) && (r.MG > 0) && (r.AL + r.SI + r.MG + r.MN + r.CU + r.FE > 98),
    ),
    (
        "Al-Zn alloy (7XXX)",
        r -> (r.AL > 80) && (r.SECONDELM == n"Zn") && (r.ZN > 2) && (r.AL + r.ZN > 90),
    ),
    ("Al-Fe alloy", r -> (r.AL > 80) && (r.SECONDELM == n"Fe") && (r.FE > 2) && (r.AL + r.FE > 90)),
    ("Aluminosilicate", r -> (r.AL > 30) && (r.SI > 10) && (r.AL + r.SI > 80)),
    ("Zircon", r -> (r.ZR > 20) && (r.ZR + r.SI > 80)),
    (
        "Elgiloy",
        r -> (r.CO > 20) &&
                 (r.CR > 10) &&
                 (r.NI > 5) &&
                 (r.FE > 5) &&
                 ((r.MO > 2) || (r.S > 2)) &&
                 (r.CO + r.CR + r.NI + r.FE + r.MO + r.MN + r.S > 80),
    ),
    ("Aluminum", r -> r.AL > 80),
    ("Fe-Al alloy", r -> (r.AL > 20) && (r.FE > 20) && (r.AL + r.FE > 80)),
    ("Calcium silicate", r -> (r.CA > 40) && (r.SI > 10) && (r.CA + r.SI > 80)),
    ("Fe-Cr stainless", r -> (r.FE > 50) && (r.CR > 5) && (r.NI < 3) && (r.FE + r.CR > 90)),
    (
        "Fe-Cr-Ni Stainless",
        r -> (r.FIRSTELM == n"Fe") && (r.CR > 5) && (r.NI > 5) && (r.FE + r.CR + r.NI > 80),
    ),
    ("Fe-Ni stainless", r -> (r.FE > 50) && (r.NI > 10) && (r.FE + r.NI > 90)),
    ("Iron oxide", r -> (r.FE > 80) && (r.O > 10)),
    ("Iron", r -> r.FE > 80),
    ("Calcite", r -> (r.CA > 80) && (r.O > 5)),
    ("Zinc", r -> r.ZN > 80),
    ("Silicate", r -> r.FIRSTELM == n"Si"),
    (
        "Anglesite",
        r -> (r.FIRSTELM == n"Pb") && (r.SECONDELM == n"S") && (r.PB + r.S > 80) && (r.O > 5),
    ),
    ("Hashemite", r -> (r.BA > 20) && (r.CR > 20) && (r.BA + r.CR > 80) && (r.O > 5)),
    ("Barite", r -> (r.BA > 40) && (r.S > 10) && (r.BA + r.S > 80) && (r.O > 5)),
    ("Gypsum", r -> (r.CA > 20) && (r.S > 20) && (r.CA + r.S > 80) && (r.O > 5)),
    ("Lead", r -> (r.PB > 60) && (r.PB + r.S > 80)),
    ("Salt", r -> (r.K + r.NA > 20) && (r.CL > 20) && (r.K + r.NA + r.CL > 80)),
    ("Bismuth", r -> (r.BI > 80)),
    ("Brass", r -> (r.CU > 20) && (r.ZN > 20) && (r.CU + r.ZN > 80)),
    ("Bronze", r -> (r.CU > 20) && (r.SN > 20) && (r.CU + r.SN > 80)),
    ("Cupronickel", r -> (r.CU > 30) && (r.NI > 10) && (r.CU + r.NI > 80)),
    ("Sodium sulfide", r -> (r.NA > 20) && (r.S > 10) && (r.NA + r.S > 80)),
    ("Wurtzite (ZnS)", r -> (r.ZN > 20) && (r.S > 10) && (r.ZN + r.S > 80)),
    ("Pyrite (FeS2)", r -> (r.FE > 20) && (r.S > 20) && (r.FE + r.S > 80)),
    ("Silver sulfide", r -> (r.AG > 20) && (r.S > 10) && (r.AG + r.S > 80)),
    (
        "Sn62 solder",
        r -> (r.SN > 30) && (r.PB > 10) && (r.AG > 1) && (r.SN + r.PB + r.AG > 80),
    ),
    (
        "SACZ solder",
        r -> (r.SN + r.AG + r.CU + r.ZN > 80) &&
                 (r.SN > 10) && (r.CU > 10) && (r.AG > 10) && (r.ZN > 2),
    ),
    ("Lead solder", r -> (r.PB > 20) && (r.SN > 20) && (r.PB + r.SN > 80)),
    (
        "SAC solder",
        r -> (r.SN + r.AG + r.CU > 80) && (r.SN > 10) && (r.CU > 10) && (r.AG > 10),
    ),
    ("Titanium", r -> (r.TI > 80)),
    ("Calcium phosphate", r -> (r.CA > 40) && (r.P > 10) && (r.CA + r.P > 80)),
    ("Calcium fluoride", r -> (r.CA > 40) && (r.F > 10) && (r.CA + r.F > 80)),
    ("Iron-bearing", r -> (r.FE > 50)),
    ("Ca-bearing", r -> r.CA > 50),
    ("Other Silicate", r -> (r.SI > 20) && (r.AL + r.FE + r.CA + r.SI > 80)),
    ("Gold-Silver alloy", r -> (r.FIRSTELM == n"Au") && (r.SECONDELM == n"Ag") && (r.AU + r.AG > 80)),
    ("Gold - other", r -> r.AU > 80),
    ("Tin", r -> r.SN > 80),
    ("Celestine", r -> (r.SR > 40) && (r.S > 10) && (r.SR + r.S > 80) && (r.O > 5)),
    ("Barite-Celestine", r -> (r.BA + r.SR > 40) && (r.BA + r.SR + r.S > 80) && (r.O > 5)),
    ("Cu-rich", r -> r.CU > 80),
    ("Cr-bearing", r -> r.CR > 50),
    ("Silver sulfide", r -> (r.AG > 40) && (r.S > 10) && (r.AG + r.S > 80)),
    ("Silver", r -> r.AG > 80),
    ("MoS2", r -> r.MO > 40 && r.S > 30 && (r.MO + r.S > 90)),
    ("Monazite", r -> sum(r, :LA, :CE, :ND, :TH, :Y) > 30 && (r.P > 10) && sum(r, :LA, :CE, :ND, :TH, :Y, :P, :SI) > 80 ),
    ("Lanthanide", r -> sum(r, :LA, :CE, :ND, :Y) > 60),
    ("Silver chloride", r -> (r.AG > 40) && (r.CL > 10)),
    ("Magnesium oxide", r -> (r.MG > 80) && (r.O > 10)),
    ("Nickel", r -> r.NI > 80),
    ("Ca-Fe silicate", r -> (r.CA > 20) && (r.FE > 20) && (r.SI > 5)),
    ("Ca-Ti silicate", r -> (r.CA > 20) && (r.TI > 20) && (r.SI > 10)),
    ("Al-Zr-Cl", r -> (r.AL > 20) && (r.ZR > 20) && (r.CL > 5)),
    ("Chlorine-rich", r -> r.CL > 80),
    ("Au-Cu alloy", r -> (r.AU > 40) && (r.CU > 10) && (r.AU + r.CU + r.NI > 80)),
    ("Uranium-bearing", r -> r.U > 20),
    ("Thorium-bearing", r -> r.TH > 20),
    ("Platinum", r -> r.PT > 80)
)


function JComplex(arg0::jdouble)
    return JComplex((jdouble,), arg0)
end

function JComplex(arg0::jdouble, arg1::jdouble)
    return JComplex((jdouble, jdouble), arg0, arg1)
end

function abs(obj::JComplex)
    return jcall(obj, "abs", jdouble, ())
end

function acos(obj::JComplex)
    return jcall(obj, "acos", JComplex, ())
end

function acosh(obj::JComplex)
    return jcall(obj, "acosh", JComplex, ())
end

function add(obj::JComplex, arg0::JComplex)
    return jcall(obj, "add", JComplex, (JComplex,), arg0)
end

function add(obj::JComplex, arg0::jdouble)
    return jcall(obj, "add", JComplex, (jdouble,), arg0)
end

function asin(obj::JComplex)
    return jcall(obj, "asin", JComplex, ())
end

function asinh(obj::JComplex)
    return jcall(obj, "asinh", JComplex, ())
end

function atan(obj::JComplex)
    return jcall(obj, "atan", JComplex, ())
end

function atan2(obj::JComplex, arg0::JComplex)
    return jcall(obj, "atan2", JComplex, (JComplex,), arg0)
end

function atanh(obj::JComplex)
    return jcall(obj, "atanh", JComplex, ())
end

function cbrt(obj::JComplex)
    return jcall(obj, "cbrt", JComplex, ())
end

function ceil(obj::JComplex)
    return jcall(obj, "ceil", JComplex, ())
end

function conjugate(obj::JComplex)
    return jcall(obj, "conjugate", JComplex, ())
end

function copy_sign(obj::JComplex, arg0::JComplex)
    return jcall(obj, "copySign", JComplex, (JComplex,), arg0)
end

function copy_sign(obj::JComplex, arg0::jdouble)
    return jcall(obj, "copySign", JComplex, (jdouble,), arg0)
end

function cos(obj::JComplex)
    return jcall(obj, "cos", JComplex, ())
end

function cosh(obj::JComplex)
    return jcall(obj, "cosh", JComplex, ())
end

function divide(obj::JComplex, arg0::JComplex)
    return jcall(obj, "divide", JComplex, (JComplex,), arg0)
end

function divide(obj::JComplex, arg0::jdouble)
    return jcall(obj, "divide", JComplex, (jdouble,), arg0)
end

function equals(::Type{JComplex}, arg0::JComplex, arg1::JComplex)
    return jcall(JComplex, "equals", jboolean, (JComplex, JComplex), arg0, arg1)
end

function equals(::Type{JComplex}, arg0::JComplex, arg1::JComplex, arg2::jdouble)
    return jcall(JComplex, "equals", jboolean, (JComplex, JComplex, jdouble), arg0, arg1, arg2)
end

function equals(::Type{JComplex}, arg0::JComplex, arg1::JComplex, arg2::jint)
    return jcall(JComplex, "equals", jboolean, (JComplex, JComplex, jint), arg0, arg1, arg2)
end

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

function equals_with_relative_tolerance(::Type{JComplex}, arg0::JComplex, arg1::JComplex, arg2::jdouble)
    return jcall(JComplex, "equalsWithRelativeTolerance", jboolean, (JComplex, JComplex, jdouble), arg0, arg1, arg2)
end

function exp(obj::JComplex)
    return jcall(obj, "exp", JComplex, ())
end

function expm1(obj::JComplex)
    return jcall(obj, "expm1", JComplex, ())
end

function floor(obj::JComplex)
    return jcall(obj, "floor", JComplex, ())
end

function get_argument(obj::JComplex)
    return jcall(obj, "getArgument", jdouble, ())
end

function get_exponent(obj::CalculusFieldElement)
    return jcall(obj, "getExponent", jint, ())
end

function get_field(obj::JComplex)
    return jcall(obj, "getField", ComplexField, ())
end

function get_imaginary(obj::JComplex)
    return jcall(obj, "getImaginary", jdouble, ())
end

function get_real(obj::JComplex)
    return jcall(obj, "getReal", jdouble, ())
end

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

function hypot(obj::JComplex, arg0::JComplex)
    return jcall(obj, "hypot", JComplex, (JComplex,), arg0)
end

function is_infinite(obj::JComplex)
    return jcall(obj, "isInfinite", jboolean, ())
end

function is_mathematical_integer(obj::JComplex)
    return jcall(obj, "isMathematicalInteger", jboolean, ())
end

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

function is_real(obj::JComplex)
    return jcall(obj, "isReal", jboolean, ())
end

function linear_combination(obj::JComplex, arg0::JComplex, arg1::JComplex, arg2::JComplex, arg3::JComplex)
    return jcall(obj, "linearCombination", JComplex, (JComplex, JComplex, JComplex, JComplex), arg0, arg1, arg2, arg3)
end

function linear_combination(obj::JComplex, arg0::JComplex, arg1::JComplex, arg2::JComplex, arg3::JComplex, arg4::JComplex, arg5::JComplex)
    return jcall(obj, "linearCombination", JComplex, (JComplex, JComplex, JComplex, JComplex, JComplex, JComplex), arg0, arg1, arg2, arg3, arg4, arg5)
end

function linear_combination(obj::JComplex, arg0::JComplex, arg1::JComplex, arg2::JComplex, arg3::JComplex, arg4::JComplex, arg5::JComplex, arg6::JComplex, arg7::JComplex)
    return jcall(obj, "linearCombination", JComplex, (JComplex, JComplex, JComplex, JComplex, JComplex, JComplex, JComplex, JComplex), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
end

function linear_combination(obj::JComplex, arg0::Vector{JComplex}, arg1::Vector{JComplex})
    return jcall(obj, "linearCombination", JComplex, (Vector{JComplex}, Vector{JComplex}), arg0, arg1)
end

function linear_combination(obj::JComplex, arg0::Vector{jdouble}, arg1::Vector{JComplex})
    return jcall(obj, "linearCombination", JComplex, (Vector{jdouble}, Vector{JComplex}), arg0, arg1)
end

function linear_combination(obj::JComplex, arg0::jdouble, arg1::JComplex, arg2::jdouble, arg3::JComplex)
    return jcall(obj, "linearCombination", JComplex, (jdouble, JComplex, jdouble, JComplex), arg0, arg1, arg2, arg3)
end

function linear_combination(obj::JComplex, arg0::jdouble, arg1::JComplex, arg2::jdouble, arg3::JComplex, arg4::jdouble, arg5::JComplex)
    return jcall(obj, "linearCombination", JComplex, (jdouble, JComplex, jdouble, JComplex, jdouble, JComplex), arg0, arg1, arg2, arg3, arg4, arg5)
end

function linear_combination(obj::JComplex, arg0::jdouble, arg1::JComplex, arg2::jdouble, arg3::JComplex, arg4::jdouble, arg5::JComplex, arg6::jdouble, arg7::JComplex)
    return jcall(obj, "linearCombination", JComplex, (jdouble, JComplex, jdouble, JComplex, jdouble, JComplex, jdouble, JComplex), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
end

function log(obj::JComplex)
    return jcall(obj, "log", JComplex, ())
end

function log10(obj::JComplex)
    return jcall(obj, "log10", JComplex, ())
end

function log1p(obj::JComplex)
    return jcall(obj, "log1p", JComplex, ())
end

function multiply(obj::JComplex, arg0::JComplex)
    return jcall(obj, "multiply", JComplex, (JComplex,), arg0)
end

function multiply(obj::JComplex, arg0::jdouble)
    return jcall(obj, "multiply", JComplex, (jdouble,), arg0)
end

function multiply(obj::JComplex, arg0::jint)
    return jcall(obj, "multiply", JComplex, (jint,), arg0)
end

function negate(obj::JComplex)
    return jcall(obj, "negate", JComplex, ())
end

function new_instance(obj::JComplex, arg0::jdouble)
    return jcall(obj, "newInstance", JComplex, (jdouble,), arg0)
end

function norm(obj::JComplex)
    return jcall(obj, "norm", JComplex, ())
end

function nth_root(obj::JComplex, arg0::jint)
    return jcall(obj, "nthRoot", List, (jint,), arg0)
end

function pow(obj::JComplex, arg0::JComplex)
    return jcall(obj, "pow", JComplex, (JComplex,), arg0)
end

function pow(obj::JComplex, arg0::jdouble)
    return jcall(obj, "pow", JComplex, (jdouble,), arg0)
end

function pow(obj::JComplex, arg0::jint)
    return jcall(obj, "pow", JComplex, (jint,), arg0)
end

function reciprocal(obj::JComplex)
    return jcall(obj, "reciprocal", JComplex, ())
end

function remainder(obj::JComplex, arg0::JComplex)
    return jcall(obj, "remainder", JComplex, (JComplex,), arg0)
end

function remainder(obj::JComplex, arg0::jdouble)
    return jcall(obj, "remainder", JComplex, (jdouble,), arg0)
end

function rint(obj::JComplex)
    return jcall(obj, "rint", JComplex, ())
end

function root_n(obj::JComplex, arg0::jint)
    return jcall(obj, "rootN", JComplex, (jint,), arg0)
end

function scalb(obj::JComplex, arg0::jint)
    return jcall(obj, "scalb", JComplex, (jint,), arg0)
end

function signum(obj::JComplex)
    return jcall(obj, "signum", JComplex, ())
end

function sin(obj::JComplex)
    return jcall(obj, "sin", JComplex, ())
end

function sin_cos(obj::CalculusFieldElement)
    return jcall(obj, "sinCos", FieldSinCos, ())
end

function sinh(obj::JComplex)
    return jcall(obj, "sinh", JComplex, ())
end

function sqrt(obj::JComplex)
    return jcall(obj, "sqrt", JComplex, ())
end

function sqrt1z(obj::JComplex)
    return jcall(obj, "sqrt1z", JComplex, ())
end

function subtract(obj::JComplex, arg0::JComplex)
    return jcall(obj, "subtract", JComplex, (JComplex,), arg0)
end

function subtract(obj::JComplex, arg0::jdouble)
    return jcall(obj, "subtract", JComplex, (jdouble,), arg0)
end

function tan(obj::JComplex)
    return jcall(obj, "tan", JComplex, ())
end

function tanh(obj::JComplex)
    return jcall(obj, "tanh", JComplex, ())
end

function to_degrees(obj::CalculusFieldElement)
    return jcall(obj, "toDegrees", Object, ())
end

function to_radians(obj::CalculusFieldElement)
    return jcall(obj, "toRadians", Object, ())
end

function to_string(obj::JComplex)
    return jcall(obj, "toString", JString, ())
end

function value_of(::Type{JComplex}, arg0::jdouble)
    return jcall(JComplex, "valueOf", JComplex, (jdouble,), arg0)
end

function value_of(::Type{JComplex}, arg0::jdouble, arg1::jdouble)
    return jcall(JComplex, "valueOf", JComplex, (jdouble, jdouble), arg0, arg1)
end


function readgamefile(filename)
    open(filename) do f
        for line in enumerate(eachline(f))
            splitline = split(line[2])
            println(splitline)
            if splitline[1] == "#"
                continue;
            elseif !isa(parse(splitline[1], raise=false), Number)
                # tree = ReadGameInfoLine(split_line)
            else
                node_id = splitline[1]
                # NodeSetGeneralInformation(split_line, tree)
                # if split_line.size() == 3 || split_line.size() == 5
                #   # CreateLeafNode(split_line, tree)
                # elseif node_id < tree->num_chance_histories()
                #   # CreateNatureNode(split_line, tree)
                # else
                #   # CreatePlayerNode(split_line, tree, game)
                # end
            end
        end
    end
end

function readgameinfoline(line)
    num_chance_histories_ = parse(split_line[1]);
    num_terminal_histories_ = parse(split_line[3]);
    num_nodes_ =  (parse(split_line[5]) + 1);
    num_infosets0 = parse(split_line[7]);
    num_infosets1 = parse(split_line[8]);
    return std::make_shared<GameTree>(
    num_chance_histories_,
    num_terminal_histories_, num_nodes_,
    num_infosets0, num_infosets1)
end


module PupperSim    #  9.716722 seconds (11.72 M allocations: 650.717 MiB, 1.66% gc time)
                    # 10.893923 seconds (13.63 M allocations: 756.021 MiB, 2.05% gc time)
                    # 13.839607 seconds (16.71 M allocations: 918.154 MiB, 1.52% gc time)

# modified from https://github.com/klowrey/MujocoSim.jl/

export loadmodel, pupper, simulate

@time using GLFW                    # 0.418646 seconds (564.20 k allocations: 33.920 MiB)
@time using MuJoCo                  # 2.657531 seconds (10.89 M allocations: 579.276 MiB, 6.15% gc time), was 0.705183 seconds (2.45 M allocations: 162.621 MiB)
      using MuJoCo.MJCore           # 0.000602 seconds (463 allocations: 28.750 KiB)
      using StaticArrays            # 0.000598 seconds (482 allocations: 29.875 KiB)
@time using FixedPointNumbers       # 0.058240 seconds (121.71 k allocations: 7.553 MiB)
@time using ColorTypes              # 0.352808 seconds (366.70 k allocations: 22.285 MiB)

const use_VideoIO = true

@static if use_VideoIO
    @time using VideoIO             # 2.877718 seconds (6.32 M allocations: 345.427 MiB, 5.85% gc time)

    const max_video_duration = 60   # max video duration in seconds
    const video_fps = 30            # frames per second determined by GLFW.GetPrimaryMonitor refresh rate
    const max_video_frames = video_fps * max_video_duration
else
    @time using FFMPEG              # 1.175542 seconds (3.00 M allocations: 160.817 MiB, 3.63% gc time)
end

@time using QuadrupedController     # 1.068143 seconds (3.38 M allocations: 171.183 MiB, 10.19% gc time)
@time using CImGui
      using CImGui.GLFWBackend
      using CImGui.OpenGLBackend
      using CImGui.GLFWBackend.GLFW
      using CImGui.OpenGLBackend.ModernGL

include("Sim.jl")

include("util.jl")
include("record_video.jl")
include("setup.jl")

"""
    loadmodel(modelfile = "model/Pupper.xml", width = 1920, height = 1080)

Loads MuJoCo XML model and starts the simulation
"""
function loadmodel(
        modelfile = normpath(joinpath(dirname(pathof(@__MODULE__)), "../model/Pupper.xml")),
        width = 1920, height = 1080
    )
    m = jlModel(modelfile)
    d = jlData(m)

    s = PupperSim.setup(m, d, width, height)

    s.modelfile = modelfile
    @info("Model file: $modelfile")

    GLFW.SetWindowRefreshCallback(s.window, (w)->render(s,w))    # Called on window resize

    return s
end

"""
    pupper(velocity = 0.4, yaw_rate = 0.0)

Creates a Robot controller with specified initial velocity and yaw_rate
"""
function pupper(velocity = 0.2, yaw_rate = 0.0)
    config = Configuration()
    config.z_clearance = 0.02     # height to pick up each foot during trot

    command = Command([velocity, 0], yaw_rate, crouch_height)
    # command.pitch = 0.1

    # Create the robot (controller and controller state)
    Robot(config, command)
end

include("simstep.jl")
include("render.jl")

# Run the simulation
"""
    simulate([s::mjSim[, robot::Robot]])

Run the simulation loop
"""
function simulate(s::mjSim = loadmodel(), robot::Union{Robot, Nothing} = pupper())
    # Assign the passed in robot to our simulator
    s.robot = robot
    robot !== nothing && toggle_activate(robot)

    # Loop until the user closes the window
    while !GLFW.WindowShouldClose(s.window)
        simstep(s)

        render(s, s.window)
        GLFW.PollEvents()
    end

    GLFW.DestroyWindow(s.window)

    return
end

"""
    simulate(modelpath::String, width = 0, height = 0, robot = nothing)

Run the simulation loop
"""
function simulate(modelpath::String, width = 0, height = 0, robot = nothing)
    simulate(loadmodel(modelpath, width, height), robot)
end

end


using  Test, Revise
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\utils\\CUDAGpuUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\distanceMetrics\\Housdorff\\mainHouseDorffKernel\\HFUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\distanceMetrics\\Housdorff\\mainHouseDorffKernel\\ProcessMainData.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\test\\GPUtestUtils.jl")

using ..HFUtils
using ..CUDAGpuUtils,BenchmarkTools , CUDA, StaticArrays

using ..HFUtils, ..ProcessMainData,CUDA,..CUDAGpuUtils,StaticArrays
using ..CUDAGpuUtils,BenchmarkTools , CUDA, StaticArrays

testArrIn = CUDA.ones(Bool,32,32,32);
referenceArray= CUDA.ones(Bool,32,32,32);
resArray = CUDA.zeros(UInt16,32,32,32);
resArraysCounter=CUDA.ones(UInt32,1);
isMaskFull = Ref(false)
isMaskEmpty = Ref(false)
locArr = Ref(Int32(0))

function testprocessMaskData(testArrInn::CuDeviceArray{Bool, 3, 1},resArray,referenceArray,resArraysCounter,isMaskFull::CUDA.CuRefValue{Bool},isMaskEmpty::CUDA.CuRefValue{Bool},locArr)
blockBeginingX,blockBeginingY,blockBeginingZ =UInt8(0),UInt8(0),UInt8(0)
resShmem =  @cuStaticSharedMem(Bool,(34,34,34))#+2 in order to get the one padding
#locArr = Int32(0)
#locArr.x |= true << UInt8(2)

for zIter::UInt8 in UInt8(1):UInt8(32)# most outer loop is responsible for z dimension
    locBool::Bool = @inbounds testArrInn[(blockBeginingX+threadIdxX()),(blockBeginingY +threadIdxY()),(blockBeginingZ+zIter)]
    locArr.x|= locBool << zIter
    #locArr[] |= locBool << UInt8(zIter)
    ProcessMainData.processMaskData( locBool, zIter, resShmem)
end#for

sync_threads() #we should have in resShmem what we need

# @unroll for zIter::UInt8  in UInt8(1):UInt8(32) # most outer loop is responsible for z dimension - importnant in this loop we ignore padding we will deal with it separately
#       local locBoolRegister::Bool = (locArr[]>>zIter & UInt32(1))==UInt32(1)
#       local locBoolShmem::Bool = resShmem[threadIdxX()+1,threadIdxY()+1,zIter+1]
#       ProcessMainData.validataDataFirstPass(locBoolRegister,locBoolShmem,resShmem,isMaskFull,isMaskEmpty,blockBeginingX,blockBeginingY,blockBeginingZ,testArrInn, referenceArray,resArray,resArraysCounter,zIter)
#     #CUDA.unsafe_free!(locBoolRegister)
#     # CUDA.unsafe_free!(locBoolShmem)
#  end#for

return

end#testprocessMaskData

@cuda threads=(32,32) blocks=1 testprocessMaskData(testArrIn,resArray,referenceArray,resArraysCounter,isMaskFull,isMaskEmpty,locArr)


# old and deprecated wrappers

## deprecated in CUDNN 8.0

@cenum cudnnConvolutionFwdPreference_t::UInt32 begin
    CUDNN_CONVOLUTION_FWD_NO_WORKSPACE = 0
    CUDNN_CONVOLUTION_FWD_PREFER_FASTEST = 1
    CUDNN_CONVOLUTION_FWD_SPECIFY_WORKSPACE_LIMIT = 2
end

@cenum cudnnConvolutionBwdFilterPreference_t::UInt32 begin
    CUDNN_CONVOLUTION_BWD_FILTER_NO_WORKSPACE = 0
    CUDNN_CONVOLUTION_BWD_FILTER_PREFER_FASTEST = 1
    CUDNN_CONVOLUTION_BWD_FILTER_SPECIFY_WORKSPACE_LIMIT = 2
end

@cenum cudnnConvolutionBwdDataPreference_t::UInt32 begin
    CUDNN_CONVOLUTION_BWD_DATA_NO_WORKSPACE = 0
    CUDNN_CONVOLUTION_BWD_DATA_PREFER_FASTEST = 1
    CUDNN_CONVOLUTION_BWD_DATA_SPECIFY_WORKSPACE_LIMIT = 2
end

@checked function cudnnGetConvolutionBackwardDataAlgorithm(handle, wDesc, dyDesc, convDesc,
                                                           dxDesc, preference,
                                                           memoryLimitInBytes, algo)
    initialize_api()
    @runtime_ccall((:cudnnGetConvolutionBackwardDataAlgorithm, libcudnn()), cudnnStatus_t,
                   (cudnnHandle_t, cudnnFilterDescriptor_t, cudnnTensorDescriptor_t,
                    cudnnConvolutionDescriptor_t, cudnnTensorDescriptor_t,
                    cudnnConvolutionBwdDataPreference_t, Csize_t,
                    Ptr{cudnnConvolutionBwdDataAlgo_t}),
                   handle, wDesc, dyDesc, convDesc, dxDesc, preference, memoryLimitInBytes,
                   algo)
end

@checked function cudnnSetRNNDescriptor(handle, rnnDesc, hiddenSize, numLayers,
                                        dropoutDesc, inputMode, direction, mode, algo,
                                        mathPrec)
    initialize_api()
    @runtime_ccall((:cudnnSetRNNDescriptor, libcudnn()), cudnnStatus_t,
                   (cudnnHandle_t, cudnnRNNDescriptor_t, Cint, Cint,
                    cudnnDropoutDescriptor_t, cudnnRNNInputMode_t, cudnnDirectionMode_t,
                    cudnnRNNMode_t, cudnnRNNAlgo_t, cudnnDataType_t),
                   handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode,
                   direction, mode, algo, mathPrec)
end

@checked function cudnnGetRNNDescriptor(handle, rnnDesc, hiddenSize, numLayers,
                                        dropoutDesc, inputMode, direction, mode, algo,
                                        mathPrec)
    initialize_api()
    @runtime_ccall((:cudnnGetRNNDescriptor, libcudnn()), cudnnStatus_t,
                   (cudnnHandle_t, cudnnRNNDescriptor_t, Ptr{Cint}, Ptr{Cint},
                    Ptr{cudnnDropoutDescriptor_t}, Ptr{cudnnRNNInputMode_t},
                    Ptr{cudnnDirectionMode_t}, Ptr{cudnnRNNMode_t}, Ptr{cudnnRNNAlgo_t},
                    Ptr{cudnnDataType_t}),
                   handle, rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode,
                   direction, mode, algo, mathPrec)
end

@checked function cudnnSetRNNDescriptor_v5(rnnDesc, hiddenSize, numLayers, dropoutDesc,
                                           inputMode, direction, mode, mathPrec)
    initialize_api()
    @runtime_ccall((:cudnnSetRNNDescriptor_v5, libcudnn()), cudnnStatus_t,
                   (cudnnRNNDescriptor_t, Cint, Cint, cudnnDropoutDescriptor_t,
                    cudnnRNNInputMode_t, cudnnDirectionMode_t, cudnnRNNMode_t,
                    cudnnDataType_t),
                   rnnDesc, hiddenSize, numLayers, dropoutDesc, inputMode, direction, mode,
                   mathPrec)
end

@checked function cudnnGetConvolutionForwardAlgorithm(handle, xDesc, wDesc, convDesc,
                                                      yDesc, preference,
                                                      memoryLimitInBytes, algo)
    initialize_api()
    @runtime_ccall((:cudnnGetConvolutionForwardAlgorithm, libcudnn()), cudnnStatus_t,
                   (cudnnHandle_t, cudnnTensorDescriptor_t, cudnnFilterDescriptor_t,
                    cudnnConvolutionDescriptor_t, cudnnTensorDescriptor_t,
                    cudnnConvolutionFwdPreference_t, Csize_t,
                    Ptr{cudnnConvolutionFwdAlgo_t}),
                   handle, xDesc, wDesc, convDesc, yDesc, preference, memoryLimitInBytes,
                   algo)
end

@checked function cudnnGetConvolutionBackwardFilterAlgorithm(handle, xDesc, dyDesc,
                                                             convDesc, dwDesc, preference,
                                                             memoryLimitInBytes, algo)
    initialize_api()
    @runtime_ccall((:cudnnGetConvolutionBackwardFilterAlgorithm, libcudnn()), cudnnStatus_t,
                   (cudnnHandle_t, cudnnTensorDescriptor_t, cudnnTensorDescriptor_t,
                    cudnnConvolutionDescriptor_t, cudnnFilterDescriptor_t,
                    cudnnConvolutionBwdFilterPreference_t, Csize_t,
                    Ptr{cudnnConvolutionBwdFilterAlgo_t}),
                   handle, xDesc, dyDesc, convDesc, dwDesc, preference, memoryLimitInBytes,
                   algo)
end


import Base

"""
    define_from(::Type{Cascade{Data}}, df)
    define_from(::Type{Plot{Data}}, cascade; kwargs...)
    define_from(::Type{T}, cascade::Cascade{Data}; kwargs...) where T<:Axis

# Keyword Arguments
- `ylabel::String`, y-axis label
-
"""
function define_from(::Type{Cascade{Data}}, df::DataFrames.DataFrame;
    permutation = missing,
    correlate = true,
    permute = true,
    kwargs...,
)
    rows = size(df,1)
    maxstep = min(rows-2,length(COLORCYCLE))

    # Check for numbers in here lager than the colorcycle length?
    permutation = coalesce(permutation, collect(1:maxstep))

    idx = [1; sort(permutation).+1; rows]
    data = define_from(Vector{Data}, df[idx,:]; kwargs...)

    cascade = Cascade(
        start = first(data),
        stop = last(data),
        steps = data[2:end-1],
        correlation = random_rotation(length(permutation);
            nrandom=correlate,
            maxdim=maxstep,
            permutation=permutation,
            kwargs...,
        ),
        permutation = permutation,
        iscorrelated = false,
        ispermuted = false,
    )

    correlate!==false && correlate!(cascade)
    permute && (cascade = permute!(cascade))

    return cascade
end

function define_from(::Type{Data}, sdf::DataFrames.SubDataFrame;
    label,
    sublabel,
    value=VALUE_COL,
    order=[],
    kwargs...,
)
    return Data( ;
        label = sdf[1,label],
        sublabel = sdf[1,sublabel],
        value = sdf[:,value],
        order = isempty(order) ? collect(1:size(sdf,1)) : order,
    )
end

function define_from(::Type{Vector{Data}}, df::DataFrames.DataFrame; kwargs...)
    gdf = fuzzify(df; kwargs...)
    data = [define_from(Data, sdf; kwargs...) for sdf in gdf]

    # Sort "start" samples by magnitude. Ensure consistent ordering in all subsequent steps.
    iiorder = sortperm(get_value(data[1]))
    [set_order!(data[ii], iiorder) for ii in 1:length(data)]

    return data
end

function define_from(::Type{Plot{T}}, cascade::Cascade{Data}; kwargs...) where T<:Geometry
    cascade_geometry = set_geometry(cascade, T; kwargs...)
    legend = _define_legend(cascade_geometry; kwargs...)

    return Plot( ;
        cascade = cascade_geometry,
        xaxis = define_from(XAxis, cascade; kwargs...),
        yaxis = define_from(YAxis, cascade; kwargs...),
        title = _define_title(cascade; kwargs...),
        path = _define_path(cascade, T; kwargs...),
        # Add other annotations to the legend.
        legend = _push!(legend, cascade, Horizontal, Statistics.mean; kwargs...),
    )
end

function define_from(::Type{XAxis}, cascade::Cascade{Data}; kwargs...)
    label_type = Vector{Label{Vector{String}}}
    pad = SEP/2

    return XAxis( ;
        ticklabels = _define_from(label_type, cascade, XAxis, :label;
            yshift = pad,
            kwargs...,
        ),
        ticksublabels = _define_from(label_type, cascade, XAxis, :sublabel;
            yshift = pad+FONTSIZE,
            scale = 0.8,
            kwargs...,
        ),
        frame = _define_from(Line, (Luxor.Point(0,HEIGHT), Luxor.Point(WIDTH+2*SEP,HEIGHT)))
    )
end

function define_from(::Type{YAxis}, cascade::Cascade{Data};
    ylabel,
    x = 0,
    y = HEIGHT,
    kwargs...,
)
    label_type = Label{String}

    return YAxis( ;
        ticks = _define_ticks(cascade, YAxis; kwargs...),
        label = _define_from(label_type, ylabel, Luxor.Point(-(LEFT_BORDER-SEP/2), HEIGHT/2);
            angle = -pi/2,
            valign = :top,
        ),
        ticklabels = _define_from(Vector{label_type}, cascade, YAxis;
            halign = :right,
            xshift = -SEP,
            scale = 0.9,
            kwargs...,
        ),
        frame = _define_from(Arrow, (Luxor.Point(0,HEIGHT), Luxor.Point(0,0))),
    )
end

function define_from(::Type{T}, x; kwargs...) where T <: Luxor.Colorant
    return scale_hsv(parse(T, _define_colorant(x)); kwargs...)
end

"""
    _define_from(Shape, Color, pos, sgn; kwargs...)
    _define_from(::Type{T}, sign, pos; kwargs...) where T<:Vector{Blending}
    _define_from(::Type{T}, sign; kwargs...) where T<:Union{Vector{Coloring},Coloring}

# Keyword Arguments:
- `style`

    _define_from(::Type{Vector{L}}, ::Type{A}, cascade; kwargs...) where {L<:Label, A<:Axis}
    _define_from(::Type{Vector{L}}, ::Type{XAxis}, cascade, fun; kwargs...) where L<:Label
These methods define LABELS for the input `Axis` subtype (XAxis or YAxis).

# Arguments
- `cascade::Cascade`
- `field::Symbol`, if defining ticks for the XAxis, include the Data field name that will
serve as the x-axis tick label or sublabel. Allowed options: `:label`, `:sublabel`.

# Returns
- `ticklabels::Vector{L}` of tick LABELS
- `ticksublabels::Vector{L}` (returned if `A=XAxis` and `fun` unspecified), of tick SUBLABELS

    _define_from(Handle, cascade, fun, args...)

    _define_from(Annotation)
"""
function _define_from(Shape, Color, pos::Vector{T1}, sgn::Vector{T2};
    kwargs...,
) where {T1<:AbstractArray, T2<:AbstractArray}
    return _define_from.(Shape, Color, pos, sgn; kwargs...)
end

function _define_from(Shape, Color, pos::Vector{T}, sgn;
    style,
    kwargs...,
) where T <: Luxor.Point
    return Shape(pos, _define_from(Color, sgn, pos; kwargs...), style)
end

function _define_from(Shape, Color, pos::Vector{T}, sgn;
    style,
    kwargs...,
) where T <: Tuple
    return Shape.(pos, _define_from(Vector{Color}, sgn, pos; kwargs...), style)
end

function _define_from(::Type{Coloring}, sign, args...;
    alpha = 0.8,
    kwargs...,
)
    hue = define_from(Luxor.RGB, sign; kwargs...)
    alpha = _define_alpha(alpha; kwargs...)
    return Coloring(hue, alpha)
end

function _define_from(::Type{Vector{Coloring}}, args...; kwargs...)
    return _define_from.(Coloring, args...; kwargs...)
end

function _define_from(::Type{Vector{Blending}}, sgn, pos; kwargs...)
    hue = _define_gradient.(sgn)

    # Calculate direction.
    xmid = mid(pos; dims=1)

    ii = Dict(k => sgn.==k for k in [-1,1])
    ymax = Dict(k => maximum(pos[v]; dims=2) for (k,v) in ii)
    ymin = Dict(k => minimum(pos[v]; dims=2) for (k,v) in ii)

    begin y1 = fill(0.0, size(sgn)); y2 = fill(0.0, size(sgn)) end
    begin y1[ii[-1]] .= ymin[-1]; y2[ii[-1]] .= ymax[-1] end
    begin y1[ii[+1]] .= ymax[+1]; y2[ii[+1]] .= ymin[+1] end

    direction = tuple.(Luxor.Point.(xmid,y1), Luxor.Point.(xmid,y2))
    return Blending.(direction, hue)
end

## Define Label; lists of Labels

function _define_from(::Type{Vector{T}}, cascade::Cascade{Data}, args...; kwargs...) where T <: Label
    txt = _define_text(cascade, args...; kwargs...)
    pos = _define_position(cascade, args...; kwargs...)
    return _define_from(Vector{T}, txt, pos; kwargs...)
end

function _define_from(::Type{T}, text, pos::Luxor.Point;
    scale = 1,
    halign = :center,
    valign = :middle,
    leading = 1,
    angle = 0,
    kwargs...,
) where T <: Label
    return Label( ;
        text = text,
        scale = scale,
        position = pos,
        halign = halign,
        valign = valign,
        angle = angle,
        leading = leading,
    )
end

function _define_from(::Type{T}, text::String, pos::Luxor.Point, width::Float64;
    scale = 1,
    kwargs...,
) where T <: Label
    return _define_from(T, wrap_to(text, width; scale), pos; scale=scale, kwargs...)
end

function _define_from(::Type{Vector{Label{T}}}, txt::AbstractArray, pos::AbstractArray;
    kwargs...,
) where T <: Vector{String}
    N = length(pos)
    wid = width(N; space=2)
    return [_define_from(Label, txt[ii], pos[ii], wid; kwargs...) for ii in 1:N]
end

function _define_from(::Type{Vector{Label{T}}}, text::AbstractArray, pos::AbstractArray;
    kwargs...,
) where T <: Any
    return [_define_from(Label{T}, text[ii], pos[ii]; kwargs...) for ii in 1:length(pos)]
end

function _define_from(::Type{T}, x; kwargs...) where T<:Label
    # If no point is given, initialize label position to (0,0)
    return _define_from(Label, x, Luxor.Point(0,0); kwargs...)
end

## Define Arrow, Line (for plot axes)

function _define_from(::Type{T}, x::Tuple{Luxor.Point,Luxor.Point};
    hue = "black",
    alpha = 1.0,
    style = :stroke,
    kwargs...,
) where T <: Union{Arrow,Line}
    return T(x, _define_from(Coloring, hue; alpha=alpha), style)
end

## Define Handle

function _define_from(::Type{Handle}, cascade::Cascade{T}, str::String;
    scale = 0.8,
    kwargs...,
) where T <: Geometry
    shape = get_shape(cascade)
    label = _define_from(Label, uppercase(str); halign=:left, scale=scale)
    return set_position!(Handle(shape, label); scale=scale, kwargs...)
end

function _define_from(::Type{Handle}, cascade, fun::Function, args...; kwargs...)
    return _define_from(Handle, cascade, string(fun); kwargs...)
end

function _define_from(::Type{Handle}, cascade, str::String, hue; kwargs...)
    h = _define_from(Handle, cascade, str; kwargs...)
    return set_hue!(h, hue)
end

function _define_from(::Type{Handle}, cascade; colorcycle, kwargs...)
    #= !!!!
    Should probably set a lower limit on alpha for large numbers of samples.
    This gets really light and hard to see.
    =#
    return if colorcycle
        [_define_from(Handle, cascade, "SAMPLE", "black"; idx=1, kwargs...)]
    else
        [_define_from(Handle, cascade, str, hue; idx=idx, kwargs...)
            for (idx, str, hue) in zip(1:2, ["GAIN","LOSS"], [HEX_GAIN,HEX_LOSS])]
    end
end

## Define Annotation

function _define_from(::Type{Annotation}, cascade::Cascade{Data}, Geometry::DataType,
    args...;
    scale = 0.8,
    kwargs...,
)
    return Annotation(
        label = _define_label(cascade, Geometry, args...; scale=scale, kwargs...),
        cascade = set_geometry(cascade, Geometry, args...; alpha=1.0, style=:stroke, kwargs...),
    )
end

## Define values for DataTypes PROPERTIES

"""
    _define_alpha(N::Int; kwargs...)
This function calculates an alpha for `N` samples to scale transparency for overlays:

```math
\\alpha = \\min\\left\\lbrace\\dfrac{w}{f(N)},\\, 0\\right\\rbrace
```

# Keyword Arguments
- `factor::Real=0.25`, scaling weighting ``w``
- `fun::Function=log`, scaling function ``\\ln``
"""
function _define_alpha(N::Int; factor::Real=0.25, fun::Function=log, kwargs...)
    return min(factor/fun(N) ,1.0)
end

_define_alpha(x; kwargs...) = x

"""
    _define_colorant(idx::Int)
This method returns the color defined at index `idx` of `COLORCYCLE`

    _define_colorant(sgn::Float64)
This method returns
- **Red**, given a negative value or
- **Blue**, given a positive value.

    _define_colorant(lst::AbstractVector)
This method returns an average of the colors defined in the `lst`
"""
function _define_colorant(lst::AbstractVector)
    rgb = _define_colorant.(lst)
    rgb_average = [Statistics.mean(getproperty.(rgb, f)) for f in fieldnames(Luxor.RGB)]
    return Luxor.Colors.RGB(rgb_average...)
end

_define_colorant(idx::Int) = COLORCYCLE[idx]
_define_colorant(sgn::Float64) = sign(sgn)>0 ? HEX_GAIN : HEX_LOSS
_define_colorant(x) = parse(Luxor.Colorant, x)

"""
    _define_gradient(x; kwargs...)
This method defines the starting and stopping colors for a color gradient, such that the
starting color is defined using [`Waterfall._define_colorant`](@ref) and the stopping color
is 50% "lighter" than this color.

# Returns
- `x::Tuple{Luxor.RGB,Luxor.RGB}`, color gradient from the starting to stopping pos of
    each waterfall in the cascade.
"""
function _define_gradient(x; kwargs...)
    lightness = 0.5
    h1 = define_from(Luxor.RGB, x)
    h2 = define_from(Luxor.RGB, x; lightness=lightness)
    return tuple(h1, h2)
end

function _define_gradient(x1, x2; kwargs...)
    return tuple(define_from(Luxor.RGB, x1; kwargs...), define_from(Luxor.RGB, x2; kwargs...))
end

"""
    _define_label(cascade, Geometry::DataType, args...; kwargs...)
"""
function _define_label(cascade::Cascade{Data}, Geometry::DataType, args...; kwargs...)
    txt = _define_text(cascade, args...; kwargs...)
    pos = _define_position(cascade, Geometry; kwargs...)
    return _define_from(Vector{Label{String}}, txt, pos; valign=:bottom, kwargs...)
end

"""
    _define_legend(cascade; kwargs)
"""
function _define_legend(cascade::Cascade{T}; kwargs...) where T <: Geometry
    legend = _define_from(Handle, cascade; kwargs...) .=> missing
    return legend |> Vector{Pair{Handle,Any}}
end

"""
    _push!(legend, cascade, Geometry, args...; kwargs...)

# Arguments
- `legend`
- `cascade::Cascade{Data}`
- `fun::Function`
"""
function _push!(
    legend::Vector{Pair{Handle,T}},
    cascade::Cascade{Data},
    Geometry::DataType,
    args...;
    kwargs...,
) where T<:Any
    a =  _define_from(Annotation, cascade, Geometry, args...; kwargs...)
    h = _define_from(Handle, a.cascade, args...; idx=length(legend)+1, kwargs...)
    push!(legend, h => a)
    return legend
end

"""
    _define_path(cascade::Cascade, Geometry; kwargs...)
This method defines the path to which to write the file:
    Geometry/Geometry_n<nsample>_colorcycle<0/1>_corr<0/1>_<permutation>.png
"""
function _define_path(x::Cascade, Geometry;
    colorcycle,
    ext = ".png",
    figdir = "fig",
    maxsample = 50,
    kwargs...,
)
    separator = "_"
    Geometry = lowercase(string(Geometry))

    # Define the path name and create the directory if it doesn't already exist.
    path = joinpath(WATERFALL_DIR, figdir, Geometry)

    if !isdir(path)
        @info("Creating directory: $path")
        mkpath(path)
    end

    str = joinpath(path, join([
        Geometry,
        "n" * _define_path(length(x); maxchar=get_order(maxsample)+1), # number of samples
        _define_path(get_label.(x.steps); kwargs...),                  # labels, in order
        "corr"       * _define_path(x.iscorrelated),                   # is it correlated?
        "colorcycle" * _define_path(colorcycle),                       # colorscheme?
        ], separator,
    ) * ext)

    println("Writing plot: $str")
    return str
end

function _define_path(lst::Vector{String}; maxchar=missing, kwargs...)
    maxchar = coalesce(maxchar, maximum(length.(lst)))
    lst = _define_path.(tryinteger(lst); maxchar=maxchar)
    return join(lst, maxchar>1 ? "-" : "")
end

_define_path(x::Int; maxchar=1) = string(Printf.@sprintf("%010.0f", x)[end-(maxchar-1):end])
_define_path(x::Bool; kwargs...) = string(convert(Int, x))
_define_path(x::String; kwargs...) = x

"""
    _define_position(cascade, ::Type{T}; kwargs...) where T <: Axis
This method defines the TICK POSITIONS for the input Axis subtype (XAxis or YAxis)
`XAxis` tick labels are centered below the associated cascade waterfall.

    _define_position(cascade, Geometry::DataType; kwargs...)
This method defines a point above the top of each cascade waterfall, so that an annotation
label can be bottom/center-aligned at these points.

# Arguments
- `cascade`
- ``

# Keyword Arguments
- `x=0`, x-coordinate of y-axis.
- `y=HEIGHT`, y-coordinate of x-axis.
- `xshift`
- `yshift`
"""
function _define_position(cascade, ::Type{YAxis};
    x = 0,
    xshift = 0,
    kwargs...,
)
    y = collect_ticks(cascade; kwargs...)
    y = scale_y(y; vlim(cascade; kwargs...)...)
    return Luxor.Point.(x+xshift, y)
end

function _define_position(cascade, ::Type{XAxis}, args...;
    y = HEIGHT,
    yshift = 0,
    kwargs...,
)
    x = scale_x( ; nrow=length(collect_data(cascade)))
    return Luxor.Point.(x, y+yshift)
end

function _define_position(cascade, Geometry; kwargs...)
    yshift = -SEP/2
    x = scale_x( ; nrow=length(collect_data(cascade)))
    pos = vectorize.(scale_for(cascade, Geometry; kwargs...))
    y = minimum.(pos; dims=2)
    return Luxor.Point.(x, y.+yshift)
end

"""
    _define_text(x::Float64; kwargs...)
This method formats `x` as a string.

# Keyword Arguments
- `digits=2`, fractional digits to include
- `sgn=true`, indicates whether to show a leading `+`

    _define_text(cascade, fun::Function, args...; kwargs...)
This method calculates a value
"""
function _define_text(x::Float64; sgn=true, digits=2)
    x = round(x; digits=digits)
    sgn = sgn ? sgn : Base.sign(x)<0

    len = length(string(abs(convert(Int, round(x))))) + 1 + sgn
    str = sgn ? @Printf.sprintf("%+2.10f", x) : @Printf.sprintf("%2.10f", x)

    return string(str[1:len+digits])
end

function _define_text(cascade, fun::Function, args...; kwargs...)
    v = collect_value(cascade)
    v = calculate(v, fun, args...)

    digits = abs(minimum(get_order.(v))) + 1

    str = _define_text.(v; digits=digits)
    [str[ii] = str[ii][2:end] for ii in [1,size(str,1)]]

    return str
end

function _define_text(cascade, ::Type{YAxis}; scale=0.9, kwargs...)
    v = collect_ticks(cascade; kwargs...)
    digits = abs(minimum(get_order.(v)))
    return _define_text.(v; digits=digits, sgn=false)
end

function _define_text(cascade, ::Type{XAxis}, field::Symbol; kwargs...)
    return getfield.(collect_data(cascade), field)
end

_define_text(x) = x

"""
"""
function _define_ticks(cascade, ::Type{T};
    x = 0,
    y = HEIGHT,
    len = SEP,
    kwargs...,
) where T <: Axis

    p1 = _define_position(cascade, T; x=x-0.5*len, y=y-0.5*len, kwargs...)
    p2 = _define_position(cascade, T; x=x+0.5*len, y=y+0.5*len, kwargs...)

    return _define_from.(Line, tuple.(p1,p2))
end

"""
    _define_title(cascade::Cascade{Data}; kwargs...)
This method formats a title to describe, where applicable:
1. The number of samples,
2. Range of correlation coefficients (if a correlation was applied), and
3. The distribution function (normal or uniform) used to create samples
    (if more than one sample).
"""
function _define_title(cascade::Cascade{Data}; nsample,
    titlescale = 1.1,
    titleleading = 1.2,
    kwargs...,
)
    # Select which rows of the title to show:
    # (1) Always show the number of samples,
    # (2) Show correlation range if one is applied,
    # (3) Show sample distribution if multiple samples are given.
    idx = [true, cascade.iscorrelated, nsample>1]

    str = [
        "$nsample SAMPLE" * (nsample>1 ? "S" : ""),
        _title_correlation( ; kwargs...),
        _title_distribution( ; kwargs...)
    ][idx]

    return Label( ;
        text = str,
        scale = titlescale,
        position = Luxor.Point(WIDTH/2, -TOP_BORDER),
        halign = :center,
        valign = :middle,
        angle = 0,
        leading = titleleading,
    )
end

"Format title string for normal distribution function"
_title_normal(x) = "f(x; $(x[1]) < s < $(x[2]))"

"Format title string for uniform distribution function"
function _title_uniform(x; abs::Bool=false)
    abs = abs ? "|" : ""
    return "f($(abs)x$(abs); a>$(x[1]), b<$(x[2]))"
end

"Format title string for SAMPLE distribution"
function _title_distribution( ; distribution, fuzziness, kwargs...)
    str = uppercase("$distribution sample distribution: ")

    return if distribution==:normal; str * _title_normal(fuzziness)
    elseif distribution==:uniform;   str * _title_uniform(fuzziness)
    else; ""
    end
end

"Format title string for correlation coefficient"
function _title_correlation( ; interactivity, kwargs...)
    return "CORRELATION COEFFICIENT: " * _title_uniform(interactivity; abs=true)
end


module IonLinearAlgebra

using LinearAlgebra

end


using Revise
using Random
using POMDPs
using LinearAlgebra
using POMDPModelTools
using POMDPPolicies
using POMDPSimulators
using IterTools
using AutomotiveDrivingModels
using AutomotivePOMDPs: polygon, overlap
using Parameters
using GridInterpolations
using StaticArrays
using AutoUrban
using AutoViz
using ProgressMeter
using BenchmarkTools
using Profile
using ProfileView
using Printf

includet("environment.jl")
includet("mdp_definition.jl")

const NTESTS = 1000

mdp = MergingMDP()
statespace = collect(states(mdp));
actionspace = actions(mdp)
rng = MersenneTwister(1)
test_states = rand(rng, statespace, NTESTS)

function benchmark_transition(mdp::MergingMDP, test_states::Vector{MergingState}, actionspace::UnitRange{Int64})
    for s in test_states
        for a in actionspace
            d = transition(mdp, s, a)
        end
    end
end

println("Starting transition benchmark")

@btime benchmark_transition($mdp, $test_states, $actionspace)

Profile.clear()
@profile benchmark_transition(mdp, test_states, actionspace)

ProfileView.view()


# ---------------------------------------------------------------------------- #
#
#   export.jl
#
#   List of functions and constants that are exposed outside of the package
#
#   egads.jl
#   Spring 2018
#
#   Max Opgenoord
#
# ---------------------------------------------------------------------------- #

# functions
export EG_alloc, EG_calloc, EG_reall, EG_free, EG_revision, EG_open,
    EG_loadModel, EG_saveModel, EG_deleteObject, EG_makeTransform,
    EG_getTransformation, EG_getContext, EG_setOutLevel, EG_getInfo,
    EG_copyObject, EG_flipObject, EG_close, EG_setUserPointer,
    EG_getUserPointer, EG_attributeAdd, EG_attributeDel, EG_attributeNum,
    EG_attributeGet, EG_attributeRet, EG_attributeDup, EG_getGeometry,
    EG_makeGeometry, EG_getRange, EG_evaluate, EG_invEvaluate,
    EG_invEvaluateGuess, EG_arcLength, EG_curvature, EG_approximate,
    EG_fitTriangles, EG_otherCurve, EG_isSame, EG_isoCline,
    EG_convertToBSpline, EG_convertToBSplineRange, EG_skinning,
    EG_tolerance, EG_getTolerance, EG_setTolerance, EG_getTopology,
    EG_makeTopology, EG_makeLoop, EG_getArea, EG_makeFace, EG_getBodyTopos,
    EG_indexBodyTopo, EG_objectBodyTopo, EG_inTopology, EG_inFace,
    EG_getEdgeUV, EG_getPCurve, EG_getBody, EG_makeSolidBody,
    EG_getBoundingBox, EG_getMassProperties, EG_isEquivalent,
    EG_sewFaces, EG_replaceFaces, EG_mapBody, EG_matchBodyFaces,
    EG_setTessParam, EG_makeTessGeom, EG_getTessGeom, EG_makeTessBody,
    EG_remakeTess, EG_mapTessBody, EG_locateTessBody, EG_getTessEdge,
    EG_getTessFace, EG_getTessLoops, EG_getTessQuads,
    EG_makeQuads, EG_getQuads, EG_getPatch, EG_quadTess, EG_insertEdgeVerts,
    EG_deleteEdgeVert, EG_moveEdgeVert, EG_openTessBody, EG_initTessBody,
    EG_statusTessBody, EG_setTessEdge, EG_setTessFace, EG_localToGlobal,
    EG_getGlobal, EG_solidBoolean, EG_intersection, EG_imprintBody, EG_filletBody,
    EG_chamferBody, EG_hollowBody, EG_extrude, EG_rotate, EG_sweep, EG_loft,
    EG_blend, EG_ruled, EG_blend_init, EG_blend_pos, EG_blend_sens, EG_blend_seval,
    EG_sens_free, EG_spline1dEval, EG_spline1dEval_psens, EG_spline1dDeriv,
    EG_spline1dFit, EG_spline1dFit_psens, EG_spline2dEval, EG_spline2dEval_psens,
    EG_spline2dDeriv, EG_spline2dAprx, eggGenerate, eggMorph, eggInfo, eggFree,
    EMP_Init, EMP_Done, EMP_ThreadCreate, EMP_ThreadExit, EMP_ThreadWait,
    EMP_ThreadSpin, EMP_ThreadID, EMP_ThreadDestroy, EMP_LockCreate, EMP_LockSet,
    EMP_LockTest, EMP_LockRelease, EMP_LockDestroy, EMP_for, EMP_sum, EMP_min,
    prm_CreateU, prm_CreateUV, prm_SmoothU, prm_SmoothUV, prm_NormalizeU, prm_NormalizeUV,
    prm_LimitGridSize, prm_FixedCfit, prm_FixedGrid, prm_BestCfit, prm_BestGrid,
    prm_EvalCfit, prm_EvalGrid, prm_FreeCfit, prm_FreeGrid

# own functions
export getBodyFromModel, cleanup, EG_join, EG_getBodyFaces

# types
export ego

# null pointer
export NULL_E

# constants
export MAGIC, MTESSPARAM, CONTXT, TRANSFORM, TESSELLATION,NIL, EMPTY
export REFERENCE, PCURVE, CURVE, SURFACE, NODE, EDGE, LOOP, FACE, SHELL
export BODY, MODEL, LINE, CIRCLE, ELLIPSE, PARABOLA, HYPERBOLA, TRIMMED
export BEZIER, BSPLINE, OFFSET, PLANE, SPHERICAL, CYLINDRICAL, REVOLUTION
export TOROIDAL, CONICAL, EXTRUSION, SREVERSE, NOMTYPE, SFORWARD, ONENODE
export TWONODE, OPEN, CLOSED, DEGENERATE, WIREBODY, FACEBODY, SHEETBODY
export SOLIDBODY, ATTRINT, ATTRREAL, ATTRSTRING, ATTRCSYS, ATTRPTR, SUBTRACTION
export INTERSECTION,FUSION, BOX, SPHERE, CONE, CYLINDER, TORUS, UISO, VISO
export NODEOFF, EDGEOFF, FACEDUP, FACECUT, FACEOFF

export EGADS_READERR, EGADS_TESSTATE, EGADS_EXISTS, EGADS_ATTRERR, EGADS_TOPOCNT
export EGADS_OCSEGFLT, EGADS_BADSCALE, EGADS_NOTORTHO, EGADS_DEGEN
export EGADS_CONSTERR, EGADS_TOPOERR, EGADS_GEOMERR, EGADS_NOTBODY
export EGADS_WRITERR, EGADS_NOTMODEL, EGADS_NOLOAD, EGADS_RANGERR, EGADS_NOTGEOM
export EGADS_NOTTESS, EGADS_EMPTY, EGADS_NOTTOPO, EGADS_REFERCE, EGADS_NOTXFORM
export EGADS_NOTCNTX, EGADS_MIXCNTX, EGADS_NODATA, EGADS_NONAME, EGADS_INDEXERR
export EGADS_MALLOC, EGADS_NOTOBJ, EGADS_NULLOBJ, EGADS_NOTFOUND, EGADS_SUCCESS
export EGADS_OUTSIDE, EGADSMAJOR, EGADSMINOR


@testset "fsm_ack_rst_win_relaxed.jl" begin
    base_seq = WrappingInt32(1 << 31)
    DEFAULT_CAPACITY = 64000
    @testset "in ESTABLISHED, send unacceptable segments and ACKs" begin
        conn = fsm_in_established(base_seq - 1, base_seq - 1)

        send_ack!(conn, base_seq, base_seq - 1)
        expect_no_seg(conn)
        send_ack!(conn, base_seq, base_seq - 1)
        expect_no_seg(conn)

        send_byte!(conn, base_seq - 1, base_seq, Char(1))
        @test conn |> unassembled_bytes == 0
        expect_one_seg(conn, ackno=base_seq)

        send_byte!(conn, base_seq + DEFAULT_CAPACITY - 1, base_seq, Char(1))
        @test conn |> unassembled_bytes == 1
        expect_one_seg(conn, ackno=base_seq)
        expect_no_data(conn)

        send_byte!(conn, base_seq, base_seq, Char(1))
        @test conn |> unassembled_bytes == 1
        expect_one_seg(conn, ackno=base_seq+1)
        expect_data!(conn)
        send_rst!(conn, base_seq + 1)
        expect_state(conn, JLSponge.RESET)
    end
end


# Author: Ritchie Lee, ritchie.lee@sv.cmu.edu
# Date: 11/23/2014

module ACASXSensorImpl

export
    initialize,
    update,

    updateSensor,

    ACASXSensor,
    ACASXSensorState,
    ACASXSensorInput,
    ACASXSensorOutput

using AbstractSensorImpl
using AbstractSensorInterfaces
using CommonInterfaces

using CASInterface

import CommonInterfaces.initialize
import CommonInterfaces.update
import AbstractSensorInterfaces.updateSensor

typealias ACASXSensorOutput CASInterface.Input #STM Input

type ACASXSensor <: AbstractSensor
  my_id::Int64 #my id
  max_intruders::Int64
  output::ACASXSensorOutput
end

function ACASXSensor(my_id::Int, max_intruders::Int)
  return ACASXSensor(my_id, max_intruders, ACASXSensorOutput(max_intruders))
end

type ACASXSensorState
  x::Float64 #East
  y::Float64 #North
  z::Float64 #Up
  vx::Float64
  vy::Float64
  vz::Float64
end

type ACASXSensorInput
  states::Vector{ACASXSensorState}
end

getListId(list_owner_id::Integer,id::Integer) = id < list_owner_id ? id : id - 1

function updateSensor(sensor::ACASXSensor, input::ACASXSensorInput)

  own_state = input.states[sensor.my_id]
  ownInput = sensor.output.ownInput

  ownInput.dz = own_state.vz
  ownInput.z = own_state.z #baro alt
  ownInput.psi = atan2(own_state.vx, own_state.vy) #zero when aligned with y-axis / north
  ownInput.h = own_state.z #agl alt
  ownInput.modes = UInt32(sensor.my_id)

  intruders = sensor.output.intruders

  for i = 1:endof(input.states)
    if i != sensor.my_id
      intr_i = getListId(sensor.my_id, i)
      intr_state = input.states[i]
      intruders[intr_i].valid = true
      intruders[intr_i].id = UInt32(i)
      intruders[intr_i].modes = UInt32(i)
      intruders[intr_i].sr = norm([own_state.x, own_state.y, own_state.z]-
                                    [intr_state.x, intr_state.y, intr_state.z]) #slant range (feet)
      intr_psi = atan2(intr_state.x - own_state.x, intr_state.y - own_state.y)
      intruders[intr_i].chi = to_plusminus_pi(intr_psi - ownInput.psi) #bearing (radians, nose is 0, clockwise is positive)
      intruders[intr_i].z = intr_state.z #altitude (feet)  #quantized

      ## These are the defaults.  Modified through coordination
      intruders[intr_i].cvc = 0x0
      intruders[intr_i].vrc = 0x0
      intruders[intr_i].vsb = 0x0
      intruders[intr_i].equipage = EQUIPAGE_TCAS
      intruders[intr_i].quant = intruders[intr_i].equipage == EQUIPAGE_TCAS ? 25 : 100
      intruders[intr_i].sensitivity_index = 0x0
      intruders[intr_i].protection_mode = 0x0
    end
  end

  return sensor.output
end

update(sensor::ACASXSensor, input) = update(sensor, convert(ACASXSensorInput, input))
update(sensor::ACASXSensor, input::ACASXSensorInput) = updateSensor(sensor, input)

function initialize(sensor::ACASXSensor)
  reset!(sensor.output)
end

#mods x to the range [-b, b]
function to_plusminus_b(x::AbstractFloat, b::AbstractFloat)
  z = mod(x, 2 * b)
  return (z > b) ? (z - 2 * b) : z
end

to_plusminus_pi(x::AbstractFloat) = to_plusminus_b(x, Float64(pi))

end


#  Functions and Constants required for traversal

const DNAPAIRS = [(DNA_A, DNA_T),   (DNA_T, DNA_A),
                  (DNA_G, DNA_C),   (DNA_C, DNA_G),
                  (DNA_G, DNA_T),   (DNA_T, DNA_G)]

const DNAGAPS  = [(DNA_A, DNA_Gap), (DNA_Gap, DNA_A),
                  (DNA_C, DNA_Gap), (DNA_Gap, DNA_C),
                  (DNA_G, DNA_Gap), (DNA_Gap, DNA_G),
                  (DNA_T, DNA_Gap), (DNA_Gap, DNA_T)]

const DNAMISMATCH = [(DNA_A, DNA_A), (DNA_A, DNA_C),
                     (DNA_A, DNA_G), (DNA_C, DNA_A),
                     (DNA_C, DNA_C), (DNA_C, DNA_T),
                     (DNA_G, DNA_A), (DNA_G, DNA_G),
                     (DNA_T, DNA_C), (DNA_T, DNA_T)]

const RNAPAIRS = [(RNA_A, RNA_U), (RNA_U, RNA_A),
                  (RNA_G, RNA_C), (RNA_C, RNA_G),
                  (RNA_G, RNA_U), (RNA_U, RNA_G)]

const RNAGAPS  = [(RNA_A, RNA_Gap), (RNA_Gap, RNA_A),
                  (RNA_C, RNA_Gap), (RNA_Gap, RNA_C),
                  (RNA_G, RNA_Gap), (RNA_Gap, RNA_G),
                  (RNA_U, RNA_Gap), (RNA_Gap, RNA_U)]

const RNAMISMATCH = [(RNA_A, RNA_A), (RNA_A, RNA_C),
                     (RNA_A, RNA_G), (RNA_C, RNA_A),
                     (RNA_C, RNA_C), (RNA_C, RNA_U),
                     (RNA_G, RNA_A), (RNA_G, RNA_G),
                     (RNA_U, RNA_C), (RNA_U, RNA_U)]

const PairsType = Vector{Tuple{DNA, DNA}}

function twobitalpha{S <: BioSequences.Sequence}( seq::S )
   t = eltype(seq)
   if t == DNANucleotide
      return DNAAlphabet{2}
   elseif t == RNANucleotide
      return RNAAlphabet{2}
   else
      error("RNASuffixArrays are only built for DNA/RNA input sequences!")
   end
end

pairs{n}(::Type{BioSequences.DNAAlphabet{n}})      = DNAPAIRS
pairs{n}(::Type{BioSequences.RNAAlphabet{n}})      = RNAPAIRS
gaps{n}(::Type{BioSequences.DNAAlphabet{n}})       = DNAGAPS
gaps{n}(::Type{BioSequences.RNAAlphabet{n}})       = RNAGAPS
mismatches{n}(::Type{BioSequences.DNAAlphabet{n}}) = DNAMISMATCH
mismatches{n}(::Type{BioSequences.RNAAlphabet{n}}) = RNAMISMATCH

gap{n}(::Type{DNAAlphabet{n}})                = DNA_Gap
gap{n}(::Type{RNAAlphabet{n}})                = RNA_Gap

onehot{I <: Integer}(x::I) = 0x01 << (x-1)

encodeindex{A <: Alphabet, N <: NucleicAcid}(::Type{A}, x::N) = (x == DNA_Gap || x == RNA_Gap) ? 0 : BioSequences.encode(A, x)+1
index{A <: Alphabet}(::Type{A}, func=pairs) = map( x->map(y->encodeindex(A, y), x), func(A) )

revoffset( x::Int, seq::BioSequence ) = revoffset( x, length(seq) )
revoffset( x, len ) = len - x + 1

Base.reverse( seq::BioSequences.ReferenceSequence ) = BioSequences.ReferenceSequence( reverse( String( seq ) ) )


"""
$(SIGNATURES)

GetNumberOfAperiodicities provides the number of dimensions for aperiodicity
coding. It is determined by only fs.

**Parameters**

- `fs` : Sampleing frequency

**Returns**

- `n` : Number of aperiodicities
"""
function get_number_of_aperiodicities(fs)
    ccall((:GetNumberOfAperiodicities, libworld), Cint, (Cint,), fs)
end

"""
$(SIGNATURES)

CodeAperiodicity codes the aperiodicity. The number of dimensions is
determined by fs.

**Parameters**

- `aperiodicity` : Aperiodicity before coding
- `fs` : Sampling frequency
- `fftsize` : FFT size (default : `get_fftsize_for_cheaptrick(fs)`)

**Returns**

- `coded_aperiodicity` : Coded aperiodicity
"""
function code_aperiodicity(aperiodicity, fs,
                           fftsize=get_fftsize_for_cheaptrick(fs))
    n = get_number_of_aperiodicities(fs)
    f0_length = size(aperiodicity, 2)
    coded_aperiodicity = zeros(Cdouble, n, f0_length)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    ccoded_aperiodicity = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(ccoded_aperiodicity, coded_aperiodicity)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    caperiodicity = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(caperiodicity, aperiodicity)

    ccall((:CodeAperiodicity, libworld), Cvoid,
          (Ptr{Ptr{Cdouble}}, Cint, Cint, Cint, Ptr{Ptr{Cdouble}}),
          caperiodicity, f0_length, fs, fftsize, ccoded_aperiodicity)

    # Array{Cdouble,2} <- Array{Ptr{Cdouble}}
    for i=1:f0_length, j=1:n
        coded_aperiodicity[j,i] = unsafe_load(ccoded_aperiodicity[i], j)
    end

    coded_aperiodicity
end

"""
$(SIGNATURES)

DecodeAperiodicity decoes the coded aperiodicity.

**Parameters**

- `coded_aperiodicity` : Coded aperiodicity
- `fs` : Sampling frequency
- `fftsize` : FFT size (default : `get_fftsize_for_cheaptrick(fs)`)

**Returns**

- `aperiodicity` : Decoded aperiodicity
"""
function decode_aperiodicity(coded_aperiodicity, fs,
                             fftsize=get_fftsize_for_cheaptrick(fs, CheapTrickOption(fs)))
    freqbins = fftsize>>1 + 1
    f0_length = size(coded_aperiodicity, 2)
    aperiodicity = zeros(Cdouble, freqbins, f0_length)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    ccoded_aperiodicity = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(ccoded_aperiodicity, coded_aperiodicity)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    caperiodicity = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(caperiodicity, aperiodicity)

    ccall((:DecodeAperiodicity, libworld), Cvoid,
          (Ptr{Ptr{Cdouble}}, Cint, Cint, Cint, Ptr{Ptr{Cdouble}}),
          ccoded_aperiodicity, f0_length, fs, fftsize, caperiodicity)

    # Array{Cdouble,2} <- Array{Ptr{Cdouble}}
    for i=1:f0_length, j=1:freqbins
        aperiodicity[j,i] = unsafe_load(caperiodicity[i], j)
    end

    aperiodicity
end

"""
$(SIGNATURES)

CodeSpectralEnvelope codes the spectral envelope.

**Parameters**

- `spectrogram` : spectrogram (time sequence of spectral envelope)
- `fs` : Sampling frequency
- `fftsize` : FFT size
- `number_of_dimentions` : Number of dimentions for coded spectral envelope

**Returns**

- `coded_spectral_envelope` : Coded spectral envelope
"""
function code_spectral_envelope(spectrogram, fs,
                                fftsize, number_of_dimentions)
    nd = number_of_dimentions
    f0_length = size(spectrogram, 2)
    coded_spectral_envelope = zeros(Cdouble, nd, f0_length)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    ccoded_spectral_envelope = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(ccoded_spectral_envelope, coded_spectral_envelope)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    cspectrogram = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(cspectrogram, spectrogram)

    ccall((:CodeSpectralEnvelope, libworld), Cvoid,
          (Ptr{Ptr{Cdouble}}, Cint, Cint, Cint, Cint, Ptr{Ptr{Cdouble}}),
          cspectrogram, f0_length, fs, fftsize, nd, ccoded_spectral_envelope)

    # Array{Cdouble,2} <- Array{Ptr{Cdouble}}
    for i=1:f0_length, j=1:nd
        coded_spectral_envelope[j,i] = unsafe_load(ccoded_spectral_envelope[i], j)
    end

    coded_spectral_envelope
end

"""
$(SIGNATURES)

DecodeSpectralEnvelope decodes the spectral envelope.

**Parameters**

- `coded_spectral_envelope` : Coded spectral envelope
- `fs` : Sampling frequency
- `fftsize` : FFT size

**Returns**

- `spectrogram` : decoded spectral envelope
"""
function decode_spectral_envelope(coded_spectral_envelope, fs, fftsize)
    freqbins = fftsize>>1 + 1
    nd = size(coded_spectral_envelope, 1)
    f0_length = size(coded_spectral_envelope, 2)
    spectrogram = zeros(Cdouble, freqbins, f0_length)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    ccoded_spectral_envelope = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(ccoded_spectral_envelope, coded_spectral_envelope)

    # Array{Cdouble,2} -> Array{Ptr{Cdouble}}
    cspectrogram = Array{Ptr{Cdouble}}(undef, f0_length)
    ptrarray2d!(cspectrogram, spectrogram)

    ccall((:DecodeSpectralEnvelope, libworld), Cvoid,
          (Ptr{Ptr{Cdouble}}, Cint, Cint, Cint, Cint, Ptr{Ptr{Cdouble}}),
          ccoded_spectral_envelope, f0_length, fs, fftsize, nd, cspectrogram)

    # Array{Cdouble,2} <- Array{Ptr{Cdouble}}
    for i=1:f0_length, j=1:freqbins
        spectrogram[j,i] = unsafe_load(cspectrogram[i], j)
    end

    spectrogram
end


function clear_all!(window)
    wh = widths(window)
    glViewport(0,0, wh...)
    fb = framebuffer(window)
    glBindFramebuffer(GL_FRAMEBUFFER, fb.id)
    glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1])
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glBindFramebuffer(GL_FRAMEBUFFER, 0)
    glClear(GL_COLOR_BUFFER_BIT)
end

"""
Renders a single frame of a `window`
"""
function render_frame(window)
    fb = framebuffer(window)
    wh = widths(window)
    resize!(fb, wh)
    prepare(fb)
    glViewport(0,0, wh...)
    glClearColor(1,1,1,1)
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT)
    render(window)
    #Read all the selection queries
    push_selectionqueries!(window)
    display(fb, window)
end

function renderloop(window::Screen)
    while isopen(window)
        render_frame(window)
        swapbuffers(window)
        GLFW.PollEvents()
        yield()
    end
    destroy!(window)
end

function prepare(fb::GLFramebuffer)
    glDisable(GL_SCISSOR_TEST)
    glBindFramebuffer(GL_FRAMEBUFFER, fb.id)
    glDrawBuffers(2, [GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1])
end

function display(fb::GLFramebuffer, window)
    glDisable(GL_SCISSOR_TEST)
    glBindFramebuffer(GL_FRAMEBUFFER, 0)
    glViewport(0,0, widths(window)...)
    render(fb.postprocess)
end

function GLAbstraction.render(x::Screen, parent::Screen=x, context=x.area.value)
    if isopen(x) && !ishidden(x)
        sa    = value(x.area)
        sa    = SimpleRectangle(context.x+sa.x, context.y+sa.y, sa.w, sa.h) # bring back to absolute values
        pa    = context
        sa_pa = intersect(pa, sa) # intersection with parent
        if (
            sa_pa != SimpleRectangle(0,0,0,0) && # if it is in the parent area
            (sa_pa.w > 0 && sa_pa.h > 0)
        ) # if it is in the parent area
            glEnable(GL_SCISSOR_TEST)
            glScissor(sa_pa)
            glViewport(sa)
            colorbits = GL_DEPTH_BUFFER_BIT
            if alpha(x.color) > 0
                glClearColor(red(x.color), green(x.color), blue(x.color), alpha(x.color))
                colorbits = colorbits | GL_COLOR_BUFFER_BIT
            end
            glClear(colorbits)

            render(x.renderlist)
            for window in x.children
                render(window, x, sa)
            end
        end
    end
end


export nbc

import ..NBCModel

using mlpack._Internal.io

import mlpack_jll
const nbcLibrary = mlpack_jll.libmlpack_julia_nbc

# Call the C binding of the mlpack nbc binding.
function nbc_mlpackMain()
  success = ccall((:nbc, nbcLibrary), Bool, ())
  if !success
    # Throw an exception---false means there was a C++ exception.
    throw(ErrorException("mlpack binding error; see output"))
  end
end

" Internal module to hold utility functions. "
module nbc_internal
  import ..nbcLibrary

import ...NBCModel

# Get the value of a model pointer parameter of type NBCModel.
function IOGetParamNBCModel(paramName::String)::NBCModel
  NBCModel(ccall((:IO_GetParamNBCModelPtr, nbcLibrary), Ptr{Nothing}, (Cstring,), paramName))
end

# Set the value of a model pointer parameter of type NBCModel.
function IOSetParamNBCModel(paramName::String, model::NBCModel)
  ccall((:IO_SetParamNBCModelPtr, nbcLibrary), Nothing, (Cstring, Ptr{Nothing}), paramName, model.ptr)
end

# Serialize a model to the given stream.
function serializeNBCModel(stream::IO, model::NBCModel)
  buf_len = UInt[0]
  buf_ptr = ccall((:SerializeNBCModelPtr, nbcLibrary), Ptr{UInt8}, (Ptr{Nothing}, Ptr{UInt}), model.ptr, Base.pointer(buf_len))
  buf = Base.unsafe_wrap(Vector{UInt8}, buf_ptr, buf_len[1]; own=true)
  write(stream, buf)
end
# Deserialize a model from the given stream.
function deserializeNBCModel(stream::IO)::NBCModel
  buffer = read(stream)
  NBCModel(ccall((:DeserializeNBCModelPtr, nbcLibrary), Ptr{Nothing}, (Ptr{UInt8}, UInt), Base.pointer(buffer), length(buffer)))
end
end # module

"""
    nbc(; [incremental_variance, input_model, labels, test, training, verbose])

This program trains the Naive Bayes classifier on the given labeled training
set, or loads a model from the given model file, and then may use that trained
model to classify the points in a given test set.

The training set is specified with the `training` parameter.  Labels may be
either the last row of the training set, or alternately the `labels` parameter
may be specified to pass a separate matrix of labels.

If training is not desired, a pre-existing model may be loaded with the
`input_model` parameter.

The `incremental_variance` parameter can be used to force the training to use an
incremental algorithm for calculating variance.  This is slower, but can help
avoid loss of precision in some cases.

If classifying a test set is desired, the test set may be specified with the
`test` parameter, and the classifications may be saved with the
`predictions`predictions  parameter.  If saving the trained model is desired,
this may be done with the `output_model` output parameter.

Note: the `output` and `output_probs` parameters are deprecated and will be
removed in mlpack 4.0.0.  Use `predictions` and `probabilities` instead.

For example, to train a Naive Bayes classifier on the dataset `data` with labels
`labels` and save the model to `nbc_model`, the following command may be used:

```julia
julia> using CSV
julia> data = CSV.read("data.csv")
julia> labels = CSV.read("labels.csv"; type=Int)
julia> _, nbc_model, _, _, _ = nbc(labels=labels, training=data)
```

Then, to use `nbc_model` to predict the classes of the dataset `test_set` and
save the predicted classes to `predictions`, the following command may be used:

```julia
julia> using CSV
julia> test_set = CSV.read("test_set.csv")
julia> predictions, _, _, _, _ = nbc(input_model=nbc_model,
            test=test_set)
```

# Arguments

 - `incremental_variance::Bool`: The variance of each class will be
      calculated incrementally.  Default value `false`.

 - `input_model::NBCModel`: Input Naive Bayes model.
 - `labels::Array{Int, 1}`: A file containing labels for the training
      set.
 - `test::Array{Float64, 2}`: A matrix containing the test set.
 - `training::Array{Float64, 2}`: A matrix containing the training set.
 - `verbose::Bool`: Display informational messages and the full list of
      parameters and timers at the end of execution.  Default value `false`.

# Return values

 - `output::Array{Int, 1}`: The matrix in which the predicted labels for
      the test set will be written (deprecated).
 - `output_model::NBCModel`: File to save trained Naive Bayes model to.
 - `output_probs::Array{Float64, 2}`: The matrix in which the predicted
      probability of labels for the test set will be written (deprecated).
 - `predictions::Array{Int, 1}`: The matrix in which the predicted labels
      for the test set will be written.
 - `probabilities::Array{Float64, 2}`: The matrix in which the predicted
      probability of labels for the test set will be written.

"""
function nbc(;
             incremental_variance::Union{Bool, Missing} = missing,
             input_model::Union{NBCModel, Missing} = missing,
             labels = missing,
             test = missing,
             training = missing,
             verbose::Union{Bool, Missing} = missing,
             points_are_rows::Bool = true)
  # Force the symbols to load.
  ccall((:loadSymbols, nbcLibrary), Nothing, ());

  IORestoreSettings("Parametric Naive Bayes Classifier")

  # Process each input argument before calling mlpackMain().
  if !ismissing(incremental_variance)
    IOSetParam("incremental_variance", convert(Bool, incremental_variance))
  end
  if !ismissing(input_model)
    nbc_internal.IOSetParamNBCModel("input_model", convert(NBCModel, input_model))
  end
  if !ismissing(labels)
    IOSetParamURow("labels", labels)
  end
  if !ismissing(test)
    IOSetParamMat("test", test, points_are_rows)
  end
  if !ismissing(training)
    IOSetParamMat("training", training, points_are_rows)
  end
  if verbose !== nothing && verbose === true
    IOEnableVerbose()
  else
    IODisableVerbose()
  end

  IOSetPassed("output")
  IOSetPassed("output_model")
  IOSetPassed("output_probs")
  IOSetPassed("predictions")
  IOSetPassed("probabilities")
  # Call the program.
  nbc_mlpackMain()

  return IOGetParamURow("output"),
         nbc_internal.IOGetParamNBCModel("output_model"),
         IOGetParamMat("output_probs", points_are_rows),
         IOGetParamURow("predictions"),
         IOGetParamMat("probabilities", points_are_rows)
end


using OpenType
using OpenType: HorizontalMetric, VerticalMetric
using GeometryExperiments
using Test

const arial = joinpath(@__DIR__, "resources", "arial.ttf")
const juliamono = joinpath(@__DIR__, "resources", "juliamono-regular.ttf")

font = OpenTypeFont(juliamono)
# OpenTypeFont(arial)

@testset "OpenType.jl" begin
    font = OpenTypeFont(juliamono)
    glyph = font.glyphs[64]
    @test glyph.header == GlyphHeader(1, 55, -15, 526, 732)
    @test glyph.data.contour_indices == [26]
    glyph_points = [
        GlyphPoint((186,-15), false),
        GlyphPoint((55,171), false),
        GlyphPoint((55,352), true),
        GlyphPoint((55,535), false),
        GlyphPoint((195,732), false),
        GlyphPoint((325,732), true),
        GlyphPoint((390,732), false),
        GlyphPoint((497,687), false),
        GlyphPoint((526,636), true),
        GlyphPoint((471,595), true),
        GlyphPoint((435,634), false),
        GlyphPoint((366,660), false),
        GlyphPoint((322,660), true),
        GlyphPoint((230,660), false),
        GlyphPoint((149,503), false),
        GlyphPoint((149,357), true),
        GlyphPoint((149,211), false),
        GlyphPoint((225,60), false),
        GlyphPoint((319,60), true),
        GlyphPoint((370,60), false),
        GlyphPoint((445,95), false),
        GlyphPoint((472,135), true),
        GlyphPoint((526,87), true),
        GlyphPoint((494,35), false),
        GlyphPoint((386,-15), false),
        GlyphPoint((318,-15), true),
    ]
    @test glyph.data.points == glyph_points

    @testset "Data extraction" begin
        curves = uncompress(glyph)
        curve = first(curves)
        for point in glyph_points
            @test point.coords in curve
        end
        @test first(curve) == Point(55,352)
        for c in curves
            @test last(c) == first(c)
            @test isodd(length(c))
            for (x, y) in c
                @test glyph.header.xmin ≤ minimum(x)
                @test glyph.header.ymin ≤ minimum(y)
                @test glyph.header.xmax ≥ maximum(x)
                @test glyph.header.ymax ≥ maximum(y)
            end
        end

        ncurves = normalize(curves, glyph)
        for ncurve in ncurves
            for point in ncurve
                @test all(0 .≤ point .≤ 1)
            end
        end

        curves = OpenType.curves(glyph)
        @test all(==(3), length.(curves))
    end

    @testset "Character to Glyph mapping" begin
        @test font['c'] == font.glyphs[626]
        @test font['\uffff'] == font.glyphs[1]
    end

    @testset "Metrics" begin
        @test font.hhea.nhmetrics == 8910
        @test font.vhea.nvmetrics == 4575

        @test first(font.hmtx.metrics) == HorizontalMetric(595, 162)
        @test last(font.hmtx.metrics) == HorizontalMetric(600, 0)

        @test first(font.vmtx.metrics) == VerticalMetric(1140, 328)
        @test last(font.vmtx.metrics) == VerticalMetric(1140, 581)
    end
end


using Makie

res = 500
game = Scene(resolution=(res, res))
campixel!(game)
cell = 10
cells = res ÷ 10
snakelen = 5
middle = cells ÷ 2
snakestart = middle - (snakelen ÷ 2)
segments = Point2f0.(middle, range(snakestart, step=cell, length=snakelen))
snake = scatter!(game, segments, markersize=cell, raw=true, color=:blue)[end]
speed = 1 / 5
global last_dir = (0, 1)
dir = lift(game.events.keyboardbuttons) do but
    global last_dir
    ispressed(but, Keyboard.left) && return (last_dir = (-1, 0))
    ispressed(but, Keyboard.up) && return (last_dir = (0, 1))
    ispressed(but, Keyboard.right) && return (last_dir = (1, 0))
    ispressed(but, Keyboard.down) && return (last_dir = (0, -1))
    last_dir
end
display(game)
newsnake = copy(segments)
while isopen(game)
    curr_snake = snake[1][]
    circshift!(newsnake, curr_snake, 1)
    newsnake[1] = newsnake[2] .+ Point2f0(dir[] .* cell)
    snake[1] = newsnake
    newsnake = curr_snake
    sleep(speed)
end


"""
    PointNode(coordinate, properties)

## Examples
```jldoctest
julia> using NeuroCore

julia> PointNode((1,2))
```
"""
struct PointNode{Dim,T,C<:AbstractPoint{Dim,T},P} <: AbstractPoint{Dim,T}
    coordinates::C
    properties::P

    PointNode{Dim,T,C,P}(coord::C, props::P) where {Dim,T,C,P} = new{Dim,T,C,P}(coord, props)
end

function PointNode{Dim,T,C}(coord::C, props::P=nothing) where {Dim,T,C<:AbstractPoint{Dim,T},P}
    return PointNode{Dim,T,C,P}(coord, props)
end

function PointNode{Dim,T}(coord::AbstractPoint{Dim,T}, props=nothing) where {Dim,T}
    return PointNode{Dim,T,typeof(coord)}(coord, props)
end

function PointNode{Dim}(coord::AbstractPoint{Dim,T}, props=nothing) where {Dim,T}
    return PointNode{Dim,T}(coord, props)
end

function PointNode(coord::AbstractPoint{Dim}, prop=nothing) where {Dim}
    return PointNode{Dim}(coord, prop)
end

PointNode(coord::Tuple) = PointNode(Point(coord))

GeometryBasics.coordinates(n::PointNode) = getfield(n, :coordinates)

FieldProperties.properties(n::PointNode) = getfield(n, :properties)

#FieldProperties.@hasfieldprop PointNode

Base.@propagate_inbounds Base.getindex(n::PointNode, i::Int) = coordinates(n)[i]

Base.show(io::IO, n::PointNode) = print_node(io, n)

Base.show(io::IO, ::MIME"text/plain", n::PointNode) = print_node(io, n)

function print_node(io::IO, n::PointNode{Dim,T}) where {Dim,T}
    return print(io, "PointNode($(coordinates(n).data), $(properties(n)))")
end

"""
    PointNodeList{Dim,T,V,P}

"""
const PointNodeList{Dim,T<:PointNode{Dim},V<:AbstractVector{T},P} = MetadataArray{T,1,P,V}

#FieldProperties.@hasfieldprop PointNodeList

function PointNodeList{Dim,T}(v::AbstractVector{T}=T[], props=nothing) where {Dim,T<:PointNode{Dim}}
    return MetadataArray(v, props)
end

PointNodeList(v::AbstractVector{T}, props=nothing) where {T<:PointNode} = PointNodeList{length(T),T}(v, props)

"""
    MultiPointNodeList{T,V,P}
"""
const MultiPointNodeList{T<:PointNodeList,V<:AbstractVector{T},P} = MetadataArray{T,1,P,V}

#FieldProperties.@hasfieldprop PointNodeListVec


@testset "Multi-threaded runs test, with init pop" begin

@info "Commencing test of multi-threaded parallel simulation runs, with initial population. This test may take a few minutes, depending on the user's hardware and Julia settings."
tStart = now()
rm( "parallel/tmps", recursive=true, force=true )
rm( "parallel/simDB2.sqlite", force=true )

mrs = MRS( mpSim )
nRuns = 50
setMRSruns!( mrs, nRuns )
setMRSdatabaseName!( mrs, "parallel/simDB2.sqlite" )
setMRSmaxThreads!( mrs, 10 )
uploadSnapshot( mrs, "snapshot/test foto 1", "Sheet1", ["B", "C", "D"],
    ("A", "G", "F", "E", "H") )
@test verifySimulation!( mrs )

run( mrs )
tElapsed = ( now() - tStart ).value / 1000
@info string( "Running ", nRuns, " simulations in parallel on ",
    min( 10, Threads.nthreads() ), " threads took ", tElapsed,
    " seconds. Thank you for your patience." )

db = SQLite.DB( "parallel/simDB2.sqlite" )
@test length( SQLite.tables( db )[:name] ) == 3 * nRuns + 5
@test all( 1:nRuns ) do ii
    queryCmd = string( "SELECT * FROM `", mrs.mpSim.transDBname, ii, "`",
        "\n    WHERE transition IS 'Init'")
    initpop = DataFrame(DBInterface.execute( db, queryCmd ))
    size( initpop, 1 ) == 50
end  # all( 1:nRuns ) do ii

println()

end  # @testset "Multi-threaded runs test, with init pop"