using JLD2
using FileIO
using ProgressMeter
using StatsBase

include("Simulation/enums.jl")

trajectory(times::AbstractVector{T} where T) = filter(isfinite, times) |> sort!
daily(times::AbstractArray{T} where T, max_days::Integer) = fit(Histogram, times, 0:(max_days) ).weights
daily(times::AbstractVector{T} where T) = daily(times, maximum(times))

const daily_trajectory = daily ∘ trajectory

if length(ARGS) < 1
  error("give path to the grid tiles and number of trajectories(optional)")
end

output_path = ARGS[1]
num_paths = length(ARGS) == 2 ? parse(Int, ARGS[2]) : 100

output_file = jldopen(output_path*"/daily_trajectories.jld2", "w", compress=true)
try
  hospitalization_progressions, death_progressions = load(output_path*"/run_params.jld2",
    "progressions/severe_symptoms_times",
    "progressions/death_times")

  @showprogress for i in 1:num_paths
    run_path = output_path*"/run_$i.jld2"

    if !isfile(run_path)
      @warn "could not load run file $run_path, stopping"
      break
    end

    try
      infection_times, detection_times, contact_kinds = load(run_path, "infection_times", "detection_times", "contact_kinds")

      max_infection_time = filter(!isnan, infection_times) |> maximum
      max_detection_time = filter(!isnan, detection_times) |> maximum
      max_days = min(max_infection_time, max_detection_time) |> floor |> Int

      trajectory_group = JLD2.Group(output_file, string(i))

      trajectory_group["daily_infections"] = daily( infection_times |> trajectory, max_days)
      trajectory_group["daily_detections"] = daily( detection_times |> trajectory, max_days)
      trajectory_group["daily_deaths"] = daily(infection_times.+death_progressions |> trajectory, max_days)
      trajectory_group["daily_hospitalizations"] = daily(infection_times.+hospitalization_progressions |> trajectory, max_days)
      for kind in values(instances(ContactKind))
        if kind == NoContact
          continue
        end
        trajectory_group["daily_" * lowercase(string(kind))] = daily(infection_times[contact_kinds.==Int(kind)], max_days)
      end
    catch ex
      @error "problem processing file " run_path ex
    end

  end
catch ex
  @error ex
finally
  close(output_file)
end


@testset "File splitting" begin

    W = addprocs(8)
    @everywhere using GigaSOM

    # re-use the PBMC8 data:
    # try load it by parts of different size and scrutinize the parts

    for splitSize = 1:8
        di = loadFCSSet(:test, md[:, :file_name], W[1:splitSize])
        dselect(di, fcsAntigens, antigens)
        cols = Vector(1:length(antigens))
        dtransform_asinh(di, cols, 5)
        dscale(di, cols)
        @test isapprox(pbmc8_data, gather_array(di), atol = 1e-4)

        splits = dmapreduce(di, d -> size(d, 1), vcat)
        @test sum(splits) == size(pbmc8_data, 1)
        @test minimum(splits) + 1 >= maximum(splits)

        sizes = loadFCSSizes(md[:, :file_name])
        dis = distributeFCSFileVector(:testF, md[:, :file_name], W[1:splitSize])
        @test dcount(length(sizes), dis) == sizes
        @test dcount_buckets(length(sizes), dis, length(sizes), dis) ==
              Matrix{Int}(LinearAlgebra.Diagonal(sizes))
    end

    rmprocs(W)
end


export
    ident,
    illum,
    illum_pl02,
    illum_plid_pl02,
    illumf,
    illumg,
    ilumin,
    inedpl,
    inelpl,
    inrypl,
    insrtc!,
    insrtc,
    insrtd!,
    insrtd,
    insrti!,
    insrti,
    inter,
    intmax,
    intmin,
    invert,
    invort,
    isordv,
    isrchc,
    isrchd,
    isrchi,
    isrot,
    iswhsp

function _ident()
    matrix = Array{SpiceDouble}(undef, 3, 3)
    ccall((:ident_c, libcspice), Cvoid, (Ref{SpiceDouble},), matrix)
    matrix
end

@deprecate ident() Array{Float64}(LinearAlgebra.I, 3, 3)

"""
    illumf(method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint)

Compute the illumination angles - phase, incidence, and emission - at a specified point on a
target body. Return logical flags indicating whether the surface point is visible from the
observer's position and whether the surface point is illuminated.

The target body's surface is represented using topographic data provided by DSK files, or by a
reference ellipsoid.

The illumination source is a specified ephemeris object.

### Arguments ###

- `method`: Computation method
- `target`: Name of target body
- `ilusrc`: Name of illumination source
- `et`: Epoch in TDB seconds past J2000 TDB
- `fixref`: Body-fixed, body-centered target body frame
- `abcorr`: Aberration correction flag
- `obsrvr`: Name of observing body
- `spoint`: Body-fixed coordinates of a target surface point

### Output ###

- `trgepc`: Target surface point epoch
- `srfvec`: Vector from observer to target surface point
- `phase`: Phase angle at the surface point
- `incdnc`: Source incidence angle at the surface point
- `emissn`: Emission angle at the surface point
- `visibl`: Visibility flag (`true` if visible)
- `lit`: Illumination flag (`true` if illuminated)

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html)
"""
function illumf(method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint)
    @checkdims 3 spoint
    trgepc = Ref{SpiceDouble}()
    srfvec = Array{SpiceDouble}(undef, 3)
    phase = Ref{SpiceDouble}()
    incdnc = Ref{SpiceDouble}()
    emissn = Ref{SpiceDouble}()
    visibl = Ref{SpiceBoolean}()
    lit = Ref{SpiceBoolean}()
    ccall((:illumf_c, libcspice), Cvoid,
          (Cstring, Cstring, Cstring, SpiceDouble, Cstring, Cstring, Cstring, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceBoolean}, Ref{SpiceBoolean}),
          method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint, trgepc, srfvec, phase, incdnc,
          emissn, visibl, lit)
    handleerror()
    trgepc[], srfvec, phase[], incdnc[], emissn[], Bool(visibl[]), Bool(lit[])
end

"""
    illumg(method, target, ilusrc, et, fixref, obsrvr, spoint, abcorr)

Find the illumination angles (phase, incidence, and emission) at a specified surface point of a
target body.

The surface of the target body may be represented by a triaxial ellipsoid or by topographic data
provided by DSK files.

The illumination source is a specified ephemeris object.

### Arguments ###

- `method`: Computation method.
- `target`: Name of target body.
- `ilusrc`: Name of illumination source.
- `et`: Epoch in ephemeris seconds past J2000 TDB.
- `fixref`: Body-fixed, body-centered target body frame.
- `obsrvr`: Name of observing body.
- `spoint`: Body-fixed coordinates of a target surface point.
- `abcorr`: Aberration correction.

### Output ###

- `trgepc`: Sub-solar point epoch.
- `srfvec`: Vector from observer to sub-solar point.
- `phase`: Phase angle at the surface point.
- `incdnc`: Solar incidence angle at the surface point.
- `emissn`: Emission angle at the surface point.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumg_c.html)
"""
function illumg(method, target, ilusrc, et, fixref, obsrvr, spoint; abcorr="NONE")
    trgepc = Ref{SpiceDouble}(0)
    srfvec = Array{SpiceDouble}(undef, 3)
    phase  = Ref{SpiceDouble}(0)
    incdnc = Ref{SpiceDouble}(0)
    emissn = Ref{SpiceDouble}(0)
    ccall((:illumg_c, libcspice), Cvoid,
          (Cstring, Cstring, Cstring, SpiceDouble, Cstring, Cstring, Cstring, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble},
           Ref{SpiceDouble}),
          method, target, ilusrc, et, fixref, abcorr, obsrvr, spoint, trgepc, srfvec, phase,
          incdnc, emissn)
    handleerror()
    trgepc[], srfvec, phase[], incdnc[], emissn[]
end

@deprecate illum illumin
@deprecate illum_pl02 illumin
@deprecate illum_plid_pl02 illumin

"""
    ilumin(method, target, et, fixref, obsrvr, spoint, abcorr)

Find the illumination angles (phase, solar incidence, and emission) at a specified surface point
of a target body.

### Arguments ###

- `method`: Computation method
- `target`: Name of target body
- `et`: Epoch in ephemeris seconds past J2000 TDB
- `fixref`: Body-fixed, body-centered target body frame
- `obsrvr`: Name of observing body
- `spoint`: Body-fixed coordinates of a target surface point
- `abcorr`: Aberration correction

### Output ###

- `trgepc`: Sub-solar point epoch
- `srfvec`: Vector from observer to sub-solar point
- `phase`: Phase angle at the surface point
- `incdnc`: Solar incidence angle at the surface point
- `emissn`: Emission angle at the surface point

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/ilumin_c.html)
"""
function ilumin(method, target, et, fixref, obsrvr, spoint; abcorr="NONE")
    trgepc = Ref{SpiceDouble}()
    srfvec = Array{SpiceDouble}(undef, 3)
    phase  = Ref{SpiceDouble}()
    incdnc = Ref{SpiceDouble}()
    emissn = Ref{SpiceDouble}()
    ccall((:ilumin_c, libcspice), Cvoid,
          (Cstring, Cstring, SpiceDouble, Cstring, Cstring, Cstring, Ref{SpiceDouble},
           Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{SpiceDouble},
           Ref{SpiceDouble}),
          method, target, et, fixref, abcorr, obsrvr, spoint, trgepc, srfvec, phase, incdnc, emissn)
    handleerror()
    trgepc[], srfvec, phase[], incdnc[], emissn[]
end

"""
    inedpl(a, b, c, plane)

Find the intersection of a triaxial ellipsoid and a plane.

### Arguments ###

- `a`: Length of ellipsoid semi-axis lying on the x-axis
- `b`: Length of ellipsoid semi-axis lying on the y-axis
- `c`: Length of ellipsoid semi-axis lying on the z-axis
- `plane`: Plane that intersects ellipsoid

### Output ###

- `ellipse`: Intersection ellipse

Returns `nothing` if no ellipse could be found.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inedpl_c.html)
"""
function inedpl(a, b, c, plane)
    ellipse = Ref{Ellipse}()
    found = Ref{SpiceBoolean}()
    ccall((:inedpl_c, libcspice), Cvoid,
          (SpiceDouble, SpiceDouble, SpiceDouble, Ref{Plane}, Ref{Ellipse}, Ref{SpiceBoolean}),
          a, b, c, plane, ellipse, found)
    handleerror()
    Bool(found[]) || return nothing
    ellipse[]
end

"""
    inelpl(ellips, plane)

Find the intersection of an ellipse and a plane.

### Arguments ###

- `ellips`: An ellipse
- `plane`: A plane

### Output ###

- `nxpts`: Number of intersection points of ellipse and plane
- `xpt1`, `xpt2`: Intersection points

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inelpl_c.html)
"""
function inelpl(ellips, plane)
    nxpts = Ref{SpiceInt}()
    xpt1 = Array{SpiceDouble}(undef, 3)
    xpt2 = Array{SpiceDouble}(undef, 3)
    ccall((:inelpl_c, libcspice), Cvoid,
          (Ref{Ellipse}, Ref{Plane}, Ref{SpiceInt}, Ref{SpiceDouble}, Ref{SpiceDouble}),
          ellips, plane, nxpts, xpt1, xpt2)
    handleerror()
    Int(nxpts[]), xpt1, xpt2
end

"""
    inrypl(vertex, dir, plane)

Find the intersection of a ray and a plane.

### Arguments ###

- `vertex`, `dir`: Vertex and direction vector of ray
- `plane`: A plane

### Output ###

- `nxpts`: Number of intersection points of ray and plane
- `xpt1`, `xpt2`: Intersection points

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inrypl_c.html)
"""
function inrypl(vertex, dir, plane)
    @checkdims 3 vertex dir
    nxpts = Ref{SpiceInt}()
    xpt = Array{SpiceDouble}(undef, 3)
    ccall((:inrypl_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}, Ref{Plane}, Ref{SpiceInt}, Ref{SpiceDouble}),
          vertex, dir, plane, nxpts, xpt)
    handleerror()
    Int(nxpts[]), xpt
end

"""
    insrtc!(set, item)

Insert an item into a character set.

### Arguments ###

- `set`: Insertion set
- `item`: Item to be inserted

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/insrtc_c.html)
"""
function insrtc!(set, item)
    ccall((:insrtc_c, libcspice), Cvoid, (Cstring, Ref{Cell{SpiceChar}}), item, set.cell)
    set
end

@deprecate insrtc insrtc!

"""
    insrtd!(set, item)

Insert an item into a double set.

### Arguments ###

- `set`: Insertion set
- `item`: Item to be inserted

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/insrtd_c.html)
"""
function insrtd!(set, item)
    ccall((:insrtd_c, libcspice), Cvoid, (SpiceDouble, Ref{Cell{SpiceDouble}}), item, set.cell)
    set
end

@deprecate insrtd insrtd!

"""
    insrti!(set, item)

Insert an item into an integer set.

### Arguments ###

- `set`: Insertion set
- `item`: Item to be inserted

### Output ###

Returns the updated set.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/insrti_c.html)
"""
function insrti!(set, item)
    ccall((:insrti_c, libcspice), Cvoid, (SpiceInt, Ref{Cell{SpiceInt}}), item, set.cell)
    set
end

@deprecate insrti insrti!

"""
    inter(a, b)

Intersect two sets of any data type to form a third set.

### Arguments ###

- `a`: First input set
- `b`: Second input set

### Output ###

Returns intersection of a and b.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/inter_c.html)
"""
function inter(a::SpiceCell{T}, b::SpiceCell{T}) where {T}
    n = card(a) + card(b)
    l = max(a.cell.length, b.cell.length)
    out = SpiceCell{T}(n, l)
    ccall((:inter_c, libcspice), Cvoid,
              (Ref{Cell{T}}, Ref{Cell{T}}, Ref{Cell{T}}),
              a.cell, b.cell, out.cell)
    handleerror()
    out
end

function _intmax()
    ccall((:intmax_c, libcspice), SpiceInt, ())
end

@deprecate intmax() typemax(Cint)

"""
    intmax()

!!! warning "Deprecated"
    Use `typemax(Cint)` instead.
"""
intmax

function _intmin()
    ccall((:intmin_c, libcspice), SpiceInt, ())
end

@deprecate intmin() typemin(Cint)

"""
    intmin()

!!! warning "Deprecated"
    Use `typemin(Cint)` instead.
"""
intmin

function _invert(matrix)
    out = Array{SpiceDouble}(undef, 3, 3)
    ccall((:invert_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}),
          matrix, out)
    out
end

@deprecate invert inv

"""
    invert(matrix)

!!! warning "Deprecated"
    Use `inv(matrix)` instead.
"""
invert

function _invort(matrix)
    out = Array{SpiceDouble}(undef, 3, 3)
    ccall((:invort_c, libcspice), Cvoid,
          (Ref{SpiceDouble}, Ref{SpiceDouble}),
          matrix, out)
    out
end

@deprecate invort inv

"""
    invort(matrix)

!!! warning "Deprecated"
    Use `inv(matrix)` instead.
"""
invort

function _isordv(vec)
    vec_ = SpiceInt.(vec .- 1)
    n = length(vec)
    res = ccall((:isordv_c, libcspice), SpiceBoolean,
                (Ref{SpiceInt}, SpiceInt),
                vec_, n)
    Bool(res[])
end

@deprecate isordv isperm

"""
    isordv(vec)

!!! warning "Deprecated"
    Use `isperm(vec)` instead.
"""
isordv

function _isrchc(value, array)
    array_, m, n = chararray(array)
    res = ccall((:isrchc_c, libcspice), SpiceInt,
                (Cstring, SpiceInt, SpiceInt, Ref{SpiceChar}),
                value, m, n, array_)
    handleerror()
    res[] + 1
end

@deprecate isrchc(value, array) findfirst(array .== item)

"""
    isrchc(value, array)

!!! warning "Deprecated"
    Use `findfirst(array .== value)` instead.
"""
isrchc

function _isrchd(value, array)
    n = length(array)
    res = ccall((:isrchd_c, libcspice), SpiceBoolean,
                (SpiceDouble, SpiceInt, Ref{SpiceDouble}),
                value, n, array)
    handleerror()
    res[] + 1
end

@deprecate isrchd(value, array) findfirst(array .== item)

"""
    isrchd(value, array)

!!! warning "Deprecated"
    Use `findfirst(array .== value)` instead.
"""
isrchd

function _isrchi(value, array)
    array_ = SpiceInt.(array)
    n = length(array)
    res = ccall((:isrchi_c, libcspice), SpiceBoolean,
                (SpiceInt, SpiceInt, Ref{SpiceInt}),
                value, n, array_)
    handleerror()
    res[] + 1
end

@deprecate isrchi(value, array) findfirst(array .== item)

"""
    isrchi(value, array)

!!! warning "Deprecated"
    Use `findfirst(array .== value)` instead.
"""
isrchi

"""
    isrot(m, ntol, dtol)

Indicate whether a 3x3 matrix is a rotation matrix.

### Arguments ###

- `m`: A matrix to be tested
- `ntol`: Tolerance for the norms of the columns of `m`
- `dtol`: Tolerance for the determinant of a matrix whose columns are the unitized columns of `m`

### Output ###

Returns `true` if `m` is a rotation matrix.

### References ###

- [NAIF Documentation](https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/isrot_c.html)
"""
function isrot(m, ntol, dtol)
    @checkdims 3 3 m
    res = ccall((:isrot_c, libcspice), SpiceBoolean,
                (Ref{SpiceDouble}, SpiceDouble, SpiceDouble),
                m, ntol, dtol)
    handleerror()
    Bool(res[])
end

function _iswhsp(str)
    res = ccall((:iswhsp_c, libcspice), SpiceBoolean,
                (Cstring,),
                str)
    handleerror()
    Bool(res[])
end

@deprecate iswhsp(str) isempty(strip(str))

"""
    iswhsp(str)

!!! warning "Deprecated"
    Use `isempty(strip(str))` instead.
"""
iswhsp


x = 0.0
for i in 1:1000000
    global x += 0.1
end

println("result=", x)


function []=PlotFaceKiteGrid(Grid,fig)
figure(fig)
clf(fig)
hold on;
for iF=1:size(Grid.Faces,2)
  for iK=1:size(Grid.Faces(iF).Kite.Faces,2)
    X=zeros(size(Grid.Faces(iF).Kite.Faces(iK).N,2),3);
    for i=1:size(Grid.Faces(iF).Kite.Faces(iK).N,2)
      X(i,:)=Grid.Faces(iF).Kite.Nodes(Grid.Faces(iF).Kite.Faces(iK).N(i)).P;
    end
    fill3(X(:,1),X(:,2),X(:,3),0);
  end
end
hold off
end


#Run this in Escher to see a rotating cube in the browser.

import ThreeJS

main(window) =  begin
    push!(window.assets,("ThreeJS","threejs"))
    eventloop = every(1/60)
    rx = 0.0
    ry = 0.0
    rz = 0.0
    map(eventloop) do _
        rx += 0.5
        ry += 0.5
        rz += 0.5
        ThreeJS.outerdiv() <<
                (ThreeJS.initscene() <<
                    [
                        ThreeJS.mesh(0.0, 0.0, 0.0; rx=rx, ry=ry, rz=rz) <<
                        [
                            ThreeJS.box(5.0, 5.0, 5.0), ThreeJS.material(Dict(:kind=>"phong",:color=>"red"))
                        ],
                        ThreeJS.pointlight(150.0, 150.0, 150.0),
                        ThreeJS.pointlight(-150.0, -150.0, -150.0),
                        ThreeJS.camera(0.0, 0.0, 250.0)
                    ]
                )
        end
end


# Assumes that the ptr does not alias
# Represents this via not actually loading/storing value to the ptr until some finalization step.
struct Reference{T,V}
    ptr::Ptr{T}
    value::V
end

Base.:(+)(r::Reference, x) = Reference(r.ptr, vadd(r.value, x))
Base.:(-)(r::Reference, x) = Reference(r.ptr, vsub(r.value, x))
Base.:(*)(r::Reference, x) = Reference(r.ptr, vmul(r.value, x))
Base.:(/)(r::Reference, x) = Reference(r.ptr, vdiv(r.value, x))
Base.:(+)(x, r::Reference) = Reference(r.ptr, vadd(x, r.value))
Base.:(-)(x, r::Reference) = Reference(r.ptr, vsub(x, r.value))
Base.:(*)(x, r::Reference) = Reference(r.ptr, vmul(x, r.value))
Base.:(/)(x, r::Reference) = Reference(r.ptr, vdiv(x, r.value))
Base.exp(r::Reference) = Reference(r.ptr, exp(r.value))
Base.log(r::Reference) = Reference(r.ptr, log(r.value))


# Definition of StrBootTest "class" for holding intermediate results, with associated utilities and get functions

"Auxilliary weight types: `rademacher`, `mammen`, `webb`, `normal`, `gamma`"
@enum AuxWtType rademacher mammen webb normal gamma

"p value types: `symmetric`, `equaltail`, `lower`, `upper`"
@enum PType symmetric equaltail lower upper

"Multiple hypothesis adjustment types: `nomadj`, `bonferroni`, `sidak`"
@enum MAdjType nomadj bonferroni sidak

"Bootstrap distribution statistics optionally returned"
@enum DistStatType nodist t numer

struct StrClust{T<:Real}
	N::Int; multiplier::T; even::Bool
	order::Vector{Int64}
	info::Vector{UnitRange{Int64}}
end

struct StrFE{T<:Real}
	is::SubArray{Int64, 1, Vector{Int64}, Tuple{UnitRange{Int64}}, true}
  wt::Vector{T}
end

mutable struct StrEstimator{T<:AbstractFloat}
  isDGP::Bool; LIML::Bool; Fuller::T; κ::T
  R₁perp::Matrix{T}; Rpar::Matrix{T}

  kZ::Int64
  y₁::Vector{T}; ü₁::Vector{T}; u⃛₁::Vector{T}; β̈::Vector{T}; γ̈::Vector{T}; β̂₀::Vector{T}; invXXXy₁par::Vector{T}
  Yendog::Vector{Bool}
  invZperpZperp::Symmetric{T,Matrix{T}}; XZ::Matrix{T}; PXZ::Matrix{T}; YPXY::Symmetric{T,Matrix{T}}; R₁invR₁R₁::Matrix{T}
	restricted::Bool; RperpX::Matrix{T}; RperpXperp::Matrix{T}; RRpar::Matrix{T}; RparY::Matrix{T}; RR₁invR₁R₁::Matrix{T}
	∂β̂∂r::Matrix{T}; YY::Symmetric{T,Matrix{T}}; AR::Matrix{T}; XAR::Matrix{T}; R₁invR₁R₁Y::Matrix{T}; invXXXZ::Matrix{T}; Ü₂::Matrix{T}; XinvXX::Matrix{T}; Rt₁::Vector{T}
	invXX::Symmetric{T,Matrix{T}}; Y₂::Matrix{T}; X₂::Matrix{T}; invH::Symmetric{T,Matrix{T}}
	y₁par::Vector{T}; Xy₁par::Vector{T}
	A::Symmetric{T,Matrix{T}}; Z::Matrix{T}; Zperp::Matrix{T}; X₁::Matrix{T}
	FillingT₀::Matrix{Matrix{T}}
	WXAR::Matrix{T}; S⋂PXYZperp::Vector{Matrix{T}}; S⋂YX::Vector{Matrix{T}}; CT_XAR::Vector{Matrix{T}}; CT_FE⋂PY::Vector{Matrix{T}}

  # IV/GMM only
  ZZ::Symmetric{T,Matrix{T}}; XY₂::Matrix{T}; XX::Symmetric{T,Matrix{T}}; H_2SLS::Symmetric{T,Matrix{T}}; V::Matrix{T}; ZY₂::Matrix{T}; X₂Y₂::Matrix{T}; X₁Y₂::Matrix{T}; ZR₁ZR₁::Symmetric{T,Matrix{T}}; X₂ZR₁::Matrix{T}; ZR₁Y₂::Matrix{T}; X₁ZR₁::Matrix{T}
  ZZR₁::Matrix{T}; X₂y₁::Vector{T}; X₁y₁::Vector{T}; Zy₁::Vector{T}; ZXinvXXXZ::Matrix{T}; H_2SLSmZZ::Symmetric{T,Matrix{T}}
  ZXinvXXXy₁par::Vector{T}; t₁Y::Vector{T}
  Y₂y₁::Vector{T}; twoR₁Zy₁::Vector{T}
  y₁y₁::T; y₁pary₁par::T
  X₂y₁par::Vector{T}; X₁y₁par::Vector{T}; Zy₁par::Vector{T}
  Y₂y₁par::Vector{T}
  Rperp::Matrix{T}; ZR₁::Matrix{T}
  kX::Int64
	S⋂y₁X₁::Matrix{T}; S⋂y₁X₂::Matrix{T}; invZperpZperpZperpy₁::Vector{T}; invZperpZperpZperpX::Matrix{T}; invZperpZperpZperpZ::Matrix{T}; invZperpZperpZperpZR₁::Matrix{T}; invZperpZperpZperpY₂::Matrix{T}; S⋂XZperp::Array{T,3}; S⋂Xy₁::Array{T,3}; S⋂ZperpZperp::Array{T,3}; ZperpX::Matrix{T}; S⋂Zperpy₁::Array{T,3}; S⋂X_ZR₁::Array{T,3}
	Zperp_ZR₁::Matrix{T}; S⋂Zperp_ZR₁::Array{T,3}; FillingT₀₀::Array{T,3}; ∂FillingT₀∂r::Array{T,3}; S⋂y₁X₀::Matrix{T}; ∂S⋂y₁X∂r::Array{T,3}
Π̂::Matrix{T}; _X₁::Matrix{T}; _Z::Matrix{T}; ZperpZ::Matrix{T}; ZperpY₂::Matrix{T}; _ZR₁::Matrix{T}

  StrEstimator{T}(isDGP, LIML, Fuller, κ) where T<:AbstractFloat = new(isDGP, LIML, Fuller, κ, Matrix{T}(undef,0,0))
end

mutable struct StrBootTest{T<:AbstractFloat}
  R::Matrix{T}; r::Vector{T}; R₁::Matrix{T}; r₁::Vector{T}
  y₁::Vector{T}; X₁::Matrix{T}; Y₂::Matrix{T}; X₂::Matrix{T}
  wt::Vector{T}; fweights::Bool
  LIML::Bool; Fuller::T; κ::T; ARubin::Bool
  B::Int64; auxtwtype::AuxWtType; rng::AbstractRNG; maxmatsize::Float16
  ptype::PType; null::Bool; bootstrapt::Bool
	ID::Matrix{Int64}; nbootclustvar::Int8; nerrclustvar::Int8; issorted::Bool; small::Bool
  FEID::Vector{Int64}; FEdfadj::Bool
  level::T; rtol::T
  madjtype::MAdjType; NH₀::Int16
  ML::Bool; β̈::Vector{T}; A::Symmetric{T,Matrix{T}}; sc::Matrix{T}
  willplot::Bool; gridmin::Vector{T}; gridmax::Vector{T}; gridpoints::Vector{Float32}

  q::Int16; twotailed::Bool; scorebs::Bool; robust::Bool

  WRE::Bool; initialized::Bool; NFE::Int64; FEboot::Bool; granular::Bool; NErrClustCombs::Int16; subcluster::Int8; BFeas::Int64; interpolating::Bool
  v_sd::T; notplotted::Bool
  confpeak::Vector{T}
  ID✻::Vector{Int64}; ID✻_✻⋂::Vector{Int64}
  anchor::Vector{T}; poles::Vector{T}; numer::Matrix{T}
  CI::Matrix{T}
  peak::NamedTuple{(:X, :p), Tuple{Vector{T}, T}}

	Nobs::Int64; NClustVar::Int8; kX₁::Int64; kX₂::Int64; kY₂::Int64; WREnonARubin::Bool; boottest!::Function
	coldotplus!::Function; colquadformminus!::Function; matmulplus!::Function; panelsum!::Function

  sqrt::Bool; _Nobs::T; kZ::Int64; sumwt::T; haswt::Bool; REst::Bool; multiplier::T; smallsample::T
		dof::Int64; dof_r::T; p::T; BootClust::Int8
		purerobust::Bool; N✻::Int64; Nw::Int64; enumerate::Bool; interpolable::Bool; interpolate_u::Bool; kX::Int64
  _FEID::Vector{Int64}; AR::Matrix{T}; v::Matrix{T}; u✻::Matrix{T}; CT_WE::Matrix{T}
  info✻::Vector{UnitRange{Int64}}; info⋂_✻⋂::Vector{UnitRange{Int64}}
  JN⋂N✻::Matrix{T}; statDenom::Matrix{T}; uXAR::Matrix{T}; SuwtXA::Matrix{T}; numer₀::Matrix{T}; β̂dev::Matrix{T}; δdenom_b::Matrix{T}
	_J⋂::Matrix{T}; YY✻_b::Matrix{T}; YPXY✻_b::Matrix{T}; numerw::Matrix{T}; Zyg::Vector{Matrix{T}}; numer_b::Vector{T}; dist::Matrix{T}

	distCDR::Matrix{T}; plotX::Vector{Vector{T}}; plotY::Vector{T}; ClustShare::Vector{T}; WeightGrp::Vector{UnitRange{Int64}}
  numersum::Vector{T}; ü₀::Vector{T}; invFEwt::Vector{T}
	β̂s::Matrix{T}; As::Matrix{T}
	info✻⋂::Vector{UnitRange{Int64}}; info⋂::Vector{UnitRange{Int64}}; ID✻⋂::Matrix{T}
	ü::Vector{T}
	DGP::StrEstimator{T}; Repl::StrEstimator{T}; M::StrEstimator{T}
	clust::Vector{StrClust{T}}
	denom::Matrix{Matrix{T}}; Kcd::Matrix{Matrix{T}}; Jcd::Matrix{Matrix{T}}; denom₀::Matrix{Matrix{T}}; Jcd₀::Matrix{Matrix{T}}; S✻⋂u₁XinvXX::Matrix{Matrix{T}}; S✻UU::Matrix{Vector{T}}; CTUX::Matrix{Matrix{T}}
	∂u∂r::Vector{Matrix{T}}; ∂numer∂r::Vector{Matrix{T}}; IDCT⋂✻::Vector{Vector{Int64}}; infoCT⋂✻::Vector{Vector{UnitRange{Int64}}}; S✻UX::Vector{Matrix{T}}; S✻UXinvXX::Vector{Matrix{T}}; S✻UZperpinvZperpZperp::Vector{Matrix{T}}; S✻UY::Matrix{Vector{T}}; S✻UMZperp::Vector{Matrix{T}}; S✻UPX::Vector{Matrix{T}}; S✻UZperp::Vector{Matrix{T}}; CTFEU::Vector{Matrix{T}}
  ∂denom∂r::Vector{Matrix{Matrix{T}}}; ∂Jcd∂r::Vector{Matrix{Matrix{T}}}
  ∂²denom∂r²::Matrix{Matrix{Matrix{T}}}
	FEs::Vector{StrFE{T}}
  T1L::Vector{Matrix{T}}; T1R::Vector{Matrix{T}}
	crosstab⋂✻ind::Vector{Int64}; crosstabBootind::Vector{Int64}
  seed::UInt64

	S✻u₁X0::Array{T,3}; ∂S✻u₁X∂β̂::Array{T,3}; ∂S✻u₁X∂γ̈::Array{T,3}; ∂S✻u₁X∂γ̈Π̂::Array{T,3}; ∂S✻u₁X∂r::Array{T,3}; S✻U₂X0::Array{T,3}; ∂S✻U₂X∂Π̂::Array{T,3}
	S✻Zperp_DGPy₁::Array{T,3}; S✻Zperp_DGPZ::Array{T,3}; S✻Zperp_DGPY₂::Array{T,3}; S✻Zperp_DGPX::Array{T,3}; S✻Zperp_DGPZR₁::Array{T,3}
	S✻⋂u₁XinvXX0::Array{T,3}; ∂S✻⋂u₁XinvXX∂β̂::Array{T,3}; ∂S✻⋂u₁XinvXX∂γ̈::Array{T,3}; ∂S✻⋂u₁XinvXX∂γ̈Π̂::Array{T,3}; ∂S✻⋂u₁XinvXX∂r::Array{T,3}; S✻⋂U₂XinvXX0::Array{T,3}; ∂S✻⋂U₂XinvXX0∂Π̂::Array{T,3}
	S✻y₁u₁0::Array{T,3}; ∂S✻y₁u₁∂β̂::Array{T,3}; ∂S✻y₁u₁∂γ̈::Array{T,3}; ∂S✻y₁u₁∂γ̈Π̂::Array{T,3}; ∂S✻y₁u₁∂r::Array{T,3}; S✻y₁U₂0::Array{T,3}; ∂S✻y₁U₂∂r::Array{T,3}; ∂S✻y₁U₂∂β̂::Array{T,3}; ∂S✻y₁U₂∂γ̈Π̂::Array{T,3}; ∂S✻y₁U₂∂γ̈::Array{T,3}; ∂S✻y₁U₂∂Π̂::Array{T,3}
	Reply₁_DGPy₁::Array{T,3}; Reply₁_DGPZR₁::Array{T,3}; Reply₁_DGPZ::Array{T,3}; Reply₁_DGPY₂::Array{T,3}; Reply₁_DGPX::Array{T,3}
	ReplZ_DGPy₁::Array{T,3}; ReplZ_DGPZR₁::Array{T,3}; ReplZ_DGPZ::Array{T,3}; ReplZ_DGPY₂::Array{T,3}; ReplZ_DGPX::Array{T,3}

	_ID✻⋂::Vector{Int}
	StrBootTest{T}(R, r, R₁, r₁, y₁, X₁, Y₂, X₂, wt, fweights, LIML,
	               Fuller, κ, ARubin, B, auxtwtype, rng, maxmatsize, ptype, null, scorebs, bootstrapt, ID, nbootclustvar, nerrclustvar, issorted, robust, small, FEID, FEdfadj, level, rtol, madjtype, NH₀, ML,
								 β̈, A, sc, willplot, gridmin, gridmax, gridpoints, turbo) where T<:Real =

		begin
			kX₂ = ncols(X₂)
			scorebs = scorebs || iszero(B) || ML
			WREnonARubin = !(iszero(kX₂) || scorebs) && !ARubin

			new(R, r, R₁, r₁, y₁, X₁, Y₂, X₂, wt, fweights, LIML || !iszero(Fuller),
					Fuller, κ, ARubin, B, auxtwtype, rng, maxmatsize, ptype, null, bootstrapt, ID, nbootclustvar, nerrclustvar, issorted, small, FEID, FEdfadj, level, rtol, madjtype, NH₀, ML,
					β̈, A, sc, willplot, gridmin, gridmax, gridpoints,
				nrows(R), ptype==symmetric || ptype==equaltail, scorebs, robust || nerrclustvar>0,
				false, false, 0, false, false, 0, 0, 0, false,
				one(T), true,
				[zero(T)],
				Vector{Int64}(undef,0), Vector{Int64}(undef,0),
				Vector{T}(undef,0), Vector{T}(undef,0), Matrix{T}(undef,0,0),
				Matrix{T}(undef,0,0),
				(X = Vector{T}(undef,0), p = T(NaN)),
				nrows(X₁), ncols(ID), ncols(X₁), kX₂, ncols(Y₂), WREnonARubin, WREnonARubin ? boottestWRE! : boottestOLSARubin!,
				turbo ? coldotplus_turbo! : coldotplus_nonturbo!, turbo ? colquadformminus_turbo! : colquadformminus_nonturbo!, turbo ? matmulplus_turbo! : matmulplus_nonturbo!, turbo ? panelsum_turbo! : panelsum_nonturbo!)
		end
end

# cross-tab sum of a column vector w.r.t. given panel info and fixed-effect var
# one row per FE, one col per other grouping
function crosstabFE(o::StrBootTest{T}, v::AbstractVector{T}, info::Vector{UnitRange{Int64}}) where T
  dest = zeros(T, o.NFE, nrows(info))
  if length(info)>0
		@inbounds for i ∈ 1:nrows(info)
	    FEIDi = @view o._FEID[info[i]]
	    vi    = @view       v[info[i]]
	    @simd for j in eachindex(vi, FEIDi)
	  	  dest[FEIDi[j],i] += vi[j]
	    end
	  end
  else  # "robust" case, no clustering
	  @inbounds @simd for i in eachindex(v,o._FEID)
	    dest[o._FEID[i],i] = v[i]
	  end
  end
  dest
end
# same, transposed
function crosstabFEt(o::StrBootTest{T}, v::AbstractVector{T}, info::Vector{UnitRange{Int64}}) where T
  dest = zeros(T, nrows(info), o.NFE)
  if length(info)>0
		@inbounds for i ∈ 1:nrows(info)
	    FEIDi = @view o._FEID[info[i]]
	    vi    = @view       v[info[i]]
	    @simd for j ∈ eachindex(vi, FEIDi)
	  	  dest[i,FEIDi[j]] += vi[j]
	    end
	  end
  else  # "robust" case, no clustering
	  @inbounds @simd for i ∈ eachindex(v,o._FEID)
	    dest[i,o._FEID[i]] = v[i]
	  end
  end
  dest
end
# same, but handles multiple columns in v
# *2nd* dimension of resulting 3-D array corresponds to cols of v;
# this facilitates reshape() to 2-D array in which results for each col of v are stacked vertically
function crosstabFEt(o::StrBootTest{T}, v::AbstractMatrix{T}, info::Vector{UnitRange{Int64}}) where T
  dest = zeros(T, nrows(info), ncols(v), o.NFE)
  if length(info)>0
	  @inbounds for i ∈ 1:nrows(info)
	    FEIDi = @view o._FEID[info[i]]
	    vi    = @view       v[info[i],:]
	    @simd for j ∈ 1:length(FEIDi)
	  	  dest[i,:,FEIDi[j]] += @view vi[j,:]
	    end
	  end
  else  # "robust" case, no clustering
	  @inbounds @simd for i ∈ 1:length(FEIDi)
	    dest[i,:,o._FEID[i]] = @view v[i,:]
	  end
  end
  dest
end

# partial any fixed effects out of a data matrix
function partialFE!(o::StrBootTest, In::AbstractArray)
  if length(In)>0
	  for f ∈ o.FEs
	    tmp = @view In[f.is,:]
	    tmp .-= f.wt'tmp
    end
  end
	nothing
end
function partialFE(o::StrBootTest, In::AbstractArray)
  Out = similar(In)
  if length(In)>0
	  for f ∈ o.FEs
	    tmp = @view In[f.is,:]
	    Out[f.is,:] .= tmp .- f.wt'tmp
	  end
  end
  Out
end

macro storeWtGrpResults!(dest, content)  # poor hygiene in referencing caller's o and w
  if dest == :(o.dist)
		return quote
			if isone($(esc(:o)).Nw)
				$(esc(dest)) .= $(esc(content))
			else
				$(esc(dest))[$(esc(:o)).WeightGrp[$(esc(:w))]] .= $(esc(content))
			end
		end
  else
	  return quote
			local _content = $(esc(content))
	    if isone($(esc(:o)).Nw)
	  	  $(esc(dest)) = _content
	    else
	  	  $(esc(dest))[:,$(esc(:o)).WeightGrp[$(esc(:w))]] = _content
	    end
	  end
  end
end

macro clustAccum!(X, c, Y)  # efficiently add a cluster combination-specific term, factoring in the needed multiplier and sign
  return quote
		local _Y = $(esc(Y))
	  if isone($(esc(c)))
	    if isone($(esc(:o)).clust[1].multiplier)
	  	  $(esc(X)) = $(esc(:o)).clust[1].even ? _Y : -_Y
	    else
	  	  $(esc(X)) = _Y * ($(esc(:o)).clust[1].even ? $(esc(:o)).clust[1].multiplier : -$(esc(:o)).clust[1].multiplier)
	    end
	  elseif $(esc(:o)).clust[$(esc(c))].even
	    if isone($(esc(:o)).clust[$(esc(c))].multiplier)
	  	  $(esc(X)) .+= _Y
	    else
	  	  $(esc(X)) .+= _Y .* $(esc(:o)).clust[$(esc(c))].multiplier
	    end
	  elseif isone($(esc(:o)).clust[$(esc(c))].multiplier)
	    $(esc(X)) .-= _Y
	  else
	    $(esc(X)) .-= _Y .* $(esc(:o)).clust[$(esc(c))].multiplier
	  end
  end
end

function getdist(o::StrBootTest, diststat::DistStatType=nodist)
  if diststat == numer
	  _numer = isone(o.v_sd) ? o.numer : o.numer / o.v_sd
	  o.distCDR = (@view _numer[:,2:end]) .+ o.r
	  sort!(o.distCDR)
  elseif nrows(o.distCDR)==0  # return test stats
    if length(o.dist) > 1
	    o.distCDR = (@view o.dist[2:end])' * o.multiplier
	    sort!(o.distCDR, dims=1)
	  else
	    o.distCDR = zeros(0,1)
	  end
  end
  o.distCDR
end

function sumgreater(x, v)
  dest = zero(Int64)
  @inbounds @simd for i in v
	  x > i && (dest += 1)
  end
  dest
end
function sumless(x, v)
  dest = zero(Int64)
  @inbounds @simd for i in v
	  x < i && (dest += 1)
  end
  dest
end
function sumlessabs(x, v)
  dest = zero(Int64)
  @inbounds @simd for i in v
	  x < abs(i) && (dest += 1)
  end
  dest
end

# store p value in o.p. Return optionally-multiple-hypothesis-adjusted p value. Robust to missing bootstrapped values interpreted as +infinity.
function getp(o::StrBootTest{T}) where T
  o.boottest!(o)
  tmp = o.dist[1]
  isnan(tmp) && return tmp
  if o.B>0
  	if o.sqrt && o.ptype ≠ upper
  	  if o.ptype==symmetric
  	    n = sumlessabs(abs(tmp), o.dist)   # symmetric p value; do so as not to count missing entries in *dist
  	  elseif o.ptype==equaltail
  	    n = 2min(sumgreater(tmp, o.dist) , sumless(tmp, o.dist))
  	  else
  		  n = sumgreater(tmp,  o.dist)  # lower-tailed p value
      end
  	else
  	  n = sumless(tmp, o.dist)  # upper-tailed p value or p value based on squared stats
    end
    o.p = n / o.BFeas |> T
  else
		tmp *= o.multiplier
		o.p = ccdf(o.small ? FDist{T}(T(o.dof), o.dof_r) : Chisq{T}(T(o.dof)), o.sqrt ? tmp^2 : tmp)
		if o.sqrt && !o.twotailed
			o.p /= 2
			(o.ptype==upper) == (tmp<0) && (o.p = 1 - o.p)
		end
  end

	if o.madjtype==bonferroni min(one(T), o.NH₀ * o.p)
  elseif o.madjtype==sidak  one(T) - (one(T) - o.p) ^ o.NH₀
  else o.p
  end
end

getb(o::StrBootTest) = isone(o.v_sd) ? o.numer[:,1] : o.numer[:,1] / o.v_sd  # numerator for full-sample test stat
getV(o::StrBootTest) = o.statDenom / ((isone(o.v_sd) ? o.smallsample : o.v_sd^2 * o.smallsample) * (o.sqrt ? o.multiplier^2 : o.multiplier) * o.dof)  # denominator for full-sample test stat
getv(o::StrBootTest) = @views isone(o.v_sd) ? o.v[:,2:end] : o.v[:,2:end] / o.v_sd  # wild weights
@inline getstat(o::StrBootTest) = o.multiplier * o.dist[1]


import Base.getproperty
import Base.findmin
import Base.findmax
import Base.push!

using Zygote: @adjoint, @nograd

import Zygote.literal_getproperty

# ---- #
# Vec3 #
# ---- #

# Hack to avoid nothing in the gradient due to typemax
@adjoint bigmul(x::T) where {T} = bigmul(x), Δ -> (zero(T),)

@adjoint Vec3(a, b, c) = Vec3(a, b, c), Δ -> (Δ.x, Δ.y, Δ.z)

@adjoint literal_getproperty(v::Vec3, ::Val{:x}) =
    getproperty(v, :x), Δ -> (Vec3(Δ, zero(v.y), zero(v.z)), nothing)

@adjoint literal_getproperty(v::Vec3, ::Val{:y}) =
    getproperty(v, :y), Δ -> (Vec3(zero(v.x), Δ, zero(v.z)), nothing)

@adjoint literal_getproperty(v::Vec3, ::Val{:z}) =
    getproperty(v, :z), Δ -> (Vec3(zero(v.x), zero(v.y), Δ), nothing)

@adjoint function dot(a::Vec3, b::Vec3)
    dot(a, b), Δ -> begin
        Δ = map(x -> isnothing(x) ? zero(eltype(a.x)) : x, Δ)
        t1 = Δ * b
        t2 = Δ * a
        if length(a.x) != length(t1.x)
            t1 = Vec3(sum(t1.x), sum(t1.y), sum(t1.z))
        end
        if length(b.x) != length(t2.x)
            t2 = Vec3(sum(t2.x), sum(t2.y), sum(t2.z))
        end
        return (t1, t2)
    end
end

# The purpose of this adjoint is to ensure type inference works
# in the backward pass
@adjoint function cross(a::Vec3{T}, b::Vec3{T}) where {T}
    cross(a, b), Δ -> begin
        ∇a = zero(a)
        ∇b = zero(b)
        x = (Δ.z .* b.y) .- (Δ.y .* b.z)
        y = (Δ.x .* b.z) .- (Δ.z .* b.x)
        z = (Δ.y .* b.x) .- (Δ.x .* b.y)
        if length(a.x) == 1
            ∇a.x .= sum(x)
            ∇a.y .= sum(y)
            ∇a.z .= sum(z)
        else
            ∇a.x .= x
            ∇a.y .= y
            ∇a.z .= z
        end
        x = (Δ.y .* a.z) .- (Δ.z .* a.y)
        y = (Δ.z .* a.x) .- (Δ.x .* a.z)
        z = (Δ.x .* a.y) .- (Δ.y .* a.x)
        if length(b.x) == 1
            ∇b.x .= sum(x)
            ∇b.y .= sum(y)
            ∇b.z .= sum(z)
        else
            ∇b.x .= x
            ∇b.y .= y
            ∇b.z .= z
        end
        return (∇a, ∇b)
    end
end

@adjoint place(a::Vec3, cond) = place(a, cond), Δ -> (Vec3(Δ.x[cond], Δ.y[cond], Δ.z[cond]), nothing)

@adjoint place(a::Array, cond) = place(a, cond), Δ -> (Δ[cond], nothing)

@adjoint place_idx!(a::Vec3, b::Vec3, idx) = place_idx!(a, b, idx), Δ -> (zero(Δ), Vec3(Δ[idx]...), nothing)

# ----- #
# Light #
# ----- #

# -------------- #
# - PointLight - #
# -------------- #

@adjoint PointLight(color::Vec3, intensity, pos::Vec3) =
    PointLight(color, intensity, pos), Δ -> (Δ.color, Δ.intensity, Δ.position)

@adjoint literal_getproperty(p::PointLight, ::Val{:color}) =
    getproperty(p, :color), Δ -> (PointLight(Δ, zero(p.intensity), zero(p.position)), nothing)

@adjoint literal_getproperty(p::PointLight, ::Val{:intensity}) =
    getproperty(p, :intensity), Δ -> (PointLight(zero(p.color), Δ, zero(p.position)), nothing)

@adjoint literal_getproperty(p::PointLight, ::Val{:position}) =
    getproperty(p, :position), Δ -> (PointLight(zero(p.color), zero(p.intensity), Δ), nothing)

# ---------------- #
# - DistantLight - #
# ---------------- #

@adjoint DistantLight(color::Vec3, intensity, direction::Vec3) =
    DistantLight(color, intensity, direction), Δ -> (Δ.color, Δ.intensity, Δ.direction)

@adjoint literal_getproperty(d::DistantLight, ::Val{:color}) =
    getproperty(d, :color), Δ -> (DistantLight(Δ, zero(d.intensity), zero(d.direction)), nothing)

@adjoint literal_getproperty(d::DistantLight, ::Val{:intensity}) =
    getproperty(d, :intensity), Δ -> (DistantLight(zero(d.color), Δ, zero(d.direction)), nothing)

@adjoint literal_getproperty(d::DistantLight, ::Val{:direction}) =
    getproperty(d, :direction), Δ -> (DistantLight(zero(d.color), zero(d.intensity), Δ), nothing)

# -------- #
# Material #
# -------- #

@adjoint Material(color_ambient, color_diffuse, color_specular, specular_exponent,
                  reflection, texture_ambient, texture_diffuse, texture_specular,
                  uv_coordinates) =
    Material(color_ambient, color_diffuse, color_specular, specular_exponent, reflection,
             texture_ambient, texture_diffuse, texture_specular, uv_coordinates),
    Δ -> (Δ.color_ambient, Δ.color_diffuse, Δ.color_specular, Δ.specular_exponent,
          Δ.reflection, Δ.texture_ambient, Δ.texture_diffuse, Δ.texture_specular,
          Δ.uv_coordinates)

@adjoint literal_getproperty(m::Material, ::Val{:color_ambient}) =
    getproperty(m, :color_ambient), Δ -> (Material(Δ, zero(m.color_diffuse), zero(m.color_specular),
                                                   zero(m.specular_exponent), zero(m.reflection),
                                                   isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient),
                                                   isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse),
                                                   isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                   isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                          nothing)

@adjoint literal_getproperty(m::Material, ::Val{:color_diffuse}) =
    getproperty(m, :color_diffuse), Δ -> (Material(zero(m.color_ambient), Δ, zero(m.color_specular),
                                                   zero(m.specular_exponent), zero(m.reflection),
                                                   isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient),
                                                   isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse),
                                                   isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                   isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                          nothing)

@adjoint literal_getproperty(m::Material, ::Val{:color_specular}) =
    getproperty(m, :color_specular), Δ -> (Material(zero(m.color_ambient), zero(m.color_diffuse), Δ,
                                                   zero(m.specular_exponent), zero(m.reflection),
                                                   isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient),
                                                   isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse),
                                                   isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                   isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                          nothing)

@adjoint literal_getproperty(m::Material, ::Val{:specular_exponent}) =
    getproperty(m, :specular_exponent), Δ -> (Material(zero(m.color_ambient), zero(m.color_diffuse),
                                                       zero(m.color_specular), Δ, zero(m.reflection),
                                                       isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient),
                                                       isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse),
                                                       isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                       isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                              nothing)

@adjoint literal_getproperty(m::Material, ::Val{:reflection}) =
    getproperty(m, :reflection), Δ -> (Material(zero(m.color_ambient), zero(m.color_diffuse),
                                                zero(m.color_specular), zero(m.specular_exponent), Δ,
                                                isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient),
                                                isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse),
                                                isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                       nothing)

@adjoint literal_getproperty(m::Material, ::Val{:texture_ambient}) =
    getproperty(m, :texture_ambient), Δ -> (Material(zero(m.color_ambient), zero(m.color_diffuse),
                                                     zero(m.color_specular), zero(m.specular_exponent),
                                                     zero(m.reflection), Δ,
                                                     isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse),
                                                     isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                     isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                            nothing)

@adjoint literal_getproperty(m::Material, ::Val{:texture_diffuse}) =
    getproperty(m, :texture_diffuse), Δ -> (Material(zero(m.color_ambient), zero(m.color_diffuse),
                                                     zero(m.color_specular), zero(m.specular_exponent),
                                                     zero(m.reflection),
                                                     isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient), Δ,
                                                     isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                     isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                            nothing)

@adjoint literal_getproperty(m::Material, ::Val{:texture_specular}) =
    getproperty(m, :texture_specular), Δ -> (Material(zero(m.color_ambient), zero(m.color_diffuse),
                                                      zero(m.color_specular), zero(m.specular_exponent),
                                                      zero(m.reflection),
                                                      isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient),
                                                      isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse), Δ,
                                                      isnothing(m.uv_coordinates) ? nothing : zero.(m.uv_coordinates)),
                                             nothing)

@adjoint literal_getproperty(m::Material, ::Val{:uv_coordinates}) =
    getproperty(m, :uv_coordinates), Δ -> (Material(zero(m.color_ambient), zero(m.color_diffuse),
                                                    zero(m.color_specular), zero(m.specular_exponent),
                                                    zero(m.reflection),
                                                    isnothing(m.texture_ambient) ? nothing : zero(m.texture_ambient),
                                                    isnothing(m.texture_diffuse) ? nothing : zero(m.texture_diffuse),
                                                    isnothing(m.texture_specular) ? nothing : zero(m.texture_specular),
                                                    Δ),
                                             nothing)
# ------- #
# Objects #
# ------- #

# ---------- #
# - Sphere - #
# ---------- #

@adjoint Sphere(center, radius, material::Material) =
    Sphere(center, radius, material), Δ -> (Δ.sphere, Δ.radius, Δ.material)

@adjoint literal_getproperty(s::Sphere, ::Val{:center}) =
    getproperty(s, :center), Δ -> (Sphere(Δ, zero(s.radius), zero(s.material)), nothing)

@adjoint literal_getproperty(s::Sphere, ::Val{:radius}) =
    getproperty(s, :radius), Δ -> (Sphere(zero(s.center), Δ, zero(s.material)), nothing)

@adjoint literal_getproperty(s::Sphere, ::Val{:material}) =
    getproperty(s, :material), Δ -> (Sphere(zero(s.center), zero(s.radius), Δ), nothing)

# ------------ #
# - Triangle - #
# ------------ #

@adjoint Triangle(v1, v2, v3, material::Material) =
    Triangle(v1, v2, v3, material), Δ -> (Δ.v1, Δ.v2, Δ.v3, Δ.material)

@adjoint literal_getproperty(t::Triangle, ::Val{:v1}) =
    getproperty(t, :v1), Δ -> (Triangle(Δ, zero(t.v2), zero(t.v3), zero(t.material)), nothing)

@adjoint literal_getproperty(t::Triangle, ::Val{:v2}) =
    getproperty(t, :v2), Δ -> (Triangle(zero(t.v1), Δ, zero(t.v3), zero(t.material)), nothing)

@adjoint literal_getproperty(t::Triangle, ::Val{:v3}) =
    getproperty(t, :v3), Δ -> (Triangle(zero(t.v1), zero(t.v2), Δ, zero(t.material)), nothing)

@adjoint literal_getproperty(t::Triangle, ::Val{:material}) =
    getproperty(t, :material), Δ -> (Triangle(zero(t.v1), zero(t.v2), zero(t.v3), Δ), nothing)

# ---------------- #
# - TriangleMesh - #
# ---------------- #

#=
@adjoint TriangleMesh(tm, mat, ftmp) =
    TriangleMesh(tm, mat, ftmp), Δ -> (Δ.triangulated_mesh, Δ.material, Δ.ftmp)

@adjoint function literal_getproperty(t::TriangleMesh, ::Val{:triangulated_mesh})
    tm = getproperty(t, :triangulated_mesh)
    z = eltype(tm[1].v1.x)
    mat = Material(PlainColor(rgb(z)), z)
    return tm, Δ -> (TriangleMesh(Δ, mat, FixedTriangleMeshParams(IdDict(), [Vec3(z)])), nothing)
end

@adjoint function literal_getproperty(t::TriangleMesh, ::Val{:material})
    mat = getproperty(t, :material)
    z = eltype(t.triangulated_mesh[1].v1.x)
    tm = [Triangle([Vec3(z)]...) for _ in 1:length(t.triangulated_mesh)]
    return mat, Δ -> (TriangleMesh(tm, Δ, FixedTriangleMeshParams(IdDict(), [Vec3(z)])), nothing)
end

@adjoint function literal_getproperty(t::TriangleMesh, ::Val{:ftmp})
    z = eltype(t.triangulated_mesh[1].v1.x)
    mat = Material(PlainColor(rgb(z)), z)
    tm = [Triangle([Vec3(z)]...) for _ in 1:length(t.triangulated_mesh)]
    return getproperty(t, :ftmp), Δ -> (TriangleMesh(tm, mat, Δ), nothing)
end

@adjoint FixedTriangleMeshParams(isect, n) =
    FixedTriangleMeshParams(isect, n), Δ -> (Δ.isect, Δ.n)

# The gradients for this params are never used so fill them with anything as long
# as they are consistent with the types
@adjoint literal_getproperty(ftmp::FixedTriangleMeshParams, ::Val{f}) where {f} =
    getproperty(ftmp, f), Δ -> (FixedTriangleMeshParams(IdDict(), ftmp.normals[1:1]))
=#

# ------ #
# Camera #
# ------ #

@adjoint Camera(lf, la, vfov, focus, fp) =
    Camera(lf, la, vfov, focus, fp), Δ -> (Δ.lookfrom, Δ.lookat, Δ.vfov,
                                           Δ.focus, Δ.fixedparams)

@adjoint literal_getproperty(c::Camera, ::Val{:lookfrom}) =
    getproperty(c, :lookfrom), Δ -> (Camera(Δ, zero(c.lookat), zero(c.vfov), zero(c.focus),
                                            zero(c.fixedparams)), nothing)

@adjoint literal_getproperty(c::Camera, ::Val{:lookat}) =
    getproperty(c, :lookat), Δ -> (Camera(zero(c.lookfrom), Δ, zero(c.vfov), zero(c.focus),
                                          zero(c.fixedparams)), nothing)

@adjoint literal_getproperty(c::Camera, ::Val{:vfov}) =
    getproperty(c, :vfov), Δ -> (Camera(zero(c.lookfrom), zero(c.lookat), Δ, zero(c.focus),
                                        zero(c.fixedparams)), nothing)

@adjoint literal_getproperty(c::Camera, ::Val{:focus}) =
    getproperty(c, :focus), Δ -> (Camera(zero(c.lookfrom), zero(c.lookat), zero(c.vfov), Δ,
                                         zero(c.fixedparams)), nothing)

@adjoint literal_getproperty(c::Camera, ::Val{:fixedparams}) =
    getproperty(c, :fixedparams), Δ -> (Camera(zero(c.lookfrom), zero(c.lookat), zero(c.vfov),
                                               zero(c.focus), Δ), nothing)

@adjoint FixedCameraParams(vup, w, h) =
    FixedCameraParams(vup, w, h), Δ -> (Δ.vup, Δ.width, Δ.height)

@adjoint literal_getproperty(fcp::FixedCameraParams, ::Val{f}) where {f} =
    getproperty(fcp, f), Δ -> (zero(fcp), nothing)

# ------- #
# ImUtils #
# ------- #

@adjoint function zeroonenorm(x)
    mini, indmin = findmin(x)
    maxi, indmax = findmax(x)
    res = (x .- mini) ./ (maxi - mini)
    function ∇zeroonenorm(Δ)
        ∇x = similar(x)
        fill!(∇x, 1 / (maxi - mini))
        res1 = (x .- maxi) ./ (maxi - mini)^2
        ∇x[indmin] = sum(res1) - minimum(res1)
        res2 = - res ./ (maxi - mini)
        ∇x[indmax] = sum(res2) - minimum(res2)
        return (∇x .* Δ, )
    end
    return res, ∇zeroonenorm
end

# ----------------- #
# General Functions #
# ----------------- #

for func in (:findmin, :findmax)
    @eval begin
        @adjoint function $(func)(xs::AbstractArray; dims = :)
            y = $(func)(xs, dims = dims)
            function dfunc(Δ)
                res = zero(xs)
                res[y[2]] .= Δ[1]
                return (res, nothing)
            end
            return y, dfunc
        end
    end
end

@adjoint reducehcat(x) = reduce(hcat, x), Δ -> ([Δ[:, i] for i in 1:length(x)], )

@adjoint push!(arr, val) = push!(arr, val), Δ -> (Δ[1:end-1], Δ[end])

@nograd fill

@nograd function update_index!(arr, i, j, val)
    arr[i, j] = val
end


typealias NumericalOrCategoricalAesthetic
    Union(Nothing, Vector, DataArray, PooledDataArray)

typealias CategoricalAesthetic
    Union(Nothing, PooledDataArray)

typealias NumericalAesthetic
    Union(Nothing, Matrix, Vector, DataArray)

@varset Aesthetics begin
    x,            Union(NumericalOrCategoricalAesthetic, Distribution)
    y,            Union(NumericalOrCategoricalAesthetic, Distribution)
    z,            Union(Nothing, Function, Matrix)
    size,         Maybe(Vector{Measure})
    color,        Maybe(AbstractDataVector{RGB{Float32}})
    label,        CategoricalAesthetic
    group,        CategoricalAesthetic

    xmin,         NumericalAesthetic
    xmax,         NumericalAesthetic
    ymin,         NumericalAesthetic
    ymax,         NumericalAesthetic

    # hexagon sizes used for hexbin
    xsize,        NumericalAesthetic
    ysize,        NumericalAesthetic

    # fixed lines
    xintercept,   NumericalAesthetic
    yintercept,   NumericalAesthetic

    # boxplots
    middle,       NumericalAesthetic
    lower_hinge,  NumericalAesthetic
    upper_hinge,  NumericalAesthetic
    lower_fence,  NumericalAesthetic
    upper_fence,  NumericalAesthetic
    outliers,     NumericalAesthetic

    # subplots
    xgroup,       CategoricalAesthetic
    ygroup,       CategoricalAesthetic

    # guides
    xtick,        NumericalAesthetic
    ytick,        NumericalAesthetic
    xgrid,        NumericalAesthetic
    ygrid,        NumericalAesthetic
    color_key_colors,     Maybe(Associative)
    color_key_title,      Maybe(String)
    color_key_continuous, Maybe(Bool)
    color_function,       Maybe(Function)
    titles,               Maybe(Dict{Symbol, String})

    # mark some ticks as initially invisible
    xtickvisible,         Maybe(Vector{Bool})
    ytickvisible,         Maybe(Vector{Bool})

    # scale at which ticks should become visible
    xtickscale,           Maybe(Vector{Float64})
    ytickscale,           Maybe(Vector{Float64})

    # pesudo-aesthetics used to indicate that drawing might
    # occur beyond any x/y value.
    xdrawmin,     Any
    xdrawmax,     Any
    ydrawmin,     Any
    ydrawmax,     Any

    # plot viewport extents
    xviewmin,     Any
    xviewmax,     Any
    yviewmin,     Any
    yviewmax,     Any

    # labeling functions
    x_label,      Function, showoff
    y_label,      Function, showoff
    xtick_label,  Function, showoff
    ytick_label,  Function, showoff
    color_label,  Function, showoff
    xgroup_label, Function, showoff
    ygroup_label, Function, showoff
end

# Alternate aesthetic names
const aesthetic_aliases =
    [:colour        => :color,
     :x_min         => :xmin,
     :x_max         => :xmax,
     :y_min         => :ymin,
     :y_max         => :ymax,
     :x_group       => :xgroup,
     :y_group       => :ygroup,
     :x_viewmin     => :xviewmin,
     :x_viewmax     => :xviewmax,
     :y_viewmin     => :yviewmin,
     :y_viewmax     => :yviewmax,
     :x_group_label => :xgroup_label,
     :y_group_label => :ygroup_label,
     :x_tick        => :xtick,
     :y_tick        => :ytick,
     :x_grid        => :xgrid,
     :y_grid        => :ygrid]

# Index as if this were a data frame
function getindex(aes::Aesthetics, i::Integer, j::String)
    getfield(aes, symbol(j))[i]
end

# Return the set of variables that are non-nothing.
function defined_aesthetics(aes::Aesthetics)
    vars = Set{Symbol}()
    for name in Aesthetics.names
        if !is(getfield(aes, name), nothing)
            push!(vars, name)
        end
    end
    vars
end

# Checking aesthetics and giving reasonable error messages.

# Raise an error if any of the given aesthetics are not defined.
#
# Args:
#   who: A string naming the caller which is printed in the error message.
#   aes: An Aesthetics object.
#   vars: Symbol that must be defined in the aesthetics.
#
function undefined_aesthetics(aes::Aesthetics, vars::Symbol...)
    setdiff(Set(vars), defined_aesthetics(aes))
end

function assert_aesthetics_defined(who::String, aes::Aesthetics, vars::Symbol...)
    undefined_vars = undefined_aesthetics(aes, vars...)
    if !isempty(undefined_vars)
        error(@sprintf("The following aesthetics are required by %s but are not defined: %s\n",
                       who, join(undefined_vars, ", ")))
    end
end

function assert_aesthetics_equal_length(who::String, aes::Aesthetics, vars::Symbol...)
    defined_vars = Symbol[]
    for var in filter(var -> !(getfield(aes, var) === nothing), vars)
        push!(defined_vars, var)
    end

    n = length(getfield(aes, vars[1]))
    for i in 2:length(vars)
        if length(getfield(aes, vars[1])) != n
            error(@sprintf("The following aesthetics are required by %s to be of equal length: %s\n",
                           who, join(vars, ", ")))
        end
    end
end

# Create a shallow copy of an Aesthetics instance.
#
# Args:
#   a: aesthetics to copy
#
# Returns:
#   Copied aesthetics.
#
copy(a::Aesthetics) = Aesthetics(a)

# Replace values in a with non-nothing values in b.
#
# Args:
#   a: Destination.
#   b: Source.
#
# Returns: nothing
#
# Modifies: a
#
function update!(a::Aesthetics, b::Aesthetics)
    for name in Aesthetics.names
        if issomething(getfield(b, name))
            setfield(a, name, getfield(b, name))
        end
    end

    nothing
end

# Serialize aesthetics to JSON.

# Args:
#  a: aesthetics to serialize.
#
# Returns:
#   JSON data as a string.
#
function json(a::Aesthetics)
    join([string(a, ":", json(getfield(a, var))) for var in aes_vars], ",\n")
end

# Concatenate aesthetics.
#
# A new Aesthetics instance is produced with data vectors in each of the given
# Aesthetics concatenated, nothing being treated as an empty vector.
#
# Args:
#   aess: One or more aesthetics.
#
# Returns:
#   A new Aesthetics instance with vectors concatenated.
#
function concat(aess::Aesthetics...)
    cataes = Aesthetics()
    for aes in aess
        for var in Aesthetics.names
            setfield!(cataes, var,
                      cat_aes_var!(getfield(cataes, var), getfield(aes, var)))
        end
    end
    cataes
end

cat_aes_var!(a::Nothing, b::Nothing) = a
cat_aes_var!(a::Nothing, b) = copy(b)
cat_aes_var!(a, b::Nothing) = a
cat_aes_var!(a::Function, b::Function) = a === string || a == showoff ? b : a

function cat_aes_var!(a::Dict, b::Dict)
    merge!(a, b)
    a
end

function cat_aes_var!{T <: Base.Callable}(a::AbstractArray{T}, b::AbstractArray{T})
    return append!(a, b)
end

function cat_aes_var!{T <: Base.Callable, U <: Base.Callable}(a::AbstractArray{T}, b::AbstractArray{U})
    return append!(a, b)
end

# Let arrays of numbers clobber arrays of functions. This is slightly odd
# behavior, comes up with with function statistics applied on a layer-wise
# basis.
function cat_aes_var!{T <: Base.Callable, U}(a::AbstractArray{T}, b::AbstractArray{U})
    return b
end

function cat_aes_var!{T, U <: Base.Callable}(a::AbstractArray{T}, b::AbstractArray{U})
    return a
end

function cat_aes_var!{T}(a::AbstractArray{T}, b::AbstractArray{T})
    return append!(a, b)
end

function cat_aes_var!{T, U}(a::AbstractArray{T}, b::AbstractArray{U})
    V = promote_type(T, U)
    if isa(a, DataArray) || isa(b, DataArray)
        ab = DataArray(V, length(a) + length(b))
    else
        ab = Array(V, length(a), length(b))
    end
    i = 1
    for x in a
        ab[i] = x
        i += 1
    end
    for x in b
        ab[i] = x
        i += 1
    end

    return ab
end

function cat_aes_var!(a, b)
    a
end

function cat_aes_var!{T}(xs::PooledDataVector{T}, ys::PooledDataVector{T})
    newpool = T[x for x in union(Set(xs.pool), Set(ys.pool))]
    newdata = vcat(T[x for x in xs], T[y for y in ys])
    PooledDataArray(newdata, newpool, [false for _ in newdata])
end

# Summarizing aesthetics

# Produce a matrix of Aesthetic or Data objects partitioning the original
# Aesthetics or Data object by the cartesian product of xgroup and ygroup.
#
# This is useful primarily for drawing facets and subplots.
#
# Args:
#   aes: Aesthetics or Data objects to partition.
#
# Returns:
#   A Array{Aesthetics} of size max(1, length(xgroup)) by
#   max(1, length(ygroup))
#
function by_xy_group{T <: Union(Data, Aesthetics)}(aes::T, xgroup, ygroup)
    @assert !is(xgroup, nothing) || !is(ygroup, nothing)
    @assert xgroup === nothing || ygroup === nothing ||
            length(xgroup) == length(ygroup)

    n = ygroup === nothing ? 1 : maximum(ygroup)
    m = xgroup === nothing ? 1 : maximum(xgroup)

    xrefs = xgroup === nothing ? [1] : xgroup
    yrefs = ygroup === nothing ? [1] : ygroup

    aes_grid = Array(T, n, m)
    staging = Array(DataArray, n, m)
    for i in 1:n, j in 1:m
        aes_grid[i, j] = T()
    end

    function make_pooled_data_array{T, U, V}(::Type{PooledDataArray{T,U,V}},
                                          arr::AbstractArray)
        PooledDataArray(convert(Array{T}, arr))
    end

    for var in T.names
        # Skipped aesthetics. Don't try to partition aesthetics for which it
        # makes no sense to pass on to subplots.
        if var == :xgroup || var == :ygroup||
           var == :xtick || var == :ytick ||
           var == :xgrid || var == :ygrid ||
           var == :x_drawmin || var == :y_drawmin ||
           var == :x_drawmax || var == :y_drawmax ||
           var == :x_viewmin || var == :y_viewmin ||
           var == :x_viewmax || var == :y_viewmax ||
           var == :color_key_colors
            continue
        end

        vals = getfield(aes, var)
        if typeof(vals) <: AbstractArray
            if !is(xgroup, nothing) && length(vals) != length(xgroup) ||
               !is(ygroup, nothing) && length(vals) != length(ygroup)
                error("Aesthetic $(var) must be the same length as xgroup or ygroup")
            end

            for i in 1:n, j in 1:m
                staging[i, j] = DataArray(eltype(vals), 0)
            end

            for (i, j, v) in zip(Iterators.cycle(yrefs), Iterators.cycle(xrefs), vals)
                push!(staging[i, j], v)
            end

            for i in 1:n, j in 1:m
                if typeof(vals) <: PooledDataArray
                    setfield!(aes_grid[i, j], var,
                              make_pooled_data_array(typeof(vals), staging[i, j]))
                else
                    if !applicable(convert, typeof(vals), staging[i, j])
                        T = eltype(vals)
                        if T <: ColorValue T = ColorValue end
                        da = DataArray(T, length(staging[i, j]))
                        copy!(da, staging[i, j])
                        setfield!(aes_grid[i, j], var, da)
                    else
                        setfield!(aes_grid[i, j], var,
                                  convert(typeof(vals), copy(staging[i, j])))
                    end
                end
            end
        else
            for i in 1:n, j in 1:m
                setfield!(aes_grid[i, j], var, vals)
            end
        end
    end

    aes_grid
end


#
# git clone https://github.com/m3g/M3GTools
#

# Load M3GTools
using M3GTools

# Load simulation data
mysim = M3GTools.init(psf="../simulation_files/structure.psf",
                  pdb="../simulation_files/structure.pdb",
                  parfiles = [ "/home/leandro/programs/toppar/charmm/par_all36_prot.prm",
                               "/home/leandro/programs/toppar/charmm/toppar_water_ions.str",
                               "/home/leandro/programs/toppar/charmm/par_all36_lipid.prm" ],
                  vmd="vmd")

# Select atoms of residue number 33 (be careful with the fact that VMD starts counting from zero):
sel1 = M3GTools.select(mysim,"protein and residue 33")

# Select atoms of residue number 29
sel2 = M3GTools.select(mysim,"protein and residue 29")

# Compute electrostatic interaction between these two selections
elecenergy = M3GTools.coulomb(mysim.atoms,sel1,sel2);

# Compute vdw interaction between these two selections
vdwenergy = M3GTools.vdw(mysim.atoms,sel1,sel2);

println("Electrostatic interaction between residues 30 and 34 = $elecenergy")
println("vdW interaction between residues 30 and 34 = $vdwenergy")

# Compute the number of hydrogen bonds between two selections
nhb = M3GTools.nhbonds(mysim.atoms,mysim.bonds,sel1,sel2)
println("Number of H-bonds between residues 30 and 34 = $nhb")


import Random.shuffle!
@testset "fsm_stream_reassembler_win.jl" begin
    NREPS = 32
    NSEGS = 128
    MAX_SEG_LEN = 2048
    for i in 1:NREPS
        buf = StreamReassembler(NSEGS * MAX_SEG_LEN)
        seq_size = Tuple{Int, Int}[]
        offset = 0
        for i in 1:NSEGS
            sz = 1 + rand(UInt) % (MAX_SEG_LEN - 1)
            offs = min(offset, 1 + rand(UInt) % 1023)
            push!(seq_size, (offset - offs, sz + offs))
            offset += sz
        end
        shuffle!(seq_size)
        d = rand(UInt8(1):UInt8(127), offset) |> pointer |> unsafe_string
        for (off, sz) in seq_size
            push_substring!(buf, @view(d[off+1:off+sz]), off, off + sz == offset)
        end
        res = read!(buf.output)
        @test buf.output.bytes_written_count == offset
        @test res == d
    end
end


include("utils.jl")
include("commands.jl")
include("inspect-vectors.jl")
using JLD2

function create_prototypes(trainfile, paramsfile)
    D = loadjson(trainfile; textkey="text", labelkey="klass")
    ncenters_per_class = 30
    kind=:fft
    kncconfig = KncProtoConfig(
        ncenters=-ncenters_per_class,
        maxiters=10,
        initial_clusters=kind,
        kernel=DirectKernel(CosineDistance()),
        centerselection=TextCentroidSelection()
    )
    best = load(paramsfile, "population") |> first |> first
    textconfig = best.textconfig # copy(best.textconfig, nlist=[3])
    config = MicroTC_Config(textconfig, best.textmodel, kncconfig)
    model = MicroTC(config, D.corpus, D.labels; tok=Tokenizer(config.textconfig))
    jldsave("H.model"; model)
    model
    # savemodel("H.model", model)
end

#model = load("H.model", "model")
#model = create_prototypes("pan21/train-es.json_train.json", "_pan21-es-microtc-64+acc+bsize=4/params")