include("forecast_types.jl")
include("helper_functions.jl")

function count_cases_in_windows!(
    forecast::Forecast,
    case_counts,
    window_lengths,
    D_total,
    sim
)
    """
    Count the cases in each observation period and store in case_counts.
    """

    t_start = 1
    for (i,t) in enumerate(window_lengths)
        case_counts[i] = sum(@view D_total[t_start:t,sim])
        t_start = t
    end

    return nothing
end

function check_sim!(
    forecast::Forecast,
    forecast_start_date,
    omicron_dominant_date,
    case_counts,
    sim,
    local_cases,
    window_lengths,
    min_cases,
    max_cases,
    reinitialise_allowed;
    day = 0,
)
    """
    Check for consistency of the simulations against the data. This will also
    check for instances of superspreading events and will insert cases if conditions
    are met.
    """

    print_status = false

    Z = forecast.sim_realisations.Z
    D = forecast.sim_realisations.D
    U = forecast.sim_realisations.U

    # days forecast observed for
    T_observed = forecast.sim_features.T_observed
    max_forecast_cases = forecast.sim_features.max_forecast_cases

    consistency_multiplier = forecast.sim_constants.consistency_multiplier

    # initialise the bad sim
    bad_sim = false
    injected_cases = false

    days_delta = (Dates.Date(omicron_dominant_date) - Dates.Date(forecast_start_date)).value

    cases_pre_backcast = sum(@view D[1:days_delta,1:2,sim])
    cases_backcast = sum(@view D[days_delta+1:T_observed,1:2,sim])
    cases_pre_nowcast = sum(@view D[21:T_observed,1:2,sim])
    cases_nowcast = sum(@view D[10:T_observed,1:2,sim])

    case_counts[1] = cases_pre_backcast
    case_counts[2] = cases_backcast
    case_counts[3] = cases_pre_nowcast
    case_counts[4] = cases_nowcast

    # this is just the total cases over the forecast horizon
    D_forecast = sum(@view D[T_observed+1:end,1:2,sim])

    # if we've exceeded the max cases over a given period
    if D_forecast > max_forecast_cases
        bad_sim = true
        print_status && println("too many forecast cases")
    end

    if !bad_sim
        for (i, (c,m)) in enumerate(zip(case_counts,max_cases))
            if c > m
                bad_sim = true
                print_status && println("too many cases", c, " ", m, " day: ", day)
                break
            end
        end

        if day == 0
            for (i, (c,m)) in enumerate(zip(case_counts,min_cases))
                if c < m
                    bad_sim = true
                    print_status && println("too few cases", c, " ", m, " day: ", day)
                    break
                end
            end
        end
    end

    # see whether we need to inject any cases
    injected_cases = false

    if !bad_sim && reinitialise_allowed
        # calculate the number of detections missed over the 5 day period
        actual_3_day_cases = sum(@view local_cases[day-2:day])
        sim_3_day_cases = sum(@view D[day-2:day,:,sim])
        sim_3_day_threshold = consistency_multiplier*max(1, sim_3_day_cases)
        # calculate total number of cases over week
        sim_week_cases = sum(@view D[day-6:day,:,sim])
        missing_detections = 0

        if actual_3_day_cases > sim_3_day_threshold || (sim_week_cases == 0 && actual_3_day_cases > 0)
            print_status && println("actual: ", actual_3_day_cases, " 3day: ", sim_3_day_cases, " 3day thresh: ", sim_3_day_threshold, " week: ", sim_week_cases, " day added: ", day)

            if sim_week_cases == 0
                missing_detections = rand(1:actual_3_day_cases)
            else
                missing_detections = rand(1:(actual_3_day_cases - sim_3_day_cases))
            end

            injected_cases = true
            inject_cases!(
                forecast::Forecast,
                missing_detections,
                day,
                sim,
            )
        end
    end

    return (bad_sim, injected_cases)
end

function inject_cases!(
    forecast::Forecast,
    missing_detections,
    day,
    sim,
)
    """
    A model for injecting cases into the simulation following long periods of low
    incidence.
    """

    Z = forecast.sim_realisations.Z
    D = forecast.sim_realisations.D
    U = forecast.sim_realisations.U
    individual_type_map = forecast.individual_type_map

    total_injected = missing_detections

    p_symp = forecast.sim_constants.p_symp
    p_detect_given_symp = forecast.sim_constants.p_detect_given_symp
    p_symp_given_detect = forecast.sim_constants.p_symp_given_detect

    omicron_dominant_day = forecast.sim_features.omicron_dominant_day

    # sample number detected
    num_symptomatic_detected = sample_binomial_limit(missing_detections, p_symp_given_detect)
    num_asymptomatic_detected = missing_detections - num_symptomatic_detected
    # infer some undetected symptomatic
    num_symptomatic_undetected = sample_negative_binomial_limit(missing_detections, p_detect_given_symp)

    # infer some undetected asumptomatic
    num_symptomatic_total = num_symptomatic_detected + num_symptomatic_undetected

    if num_symptomatic_total == 0
        num_asymptomatic_undetected = sample_negative_binomial_limit(1, p_symp)
    else
        num_asymptomatic_undetected = sample_negative_binomial_limit(num_symptomatic_total, p_symp)
    end

    # detection times are determined triangularly
    num_symptomatic_each_day = (
        num_symptomatic_detected ÷ 2,
        num_symptomatic_detected ÷ 2 + num_symptomatic_detected ÷ 3,
        num_symptomatic_detected ÷ 2 + num_symptomatic_detected ÷ 3 + num_symptomatic_detected ÷ 6
    )
    counter = 0
    onset_time = day

    # infection time is then 1 generation interval earlier
    for _ in 1:num_symptomatic_detected
        total_injected += 1
        if counter >= num_symptomatic_each_day[2]
            onset_time = day-1
        elseif counter >= num_symptomatic_each_day[3]
            onset_time = day-2
        end
        infection_time = onset_time - sample_onset_time(omicron = onset_time >= omicron_dominant_day)
        Z[infection_time+36,individual_type_map.S,sim] += 1
        D[onset_time,individual_type_map.S,sim] += 1
        counter += 1
    end

    # detection times are determined triangularly
    num_asymptomatic_each_day = (
        num_asymptomatic_detected ÷ 2,
        num_asymptomatic_detected ÷ 2 + num_asymptomatic_detected ÷ 3,
        num_asymptomatic_detected ÷ 2 + num_asymptomatic_detected ÷ 3 + num_asymptomatic_detected ÷ 6
    )
    counter = 0
    onset_time = day

    # infection time is then 1 generation interval earlier
    for _ in 1:num_asymptomatic_detected
        total_injected += 1
        if counter >= num_asymptomatic_each_day[2]
            onset_time = day-1
        elseif counter >= num_asymptomatic_each_day[3]
            onset_time = day-2
        end
        infection_time = onset_time - sample_onset_time(omicron = onset_time >= omicron_dominant_day)
        Z[infection_time+36,individual_type_map.A,sim] += 1
        D[onset_time,individual_type_map.A,sim] += 1
        counter += 1
    end

    # now add in the inferred undetected cases in the same way. Note that the
    # onset times are kinda irrelevant here, but just help with consistent
    # simulation recording.
    num_symptomatic_each_day = (
        num_symptomatic_undetected ÷ 2,
        num_symptomatic_undetected ÷ 2 + num_symptomatic_undetected ÷ 3,
        num_symptomatic_undetected ÷ 2 + num_symptomatic_undetected ÷ 3 + num_symptomatic_undetected ÷ 6
    )
    counter = 0
    onset_time = day

    for _ in 1:num_symptomatic_undetected
        total_injected += 1
        if counter >= num_symptomatic_each_day[2]
            onset_time = day-1
        elseif counter >= num_symptomatic_each_day[3]
            onset_time = day-2
        end
        infection_time = onset_time - sample_onset_time(omicron = onset_time >= omicron_dominant_day)
        Z[infection_time+36,individual_type_map.S,sim] += 1
        U[onset_time,individual_type_map.S,sim] += 1
        counter += 1
    end

    # detection times are determined triangularly
    num_asymptomatic_each_day = (
        num_asymptomatic_undetected ÷ 2,
        num_asymptomatic_undetected ÷ 2 + num_asymptomatic_undetected ÷ 3,
        num_asymptomatic_undetected ÷ 2 + num_asymptomatic_undetected ÷ 3 + num_asymptomatic_undetected ÷ 6
    )
    counter = 0
    onset_time = day

    # now add in the inferred undetected cases
    for _ in 1:num_asymptomatic_undetected
        total_injected += 1
        if counter >= num_asymptomatic_each_day[2]
            onset_time = day-1
        elseif counter >= num_asymptomatic_each_day[3]
            onset_time = day-2
        end
        infection_time = onset_time - sample_onset_time(omicron = onset_time >= omicron_dominant_day)
        Z[infection_time+36,individual_type_map.A,sim] += 1
        U[onset_time,individual_type_map.A,sim] += 1
        counter += 1
    end

    # println("total injected is: ", total_injected, " at time: ", day)

    return nothing
end


# start julia with: julia -p 4

using MAVs
using VehicleModels

mdl,n,c,r,params=initializeAutonomousControl(:c3);
pa=params[1];

function createParallelModels(n,c,pa)

#  http://docs.julialang.org/en/stable/manual/parallel-computing/#clustermanagers
  #addprocs(3);
  Xmin=[0.,180.,220.];
  #sm1=remotecall(initializeSimpleModel,2,(n,c,pa;x_min=Xmin[1]));
  #sm2=remotecall(initializeSimpleModel,3,(n,c,pa;x_min=Xmin[2]));
  #sm3=remotecall(initializeSimpleModel,4,(n,c,pa;x_min=Xmin[3]));
  sm1=@spawn initializeSimpleModel(n,c,pa;x_min=Xmin[1])
  sm2=@spawn initializeSimpleModel(n,c,pa;x_min=Xmin[2])
  sm3=@spawn initializeSimpleModel(n,c,pa;x_min=Xmin[3])

  sm=[sm1,sm2,sm3];
  return sm
end

sm=createParallelModels(n,c,pa);

#https://github.com/JuliaParallel/ClusterManagers.jl

#https://juliacomputing.com/domains/parallel-computing.html
#https://github.com/JuliaParallel


using PatternDispatch

degree_scale(degree, scale) = error()
degree_scale(degree, scale, tuning) = error()

@patterns begin
  function (@inverse degree_scale(degree, scale::Scale))(e)
  	e::Event
  	@guard haskey(e, :degree)
  	@guard haskey(e, :scale)
  	degree = e[:degree]
  	scale = e[:scale]
  end
  function (@inverse degree_scale(degree, scale::Scale, tuning::Tuning))(e)
  	e::Event
  	@guard haskey(e, :degree)
  	@guard haskey(e, :scale)
  	@guard haskey(e, :tuning)
  	degree = e[:degree]
  	scale = e[:scale]
  	tuning = e[:tuning]
  end

  to_freq(x) = error("Cannot convert to frequency: $(x)")
  to_freq(degree_scale(d, s)) = dst_to_freq(d, s, Tuning(:edo_12))
  to_freq(degree_scale(d, s, t)) = dst_to_freq(d, s, t)
end

function dst_to_freq(d::Int, s::Scale, t::Tuning)
  # TODO make this more robust
  t[s[d]]
end


const DISCRETE_INPLACE_DEFAULT = (t,u,du) -> fill!(du,zero(eltype(u)))
const DISCRETE_OUTOFPLACE_DEFAULT = (t,u) -> u

type DiscreteProblem{uType,tType,isinplace,F,C} <: AbstractDiscreteProblem{uType,tType,isinplace}
  f::F
  u0::uType
  tspan::Tuple{tType,tType}
  callback::C
end

function DiscreteProblem(f,u0,tspan; iip = isinplace(f,3),callback = nothing)
  DiscreteProblem{typeof(u0),eltype(tspan),iip,typeof(f),typeof(callback)}(f,u0,tspan,callback)
end

function DiscreteProblem(u0,tspan::Tuple; callback = nothing)
  iip = typeof(u0) <: AbstractArray
  if iip
    f = DISCRETE_INPLACE_DEFAULT
  else
    f = DISCRETE_OUTOFPLACE_DEFAULT
  end
  DiscreteProblem{typeof(u0),eltype(tspan),iip,typeof(f),typeof(callback)}(f,u0,tspan,callback)
end


function FieldCartesianOrbit(arg0::FieldOrbit)
    return FieldCartesianOrbit((FieldOrbit,), arg0)
end

function FieldCartesianOrbit(arg0::FieldPVCoordinates, arg1::Frame, arg2::FieldAbsoluteDate, arg3::RealFieldElement)
    return FieldCartesianOrbit((FieldPVCoordinates, Frame, FieldAbsoluteDate, RealFieldElement), arg0, arg1, arg2, arg3)
end

function FieldCartesianOrbit(arg0::TimeStampedFieldPVCoordinates, arg1::Frame, arg2::RealFieldElement)
    return FieldCartesianOrbit((TimeStampedFieldPVCoordinates, Frame, RealFieldElement), arg0, arg1, arg2)
end

function add_kepler_contribution(obj::FieldCartesianOrbit, arg0::PositionAngle, arg1::RealFieldElement, arg2::Vector{RealFieldElement})
    return jcall(obj, "addKeplerContribution", void, (PositionAngle, RealFieldElement, Vector{RealFieldElement}), arg0, arg1, arg2)
end

function get_a(obj::FieldCartesianOrbit)
    return jcall(obj, "getA", RealFieldElement, ())
end

function get_a_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getADot", RealFieldElement, ())
end

function get_date(obj::FieldOrbit)
    return jcall(obj, "getDate", FieldAbsoluteDate, ())
end

function get_e(obj::FieldCartesianOrbit)
    return jcall(obj, "getE", RealFieldElement, ())
end

function get_e_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getEDot", RealFieldElement, ())
end

function get_equinoctial_ex(obj::FieldCartesianOrbit)
    return jcall(obj, "getEquinoctialEx", RealFieldElement, ())
end

function get_equinoctial_ex_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getEquinoctialExDot", RealFieldElement, ())
end

function get_equinoctial_ey(obj::FieldCartesianOrbit)
    return jcall(obj, "getEquinoctialEy", RealFieldElement, ())
end

function get_equinoctial_ey_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getEquinoctialEyDot", RealFieldElement, ())
end

function get_frame(obj::FieldOrbit)
    return jcall(obj, "getFrame", Frame, ())
end

function get_hx(obj::FieldCartesianOrbit)
    return jcall(obj, "getHx", RealFieldElement, ())
end

function get_hx_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getHxDot", RealFieldElement, ())
end

function get_hy(obj::FieldCartesianOrbit)
    return jcall(obj, "getHy", RealFieldElement, ())
end

function get_hy_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getHyDot", RealFieldElement, ())
end

function get_i(obj::FieldCartesianOrbit)
    return jcall(obj, "getI", RealFieldElement, ())
end

function get_i_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getIDot", RealFieldElement, ())
end

function get_jacobian_wrt_cartesian(obj::FieldOrbit, arg0::PositionAngle, arg1::Vector{Vector{RealFieldElement}})
    return jcall(obj, "getJacobianWrtCartesian", void, (PositionAngle, Vector{Vector{RealFieldElement}}), arg0, arg1)
end

function get_jacobian_wrt_parameters(obj::FieldOrbit, arg0::PositionAngle, arg1::Vector{Vector{RealFieldElement}})
    return jcall(obj, "getJacobianWrtParameters", void, (PositionAngle, Vector{Vector{RealFieldElement}}), arg0, arg1)
end

function get_keplerian_mean_motion(obj::FieldOrbit)
    return jcall(obj, "getKeplerianMeanMotion", RealFieldElement, ())
end

function get_keplerian_period(obj::FieldOrbit)
    return jcall(obj, "getKeplerianPeriod", RealFieldElement, ())
end

function get_le(obj::FieldCartesianOrbit)
    return jcall(obj, "getLE", RealFieldElement, ())
end

function get_le_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getLEDot", RealFieldElement, ())
end

function get_lm(obj::FieldCartesianOrbit)
    return jcall(obj, "getLM", RealFieldElement, ())
end

function get_lm_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getLMDot", RealFieldElement, ())
end

function get_lv(obj::FieldCartesianOrbit)
    return jcall(obj, "getLv", RealFieldElement, ())
end

function get_lv_dot(obj::FieldCartesianOrbit)
    return jcall(obj, "getLvDot", RealFieldElement, ())
end

function get_mu(obj::FieldOrbit)
    return jcall(obj, "getMu", RealFieldElement, ())
end

function get_pv_coordinates(obj::FieldOrbit)
    return jcall(obj, "getPVCoordinates", TimeStampedFieldPVCoordinates, ())
end

function get_pv_coordinates(obj::FieldOrbit, arg0::FieldAbsoluteDate, arg1::Frame)
    return jcall(obj, "getPVCoordinates", TimeStampedFieldPVCoordinates, (FieldAbsoluteDate, Frame), arg0, arg1)
end

function get_pv_coordinates(obj::FieldOrbit, arg0::Frame)
    return jcall(obj, "getPVCoordinates", TimeStampedFieldPVCoordinates, (Frame,), arg0)
end

function get_type(obj::FieldCartesianOrbit)
    return jcall(obj, "getType", OrbitType, ())
end

function has_derivatives(obj::FieldCartesianOrbit)
    return jcall(obj, "hasDerivatives", jboolean, ())
end

function interpolate(obj::FieldCartesianOrbit, arg0::FieldAbsoluteDate, arg1::Stream)
    return jcall(obj, "interpolate", FieldCartesianOrbit, (FieldAbsoluteDate, Stream), arg0, arg1)
end

function interpolate(obj::FieldTimeInterpolable, arg0::FieldAbsoluteDate, arg1::Collection)
    return jcall(obj, "interpolate", FieldTimeInterpolable, (FieldAbsoluteDate, Collection), arg0, arg1)
end

function shifted_by(obj::FieldCartesianOrbit, arg0::RealFieldElement)
    return jcall(obj, "shiftedBy", FieldCartesianOrbit, (RealFieldElement,), arg0)
end

function shifted_by(obj::FieldCartesianOrbit, arg0::jdouble)
    return jcall(obj, "shiftedBy", FieldCartesianOrbit, (jdouble,), arg0)
end

function to_orbit(obj::FieldCartesianOrbit)
    return jcall(obj, "toOrbit", CartesianOrbit, ())
end

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


"""
    abstract type Environment end

Supertype for all reinforcement learning (RL) environments. In
essence a subtype of an `Environment` is expected to behave
according to some underlying Markov Decision Process (MDP) that
only can only be interacted with in a stepwise fashion.

There are some notable conceptual differences between a subtype
of `Environment` and a subtype of `MDP`.

- While an environment is assumed to be governed by an MDP,
  this MDP is generally only partially known (emphasis on
  "known"; this doesn't imply it is only partially observable).
  The typical unknowns are the dynamics (transitions) and/or the
  reward function.

- Consequently an environment does not need to be able expose
  arbitrary state transitions. Instead, it can be stateful and
  only ever expose a sampled trajectory starting from initial
  states of its own choosing.

- Any subtype of an `MDP` can easily be converted to an
  `Environment` by keeping track of its own state. The other
  direction is not as simple.

Any subtype of `Environment` has to implement the following
functions: [`actions`](@ref), [`step!`](@ref), [`reset!`](@ref),
[`isterminal`](@ref).
"""
abstract type Environment end

"""
    step!(env::Environment, a) -> (s´, r)

Apply the given action `a` to the current state of the
environment `env` and return the next state `s´` as well as the
received award `r` caused by this transition.

Note that this function does not expect the user to provide a
state. This is because the environment is in general expected to
be stateful and thus knows its actual state. The state `s´`
observed from the environment is not required to have a causal
relationship to the next state. This does not imply that the
underlying MDP is therefor partially observed. For example the
next screen in an Atari game is not actually determined by
looking at the last 5 screens. Yet for Atari games it is still a
fair assumption that the last 5 screens correspond to a unique
game state.
"""
function step! end

"""
    reset!(env::Environment, [...]) -> s₀

Reset the given environment to an initial state and return it.
Note that individual environment may support additional
parameters.
"""
function reset! end

"""
    isterminal(env::Environment) -> Bool

Check is `env` is in a terminal state.
"""
isterminal(env::Environment) = false


using PortAudio, SampledSignals, LibSndFile
using HTTP
using WAV
using Statistics
using Plots
using Images
using PyCall
using DSP

function spectronew(val)
    fs = 16000
    audio_test = SampleBuf(val, 16000)
    n = length(audio_test.data)
    nw = n÷25
    spec = DSP.spectrogram(audio_test.data[:], nw, nw÷10; fs=fs)
    png(
        Plots.heatmap(spec.time, spec.freq, pow2db.(spec.power), xaxis = false, yaxis=false, legend = :none, ticks=false, margins = -1.2Plots.cm, size=(28.5,29)),
        "testcase",
    )
    img = load("testcase.png")
    img = imresize(img, (28,28))
    img = reshape(convert(Array{Float32}, Gray.(img)), (28, 28, 1));
    return img
end
function senddataup(data)
    batch = map(x->data[16000*(x-1)+1:16000*x] ,1:5)
    batch = map(x->spectronew(x), batch)
    for i in batch
        r  = HTTP.post("http://localhost:8003/adddata", [], string(i))
        # println(String(r.body))
    end
end

function sendfileup(filename)
    splited = split(filename, "/")
    label = splited[end-1]
    label2 = splited[end]
    img =  convert(Array{Float32}, Gray.(load("./datasetspectro2/"*label*"#"*label2)))
    println(size(img))
    batch = map(x->img , 1:5)
    #batch = map(x->spectronew(x), batch)
    for i in batch
        r  = HTTP.post("http://localhost:8003/adddata", [], string(i))
        # println(String(r.body))
    end
end

function sendsingledataup(data)
   r  = HTTP.post("http://localhost:8003/adddata", [], string(spectronew(data)))
   # println(String(r.body))
end

labeldict = Dict("left" => 0, "right" => 1, "up" => 2,  "down" => 3,""=>4)

function makeamove(mode)
    println(mode)
    py"""
    import pyautogui as autogui
    def makemode(mode):
        if mode == 3:
            autogui.press('left')
        elif mode == 4:
            autogui.press('right')
        elif mode == 5:
            autogui.press('up')
        elif mode == 6:
            autogui.press('down')
        else:
            print(mode)
            print("mode not yet made")
    """
    py"makemode"(mode)
end

py"""
import collections
import contextlib
import sys
import wave

import webrtcvad

def read_wave(path):
    with contextlib.closing(wave.open(path, 'rb')) as wf:
        num_channels = wf.getnchannels()
        assert num_channels == 1
        sample_width = wf.getsampwidth()
        assert sample_width == 2
        sample_rate = wf.getframerate()
        assert sample_rate in (8000, 16000, 32000, 48000)
        pcm_data = wf.readframes(wf.getnframes())
        return pcm_data, sample_rate

def write_wave(path, audio, sample_rate):
    with contextlib.closing(wave.open(path, 'wb')) as wf:
        wf.setnchannels(1)
        wf.setsampwidth(2)
        wf.setframerate(sample_rate)
        wf.writeframes(audio)

class Frame(object):
    def __init__(self, bytes, timestamp, duration):
        self.bytes = bytes
        self.timestamp = timestamp
        self.duration = duration

def frame_generator(frame_duration_ms, audio, sample_rate):
    n = int(sample_rate * (frame_duration_ms / 1000.0) * 2)
    offset = 0
    timestamp = 0.0
    duration = (float(n) / sample_rate) / 2.0
    while offset + n < len(audio):
        yield Frame(audio[offset:offset + n], timestamp, duration)
        timestamp += duration
        offset += n

def vad_collector(sample_rate, frame_duration_ms,
                  padding_duration_ms, vad, frames):
    num_padding_frames = int(padding_duration_ms / frame_duration_ms)
    ring_buffer = collections.deque(maxlen=num_padding_frames)
    triggered = False

    voiced_frames = []
    for frame in frames:
        is_speech = vad.is_speech(frame.bytes, sample_rate)

        sys.stdout.write('1' if is_speech else '0')
        if not triggered:
            ring_buffer.append((frame, is_speech))
            num_voiced = len([f for f, speech in ring_buffer if speech])
            if num_voiced > 0.9 * ring_buffer.maxlen:
                triggered = True
                # sys.stdout.write('+(%s)' % (ring_buffer[0][0].timestamp,))
                for f, s in ring_buffer:
                    voiced_frames.append(f)
                ring_buffer.clear()
        else:
            voiced_frames.append(frame)
            ring_buffer.append((frame, is_speech))
            num_unvoiced = len([f for f, speech in ring_buffer if not speech])
            if num_unvoiced > 0.9 * ring_buffer.maxlen:
                # sys.stdout.write('-(%s)' % (frame.timestamp + frame.duration))
                triggered = False
                yield b''.join([f.bytes for f in voiced_frames])
                ring_buffer.clear()
                voiced_frames = []
    # if triggered:
        # print("Here I arrive")
        # sys.stdout.write('-(%s)' % (frame.timestamp + frame.duration))
    #sys.stdout.write('\n')
    if voiced_frames:
        yield b''.join([f.bytes for f in voiced_frames])

def doyourvaejobplease(path):
    audio, sample_rate = read_wave(path)
    vad = webrtcvad.Vad(int(1))
    frames = frame_generator(30, audio, sample_rate)
    frames = list(frames)
    segments = vad_collector(sample_rate, 30, 300, vad, frames)
    for i, segment in enumerate(segments):
        path = './audios/chunk-%002d.wav' % (i,)
        # print(' Writing %s' % (path,))
        write_wave(path, segment, sample_rate)
"""

function dealwithmyissue(results)
	for i in results
		if sign(i[1]) == -1
			lowval =  abs(i[1])
			i[1] = 0
			i[2] = i[2] + lowval
		elseif i[2] > 88200
			highval = i[2]-88200
			i[2] = 88200
			i[1] = i[1] - highval
		end
	end
	return results
end
function findislands(arr)
	startindices = []
	finalindices = []
	for (i,val) in enumerate(arr)
		if i == 1
			continue
		elseif arr[i] == 0 && arr[i-1] == 1
			push!(finalindices, i-1)
		elseif arr[i-1] == 0 && arr[i] == 1
			push!(startindices, i)
		end
	end
	lengths = finalindices .- startindices .+1
	centeralpoints = round.(Int, (startindices .+ finalindices)/2)
	results = map(x-> [x-8000,x+7999], centeralpoints)
	results = dealwithmyissue(results)
	return results
end
using Glob
while true
    stream = PortAudioStream("default", 1, 0)
    buf = read(stream, 2s).data[:]
    # #binarymask = abs.(buf .-0.02) .> 0.1
    # #islanddetails = findislands(binarymask)
    # #interestingdata = map(x->buf[x[1]:x[2]], islanddetails)
    wavwrite(buf,  "test8final.wav", Fs=48000, nbits=16,  compression=WAVE_FORMAT_PCM)
    # mkdir("audios")
    py"doyourvaejobplease"("test8final.wav")
    close(stream)
    # y, fs = wavread("./mini_speech_commands/down/00b01445_nohash_1.wav") # works for down
    # y, fs = wavread("./mini_speech_commands/left/012c8314_nohash_0.wav") # works for left
    # y, fs = wavread("./mini_speech_commands/up/0132a06d_nohash_2.wav") # works for up
    # y, fs = wavread("./mini_speech_commands/right/ffd2ba2f_nohash_4.wav ") # works for right
    #senddataup(vec(buf.data))
    #senddataup(repeat(y, 6))
    listofwavs = glob("*.wav","./audios")
    # println(listofwavs)
    interestingdata = map(x->wavread(x)[1][:], listofwavs)
    # println(typeof(interestingdata[1]))
    for i in interestingdata
        datara = ceil(Int, length(i)/16000)
        zerosadd =  datara*16000 - length(i)
        println(zerosadd)
        zeroestobeadded = zeros(Float64, zerosadd)
        i = vcat(i, zeroestobeadded)
        batch = map(x->i[16000*(x-1)+1:16000*x] ,1:datara)
    	for j in batch
            sendsingledataup(j)
        end
    end

    # # rm("audios")
    map(x->rm(x), listofwavs)
    interestingdata = nothing
    listofwavs = nothing
    buf = nothing
    # #sendfileup("./mini_speech_commands/up/0132a06d_nohash_2.wav.png")
    # #down#00b01445_nohash_1.wav.png # for down
    # #./datasetspectro2/left#ffd2ba2f_nohash_4.wav.png" # for left
    # #./datasetspectro2/up#0132a06d_nohash_2.wav.png # for up
    # #./datasetspectro2/right#ffd2ba2f_nohash_4.wav.png for right
    r = HTTP.get("http://localhost:8003/digitreg", [])
    mode = labeldict[String(r.body)]
    makeamove(mode)
    sleep(0.1)
end


################################################################################

@testset "Sort" begin

  ################################################################################

  # load small array into hidden Markov model
  include("data/smallArray.jl")

  # load small array on model data
  for ι ∈ 1:10
    push!(hmm.data, x[ι, :])
  end

  # sort model data
  HiddenMarkovModelReaders.sortHMM!(hmm)

  # testing
  @test hmm.data == [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [6898.439972, 1674.825584, 2142.319328, 923.535397, 562.254679, 542.897036, 544.597461, 447.280304, 338.127405, 249.294769, 331.028492, 335.807143, 421.090866, 236.912484, 241.648853, 423.595156], [8597.829269, 1833.756462, 1628.936111, 1163.014809, 697.425591, 718.742764, 431.683627, 524.160545, 522.474073, 404.779799, 286.859573, 291.428293, 371.560964, 297.910174, 426.403369, 289.336908], [8926.864639, 2024.670303, 1371.395947, 1277.803127, 978.470009, 456.044172, 580.524843, 546.832397, 342.843889, 706.63784, 408.624054, 324.00528, 337.768628, 331.042826, 444.800913, 449.033676], [9221.546188, 1511.961166, 1357.589783, 1006.436895, 532.545324, 605.403816, 468.057093, 388.927434, 439.460726, 281.329487, 469.301092, 309.418703, 340.09914, 381.352773, 227.883408, 402.3395], [9112.851349, 1626.732288, 1602.905491, 1432.479465, 663.49947, 804.251534, 739.028986, 512.910912, 397.43222, 389.500046, 407.156647, 517.501736, 347.87634, 241.200329, 416.171814, 341.232077], [9246.340912, 1466.947083, 1906.052313, 1209.67667, 606.815348, 520.541983, 571.563523, 557.675089, 473.436043, 402.273785, 345.846071, 505.062222, 376.109373, 439.998122, 406.717417, 398.954038], [10628.121246, 1498.386951, 1187.115562, 925.775265, 694.86956, 257.954501, 592.700003, 492.023813, 452.887017, 563.844071, 473.371099, 296.826092, 479.452673, 288.918503, 369.519865, 430.178178], [10362.170502, 2234.273811, 2244.926224, 647.264278, 655.394653, 571.437164, 662.987797, 486.684425, 395.458192, 392.33078, 349.11353, 403.472491, 409.692116, 500.905411, 547.84833, 789.415714], [11321.235947, 1362.566475, 944.705898, 696.605307, 411.700334, 517.204907, 383.636882, 489.580448, 301.713282, 371.661125, 476.757751, 266.741815, 154.978138, 412.402929, 279.845093, 382.712366], [11443.098831, 1500.468246, 1641.78447, 929.626314, 695.049788, 473.568182, 568.370424, 542.348984, 449.226664, 351.930719, 440.918041, 346.611481, 539.18642, 325.506171, 361.940255, 451.27547]]
  @test hmm.model == [[0.0; repeat([-1.0], size(x, 1))]]

  ################################################################################

end

################################################################################


import ..Topologies
using ..RecursiveApply

function dss_transform(arg, local_geometry)
    RecursiveApply.rmap(arg) do x
        dss_transform(x, local_geometry)
    end
end
dss_transform(arg::Number, local_geometry) = arg
dss_transform(
    arg::Geometry.AxisTensor{T, N, <:Tuple{Vararg{Geometry.CartesianAxis}}},
    local_geometry::Geometry.LocalGeometry,
) where {T, N} = arg
dss_transform(
    arg::Geometry.CartesianVector,
    local_geometry::Geometry.LocalGeometry,
) where {T, N} = arg
dss_transform(
    arg::Geometry.AxisTensor{T, N, <:Tuple{Vararg{Geometry.LocalAxis}}},
    local_geometry::Geometry.LocalGeometry,
) where {T, N} = arg
dss_transform(
    arg::Geometry.LocalVector,
    local_geometry::Geometry.LocalGeometry,
) where {T, N} = arg

function dss_transform(
    arg::Geometry.AxisVector,
    local_geometry::Geometry.LocalGeometry,
)
    ax = axes(local_geometry.∂x∂ξ, 1)
    axfrom = axes(arg, 1)
    # TODO: make this consistent
    if ax isa Geometry.UVAxis && (
        axfrom isa Geometry.Covariant3Axis ||
        axfrom isa Geometry.Contravariant3Axis
    )
        return arg
    end
    if ax isa Geometry.UWAxis && (
        axfrom isa Geometry.Covariant2Axis ||
        axfrom isa Geometry.Contravariant2Axis
    )
        return arg
    end
    Geometry.transform(ax, arg, local_geometry)
end

function dss_untransform(refarg, targ, local_geometry)
    RecursiveApply.rmap(refarg, targ) do rx, tx
        dss_untransform(rx, tx, local_geometry)
    end
end
dss_untransform(refarg::Number, targ, local_geometry) = targ
dss_untransform(
    refarg::T,
    targ::T,
    local_geometry::Geometry.LocalGeometry,
) where {T <: Geometry.AxisTensor} = targ
function dss_untransform(
    refarg::Geometry.AxisTensor,
    targ::Geometry.AxisTensor,
    local_geometry::Geometry.LocalGeometry,
)
    ax = axes(refarg, 1)
    Geometry.transform(ax, targ, local_geometry)
end

function dss_1d!(
    dest,
    src,
    local_geometry_data,
    htopology::Topologies.AbstractTopology,
    Nq::Int,
    Nv::Int = 1,
)
    idx1 = CartesianIndex(1, 1, 1, 1, 1)
    idx2 = CartesianIndex(Nq, 1, 1, 1, 1)
    for (elem1, face1, elem2, face2, reversed) in
        Topologies.interior_faces(htopology)
        for level in 1:Nv
            @assert face1 == 1 && face2 == 2 && !reversed
            local_geometry_slab1 = slab(local_geometry_data, level, elem1)
            src_slab1 = slab(src, level, elem1)
            local_geometry_slab2 = slab(local_geometry_data, level, elem2)
            src_slab2 = slab(src, level, elem2)
            val =
                dss_transform(src_slab1[idx1], local_geometry_slab1[idx1]) ⊞
                dss_transform(src_slab2[idx2], local_geometry_slab2[idx2])

            dest_slab1 = slab(dest, level, elem1)
            dest_slab2 = slab(dest, level, elem2)

            dest_slab1[idx1] = dss_untransform(
                dest_slab1[idx1],
                val,
                local_geometry_slab1[idx1],
            )
            dest_slab2[idx2] = dss_untransform(
                dest_slab2[idx2],
                val,
                local_geometry_slab2[idx2],
            )
        end
    end
    return dest
end

"""
    horizontal_dss!(dest, src, topology, Nq)

Apply horizontal direct stiffness summation (DSS) to `src`, storing the result in `dest`.
"""
function dss_2d!(
    dest,
    src,
    local_geometry_data,
    topology::Topologies.AbstractTopology,
    Nq::Int,
    Nv::Int = 1,
)
    # TODO: generalize to extruded domains by returning a cartesian index?
    # iterate over the interior faces for each element of the mesh
    for (elem1, face1, elem2, face2, reversed) in
        Topologies.interior_faces(topology)
        for level in 1:Nv
            # iterate over non-vertex nodes
            src_slab1 = slab(src, level, elem1)
            src_slab2 = slab(src, level, elem2)
            local_geometry_slab1 = slab(local_geometry_data, level, elem1)
            local_geometry_slab2 = slab(local_geometry_data, level, elem2)
            dest_slab1 = slab(dest, level, elem1)
            dest_slab2 = slab(dest, level, elem2)
            for q in 2:(Nq - 1)
                i1, j1 = Topologies.face_node_index(face1, Nq, q, false)
                i2, j2 = Topologies.face_node_index(face2, Nq, q, reversed)
                val =
                    dss_transform(
                        src_slab1[i1, j1],
                        local_geometry_slab1[i1, j1],
                    ) ⊞ dss_transform(
                        src_slab2[i2, j2],
                        local_geometry_slab2[i2, j2],
                    )

                dest_slab1[i1, j1] = dss_untransform(
                    dest_slab1[i1, j1],
                    val,
                    local_geometry_slab1[i1, j1],
                )
                dest_slab2[i2, j2] = dss_untransform(
                    dest_slab2[i2, j2],
                    val,
                    local_geometry_slab2[i2, j2],
                )
            end
        end
    end

    # iterate over all vertices
    for vertex in Topologies.vertices(topology)
        # for each level
        for level in 1:Nv
            # gather: compute sum over shared vertices
            sum_data = mapreduce(⊞, vertex) do (elem, vertex_num)
                i, j = Topologies.vertex_node_index(vertex_num, Nq)
                local_geometry_slab = slab(local_geometry_data, level, elem)
                src_slab = slab(src, level, elem)
                dss_transform(src_slab[i, j], local_geometry_slab[i, j])
            end

            # scatter: assign sum to shared vertices
            for (elem, vertex_num) in vertex
                dest_slab = slab(dest, level, elem)
                i, j = Topologies.vertex_node_index(vertex_num, Nq)
                local_geometry_slab = slab(local_geometry_data, level, elem)
                dest_slab[i, j] = dss_untransform(
                    dest_slab[i, j],
                    sum_data,
                    local_geometry_slab[i, j],
                )
            end
        end
    end
    return dest
end

function horizontal_dss!(dest, src, space::AbstractSpace)
    if space isa ExtrudedFiniteDifferenceSpace
        Nv = nlevels(space)
        hspace = space.horizontal_space
    else
        Nv = 1
        hspace = space
    end
    htopology = hspace.topology
    Nq = Quadratures.degrees_of_freedom(hspace.quadrature_style)::Int
    if hspace isa SpectralElementSpace1D
        dss_1d!(dest, src, local_geometry_data(space), htopology, Nq, Nv)
    elseif hspace isa SpectralElementSpace2D
        dss_2d!(dest, src, local_geometry_data(space), htopology, Nq, Nv)
    end
end

horizontal_dss!(data, space::AbstractSpace) =
    horizontal_dss!(data, data, space::AbstractSpace)

weighted_dss!(dest, src, space::AbstractSpace) = horizontal_dss!(
    dest,
    Base.Broadcast.broadcasted(⊠, src, space.dss_weights),
    space,
)

weighted_dss!(dest, src, space::ExtrudedFiniteDifferenceSpace) =
    horizontal_dss!(
        dest,
        Base.Broadcast.broadcasted(⊠, src, space.horizontal_space.dss_weights),
        space,
    )

weighted_dss!(data, space::AbstractSpace) = weighted_dss!(data, data, space)


module Flux

# Zero Flux Given

using Base: tail
using Zygote, MacroTools, Juno, Reexport, Statistics, Random
using MacroTools: @forward
@reexport using NNlib
using Zygote: Params, @adjoint, gradient, pullback
export gradient

export Chain, Dense, Maxout, RNN, LSTM, GRU, Conv, CrossCor, ConvTranspose, MaxPool, MeanPool,
       DepthwiseConv, Dropout, AlphaDropout, LayerNorm, BatchNorm, InstanceNorm, GroupNorm,
       SkipConnection, params, fmap, cpu, gpu, f32, f64

include("optimise/Optimise.jl")
using .Optimise
using .Optimise: @epochs
export SGD, Descent, ADAM, Momentum, Nesterov, RMSProp,
  ADAGrad, AdaMax, ADADelta, AMSGrad, NADAM,
  ADAMW, RADAM, InvDecay, ExpDecay, WeightDecay

allow_cuda() = parse(Bool, get(ENV, "FLUX_USE_CUDA", "true"))
const consider_cuda = allow_cuda()

using CUDAapi
const use_cuda = consider_cuda && has_cuda()
if use_cuda
  try
    using CuArrays
  catch
    @error "CUDA is installed, but CuArrays.jl fails to load. Please fix the issue, or load Flux with FLUX_USE_CUDA=false."
    rethrow()
  end
end

include("utils.jl")
include("onehot.jl")
include("functor.jl")

include("layers/stateless.jl")
include("layers/basic.jl")
include("layers/conv.jl")
include("layers/recurrent.jl")
include("layers/normalise.jl")

include("data/Data.jl")

include("deprecations.jl")

if use_cuda
  include("cuda/cuda.jl")
end

function __init__()
  # check if the GPU usage conditions that are baked in the precompilation image
  # match the current situation, and force a recompilation if not.
  if (allow_cuda() != consider_cuda) || (consider_cuda && has_cuda() != use_cuda)
      cachefile = if VERSION >= v"1.3-"
          Base.compilecache_path(Base.PkgId(Flux))
      else
          abspath(DEPOT_PATH[1], Base.cache_file_entry(Base.PkgId(Flux)))
      end
      rm(cachefile)
      error("Your set-up changed, and Flux.jl needs to be reconfigured. Please load the package again.")
  end
end

end # module


#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct AVRTypeI <: AVR
        Ka::Float64
        Ke::Float64
        Kf::Float64
        Ta::Float64
        Te::Float64
        Tf::Float64
        Tr::Float64
        Vr_max::Float64
        Vr_min::Float64
        Ae::Float64
        Be::Float64
        V_ref::Float64
        ext::Dict{String, Any}
        states::Vector{Symbol}
        n_states::Int64
        internal::InfrastructureSystemsInternal
    end

Parameters of an Automatic Voltage Regulator Type I - Resembles IEEE Type DC1

# Arguments
- `Ka::Float64`: Amplifier Gain, validation range: (0, nothing)
- `Ke::Float64`: Field circuit integral deviation, validation range: (0, nothing)
- `Kf::Float64`: Stabilizer Gain in s * pu/pu, validation range: (0, nothing)
- `Ta::Float64`: Amplifier Time Constant in s, validation range: (0, nothing)
- `Te::Float64`: Field Circuit Time Constant in s, validation range: (0, nothing)
- `Tf::Float64`: Stabilizer Time Constant in s, validation range: (0, nothing)
- `Tr::Float64`: Voltage Measurement Time Constant in s, validation range: (0, nothing)
- `Vr_max::Float64`: Maximum regulator voltage in pu, validation range: (0, nothing)
- `Vr_min::Float64`: Minimum regulator voltage in pu, validation range: (0, nothing)
- `Ae::Float64`: 1st ceiling coefficient, validation range: (0, nothing)
- `Be::Float64`: 2nd ceiling coefficient, validation range: (0, nothing)
- `V_ref::Float64`: Reference Voltage Set-point, validation range: (0, nothing)
- `ext::Dict{String, Any}`
- `states::Vector{Symbol}`
- `n_states::Int64`
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct AVRTypeI <: AVR
    "Amplifier Gain"
    Ka::Float64
    "Field circuit integral deviation"
    Ke::Float64
    "Stabilizer Gain in s * pu/pu"
    Kf::Float64
    "Amplifier Time Constant in s"
    Ta::Float64
    "Field Circuit Time Constant in s"
    Te::Float64
    "Stabilizer Time Constant in s"
    Tf::Float64
    "Voltage Measurement Time Constant in s"
    Tr::Float64
    "Maximum regulator voltage in pu"
    Vr_max::Float64
    "Minimum regulator voltage in pu"
    Vr_min::Float64
    "1st ceiling coefficient"
    Ae::Float64
    "2nd ceiling coefficient"
    Be::Float64
    "Reference Voltage Set-point"
    V_ref::Float64
    ext::Dict{String, Any}
    states::Vector{Symbol}
    n_states::Int64
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function AVRTypeI(Ka, Ke, Kf, Ta, Te, Tf, Tr, Vr_max, Vr_min, Ae, Be, V_ref=1.0, ext=Dict{String, Any}(), )
    AVRTypeI(Ka, Ke, Kf, Ta, Te, Tf, Tr, Vr_max, Vr_min, Ae, Be, V_ref, ext, [:Vf, :Vr1, :Vr2, :Vm], 4, InfrastructureSystemsInternal(), )
end

function AVRTypeI(; Ka, Ke, Kf, Ta, Te, Tf, Tr, Vr_max, Vr_min, Ae, Be, V_ref=1.0, ext=Dict{String, Any}(), )
    AVRTypeI(Ka, Ke, Kf, Ta, Te, Tf, Tr, Vr_max, Vr_min, Ae, Be, V_ref, ext, )
end

# Constructor for demo purposes; non-functional.
function AVRTypeI(::Nothing)
    AVRTypeI(;
        Ka=0,
        Ke=0,
        Kf=0,
        Ta=0,
        Te=0,
        Tf=0,
        Tr=0,
        Vr_max=0,
        Vr_min=0,
        Ae=0,
        Be=0,
        V_ref=0,
        ext=Dict{String, Any}(),
    )
end

"""Get AVRTypeI Ka."""
get_Ka(value::AVRTypeI) = value.Ka
"""Get AVRTypeI Ke."""
get_Ke(value::AVRTypeI) = value.Ke
"""Get AVRTypeI Kf."""
get_Kf(value::AVRTypeI) = value.Kf
"""Get AVRTypeI Ta."""
get_Ta(value::AVRTypeI) = value.Ta
"""Get AVRTypeI Te."""
get_Te(value::AVRTypeI) = value.Te
"""Get AVRTypeI Tf."""
get_Tf(value::AVRTypeI) = value.Tf
"""Get AVRTypeI Tr."""
get_Tr(value::AVRTypeI) = value.Tr
"""Get AVRTypeI Vr_max."""
get_Vr_max(value::AVRTypeI) = value.Vr_max
"""Get AVRTypeI Vr_min."""
get_Vr_min(value::AVRTypeI) = value.Vr_min
"""Get AVRTypeI Ae."""
get_Ae(value::AVRTypeI) = value.Ae
"""Get AVRTypeI Be."""
get_Be(value::AVRTypeI) = value.Be
"""Get AVRTypeI V_ref."""
get_V_ref(value::AVRTypeI) = value.V_ref
"""Get AVRTypeI ext."""
get_ext(value::AVRTypeI) = value.ext
"""Get AVRTypeI states."""
get_states(value::AVRTypeI) = value.states
"""Get AVRTypeI n_states."""
get_n_states(value::AVRTypeI) = value.n_states
"""Get AVRTypeI internal."""
get_internal(value::AVRTypeI) = value.internal

"""Set AVRTypeI Ka."""
set_Ka!(value::AVRTypeI, val::Float64) = value.Ka = val
"""Set AVRTypeI Ke."""
set_Ke!(value::AVRTypeI, val::Float64) = value.Ke = val
"""Set AVRTypeI Kf."""
set_Kf!(value::AVRTypeI, val::Float64) = value.Kf = val
"""Set AVRTypeI Ta."""
set_Ta!(value::AVRTypeI, val::Float64) = value.Ta = val
"""Set AVRTypeI Te."""
set_Te!(value::AVRTypeI, val::Float64) = value.Te = val
"""Set AVRTypeI Tf."""
set_Tf!(value::AVRTypeI, val::Float64) = value.Tf = val
"""Set AVRTypeI Tr."""
set_Tr!(value::AVRTypeI, val::Float64) = value.Tr = val
"""Set AVRTypeI Vr_max."""
set_Vr_max!(value::AVRTypeI, val::Float64) = value.Vr_max = val
"""Set AVRTypeI Vr_min."""
set_Vr_min!(value::AVRTypeI, val::Float64) = value.Vr_min = val
"""Set AVRTypeI Ae."""
set_Ae!(value::AVRTypeI, val::Float64) = value.Ae = val
"""Set AVRTypeI Be."""
set_Be!(value::AVRTypeI, val::Float64) = value.Be = val
"""Set AVRTypeI V_ref."""
set_V_ref!(value::AVRTypeI, val::Float64) = value.V_ref = val
"""Set AVRTypeI ext."""
set_ext!(value::AVRTypeI, val::Dict{String, Any}) = value.ext = val
"""Set AVRTypeI states."""
set_states!(value::AVRTypeI, val::Vector{Symbol}) = value.states = val
"""Set AVRTypeI n_states."""
set_n_states!(value::AVRTypeI, val::Int64) = value.n_states = val
"""Set AVRTypeI internal."""
set_internal!(value::AVRTypeI, val::InfrastructureSystemsInternal) = value.internal = val


# Copyright 2016, Wen-chih Chen, Yueh-shan Chung
# JuMP4DEA
# A Modeling Language Package for Solving Data Envelopment Analysis Problems

module JuMP4DEA

# An algebraic modeling language for Julia
import JuMP
# Solver-independent functions and low-level interface for Mathematical Programming
import MathProgBase

# function
export solveDEA

# solving dea problems by a smart algorithm
# it is computationally efficient and can solve large scale DEA problems via any solvers (even with LP size limitation)
include("solveDEA.jl")

end


"""
    get_setup(SetupType)

Get a preconfigured setup instance of type `SetupType`.
"""
function get_setup end

get_setup(S::Type{PlateSetup{T}}) where {T} = S(;
    name = "plates/someplates",
    width = 20.0,
    depth = 20.0,
    heights = fill(5.0, 4),
    bend = 0.0,
    twist = 0.0,
    # refinement = 0.5,
)

get_setup(S::Type{CylinderSetup{T}}) where {T} = S(;
    name = "cylinders/somecylinders",
    ncell = 1,
    rmin = 2.0,
    rmax = 6.0,
    dmin = 0.2,
    dmax = 0.3,
    height = 1.0,
    bend = 0.0,
    twist = 0.0,
    include_in = true,
    in_ratio = 0.6,
    ecs_shape = :convex_hull,
    ecs_ratio = 0.5,
    # refinement = 0.5,
)

get_setup(S::Type{SphereSetup{T}}) where {T} = S(;
    name = "spheres/somespheres",
    ncell = 4,
    rmin = 3.0,
    rmax = 5.0,
    dmin = 0.2,
    dmax = 0.3,
    include_in = false,
    in_ratio = 0.7,
    ecs_shape = :convex_hull,
    ecs_ratio = 0.3,
    # refinement = 0.5,
)

get_setup(S::Type{NeuronSetup{T}}) where {T} = S(;
    name = "neuron",
    ecs_shape = :no_ecs,
    ecs_ratio = 0.3,
    # refinement = 0.5,
)

"""
    get_coeffs(setup)

Get preconfigured compartment coefficients for `setup`.
"""
function get_coeffs end

get_coeffs(setup::PlateSetup) = coefficients(
    setup;
    D = [0.002 * I(3) for _ = 1:length(setup.heights)],
    T₂ = [Inf for _ = 1:length(setup.heights)],
    ρ = [1.0 for _ = 1:length(setup.heights)],
    κ = (;
        interfaces = [1e-4 for _ = 1:length(setup.heights)-1],
        boundaries = [0.0 for _ = 1:length(setup.heights)],
    ),
    γ = 2.67513e-4,
)

get_coeffs(setup::CylinderSetup) = coefficients(
    setup;
    D = (; in = 0.002 * I(3), out = 0.002 * I(3), ecs = 0.002 * I(3)),
    T₂ = (; in = Inf, out = Inf, ecs = Inf),
    ρ = (; in = 1.0, out = 1.0, ecs = 1.0),
    κ = (; in_out = 1e-4, out_ecs = 1e-4, in = 0.0, out = 0.0, ecs = 0.0),
    γ = 2.67513e-4,
)

get_coeffs(setup::SphereSetup) = coefficients(
    setup;
    D = (; in = 0.002 * I(3), out = 0.002 * I(3), ecs = 0.002 * I(3)),
    T₂ = (; in = Inf, out = Inf, ecs = Inf),
    ρ = (; in = 1.0, out = 1.0, ecs = 1.0),
    κ = (; in_out = 1e-4, out_ecs = 1e-4, in = 0.0, out = 0.0, ecs = 0.0),
    γ = 2.67513e-4,
)

get_coeffs(setup::NeuronSetup) = coefficients(
    setup;
    D = (; neuron = 0.002 * I(3), ecs = 0.002 * I(3)),
    T₂ = (; neuron = Inf, ecs = Inf),
    ρ = (; n = 1.0, neuron = 1.0, ecs = 1.0),
    κ = (; neuron_ecs = 1e-4, neuron = 0.0, ecs = 0.0),
    γ = 2.67513e-4,
)


function download_dep(orig, dest, hash)
    dest_file = joinpath("data", dest)
    if isfile(dest_file)
        dest_hash = open(dest_file, "r") do f
            bytes2hex(sha256(f))
        end
        if dest_hash == hash
            return nothing
        end
    end
    mkpath("data")
    download(orig, dest_file)
    return nothing
end

@testset "reproject-core" begin
    download_dep("https://astropy.stsci.edu/data/galactic_center/gc_2mass_k.fits", "gc_2mass_k.fits",
                 "763ef344df3ac8fa80ff46f00ca1ec59946ca3f99502562d6fcfb73320b1cec3")
    download_dep("https://astropy.stsci.edu/data/galactic_center/gc_msx_e.fits", "gc_msx_e.fits",
                 "3687fb3763911825f981e74b6a9b82c0e618f7e592b1e0cb17e2c63164e28cd6")

    imgin = FITS(joinpath("data", "gc_msx_e.fits"))    # project this
    imgout = FITS(joinpath("data", "gc_2mass_k.fits")) # into this coordinate

    hdu1 = astropy.io.fits.open(joinpath("data", "gc_2mass_k.fits"))[1]
    hdu2 = astropy.io.fits.open(joinpath("data", "gc_msx_e.fits"))[1]

    @test isapprox(reproject(imgin, imgout, order = 0)[1]', rp.reproject_interp(hdu2, hdu1.header, order = 0)[1], nans = true, rtol = 1e-7)
    @test isapprox(reproject(imgout, imgin, order = 0)[1]', rp.reproject_interp(hdu1, hdu2.header, order = 0)[1], nans = true, rtol = 1e-6)
    @test isapprox(reproject(imgin, imgout, order = 1)[1]', rp.reproject_interp(hdu2, hdu1.header, order = 1)[1], nans = true, rtol = 1e-7)
    @test isapprox(reproject(imgin, imgout, order = 2)[1]', rp.reproject_interp(hdu2, hdu1.header, order = 2)[1], nans = true, rtol = 6e-2)
    @test isapprox(reproject(imgin[1], imgout[1], shape_out = (1000,1000))[1]',
            rp.reproject_interp(hdu2, astropy.wcs.WCS(hdu1.header), shape_out = (1000,1000))[1], nans = true, rtol = 1e-7)

    wcs = WCSTransform(2; ctype = ["RA---AIR", "DEC--AIR"], radesys = "UNK")
    @test_throws ArgumentError reproject(imgin, wcs, shape_out = (100,100))

    fname = tempname() * ".fits"
    f = FITS(fname, "w")
    inhdr = FITSHeader(["CTYPE1", "CTYPE2", "RADESYS", "FLTKEY", "INTKEY", "BOOLKEY", "STRKEY", "COMMENT",
                        "HISTORY"],
                       ["RA---TAN", "DEC--TAN", "UNK", 1.0, 1, true, "string value", nothing, nothing],
                       ["",
                        "",
                        "",
                        "floating point keyword",
                        "",
                        "boolean keyword",
                        "string value",
                        "this is a comment",
                        "this is a history"])

    indata = reshape(Float32[1:100;], 5, 20)
    write(f, indata; header=inhdr)
    @test_throws ArgumentError reproject(f[1], imgin, shape_out = (100,100))
    close(f)
end


struct Entry
    plottype::PlotFunc
    mappings::Arguments
    attributes::Dict{Symbol, Any}
end

Entry(plottype::PlotFunc=Any, mappings=arguments(); attributes...) =
    Entry(plottype, mappings, Dict{Symbol, Any}(attributes))

Entry(mappings::Arguments; attributes...) = Entry(Any, mappings; attributes...)

function separate!(entry::Entry)
    discrete, _ = separate!(entry.mappings)
    return discrete => entry
end

function recombine!(discrete, entry::Entry)
    return Entry(
        entry.plottype,
        recombine!(discrete, entry.mappings),
        entry.attributes
    )
end

struct Entries
    entries::Vector{Entry}
    scales::Arguments
    labels::Arguments
end

Entries() = Entries(Entry[], arguments(), arguments())

function compute_axes_grid(fig, e::Entries; axis=NamedTuple())

    rowcol = (:row, :col)

    layout_scale, scales... = map((:layout, rowcol...)) do sym
        return get(e.scales, sym, nothing)
    end

    grid_size = map(scales, (first, last)) do scale, f
        isnothing(scale) || return maximum(scale.plot)
        isnothing(layout_scale) || return maximum(f, layout_scale.plot)
        return 1
    end

    axes_grid = map(CartesianIndices(grid_size)) do c
        type = get(axis, :type, Axis)
        options = Base.structdiff(axis, (; type))
        ax = type(fig[Tuple(c)...]; options...)
        return AxisEntries(ax, Entry[], e.scales, e.labels)
    end

    for entry in e.entries
        rows, cols = map(rowcol, scales, (first, last)) do sym, scale, f
            v = get(entry.mappings, sym, nothing)
            layout_v = get(entry.mappings, :layout, nothing)
            # without layout info, plot on all axes
            # all values in `v` and `layout_v` are equal
            isnothing(v) || return rescale(v[1:1], scale)
            isnothing(layout_v) || return map(f, rescale(layout_v[1:1], layout_scale))
            return 1:f(grid_size)
        end
        for i in rows, j in cols
            ae = axes_grid[i, j]
            push!(ae.entries, entry)
        end
    end

    return axes_grid

end

function AbstractPlotting.plot(entries::Entries; axis=NamedTuple(), figure=NamedTuple())
    fig = Figure(; figure...)
    grid = plot!(fig, entries; axis)
    return FigureGrid(fig, grid)
end

function AbstractPlotting.plot!(fig, entries::Entries; axis=NamedTuple())
    axes_grid = compute_axes_grid(fig, entries; axis)
    foreach(plot!, axes_grid)
    return axes_grid
end

"""
    AxisEntries(axis::Union{Axis, Nothing}, entries::Vector{Entry}, labels, scales)

Define all ingredients to make plots on an axis.
Each scale can be either a `CategoricalScale` (for discrete collections), such as
`CategoricalScale(["a", "b"], ["red", "blue"])`, or a function,
such as `log10`. Other scales may be supported in the future.
"""
struct AxisEntries
    axis::Union{Axis, Axis3}
    entries::Vector{Entry}
    scales::Arguments
    labels::Arguments
end

AbstractPlotting.Axis(ae::AxisEntries) = ae.axis
Entries(ae::AxisEntries) = Entries(ae.entries, ae.labels, ae.scales)

function prefix(i::Int, sym::Symbol)
    var = (:x, :y, :z)[i]
    return Symbol(var, sym)
end

# Slightly complex machinery to recombine stacked barplots
function mustbemerged(e::Entry)
    isbarplot = e.plottype <: BarPlot
    hasstack = :stack in keys(e.mappings.named) || :stack in keys(e.attributes)
    return isbarplot && hasstack
end

# Combine both entries as a unique entry with longer data
function stack!(e1::Entry, e2::Entry)
    p1, p2 = e1.plottype, e2.plottype
    m1, m2 = e1.mappings, e2.mappings
    a1, a2 = e1.attributes, e2.attributes
    l1, l2 = length(m1[1]), length(m2[1])
    assert_equal(p1, p2)
    for (k, v) in pairs(a1)
        assert_equal(v, a2[k])
    end
    mergewith!(m1, m2) do v1, v2
        long1 = size(v1) == () ? fill(v1[], l1) : v1
        long2 = size(v2) == () ? fill(v2[], l2) : v2
        return vcat(long1, long2)
    end
    return e1
end

function combine(entries::AbstractVector{Entry})
    combinedentries = Entry[]
    for entry in entries
        idx = findfirst(mustbemerged, combinedentries)
        if !isnothing(idx) && mustbemerged(entry)
            stack!(combinedentries[idx], entry)
        else
            push!(combinedentries, entry)
        end
    end
    return combinedentries
end

function AbstractPlotting.plot!(ae::AxisEntries)
    axis, entries, labels, scales = ae.axis, ae.entries, ae.labels, ae.scales
    for entry in combine(entries)
        plottype, mappings, attributes = entry.plottype, entry.mappings, entry.attributes
        trace = map(unwrap∘rescale, mappings, scales)
        positional, named = trace.positional, trace.named
        merge!(named, attributes)

        # Remove layout info
        for sym in [:col, :row, :layout]
            pop!(named, sym, nothing)
        end

        # Implement defaults
        for (key, val) in pairs(default_styles())
            get!(named, key, val)
        end

        # Set dodging and colorrange information
        dodge, colorscale = get(scales, :dodge, nothing), get(scales, :color, nothing)
        isa(dodge, CategoricalScale) && (named[:n_dodge] = maximum(dodge.plot))
        isa(colorscale, ContinuousScale) && (named[:colorrange] = colorscale.extrema)

        # Implement alpha transparency
        alpha = pop!(named, :alpha, nothing)
        color = get(named, :color, nothing)
        color isa Color && alpha isa Number && (named[:color] = (color, alpha))

        plot!(plottype, axis, positional...; named...)
    end
    # TODO: support log colorscale
    ndims = isaxis2d(ae) ? 2 : 3
    for i in 1:ndims
        label, scale = get(labels, i, nothing), get(scales, i, nothing)
        any(isnothing, (label, scale)) && continue
        axislabel, axisticks, axisscale = prefix.(i, (:label, :ticks, :scale))
        getproperty(axis, axisticks)[] = ticks(scale)
        getproperty(axis, axislabel)[] = string(label)
    end
    return axis
end

struct FigureGrid
    figure::Figure
    grid::Matrix{AxisEntries}
end

Base.show(io::IO, fg::FigureGrid) = show(io, fg.figure)
Base.show(io::IO, m::MIME, fg::FigureGrid) = show(io, m, fg.figure)
Base.show(io::IO, ::MIME"text/plain", fg::FigureGrid) = print(io, "FigureGrid()")

Base.showable(mime::MIME{M}, fg::FigureGrid) where {M} = showable(mime, fg.figure)

Base.display(fg::FigureGrid) = display(fg.figure)

function FileIO.save(filename::String, fg::FigureGrid; kwargs...)
    return FileIO.save(FileIO.query(filename), fg; kwargs...)
end

function FileIO.save(file::FileIO.Formatted, fg::FigureGrid; kwargs...)
    return FileIO.save(file, fg.figure; kwargs...)
end

to_tuple(fg) = (fg.figure, fg.grid)

Base.iterate(fg::FigureGrid) = iterate(to_tuple(fg))
Base.iterate(fg::FigureGrid, i) = iterate(to_tuple(fg), i)


using .Flux

import Base: convert
import IncrementalInference: getSample

export MixtureFluxPose2Pose2, PackedMixtureFluxPose2Pose2

struct MixtureFluxPose2Pose2{F <: FunctorInferenceType} <: AbstractRelativeRoots
  Zij::F
  # delta time between variables
  DT::Ref{Float64}
end

mutable struct PackedMixtureFluxPose2Pose2 <: PackedInferenceType
  packedZij::PackedMixture
  DT::Float64
  specialSampler::String
end

function IIF.getSample(cfo::CalcFactor{<:MixtureFluxPose2Pose2}, N::Int=1)
  #
  nfb = cfo.factor
  fmd = cfo.metadata

  # only calculate once after default value of 0
  if nfb.DT[] == 0
    # cache the time difference estimate
    nfb.DT[] = (getTimestamp(fmd.fullvariables[2]) - getTimestamp(fmd.fullvariables[1])).value * 1e-3
  end

  cf_ = CalcFactor( cfo.factor.Zij, cfo.metadata, 0, length(cfo._legacyMeas), cfo._legacyMeas, cfo._legacyParams)

  smpl = getSample(cf_, N)[1]

  return (smpl, cf_.factor.labels)

end

function MixtureFluxPose2Pose2( fluxmodels::AbstractVector,
                                inputDims::NTuple,
                                data::AbstractArray,
                                outputDims::NTuple,
                                otherComponents::AbstractVector{<:SamplableBelief}=[MvNormal(zeros(3),diagm(ones(3)));],
                                diversity=[0.5;0.5];
                                DT::Real=0,
                                shuffle::Bool=true,
                                serializeHollow::Bool=false )
  #
  mix = MixtureFluxModels(Pose2Pose2,
                          fluxmodels,
                          inputDims,
                          data,
                          outputDims,
                          otherComponents,
                          diversity,
                          shuffle=shuffle,
                          serializeHollow=serializeHollow)
  #
  MixtureFluxPose2Pose2(mix, Base.RefValue{Float64}(DT))
end

function Base.convert(::Union{Type{<:PackedInferenceType},Type{<:PackedMixtureFluxPose2Pose2}},
                      obj::MixtureFluxPose2Pose2 )
  #
  toFnc = typeof(obj.specialSampler)
  fncName = string(toFnc.name.module)*"."*string(toFnc.name.name)
  PackedMixtureFluxPose2Pose2(convert(PackedMixture,obj.Zij),
                              obj.DT[],
                              fncName)
end

function Base.convert(::Union{Type{<:FunctorInferenceType},Type{<:MixtureFluxPose2Pose2}},
                      obj::PackedMixtureFluxPose2Pose2 )
  #
  sFnc = split(obj.specialSampler, '.') .|> Symbol
  specFnc = getfield(getfield(Main, sFnc[1]), sFnc[2])
  PackedMixtureFluxPose2Pose2(convert(Mixture,obj.packedZij),
                                      obj.DT,
                                      specFnc)
end

function calcVelocityInterPose2!( nfb::MixtureFluxPose2Pose2,
                                  iPts::AbstractVector{<:Real},
                                  jPts::AbstractVector{<:Real} )
  #
  joyVelData = nfb.Zij.components.fluxnn.data
  # DXY[1:2,i] .= TransformUtils.R(iPts[3,i])'*DXY[1:2,i]
  joyVelData[1,3:4] .= jPts[1:2]
  joyVelData[1,3:4] .-= iPts[1:2]
  joyVelData[1,3:4] ./= nfb.DT[] # convert to velocity
  joyVelData[1,3:4] .= TransformUtils.R(iPts[3])'*joyVelData[1,3:4]
  # just set zero if something is wrong
  if isnan(joyVelData[1,3]) || isinf(abs(joyVelData[1,3])) || isnan(joyVelData[1,4]) || isinf(abs(joyVelData[1,4]))
    joyVelData[1,3:4] .= 0.0
  end
  for i in 2:size(joyVelData,1)
    joyVelData[i,3:4] .= joyVelData[1,3:4]
  end
  return nothing
end

# function calcVelocityInterPose2!( nfb::MixtureFluxPose2Pose2,
#                                   iPts::AbstractMatrix{<:Real},
#                                   jPts::AbstractMatrix{<:Real}  )
#   #
#   @assert size(jPts,2) == size(iPts,2) "calcVelocityInterPose2! can currently only evaluate equal population size variables"

#   # calculate an average velocity component
#   if nfb.DT[] == 0
#     # DXY = (@view jPts[1:2,:]) - (@view jPts[1:2,:])
#     # rotate delta position from world to local iX frame
#     for i in 1:size(iPts,2)
#       calcVelocityInterPose2!(nfb, iPts, jPts, i)
#     end
#   else
#     nfb.joyVelData[:,3:4] .= 0.0
#   end
#   nothing
# end

function (cfo::CalcFactor{<:MixtureFluxPose2Pose2})(meas1, meas2, Xi, Xj)
  #
  userdata = cfo.metadata
  nfb = cfo.factor
  fmd = cfo.metadata

  # if, use prediction sample
  if meas2 == 2
    # get live velocity estimate for each sample (nfb.joyVelData[:,3:4])
    calcVelocityInterPose2!(nfb, Xi, Xj)
    # predict odom for this sample from a specific prediction model
    # NOTE, already integrated as part of Mixture
    # meas[1][1:2,idx] = nfb.allPredModels[meas[3][idx]](nfb.joyVelData)
  end

  # calculate the error for that measurement sample as Pose2Pose2
  #TODO
  cfZij = CalcFactor( cfo.factor.Zij.mechanics, cfo.metadata, 0, length(cfo._legacyMeas), cfo._legacyMeas, cfo._legacyParams)

  return  cfZij(meas1, Xi, Xj)

end

## ============================================================================================
## Legacy support:  FluxModelsPose2Pose2
## ============================================================================================

export FluxModelsPose2Pose2

# Convenience function to help call the right constuctor
FluxModelsPose2Pose2( allModels::Vector{P},
                      jvd::D,
                      naiveModel::M,
                      naiveFrac::Real=0.5,
                      DT::Real=0.0,
                      shuffle::Bool=true ) where {P, M <: SamplableBelief, D <: AbstractMatrix} = MixtureFluxPose2Pose2(
                                        allModels,
                                        (25,4),
                                        jvd,
                                        (3,),
                                        [naiveModel;],
                                        [1-naiveFrac;naiveFrac],
                                        DT=DT,
                                        shuffle=shuffle )
#

# struct MixtureFlux{N,F<:FunctorInferenceType,S,T}
#   mixture::Mixture{N,F,S,T}
#   # special keyword field name used to invoke 'specialSampler' logic
#   specialSampler::Function
# end

# # to be overrided by user via more specialized dispatch
# # FIXME as part of
# function sampleMixtureFlux( nfb::MixtureFluxPose2Pose2,
#                             N::Int,
#                             fmd::FactorMetadata,
#                             legacy... )
#   #
#   getSample(nfb.mixture, N)
# end

# # const _IIFListTypes = Union{<:AbstractVector, <:Tuple, <:NTuple}

# function MixtureFlux( F_::FunctorInferenceType,
#                       compList::_IIFListTypes,
#                       diversity::Union{<:AbstractVector, <:NTuple, <:DiscreteNonParametric})
#   #
#   mix = Mixture(F_, compList, diversity)
#   _populateMixture(mix{N,F,S,T}) where {N,F,S,T} = MixtureFlux{N,F,S,T}(mix, sampleMixtureFlux)
#   return _populateMixture(mix)
# end

# MixtureFlux(::Type{F}, w...;kw...) where F <: FunctorInferenceType = MixtureFlux(F(LA.I), w...;kw...)

#


module ReachableControls

using ConcreteStructs: @concrete
using ControlSystems
using IntervalArithmetic
using TaylorModels

export (..), ±

include("as_real.jl")
export as_real

end


include("bgzip_file.jl")
include("readbigvcf.jl")
include("vcf2dataframe.jl")
include("get_pedfile_trios.jl")
include("str_allele_length.jl")

"""
    (t, dt, p, nd, mutCon) = STR_iht(vcffile.vcf.gz, pedigree, STR_location, iid; showhaps=false)

Read a small **phased** vcf data array and return dataframes containing 1)
transmitting chromosomes and the STR allele differences and 2) a dataframe
of all phased individuals.

Individuals in the pedigree must match those in the vcf file.  The pedigree
file may contain multiple pedigrees.

STR location format is 1:258132133

Options:
    showhaps        show haplotypes for offspring with de novo STR [false]

"""
function STR_iht(vcf, pedfile, str, iid, mutSTR, othpar; showhaps=false)

    vcf_data_array = readbigvcf(vcf)

    hap_df = vcf2dataframe(vcf_data_array)

    (trios, peds) = get_pedfile_trios(pedfile)

    ftdf = DataFrame(ped=Any[], chr=Any[], pos=Any[], parent_chr=Any[],
    parent_sex=Any[], parent_str=Any[], parent_bp=Any[], child_chr=Any[],
    child_str=Any[], child_bp=Any[], str_diff=Any[], str_bp_diff=Any[] )

    fftdf = DataFrame(ped=Any[], chr=Any[], pos=Any[], parent_chr=Any[],
    parent_sex=Any[], parent_str=Any[], parent_bp=Any[], child_chr=Any[],
    child_str=Any[], child_bp=Any[], str_diff=Any[], str_bp_diff=Any[] )

    strchr, strpos = split(str, ':')
    stridx = argmax((hap_df[:, :POS] .== strpos))    #idx of str site

    str_sizes = str_allele_length(vcf, str)

for i in peds

    pid = i; header = []; tp = []; t_all=[]; tdf=DataFrame();

    t_all = get(trios, i, 0)

    for i in t_all
        line = "-------------------------\n"

        ids = split(i, ',')

        for a in eachindex(ids)
            push!(header, Symbol(ids[a] * "p"), Symbol(ids[a] * "q"))
        end

        tdf = hap_df[ :, header]  #df of each child,father,mother trio

        c1f1 = sum(tdf[!,1] .== tdf[!,3]) / size(tdf, 1) #fraction matching
        c1f2 = sum(tdf[!,1] .== tdf[!,4]) / size(tdf, 1) #across all sites
        c1m1 = sum(tdf[!,1] .== tdf[!,5]) / size(tdf, 1)
        c1m2 = sum(tdf[!,1] .== tdf[!,6]) / size(tdf, 1)
        c2f1 = sum(tdf[!,2] .== tdf[!,3]) / size(tdf, 1)
        c2f2 = sum(tdf[!,2] .== tdf[!,4]) / size(tdf, 1)
        c2m1 = sum(tdf[!,2] .== tdf[!,5]) / size(tdf, 1)
        c2m2 = sum(tdf[!,2] .== tdf[!,6]) / size(tdf, 1)

        c1altidx = findall(tdf[!,1] .!= "0")  #alt alleles matching
        c2altidx = findall(tdf[!,2] .!= "0")  #across sites, >1 allele
        f1altidx = findall(tdf[!,3] .!= "0")  #allowed
        f2altidx = findall(tdf[!,4] .!= "0")
        m1altidx = findall(tdf[!,5] .!= "0")
        m2altidx = findall(tdf[!,6] .!= "0")

        c1f1alt = length(intersect( Set(c1altidx), Set(f1altidx) ) ) #alt allele
        c1f2alt = length(intersect( Set(c1altidx), Set(f2altidx) ) ) #matched
        c1m1alt = length(intersect( Set(c1altidx), Set(m1altidx) ) ) #counts
        c1m2alt = length(intersect( Set(c1altidx), Set(m2altidx) ) )
        c2f1alt = length(intersect( Set(c2altidx), Set(f1altidx) ) )
        c2f2alt = length(intersect( Set(c2altidx), Set(f2altidx) ) )
        c2m1alt = length(intersect( Set(c2altidx), Set(m1altidx) ) )
        c2m2alt = length(intersect( Set(c2altidx), Set(m2altidx) ) )

        c1pfm =  [c1f1, c1f2, c1m1, c1m2]  #fractions for data above
        c2pfm =  [c2f1, c2f2, c2m1, c2m2]

        c1altmatch = [c1f1alt, c1f2alt, c1m1alt, c1m2alt] ./ size(tdf, 1)
        c2altmatch = [c2f1alt, c2f2alt, c2m1alt, c2m2alt] ./ size(tdf, 1)

        chriht = ["f1", "f2", "m1", "m2"]

        #pick all max indices for 1) total fraction of sites matched
        #and total alt alleles matched
        c1maxidx = [i for (i, x) in enumerate(c1pfm) if x == maximum(c1pfm)]
        c2maxidx = [i for (i, x) in enumerate(c2pfm) if x == maximum(c2pfm)]
        c1altmaxidx = [i for (i, x) in enumerate(c1altmatch) if x == maximum(c1altmatch)]
        c2altmaxidx = [i for (i, x) in enumerate(c2altmatch) if x == maximum(c2altmatch)]

        c1max = chriht[c1maxidx]   #translated idx to f1, f2, ...
        c2max = chriht[c2maxidx]

        c1altmax = chriht[c1altmaxidx]
        c2altmax = chriht[c2altmaxidx]

        if maximum(c1pfm) < 0.80     #declare phasing/rec. error for >20% mismatch
            c1max = ["pe"]; c1altmax = ["pe"]
        end

        if maximum(c2pfm) < 0.80
            c2max = ["pe"]; c2altmax = ["pe"]
        end

        c1_i = intersect(c1max, c1altmax)  #take isect of best frac & best alts
        c2_i = intersect(c2max, c2altmax)  #->most likely inheritance

        function pickone(c)  #if f1,f2 or m1,m2 equal probs then choose randomly
            if length(c) == 2 && ( c[1][1] == c[2][1] )
                nc = rand(c, 1)
                return nc
            else
                return c
            end
        end

        c1_i = pickone(c1_i)
        c2_i = pickone(c2_i)

        if length(c1_i) == 1  #logic to test other parent if one parent works
            if sum(occursin.(r"pe", c1_i)) > 0
                c1_i = ["nd"]
            elseif sum(occursin.(r"f", c1_i)) > 0
                c2_i = c2_i[occursin.(r"m", c2_i), :]
            elseif sum(occursin.(r"m", c1_i)) > 0
                c2_i = c2_i[occursin.(r"f", c2_i), :]
            end
        end

        if length(c2_i) == 1
            if sum(occursin.(r"pe", c2_i)) > 0
                c2_i = ["nd"]
            elseif sum(occursin.(r"f", c2_i)) > 0
                c1_i = c1_i[occursin.(r"m", c1_i), :]
            elseif sum(occursin.(r"m", c2_i)) > 0
                c1_i = c1_i[occursin.(r"f", c1_i), :]
            end
        end

        if ( (length(c1_i) == 0) || (length(c1_i) ≥ 2) )
            c1_i = []
            push!(c1_i, "nd")
        end

        if ( (length(c2_i) == 0) || (length(c2_i) ≥ 2) )
            c2_i = []
            push!(c2_i, "nd")
        end

        h = String.(header)

        function mapid(c, h)  #translate back to ids
            k = replace.(c, "f1" => h[3])
            l = replace.(k, "f2" => h[4])
            m = replace.(l, "m1" => h[5])
            n = replace.(m, "m2" => h[6])
            return n
        end

        c1_bestids = mapid(c1_i, h)
        c2_bestids = mapid(c2_i, h)

        #flatten arrays and push results to a df
        c1_i = join(reshape(c1_i, (1, length(c1_i) ) ), ",")
        c2_i = join(reshape(c2_i, (1, length(c2_i) ) ), ",")
        c1_bestids = join(reshape(c1_bestids, (1, length(c1_bestids) ) ), ",")
        c2_bestids = join(reshape(c2_bestids, (1, length(c2_bestids) ) ), ",")

        #SET TO TRUE TO SEE THE HAPLOTYPES FOR PARENT AND GRANDPARENTS!
        if showhaps == true
            if occursin(Regex("$iid[p|q]"), names(tdf)[1]) #print only denovo parent trio
                println("TABLE: phased chromosomes for parent with de novo STR and both grandparents.")
                println("TABLE INFO: genotypes for the (de novo) STR locus are shown as -1 (unphasable).")
                println(tdf)
                println("$iid p transmitted from: ", c1_bestids,
                "\n", "$iid q transmitted from: ", c2_bestids, "\n")
            end
        end

        c1chr = h[1]
        c2chr = h[2]

        if (c1_bestids != "nd")
            p1_allele = parse(Int64, (tdf[ stridx, "$c1_bestids" ] ) )
            c1_allele = parse(Int64, (tdf[ stridx, "$c1chr" ] ) )
            p1_length = str_sizes[p1_allele]
            c1_length = str_sizes[c1_allele]
            c1strdiff = abs(p1_allele - c1_allele)
            c1bpdiff  = p1_length - c1_length
        else
            c1strdiff = "nd"; c1_allele = "nd"; c1_length = "nd"
            c1bpdiff  = "nd"; p1_allele = "nd"; p1_length = "nd"
        end

        if (c2_bestids != "nd")
            p2_allele = parse(Int64, (tdf[ stridx, "$c2_bestids" ] ) )
            c2_allele = parse(Int64, (tdf[ stridx, "$c2chr" ] ) )
            p2_length = str_sizes[p2_allele]
            c2_length = str_sizes[c2_allele]
            c2strdiff = abs(p2_allele - c2_allele)
            c2bpdiff  = p2_length - c2_length
        else
            c2strdiff = "nd"; c2_allele = "nd"; c2_length = "nd"
            c2bpdiff  = "nd"; p2_allele = "nd"; p2_length = "nd"
        end

        if occursin(r"f", c1_i)
            p1_sex = "male"
        elseif occursin(r"m", c1_i)
            p1_sex = "female"
        else
            p1_sex = "nd"
        end

        if occursin(r"f", c2_i)
            p2_sex = "male"
        elseif occursin(r"m", c2_i)
            p2_sex = "female"
        else
            p2_sex = "nd"
        end

        push!(ftdf, [pid strchr strpos c1_bestids p1_sex p1_allele p1_length c1chr c1_allele c1_length c1strdiff c1bpdiff] )
        push!(ftdf, [pid strchr strpos c2_bestids p2_sex p2_allele p2_length c2chr c2_allele c2_length c2strdiff c2bpdiff] )

        r1 = []   #reinit for next trio
        r2 = []
        header = []
    end

    fracnd = round(sum(ftdf.str_diff .== "nd") / size(ftdf, 1), digits=2) #frac bad

    fftdf = ftdf[ .&(ftdf.str_diff .!= 0, ftdf.str_diff .!= "nd"), :]

    #Next, extract the rest of the family that carries the mutant STR allele
    #and get the consensus haplotype with the mutation and compare
    #to the grandparents later.

    mutCon = nothing
    num_mut_iht = 0

    if size(hap_df,2) > 7  #run fam but not trio here

        #remove othpar if present
        othpar1 = join([othpar, "p"], "")
        othpar2 = join([othpar, "q"], "")
        ftdf = ftdf[findall(!in([ othpar1, othpar2 ]), ftdf.child_chr), : ]

        mut_offspr = (ftdf[ ftdf.child_str .== mutSTR, [:child_chr]]) #df
        mut_ids = vec(Array(mut_offspr))  #array, 2209p ...
        mut_df = hap_df[:, Symbol.(mut_ids)]

        if sum(occursin.(Regex(othpar), mut_ids)) > 0 #other parent has de novo
            println("WARN: one or more phased chromosomes from parent $othpar
cannot be distiguished from the chromosome with the de novo STR allele.")
            println("INFO: omitting phased chromosomes containing the de novo STR
allele that match the other parent $othpar at a rate of ≥ 0.85")
            othchrid = mut_offspr[ occursin.(Regex(othpar), mut_ids), :]
            othchrid = othchrid[1,1]
            othchr = Array(mut_df[:, Symbol(othchrid)])

            p_oth = Float64[]
            not_othchr = Int64[]

            if showhaps == true
                println("TABLE: Phased offspring chromosomes with de novo allele:")
                println(mut_df)
            end

            for i in 1:size(mut_df, 2)
                p = sum(othchr .== mut_df[:,i]) / size(mut_df, 1)
                push!(p_oth, p)
            end

            for i in 1:length(p_oth)
                if p_oth[i] ≥ 0.85
                    continue
                else
                    push!(not_othchr, i)
                end
            end

            mut_df = mut_df[:, not_othchr]

        end

        if size(mut_df, 2) == 0

            println("WARN: The offsprings' consensus phased chromosome with the
de novo STR is very similar the other parent. This result can be
caused by 1) high similarity among grandparent haplotypes,
2) incorrect phasing, or 3) a false positive de novo STR.
Try different haplotype lengths.\n")

            num_mut_iht = -9
            return(fftdf, ftdf, hap_df, fracnd, mutCon, str_sizes, num_mut_iht)
        end

        av = sum(eachcol(mut_df .== "1")) / size(mut_df,2) #generate a consensus
        mutCon = Array{String,1}(undef, length(av) )       #haplotype (mutCon)

        for i in eachindex(av)
            if av[i] ≥ 0.5
                mutCon[i] = "1"
            else
                mutCon[i] = "0"
            end
        end

        num_mut_iht = size(mut_df,2)

        println("INFO: Found $num_mut_iht offspring carrying the de novo allele $mutSTR\n")

    end

    sort!(fftdf, :parent_chr)

    return (fftdf, ftdf, hap_df, fracnd, mutCon, str_sizes, num_mut_iht)

end
end


function RLBase.update!(π::Union{AbstractLearner, OffPolicy, QBasedPolicy{<:TDLearner{<:AbstractApproximator,:ExpectedSARSA}}}, t::AbstractTrajectory)
    experience = extract_experience(t, π)
    isnothing(experience) || update!(π, experience)
end

function RLBase.update!(π::OffPolicy{<:VBasedPolicy}, transitions::NamedTuple)
    # ??? define a `get_batch_prob` function for efficiency
    weights = [
        get_prob(π.π_target, (state = s,), a) / get_prob(π.π_behavior, (state = s,), a)
        for (s, a) in zip(transitions.states, transitions.actions)
    ]  # TODO: implement iterate interface for (SubArray of) CircularArrayBuffer
    experience = merge(transitions, (weights = weights,))
    update!(π.π_target, experience)
end

extract_experience(t::AbstractTrajectory, π::OffPolicy) =
    extract_experience(t, π.π_target)

extract_experience(trajectory::AbstractTrajectory, p::VBasedPolicy) =
    extract_experience(trajectory, p.learner)


# low-level wrappers of the MAGMA library
include("common.jl")

# magma_init
function magmaInit()
	ccall((:magma_init, libmagma),Cint,())
end

# magma_finalize
function magmaFinalize()
	ccall((:magma_finalize, libmagma),Cint,())
end

# magma_cgesvd wrapper
function magmaCgesvd(jobu, jobvt, m, n, A, lda, s, U,
	ldu, VT, ldvt, work, lwork, rwork, info)
    ccall((:magma_cgesvd, libmagma),
               Cint,
               (Cint, Cint, Cint, Cint, PtrOrCuPtr{ComplexF32},
			    Cint, PtrOrCuPtr{Float32}, PtrOrCuPtr{ComplexF32},
				Cint, PtrOrCuPtr{ComplexF32}, Cint,
				PtrOrCuPtr{ComplexF32}, Cint, PtrOrCuPtr{Float32},
				PtrOrCuPtr{Cint}),
                jobu, jobvt, m, n, A, lda, s, U, ldu, VT, ldvt,
			    work, lwork, rwork, info)
end

# magma_dgesvd wrapper
function magmaDgesvd(jobu, jobvt, m, n, A, lda, s, U,
	 ldu, VT, ldvt, work, lwork, info)
    ccall((:magma_dgesvd, libmagma),
               Cint,
               (Cint, Cint, Cint, Cint, PtrOrCuPtr{Float64},
			    Cint, PtrOrCuPtr{Float64}, PtrOrCuPtr{Float64},
				Cint, PtrOrCuPtr{Float64}, Cint,
				PtrOrCuPtr{Float64}, Cint, PtrOrCuPtr{Cint}),
                jobu, jobvt, m, n, A, lda, s, U, ldu, VT, ldvt,
			    work, lwork, info)
end

# magma_sgesvd wrapper
function magmaSgesvd(jobu, jobvt, m, n, A, lda, s, U,
	 ldu, VT, ldvt, work, lwork, info)
    ccall((:magma_sgesvd, libmagma),
               Cint,
               (Cint, Cint, Cint, Cint, PtrOrCuPtr{Float32},
			    Cint, PtrOrCuPtr{Float32}, PtrOrCuPtr{Float32},
				Cint, PtrOrCuPtr{Float32}, Cint, PtrOrCuPtr{Float32},
				Cint, Ptr{Cint}),
                jobu, jobvt, m, n, A, lda, s, U, ldu, VT, ldvt,
				work, lwork, info)
end

# magma_cgesvd wrapper
function magmaZgesvd(jobu, jobvt, m, n, A, lda, s, U,
	 ldu, VT, ldvt, work, lwork, rwork, info)
    ccall((:magma_zgesvd, libmagma),
               Cint,
               (Cint, Cint, Cint, Cint, PtrOrCuPtr{ComplexF64},
			    Cint, PtrOrCuPtr{Float64}, PtrOrCuPtr{ComplexF64},
				Cint, PtrOrCuPtr{ComplexF64}, Cint, PtrOrCuPtr{ComplexF64},
				Cint, PtrOrCuPtr{Float64}, PtrOrCuPtr{Cint}),
                jobu, jobvt, m, n, A, lda, s, U, ldu, VT, ldvt,
			    work, lwork, rwork, info)
end

# wrappers of the low-level MAGMA functions
for (function_name, element_type, singular_value_type) in
	((:magmaCgesvd,:ComplexF32,:Float32),
                      (:magmaZgesvd,:ComplexF32,:Float64))
	@eval begin
		# magma_int_t magma_cgesvd 	( 	magma_vec_t  	jobu,
		# 	magma_vec_t  	jobvt,
		# 	magma_int_t  	m,
		# 	magma_int_t  	n,
		# 	magmaFloatComplex *  	A,
		# 	magma_int_t  	lda,
		# 	float *  	s,
		# 	magmaFloatComplex *  	U,
		# 	magma_int_t  	ldu,
		# 	magmaFloatComplex *  	VT,
		# 	magma_int_t  	ldvt,
		# 	magmaFloatComplex *  	work,
		# 	magma_int_t  	lwork,
		# 	float *  	rwork,
		# 	magma_int_t *  	info
		# )
		function gesvd!(jobu::Cint,
                      jobvt::Cint,
                      A::CuMatrix{$element_type},
                      ldu::Cint,
                      ldvt::Cint,
					  lwork::Cint,
					  rwork::PtrOrCuPtr{$singular_value_type})
			convert(Cint,jobu)
			convert(Cint,jobvt)
           m, n = size(A)
           # there should be some certain checking
		   # for the input matrix and arrays
           # before we can calculate further input
		   # and call the lower C function wrappers
           lda = max(1,stride(A,2))
           s = CuArray{$singular_value_type}(0,min(m,n))

		   # there should be some conditions for the size of U and VT
		   # but we will deal with them later
		   U = zeros($element_type,ldu,m)
		   VT = zeros($element_type,ldvt,n)

		   work = zeros($element_type,max(1,lwork))
		   info = zeros(Cint,1)

           $function_name(jobu, jobvt, m, n, A, lda, s, U, ldu, VT,
		    ldvt, work, lwork, rwork, info)
           return U, s, VT, work, info
	   end
    end
end
for (function_name, element_type, singular_value_type) in
	 ((:magmaDgesvd,:Float64,:Float64),
                      (:magmaSgesvd,:Float32,:Float32))
	@eval begin
		# magma_int_t magma_dgesvd 	( 	magma_vec_t  	jobu,
		# 	magma_vec_t  	jobvt,
		# 	magma_int_t  	m,
		# 	magma_int_t  	n,
		# 	magmaFloatComplex *  	A,
		# 	magma_int_t  	lda,
		# 	float *  	s,
		# 	magmaFloatComplex *  	U,
		# 	magma_int_t  	ldu,
		# 	magmaFloatComplex *  	VT,
		# 	magma_int_t  	ldvt,
		# 	magmaFloatComplex *  	work,
		# 	magma_int_t  	lwork,
		# 	magma_int_t *  	info
		# )
		Base.unsafe_convert(::CuPtr{$element_type},x::CuArray{$element_type})=Base.unsafe_convert(CuPtr{$element_type},Base.cconvert(CuPtr{($element_type)}x))
		Base.unsafe_convert(::PtrOrCuPtr{$element_type},x::CuArray{$element_type})=Base.unsafe_convert(PtrOrCuPtr{$element_type},Base.cconvert(PtrOrCuPtr{($element_type)}x))
		function gesvd!(jobu,
                      jobvt,
                      A::CuMatrix{$element_type},
                      ldu,
                      ldvt,
					  lwork)
			convert(Cint,jobu)
			convert(Cint,jobvt)
           m, n = size(A)
           # there should be some certain checking
		   # for the input matrix and arrays
           # before we can calculate further input
		   # and call the lower C function wrappers
           lda = max(1,stride(A,2))
           s = cu(zeros(m,n))

		   # there should be some conditions for the size of U and VT
		   # but we will deal with them later
		   U = cu(zeros($element_type,ldu,m))
		   VT = cu(zeros($element_type,ldvt,n))

		   work = cu(zeros($element_type,max(1,lwork)))
		   info = (zeros(Cint,1))

		   $function_name(jobu, jobvt, m, n, A, lda, s, U, ldu, VT,
		    ldvt, work, lwork, info)
           return U, s, VT, work, info
	   end
    end
end

#print(1+1)
print("Test begins!\n")

jobu = MagmaAllVec
jobvt = MagmaAllVec
A = cu(zeros(2,2))
A[1,1]=2.0
A[1,2]=1.0
A[2,1]=1.0
A[2,2]=2.0
ldu=2
ldvt=2
lwork=134
success=magmaInit()

print("Magma Initialization success=", success)
print('\n')

U, s, VT, work, info = gesvd!(jobu,jobvt,A,ldu,ldvt,lwork)

print("U=")
print(U)
print('\n')

print("s=")
print(s)
print('\n')

print("V**T=")
print(VT)
print('\n')

print("work=")
print(work)
print('\n')

print("info=")
print(info)
print('\n')

print("Test ends.")