text:
export NStepInserter, BatchSampler, NStepBatchSampler

using Random

#####
# Inserters
#####

abstract type AbstractInserter end

Base.@kwdef struct NStepInserter <: AbstractInserter
    n::Int = 1
end

function Base.push!(
    t::CircularVectorSARTSATrajectory,
    𝕥::CircularArraySARTTrajectory,
    inserter::NStepInserter,
)
    N = length(𝕥)
    n = inserter.n
    for i in 1:(N-n+1)
        for k in SART
            push!(t[k], select_last_dim(𝕥[k], i))
        end
        push!(t[:next_state], select_last_dim(𝕥[:state], i + n))
        push!(t[:next_action], select_last_dim(𝕥[:action], i + n))
    end
end

#####
# Samplers
#####

abstract type AbstractSampler{traces} end

# TODO: deprecate this method with `(s::AbstractSampler)(traj)` instead

"""
    sample([rng=Random.GLOBAL_RNG], trajectory, sampler, [traces=Val(keys(trajectory))])

!!! note
    Here we return a copy instead of a view:
    1. Each sample is independent of the original `trajectory` so that `trajectory` can be updated async.
    2. [Copy is not always so bad](https://docs.julialang.org/en/v1/manual/performance-tips/#Copying-data-is-not-always-bad).
"""
function StatsBase.sample(t::AbstractTrajectory, sampler::AbstractSampler)
    sample(Random.GLOBAL_RNG, t, sampler)
end

# TODO: add an async batch sampler to pre-fetch next batch

#####
## BatchSampler
#####

mutable struct BatchSampler{traces} <: AbstractSampler{traces}
    batch_size::Int
    cache::Any
    rng::Any
end

BatchSampler(batch_size::Int; cache=nothing, rng=Random.GLOBAL_RNG) = BatchSampler{SARTSA}(batch_size, cache, rng)
BatchSampler{T}(batch_size::Int; cache=nothing, rng=Random.GLOBAL_RNG) where T = BatchSampler{T}(batch_size, cache, rng)

(s::BatchSampler)(t::AbstractTrajectory) = sample(s.rng, t, s)

# TODO: deprecate
function StatsBase.sample(rng::AbstractRNG, t::AbstractTrajectory, s::BatchSampler)
    inds = rand(rng, 1:length(t), s.batch_size)
    fetch!(s, t, inds)
    inds, s.cache
end

function fetch!(
    s::BatchSampler,
    t::AbstractTrajectory,
    inds::Vector{Int},
)
    batch = NamedTuple{keys(t)}(view(t[x], inds) for x in keys(t))
    if isnothing(s.cache)
        s.cache = map(Flux.batch, batch)
    else
        map(s.cache, batch) do dest, src
            batch!(dest, src)
        end
    end
end

function fetch!(
    s::BatchSampler{SARTS},
    t::CircularArraySARTTrajectory,
    inds::Vector{Int}
)
    batch = NamedTuple{SARTS}(
        (
            (consecutive_view(t[x], inds) for x in SART)...,
            consecutive_view(t[:state], inds .+ 1),
        )
    )
    if isnothing(s.cache)
        s.cache = map(batch) do x
            convert(Array, x)
        end
    else
        map(s.cache, batch) do dest, src
            copyto!(dest, src)
        end
    end
end

#####
## NStepBatchSampler
#####

Base.@kwdef mutable struct NStepBatchSampler{traces} <: AbstractSampler{traces}
    γ::Float32
    n::Int = 1
    batch_size::Int = 32
    stack_size::Union{Nothing,Int} = nothing
    rng::Any = Random.GLOBAL_RNG
    cache::Any = nothing
end

# TODO:deprecate
function StatsBase.sample(rng::AbstractRNG, t::AbstractTrajectory, s::NStepBatchSampler)
    valid_range =
        isnothing(s.stack_size) ? (1:(length(t)-s.n+1)) : (s.stack_size:(length(t)-s.n+1))
    inds = rand(rng, valid_range, s.batch_size)
    inds, fetch!(s, t, inds)
end

function StatsBase.sample(rng::AbstractRNG, t::PrioritizedTrajectory, s::NStepBatchSampler)
    bz, sz = s.batch_size, s.stack_size
    inds = Vector{Int}(undef, bz)
    priorities = Vector{Float32}(undef, bz)
    valid_ind_range = isnothing(sz) ? (1:(length(t)-s.n+1)) : (sz:(length(t)-s.n+1))
    for i in 1:bz
        ind, p = sample(rng, t.priority)
        while ind ∉ valid_ind_range
            ind, p = sample(rng, t.priority)
        end
        inds[i] = ind
        priorities[i] = p
    end
    inds, (priority = priorities, fetch!(s, t.traj, inds)...)
end

function fetch!(
    sampler::NStepBatchSampler{traces},
    traj::CircularArraySARTTrajectory,
    inds::Vector{Int},
) where {traces}
    γ, n, bz, sz = sampler.γ, sampler.n, sampler.batch_size, sampler.stack_size
    cache = sampler.cache
    next_inds = inds .+ n

    s = consecutive_view(traj[:state], inds; n_stack = sz)
    a = consecutive_view(traj[:action], inds)
    s′ = consecutive_view(traj[:state], next_inds; n_stack = sz)

    consecutive_rewards = consecutive_view(traj[:reward], inds; n_horizon = n)
    consecutive_terminals = consecutive_view(traj[:terminal], inds; n_horizon = n)
    r = isnothing(cache) ? zeros(Float32, bz) : cache.reward
    t = isnothing(cache) ? fill(false, bz) : cache.terminal

    # make sure that we only consider experiences in current episode
    for i in 1:bz
        m = findfirst(view(consecutive_terminals, :, i))
        if isnothing(m)
            t[i] = false
            r[i] = discount_rewards_reduced(view(consecutive_rewards, :, i), γ)
        else
            t[i] = true
            r[i] = discount_rewards_reduced(view(consecutive_rewards, 1:m, i), γ)
        end
    end

    if traces == SARTS
        batch = NamedTuple{SARTS}((s, a, r, t, s′))
    elseif traces == SLARTSL
        l = consecutive_view(traj[:legal_actions_mask], inds)
        l′ = consecutive_view(traj[:next_legal_actions_mask], next_inds)
        batch = NamedTuple{SLARTSL}((s, l, a, r, t, s′, l′))
    else
        @error "unsupported traces $traces"
    end

    if isnothing(sampler.cache)
        sampler.cache = map(batch) do x
            convert(Array, x)
        end
    else
        map(sampler.cache, batch) do dest, src
            copyto!(dest, src)
        end
    end
end


text:
using Optim

export SES, loss, fit, forecast

struct SES
    α::Float64
    l0::Float64

    SES() = new(0, 0)
    SES(α::Number, l0::Number) = new(Float64(α), Float64(l0))
end

function loss(model::SES, time_series)
    α = model.α
    l0 = model.l0

    loss = 0
    N = length(time_series)
    y_pred = 0

    for i in 1:(N)
        if i == 1
            y_pred = l0
        else
            y_pred = time_series[i - 1] * α + y_pred * (1 - α)
        end

        loss += (time_series[i] - y_pred)^2
    end

    return loss
end

function fit(model::SES, y)
    lower = [0.0, -Inf]
    upper = [1.0, Inf]
    initial = [model.α, model.l0]

    function loss_(parameters::Array{Float64})
        α = parameters[1]
        l0 = parameters[2]
        return loss(SES(α, l0), y)
    end

    res = Optim.optimize(loss_, lower, upper, initial)
    optimal = Optim.minimizer(res)
    return SES(optimal[1], optimal[2])
end

function forecast(model, time_series, forecast_length)
    α = model.α
    l0 = model.l0

    N = length(time_series)
    y_pred = 0
    pred = []

    for i in 1:(N)
        if i == 1
            y_pred = l0
        else
            y_pred = time_series[i - 1] * α + y_pred * (1 - α)
        end
    end

    y_pred = time_series[N] * α + y_pred * (1 - α)

    for j in 1:forecast_length
        push!(pred, y_pred)
    end

    return pred
end

ext: jl


text:
using LinearRegression
using Test

@testset "LinearRegression.jl" begin
    include("linear_regression.jl")
end


text:
# ------------------------------------------------------------------
# Licensed under the MIT License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    HomogeneousSampling(size)

Generate sample of given `size` from geometric object
according to a homogeneous density.
"""
struct HomogeneousSampling <: ContinuousSamplingMethod
  size::Int
end

function sample(Ω::DomainOrData, method::HomogeneousSampling)
  size    = method.size
  weights = measure.(Ω)

  # sample elements with weights proportial to measure
  w = WeightedSampling(size, weights, replace=true)

  # within each element sample a single point
  h = HomogeneousSampling(1)

  ivec(first(sample(e, h)) for e in sample(Ω, w))
end

function sample(triangle::Triangle{Dim,T}, method::HomogeneousSampling) where {Dim,T}
  A, B, C = coordinates.(vertices(triangle))
  function randpoint()
    # sample barycentric coordinates
    u₁, u₂ = rand(T, 2)
    λ₁, λ₂ = 1 - √u₁, u₂ * √u₁
    λ₃     = 1 - λ₁ - λ₂
    Point(λ₁ .* A + λ₂ .* B + λ₃ .* C)
  end
  ivec(randpoint() for _ in 1:method.size)
end


text:
export BrickletMotionDetectorV2Indicator
struct BrickletMotionDetectorV2Indicator
    top_left::Integer
    top_right::Integer
    bottom::Integer
end

export BrickletMotionDetectorV2SPITFPErrorCount
struct BrickletMotionDetectorV2SPITFPErrorCount
    error_count_ack_checksum::Integer
    error_count_message_checksum::Integer
    error_count_frame::Integer
    error_count_overflow::Integer
end

export BrickletMotionDetectorV2Identity
struct BrickletMotionDetectorV2Identity
    uid::String
    connected_uid::String
    position::Char
    hardware_version::Vector{Integer}
    firmware_version::Vector{Integer}
    device_identifier::Integer
end

export BrickletMotionDetectorV2
"""
Passive infrared (PIR) motion sensor with 12m range and dimmable backlight
"""
mutable struct BrickletMotionDetectorV2 <: TinkerforgeDevice
    ipcon::IPConnection
    deviceInternal::PyObject

    """
    Creates an object with the unique device ID *uid* and adds it to
    the IP Connection *ipcon*.
    """
    function BrickletMotionDetectorV2(uid::String, ipcon::IPConnection)
        package = pyimport("tinkerforge.bricklet_motion_detector_v2")
        deviceInternal = package.BrickletMotionDetectorV2(uid, ipcon.ipconInternal)

        return new(ipcon, deviceInternal)
    end
end

export get_motion_detected
"""
    $(SIGNATURES)

Returns 1 if a motion was detected. It returns 1 approx. for 1.8 seconds
until the sensor checks for a new movement.
"""
function get_motion_detected(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_motion_detected()
end

export set_sensitivity
"""
    $(SIGNATURES)

Sets the sensitivity of the PIR sensor. At full
sensitivity (100), the Bricklet can detect motion in a range of approximately 12m.

The actual range depends on many things in the environment (e.g. reflections) and the
size of the object to be detected. While a big person might be detected in a range
of 10m a cat may only be detected at 2m distance with the same setting.

So you will have to find a good sensitivity for your application by trial and error.
"""
function set_sensitivity(device::BrickletMotionDetectorV2, sensitivity)
    device.deviceInternal.set_sensitivity(sensitivity)
end

export get_sensitivity
"""
    $(SIGNATURES)

Returns the sensitivity as set by :func:`Set Sensitivity`.
"""
function get_sensitivity(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_sensitivity()
end

export set_indicator
"""
    $(SIGNATURES)

Sets the blue backlight of the fresnel lens. The backlight consists of
three LEDs. The brightness of each LED can be controlled with a 8-bit value
(0-255). A value of 0 turns the LED off and a value of 255 turns the LED
to full brightness.
"""
function set_indicator(device::BrickletMotionDetectorV2, top_left, top_right, bottom)
    device.deviceInternal.set_indicator(top_left, top_right, bottom)
end

export get_indicator
"""
    $(SIGNATURES)

Returns the indicator configuration as set by :func:`Set Indicator`.
"""
function get_indicator(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_indicator()
end

export get_spitfp_error_count
"""
    $(SIGNATURES)

Returns the error count for the communication between Brick and Bricklet.

The errors are divided into

* ACK checksum errors,
* message checksum errors,
* framing errors and
* overflow errors.

The errors counts are for errors that occur on the Bricklet side. All
Bricks have a similar function that returns the errors on the Brick side.
"""
function get_spitfp_error_count(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_spitfp_error_count()
end

export set_bootloader_mode
"""
    $(SIGNATURES)

Sets the bootloader mode and returns the status after the requested
mode change was instigated.

You can change from bootloader mode to firmware mode and vice versa. A change
from bootloader mode to firmware mode will only take place if the entry function,
device identifier and CRC are present and correct.

This function is used by Brick Viewer during flashing. It should not be
necessary to call it in a normal user program.
"""
function set_bootloader_mode(device::BrickletMotionDetectorV2, mode)
    return device.deviceInternal.set_bootloader_mode(mode)
end

export get_bootloader_mode
"""
    $(SIGNATURES)

Returns the current bootloader mode, see :func:`Set Bootloader Mode`.
"""
function get_bootloader_mode(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_bootloader_mode()
end

export set_write_firmware_pointer
"""
    $(SIGNATURES)

Sets the firmware pointer for :func:`Write Firmware`. The pointer has
to be increased by chunks of size 64. The data is written to flash
every 4 chunks (which equals to one page of size 256).

This function is used by Brick Viewer during flashing. It should not be
necessary to call it in a normal user program.
"""
function set_write_firmware_pointer(device::BrickletMotionDetectorV2, pointer)
    device.deviceInternal.set_write_firmware_pointer(pointer)
end

export write_firmware
"""
    $(SIGNATURES)

Writes 64 Bytes of firmware at the position as written by
:func:`Set Write Firmware Pointer` before. The firmware is written
to flash every 4 chunks.

You can only write firmware in bootloader mode.

This function is used by Brick Viewer during flashing. It should not be
necessary to call it in a normal user program.
"""
function write_firmware(device::BrickletMotionDetectorV2, data)
    return device.deviceInternal.write_firmware(data)
end

export set_status_led_config
"""
    $(SIGNATURES)

Sets the status LED configuration. By default the LED shows
communication traffic between Brick and Bricklet, it flickers once
for every 10 received data packets.

You can also turn the LED permanently on/off or show a heartbeat.

If the Bricklet is in bootloader mode, the LED is will show heartbeat by default.
"""
function set_status_led_config(device::BrickletMotionDetectorV2, config)
    device.deviceInternal.set_status_led_config(config)
end

export get_status_led_config
"""
    $(SIGNATURES)

Returns the configuration as set by :func:`Set Status LED Config`
"""
function get_status_led_config(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_status_led_config()
end

export get_chip_temperature
"""
    $(SIGNATURES)

Returns the temperature as measured inside the microcontroller. The
value returned is not the ambient temperature!

The temperature is only proportional to the real temperature and it has bad
accuracy. Practically it is only useful as an indicator for
temperature changes.
"""
function get_chip_temperature(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_chip_temperature()
end

export reset
"""
    $(SIGNATURES)

Calling this function will reset the Bricklet. All configurations
will be lost.

After a reset you have to create new device objects,
calling functions on the existing ones will result in
undefined behavior!
"""
function reset(device::BrickletMotionDetectorV2)
    device.deviceInternal.reset()
end

export write_uid
"""
    $(SIGNATURES)

Writes a new UID into flash. If you want to set a new UID
you have to decode the Base58 encoded UID string into an
integer first.

We recommend that you use Brick Viewer to change the UID.
"""
function write_uid(device::BrickletMotionDetectorV2, uid)
    device.deviceInternal.write_uid(uid)
end

export read_uid
"""
    $(SIGNATURES)

Returns the current UID as an integer. Encode as
Base58 to get the usual string version.
"""
function read_uid(device::BrickletMotionDetectorV2)
    return device.deviceInternal.read_uid()
end

export get_identity
"""
    $(SIGNATURES)

Returns the UID, the UID where the Bricklet is connected to,
the position, the hardware and firmware version as well as the
device identifier.

The position can be 'a', 'b', 'c', 'd', 'e', 'f', 'g' or 'h' (Bricklet Port).
A Bricklet connected to an :ref:`Isolator Bricklet <isolator_bricklet>` is always at
position 'z'.

The device identifier numbers can be found :ref:`here <device_identifier>`.
|device_identifier_constant|
"""
function get_identity(device::BrickletMotionDetectorV2)
    return device.deviceInternal.get_identity()
end

export register_callback
"""
Registers the given *function* with the given *callback_id*.
"""
function register_callback(device::BrickletMotionDetectorV2, callback_id, function_)
    if isnothing(function_)
        device.registered_callbacks.pop(callback_id, None)
    else
        device.registered_callbacks[callback_id] = function_
    end
end


text:
using SimpleFWF
using DataFrames
using Test

@testset "Sample Data Test" begin
    example_data_dict = Dict(
        "COLUMN A" => string.(1:11),
        "COLUMN B" => [
            "SOME VERY LONG STRING WITH SPACES",
            "ANOTHER VERY LONG STRING",
            "MORE STRINGS",
            "STILL MORE STRINGS",
            "FIVE STRINGS",
            "SIX STRINGS",
            "SIX PLUS ONE STRINGS",
            "EIGHT STRINGS",
            "NINE STRINGS",
            "TEN STRINGS",
            "ELEVEN STRINGS",
        ],
        "COLUMN C" => string.(0.5:0.5:5.5),
        "COLUMN D" => [
            "01/01/21",
            "02/01/21",
            "03/01/21",
            "04/01/21",
            "05/01/21",
            "06/01/21",
            "07/01/21",
            "08/01/21",
            "09/01/21",
            "10/01/21",
            "11/01/21"
        ]
    )
    example_df = DataFrame(example_data_dict)
    file_path = joinpath(dirname(@__DIR__), "examples", "sample.dat")
    ranges = (1:9, 10:43, 44:52, 53:60)
    @test readfwf(file_path, ranges) == example_df
end

@testset "Sample Data No Headers Test" begin
    example_data_dict = Dict(
        "x_1" => string.(1:11),
        "x_2" => [
            "SOME VERY LONG STRING WITH SPACES",
            "ANOTHER VERY LONG STRING",
            "MORE STRINGS",
            "STILL MORE STRINGS",
            "FIVE STRINGS",
            "SIX STRINGS",
            "SIX PLUS ONE STRINGS",
            "EIGHT STRINGS",
            "NINE STRINGS",
            "TEN STRINGS",
            "ELEVEN STRINGS",
        ],
        "x_3" => string.(0.5:0.5:5.5),
        "x_4" => [
            "01/01/21",
            "02/01/21",
            "03/01/21",
            "04/01/21",
            "05/01/21",
            "06/01/21",
            "07/01/21",
            "08/01/21",
            "09/01/21",
            "10/01/21",
            "11/01/21"
        ]
    )
    example_df = DataFrame(example_data_dict)
    file_path = joinpath(dirname(@__DIR__), "examples", "sample_no_headers.dat")
    ranges = (1:9, 10:43, 44:52, 53:60)
    @test readfwf(file_path, ranges; headers=false) == example_df
end


text:
function make_gif(hist, mdp, belief_hist=nothing)
    frames = Frames(MIME("image/png"), fps=6);
    for step in 1:n_steps(hist)+1
        # b = nothing
        # if belief
        #     b = hist.belief_hist[step +1]
        # end
        s = state_hist(hist)[step]
        # a = collect(action_hist(hist))[step]
        # acc = s.ego_info.acc
        if step < n_steps(hist)+1
            # a = collect(action_hist(hist))[step]
            # act = action_map(mdp, acc, a).a
        else
            a = -1
            act = NaN
        end
        if belief_hist != nothing
            f = super_render(mdp, s, belief_hist[step])
        else
            f = super_render(mdp, s)
        end
        # f = AutoViz.render(s.scene, mdp.env.roadway,
        #     SceneOverlay[
        #                 IDOverlay(),
        #                 MergingNeighborsOverlay(target_id=EGO_ID, env=mdp.env),
        #                 DistToMergeOverlay(target_id=EGO_ID, env=mdp.env),
        #                 DistToMergeOverlay(target_id=2, env=mdp.env),
        #                 CooperativeIDMOverlay(targetid=2, model=model, textparams=TextParams(size = 20, x = 100, y_start=300)),
        #                 TextOverlay(text=[@sprintf("Acc %2.2f m/s^2", act),
        #                                     @sprintf("Action %d", a),
        #                                     @sprintf("Time %2.1f", mdp.dt*step)],
        #                             pos=VecE2(300, 20), font_size=20)
        #                     #    NeighborsOverlay(EGO_ID),
        #                 #    CarFollowingStatsOverlay(EGO_ID),
        #                     ],
        #         car_colors=get_car_type_colors(s0.scene, mdp.driver_models),
        #         #  cam=FitToContentCamera(0.0))
        #         cam=StaticCamera(VecE2(-25.0, -10.0), 6.0))
            #   cam=CarFollowCamera(EGO_ID, 10.0),
            #   car_colors = Dict{Int64, Colorant}(1 => COLOR_CAR_EGO))
        push!(frames, f)
    end

    write("out.gif", frames)
end


text:
using NumCME
using Test
using BenchmarkTools
using SparseArrays
using ForwardDiff

𝕊 = [[-1, 1, 0] [1, -1, 0] [0, 0, 1] [0, 0, -1]]
x₀ = [1, 0, 0]
k₀₁ = 0.05
k₁₀ = 0.1
λ = 5.0
γ = 0.5
L = 20.0
θ = [k₀₁, k₁₀, λ, γ, L]
a1 = propensity() do x, p
    p[1] * x[1]
end
a2 = propensity() do x, p
    p[2] * x[2]
end
a3 = propensity() do x, p
    p[3] * x[2]
end
a4 = propensity((t,p) -> max(0.0, 1.0-sin(π*t/p[5]))) do x, p
    p[4] * x[3]
end
propensities = [a1, a2, a3, a4]
sensmodel = CmeModelWithSensitivity(CmeModel(𝕊, propensities, θ))

# Test correctness of the generated propensity gradients across multiple CME states and times
test_space = StateSpaceSparse(𝕊, x₀)
expand!(test_space, 100)
test_states = get_states(test_space)
test_times = 0.0:1.0:40.0

a1grad(x, p) = [x[1], 0.0, 0.0, 0.0, 0.0]
a2grad(x, p) = [0.0, x[2], 0.0, 0.0, 0.0]
a3grad(x, p) = [0.0, 0.0, x[2], 0.0, 0.0]
a4grad(t, x, p) = (1.0-sin(π*t/p[5]) > 0) ? [0.0, 0.0, 0.0, (1.0-sin(π*t/p[5]))*x[3], p[4]*x[3]*cos(π*t/p[5])*π*t/(p[5]^2)] : [0.0, 0.0, 0.0, 0.0, 0.0]
propensity_grads = get_propensity_gradients(sensmodel)

@test prod([a1grad(x, θ) ≈ propensity_grads[1](x, θ) for x in test_states])
@test prod([a2grad(x, θ) ≈ propensity_grads[2](x, θ) for x in test_states])
@test prod([a3grad(x, θ) ≈ propensity_grads[3](x, θ) for x in test_states])

tmp = []
for t in test_times
    push!(tmp, prod([isapprox(a4grad(t, x, θ),propensity_grads[4](t, x, θ), rtol=1.0E-8, atol=10*eps()) for x in test_states]))
    for x in test_states
        !(isapprox(a4grad(t, x, θ),propensity_grads[4](t, x, θ), rtol=1.0E-8, atol=10*eps())) && println(a4grad(t, x, θ)," ",propensity_grads[4](t, x, θ))
    end
end
@test prod(tmp)

# Test correctness of auto-determined sparsity pattern of propensity gradients
correct_sparsity = sparse([1, 2, 3, 4, 4],[1, 2, 3, 4, 5],[true, true, true, true, true])
sparsity_patterns = get_gradient_sparsity_patterns(sensmodel)
@test correct_sparsity == sparsity_patterns


text:
"""
This is the GLViualize Test suite.
It tests all examples in the example folder and has the options to create
docs from the examples.
"""
module ExampleRunner

# add one worker process for parallel examples

using GLAbstraction, GLWindow, GLVisualize
using FileIO, GeometryTypes, Reactive, Images
export RunnerConfig
import GLVisualize: toggle_button, slider, button, mm, create_video_stream, add_frame!

include("mouse.jl")

const installed_pkgs = Pkg.installed()

const hasplots = get(installed_pkgs, "Plots", v"0") > v"0.9.3"

if !hasplots
    message = "Plots.jl is not installed, excluding a lot of interesting examples from the tests
    and a nice summary.\n"
    message *= "Please consider executing: `Pkg.add(\"Plots\")`"
    warn(message)
end

include("texthighlight.jl")

function flatten_paths(files::Vector, paths = String[])
    for file in files
        flatten_paths(file, paths)
    end
    paths
end
function flatten_paths(path::String, paths = String[])
    if isdir(path)
            flatten_paths(map(x-> joinpath(path, x), readdir(path)), paths)
    elseif isfile(path) && endswith(path, ".jl")
        push!(paths, path)
    end
    paths
end

mutable struct RunnerConfig
    resolution
    make_docs
    files
    exclude_dirs
    number_of_frames
    interactive_time
    resampling
    screencast_folder
    record
    record_image
    thumbnail
    rootscreen
    window
    code_screen
    toolbar
    attributes
    current_file
    buttons
end
const source_signals = Dict()

const text_signals = Dict(
    :title => Signal(String, "Nothing to show"),
    :message => Signal(String, "\n"),
    :codepath => Signal(String, ""),
)

function create_screens(rootscreen)
    # partition screen into 4 areas at 15% and 17%
    iconsize = 10mm
    tool_area, view_area = y_partition_abs(rootscreen.area, 3iconsize) # three rows
    control_area, message_area = x_partition_abs(tool_area, 4.5iconsize) # 4.5 columns

    toolbar = Screen(rootscreen,
        area = tool_area, color = RGBA(0.95f0, 0.95f0, 0.95f0, 1.0f0)
    )
    messagescreen = Screen(toolbar, area = message_area)
    control_screen = Screen(toolbar, area = control_area)

    # load the icons
    paths = [
        "rewind_inactive.png", "rewind_active.png",
        "back_inactive.png", "back_active.png",
        "play.png", "pause.png"
    ]
    imgs = map(paths) do path
        img = convert(Matrix{RGBA{N0f8}}, loadasset(path))
        img, flipdim(img, 2)
    end

    code_toggle, code_s = widget(
        Signal(["code", "visual"]),
        relative_scale = 9mm,
        control_screen, area = (4*iconsize, iconsize),
    )

    # set up code and view screen
    code_hide = map(id-> id == "code", code_s)
    view_hide = map(id-> id == "visual", code_s)
    view_screen = Screen(
        rootscreen, area = view_area, hidden = view_hide
    )
    # `copy` all signals to make cleaning up easier
    for (k, v) in view_screen.inputs
        x = Signal(value(v))
        bind!(x, v, false)
        view_screen.inputs[k] = x
        source_signals[k] = v
    end
    code_screen = Screen(rootscreen, area = view_area, hidden = code_hide)
    GLVisualize.add_screen(view_screen)

    # setup message and code display
    text_color = map(text_signals[:title], text_signals[:message]) do title, message
        colors = [
            fill(RGBA(0.3f0, 0.3f0, 0.3f0, 1f0), length(title));
            fill(RGBA(0.6f0, 0.6f0, 0.6f0, 1f0), length(message));
        ]
        title*message, colors
    end
    _view(visualize(
        map(first, text_color),
        color = map(last, text_color),
        relative_scale = 4mm
    ), messagescreen, camera = :orthographic_pixel)

    text_color = map(text_signals[:codepath]) do codepath
        if isfile(codepath)
            code, colors = highlight_text(readstring(codepath))
        else
            txt = "error: file $codepath not found"
            colors = fill(RGBA(0f0,0f0,0f0,1f0), length(txt))
            txt, colors
        end
    end
    _view(visualize(
        map(first, text_color), color = map(last, text_color),
        relative_scale = 4mm
    ), code_screen, camera = :orthographic_pixel)

    set_arg!(
        code_toggle, :model,
        translationmatrix(Vec3f0(1, iconsize * 2 + 2, 0))
    )
    _view(code_toggle, control_screen, camera=:fixed_pixel)

    # create buttons
    buttons = Dict{Symbol, Any}(
        :rewind => toggle_button(imgs[1][1], imgs[2][1], control_screen),
        :back => button(imgs[3][1], control_screen),
        :play => toggle_button(imgs[5][1], imgs[6][1], control_screen),
        :forward => button(imgs[3][2], control_screen),
        :fastforward => toggle_button(imgs[1][2], imgs[2][2], control_screen),
    )

    play_button, play_stop_signal = buttons[:play]
    play_s = map(!, play_stop_signal)

    slider_w, slider_s = slider(
        linspace(0f0, 1f0, 120), control_screen,
        play_signal = play_s,
        slider_length = 4*iconsize,
        icon_size = Signal(iconsize)
    )
    buttons[:timesignal] = slider_s

    set_arg!(slider_w.children[1], :stroke_width, 0f0)
    set_arg!(slider_w.children[1], :stroke_color, RGBA(1f0, 1f0, 1f0, 1f0))
    set_arg!(slider_w.children[2], :thickness, 0.8f0)
    set_arg!(slider_w.children[2], :color, RGBA(0.7f0, 0.7f0, 0.7f0, 1f0))

    _view(slider_w, control_screen, camera=:fixed_pixel)
    last_x = 0f0
    last_y = iconsize+1
    for k in [:rewind, :back, :play, :forward, :fastforward]
        b, s = buttons[k]

        _w, _h, _ = widths(value(boundingbox(b)))
        ratio = _w / _h
        x_width = ratio * iconsize # we have half widthed icons
        place = SimpleRectangle{Float32}(
            last_x + 1, last_y + 1,
            x_width, iconsize
        )
        layout!(place, b)
        _view(b, control_screen, camera = :fixed_pixel)
        last_x += x_width + 0.5mm
    end

    toolbar, code_screen, view_screen, buttons

end

function RunnerConfig(;
        resolution = (300, 300),
        make_docs = true,
        files = String[],
        exclude_dirs = [
            "parallel", "compose", "mouse.jl", "richtext.jl",
            "ExampleRunner.jl", "grids.jl",
            "texthighlight.jl", "gtk_events.jl", "gtk_example.jl", "gtk_mwe.jl"
        ],
        number_of_frames = 360,
        interactive_time = 5.0,
        resampling = 0,
        screencast_folder = pwd(),
        record = true,
        record_image = false,
        thumbnail = true,
        rootscreen = glscreen(resolution = resolution)
    )
    w, h = resolution
    resize!(rootscreen, w*mm, h*mm)
    yield() # let screen area signal arrive
    if !hasplots
        push!(exclude_dirs, "plots")
        push!(exclude_dirs, "summary.jl")
    end
    if !haskey(installed_pkgs, "BilliardModels")
        push!(exclude_dirs, "billiard.jl")
    end

    control_screen, code_screen, view_screen, buttons = create_screens(rootscreen)

    if isempty(files)
        push!(files, flatten_paths(GLVisualize.dir("examples"))) # make sure we have something to show
    end
    files = filter(files) do file
        !(basename(file) in exclude_dirs) &&
        !(basename(dirname(file)) in exclude_dirs)
    end

    RunnerConfig(
        resolution,
        make_docs,
        files,
        exclude_dirs,
        number_of_frames,
        interactive_time,
        resampling,
        screencast_folder,
        record,
        record_image,
        thumbnail,
        rootscreen,
        view_screen,
        code_screen,
        control_screen,
        Dict(),
        "",
        buttons
    )
end

function Base.setindex!(x::RunnerConfig, value, sym::Symbol)
    dict = get!(x.attributes, x.current_file, Dict())
    dict[sym] = value
end
function Base.getindex(x::RunnerConfig, sym::Symbol)
    dict = get(x.attributes, x.current_file, Dict())
    dict[sym]
end

function record_thumbnail(config, approx_size=128)
    if config.thumbnail && isopen(config.window)
        thumb = screenbuffer(config.window)
        w,h = size(thumb)
        minlen = min(w,h)
        thumb_sqr = thumb[1:minlen, 1:minlen]
        last_img = thumb_sqr
        while w > approx_size
            last_img = thumb_sqr
            thumb_sqr = restrict(thumb_sqr)
            w = size(thumb_sqr, 1)
        end
        img = if abs(approx_size-w) > abs(approx_size-size(last_img, 1))
            last_img
        else
            thumb_sqr
        end
        config[:thumbnail] = img
    end
end

"""
 include the example in it's own module
 to avoid variable conflicts.
 this can be done only via eval.
"""
function include_in_module(name::Symbol, config, include_path, window, timesignal)
    eval(:(
        module $(name)
            using Reactive

            const runtests   = true
            const config     = $(config)
            const window     = $(window)
            const timesignal = $(timesignal)

           # const composebackend = GLTest.composebackend
            include($include_path)
        end
    ))
end

function _test_include(path, config)
    config.current_file = path
    timesignal = config.buttons[:timesignal]
    name = basename(path)[1:end-3] # remove .jl
    # include the example file in it's own module
    window = config.window
    test_module = include_in_module(Symbol(name), config, path, window, timesignal)
    for (camname, cam) in window.cameras
        # don't center non standard cams
        camname in (:orthographic_pixel, :perspective) || continue
        rlist = GLAbstraction.robj_from_camera(window, camname)
        bb = GLAbstraction.renderlist_boundingbox(rlist)
        # make sure we don't center if bb is undefined
        if !isnan(origin(bb))
            center!(cam, bb)
        end
    end
    config[:success] = true
    test_module
end

function display_msg(test_module, config)
    message = if isdefined(test_module, :description)
        getfield(test_module, :description)
    else
        """
        You can move the camera around
        with the left and right mousebutton
        """
    end
    # make sure this is still needed
    if isopen(config.toolbar) && !isempty(config.toolbar.children)
        name = basename(config.current_file)
        title = "Now showing $name:\n"
        println(title)

        push!(text_signals[:title], title)
        push!(text_signals[:message], message)
        cam = config.toolbar.children[1].cameras[:orthographic_pixel]
        pad = 20
        w, h = widths(config.toolbar.children[1])
        center!(cam, AABB(Vec3f0(-pad, -h+2pad, 0), Vec3f0(w-pad, h-2pad, 0)))

        push!(text_signals[:codepath], config.current_file)
        cam = config.code_screen.cameras[:orthographic_pixel]
        w, h = widths(config.code_screen)
        center!(cam, AABB(Vec3f0(-pad, -h+3pad, 0), Vec3f0(w-pad, h-3pad, 0)))
    end
    return
end
to_toggle(v0, b) = !v0

import GLWindow: reactive_run_till_now, poll_glfw, sleep_pessimistic

function inner_test(path, config, window, break_loop, runthrough, increase)
    try
        test_module = _test_include(path, config)
        display_msg(test_module, config)
        timings = Float64[]
        frames = 0;
        poll_glfw()
        reactive_run_till_now()
        while !break_loop[] && isopen(config.rootscreen)
            t = time()
            poll_glfw()
            if Base.n_avail(Reactive._messages) > 0
                reactive_run_till_now() # two times for secondary signals
                render_frame(config.rootscreen)
                swapbuffers(config.rootscreen)
            end
            frames += 1
            t = time() - t
            if length(timings) < 1000 && frames > 2
                push!(timings, t)
            end
            yield() # yield in timings? Seems fair
            GLWindow.sleep_pessimistic((1/60) - t)
            (runthrough[] != 0 && frames > 20) && break
        end
        record_thumbnail(config) # record thumbnail in the end
        config[:timings] = timings
        break_loop[] = false
        runthrough[] != 0 && increase()
        for n in names(test_module, true)
            x = getfield(test_module, n)
            if n != :timesignal && isa(x, Signal) && !(x in values(window.inputs))
                close(x, false)
            end
        end
    catch e
        increase() # skip example
        bt = catch_backtrace()
        ex = CapturedException(e, bt)
        showerror(STDERR, ex)
        config[:success] = false
        config[:exception] = ex
        return false
    end
    true
end

function make_tests(config)
    i = 1; frames = 0; window = config.window; break_loop = Ref(false)
    # start in run through when recording images
    runthrough = Ref(config.record_image ? 1 : 0) # -1, backwards, 0 no running, 1 forward

    function increase(x = runthrough[])
        x = x == 0 ? 1 : x
        i = max(i+x, 1)
        if i <= length(failed) && failed[i]
            increase(x)
        end
        i
    end

    foreach(config.buttons[:back][2], init=0) do clicked
        clicked && (break_loop[] = true; increase(-1))
    end
    foreach(config.buttons[:forward][2], init=0) do clicked
        clicked && (break_loop[] = true; increase(1))
    end
    foreach(config.buttons[:fastforward][2], init = 0) do toggled
        # reset frames, so we loop a few times longer before loading next example
        # this will keep the buttons responsive
        frames = 0
        runthrough[] = !toggled ? 1 : 0
    end
    foreach(config.buttons[:rewind][2], init = 0) do toggled
        frames = 0
        runthrough[] = !toggled ? -1 : 0
    end

    failed = fill(false, length(config.files))
    io = nothing
    while i <= length(config.files) && isopen(config.rootscreen)
        path = config.files[i]
        failed[i] = @eval inner_test($path, $config, $window, $break_loop, $runthrough, $increase)
        yield()
        empty!(window)
        window.color = RGBA{Float32}(1,1,1,1)
        window.clear = true
        GLVisualize.empty_screens!()
        empty!(window.cameras)
        GLVisualize.add_screen(window) # make window default again!
        slider = config.buttons[:timesignal]
        for childweakref in Reactive.nodes[Reactive.edges[slider.id]]
            # remove signals depending on slider
            child = childweakref.value
            child.parents = Tuple(Iterators.filter(p-> p != slider, child.parents))
        end
        gc()
    end

end

function run(;kw_args...)
    config = RunnerConfig(;kw_args...)
    run(config)
end
function run(config::RunnerConfig)
    srand(777) # set rand seed, to get the same results for tests that use rand
    make_tests(config)
    config
end

end


text:
#=
# Add interactions to your graph plot

In this example you will see, how to register interactions with your graph plot.
This tutorial will make use of the more basic [Interaction Interface](@ref).
If you just want to move nodes check out the [Predefined Interactions](@ref). The
implementation of those is quit similar to what is shown in this tutorial.

We star with a simple wheel graph again. This time we use arrays for some attributes
because we want to change them later in the interactions for individual nodes/edges.
=#
using CairoMakie
CairoMakie.activate!(type="png") # hide
set_theme!(resolution=(800, 400)) #hide
CairoMakie.inline!(true) # hide
using GraphMakie
using Graphs
using CairoMakie.Colors

import Random; Random.seed!(2) # hide
g = wheel_graph(10)
f, ax, p = graphplot(g,
                     edge_width = [2.0 for i in 1:ne(g)],
                     edge_color = [colorant"gray" for i in 1:ne(g)],
                     node_size = [10 for i in 1:nv(g)],
                     node_color = [colorant"red" for i in 1:nv(g)])
hidedecorations!(ax); hidespines!(ax)
ax.aspect = DataAspect()
f # hide

# Later on we want to enable drag interactions, therefore we disable the default
# `:rectanglezoom` interaction
deregister_interaction!(ax, :rectanglezoom)
nothing #hide

# ## Hover interactions
# At first, let's add some hover interaction for our nodes using the [`NodeHoverHandler`](@ref)
# constructor. We need to define a action function with the signature `fun(state, idx, event, axis)`.
# We use the action to make the nodes bigger on hover events.
function node_hover_action(state, idx, event, axis)
    @info idx #hide
    p.node_size[][idx] = state ? 20 : 10
    p.node_size[] = p.node_size[] # trigger observable
end
nhover = NodeHoverHandler(node_hover_action)
register_interaction!(ax, :nhover, nhover)

function set_cursor!(p) #hide
    direction = Point2f(-0.1, 0.2) #hide
    arrows!([p-direction], [direction], linewidth=3, arrowsize=20, lengthscale=0.8) #hide
end #hide
nodepos = copy(p[:node_pos][]) #hide
set_cursor!(nodepos[5] + Point2f(0.05, 0)) #hide
p.node_size[][5] = 20; p.node_size[] = p.node_size[] #hide
f #hide

# Please run the script locally with `GLMakie.jl` if you want to play with the Graph 🙂
# The edge hover interaction is quite similar:

pop!(ax.scene.plots) #hide
p.node_size[][5] = 10; p.node_size[] = p.node_size[] #hide
function edge_hover_action(state, idx, event, axis)
    @info idx #hide
    p.edge_width[][idx]= state ? 5.0 : 2.0
    p.edge_width[] = p.edge_width[] # trigger observable
end
ehover = EdgeHoverHandler(edge_hover_action)
register_interaction!(ax, :ehover, ehover)

set_cursor!((nodepos[4]+nodepos[1])/2) #hide
p.edge_width[][3] = 5.0; p.edge_width[] = p.edge_width[] #hide
f #hide

# ## Click interactions
# In a similar fashion we might change the color of nodes and lines by click.
function node_click_action(idx, args...)
    p.node_color[][idx] = rand(RGB)
    p.node_color[] = p.node_color[]
end
nclick = NodeClickHandler(node_click_action)
register_interaction!(ax, :nclick, nclick)

function edge_click_action(idx, args...)
    p.edge_color[][idx] = rand(RGB)
    p.edge_color[] = p.edge_color[]
end
eclick = EdgeClickHandler(edge_click_action)
register_interaction!(ax, :eclick, eclick)

p.edge_color[][3] = colorant"blue"; p.edge_color[] = p.edge_color[] #hide
p.node_color[][7] = colorant"yellow" #hide
p.node_color[][2] = colorant"brown" #hide
p.node_color[][9] = colorant"pink" #hide
p.node_color[][6] = colorant"green" #hide
p.node_color[] = p.node_color[] #hide
f #hide

# ## Drag interactions
pop!(ax.scene.plots) #hide
p.edge_width[][3] = 2.0; p.edge_width[] = p.edge_width[] #hide
function node_drag_action(state, idx, event, axis)
    p[:node_pos][][idx] = event.data
    p[:node_pos][] = p[:node_pos][]
end
ndrag = NodeDragHandler(node_drag_action)
register_interaction!(ax, :ndrag, ndrag)

p[:node_pos][][1] = nodepos[1] + Point2f(1.0,0.5) #hide
p[:node_pos][] = p[:node_pos][] #hide
set_cursor!(p[:node_pos][][1] + Point2f(0.05, 0)) #hide
p.node_size[][1] = 20; p.node_size[] = p.node_size[] #hide
f # hide

# The last example is not as straight forward. By dragging an edge we want to
# change the positions of both attached nodes. Therefore we need some more state
# inside the action. We can achieve this with a callable struct.
pop!(ax.scene.plots) #hide
p.node_size[][1] = 10; p.node_size[] = p.node_size[] #hide
mutable struct EdgeDragAction
    init::Union{Nothing, Point2f} # save click position
    src::Union{Nothing, Point2f}  # save src vertex position
    dst::Union{Nothing, Point2f}  # save dst vertex position
    EdgeDragAction() = new(nothing, nothing, nothing)
end
function (action::EdgeDragAction)(state, idx, event, axis)
    edge = collect(edges(g))[idx]
    if state == true
        if action.src===action.dst===action.init===nothing
            action.init = event.data
            action.src = p[:node_pos][][edge.src]
            action.dst = p[:node_pos][][edge.dst]
        end
        offset = event.data - action.init
        p[:node_pos][][edge.src] = action.src + offset
        p[:node_pos][][edge.dst] = action.dst + offset
        p[:node_pos][] = p[:node_pos][] # trigger change
    elseif state == false
        action.src = action.dst = action.init =  nothing
    end
end
edrag = EdgeDragHandler(EdgeDragAction())
register_interaction!(ax, :edrag, edrag)

p[:node_pos][][9] = nodepos[9] + Point2f(0.9,1.0) #hide
p[:node_pos][][10] = nodepos[10] + Point2f(0.9,1.0) #hide
p[:node_pos][] = p[:node_pos][] #hide
pm = (p[:node_pos][][9] + p[:node_pos][][10])/2 #hide
set_cursor!(pm) #hide
p.edge_width[][18] = 5.0; p.edge_width[] = p.edge_width[] #hide
f # hide


text:
# parse .map file into dict of external symbols

export microchip_parsemap

function returnvalidmapfile(filenameorprojectdirectory::ASCIIString, maptype::ASCIIString = "production")
  # filenameorprojectdirectory may be either the X project directory or the .map file
  # maptype: if project directory is specified maptype may be either "production" or "debug"
  if isfile(filenameorprojectdirectory)
    (f,e) = splitext(filenameorprojectdirectory)
    if e == ".map"
      return filenameorprojectdirectory
    else
      error("not a .map file")
    end
  else
    if filenameorprojectdirectory[end] == '/'
      filenameorprojectdirectory = filenameorprojectdirectory[1:end-1]
    end
    (f,e) = splitext(basename(filenameorprojectdirectory))
    if e != ".x"
      error("directory must end in .x")
    end
    canidatefile = joinpath(filenameorprojectdirectory,"dist/default/$maptype","$f.X.$maptype.map")
    if isfile(canidatefile)
      return canidatefile
    else
      error(".X.$maptype.map file not found in dist/default/$maptype directory")
    end
  end
end

function microchip_parsemap(filenameorprojectdirectory::ASCIIString, maptype::ASCIIString = "production")
  filename = returnvalidmapfile(filenameorprojectdirectory)
  symboldict = Dict{Symbol,UInt32}()
  open(filename) do iofile
    line = readline(iofile)
    while ~ismatch(r"External Symbols in Data Memory \(by address\):",line) && ~eof(iofile)
      line = readline(iofile)
    end
    while ~ismatch(r"0x([0-9a-f]{4,4})\s*_(\S*)",line) && ~eof(iofile)
      line = readline(iofile)
    end
    m = match(r"0x([0-9a-f]{4,4})\s*_(\S*)",line)
    while m!=nothing  && ~eof(iofile)
      address = m.captures[1]
      name = m.captures[2]
      push!(symboldict,symbol(name)=>parse(UInt32,address,16))
      line = readline(iofile)
      m = match(r"0x([0-9a-f]{4,4})\s*_(\S*)",line)
    end
  end
  return symboldict
end


text:
export EPITrajectory, epiNodes, epiDensity

"""
    EPITrajectory(T, numProfiles, numSamplingPerProfile
                  ; TE::T=0.0
                  , AQ::T=1.e-3
                  , EPI_factor::Int64=1
                  , profileOffset= :equispaced
                  , kargs...)

returns a 2d cartesian trajectory.

# Arguments
* `numProfiles::Int64`            - number of profiles
* `numSamplingPerProfile::Int64`  - number of sampling points per profile
* (`TE::T=0.0`)             - echo time in s
* (`AQ::T=1.e-3`)           - readout duration in s (per profile)
* (`EPI_factor::Int64=1`)         - EPI factor, e.g. how many profiles to acquire per shot
* (`profileOffset= :equispaced`)  - equispaced or random ordering of the shots
"""
function EPITrajectory(::Type{T}, numProfiles, numSamplingPerProfile
                  ; TE=0.0
                  , AQ=1.e-3
                  , EPI_factor::Int64=1
                  , profileOffset= :equispaced
                  , kargs...) where T
  nodes = epiNodes(T, numProfiles, numSamplingPerProfile; EPI_factor=EPI_factor, profileOffset=profileOffset)
  times = readoutTimes(T, numProfiles, numSamplingPerProfile; TE=TE, AQ=AQ)
  return Trajectory("EPI", nodes, times, TE, AQ, numProfiles, numSamplingPerProfile, 1, true, false)
end

function epiNodes(::Type{T}, numProfiles::Int64
                    , numSamplingPerProfile::Int64
                    ; EPI_factor::Int64=1
                    , profileOffset= :equispaced
                    , kargs...) where T
  nodes = zeros(T, 2, numSamplingPerProfile, numProfiles)

  if profileOffset == :equispaced
      samplesperline = numSamplingPerProfile/EPI_factor -1
      for l = 1:numProfiles
        for gamma1 = 1:EPI_factor
          for gamma2 = 1:numSamplingPerProfile/EPI_factor
            # Node index
            index = Int64( (gamma1-1) * (numSamplingPerProfile/EPI_factor) + gamma2 )
            # Nodes containing kx values, second index is the actual sample index, l profile index
            nodes[1,index,l] =  (-1)^(gamma1-1) * (gamma2-1) / samplesperline + ((gamma1-1) % 2 ) - 0.5
            # Nodes containing ky values, second index is the actual sample index, l profile index
            nodes[2,index,l] = ( (l-1) + (gamma1-1)*numProfiles)/(numProfiles*EPI_factor) - 0.5
          end
        end
      end
  elseif profileOffset == :random
      samplesperline = numSamplingPerProfile/EPI_factor -1
      for l = 1:numProfiles
        # Sorting the random samples
        angleOffsets = sort(rand(EPI_factor)-0.5)
        for gamma1 = 1:EPI_factor
          for gamma2 = 1:numSamplingPerProfile/EPI_factor
            # Node index
            index = Int64( (gamma1-1) * (numSamplingPerProfile/EPI_factor) + gamma2 )
            # Nodes containing kx values, second index is the actual sample index, l profile index
            nodes[1,index,l] =  (-1)^(gamma1-1) * (gamma2-1) / samplesperline + ((gamma1-1) % 2 ) - 0.5
            # Nodes containing ky values, second index is the actual sample index, l profile index
            nodes[2,index,l] = angleOffsets[gamma1]
          end
        end
      end
  end
  return reshape(nodes, 2, numSamplingPerProfile*numProfiles)
end

function epiDensity(::Type{T}, numProfiles::Int64, numSamplingPerProfile::Int64) where T
  @error "not implement yet"
end


text:
"""
    plssimp(X, Y, weights = ones(size(X, 1)); nlv)
Partial Least Squares Regression (PLSR) with the SIMPLS algorithm (de Jong 1993).
* `X` : X-data.
* `Y` : Y-data.
* `weights` : Weights of the observations.
* `nlv` : Nb. latent variables (LVs) to compute.

The function has the following difference with the original
algorithm of de Jong (2013):
* Scores T are not normed to 1.

Vector `weights` is internally normalized to sum to 1.
See the help of `plskern` for details.

`X` and `Y` are internally centered.

## References

de Jong, S., 1993. SIMPLS: An alternative approach to partial least squares
regression. Chemometrics and Intelligent Laboratory Systems 18, 251–263.
https://doi.org/10.1016/0169-7439(93)85002-X

"""
function plssimp(X, Y, weights = ones(size(X, 1)); nlv)
    plssimp!(copy(X), copy(Y), weights; nlv = nlv)
end

function plssimp!(X, Y, weights = ones(size(X, 1)); nlv)
    X = ensure_mat(X)
    Y = ensure_mat(Y)
    n = size(X, 1)
    p = size(X, 2)
    q = size(Y, 2)
    nlv = min(nlv, n, p)
    weights = mweights(weights)
    xmeans = colmeans(X, weights)
    ymeans = colmeans(Y, weights)
    center!(X, xmeans)
    center!(Y, ymeans)
    D = Diagonal(weights)
    XtY = X' * (D * Y)
    # Pre-allocation
    T = similar(X, n, nlv)
    P = similar(X, p, nlv)
    W = copy(P)
    R = copy(P)
    C = similar(X, q, nlv)
    TT = similar(X, nlv)
    t   = similar(X, n)
    dt  = similar(X, n)
    zp  = similar(X, p)
    r   = similar(X, p)
    c   = similar(X, q)
    tmp = similar(XtY)
    # End
    # This is Table 1 (as fast as Appendix) in de Jong 1993
    @inbounds for a = 1:nlv
        if a == 1
            tmp .= XtY
        else
            z = vcol(P, 1:(a - 1))
            tmp .= XtY .- z * inv(z' * z) * z' * XtY
        end
        u = svd!(tmp).U
        r .= u[:, 1]
        mul!(t, X, r)
        dt .= weights .* t
        tt = dot(t, dt)
        mul!(c, XtY', r)
        c ./= tt
        mul!(zp, X', dt)
        P[:, a] .= zp ./ tt
        T[:, a] .= t
        R[:, a] .= r
        C[:, a] .= c
        TT[a] = tt
     end
     #B = R * inv(T' * D * T) * T' * D * Y
     Plsr(T, P, R, W, C, TT, xmeans, ymeans, weights, nothing)
end


text:
using ARDESPOT
using Test

using POMDPs
using POMDPModels
using POMDPSimulators
using Random
using POMDPModelTools
using ParticleFilters

include("memorizing_rng.jl")
include("independent_bounds.jl")

pomdp = BabyPOMDP()
pomdp.discount = 1.0

K = 10
rng = MersenneTwister(14)
rs = MemorizingSource(K, 50, rng)
Random.seed!(rs, 10)
b_0 = initialstate(pomdp)
scenarios = [i=>rand(rng, b_0) for i in 1:K]
o = false
b = ScenarioBelief(scenarios, rs, 0, o)
pol = FeedWhenCrying()
r1 = ARDESPOT.branching_sim(pomdp, pol, b, 10, (m,x)->0.0)
r2 = ARDESPOT.branching_sim(pomdp, pol, b, 10, (m,x)->0.0)
@test r1 == r2
tval = 7.0
r3 = ARDESPOT.branching_sim(pomdp, pol, b, 10, (m,x)->tval)
@test r3 == r2 + tval*length(b.scenarios)

scenarios = [1=>rand(rng, b_0)]
b = ScenarioBelief(scenarios, rs, 0, false)
pol = FeedWhenCrying()
r1 = ARDESPOT.rollout(pomdp, pol, b, 10, (m,x)->0.0)
r2 = ARDESPOT.rollout(pomdp, pol, b, 10, (m,x)->0.0)
@test r1 == r2
tval = 7.0
r3 = ARDESPOT.rollout(pomdp, pol, b, 10, (m,x)->tval)
@test r3 == r2 + tval

# AbstractParticleBelief interface
@test n_particles(b) == 1
s = particle(b,1)
@test rand(rng, b) == s
@test pdf(b, rand(rng, b_0)) == 1
sup = support(b)
@test length(sup) == 1
@test first(sup) == s
@test mode(b) == s
@test mean(b) == s
@test first(particles(b)) == s
@test first(weights(b)) == 1.0
@test first(weighted_particles(b)) == (s => 1.0)
@test weight_sum(b) == 1.0
@test weight(b, 1) == 1.0
@test currentobs(b) == o
@test_deprecated previous_obs(b)
@test history(b)[end].o == o

pomdp = BabyPOMDP()

# constant bounds
bds = (reward(pomdp, true, false)/(1-discount(pomdp)), 0.0)
solver = DESPOTSolver(bounds=bds)
planner = solve(solver, pomdp)
hr = HistoryRecorder(max_steps=2)
@time hist = simulate(hr, pomdp, planner)

# policy lower bound
bds = IndependentBounds(DefaultPolicyLB(FeedWhenCrying()), 0.0)
solver = DESPOTSolver(bounds=bds)
planner = solve(solver, pomdp)
hr = HistoryRecorder(max_steps=2)
@time hist = simulate(hr, pomdp, planner)

# policy lower bound with final value
fv(m::BabyPOMDP, x) = reward(m, true, false)/(1-discount(m))
bds = IndependentBounds(DefaultPolicyLB(FeedWhenCrying(), final_value=fv), 0.0)
solver = DESPOTSolver(bounds=bds)
planner = solve(solver, pomdp)
hr = HistoryRecorder(max_steps=2)
@time hist = simulate(hr, pomdp, planner)

# Type stability
pomdp = BabyPOMDP()
bds = IndependentBounds(reward(pomdp, true, false)/(1-discount(pomdp)), 0.0)
solver = DESPOTSolver(epsilon_0=0.1,
                      bounds=bds,
                      rng=MersenneTwister(4)
                     )
p = solve(solver, pomdp)

b0 = initialstate(pomdp)
D = @inferred ARDESPOT.build_despot(p, b0)
@inferred ARDESPOT.explore!(D, 1, p)
@inferred ARDESPOT.expand!(D, length(D.children), p)
@inferred ARDESPOT.prune!(D, 1, p)
@inferred ARDESPOT.find_blocker(D, length(D.children), p)
@inferred ARDESPOT.make_default!(D, length(D.children))
@inferred ARDESPOT.backup!(D, 1, p)
@inferred ARDESPOT.next_best(D, 1, p)
@inferred ARDESPOT.excess_uncertainty(D, 1, p)
@inferred action(p, b0)

bds = IndependentBounds(reward(pomdp, true, false)/(1-discount(pomdp)), 0.0)
rng = MersenneTwister(4)
solver = DESPOTSolver(epsilon_0=0.1,
                      bounds=bds,
                      rng=rng,
                      random_source=MemorizingSource(500, 90, rng),
                      tree_in_info=true
                     )
p = solve(solver, pomdp)
a = action(p, initialstate(pomdp))

include("random_2.jl")

# visualization
show(stdout, MIME("text/plain"), D)
a, info = action_info(p, initialstate(pomdp))
show(stdout, MIME("text/plain"), info[:tree])

# from README:
using POMDPs, POMDPModels, POMDPSimulators, ARDESPOT

pomdp = TigerPOMDP()

solver = DESPOTSolver(bounds=(-20.0, 0.0))
planner = solve(solver, pomdp)

for (s, a, o) in stepthrough(pomdp, planner, "s,a,o", max_steps=10)
    println("State was $s,")
    println("action $a was taken,")
    println("and observation $o was received.\n")
end


text:
using Test

# Perhaps someday we'll have a `go_jll`. ;)
@testset "S3Vendor" begin
    @test isfile(joinpath(@__DIR__, "..", "S3Vendor_go", "main.go"))
    cd(joinpath(@__DIR__, "..", "S3Vendor_go")) do
        # Check to see if `go version` gives us something new enough:
        if Sys.which("go") === nothing
            @info("Skipping S3Vendor compile test due to lack of `go` compiler")
            return
        end

        gv = read(`go version`)
        m = match(r"^go version go([\d\.]+) ", String(read(`go version`)))
        if m === nothing
            @info("Skipping S3Vendor compile test due to inability to run `go version` check")
            return
        end

        if VersionNumber(m.captures[1]) < v"1.11"
            @info("Skipping S3Vendor compile test due to outdated `go` version ($(m.captures[1]) < 1.11)")
            return
        end

        @info("Testing that S3Vendor compiles...")
        run(`go build`)
        @test isfile("S3Vendor_go")
        rm("S3Vendor_go")
    end
end


text:
module TestTrophicRank
    using Test
    using BioEnergeticFoodWebs

    food_chain = [0 0 0;0 0 1; 1 0 0]
    @assert trophic_rank(food_chain) == vec([1.0 3.0 2.0])

    food_chain = [0 1 0; 0 0 1; 0 0 0]
    @assert trophic_rank(food_chain) == vec([3.0 2.0 1.0])

    omnivory = [0 1 1; 0 0 1; 0 0 0]
    @assert trophic_rank(omnivory) == vec([2.5 2.0 1.0])

    diamond = [0 1 1 0; 0 0 0 1; 0 0 0 1; 0 0 0 0]
    @assert trophic_rank(diamond) == vec([3.0 2.0 2.0 1.0])

    chain_four = [0 1 0 0; 0 0 1 0; 0 0 0 1; 0 0 0 0]
    @assert trophic_rank(chain_four) == vec([4.0 3.0 2.0 1.0])

    @assert trophic_rank(zeros(Int64, (3, 3))) == ones(3)

end

max_stars_repo_name: SimmonsBI/BioEnergeticFoodWebs

max_issues_repo_name: SimmonsBI/BioEnergeticFoodWebs

max_forks_repo_name: SimmonsBI/BioEnergeticFoodWebs


text:
# This file is a part of Julia. License is MIT: https://julialang.org/license

using Test, Base.Threads

let p, cmd = `$(Base.julia_cmd()) --depwarn=error --startup-file=no threads_exec.jl`
    # test both nthreads==1 and nthreads>1. spawn a process to test whichever
    # case we are not running currently.
    other_nthreads = nthreads() == 1 ? 4 : 1
    p = run(pipeline(setenv(cmd, "JULIA_NUM_THREADS" => other_nthreads), stdout = stdout, stderr = stderr),
            wait = false)
    include("threads_exec.jl")
    if !success(p)
        error("threads test failed with nthreads == $other_nthreads")
    end
end

# issue #34415 - make sure external affinity settings work
if Sys.islinux() && Sys.CPU_THREADS > 1 && Sys.which("taskset") !== nothing
    run_with_affinity(spec) = readchomp(`taskset -c $spec $(Base.julia_cmd()) -e "run(\`taskset -p \$(getpid())\`)"`)
    @test endswith(run_with_affinity("1"), "2")
    @test endswith(run_with_affinity("0,1"), "3")
end

max_stars_repo_name: Dictino/julia

max_issues_repo_name: Dictino/julia

max_forks_repo_name: Dictino/julia


text:
include("hypercube.jl")
include("crosspolytope.jl")
include("ex1.jl")
include("infeasible.jl")
include("jumpsimplex.jl")
include("nonfulldimensional.jl")

const jumptests = Dict("hypercube"          => lib->hypercubetest(lib, 2),
                       "jumpsimplex"        => lib->jumpsimplextest(lib, 2),
                       "simplexorig"        => lib->simplexorigtest(lib, 2),
                       "crosspolytope"      => lib->crosspolytopetest(lib, 2),
                       "ex1"                => ex1test,
                       "infeasible"         => lib->infeasibletest(lib, 2),
                       "nonfulldimensional" => nonfulldimensionaltest)

@polytestset jump


text:
struct NeptuneBarycenter <: abstractBarycenter end

naif_id(::Type{NeptuneBarycenter}) = 8
parent(::Type{NeptuneBarycenter}) = SSB


text:
export get_header

"""
getheader(block, header)

Returns a vector of 'header' values from each trace in 'block'. Trace order is preserved.

# Example
sx = get_header(block, "SourceX")
sy = get_header(block, :SourceX)
"""
function get_header(block::SeisBlock, name_in::Union{Symbol, String}; scale::Bool = true)
    name = Symbol(name_in)
    ntraces = length(block)
    ftype = fieldtype(BinaryTraceHeader, name)
    out = zeros(ftype, ntraces)

    for i in 1:ntraces
        out[i] = getfield(block.traceheaders[i], name)
    end

    # Check, and apply scaling
    is_scalable, scale_name = check_scale(name)
    if scale && is_scalable
        scaling_factor = get_header(block, scale_name)
        out = Float64.(out)
        for ii in 1:ntraces
            fact = scaling_factor[ii]
            fact > 0 ? out[ii] *= fact : out[ii] /= abs(fact)
        end
    end

    return out
end

"""
    is_scalable, scale_name = check_scale(sym)

Checks to see if the BinaryTraceHeader field `sym` requires scaling.

`is_scalable` returns true if the field can be scaled, and `scale_name` returns
the symbol of the appropriate scaling field.
"""
function check_scale(sym::Symbol)
    recsrc = false
    el = false
    scale_name = :Null

    recsrc_str = "SourceX
            SourceY
            GroupX
            GroupY
            CDPX
            CDPY"

    el_str = " RecGroupElevation
          SourceSurfaceElevation
          SourceDepth
          RecDatumElevation
          SourceDatumElevation
          SourceWaterDepth
          GroupWaterDepth"

    if occursin(String(sym), recsrc_str)
        recsrc = true
        scale_name = :RecSourceScalar
    elseif occursin(String(sym), el_str)
        el = true
        scale_name = :ElevationScalar
    end

    return (recsrc||el), scale_name
end

"""
    get_header(con::SeisCon, name::String)

Gets the metadata summary, `name`, from every `BlockScan` in `con`.
Column 1 contains the minimum value of `name` within the block, and Column 2
contains the maximum value of `name` within the block. Src/Rec scaling is not applied.

To get source coordinate pairs as an array, see `get_sources`.

# Example

trace = get_header(s, "TraceNumber")

"""
function get_header(con::SeisCon,name::String)
    minmax = [con.blocks[i].summary[name] for i in 1:length(con)]
    I = length(minmax)
    vals = Array{Int32,2}(undef, I, 2)
    for i in 1:I
        vals[i,1] = minmax[i][1]
        vals[i,2] = minmax[i][2]
    end

    return vals
end


text:
using Dates
using GitHub
using Pkg
using Printf
using RegistryCI
using Test
using TimeZones
import JSON

const AutoMerge = RegistryCI.AutoMerge

@testset "Guidelines for new packages" begin
    @testset "Normal capitalization" begin
        @test AutoMerge.meets_normal_capitalization("Zygote")[1]
        @test AutoMerge.meets_normal_capitalization("Zygote")[1]
        @test !AutoMerge.meets_normal_capitalization("HTTP")[1]
        @test !AutoMerge.meets_normal_capitalization("HTTP")[1]
    end
    @testset "Not too short - at least five letters" begin
        @test AutoMerge.meets_name_length("Zygote")[1]
        @test AutoMerge.meets_name_length("Zygote")[1]
        @test !AutoMerge.meets_name_length("Flux")[1]
        @test !AutoMerge.meets_name_length("Flux")[1]
    end
    @testset "Standard initial version number" begin
        @test AutoMerge.meets_standard_initial_version_number(v"0.0.1")[1]
        @test AutoMerge.meets_standard_initial_version_number(v"0.1.0")[1]
        @test AutoMerge.meets_standard_initial_version_number(v"1.0.0")[1]
        @test !AutoMerge.meets_standard_initial_version_number(v"0.0.2")[1]
        @test !AutoMerge.meets_standard_initial_version_number(v"0.1.1")[1]
        @test !AutoMerge.meets_standard_initial_version_number(v"0.2.0")[1]
        @test !AutoMerge.meets_standard_initial_version_number(v"1.0.1")[1]
        @test !AutoMerge.meets_standard_initial_version_number(v"1.1.0")[1]
        @test !AutoMerge.meets_standard_initial_version_number(v"1.1.1")[1]
        @test !AutoMerge.meets_standard_initial_version_number(v"2.0.0")[1]
    end
    @testset "Repo URL ends with /name.jl.git where name is the package name" begin
        @test AutoMerge.url_has_correct_ending("https://github.com/FluxML/Flux.jl.git", "Flux")[1]
        @test !AutoMerge.url_has_correct_ending("https://github.com/FluxML/Flux.jl", "Flux")[1]
        @test !AutoMerge.url_has_correct_ending("https://github.com/FluxML/Zygote.jl.git", "Flux")[1]
        @test !AutoMerge.url_has_correct_ending("https://github.com/FluxML/Zygote.jl", "Flux")[1]
    end
end

@testset "Guidelines for new versions" begin
    @testset "Sequential version number" begin
        @test AutoMerge.meets_sequential_version_number(v"0.0.1", v"0.0.2")[1]
        @test AutoMerge.meets_sequential_version_number(v"0.1.0", v"0.1.1")[1]
        @test AutoMerge.meets_sequential_version_number(v"0.1.0", v"0.2.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"1.0.0", v"1.0.1")[1]
        @test AutoMerge.meets_sequential_version_number(v"1.0.0", v"1.1.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"1.0.0", v"2.0.0")[1]
        @test !AutoMerge.meets_sequential_version_number(v"0.0.1", v"0.0.3")[1]
        @test !AutoMerge.meets_sequential_version_number(v"0.1.0", v"0.3.0")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.0", v"1.0.2")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.0", v"1.2.0")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.0", v"3.0.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"0.1.1", v"0.2.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"0.1.2", v"0.2.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"0.1.3", v"0.2.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"1.0.1", v"1.1.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"1.0.2", v"1.1.0")[1]
        @test AutoMerge.meets_sequential_version_number(v"1.0.3", v"1.1.0")[1]
        @test !AutoMerge.meets_sequential_version_number(v"0.1.1", v"0.2.1")[1]
        @test !AutoMerge.meets_sequential_version_number(v"0.1.2", v"0.2.2")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.1", v"1.1.1")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.3", v"1.2.0")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.3", v"1.2.1")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.3", v"1.1.1")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.0", v"2.0.1")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.0", v"2.1.0")[1]
        @test !AutoMerge.meets_sequential_version_number(v"1.0.0", v"2.1.0")[1]
    end
    @testset "Patch releases cannot narrow Julia compat" begin
        r1 = Pkg.Types.VersionRange("1.3-1.7")
        r2 = Pkg.Types.VersionRange("1.4-1.7")
        r3 = Pkg.Types.VersionRange("1.3-1.6")
        @test AutoMerge.range_did_not_narrow(r1, r1)[1]
        @test AutoMerge.range_did_not_narrow(r2, r2)[1]
        @test AutoMerge.range_did_not_narrow(r3, r3)[1]
        @test AutoMerge.range_did_not_narrow(r2, r1)[1]
        @test AutoMerge.range_did_not_narrow(r3, r1)[1]
        @test !AutoMerge.range_did_not_narrow(r1, r2)[1]
        @test !AutoMerge.range_did_not_narrow(r1, r3)[1]
        @test !AutoMerge.range_did_not_narrow(r2, r3)[1]
        @test !AutoMerge.range_did_not_narrow(r3, r2)[1]
    end
end

@testset "Unit tests" begin
    @testset "assert.jl" begin
        @test nothing == @test_nowarn AutoMerge.always_assert(1 == 1)
        @test_throws AutoMerge.AlwaysAssertionError AutoMerge.always_assert(1 == 2)
    end
end

@testset "CIService unit testing" begin
    @testset "Travis CI" begin
        # pull request build
        withenv("TRAVIS_BRANCH" => "master",
                "TRAVIS_EVENT_TYPE" => "pull_request",
                "TRAVIS_PULL_REQUEST" => "42",
                "TRAVIS_PULL_REQUEST_SHA" => "abc123",
                "TRAVIS_BUILD_DIR" => "/tmp/clone") do
            cfg = AutoMerge.TravisCI()
            @test AutoMerge.conditions_met_for_pr_build(cfg; master_branch="master")
            @test !AutoMerge.conditions_met_for_pr_build(cfg; master_branch="retsam")
            @test !AutoMerge.conditions_met_for_merge_build(cfg; master_branch="master")
            @test AutoMerge.pull_request_number(cfg) == 42
            @test AutoMerge.current_pr_head_commit_sha(cfg) == "abc123"
            @test AutoMerge.directory_of_cloned_registry(cfg) == "/tmp/clone"
        end
        # merge build with cron
        withenv("TRAVIS_BRANCH" => "master",
                "TRAVIS_EVENT_TYPE" => "cron",
                "TRAVIS_PULL_REQUEST" => "false",
                "TRAVIS_PULL_REQUEST_SHA" => "abc123",
                "TRAVIS_BUILD_DIR" => "/tmp/clone") do
            cfg = AutoMerge.TravisCI()
            @test !AutoMerge.conditions_met_for_pr_build(cfg; master_branch="master")
            @test AutoMerge.conditions_met_for_merge_build(cfg; master_branch="master")
            @test !AutoMerge.conditions_met_for_merge_build(cfg; master_branch="retsam")
            @test !AutoMerge.conditions_met_for_merge_build(AutoMerge.TravisCI(enable_cron_builds=false); master_branch="master")
            @test AutoMerge.current_pr_head_commit_sha(cfg) == "abc123"
            @test AutoMerge.directory_of_cloned_registry(cfg) == "/tmp/clone"
        end
        # merge build with api
        withenv("TRAVIS_BRANCH" => "master",
                "TRAVIS_EVENT_TYPE" => "api",
                "TRAVIS_PULL_REQUEST" => "false",
                "TRAVIS_PULL_REQUEST_SHA" => "abc123",
                "TRAVIS_BUILD_DIR" => "/tmp/clone") do
            cfg = AutoMerge.TravisCI()
            @test !AutoMerge.conditions_met_for_pr_build(cfg; master_branch="master")
            @test AutoMerge.conditions_met_for_merge_build(cfg; master_branch="master")
            @test !AutoMerge.conditions_met_for_merge_build(cfg; master_branch="retsam")
            @test !AutoMerge.conditions_met_for_merge_build(AutoMerge.TravisCI(enable_api_builds=false); master_branch="master")
            @test AutoMerge.current_pr_head_commit_sha(cfg) == "abc123"
            @test AutoMerge.directory_of_cloned_registry(cfg) == "/tmp/clone"
        end
        # neither pull request nor merge build
        withenv("TRAVIS_BRANCH" => nothing,
                "TRAVIS_EVENT_TYPE" => nothing) do
            cfg = AutoMerge.TravisCI()
            @test !AutoMerge.conditions_met_for_pr_build(cfg; master_branch="master")
            @test !AutoMerge.conditions_met_for_merge_build(cfg; master_branch="master")
        end
    end

    @testset "GitHub Actions" begin; mktemp() do file, io
        # mimic the workflow file for GitHub Actions
        workflow = Dict("pull_request" => Dict("head" => Dict("sha" => "abc123")))
        JSON.print(io, workflow); close(io)

        # pull request build
        withenv("GITHUB_REF" => "refs/pull/42/merge",
                "GITHUB_EVENT_NAME" => "pull_request",
                "GITHUB_SHA" => "123abc", # "wrong", should be taken from workflow file
                "GITHUB_EVENT_PATH" => file,
                "GITHUB_WORKSPACE" => "/tmp/clone") do
            cfg = AutoMerge.GitHubActions()
            @test AutoMerge.conditions_met_for_pr_build(cfg; master_branch="master")
            @test_broken !AutoMerge.conditions_met_for_pr_build(cfg; master_branch="retsam")
            @test !AutoMerge.conditions_met_for_merge_build(cfg; master_branch="master")
            @test AutoMerge.pull_request_number(cfg) == 42
            @test AutoMerge.current_pr_head_commit_sha(cfg) == "abc123"
            @test AutoMerge.directory_of_cloned_registry(cfg) == "/tmp/clone"
        end
        # merge build with schedule: cron
        withenv("GITHUB_REF" => "refs/heads/master",
                "GITHUB_EVENT_NAME" => "schedule",
                "GITHUB_SHA" => "123abc",
                "GITHUB_WORKSPACE" => "/tmp/clone") do
            cfg = AutoMerge.GitHubActions()
            @test !AutoMerge.conditions_met_for_pr_build(cfg; master_branch="master")
            @test AutoMerge.conditions_met_for_merge_build(cfg; master_branch="master")
            @test !AutoMerge.conditions_met_for_merge_build(cfg; master_branch="retsam")
            @test !AutoMerge.conditions_met_for_merge_build(AutoMerge.GitHubActions(enable_cron_builds=false); master_branch="master")
            @test AutoMerge.directory_of_cloned_registry(cfg) == "/tmp/clone"
        end
        # neither pull request nor merge build
        withenv("GITHUB_REF" => nothing,
                "GITHUB_EVENT_NAME" => nothing) do
            cfg = AutoMerge.GitHubActions()
            @test !AutoMerge.conditions_met_for_pr_build(cfg; master_branch="master")
            @test !AutoMerge.conditions_met_for_merge_build(cfg; master_branch="master")
        end
    end end

    @testset "auto detection" begin
        withenv("TRAVIS_REPO_SLUG" => "JuliaRegistries/General",
                "GITHUB_REPOSITORY" => nothing) do
            @test AutoMerge.auto_detect_ci_service() == AutoMerge.TravisCI()
            @test AutoMerge.auto_detect_ci_service(env=ENV) == AutoMerge.TravisCI()
        end
        withenv("TRAVIS_REPO_SLUG" => nothing,
                "GITHUB_REPOSITORY" => "JuliaRegistries/General") do
            @test AutoMerge.auto_detect_ci_service() == AutoMerge.GitHubActions()
            @test AutoMerge.auto_detect_ci_service(env=ENV) == AutoMerge.GitHubActions()
        end
    end
end


text:
"""
Binary Cross Entropy calc.
"""
function calculate_cost(Ŷ, Y)
    m = size(Y, 2)
    epsilon = eps(1.0)

    # Deal with log(0)
    Ŷ_new = [min(i, epsilon) for i in [max(i, epsilon) for i in Ŷ]]

    # using hardcoded 29 as number of characters
    #cost = -sum(Y .* log2.(Ŷ_new) + ((1 + 2/29) .- Y) .* log2.((1 + 2/29) .- Ŷ_new)) /m
    cost = -sum([ Y[i] == 1 ? log2(abs(Ŷ_new[i])) : log2(abs(Ŷ_new[i] + 27/29)) for i in 1:length(Y)]) /m
    #cost = -log2(Ŷ_new[findmax(Y)[2]])
    return cost
end # function

max_stars_repo_name: Squalm/NeuralNetworks

max_issues_repo_name: Squalm/NeuralNetworks

max_forks_repo_name: Squalm/NeuralNetworks


text:
export BrickletDualRelayState
struct BrickletDualRelayState
    relay1::Bool
    relay2::Bool
end

export BrickletDualRelayMonoflop
struct BrickletDualRelayMonoflop
    state::Bool
    time::Integer
    time_remaining::Integer
end

export BrickletDualRelayIdentity
struct BrickletDualRelayIdentity
    uid::String
    connected_uid::String
    position::Char
    hardware_version::Vector{Integer}
    firmware_version::Vector{Integer}
    device_identifier::Integer
end

export BrickletDualRelay
"""
Two relays to switch AC/DC devices
"""
mutable struct BrickletDualRelay <: TinkerforgeDevice
    ipcon::IPConnection
    deviceInternal::PyObject

    """
    Creates an object with the unique device ID *uid* and adds it to
    the IP Connection *ipcon*.
    """
    function BrickletDualRelay(uid::String, ipcon::IPConnection)
        package = pyimport("tinkerforge.bricklet_dual_relay")
        deviceInternal = package.BrickletDualRelay(uid, ipcon.ipconInternal)

        return new(ipcon, deviceInternal)
    end
end

export set_state
"""
    $(SIGNATURES)

Sets the state of the relays, *true* means on and *false* means off.
For example: (true, false) turns relay 1 on and relay 2 off.

If you just want to set one of the relays and don't know the current state
of the other relay, you can get the state with :func:`Get State` or you
can use :func:`Set Selected State`.

All running monoflop timers will be aborted if this function is called.
"""
function set_state(device::BrickletDualRelay, relay1, relay2)
    device.deviceInternal.set_state(relay1, relay2)
end

export get_state
"""
    $(SIGNATURES)

Returns the state of the relays, *true* means on and *false* means off.
"""
function get_state(device::BrickletDualRelay)
    return device.deviceInternal.get_state()
end

export set_monoflop
"""
    $(SIGNATURES)

The first parameter can be 1 or 2 (relay 1 or relay 2). The second parameter
is the desired state of the relay (*true* means on and *false* means off).
The third parameter indicates the time that the relay should hold
the state.

If this function is called with the parameters (1, true, 1500):
Relay 1 will turn on and in 1.5s it will turn off again.

A monoflop can be used as a failsafe mechanism. For example: Lets assume you
have a RS485 bus and a Dual Relay Bricklet connected to one of the slave
stacks. You can now call this function every second, with a time parameter
of two seconds. The relay will be on all the time. If now the RS485
connection is lost, the relay will turn off in at most two seconds.
"""
function set_monoflop(device::BrickletDualRelay, relay, state, time)
    device.deviceInternal.set_monoflop(relay, state, time)
end

export get_monoflop
"""
    $(SIGNATURES)

Returns (for the given relay) the current state and the time as set by
:func:`Set Monoflop` as well as the remaining time until the state flips.

If the timer is not running currently, the remaining time will be returned
as 0.
"""
function get_monoflop(device::BrickletDualRelay, relay)
    return device.deviceInternal.get_monoflop(relay)
end

export set_selected_state
"""
    $(SIGNATURES)

Sets the state of the selected relay (1 or 2), *true* means on and *false* means off.

A running monoflop timer for the selected relay will be aborted if this function is called.

The other relay remains untouched.
"""
function set_selected_state(device::BrickletDualRelay, relay, state)
    device.deviceInternal.set_selected_state(relay, state)
end

export get_identity
"""
    $(SIGNATURES)

Returns the UID, the UID where the Bricklet is connected to,
the position, the hardware and firmware version as well as the
device identifier.

The position can be 'a', 'b', 'c', 'd', 'e', 'f', 'g' or 'h' (Bricklet Port).
A Bricklet connected to an :ref:`Isolator Bricklet <isolator_bricklet>` is always at
position 'z'.

The device identifier numbers can be found :ref:`here <device_identifier>`.
|device_identifier_constant|
"""
function get_identity(device::BrickletDualRelay)
    return device.deviceInternal.get_identity()
end

export register_callback
"""
Registers the given *function* with the given *callback_id*.
"""
function register_callback(device::BrickletDualRelay, callback_id, function_)
    if isnothing(function_)
        device.registered_callbacks.pop(callback_id, None)
    else
        device.registered_callbacks[callback_id] = function_
    end
end


text:
const version = "1.2.8"
const name = "GCoptimization"
const libname = "lib"*name
const repo = "https://github.com/Gnimuc/$name"
const suffix = Sys.WORD_SIZE == 64 ? "x64" : "x86"

prefix = joinpath(dirname(@__FILE__), "usr")
srcdir = joinpath(dirname(@__FILE__), "src", "GraphCutsBuilder-$version")
builddir = joinpath(dirname(@__FILE__), "builds", "$name-$version")

if is_windows()
    # download source code
    mkpath("downloads")
    source = "downloads/$name-$version.zip"
    if !isfile(source)
        info("Downloading $name-$version source from $repo...")
        download("$repo/archive/v$version.zip", source)
    end

    # unpack source zip
    mkpath(srcdir)
    run(`7z x $source -osrc -y`)

    const archive = "downloads/$libname-$suffix-$version.zip"
    if !isfile(archive)
        info("Downloading $name-$version binaries from $repo...")
        download("$repo/releases/download/v$version/$libname-$suffix.zip", archive)
    end
    run(`7z x $archive -y`)
end

if is_apple() || is_linux()
    # download source code
    mkpath("downloads")
    source = "downloads/$name-$version.tar.gz"
    if !isfile(source)
        info("Downloading $name-$version source from $repo...")
        download("$repo/archive/v$version.tar.gz", source)
    end

    # unpack source tarball
    mkpath(srcdir)
    run(`tar xzf $source -C src`)

    try
        mkpath("$prefix/lib")
        mkpath(builddir)
        cd(builddir)
        run(`cmake $srcdir`)
        run(`make`)
        cp("$libname.$(Libdl.dlext)", "$prefix/lib/$libname.$(Libdl.dlext)", remove_destination=true)
    catch err
        warn(err)
        info("Downloading $name-$version binaries from $repo...")
        download("$repo/releases/download/v$version/$libname.$(Libdl.dlext)", "$prefix/lib/$libname.$(Libdl.dlext)")
    end
end

# copy header files
mkpath(joinpath(prefix, "include"))
cp(joinpath(srcdir, "include"), joinpath(prefix, "include"), remove_destination=true)


text:
module Green

export alphas, full_cov

""" Returns the coefficients giving the linear combination of values
satisfying the homogeneous equation with the given roots.  For `p`
roots, the first `p` alphas are zero, and the remaining will satisfy

    y[k] - sum([alphas[i,k]*y[k-i] for i in 1:p]) == 0

when `y` satisfies the homogeneous equation.  """
function alphas(ts::Array{Float64, 1}, roots::Array{ComplexF64, 1})
    p = size(roots, 1)
    n = size(ts, 1)

    alphas = zeros(p, n)

    M = zeros(ComplexF64, p,p)
    b = ones(p)

    # Alphas for samples before the p+1-st are zero
    for j in p+1:n
        for k in 1:p
            for l in 1:p
                M[k,l] = exp(roots[k]*(ts[j-(p-l+1)]-ts[j]))
            end
        end
        alphas[:,j] = real(M\b)
    end

    alphas
end

""" Returns the coefficients `g' of the exponential solutions in the
Green's function:

    g(t,xi) == sum([g[i]*exp(roots[i]*(t-xi)) for i in 1:p]) """
function greens_coeff(roots::Array{ComplexF64, 1})
    p = size(roots, 1)

    M = zeros(ComplexF64, p, p)
    b = zeros(p)

    for i in 1:p
        for j in 1:p
            M[i,j] = roots[j]^(i-1)
        end
    end

    b[end] = 1.0

    M\b
end

""" Returns the coefficients of the exponential terms in the Green's
function integrand for the solution `y`:

    c[i] == g[i]*prod([(roots[i] + maroots[j])/(-maroots[j]) for j in 1:q])

where `y` satisfies the ODE

    prod([(ddt - roots[i]) for i in 1:p])*y(t) == prod([(ddt - maroots[i])/(-maroots[i]) for i in 1:q])*eta(t)

with `eta` a white noise process with variance `sigma^2`.

"""
function greens_integrand_coeff(roots::Array{ComplexF64,1}, maroots::Array{ComplexF64, 1}, g::Array{ComplexF64, 1})
    p = size(roots, 1)
    q = size(maroots, 1)

    if q == 0
        g
    else
        coeff = zeros(ComplexF64, p)

        for i in 1:p
            coeff[i] = g[i]
            for j in 1:q
                coeff[i] *= (roots[i] + maroots[j])/(-maroots[j])
            end
        end

        coeff
    end
end

function cov_coeffs(gic::Array{ComplexF64, 1}, roots::Array{ComplexF64, 1})
    p = size(roots, 1)

    cc = zeros(ComplexF64, p)

    for i in 1:p
        cc[i] = zero(cc[i])
        for j in 1:p
            cc[i] -= gic[i]*conj(gic[j])/(roots[i] + conj(roots[j]))
        end
    end

    cc
end

""" Returns the full covariance matrix computed using the Green's
function method at the given times, `ts`.  Requires that the `ts`
array is sorted to make bookkeeping easier.  """
function full_cov(ts::Array{Float64, 1}, sigma::Float64, roots::Array{ComplexF64, 1}, maroots::Array{ComplexF64, 1})
    n = size(ts, 1)
    p = size(roots, 1)
    q = size(maroots, 1)

    for i in 1:n-1
        @assert ts[i] < ts[i+1]
    end

    g = greens_coeff(roots)
    gi = greens_integrand_coeff(roots, maroots, g)

    ccs = cov_coeffs(gi, roots)

    cov = zeros(Float64, n, n)
    for i in 1:n
        for j in 1:i
            celt = zero(ComplexF64)
            dt = ts[i] - ts[j]
            for k in 1:p
                celt += ccs[k]*exp(roots[k]*dt)
            end
            cov[i,j] = real(celt)
            cov[j,i] = cov[i,j]
        end
    end

    sigma2 = sigma*sigma
    factor = sigma2 / cov[1,1]

    for j in 1:n
        for i in 1:n
            cov[i,j] = cov[i,j] * factor
        end
    end

    cov
end

end


text:
using ForwardDiff

#==========================#
# https://github.com/JuliaDiff/ForwardDiff.jl/pull/495/files

using AbstractFFTs
ForwardDiff.value(x::Complex{<:ForwardDiff.Dual}) =
    Complex(x.re.value, x.im.value)

ForwardDiff.partials(x::Complex{<:ForwardDiff.Dual}, n::Int) =
    Complex(ForwardDiff.partials(x.re, n), ForwardDiff.partials(x.im, n))

ForwardDiff.npartials(x::Complex{<:ForwardDiff.Dual{T,V,N}}) where {T,V,N} = N
ForwardDiff.npartials(::Type{<:Complex{<:ForwardDiff.Dual{T,V,N}}}) where {T,V,N} = N

# AbstractFFTs.complexfloat(x::AbstractArray{<:ForwardDiff.Dual}) = float.(x .+ 0im)
AbstractFFTs.complexfloat(x::AbstractArray{<:ForwardDiff.Dual}) = AbstractFFTs.complexfloat.(x)
AbstractFFTs.complexfloat(d::ForwardDiff.Dual{T,V,N}) where {T,V,N} = convert(ForwardDiff.Dual{T,float(V),N}, d) + 0im

AbstractFFTs.realfloat(x::AbstractArray{<:ForwardDiff.Dual}) = AbstractFFTs.realfloat.(x)
AbstractFFTs.realfloat(d::ForwardDiff.Dual{T,V,N}) where {T,V,N} = convert(ForwardDiff.Dual{T,float(V),N}, d)

for plan in [:plan_fft, :plan_ifft, :plan_bfft]
    @eval begin

        AbstractFFTs.$plan(x::AbstractArray{<:ForwardDiff.Dual}, region=1:ndims(x)) =
            AbstractFFTs.$plan(ForwardDiff.value.(x) .+ 0im, region)

        AbstractFFTs.$plan(x::AbstractArray{<:Complex{<:ForwardDiff.Dual}}, region=1:ndims(x)) =
            AbstractFFTs.$plan(ForwardDiff.value.(x), region)

    end
end

# rfft only accepts real arrays
AbstractFFTs.plan_rfft(x::AbstractArray{<:ForwardDiff.Dual}, region=1:ndims(x)) =
    AbstractFFTs.plan_rfft(ForwardDiff.value.(x), region)

for plan in [:plan_irfft, :plan_brfft]  # these take an extra argument, only when complex?
    @eval begin

        AbstractFFTs.$plan(x::AbstractArray{<:ForwardDiff.Dual}, region=1:ndims(x)) =
            AbstractFFTs.$plan(ForwardDiff.value.(x) .+ 0im, region)

        AbstractFFTs.$plan(x::AbstractArray{<:Complex{<:ForwardDiff.Dual}}, d::Integer, region=1:ndims(x)) =
            AbstractFFTs.$plan(ForwardDiff.value.(x), d, region)

    end
end

for P in [:Plan, :ScaledPlan]  # need ScaledPlan to avoid ambiguities
    @eval begin

        Base.:*(p::AbstractFFTs.$P, x::AbstractArray{<:ForwardDiff.Dual}) =
            _apply_plan(p, x)

        Base.:*(p::AbstractFFTs.$P, x::AbstractArray{<:Complex{<:ForwardDiff.Dual}}) =
            _apply_plan(p, x)

    end
end

function _apply_plan(p::AbstractFFTs.Plan, x::AbstractArray)
    xtil = p * ForwardDiff.value.(x)
    dxtils = ntuple(ForwardDiff.npartials(eltype(x))) do n
        p * ForwardDiff.partials.(x, n)
    end
    map(xtil, dxtils...) do val, parts...
        Complex(
            ForwardDiff.Dual(real(val), map(real, parts)),
            ForwardDiff.Dual(imag(val), map(imag, parts)),
        )
    end
end

using Test
using FFTW

@testset "fft on Dual" begin
    x1 = ForwardDiff.Dual.(1:4.0, 2:5, 3:6)

    @test ForwardDiff.value.(x1) == 1:4
    @test ForwardDiff.partials.(x1, 1) == 2:5

    @test AbstractFFTs.complexfloat(x1)[1] === AbstractFFTs.complexfloat(x1[1]) === ForwardDiff.Dual(1.0, 2.0, 3.0) + 0im
    @test AbstractFFTs.realfloat(x1)[1] === AbstractFFTs.realfloat(x1[1]) === ForwardDiff.Dual(1.0, 2.0, 3.0)

    @test fft(x1, 1)[1] isa Complex{<:ForwardDiff.Dual}

    @testset "$f" for f in [fft, ifft, rfft, bfft]
        @test ForwardDiff.value.(f(x1)) == f(ForwardDiff.value.(x1))
        @test ForwardDiff.partials.(f(x1), 1) == f(ForwardDiff.partials.(x1, 1))
    end
end

#==========================#

f(x) = sum(real(rfft(x)))
x = rand(10)
f(x)
ForwardDiff.gradient(f, x)
# ERROR: LoadError: Cannot determine ordering of Dual tags Nothing and ForwardDiff.Tag{typeof(f), Float64}
# Stacktrace:
# [1] ≺(a::Type, b::Type)
#   @ ForwardDiff ~/.julia/packages/ForwardDiff/QOqCN/src/dual.jl:49
# [2] partials
#   @ ~/.julia/packages/ForwardDiff/QOqCN/src/dual.jl:103 [inlined]
# [3] extract_gradient!(#unused#::Type{ForwardDiff.Tag{typeof(f), Float64}}, result::Vector{Float64}, dual::ForwardDiff.Dual{Nothing, Float64, 10})
#   @ ForwardDiff ~/.julia/packages/ForwardDiff/QOqCN/src/gradient.jl:81
# [4] vector_mode_gradient(f::typeof(f), x::Vector{Float64}, cfg::ForwardDiff.GradientConfig{ForwardDiff.Tag{typeof(f), Float64}, Float64, 10, Vector{ForwardDiff.Dual{ForwardDiff.Tag{typeof(f), Float64}, Float64, 10}}})
#   @ ForwardDiff ~/.julia/packages/ForwardDiff/QOqCN/src/gradient.jl:109
# [5] gradient(f::Function, x::Vector{Float64}, cfg::ForwardDiff.GradientConfig{ForwardDiff.Tag{typeof(f), Float64}, Float64, 10, Vector{ForwardDiff.Dual{ForwardDiff.Tag{typeof(f), Float64}, Float64, 10}}}, ::Val{true})
#   @ ForwardDiff ~/.julia/packages/ForwardDiff/QOqCN/src/gradient.jl:19
# [6] gradient(f::Function, x::Vector{Float64}, cfg::ForwardDiff.GradientConfig{ForwardDiff.Tag{typeof(f), Float64}, Float64, 10, Vector{ForwardDiff.Dual{ForwardDiff.Tag{typeof(f), Float64}, Float64, 10}}}) (repeats 2 times)
#   @ ForwardDiff ~/.julia/packages/ForwardDiff/QOqCN/src/gradient.jl:17
# [7] top-level scope
#   @ ~/Documents/cloud/sandbox/julia/dftk/DifferentiableDFTK/sandbox/forwarddiff_fft/forwarddiff_fft.jl:103

max_stars_repo_name: niklasschmitz/DifferentiableDFTK

max_issues_repo_name: niklasschmitz/DifferentiableDFTK

max_forks_repo_name: niklasschmitz/DifferentiableDFTK