text:
# Statistics for interpreting actomyosin simulation results
# Alex Tam, 23/11/20

"Actin filament curvature"
function curvature(af, s, Lxx, Lxy, Lyx, Lyy)
    filament_curvatures = Vector{Float64}(); # Pre-allocate mean curvature data
    # Loop over filaments
    for j = 1:length(af)
        f::Actin_Filament = af[j]; # Extract current filament
        seg_lengths = get_segment_lengths(f, s, Lxx, Lxy, Lyx, Lyy); # Dimensional segment lengths
        total_filament_curvature = 0.0; # Pre-allocate integrated curvature of current filament
        # Compute curvature at interior nodes
        if length(s.an[f.index]) >= 3
            for i = 1:length(s.an[f.index])-2
                # Extract physical positions of plus node
                xp = s.an[f.index][i+2][1]*Lxx + s.an[f.index][i+2][2]*Lyx;
                yp = s.an[f.index][i+2][1]*Lxy + s.an[f.index][i+2][2]*Lyy;
                # Extract physical positions of centre node
                xc = s.an[f.index][i+1][1]*Lxx + s.an[f.index][i+1][2]*Lyx;
                yc = s.an[f.index][i+1][1]*Lxy + s.an[f.index][i+1][2]*Lyy;
                # Extract physical positions of minus node
                xm = s.an[f.index][i][1]*Lxx + s.an[f.index][i][2]*Lyx;
                ym = s.an[f.index][i][1]*Lxy + s.an[f.index][i][2]*Lyy;
                # Compute numerical second derivatives
                L01 = seg_lengths[i]; L12 = seg_lengths[i+1]; # Extract segment lengths
                avl = (L01 + L12)/2; # Average length
                ddfx = ((xp-xc)/L12 - (xc-xm)/L01)/avl; # Numerical second derivative (x)
                ddfy = ((yp-yc)/L12 - (yc-ym)/L01)/avl; # Numerical second derivative (y)
                # Add contribution to integrated curvature
                total_filament_curvature += sqrt(ddfx^2 + ddfy^2)*avl;
            end
        end
        push!(filament_curvatures, total_filament_curvature);
    end
    # histogram(filament_curvatures)
    return mean(filament_curvatures)
end

"Two-filament index"
function two_filament_index(mm, s, Lxx, Lxy, Lyx, Lyy)
    index = Vector{Float64}(); # Pre-allocate index data
    # Loop over motors
    for i = 1:length(mm)
        m::Myosin_Motor = mm[i]; # Extract current motor
        f1::Actin_Filament = m.f1; f2::Actin_Filament = m.f2; # Extract filaments attached to current motor
        # Calculate filament lengths
        L1 = sum(get_segment_lengths(f1, s, Lxx, Lxy, Lyx, Lyy));
        L2 = sum(get_segment_lengths(m.f2, s, Lxx, Lxy, Lyx, Lyy));
        # Calculate motor positions
        x1, y1, x2, y2 = get_motor_pos(m, s, Lxx, Lxy, Lyx, Lyy);
        m1x = (x1 - m.t1[1])*Lxx + (y1 - m.t1[2])*Lyx;
        m1y = (x1 - m.t1[1])*Lxy + (y1 - m.t1[2])*Lyy;
        m2x = (x2 - m.t2[1])*Lxx + (y2 - m.t2[2])*Lyx;
        m2y = (x2 - m.t2[1])*Lxy + (y2 - m.t2[2])*Lyy;
        # Find segments on which motor attaches
        seg1, seg2, r1, r2 = get_motor_relative_pos_segment(m, s, Lxx, Lxy, Lyx, Lyy);
        # Calculate segment positions
        mx1, my1, px1, py1 = get_segment_nodes(f1, f1.segments[seg1], s, m.t1, Lxx, Lxy, Lyx, Lyy);
        mx2, my2, px2, py2 = get_segment_nodes(f2, f2.segments[seg2], s, m.t2, Lxx, Lxy, Lyx, Lyy);
        # Calculate angle between vectors
        vec1 = [px1 - mx1, py1 - my1]; # Vector between motor and plus end of filament 1
        vec2 = [px2 - mx2, py2 - my2]; # Vector between motor and plus end of filament 2
        cosine = (vec1[1]*vec2[1] + vec1[2]*vec2[2])/( sqrt(vec1[1]^2 + vec1[2]^2)*sqrt(vec2[1]^2 + vec2[2]^2)); # Cosine of angle between vectors
        # Prevent spurious errors that occur in rare instances
        if abs(cosine) <= 1
            theta = acos(cosine); # Angle between vectors
        elseif cosine > 1
            theta = acos(1); # Angle between vectors
        else
            theta = acos(-1); # Angle between vectors
        end
        # Compute index
        if all(s.mp[i] .<=1)
            Id = ( 2*(L1*s.mp[i][1] + L2*s.mp[i][2])/(L1+L2) - 1)*sin(theta/2)^2; # Heuristic index based on four filament branches
        else
            Id = 0; # Return zero if motor is detached
        end
        push!(index, Id);
    end
    # histogram(index)
    return mean(index)
end

"Actin filament speed"
function filament_speed(parN, af, s, s_old, Lxx, Lxy, Lyx, Lyy)
    node_speeds = Vector{Float64}(); # Pre-allocate vector of node speeds
    # Loop over filaments
    for f in af
        # Loop over nodes
        for i = 1:length(s.an[f.index])
            # Extract physical node position (new)
            xn = s.an[f.index][i][1]*Lxx + s.an[f.index][i][2]*Lyx;
            yn = s.an[f.index][i][1]*Lxy + s.an[f.index][i][2]*Lyy;
            # Extract physical node position (old)
            xo = s_old.an[f.index][i][1]*Lxx + s_old.an[f.index][i][2]*Lyx;
            yo = s_old.an[f.index][i][1]*Lxy + s_old.an[f.index][i][2]*Lyy;
            # Calculate speed
            speed = sqrt((xn-xo)^2 + (yn-yo)^2)/parN.dt;
            push!(node_speeds, speed)
        end
    end
    # histogram(node_speeds)
    return mean(node_speeds)
end

"Myosin motor speed"
function motor_speed(parN, mm, s, s_old, Lxx, Lxy, Lyx, Lyy)
    motor_speeds = Vector{Float64}(); # Pre-allocate vector of motor head speeds
    # Loop over motors
    for m in mm
        # Calculate (current) filament lengths
        L1 = sum(get_segment_lengths(m.f1, s, Lxx, Lxy, Lyx, Lyy));
        L2 = sum(get_segment_lengths(m.f2, s, Lxx, Lxy, Lyx, Lyy));
        # Calculate motor speeds
        s1 = L1*(s.mp[m.index][1]-s_old.mp[m.index][1])/parN.dt;
        s2 = L2*(s.mp[m.index][2]-s_old.mp[m.index][2])/parN.dt;
        push!(motor_speeds, s1); push!(motor_speeds, s2);
    end
    # histogram(motor_speeds)
    return mean(motor_speeds)
end

"Rate of change of angle between two filaments attached to a motor"
function motor_angle_roc(parN, mm, s, s_old, Lxx, Lxy, Lyx, Lyy)
    motor_angle_roc = Vector{Float64}();  # Pre-allocate vector of motor head speeds
    # Loop over motors
    for m in mm
        f1::Actin_Filament = m.f1; f2::Actin_Filament = m.f2; # Extract filaments attached to current motor
        # 1. Compute angles
        # Find segments on which motor attaches
        seg1, seg2, r1, r2 = get_motor_relative_pos_segment(m, s, Lxx, Lxy, Lyx, Lyy);
        # Calculate segment positions
        mx1, my1, px1, py1 = get_segment_nodes(f1, f1.segments[seg1], s, m.t1, Lxx, Lxy, Lyx, Lyy);
        mx2, my2, px2, py2 = get_segment_nodes(f2, f2.segments[seg2], s, m.t2, Lxx, Lxy, Lyx, Lyy);
        # Calculate angle between vectors
        vec1 = [px1 - mx1, py1 - my1]; # Vector between motor and plus end of filament 1
        vec2 = [px2 - mx2, py2 - my2]; # Vector between motor and plus end of filament 2
        cosine = (vec1[1]*vec2[1] + vec1[2]*vec2[2])/( sqrt(vec1[1]^2 + vec1[2]^2)*sqrt(vec2[1]^2 + vec2[2]^2)); # Cosine of angle between vectors
        # Prevent spurious errors that occur in rare instances
        if abs(cosine) <= 1
            theta = acos(cosine); # Angle between vectors
        elseif cosine > 1
            theta = acos(1); # Angle between vectors
        else
            theta = acos(-1); # Angle between vectors
        end
        # Find segments on which motor attaches
        seg1, seg2, r1, r2 = get_motor_relative_pos_segment(m, s_old, Lxx, Lxy, Lyx, Lyy);
        # Calculate segment positions
        mx1, my1, px1, py1 = get_segment_nodes(f1, f1.segments[seg1], s_old, m.t1, Lxx, Lxy, Lyx, Lyy);
        mx2, my2, px2, py2 = get_segment_nodes(f2, f2.segments[seg2], s_old, m.t2, Lxx, Lxy, Lyx, Lyy);
        # Calculate angle between vectors
        vec1 = [px1 - mx1, py1 - my1]; # Vector between motor and plus end of filament 1
        vec2 = [px2 - mx2, py2 - my2]; # Vector between motor and plus end of filament 2
        cosine = (vec1[1]*vec2[1] + vec1[2]*vec2[2])/( sqrt(vec1[1]^2 + vec1[2]^2)*sqrt(vec2[1]^2 + vec2[2]^2)); # Cosine of angle between vectors
        # Prevent spurious errors that occur in rare instances
        if abs(cosine) <= 1
            theta_old = acos(cosine); # Angle between vectors
        elseif cosine > 1
            theta_old = acos(1); # Angle between vectors
        else
            theta_old = acos(-1); # Angle between vectors
        end
        # 2. Calculate rate of change of angle
        rate = (theta - theta_old)/(parN.dt);
        push!(motor_angle_roc, rate);
    end
    # histogram(motor_angle_roc)
    return mean(motor_angle_roc)
end

"Myosin motor head positions"
function motor_position(mm, s)
    motor_pos = Vector{Float64}();  # Pre-allocate vector of motor head speeds
    # Loop over motors
    for m in mm
        push!(motor_pos, s.mp[m.index][1]);
        push!(motor_pos, s.mp[m.index][2]);
    end
    # histogram(motor_pos)
    return mean(motor_pos)
end

"Angle between two filaments attached to a motor"
function motor_angle(mm, s, parN, Lxx, Lxy, Lyx, Lyy)
    motor_angle = Vector{Float64}();  # Pre-allocate vector of motor head speeds
    # Loop over motors
    for m in mm
        f1::Actin_Filament = m.f1; f2::Actin_Filament = m.f2; # Extract filaments attached to current motor
        # Find segments on which motor attaches
        seg1, seg2, r1, r2 = get_motor_relative_pos_segment(m, s, Lxx, Lxy, Lyx, Lyy);
        # Calculate segment positions
        mx1, my1, px1, py1 = get_segment_nodes(f1, f1.segments[seg1], s, m.t1, Lxx, Lxy, Lyx, Lyy);
        mx2, my2, px2, py2 = get_segment_nodes(f2, f2.segments[seg2], s, m.t2, Lxx, Lxy, Lyx, Lyy);
        # Calculate angle between vectors
        vec1 = [px1 - mx1, py1 - my1]; # Vector between motor and plus end of filament 1
        vec2 = [px2 - mx2, py2 - my2]; # Vector between motor and plus end of filament 2
        cosine = (vec1[1]*vec2[1] + vec1[2]*vec2[2])/( sqrt(vec1[1]^2 + vec1[2]^2)*sqrt(vec2[1]^2 + vec2[2]^2)); # Cosine of angle between vectors
        # Prevent spurious errors that occur in rare instances
        if abs(cosine) <= 1
            theta = acos(cosine); # Angle between vectors
        elseif cosine > 1
            theta = acos(1); # Angle between vectors
        else
            theta = acos(-1); # Angle between vectors
        end
        push!(motor_angle, theta);
    end
    # histogram(motor_angle)
    return mean(motor_angle)
end

"Paired distances between nodes"
function pcf(af, s, Lxx, Lyy)
    distances = Vector{Float64}(); # Pre-allocate vector of all paired distances
    # Loop over all filament pairs
    for i = 1:length(af)
        for j = 1:length(af)
            if j > i # Prevent double counting
                # Loop over all nodes
                for k = 1:length(s.an[i])
                    n1x = Lxx*(s.an[i][k][1] - floor(s.an[i][k][1])); # In-domain node position on 1st filament (x)
                    n1y = Lyy*(s.an[i][k][2] - floor(s.an[i][k][2])); # In-domain node position on 1st filament (y)
                    for l = 1:length(s.an[j])
                        n2x = Lxx*(s.an[j][l][1] - floor(s.an[j][l][1])); # In-domain node position on 2nd filament (x)
                        n2y = Lyy*(s.an[j][l][2] - floor(s.an[j][l][2])); # In-domain node position on 2nd filament (y)
                        dist_candidates = Vector{Float64}(); # Pre-allocate possible shortest distances given periodic BCs
                        test = 0; #[-1, 0, 1]; # Translations to test for shortest distance
                        # Loop over translations
                        for t1x in test
                            for t1y in test
                                for t2x in test
                                    for t2y in test
                                        push!(dist_candidates, sqrt( (n1x - n2x - t1x*Lxx + t2x*Lxx)^2 + (n1y - n2y - t1y*Lyy + t2y*Lyy)^2 ));
                                    end
                                end
                            end
                        end
                        push!(distances, minimum(dist_candidates)); # Store shortest distance between nodes
                    end
                end
            end
        end
    end
    # Generate normalised histogram
    nBins = 50;
    BinWidth = sqrt(Lxx^2 + Lyy^2)/nBins;
    Bins = Vector{Float64}(undef, nBins);
    Hist = Vector{Int}(undef, nBins); # Pre-allocate histogram
    for i = 1:nBins
        count::Int = 0;
        for d in distances
            if (d >= (i-1)*BinWidth) && (d < i*BinWidth)
                count += 1;
            end
        end
        Hist[i] = count;
        Bins[i] = i*BinWidth;
    end
    gr(); plot(); # Load GR plotting back-end and clear previous plots
    default(guidefont = (18, "times"), tickfont = (12, "times"))
    bar(Bins, Hist/(sum(Hist)*BinWidth), label=:false, left_margin = 5mm, bottom_margin = 5mm, xlabel = L"d", ylabel = L"P(d)")
    x1::Vector{Float64} = 0.0:0.001:1;
    x2::Vector{Float64} = 1.0:(sqrt(2)-1)/1000:sqrt(2);
    p1 = 2*x1.*(x1.^2 .- 4*x1 .+ pi);
    p2 = 2*x2.*(4*sqrt.(x2.^2 .- 1) - (x2.^2 .+ 2 .- pi) - 4*atan.(sqrt.(x2.^2 .- 1)));
    plot!(2.5*x1, p1/2.5, linewidth=2, color = "black", label=:false);
    plot!(2.5*x2, p2/2.5, linewidth=2, color = "black", label=:false);
    default(titlefont = (18, "times"), guidefont = (26, "times"), tickfont = (18, "times"))
    return Bins, Hist
    # histogram(distances)
end

"Compute integrated network statistics"
function integrated_statistics(parN, Curvature, Index, Filament_Speed, Motor_Speed, Angle_ROC, Motor_Pos, Motor_Angle)
    Curvature_Int = Vector{Float64}(); Index_Int = Vector{Float64}(); Filament_Speed_Int = Vector{Float64}();
    Motor_Speed_Int = Vector{Float64}(); Angle_ROC_Int = Vector{Float64}(); Motor_Pos_Int = Vector{Float64}(); Motor_Angle_Int = Vector{Float64}(); # Pre-allocate time-integrated variables
    times = (0:parN.nT-1).*parN.dt; # Vector of times at which we obtain measurements
    for i = 1:parN.nT
        push!(Curvature_Int, trapz(Curvature, times)); # Integrate curvature over time
        push!(Index_Int, trapz(Index, times)); # Integrate two-filament index over time
        push!(Filament_Speed_Int, trapz(Filament_Speed, times)); # Integrate filament node speed over time
        push!(Motor_Speed_Int, trapz(Motor_Speed, times)); # Integrate motor head speed over time
        push!(Angle_ROC_Int, trapz(Angle_ROC, times)); # Integrate motor head speed over time
        push!(Motor_Pos_Int, trapz(Motor_Pos, times)); # Integrate motor head position over time
        push!(Motor_Angle_Int, trapz(Motor_Angle, times)); # Integrate motor angle over time
    end
    return Curvature_Int, Index_Int, Filament_Speed_Int, Motor_Speed_Int, Angle_ROC_Int, Motor_Pos_Int, Motor_Angle_Int
end


text:
ispointerstruct(@nospecialize(T)) = false
function offset end

# function offset_expr(expr_offset::Vector{Any}, known_offset::Int)
#   px = :(pointer(x))
#   for ex ∈ expr_offset
#     px = :($px + $ex)
#   end
#   known_offset == 0 ? :($px + $known_offset) : px
# end
function create_offset_expr(offset_expr::Vector{Expr}, known_offset::Int)
  offex = :($getfield(x, Symbol("##pointer##")))
  for ex ∈ offset_expr
    offex = :($offex + $ex)
  end
  known_offset == 0 ? offex : :($offex + $known_offset)
end

# create_offset_expr(offset_expr::Expr, known_offset::Int) = known_offset == 0 ? offset_expr : :($offset_expr + $known_offset)
# const EMPTY_DICT_NEST = Dict{Symbol,Dict{Symbol,Symbol}}()
const EMPTY_DICT = Dict{Symbol,Tuple{Symbol,Symbol}}()

function push_fields!(
  ret::Expr, construct_expr::Expr, structfields::Expr, offset_expr::Vector{Expr}, #pair_types::Dict{Symbol,DataType},
  sym::Symbol, @nospecialize(type::DataType), name::Symbol, known_offset::Int,
  field_map::Dict{Symbol,Tuple{Symbol,Symbol}}, ismut::Bool, skipfirst::Bool
)
  fns = fieldnames(type); fts = fieldtypes(type)
  for i ∈ 1+skipfirst:length(fns)
    fn = (fns[i])::Symbol
    unpacktup = get(field_map, fn, nothing)
    ft = fts[i]
    if unpacktup ≢ nothing
      unpackprop, unpackf = unpacktup
      # unpack from earlier
      unpack_expr = :($unpack(x, $(Val{unpackprop}())))
      if unpackf ≢ :identity
        unpack_expr = Expr(:call, unpackf, unpack_expr)
      end
      push!(construct_expr.args, unpack_expr)
      # (unpackf ≡ :identity) && (@assert get!(pair_types, unpackprop, ft) === ft)
    else
      @assert isbitstype(ft)
      if sizeof(ft) ≠ 0
        new_field_name = Symbol(sym, ".", fn)
        known_offset = push_nonnested_field!(ret, structfields, offset_expr, new_field_name, ft, name, known_offset, ismut)
        push!(construct_expr.args, :($unpack(x, $(Val{new_field_name}()))))
      else
        push!(construct_expr.args, ft.instance)
      end
    end
  end
  known_offset
end
function align_offset(st::Int, known_offset::Int)
  # half-hearted attempt at alignment
  alignment = (known_offset & min(st-1,7))
  ifelse(alignment == 0, known_offset, known_offset + min(st,8) - alignment)
end
function push_nonnested_field!(
  ret::Expr, structfields::Expr, offset_expr::Vector{Expr}, sym::Symbol, @nospecialize(typ::DataType), name::Symbol, known_offset::Int, ismut::Bool
)
  if ismut
    st = sizeof(typ)::Int
    known_offset = align_offset(st, known_offset)
    unpack_expr = :(@inline $UnPack.unpack(x::$name, ::Val{$(QuoteNode(sym))}) = $unsafe_load($reinterpret($(Ptr{typ}), $(create_offset_expr(offset_expr, known_offset)))))
    pack_expr = :(@inline $UnPack.pack!(x::$name, ::Val{$(QuoteNode(sym))}, val) = $unsafe_store!($reinterpret($(Ptr{typ}), $(create_offset_expr(offset_expr, known_offset))), $convert($typ, val)))
    push!(ret.args, unpack_expr, pack_expr)
    known_offset += st
  else
    push!(structfields.args, Expr(:(::), sym, typ))
    unpack_expr = :(@inline $UnPack.unpack(x::$name, ::Val{$(QuoteNode(sym))}) = $getfield(x, $(QuoteNode(sym))))
    push!(ret.args, unpack_expr)
  end
  known_offset
end

function push_field!(
  ret::Expr, structfields::Expr, offset_expr::Vector{Expr}, #pair_types::Dict{Symbol,DataType},
  sym::Symbol, @nospecialize(type::DataType), name::Symbol,
  known_offset::Int, field_mappings::Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}, ismut::Bool
)::Int

  if Base.isbitstype(type)
    if length(fieldnames(type)) == 0
      # @show structfields
      return push_nonnested_field!(ret, structfields, offset_expr, sym, type, name, known_offset, ismut)
    # elseif type <: Tuple
    #   skipfirst = false
    #   construct_expr = Expr(:tuple)
    #   field_map = EMPTY_DICT
    else
      skipfirst = ispointerstruct(type)::Bool
      construct_expr = if skipfirst
        # field_map = get(() -> Dict{Symbol,Symbol}(), field_mappings, sym)
        field_map = get(field_mappings, sym, EMPTY_DICT) # allows checking field_map === EMPTY_DICT
        Expr(:call, type)
      else
        field_map = get(field_mappings, sym, nothing)
        if field_map ≡ nothing # store as collection
          return push_nonnested_field!(ret, structfields, offset_expr, sym, type, name, known_offset, ismut)
        end
        Expr(:new, type)
      end
    end
    known_offset = push_fields!(
      ret, construct_expr, structfields, offset_expr, #=pair_types,=# sym, type, name, known_offset,
      field_map, ismut, skipfirst
    )

    if skipfirst
      insert!(construct_expr.args, 2, create_offset_expr(offset_expr, known_offset))
      push!(offset_expr, :($offset($unpack(x, $(Val{sym}())))))
    end

    unpack_expr = :(@inline $UnPack.unpack(x::$name, ::Val{$(QuoteNode(sym))}) = $construct_expr)
    # offset_fun_expr = :(@inline $offset(x::$name, ::Val{(QuoteNode(sym))}) = $(create_offset_expr(offset_expr, known_offset)))
    push!(ret.args, unpack_expr)#, offset_fun_expr)
  elseif type <: AbstractArray # PtrArray
    known_offset = align_offset(sizeof(eltype(type))::Int, known_offset)
    N::Int = ndims(type)
    ptype = PtrArray{eltype(type), N, NTuple{N,Int}, Tuple{One,Vararg{Int,N-1}}}
    push_field!(ret, structfields, offset_expr, #=pair_types,=# sym, ptype, name, known_offset, field_mappings, ismut)
    # push!(offset_expr, :($offset($unpack(x, $(Val{sym}())))))
  else # fall back, ordinary struct

  end
  known_offset
end
function push_field!(
  ret::Expr, structfields::Expr, offset_expr::Vector{Expr}, #pair_types::Dict{Symbol,DataType},
  sym::Symbol, typ::Symbol, name::Symbol, known_offset::Int, field_mappings::Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}, ismut::Bool, mod::Module
)::Int
  type = getproperty(mod, typ)
  push_field!(ret, structfields, offset_expr, #=pair_types,=# sym, type, name, known_offset, field_mappings, ismut)
end
function curly_to_type(mod::Module, typ::Expr)
  base = typ.args[1]
  if base isa Symbol
    baset = getproperty(mod, base)
  else
    @assert Meta.isexpr(base, :curly)
    baset = curly_to_type(mod, base)
  end
  curlytypes = Vector{Any}(undef, length(typ.args)-1)
  for i ∈ eachindex(curlytypes)
    c = typ.args[1+i]
    if c isa Symbol
      curlytypes[i] = getproperty(mod, c)
    elseif Meta.isexpr(base, :curly)
      curlytypes[i] = curly_to_type(mod, c)
    else
      @assert isbitstype(c)
      curlytypes[i] = c
    end
  end
  baset{curlytypes...}
end
function push_field!(
  ret::Expr, structfields::Expr, offset_expr::Vector{Expr}, #pair_types::Dict{Symbol,DataType},
  sym::Symbol, typ::Expr, name::Symbol, known_offset::Int, field_mappings::Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}, ismut::Bool, mod::Module
)::Int
  type = curly_to_type(mod, typ)
  push_field!(ret, structfields, offset_expr, #=pair_types,=# sym, type, name, known_offset, field_mappings, ismut)
end
function pushtofieldmapping!(ret::Expr, field_mappings::Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}, lp::Symbol, rp::Symbol, fieldname::Symbol, f::Symbol = :identity)
  get!(() -> Dict{Symbol,Symbol}(), field_mappings, lp)[rp] = (fieldname,f)
end
function addfieldmapfunction!(ret::Expr, _rpr::Expr, fieldname::Symbol)::Symbol
  rpr::Expr = _rpr
  if Meta.isexpr(rpr, :call)
    if length(rpr.args) == 2
      @assert rpr.args[2] === fieldname
      rprs = rpr.args[1]
      if rprs isa Symbol
        return rprs
      end
      rpr = rprs
    else
      throw("Only Single-arg functions are supported at the moment for lp => rp")
    end
  end
  @assert rpr.head === :(->)
  @assert rpr.args[1] isa Symbol
  anonname = gensym()
  push!(ret.args, Expr(:const, Expr(:(=), anonname, rpr)))
  anonname
end
function pushtofieldmapping!(ret::Expr, field_mappings::Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}, lp::Symbol, rp::Expr, fieldname::Symbol)
  if Meta.isexpr(rp, :tuple)
    for rpi ∈ rp.args
      if rpi isa Symbol
        pushtofieldmapping!(ret, field_mappings, lp, rpi::Symbol, fieldname)
      else
        pushtofieldmapping!(ret, field_mappings, lp, rpi::Expr, fieldname)
      end
    end
  elseif Meta.isexpr(rp, :(=), 2)
    rpl::Symbol = rp.args[1]
    rpr::Union{Expr,Symbol} = rp.args[2]
    if rpr isa Symbol
      pushtofieldmapping!(ret, field_mappings, lp, rpl, fieldname, rpr)
    else
      pushtofieldmapping!(ret, field_mappings, lp, rpl, fieldname, addfieldmapfunction!(ret, rpr, fieldname))
    end
  else
    throw("rp must be a tuple or a call, but the lp => rp was $lp => $rp")
  end
end
function pushtofieldmapping!(ret::Expr, field_mappings::Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}, lp::Expr, rp::Symbol, fieldname::Symbol)
  @assert Meta.isexpr(lp, :tuple)
  for _lps ∈ lp.args
    get!(() -> Dict{Symbol,Symbol}(), field_mappings, _lps::Symbol)[rp] = fieldname
  end
end
function pushtofieldmapping!(ret::Expr, field_mappings::Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}, lp::Expr, rp::Expr, fieldname::Symbol)
  @assert Meta.isexpr(lp, :tuple)
  if Meta.isexpr(rp, :tuple)
    for i ∈ eachindex(lp.args, rp.args)
      pushtofieldmapping!(ret, field_mappings, (lp.args[i])::Symbol, rp.args[i], fieldname)
    end
  else
    for lpi ∈ lp.args
      pushtofieldmapping!(ret, field_mappings, lpi::Symbol, rp, fieldname)
    end
  end
end
"""
  @pointer struct Foo
    a::((b,c)=>len)::Int
    b::Bar # .b.len === a
    c::Buz # .c.len === a
  end

"""
macro pointer(ex)
  @assert ex.head === :struct
  args = ex.args
  ismut::Bool = args[1]
  name::Symbol = args[2]
  # name::Union{Symbol,Expr} = args[2]
  fieldargs::Vector{Any} = ((args[3])::Expr).args
  structfields = Expr(:block, Expr(:(::), Symbol("##pointer##"), Ptr{Cvoid}))
  structdef = Expr(:struct, false, name, structfields)
  # we mutate ex
  # ret = Expr(:block, __source__, structdef, :($ispointerstruct(::Type{$name})=true))
  ret = Expr(:block, __source__, structdef, :($PointerStructs.ispointerstruct(::Type{$name})=true))
  if Base.isdefined(Base, Symbol("@constprop"))
    push!(ret.args, :(@inline Base.@constprop :aggressive Base.getproperty(x::$name, s::Symbol) = $(UnPack.unpack)(x, Val(s))))
    push!(ret.args, :(@inline Base.@constprop :aggressive Base.setproperty!(x::$name, s::Symbol, v) = $(UnPack.pack!)(x, Val(s), v)))
  else
    push!(ret.args, :(@inline Base.getproperty(x::$name, s::Symbol) = $(UnPack.unpack)(x, Val(s))))
    push!(ret.args, :(@inline Base.setproperty!(x::$name, s::Symbol, v) = $(UnPack.pack!)(x, Val(s), v)))
  end
  known_offset = 0
  # offset_expr = :(pointer(x))
  # offset_expr = :($getfield(x, Symbol("##pointer##")))
  offset_expr = Expr[]
  field_mappings = Dict{Symbol,Dict{Symbol,Tuple{Symbol,Symbol}}}() # (sym,
  for field ∈ fieldargs
    if Meta.isexpr(field, :(::), 2)
      _sym::Union{Symbol,Expr} = field.args[1]
      if _sym isa Expr
        fieldname::Symbol = _sym.args[1]
        fieldmap::Expr = _sym.args[2]
        @assert fieldmap.args[1] === :(=>)
        lp::Union{Expr,Symbol} = fieldmap.args[2]
        rp::Union{Expr,Symbol} = fieldmap.args[3]
        pushtofieldmapping!(ret, field_mappings, lp, rp, fieldname)
      end
    end
  end
  # @show field_mappings
  # pair_types = Dict{Symbol,DataType}()
  for field ∈ fieldargs
    if field isa LineNumberNode
      push!(structfields.args, field)
      continue
    end
    @assert Meta.isexpr(field, :(::), 2)
    _sym::Union{Symbol,Expr} = field.args[1]
    sym::Symbol = _sym isa Symbol ? _sym : _sym.args[1]
    typ::Union{Symbol,Expr} = (field.args[2])::Union{Symbol,Expr}
    if typ isa Symbol
      # @show sym, typ, name structfields
      known_offset = push_field!(ret, structfields, offset_expr, #=pair_types,=# sym, typ, name, known_offset, field_mappings, ismut, __module__)
    elseif Meta.isexpr(typ, :curly)
      if typ.args[1] === :SizedArray
        sxₙ = typ.args[2]
        (sx::Int,st::Vector{Any}) = if sxₙ isa Int
          sxₙ, Any[StaticInt{sx}]
        else
          0, Any[Int]
        end
        xt = Any[One()]
        for n in 3:length(typ.args)-1
          if sx == 0
            push!(ex, Int)
          else
            push!(xt, StaticInt{sx})
          end
          sxₙ = typ.args[n]
          if sxₙ isa Int
            push!(st, StaticInt{sxₙ})
            sx *= sxₙ
          else
            push!(st, Int)
            sx = 0
          end
        end
        ptype = PtrArray{getproperty(__module__, last(typ.args)), length(typ.args)-2, Tuple{st...}, Tuple{xt...}}
        known_offset = push_field!(ret, structfields, offset_expr, #=pair_types,=# sym, ptype, name, known_offset, field_mappings, ismut, __module__)
      else
        known_offset = push_field!(ret, structfields, offset_expr, #=pair_types,=# sym, typ, name, known_offset, field_mappings, ismut, __module__)
      end
    elseif Meta.isexpr(typ, :&, 1)
      known_offset = push_field!(ret, structfields, offset_expr, #=pair_types,=# sym, (typ.args[1])::Union{Symbol,Expr}, name, known_offset, field_mappings, true, __module__)
    # elseif Meta.isexpr(typ, :call, 3)
    #   current_name = (structdef.args[2])::Union{Symbol,Expr}
    #   if current_name isa Symbol
    #     newtyp = gensym(:T)
    #     structdef.args[2] = Expr(:curly, current_name, newtyp)
    #   else
    #     newtyp = gensym(:T)
    #     push!(current_name.args, newtyp)
    #   end
    #   push!(structfields.args, Expr(:(::), sym, newtyp))
    end
  end
  if length(offset_expr) == 0
    push!(ret.args, :($PointerStructs.offset(x::$name) = $known_offset))
  else
    offex = offset_expr[1]
    for i in 2:length(offset_expr)
      offex = :($offex + $(offset_expr[i]))
    end
    offex = known_offset == 0 ? offex : :($offex + $known_offset)
    push!(ret.args, :($PointerStructs.offset(x::$name) = $offex))
  end
  # for (sym, type) ∈ pair_types
  #   offset_expr, known_offset = push_field!(ret, structfields, pair_types, sym, type, name, offset_expr, known_offset, field_mappings, ismut)
  # end
  esc(ret)
end

ext: jl


text:
using JuliaImageMaker
using Test

@testset "JuliaImageMaker.jl" begin
    # Write your tests here.
end


text:
"""
$(DocStringExtensions.README)
"""
module DiffEqBayesStan

    using DocStringExtensions
    using DiffEqBase, Distributions, MacroTools
    using RecursiveArrayTools, ModelingToolkit
    using Parameters, Distributions, Optim, Requires
    using Distances, DocStringExtensions, Random
    using StanSample

    STANDARD_PROB_GENERATOR(prob,p) = remake(prob;u0=eltype(p).(prob.u0),p=p)
    STANDARD_PROB_GENERATOR(prob::EnsembleProblem,p) = EnsembleProblem(remake(prob.prob;u0=eltype(p).(prob.prob.u0),p=p))

    include("stan_string.jl")
    include("stan_inference.jl")

    const src_path = @__DIR__

    """

    # debs_path

    Relative path using the StatisticalRethinking src/ directory.

    ### Example to get access to the data subdirectory
    ```julia
    debs_path("..", "data")
    ```

    Note that in the projects, e.g. StatisticalRethinkingStan.jl and StatisticalRethinkingTuring.jl, the
    DrWatson approach is a better choics, i.e: `sr_datadir(filename)`

    """
    debs_path(parts...) = normpath(joinpath(src_path, parts...))

    # DrWatson extension
    """

    # debs_datadir

    Relative path using the StatisticalRethinking src/ directory.

    ### Example to access `Howell1.csv` in StatisticalRethinking:
    ```julia
    df = CSV.read(sr_datadir("Howell1.csv"), DataFrame)
    ```
    """
    debs_datadir(parts...) = debs_path("..", "data", parts...)

    export
        stan_inference,
        debs_path,
        debs_datadir

end # module


text:
module LocalMethods

using GSL
using LinearAlgebra
using Statistics
import JLD2

export cov_n1_dot_n2, cov_angle

const data_dir = "../data/"
const cls = JLD2.FileIO.load(data_dir*"cls.jld2", "cls")
const w_on_Pl = cls[:cl_len_scalar][:,1] .* (2 .* cls[:ell] .+ 1) ./ (4π) ./ cls[:factor_on_cl_cmb]
const lmax = maximum(cls[:ell])

function cov_cosθ(cosθ,lmax::Int)
	@assert lmax >= 2 "lmax too small, must be greater than 1"
	dot(sf_legendre_Pl_array(lmax, cosθ)[3:end], w_on_Pl[3:lmax+1])
end

cov_n1_dot_n2(n1_dot_n2) = cov_cosθ(n1_dot_n2,lmax)
cov_angle(angle) = cov_cosθ(cos(angle),lmax)

function bin_mean(fk; bin=0)
	fkrtn = copy(fk)
	if bin > 1
		Nm1    = length(fk)
		subNm1 = bin * (Nm1÷bin)
		fmk    = reshape(fk[1:subNm1], bin, Nm1÷bin)
		fmk   .= mean(fmk, dims=1)
		fkrtn[1:subNm1] .= vec(fmk)
		fkrtn[subNm1+1:end] .= mean(fk[subNm1+1:end])
	end
	return fkrtn
end

end # module


text:
#    myforeachfield((col, val) -> (@inbounds col[I] = val), s, vals)

using StructArrays
import StructArrays: staticschema, map_params, astuple, _map_params
using Base: tuple_type_cons, tuple_type_head, tuple_type_tail, tail

function _sstuple(::Type{<:NTuple{N, Any}}) where {N}
    ntuple(j->Symbol(j), N)
end

function _sstuple(::Type{NT}) where {NT<:NamedTuple}
    _map_params(x->_sstuple(staticschema(x)), NT)
end

function _getcolproperties!(exprs, s, es=[])
    if typeof(s) <: Symbol
        push!(exprs, es)
        return
    end
    for key in keys(s)
        _getcolproperties!(exprs, getproperty(s,key), vcat(es, key))
    end
end

using StaticArrays
c = [(a=SHermitianCompact(@SVector(rand(3))), b=(@SVector(rand(2)))) for i=1:5]
d = StructArray(c, unwrap = t -> t <: Union{SHermitianCompact,SVector,Tuple})
b = []
S = StructArrays.staticschema(typeof(d))
s = _sstuple(S)
_getcolproperties!(b, s)
@show b

function f(columnsproperties, xs)
  exprs = Expr[]
  for col in columnsproperties
    args = Expr[]
    for prop in col
      if length(args) == 0
        args = [Expr(:call, :_getproperty, :(getfield(xs, $j)), prop) for j in 1:length(xs)]
      else
        for j in 1:length(xs)
          args[j] = Expr(:call, :_getproperty, args[j], prop)
        end
      end
    end
    push!(exprs, Expr(:call, :f, args...))
  end
  push!(exprs, :(return nothing))
  return Expr(:block, exprs...)
end


text:
### WarpedArray
"""
A `WarpedArray` `W` is an AbstractArray for which `W[x] = A[ϕ(x)]` for
some parent array `A` and some deformation `ϕ`.  The object is created
lazily, meaning that computation of the displaced values occurs only
when you ask for them explicitly.

Create a `WarpedArray` like this:

```
W = WarpedArray(A, ϕ)
```
where

- The first argument `A` is an `AbstractExtrapolation` that can be
  evaluated anywhere.  See the Interpolations package.
- ϕ is an `AbstractDeformation`
"""
struct WarpedArray{T,N,A<:Extrapolatable,D<:AbstractDeformation} <: AbstractArray{T,N}
    data::A
    ϕ::D
end

# User already supplied an interpolatable ϕ
function WarpedArray(data::Extrapolatable{T,N},
                     ϕ::GridDeformation{S,N,A}) where {T,N,S,A<:AbstractInterpolation}
    WarpedArray{T,N,typeof(data),typeof(ϕ)}(data, ϕ)
end

# Create an interpolatable ϕ
function WarpedArray(data::Extrapolatable{T,N}, ϕ::GridDeformation) where {T,N}
    itp = scale(interpolate(ϕ.u, BSpline(Quadratic(Flat(OnCell())))), ϕ.nodes...)
    ϕ′ = GridDeformation(itp, ϕ.nodes)
    WarpedArray{T,N,typeof(data),typeof(ϕ′)}(data, ϕ′)
end

WarpedArray(data, ϕ::GridDeformation) = WarpedArray(to_etp(data), ϕ)

Base.size(A::WarpedArray) = size(A.data)
Base.size(A::WarpedArray, i::Integer) = size(A.data, i)
Base.axes(A::WarpedArray) = axes(A.data)
Base.axes(A::WarpedArray, i::Integer) = axes(A.data, i)

@inline function Base.getindex(W::WarpedArray{T,N}, I::Vararg{Number,N}) where {T,N}
    ϕx = W.ϕ(I...)
    return W.data(ϕx...)
end

ImageAxes.getindex!(dest, W::WarpedArray{T,N}, coords::Vararg{Any,N}) where {T,N} =
    Base._unsafe_getindex!(dest, W, coords...)


text:
input = Array{Int64}(1)

open("input.txt") do f
    lines = readlines(f)

    resize!(input, length(lines))

    for i in 1:length(lines)
        input[i] = parse(Int64, replace(lines[i], "\n", ""))
    end
end

counter = 0
index = 1
prevIndex = 1
while(true)
    counter += 1
    prevIndex = index
    index += input[index]

    if(index > length(input))
        break
    end

    input[prevIndex] += 1
end

println("Solved in $counter steps")


text:
# takes in clean dataframes, outputs students, projects, groups
function process_projects(projects::DataFrame; min=9, max=12)
    pdata = ProjectData[]
    for p in eachrow(projects)
        name = p["Sponsor"]*": "*p["Title"]

        minroles = (hardware = p["Min # of Hardware"],
                    software = p["Min # of Software"],
                    electronics = p["Min # of Electrical"])

        project = ProjectData(name, minroles, min, max)

        push!(pdata, project)
    end

    return pdata
end

function process_survey(survey, pnames; manual_singles=String[], map_roles=default_map_roles)
    sdata = StudentData[]
    teammates = Dict{String, Vector{String}}()
    for s in eachrow(survey)
        name = string(s["RecipientFirstName"], ' ', filter(!isspace, s["RecipientLastName"]))

        tms = collect(map(teammate_to_name, skipmissing([s["Q1_1"], s["Q3_1"]])))
        # filter out strange numbers that appeared in the 011 survey
        teammates[name] = filter(tms) do tmname
            if isnothing(tryparse(Int, tmname)) && tmname != name
                return true
            else
                @warn ("Ignoring teammate name $tmname for $name")
                return false
            end
        end

        pm = !ismissing(s["Q6"])

        # roles
        roles = (electronics = s["Q4_3"],
                 hardware = s["Q4_1"],
                 software = s["Q4_2"])
        roles = map_roles(roles)

        @assert length(pnames) <= 9 # can only handle single digit numbers
        prefs = Vector{Union{Missing, String}}(missing, length(pnames))
        multiples = PriorityQueue()
        for (i, p) in enumerate(pnames)
            rank = passmissing(x->parse(Int, first(x)))(s[string("Q9_", i)])
            if ismissing(rank)
                @info("$name gave no rank for project $p")
            else
                if !ismissing(prefs[rank])
                    @info("Double entry for rank $rank ($name, $p). Moving!")
                    multiples[p] = rank
                else
                    prefs[rank] = p
                end
            end
        end
        for r in 1:length(prefs)
            if ismissing(prefs[r])
                if !isempty(multiples)
                    prefs[r] = dequeue!(multiples)
                end
            end
        end
        @assert isempty(multiples)

        push!(sdata, StudentData(name, roles, pm, prefs, s["Q10"]))
    end

    groups = Vector{String}[]
    gmap = Dict{String, Int}()
    for (n, mates) in teammates
        if haskey(gmap, n) || isempty(mates)
            continue
        end
        valid = true
        for tm in mates
            if !haskey(teammates, tm)
                @warn("Could not find teammates entry for $tm. Ignoring")
                valid = false
            # elseif !(n in teammates[tm]) || haskey(gmap, tm)
            elseif !(n in teammates[tm])
                @warn("Invalid group: $n => $tm was not reciprocated.")
                valid = false
            end
        end
        if valid
            group = [n, teammates[n]...]
            push!(groups, group)
            for s in group
                gmap[s] = length(groups)
            end
        end
    end

    # force same prefs
    for g in groups
        selected = first(shuffle(g))
        # selected = first(g)
        # selected = last(g)
        prefs = copy(only(filter(s->s.id==selected, sdata)).prefs)
        for n in g
            otherprefs = only(filter(s->s.id==n, sdata)).prefs
            if any(skipmissing(otherprefs .!= prefs))
                @info("Preferences for $n don't match $selected in group $g.")
            end
            otherprefs[:] = prefs
        end
    end

    return sdata, groups
end

function default_map_roles(roles)
    # specialty = argmax(roles)
    # roles = merge((;), [specialty=>1.0]) # roundabout way
    # floor anything <= 3
    roles = map(roles) do frac
        if ismissing(frac) || frac <= 3
            return 0
        else
            return frac
        end
    end

    if sum(roles) == 0.0
        return roles
    else
        # normalize
        return map(frac->frac/sum(roles), roles)
    end
end

teammate_to_name(teammate) = join(reverse(split(teammate, '_')), ' ')

function Base.convert(::Type{DataFrame}, sd::Vector{StudentData})
    pairs = [:id => [s.id for s in sd],
             :hardware => [get(s.roles, :hardware, 0.0) for s in sd],
             :software => [get(s.roles, :software, 0.0) for s in sd],
             :electronics => [get(s.roles, :electronics, 0.0) for s in sd],
             :pm => [s.pm for s in sd]
            ]

    for j in 1:length(first(sd).prefs)
        push!(pairs, Symbol(string("pref_", j)) => [s.prefs[j] for s in sd])
    end
    return DataFrame(pairs)
end


text:
if basename(pwd()) == "aoc"
    cd("2017/12")
end

function loadnet(filename::AbstractString)
    Dict{Int,Vector{Int}}(
        begin
            l, r = split(line, " <-> ")
            k = parse(Int, l)
            vs = parse.(Int, sort!(split(r, ", ")))
            k => vs
        end
        for line in eachline(filename)
    )
end

loadnet("example.txt")

part1(filename::AbstractString) = length(visit(loadnet(filename), 0))

function visit(net, i::Int, visited = Int[])
    sort!(push!(visited, i))
    for j in net[i]
        if !insorted(j, visited)
            visit(net, j, visited)
        end
    end
    visited
end
@assert part1("example.txt") == 6
part1("input.txt")

function dot(net)
    join((
        "graph {\n",
        ("  $k -- $v\n" for k in keys(net) for v in net[k])...,
        "}"
    ))
end

function part2(filename::AbstractString)
    net = loadnet(filename)
    result = IOBuffer()
    run(pipeline(IOBuffer(dot(net)), `dot -Tsvg`, result))
    open(replace(filename, r"\.[^.]*$" => ".svg"), "w") do f
        write(
            f,
            replace(
                String(take!(result)),
                "</svg>" => raw"""
                    <script>
                    window.addEventListener("load",  function() {
                        alert('The answer is ' + document.querySelectorAll(`ellipse[cy="${
                            Math.min(...[...document.querySelectorAll('ellipse')].map((el) => el.getAttribute('cy')))
                        }"]`).length)
                      })
                    </script>
                    </svg>
                    """
            )
        )
    end
end

part2("input.txt")

max_stars_repo_name: stellartux/advent-of-code

max_issues_repo_name: stellartux/advent-of-code

max_forks_repo_name: stellartux/advent-of-code


text:
ENV["GKSwstype"]="nul"
using KitePodModels, Plots

cd("..")

const t_end = 10.0 # simulation time
const dt = 1.0 / se().sample_freq

kcu = KCU(se())

const times = Float64[]
const depower = Float64[]
const depower_set = Float64[]
const steering = Float64[]
const steering_set = Float64[]
const rel_depower_offset = se().depower_offset/100.0
for step in 1:Int(round(t_end/dt))
    time = step * dt
    if step < 20
        set_depower_steering(kcu, rel_depower_offset, 0.0)
        push!(depower_set, rel_depower_offset)
        push!(steering_set, 0.0)
    else
        set_depower_steering(kcu, rel_depower_offset+0.5, 0.5)
        push!(depower_set, rel_depower_offset+0.5)
        push!(steering_set, 0.5)
    end
    push!(times, time)
    KitePodModels.on_timer(kcu)
    push!(depower, get_depower(kcu))
    push!(steering, get_steering(kcu))
end
l = @layout([a; b])
plot(times, depower, layout=l, subplot=1, label = "depower", legend = :bottomright)
plot!(times, depower_set, layout=l, subplot=1, label  = "depower_set")
plot!(times, steering, layout=l, subplot=2, label = "steering", legend = :bottomright)
plot!(times, steering_set, layout=l, subplot=2, label  = "steering_set")
# This could become an example script, but a test should not write to this directory
# savefig("docs/src/step_response.png")
# println("Saved step response in docs/src/step_response.png !")


text:
#Create file
stream = open("my_file.txt" , "w")

#Write a single line
write(stream , "This is a line!")

#Close file
close(stream)

#Open file as a read!
stream = open("my_file.txt", "r")

#Read a single line
line = readline(stream)

#Close file!
close(stream)


text:
##### Paths configuration file #####
# Copy this file to 'config/paths.jl' and modify as needed

push!(dataconfig.paths,
    "raw" => "raw",
    "results" => "results",
    "figs" => "figs",
)


text:
# Starting point date and time example for Learning Julia

# import the Dates module to use the code it contains
using Dates

# create new Date and DateTime
d1 = Date(2018, 12, 22)
dt1 = DateTime(2019, 7, 28)
println("Date: ", d1)
println("DateTime: ", dt1, "\n")

# parse a date or date/time string using DateFormat
df1 = DateFormat("y-m-d")
d2 = Date("2020-10-10", df1) # parse the String using DateFormat df1
println("Parsed Date: ", d2, "\n")

# differences between Dates and Times
d3 = Date(2019, 12, 22)
d4 = Date(2019, 7, 28)
dt2 = DateTime(2000, 01, 01)
dt3 = DateTime(2000, 12, 31)

println("Does d4 comes before d3: ", d4 < d3)
println("Difference between d4 and d3: ", d3 - d4)
println("Difference between dt2 and dt3: ", dt2 - dt3, "\n")

# access the values of a date or time
println("Year of d3: ", Dates.year(d3))
println("Week of d4: ", Dates.week(d4), "\n")

# query functions
println("Day of week for d3: ", Dates.dayofweek(d3))
println("Name of day of week for d3: ", Dates.dayname(d3))
println("Is d3 in leap year: ", Dates.isleapyear(d3), "\n")

# adjuster functions
println("First day of week for d4: ", Dates.firstdayofweek(d4))
println("Last day of month for d4: ", Dates.lastdayofmonth(d4))

max_stars_repo_name: scaotravis/Julia-Course

max_issues_repo_name: scaotravis/Julia-Course

max_forks_repo_name: scaotravis/Julia-Course


text:
# # [Illustration of the Gradient of a Second Order Difference](@id secondOrderDifferenceGrad)
#
# This example explains how to compute the gradient of the second order
# difference mid point model using [`adjoint_Jacobi_field`](@ref)s.
#
# This example also illustrates the `PowerManifold` manifold as well
# as [`ArmijoLinesearch`](@ref).

# We first initialize the manifold
exportFolder = joinpath(@__DIR__,"..","..","docs","src","assets","images","tutorials") #src
using Manopt, Manifolds
# and we define some colors from [Paul Tol](https://personal.sron.nl/~pault/)
using Colors
black = RGBA{Float64}(colorant"#000000")
TolVibrantBlue = RGBA{Float64}(colorant"#0077BB") # points
TolVibrantOrange = RGBA{Float64}(colorant"#EE7733") # results
TolVibrantCyan = RGBA{Float64}(colorant"#33BBEE") # vectors
TolVibrantTeal = RGBA{Float64}(colorant"#009988") # geo
nothing #hide
# Assume we have two points $x,y$ on the equator of the
# [Sphere](https://juliamanifolds.github.io/Manifolds.jl/stable/manifolds/sphere.html) $\mathcal M = \mathbb S^2$
# and a point $y$ near the north pole
M = Sphere(2)
p = [1., 0., 0.]
q = [0., 1., 0.]
c = mid_point(M,p,q)
#src y is the north pole just bend a little bit towards
r = shortest_geodesic(M, [0., 0., 1.], c, 0.1)
[c,r]
# Now the second order absolute difference can be stated as (see [[Bačák, Bergmann, Steidl, Weinmann, 2016](#BacakBergmannSteidlWeinmann2016)])
#
# $d_2(x,y,z) := \min_{c ∈ \mathcal C_{x,z}} d_{\mathcal M}(c,y),\qquad x,y,z∈\mathcal M,$
#
# where $\mathcal C_{x,z}$ is the set of all mid points $g(\frac{1}{2};x,z)$, where $g$
# is a (not necessarily minimizing) geodesic connecting $x$ and $z$.
#
# For illustration we further define the point opposite of
c2 = -c
# and draw the geodesic connecting $y$ and the nearest mid point $c$, namely
T = [0:0.1:1.0...]
geoPts_yc = shortest_geodesic(M,r,c,T)
nothing #hide
# looks as follows using the [`asymptote_export_S2_signals`](@ref) export
asymptote_export_S2_signals(exportFolder*"/SecondOrderData.asy"; #src
    curves = [ geoPts_yc ], #src
    points = [ [p,r,q], [c,c2] ], #src
    colors=Dict(:curves => [TolVibrantTeal], :points => [black, TolVibrantBlue]), #src
    dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5) #src
) #src
render_asymptote(exportFolder*"/SecondOrderData.asy"; render=2) #src
#md #
#md # ```julia
#md # asymptote_export_S2_signals("secondOrderData.asy";
#md #     render = asyResolution,
#md #     curves = [ geoPts_yc ],
#md #     points = [ [x,y,z], [c,c2] ],
#md #     colors=Dict(:curves => [TolVibrantTeal], :points => [black, TolVibrantBlue]),
#md #     dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5)
#md # )
#md # render_asymptote("SecondOrderData.asy"; render=2)
#md # ```
#
#md # ![Three points $p,r,q$ and the midpoint $c=c(p,q)$ (blue)](../assets/images/tutorials/SecondOrderData.png)
#
# Since we moved $r$ 10% along the geodesic from the north pole to $c$, the distance
# to $c$ is $\frac{9\pi}{20}\approx 1.4137$, and this is also what
costTV2(M, (p,r,q) )
# returns, see [`costTV2`](@ref) for reference. But also its gradient can be
# easily computed since it is just a distance with respect to $y$ and a
# concatenation of a geodesic, where the start or end point is the argument,
# respectively, with a distance.
# Hence the [adjoint differentials](@ref adjointDifferentialFunctions)
# [`adjoint_differential_geodesic_startpoint`](@ref) and [`adjoint_differential_geodesic_endpoint`](@ref) can be employed,
# see [`∇TV2`](@ref) for details.
# we obtain
(Xp, Xr, Xq) = ∇TV2(M, (p,r,q) )
#
# When we aim to minimize this, we look at the negative gradient, i.e.
# we can draw this as
asymptote_export_S2_signals(exportFolder*"/SecondOrderGradient.asy"; #src
    points = [ [p,r,q], [c,c2] ], #src
    tVectors = [Tuple.([ [p, -Xp], [r, -Xr], [q, Xq]])], #src
    colors=Dict(:tvectors => [TolVibrantCyan], :points => [black, TolVibrantBlue]), #src
    dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5) #src
) #src
render_asymptote(exportFolder*"/SecondOrderGradient.asy"; render=2) #src
#md #
#md # ```julia
#md # asymptote_export_S2_signals("SecondOrderGradient.asy";
#md #    points = [ [x,y,z], [c,c2] ],
#md #    tVectors = [Tuple.([ [p, -Xp], [r, -Xr], [q, Xq]])], #src
#md #    colors=Dict(:tvectors => [TolVibrantCyan], :points => [black, TolVibrantBlue]),
#md #    dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5)
#md # )
#md # render_asymptote("SecondOrderGradient.asy"; render=2)
#md # ```
#md #
#md # ![Three points $x,y,z$ and the negative gradient of the second order absolute difference](../assets/images/tutorials/SecondOrderGradient.png)
#
# If we now perform a gradient step, we obtain the three points
pn, rn, qn = exp.(Ref(M), [p,r,q], [-Xp,-Xr,-Xq])
# as well we the new mid point
cn = mid_point(M,pn,qn)
geoPts_yncn = shortest_geodesic(M,rn,cn,T)
nothing #hide
# and obtain the new situation
asymptote_export_S2_signals(exportFolder*"/SecondOrderMin1.asy"; #src
    points = [ [p,r,q], [c,c2,cn], [pn,rn,qn] ], #src
    curves = [ geoPts_yncn ], #src
    tVectors = [Tuple.([ [p, -Xp], [r, Xr], [q, Xq] ])], #src
    colors=Dict(:tvectors => [TolVibrantCyan], #src
                :points => [black, TolVibrantBlue, TolVibrantOrange], #src
                :curves => [TolVibrantTeal] #src
                ), #src
    dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5) #src
) #src
render_asymptote(exportFolder*"/SecondOrderMin1.asy"; render=2) #src
#md #
#md # ```julia
#md # asymptote_export_S2_signals("SecondOrderMin1.asy";
#md #     points = [ [x,y,z], [c,c2,cn], [xn,yn,zn] ],
#md #     curves = [ geoPts_yncn ] ,
#md #     tVectors = [Tuple.([ [p, -Xp], [r, Xr], [q, Xq] ])],
#md #     colors=Dict(:tvectors => [TolVibrantCyan],
#md #         :points => [black, TolVibrantBlue, TolVibrantOrange],
#md #         :curves => [TolVibrantTeal]
#md #     ),
#md #     dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5)
#md # )
#md # render_asymptote("SecondOrderMin1.asy"; render=2)
#md # ```
#md
#md # ![A gradient Step](../assets/images/tutorials/SecondOrderMin1.png)
#
# One can see, that this step slightly “overshoots”, i.e. $r$ is now even below $c$.
# and the cost function is still at
costTV2(M, (pn, rn, qn) )
#
# But we can also search for the best step size using [`ArmijoLinesearch`](@ref)
# on the `PowerManifold` manifold $\mathcal N = \mathcal M^3 = (\mathbb S^2)^3$
x = [p,r,q]
N = PowerManifold(M, NestedPowerRepresentation(),3)
s = ArmijoLinesearch(1.0,ExponentialRetraction(),0.999,0.96)(N, x,
    x -> costTV2(M, Tuple(x)),
     [ ∇TV2(M, (p,r,q))... ]  # transform from tuple to PowTVector
)
# and for the new points
pm, rm, qm = exp.(Ref(M), [p,r,q], s*[-Xp,-Xr,-Xq])
cm = mid_point(M,pm,qm)
geoPts_xmzm = shortest_geodesic(M,pm,qm,T)
nothing #hide
# we obtain again with
asymptote_export_S2_signals(exportFolder*"/SecondOrderMin2.asy"; #src
    points = [ [p,r,q], [c,c2,cm], [pm,rm,qm] ], #src
    curves = [ geoPts_xmzm ], #src
    tVectors = [Tuple.([ [p, -Xp], [r, Xr], [q, Xq] ])], #src
    colors=Dict(:tvectors => [TolVibrantCyan], #src
                :points => [black, TolVibrantBlue, TolVibrantOrange], #src
                :curves => [TolVibrantTeal] #src
                ), #src
    dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5) #src
) #src
render_asymptote(exportFolder*"/SecondOrderMin2.asy"; render=2) #src
#md #
#md # ```julia
#md # asymptote_export_S2_signals("SecondOrderMin2.asy";
#md #     points = [ [x,y,z], [c,c2,cm], [xm,ym,zm] ],
#md #     curves = [ geoPts_xmzm ] ,
#md #     tVectors = [Tuple.( [-ξx, -ξy, -ξz], [x, y, z] )],
#md #     colors=Dict(:tvectors => [TolVibrantCyan],
#md #                 :points => [black, TolVibrantBlue, TolVibrantOrange],
#md #                 :curves => [TolVibrantTeal]
#md #                 ),
#md #     dotSize = 3.5, lineWidth = 0.75, cameraPosition = (1.2,1.,.5)
#md # )
#md # render_asymptote("SecondOrderMin2.asy"; render=2) #src
#md # ```
#md #
#md # ![A gradient Step](../assets/images/tutorials/SecondOrderMin2.png)
#
# Here, the cost function yields
costTV2( M, (pm, rm, qm) )
# which is nearly zero, as one can also see, since the new center $c$ and $r$
# are quite close.
#
# ## Literature
#
# ```@raw html
# <ul>
# <li id="BačákBergmannSteidlWeinmann2016">[<a>Bačák, Bergmann, Steidl, Weinmann, 2016</a>]
#   Bačák, M; Bergmann, R.; Steidl, G; Weinmann, A.: <emph>A second order nonsmooth
#   variational model for restoring manifold-valued images.</emph>,
#   SIAM Journal on Scientific Computations, Volume 38, Number 1, pp. A567–597,
#   doi: <a href="https://doi.org/10.1137/15M101988X">10.1137/15M101988X</a></li>
# </ul>
# ```


text:
#######################################################################
# Coverage.jl
# Take Julia test coverage results and bundle them up in JSONs
# https://github.com/JuliaCI/Coverage.jl
#######################################################################

using Coverage, Base.Test, Compat
using Compat.String

# test our filename matching. These aren't exported functions but it's probably
# a good idea to have explicit tests for them, as they're used to match files
# that get deleted

@test Coverage.iscovfile("test.jl.cov")
@test Coverage.iscovfile("test.jl.2934.cov")
@test Coverage.iscovfile("/home/somebody/test.jl.2934.cov")
@test !Coverage.iscovfile("test.ji.2934.cov")
@test !Coverage.iscovfile("test.jl.2934.cove")
@test !Coverage.iscovfile("test.jicov")
@test !Coverage.iscovfile("test.c.cov")
@test Coverage.iscovfile("test.jl.cov", "test.jl")
@test !Coverage.iscovfile("test.jl.cov", "other.jl")
@test Coverage.iscovfile("test.jl.8392.cov", "test.jl")
@test Coverage.iscovfile("/somedir/test.jl.8392.cov", "/somedir/test.jl")
@test !Coverage.iscovfile("/otherdir/test.jl.cov", "/somedir/test.jl")

cd(dirname(@__DIR__)) do
    datadir = joinpath("test", "data")
    # Process a saved set of coverage data...
    r = process_file(joinpath(datadir,"Coverage.jl"))
    # ... and memory data
    analyze_malloc(datadir)
    lcov = IOBuffer()
    # we only have a single file, but we want to test on the Vector of file results
    LCOV.write(lcov, FileCoverage[r])
    open(joinpath(datadir, "expected.info")) do f
        @test String(take!(lcov)) == readstring(f)
    end

    # Test a file from scratch
    srcname = joinpath("test", "data","testparser.jl")
    covname = srcname*".cov"
    # clean out any previous coverage files. Don't use clean_folder because we
    # need to preserve the pre-baked coverage file Coverage.jl.cov
    clean_file(srcname)
    cmdstr = "include(\"$(escape_string(srcname))\"); using Base.Test; @test f2(2) == 4"
    run(`$JULIA_HOME/julia --code-coverage=user -e $cmdstr`)
    r = process_file(srcname, datadir)

    # Parsing seems to have changed slightly in Julia (or JuliaParser?) between v0.6 and v0.7.
    # Line 10 is the end of a @doc string (`""" ->`), and Line 11 is an expression (`f6(x) = 6x`)
    # In v0.6, the zero count goes to line 10, and in v0.7, the zero count goes (more correctly?)
    # to line 11

    if VERSION < v"0.7.0-DEV.468"
        target = Union{Int64,Void}[nothing, 1, nothing, 0, nothing, 0, nothing, nothing, nothing, 0, nothing, nothing, nothing, nothing, nothing, nothing, 0, nothing, nothing, 0, nothing, nothing, nothing, nothing]
    else
        target = Union{Int64,Void}[nothing, 1, nothing, 0, nothing, 0, nothing, nothing, nothing, nothing, 0, nothing, nothing, nothing, nothing, nothing, 0, nothing, nothing, 0, nothing, nothing, nothing, nothing]
    end
    @test r.coverage[1:length(target)] == target

    covtarget = (sum(x->x != nothing && x > 0, target), sum(x->x != nothing, target))
    @test get_summary(r) == covtarget
    @test get_summary(process_folder(datadir)) != covtarget

    #json_data = Codecov.build_json_data(Codecov.process_folder("data"))
    #@test typeof(json_data["coverage"]["data/Coverage.jl"]) == Array{Union{Int64,Void},1}
    open("fakefile",true,true,true,false,false)
    @test isempty(Coverage.process_cov("fakefile",datadir))
    rm("fakefile")

    # Test `using Coverage` with non-empty command-line arguments
    script = tempname()
    write(script, """
        try
            using Coverage
            println(join(ARGS, ","))
        catch
            nothing
        end
        """)
    @test readchomp(`$(Base.julia_cmd()) $script argument`) == "argument"
    rm(script)

    # Test command-line usage
    @test readchomp(`$(Base.julia_cmd()) $(joinpath("src", "Coverage.jl"))`) == "Coverage.MallocInfo[]"
end

######################
# codecovio.jl tests #
######################

"""
extracts the api URL from stdout in a codecov.io submit call
very helpful for testing codecovio.jl
"""
function extract_codecov_url(fun)
    originalSTDOUT = STDOUT

    (outRead, outWrite) = redirect_stdout()

    fun()

    close(outWrite)

    data = String(readavailable(outRead))

    close(outRead)
    redirect_stdout(originalSTDOUT)

    lines = split(data, "\n")

    url = "None"
    get_next = false
    for line in lines
        if get_next
            url = line
            get_next = false
        end
        if contains(line, "Codecov.io API URL")
            get_next = true
        end
    end

    # println("url: $(url)")
    @assert url != "None" "unable to find codecov api url in stdout, check for changes in codecovio.jl"
    return url
end

# empty file coverage for testing
fcs = FileCoverage[]

# set up base system ENV vars for testing
withenv(
    "CODECOV_URL" => nothing,
    "CODECOV_TOKEN" => nothing,
    "TRAVIS" => nothing,
    "TRAVIS_BRANCH" => nothing,
    "TRAVIS_COMMIT" => nothing,
    "TRAVIS_PULL_REQUEST" => nothing,
    "TRAVIS_JOB_ID" => nothing,
    "TRAVIS_REPO_SLUG" => nothing,
    "TRAVIS_JOB_NUMBER" => nothing,
    "APPVEYOR" => nothing,
    "APPVEYOR_PULL_REQUEST_NUMBER" => nothing,
    "APPVEYOR_ACCOUNT_NAME" => nothing,
    "APPVEYOR_PROJECT_SLUG" => nothing,
    "APPVEYOR_BUILD_VERSION" => nothing,
    "APPVEYOR_REPO_BRANCH" => nothing,
    "APPVEYOR_REPO_COMMIT" => nothing,
    "APPVEYOR_REPO_NAME" => nothing,
    "APPVEYOR_JOB_ID" => nothing,
    ) do

    # test local submission process

    # default values
    codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit_local(fcs; dry_run = true) )
    @test contains(codecov_url, "codecov.io")
    @test contains(codecov_url, "commit")
    @test contains(codecov_url, "branch")
    @test !contains(codecov_url, "service")

    # default values in depreciated call
    codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit_local(fcs) )
    @test contains(codecov_url, "codecov.io")
    @test contains(codecov_url, "commit")
    @test contains(codecov_url, "branch")
    @test !contains(codecov_url, "service")

    # env var url override
    withenv( "CODECOV_URL" => "https://enterprise-codecov-1.com" ) do

        codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit_local(fcs; dry_run = true) )
        @test contains(codecov_url, "enterprise-codecov-1.com")
        @test contains(codecov_url, "commit")
        @test contains(codecov_url, "branch")
        @test !contains(codecov_url, "service")

        # function argument url override
        codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit_local(fcs; dry_run = true, codecov_url = "https://enterprise-codecov-2.com") )
        @test contains(codecov_url, "enterprise-codecov-2.com")
        @test contains(codecov_url, "commit")
        @test contains(codecov_url, "branch")
        @test !contains(codecov_url, "service")

        # env var token
        withenv( "CODECOV_TOKEN" => "token_name_1" ) do

            codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit_local(fcs; dry_run = true) )
            @test contains(codecov_url, "enterprise-codecov-1.com")
            @test contains(codecov_url, "token=token_name_1")
            @test !contains(codecov_url, "service")

            # function argument token url override
            codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit_local(fcs; dry_run = true, token="token_name_2") )
            @test contains(codecov_url, "enterprise-codecov-1.com")
            @test contains(codecov_url, "token=token_name_2")
            @test !contains(codecov_url, "service")
        end
    end

    # test faulty non-CI submission

    @test_throws ErrorException extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )

    # test travis-ci submission process

    # set up travis env
    withenv(
        "TRAVIS" => "true",
        "TRAVIS_BRANCH" => "t_branch",
        "TRAVIS_COMMIT" => "t_commit",
        "TRAVIS_PULL_REQUEST" => "t_pr",
        "TRAVIS_JOB_ID" => "t_job_id",
        "TRAVIS_REPO_SLUG" => "t_slug",
        "TRAVIS_JOB_NUMBER" => "t_job_num",
        ) do

        # default values
        codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
        @test contains(codecov_url, "codecov.io")
        @test contains(codecov_url, "service=travis-org")
        @test contains(codecov_url, "branch=t_branch")
        @test contains(codecov_url, "commit=t_commit")
        @test contains(codecov_url, "pull_request=t_pr")
        @test contains(codecov_url, "job=t_job_id")
        @test contains(codecov_url, "slug=t_slug")
        @test contains(codecov_url, "build=t_job_num")

        # env var url override
        withenv( "CODECOV_URL" => "https://enterprise-codecov-1.com" ) do

            codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
            @test contains(codecov_url, "enterprise-codecov-1.com")
            @test contains(codecov_url, "service=travis-org")
            @test contains(codecov_url, "branch=t_branch")
            @test contains(codecov_url, "commit=t_commit")
            @test contains(codecov_url, "pull_request=t_pr")
            @test contains(codecov_url, "job=t_job_id")
            @test contains(codecov_url, "slug=t_slug")
            @test contains(codecov_url, "build=t_job_num")

            # function argument url override
            codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true, codecov_url = "https://enterprise-codecov-2.com") )
            @test contains(codecov_url, "enterprise-codecov-2.com")
            @test contains(codecov_url, "service=travis-org")
            @test contains(codecov_url, "branch=t_branch")
            @test contains(codecov_url, "commit=t_commit")
            @test contains(codecov_url, "pull_request=t_pr")
            @test contains(codecov_url, "job=t_job_id")
            @test contains(codecov_url, "slug=t_slug")
            @test contains(codecov_url, "build=t_job_num")

            # env var token
            withenv( "CODECOV_TOKEN" => "token_name_1" ) do

                codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
                @test contains(codecov_url, "enterprise-codecov-1.com")
                @test contains(codecov_url, "token=token_name_1")
                @test contains(codecov_url, "branch=t_branch")
                @test contains(codecov_url, "commit=t_commit")
                @test contains(codecov_url, "pull_request=t_pr")
                @test contains(codecov_url, "job=t_job_id")
                @test contains(codecov_url, "slug=t_slug")
                @test contains(codecov_url, "build=t_job_num")

                # function argument token url override
                codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true, token="token_name_2") )
                @test contains(codecov_url, "enterprise-codecov-1.com")
                @test contains(codecov_url, "token=token_name_2")
                @test contains(codecov_url, "branch=t_branch")
                @test contains(codecov_url, "commit=t_commit")
                @test contains(codecov_url, "pull_request=t_pr")
                @test contains(codecov_url, "job=t_job_id")
                @test contains(codecov_url, "slug=t_slug")
                @test contains(codecov_url, "build=t_job_num")
            end
        end
    end

    # test appveyor submission process

    # set up appveyor env
    withenv(
        "APPVEYOR" => "true",
        "APPVEYOR_PULL_REQUEST_NUMBER" => "t_pr",
        "APPVEYOR_ACCOUNT_NAME" => "t_account",
        "APPVEYOR_PROJECT_SLUG" => "t_slug",
        "APPVEYOR_BUILD_VERSION" => "t_version",
        "APPVEYOR_REPO_BRANCH" => "t_branch",
        "APPVEYOR_REPO_COMMIT" => "t_commit",
        "APPVEYOR_REPO_NAME" => "t_repo",
        "APPVEYOR_JOB_ID" => "t_job_num",
        ) do

            # default values
            codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
            @test contains(codecov_url, "codecov.io")
            @test contains(codecov_url, "service=appveyor")
            @test contains(codecov_url, "branch=t_branch")
            @test contains(codecov_url, "commit=t_commit")
            @test contains(codecov_url, "pull_request=t_pr")
            @test contains(codecov_url, "job=t_account%2Ft_slug%2Ft_version")
            @test contains(codecov_url, "build=t_job_num")

            # env var url override
            withenv( "CODECOV_URL" => "https://enterprise-codecov-1.com" ) do

                codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
                @test contains(codecov_url, "enterprise-codecov-1.com")
                @test contains(codecov_url, "service=appveyor")
                @test contains(codecov_url, "branch=t_branch")
                @test contains(codecov_url, "commit=t_commit")
                @test contains(codecov_url, "pull_request=t_pr")
                @test contains(codecov_url, "job=t_account%2Ft_slug%2Ft_version")
                @test contains(codecov_url, "build=t_job_num")

                # function argument url override
                codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true, codecov_url = "https://enterprise-codecov-2.com") )
                @test contains(codecov_url, "enterprise-codecov-2.com")
                @test contains(codecov_url, "service=appveyor")
                @test contains(codecov_url, "branch=t_branch")
                @test contains(codecov_url, "commit=t_commit")
                @test contains(codecov_url, "pull_request=t_pr")
                @test contains(codecov_url, "job=t_account%2Ft_slug%2Ft_version")
                @test contains(codecov_url, "build=t_job_num")

                # env var token
                withenv( "CODECOV_TOKEN" => "token_name_1" ) do

                    codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
                    @test contains(codecov_url, "enterprise-codecov-1.com")
                    @test contains(codecov_url, "token=token_name_1")
                    @test contains(codecov_url, "branch=t_branch")
                    @test contains(codecov_url, "commit=t_commit")
                    @test contains(codecov_url, "pull_request=t_pr")
                    @test contains(codecov_url, "job=t_account%2Ft_slug%2Ft_version")
                    @test contains(codecov_url, "build=t_job_num")

                    # function argument token url override
                    codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true, token="token_name_2") )
                    @test contains(codecov_url, "enterprise-codecov-1.com")
                    @test contains(codecov_url, "token=token_name_2")
                    @test contains(codecov_url, "branch=t_branch")
                    @test contains(codecov_url, "commit=t_commit")
                    @test contains(codecov_url, "pull_request=t_pr")
                    @test contains(codecov_url, "job=t_account%2Ft_slug%2Ft_version")
                    @test contains(codecov_url, "build=t_job_num")
                end
            end
        end

    # test circle ci submission process

    # set up circle ci env
    withenv(
        "CIRCLECI" => "true",
        "CIRCLE_PR_NUMBER" => "t_pr",
        "CIRCLE_PROJECT_USERNAME" => "t_proj",
        "CIRCLE_BRANCH" => "t_branch",
        "CIRCLE_SHA1" => "t_commit",
        "CIRCLE_PROJECT_REPONAME" => "t_repo",
        "CIRCLE_BUILD_URL" => "t_url",
        "CIRCLE_BUILD_NUM" => "t_num",
        ) do

        # default values
        codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
        @test contains(codecov_url, "codecov.io")
        @test contains(codecov_url, "service=circleci")
        @test contains(codecov_url, "branch=t_branch")
        @test contains(codecov_url, "commit=t_commit")
        @test contains(codecov_url, "pull_request=t_pr")
        @test contains(codecov_url, "build_url=t_url")
        @test contains(codecov_url, "build=t_num")

        # env var url override
        withenv( "CODECOV_URL" => "https://enterprise-codecov-1.com" ) do

            codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
            @test contains(codecov_url, "enterprise-codecov-1.com")
            @test contains(codecov_url, "service=circleci")
            @test contains(codecov_url, "branch=t_branch")
            @test contains(codecov_url, "commit=t_commit")
            @test contains(codecov_url, "pull_request=t_pr")
            @test contains(codecov_url, "build_url=t_url")
            @test contains(codecov_url, "build=t_num")

            # function argument url override
            codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true, codecov_url = "https://enterprise-codecov-2.com") )
            @test contains(codecov_url, "enterprise-codecov-2.com")
            @test contains(codecov_url, "service=circleci")
            @test contains(codecov_url, "branch=t_branch")
            @test contains(codecov_url, "commit=t_commit")
            @test contains(codecov_url, "pull_request=t_pr")
            @test contains(codecov_url, "build_url=t_url")
            @test contains(codecov_url, "build=t_num")

            # env var token
            withenv( "CODECOV_TOKEN" => "token_name_1" ) do

                codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true) )
                @test contains(codecov_url, "enterprise-codecov-1.com")
                @test contains(codecov_url, "token=token_name_1")
                @test contains(codecov_url, "service=circleci")
                @test contains(codecov_url, "branch=t_branch")
                @test contains(codecov_url, "commit=t_commit")
                @test contains(codecov_url, "pull_request=t_pr")
                @test contains(codecov_url, "build_url=t_url")
                @test contains(codecov_url, "build=t_num")

                # function argument token url override
                codecov_url = extract_codecov_url( () -> Coverage.Codecov.submit(fcs; dry_run = true, token="token_name_2") )
                @test contains(codecov_url, "enterprise-codecov-1.com")
                @test contains(codecov_url, "service=circleci")
                @test contains(codecov_url, "branch=t_branch")
                @test contains(codecov_url, "commit=t_commit")
                @test contains(codecov_url, "pull_request=t_pr")
                @test contains(codecov_url, "build_url=t_url")
                @test contains(codecov_url, "build=t_num")
            end
        end
    end
end


text:
legendlines = OrderedDict(
                            :homing => (linestyle = nothing, linewidth = 0.3, color = :black),
                            :searching => (linestyle = nothing, linewidth = 0.3, color = :gray),
                           )
markers = Dict(:turning_point => '•', :center_of_search => '■')
legendmarkers = OrderedDict(
                            :nest => (color = :black, marker = '⋆', strokecolor = :black, markerstrokewidth = 0.5, strokewidth = 0.5, markersize = 15px),
                            :feeder => (color = :transparent, marker = '•', strokecolor = :black, markerstrokewidth = 0.5, strokewidth = 0.5, markersize = 25px),
                            :fictive_nest => (color = :white, marker = '⋆', strokecolor = :black, markerstrokewidth = 0.5, strokewidth = 0.5, markersize = 10px),
                            :dropoff => (color = :black, marker = '↓', strokecolor = :white, markerstrokewidth = 0.5, strokewidth = 0.1, markersize = 15px),
                            :pickup => (color = :black, marker = '↑', strokecolor = :white, markerstrokewidth = 0.5, strokewidth = 0.1, markersize = 15px),
                            :turning_point => (color = :black, marker = markers[:turning_point], strokecolor = :transparent, markersize = 15px),
                            :center_of_search => (color = :black, marker = markers[:center_of_search], strokecolor = :transparent, markersize = 5px),
                            :pellets => (color = :black, marker = '▴', strokecolor = :black, markerstrokewidth = 0.5, strokewidth = 0.5, markersize = 15px),
                                          )
function plotrun(x)

    fig = Figure(resolution = (1000,1000))
    ax = fig[1, 1] = Axis(fig, aspect = DataAspect(), xlabel = "X (cm)", ylabel = "Y (cm)")

    # scene, layout = layoutscene()
    # ax = layout[1,1] = LAxis(scene, aspect = DataAspect(), xlabel = "X (cm)", ylabel = "Y (cm)")#, yreversed = true)
    h = OrderedDict()
    for (k,v) in legendlines
        h[k] = lines!(ax, getproperty(x, k); v...)
    end
    for (k,v) in legendmarkers
        xy = getproperty(x, k)
        if !ismissing(xy) && !isempty(xy)
            h[k] = scatter!(ax, xy; v...)
        end
    end
    fig[0,1] = Legend(fig, collect(values(h)), string.(keys(h)), orientation = :vertical, nbanks = 5, tellheight = true, height = Auto(), groupgap = 30);

    for radius in intervals
        lines!(ax, Circle(Point(x.dropoff), radius), color = :red)
    end
    fig
end

function plotrun_of_tracks(x)
    fig = Figure()
    ax = fig[1, 1] = Axis(fig, aspect = DataAspect(), xlabel = "X (cm)", ylabel = "Y (cm)")
    # scene, layout = layoutscene()
    # ax = layout[1,1] = LAxis(scene, aspect = DataAspect(), xlabel = "X (cm)", ylabel = "Y (cm)")#, yreversed = true)
    for (k,v) in x
        lines!(ax, space(v))
    end
    fig
end

ntentries = OrderedDict(:homing => (linestyle = nothing, linewidth = 1, color = :black),
                        :fictive_nest => (color = :white, marker = '⋆', strokecolor = :black, markerstrokewidth = 1, strokewidth = 1, markersize = 15px),
                        :dropoff => (color = :black, marker = '↓', markersize = 15px),
                        :turning_point => (color = :black, marker = '•', strokecolor = :transparent, markersize = 15px),
                       )
labels = Dict(:homing => "Tracks",
              :fictive_nest => "Fictive nest",
              :dropoff => "Dropoff",
              :turning_point => "Turning points",
             )

function plotruns(xs::Vector{Standardized})

  fig = Figure(resolution = (1000,1000))
  ax = fig[1, 1] = Axis(fig, aspect = DataAspect(), xlabel = "X (cm)", ylabel = "Y (cm)")

  colors = range(colorant"red", stop=colorant"black", length=length(xs) + 1)
  pop!(colors)

  for (isfirst, (x, color)) in flagfirst(zip(xs, colors))
    s = :homing
    l = lines!(ax, getproperty(x, s); ntentries[s]..., color = color)
    isfirst && (l.label = labels[s])
    s = :turning_point
    l = scatter!(ax, x.turning_point; ntentries[s]..., color = color)
    isfirst && (l.label = labels[s])
  end
  x = xs[1]
  for k in (:fictive_nest, :dropoff)
    l = scatter!(ax, getproperty(x, k); ntentries[k]...)
    l.label = labels[k]
  end

  limits!(ax.finallimits[])

  for radius in MAT2db.intervals
    lines!(ax, Circle(Point(x.dropoff), radius), color = :grey)
    text!(ax, string(radius), position = Point2f0(0, radius - x.nest2feeder), align = (:left, :baseline))
  end

  Legend(fig[0,1], ax, orientation = :vertical, nbanks = 2, tellheight = true, height = Auto(), groupgap = 30);

  fig
end