# Create and return VTK grids that are ready to be filled with data (vtu version)
function build_vtk_grids(::Val{:vtu}, mesh::TreeMesh, n_visnodes, verbose,
                         output_directory, is_datafile, filename)
  coordinates, levels, center_level_0, length_level_0 = extract_mesh_information(mesh)

  # Extract number of spatial dimensions
  ndims_ = size(coordinates, 1)

  # Calculate VTK points and cells
  verbose && println("| Preparing VTK cells...")
  if is_datafile
    @timeit "prepare VTK cells (node data)" begin
      vtk_points, vtk_cells = calc_vtk_points_cells(Val(ndims_), coordinates, levels,
                                                    center_level_0, length_level_0, n_visnodes)
    end
  end

  # Prepare VTK points and cells for celldata file
  @timeit "prepare VTK cells (cell data)" begin
    vtk_celldata_points, vtk_celldata_cells = calc_vtk_points_cells(Val(ndims_), coordinates,
                                                                    levels, center_level_0,
                                                                    length_level_0, 1)
  end

  # Determine output file names
  base, _ = splitext(splitdir(filename)[2])
  vtk_filename = joinpath(output_directory, base)
  vtk_celldata_filename = vtk_filename * "_celldata"

  # Open VTK files
  verbose && println("| Building VTK grid...")
  if is_datafile
    @timeit "build VTK grid (node data)" vtk_nodedata = vtk_grid(vtk_filename, vtk_points,
                                                                 vtk_cells)
  else
    vtk_nodedata = nothing
  end
  @timeit "build VTK grid (cell data)" vtk_celldata = vtk_grid(vtk_celldata_filename,
                                                                vtk_celldata_points,
                                                                vtk_celldata_cells)

  return vtk_nodedata, vtk_celldata
end

# Create and return VTK grids that are ready to be filled with data (vti version)
function build_vtk_grids(::Val{:vti}, mesh, n_visnodes, verbose,
                         output_directory, is_datafile, filename)

  coordinates, levels, center_level_0, length_level_0 = extract_mesh_information(mesh)

  # Extract number of spatial dimensions
  ndims_ = size(coordinates, 1)

  # Prepare VTK points and cells for celldata file
  @timeit "prepare VTK cells" vtk_celldata_points, vtk_celldata_cells = calc_vtk_points_cells(
      Val(ndims_), coordinates, levels, center_level_0, length_level_0, 1)

  # Determine output file names
  base, _ = splitext(splitdir(filename)[2])
  vtk_filename = joinpath(output_directory, base)
  vtk_celldata_filename = vtk_filename * "_celldata"

  # Open VTK files
  verbose && println("| Building VTK grid...")
  if is_datafile
    # Determine level-wise resolution
    max_level = maximum(levels)
    resolution = n_visnodes * 2^max_level

    Nx = Ny = resolution + 1
    dx = dy = length_level_0/resolution
    origin = center_level_0 .- 1/2 * length_level_0
    spacing = [dx, dy]
    @timeit "build VTK grid (node data)" vtk_nodedata = vtk_grid(vtk_filename, Nx, Ny,
                                                        origin=origin,
                                                        spacing=spacing)
  else
    vtk_nodedata = nothing
  end
  @timeit "build VTK grid (cell data)" vtk_celldata = vtk_grid(vtk_celldata_filename,
                                                      vtk_celldata_points,
                                                      vtk_celldata_cells)

  return vtk_nodedata, vtk_celldata
end

# Create and return VTK grids that are ready to be filled with data
# (StructuredMesh/UnstructuredMesh2D/P4estMesh version)
function build_vtk_grids(::Val{:vtu},
                         mesh::Union{StructuredMesh, UnstructuredMesh2D, P4estMesh},
                         n_visnodes, verbose, output_directory, is_datafile, filename)

  @timeit "prepare coordinate information" node_coordinates = calc_node_coordinates(mesh, n_visnodes)

  # Calculate VTK points and cells
  verbose && println("| Preparing VTK cells...")
  if is_datafile
    @timeit "prepare VTK cells (node data)" begin
      vtk_points, vtk_cells = calc_vtk_points_cells(node_coordinates)
    end
  end

  # Prepare VTK points and cells for celldata file
  @timeit "prepare VTK cells (cell data)" begin
    vtk_celldata_points, vtk_celldata_cells = calc_vtk_points_cells(node_coordinates)
  end

  # Determine output file names
  base, _ = splitext(splitdir(filename)[2])
  vtk_filename = joinpath(output_directory, base)
  vtk_celldata_filename = vtk_filename * "_celldata"

  # Open VTK files
  verbose && println("| Building VTK grid...")
  if is_datafile
    @timeit "build VTK grid (node data)" vtk_nodedata = vtk_grid(vtk_filename, vtk_points,
                                                                 vtk_cells)
  else
    vtk_nodedata = nothing
  end
  @timeit "build VTK grid (cell data)" vtk_celldata = vtk_grid(vtk_celldata_filename,
                                                                vtk_celldata_points,
                                                                vtk_celldata_cells)

  return vtk_nodedata, vtk_celldata
end

function calc_node_coordinates(mesh::StructuredMesh, n_visnodes)
  # Extract number of spatial dimensions
  ndims_ = ndims(mesh)
  n_elements = prod(size(mesh))

  nodes = range(-1, 1, length=n_visnodes)
  f(args...; kwargs...) = Base.invokelatest(mesh.mapping, args...; kwargs...)

  node_coordinates = Array{Float64, ndims_+2}(undef, ndims_, ntuple(_ -> n_visnodes, ndims_)..., n_elements)

  return calc_node_coordinates!(node_coordinates, f, nodes, mesh)
end

function calc_node_coordinates(mesh::UnstructuredMesh2D, n_visnodes)
  # Extract number of spatial dimensions
  ndims_ = ndims(mesh)
  n_elements = length(mesh)

  # get the uniform nodes
  nodes = range(-1, 1, length=n_visnodes)

  # intialize the container for the node coordinates
  node_coordinates = Array{Float64, ndims_+2}(undef, ndims_, ntuple(_ -> n_visnodes, ndims_)..., n_elements)

  # work container for the corners of elements
  four_corners = zeros(eltype(mesh.corners), 4, 2)

  # loop through all elements and call the correct node coordinate constructor based on curved
  for element = 1:n_elements
    if mesh.element_is_curved[element]
      Trixi.calc_node_coordinates!(node_coordinates, element, nodes, view(mesh.surface_curves, :, element))
    else # straight sided element
      for i in 1:4, j in 1:2
        # pull the (x,y) values of these corners out of the global corners array
        four_corners[i, j] = mesh.corners[j, mesh.element_node_ids[i, element]]
      end
      Trixi.calc_node_coordinates!(node_coordinates, element, nodes, four_corners)
    end
  end

  return node_coordinates
end

function calc_node_coordinates(mesh::P4estMesh, n_visnodes)
  # Extract number of spatial dimensions
  ndims_ = ndims(mesh)

  nodes = range(-1, 1, length=n_visnodes)

  node_coordinates = Array{Float64, ndims_+2}(undef, ndims_,
                                              ntuple(_ -> n_visnodes, ndims_)...,
                                              Trixi.ncells(mesh))

  return Trixi.calc_node_coordinates!(node_coordinates, mesh, nodes)
end

# Calculation of the node coordinates for `StructuredMesh` in 2D
function calc_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, f, nodes, mesh)
  linear_indices = LinearIndices(size(mesh))

  # Get cell length in reference mesh
  dx = 2 / size(mesh, 1)
  dy = 2 / size(mesh, 2)

  for cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1)
    element = linear_indices[cell_x, cell_y]

    # Calculate node coordinates of reference mesh
    cell_x_offset = -1 + (cell_x-1) * dx + dx/2
    cell_y_offset = -1 + (cell_y-1) * dy + dy/2

    for j in eachindex(nodes), i in eachindex(nodes)
      # node_coordinates are the mapped reference node_coordinates
      node_coordinates[:, i, j, element] .= f(cell_x_offset + dx/2 * nodes[i],
                                              cell_y_offset + dy/2 * nodes[j])
    end
  end

  return node_coordinates
end

# Calculation of the node coordinates for `StructuredMesh` in 3D
function calc_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, f, nodes, mesh)
  linear_indices = LinearIndices(size(mesh))

  # Get cell length in reference mesh
  dx = 2 / size(mesh, 1)
  dy = 2 / size(mesh, 2)
  dz = 2 / size(mesh, 3)

  for cell_z in 1:size(mesh, 3), cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1)
    element = linear_indices[cell_x, cell_y, cell_z]

    # Calculate node coordinates of reference mesh
    cell_x_offset = -1 + (cell_x-1) * dx + dx/2
    cell_y_offset = -1 + (cell_y-1) * dy + dy/2
    cell_z_offset = -1 + (cell_z-1) * dz + dz/2

    for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes)
      # node_coordinates are the mapped reference node_coordinates
      node_coordinates[:, i, j, k, element] .= f(cell_x_offset + dx/2 * nodes[i],
                                                 cell_y_offset + dy/2 * nodes[j],
                                                 cell_z_offset + dz/2 * nodes[k])
    end
  end

  return node_coordinates
end

# Determine and return filenames for PVD fiels
function pvd_filenames(filenames, pvd, output_directory)
  # Determine pvd filename
  if !isnothing(pvd)
    # Use filename if given on command line
    filename = pvd

    # Strip of directory/extension
    filename, _ = splitext(splitdir(filename)[2])
  else
    filename = get_pvd_filename(filenames)

    # If filename is empty, it means we were not able to determine an
    # appropriate file thus the user has to supply one
    if filename == ""
      error("could not auto-detect PVD filename (input file names have no common prefix): " *
            "please provide a PVD filename name with the keyword argument `pvd=...`")
    end
  end

  # Get full filenames
  pvd_filename = joinpath(output_directory, filename)
  pvd_celldata_filename = pvd_filename * "_celldata"

  return pvd_filename, pvd_celldata_filename
end

# Determine filename for PVD file based on common name
function get_pvd_filename(filenames)
  filenames = getindex.(splitdir.(filenames), 2)
  bases = getindex.(splitext.(filenames), 1)
  pvd_filename = longest_common_prefix(bases)
  return pvd_filename
end

# Determine longest common prefix
function longest_common_prefix(strings)
  # Return early if array is empty
  if isempty(strings)
    return ""
  end

  # Count length of common prefix, by ensuring that all strings are long enough
  # and then comparing the next character
  len = 0
  while all(length.(strings) .> len) && all(getindex.(strings, len+1) .== strings[1][len+1])
    len +=1
  end

  return strings[1][1:len]
end

# Convert coordinates and level information to a list of points and VTK cells (2D version)
function calc_vtk_points_cells(::Val{2}, coordinates::AbstractMatrix{Float64},
                               levels::AbstractVector{Int},
                               center_level_0::AbstractVector{Float64},
                               length_level_0::Float64,
                               n_visnodes::Int=1)
  ndim = 2
  # Create point locator
  pl = PointLocator{2}(center_level_0, length_level_0, 1e-12)

  # Create arrays for points and cells
  n_elements = length(levels)
  points = Vector{SVector{2, Float64}}()
  vtk_cells = Vector{MeshCell}(undef, n_elements * n_visnodes^ndim)
  point_ids = Vector{Int}(undef, 2^ndim)

  # Reshape cell array for easy-peasy access
  reshaped = reshape(vtk_cells, n_visnodes, n_visnodes, n_elements)

  # Create VTK cell for each Trixi element
  for element_id in 1:n_elements
    # Extract cell values
    cell_x = coordinates[1, element_id]
    cell_y = coordinates[2, element_id]
    cell_dx = length_level_0 / 2^levels[element_id]

    # Adapt to visualization nodes for easy-to-understand loops
    dx = cell_dx / n_visnodes
    x_lowerleft = cell_x - cell_dx/2 - dx/2
    y_lowerleft = cell_y - cell_dx/2 - dx/2

    # Create cell for each visualization node
    for j = 1:n_visnodes
      for i = 1:n_visnodes
        # Determine x and y
        x = x_lowerleft + i * dx
        y = y_lowerleft + j * dx

        # Get point id for each vertex
        point_ids[1] = insert!(pl, points, (x - dx/2, y - dx/2))
        point_ids[2] = insert!(pl, points, (x + dx/2, y - dx/2))
        point_ids[3] = insert!(pl, points, (x - dx/2, y + dx/2))
        point_ids[4] = insert!(pl, points, (x + dx/2, y + dx/2))

        # Add cell
        reshaped[i, j, element_id] = MeshCell(VTKCellTypes.VTK_PIXEL, copy(point_ids))
      end
    end
  end

  # Convert array-of-points to two-dimensional array
  vtk_points = Matrix{Float64}(undef, ndim, length(points))
  for point_id in 1:length(points)
    vtk_points[1, point_id] = points[point_id][1]
    vtk_points[2, point_id] = points[point_id][2]
  end

  return vtk_points, vtk_cells
end

# Convert coordinates and level information to a list of points and VTK cells (3D version)
function calc_vtk_points_cells(::Val{3}, coordinates::AbstractMatrix{Float64},
                               levels::AbstractVector{Int},
                               center_level_0::AbstractVector{Float64},
                               length_level_0::Float64,
                               n_visnodes::Int=1)
  ndim = 3
  # Create point locator
  pl = PointLocator{3}(center_level_0, length_level_0, 1e-12)

  # Create arrays for points and cells
  n_elements = length(levels)
  points = Vector{SVector{3, Float64}}()
  vtk_cells = Vector{MeshCell}(undef, n_elements * n_visnodes^ndim)
  point_ids = Vector{Int}(undef, 2^ndim)

  # Reshape cell array for easy-peasy access
  reshaped = reshape(vtk_cells, n_visnodes, n_visnodes, n_visnodes, n_elements)

  # Create VTK cell for each Trixi element
  for element_id in 1:n_elements
    # Extract cell values
    cell_x = coordinates[1, element_id]
    cell_y = coordinates[2, element_id]
    cell_z = coordinates[3, element_id]
    cell_dx = length_level_0 / 2^levels[element_id]

    # Adapt to visualization nodes for easy-to-understand loops
    dx = cell_dx / n_visnodes
    x_lowerleft = cell_x - cell_dx/2 - dx/2
    y_lowerleft = cell_y - cell_dx/2 - dx/2
    z_lowerleft = cell_z - cell_dx/2 - dx/2

    # Create cell for each visualization node
    for k = 1:n_visnodes, j = 1:n_visnodes, i = 1:n_visnodes
      # Determine x and y
      x = x_lowerleft + i * dx
      y = y_lowerleft + j * dx
      z = z_lowerleft + k * dx

      # Get point id for each vertex
      point_ids[1] = insert!(pl, points, (x - dx/2, y - dx/2, z - dx/2))
      point_ids[2] = insert!(pl, points, (x + dx/2, y - dx/2, z - dx/2))
      point_ids[3] = insert!(pl, points, (x - dx/2, y + dx/2, z - dx/2))
      point_ids[4] = insert!(pl, points, (x + dx/2, y + dx/2, z - dx/2))
      point_ids[5] = insert!(pl, points, (x - dx/2, y - dx/2, z + dx/2))
      point_ids[6] = insert!(pl, points, (x + dx/2, y - dx/2, z + dx/2))
      point_ids[7] = insert!(pl, points, (x - dx/2, y + dx/2, z + dx/2))
      point_ids[8] = insert!(pl, points, (x + dx/2, y + dx/2, z + dx/2))

      # Add cell
      reshaped[i, j, k, element_id] = MeshCell(VTKCellTypes.VTK_VOXEL, copy(point_ids))
    end
  end

  # Convert array-of-points to two-dimensional array
  vtk_points = Matrix{Float64}(undef, ndim, length(points))
  for point_id in 1:length(points)
    vtk_points[1, point_id] = points[point_id][1]
    vtk_points[2, point_id] = points[point_id][2]
    vtk_points[3, point_id] = points[point_id][3]
  end

  return vtk_points, vtk_cells
end

# Convert coordinates and level information to a list of points and VTK cells for `StructuredMesh` (2D version)
function calc_vtk_points_cells(node_coordinates::AbstractArray{<:Any,4})
  n_elements = size(node_coordinates, 4)
  size_ = size(node_coordinates)
  n_points = prod(size_[2:end])
  # Linear indices to access points by node indices and element id
  linear_indices = LinearIndices(size_[2:end])

  # Use lagrange nodes as VTK points
  vtk_points = reshape(node_coordinates, (2, n_points))
  vtk_cells = Vector{MeshCell}(undef, n_elements)

  # Create cell for each element
  for element in 1:n_elements
    vertices = [linear_indices[1, 1, element],
                linear_indices[end, 1, element],
                linear_indices[end, end, element],
                linear_indices[1, end, element]]

    edges = vcat(linear_indices[2:end-1, 1, element],
                 linear_indices[end, 2:end-1, element],
                 linear_indices[2:end-1, end, element],
                 linear_indices[1, 2:end-1, element])

    faces = vec(linear_indices[2:end-1, 2:end-1, element])

    point_ids = vcat(vertices, edges, faces)
    vtk_cells[element] = MeshCell(VTKCellTypes.VTK_LAGRANGE_QUADRILATERAL, point_ids)
  end

  return vtk_points, vtk_cells
end

# Convert coordinates and level information to a list of points and VTK cells for `StructuredMesh` (3D version)
function calc_vtk_points_cells(node_coordinates::AbstractArray{<:Any,5})
  n_elements = size(node_coordinates, 5)
  size_ = size(node_coordinates)
  n_points = prod(size_[2:end])
  # Linear indices to access points by node indices and element id
  linear_indices = LinearIndices(size_[2:end])

  # Use lagrange nodes as VTK points
  vtk_points = reshape(node_coordinates, (3, n_points))
  vtk_cells = Vector{MeshCell}(undef, n_elements)

  # Create cell for each element
  for element in 1:n_elements
    vertices = [linear_indices[1, 1, 1, element],
                linear_indices[end, 1, 1, element],
                linear_indices[end, end, 1, element],
                linear_indices[1, end, 1, element],
                linear_indices[1, 1, end, element],
                linear_indices[end, 1, end, element],
                linear_indices[end, end, end, element],
                linear_indices[1, end, end, element]]

    # This order doesn't make any sense. This is completely different
    # from what is shown in
    # https://blog.kitware.com/wp-content/uploads/2018/09/Source_Issue_43.pdf
    # but this is the way it works.
    edges = vcat(linear_indices[2:end-1, 1, 1, element],
                 linear_indices[end, 2:end-1, 1, element],
                 linear_indices[2:end-1, end, 1, element],
                 linear_indices[1, 2:end-1, 1, element],
                 linear_indices[2:end-1, 1, end, element],
                 linear_indices[end, 2:end-1, end, element],
                 linear_indices[2:end-1, end, end, element],
                 linear_indices[1, 2:end-1, end, element],
                 linear_indices[1, 1, 2:end-1, element],
                 linear_indices[end, 1, 2:end-1, element],
                 linear_indices[1, end, 2:end-1, element],
                 linear_indices[end, end, 2:end-1, element])

    # See above
    faces = vcat(vec(linear_indices[1, 2:end-1, 2:end-1, element]),
                 vec(linear_indices[end, 2:end-1, 2:end-1, element]),
                 vec(linear_indices[2:end-1, 1, 2:end-1, element]),
                 vec(linear_indices[2:end-1, end, 2:end-1, element]),
                 vec(linear_indices[2:end-1, 2:end-1, 1, element]),
                 vec(linear_indices[2:end-1, 2:end-1, end, element]))

    volume = vec(linear_indices[2:end-1, 2:end-1, 2:end-1, element])

    point_ids = vcat(vertices, edges, faces, volume)
    vtk_cells[element] = MeshCell(VTKCellTypes.VTK_LAGRANGE_HEXAHEDRON, point_ids)
  end

  return vtk_points, vtk_cells
end


##############################################################################
##
## FixedEffect
##
## The categoricalarray may have pools that are never referred. Note that the pool does not appear in FixedEffect anyway.
##
##############################################################################

struct FixedEffect{R <: AbstractVector{<:Integer}, I <: AbstractVector{<: Real}}
    refs::R                 # refs of the original CategoricalVector
    interaction::I          # the continuous interaction
    n::Int                  # Number of potential values (= maximum(refs))
    function FixedEffect{R, I}(refs, interaction, n) where {R <: AbstractVector{<:Integer}, I <: AbstractVector{<: Real}}
        isa(refs, Vector) && maximum(refs) > n && error("Categorical Vector used to construct Fixed Effect is malformed. Some elements of refs do not refer to pool")
        new(refs, interaction, n)
    end
end

function FixedEffect(args...; interaction::AbstractVector = Ones{Float64}(length(args[1])))
    if length(args) != 1
        FixedEffect(group(args...); interaction = interaction)
    else
        x = args[1]
        if !isa(x, CategoricalVector)
            FixedEffect(categorical(x); interaction = interaction)
        else
            FixedEffect{typeof(x.refs), typeof(interaction)}(x.refs, interaction, length(x.pool))
        end
    end
end

function Base.show(io::IO, fe::FixedEffect)
    println(io, "Refs:        ", length(fe.refs), "-element ", typeof(fe.refs))
    println(io, "             ", Int.(fe.refs[1:min(5, length(fe.refs))]), "...")
    println(io, "Interaction: ", length(fe.interaction), "-element ", typeof(fe.interaction))
    println(io, "             ", fe.interaction[1:min(5, length(fe.interaction))], "...")
end

Base.ismissing(fe::FixedEffect) = any(fe.refs .== 0) | ismissing(fe.interaction)
Base.length(fe::FixedEffect) = length(fe.refs)
Base.eltype(fe::FixedEffect) = eltype(I)

##############################################################################
##
## group transform multiple CategoricalVector into one
## Output is a CategoricalVector where pool is type Int64, equal to ranking of group
## Missing in some row mean result has Missing on this row
##
##############################################################################

function group(args...)
    v = categorical(args[1])
    if length(args) == 1
        refs = v.refs
    else
        refs, ngroups = convert(Vector{UInt}, v.refs), length(levels(v))
        for j = 2:length(args)
            v = categorical(args[j])
            refs, ngroups = pool_combine!(refs, v, ngroups)
        end
    end
    factorize!(refs)
end
function pool_combine!(refs::Array{T, N}, dv::CategoricalVector, ngroups::Integer) where {T, N}
    for i in 1:length(refs)
        # if previous one is NA or this one is NA, set to NA
        refs[i] = (dv.refs[i] == 0 || refs[i] == zero(T)) ? zero(T) : refs[i] + (dv.refs[i] - 1) * ngroups
    end
    return refs, ngroups * length(levels(dv))
end

function factorize!(refs::Vector{T}) where {T}
    uu = sort!(unique(refs))
    has_missing = uu[1] == 0
    dict = Dict{T, Int}(zip(uu, (1-has_missing):(length(uu)-has_missing)))
    newrefs = zeros(UInt32, length(refs))
    for i in 1:length(refs)
         newrefs[i] = dict[refs[i]]
    end
    Tout = has_missing ? Union{Int, Missing} : Int
    CategoricalArray{Tout, 1}(newrefs, CategoricalPool(collect(1:(length(uu)-has_missing))))
end

##############################################################################
##
## Find connected components
##
##############################################################################
# Return a vector of sets that contains the indices of each unique value
function refsrev(fe::FixedEffect)
    out = Vector{Int}[Int[] for _ in 1:fe.n]
    for i in eachindex(fe.refs)
        push!(out[fe.refs[i]], i)
    end
    return out
 end

# Returns a vector of all components
# A component is a vector that, for each fixed effect,
# contains all the refs that are included in the component.
function components(fes::AbstractVector{<:FixedEffect})
    refs_vec = Vector{UInt32}[fe.refs for fe in fes]
    refsrev_vec = Vector{Vector{Int}}[refsrev(fe) for fe in fes]
    visited = falses(length(refs_vec[1]))
    out = Vector{Set{Int}}[]
    for i in eachindex(visited)
        if !visited[i]
            # obs not visited yet, so create new component
            component_vec = Set{UInt32}[Set{UInt32}() for _ in 1:length(refsrev_vec)]
            # visit all obs in the same components
            tovisit = Set{Int}(i)
            while !isempty(tovisit)
                for (component, refs, refsrev) in zip(component_vec, refs_vec, refsrev_vec)
                    ref = refs[i]
                    # if group is not in component yet
                    if ref ∉ component
                        # add group to the component
                        push!(component, ref)
                        # visit other observations in same group
                        union!(tovisit, refsrev[ref])
                    end
                end
                # mark obs as visited
                i = pop!(tovisit)
                visited[i] = true
            end
            push!(out, component_vec)
        end
    end
    return out
end

##############################################################################
##
## normalize! a vector of fixedeffect coefficients using connected components
##
##############################################################################

function normalize!(fecoefs::AbstractVector{<: Vector{<: Real}}, fes::AbstractVector{<:FixedEffect}; kwargs...)
    # The solution is generally not unique. Find connected components and scale accordingly
    idx = findall(fe -> isa(fe.interaction, Ones), fes)
    length(idx) >= 2 && rescale!(view(fecoefs, idx), view(fes, idx))
    return fecoefs
end

function rescale!(fecoefs::AbstractVector{<: Vector{<: Real}}, fes::AbstractVector{<:FixedEffect})
    for component_vec in components(fes)
        m = 0.0
        # demean all fixed effects except the first
        for j in length(fecoefs):(-1):2
            fecoef, component = fecoefs[j], component_vec[j]
            mj = 0.0
            for k in component
                mj += fecoef[k]
            end
            mj = mj / length(component)
            for k in component
                fecoef[k] -= mj
            end
            m += mj
        end
        # rescale the first fixed effects
        fecoef, component = fecoefs[1], component_vec[1]
        for k in component
            fecoef[k] += m
        end
    end
end

function full(fecoefs::AbstractVector{<: Vector{<: Real}}, fes::AbstractVector{<:FixedEffect})
    [fecoef[fe.refs] for (fecoef, fe) in zip(fecoefs, fes)]
end


abstract type NoAlphaModel <: AlphaModel end

struct NoAlphaParam <: EoSParam
end

@newmodelsimple NoAlpha NoAlphaModel NoAlphaParam

export NoAlpha
function NoAlpha(components::Vector{String}; userlocations::Vector{String}=String[], verbose::Bool=false)
    model = NoAlpha(NoAlphaParam())
    return model
end

function α_function(model::CubicModel,V,T,z,alpha_model::NoAlphaModel)
   return FillArrays.Ones{Float64}(length(z))
end

is_splittable(::NoAlpha) = false


using EvolutionaryModelingTools
using EvolutionaryModelingTools: sample, collectargs, _parsefunc
using Test

@testset "EvolutionaryModelingTools" begin
    @testset "Examples" begin
        # Examples are test with hand-code simulations
        @testset "Logistic growth with noise" begin
            include("example/logistic.jl")
        end
        @testset "Competitive Dynamics with noise and mutation" begin
            include("example/competition.jl")
        end
        @testset "SIR model with noise" begin
            include("example/sir.jl")
        end
    end

    @testset "Sample" begin
        @test sample(1, 1) == CartesianIndex()
        @test sample(fill(1), 1) == CartesianIndex()
        @test sample([1, 2], 2) == CartesianIndex(2)
        @test sample([1 2], 2) == CartesianIndex(1, 2)
        @test sample(reshape(1:4, 2, 2), 1) == CartesianIndex(1, 1)
        @test sample(reshape(1:4, 2, 2), 2) == CartesianIndex(2, 1)
        @test sample(reshape(1:4, 2, 2), 4) == CartesianIndex(1, 2)
        @test sample(reshape(1:4, 2, 2), 7) == CartesianIndex(2, 2)
    end

    @testset "Tools" begin
        @testset "parse function" begin
            fn, args =
                _parsefunc(:(@inline f(rng, ind, v) = v[ind] = rand(rng)), (:rng, :ind))
            @test fn == :f
            @test args == [:rng, :ind, :(args.v)]
        end

        @testset "collectargs" begin
            @test collectargs(quote
                global exp
                iv = f(arg) # internal variable
                mpd = map(exp, iv) # global variable
                A[1] = 1 # setindex
                A[begin+1:end] # setindex
                A[(i = 1; begin+i:end-i)]
                i # i is a local variable defined in above expression
                A[begin+j:end-j] # j is a arguments needed
                arg.x
                g(x) = x + 1 # local function
                (x, y) -> x + y # anonymous function
                @inbounds iv[ind] # macro call
                rand(rng)::Float64 # type annotation
            end) == Set([:A, :arg, :j, :ind, :rng])
            @test collectargs(quote
                iv = f(arg) # internal variable
                mpd = map(exp, iv) # global variable
                A[1] = 1 # setindex
                A[begin+1:end] # setindex
                arg.x
                g(x) = x + 1 # local function
                (x, y) -> x + y # anonymous function
                @inbounds iv[ind] # macro call
                rand(rng)::Float64 # type annotation
            end) == Set([:A, :arg, :exp, :ind, :rng]) # if exp is not mark as global, it will be collected
            @test collectargs(:((x::Int, y) -> x * y)) == Set([:x, :y])
            @test collectargs(:((x, y) -> @ein C[i, j] := x[i] * y[j])) == Set([:x, :y])
        end
    end
end


using Base.Broadcast: broadcastable
using ChainRules
using ChainRulesCore
using ChainRulesTestUtils
using ChainRulesTestUtils: rand_tangent, _fdm
using Compat: hasproperty, only
using FiniteDifferences
using FiniteDifferences: rand_tangent
using SpecialFunctions
using LinearAlgebra
using LinearAlgebra.BLAS
using LinearAlgebra: dot
using Random
using Statistics
using Test

Random.seed!(1) # Set seed that all testsets should reset to.

function include_test(path)
    print("Testing $path:\t")  # print so TravisCI doesn't timeout due to no output
    @time include(path)  # show basic timing, (this will print a newline at end)
end

println("Testing ChainRules.jl")
@testset "ChainRules" begin
    @testset "rulesets" begin
        @testset "Base" begin
            include_test("rulesets/Base/base.jl")
            include_test("rulesets/Base/fastmath_able.jl")
            include_test("rulesets/Base/evalpoly.jl")
            include_test("rulesets/Base/array.jl")
            include_test("rulesets/Base/arraymath.jl")
            include_test("rulesets/Base/indexing.jl")
            include_test("rulesets/Base/mapreduce.jl")
        end
        println()

        @testset "Statistics" begin
            include_test("rulesets/Statistics/statistics.jl")
        end
        println()

        @testset "LinearAlgebra" begin
            include_test("rulesets/LinearAlgebra/dense.jl")
            include_test("rulesets/LinearAlgebra/norm.jl")
            include_test("rulesets/LinearAlgebra/matfun.jl")
            include_test("rulesets/LinearAlgebra/structured.jl")
            include_test("rulesets/LinearAlgebra/symmetric.jl")
            include_test("rulesets/LinearAlgebra/factorization.jl")
            include_test("rulesets/LinearAlgebra/blas.jl")
        end
        println()

        @testset "Random" begin
            include_test("rulesets/Random/random.jl")
        end
        println()

        @testset "packages" begin
            include_test("rulesets/packages/NaNMath.jl")
            # Note: drop SpecialFunctions dependency in next breaking release
            # https://github.com/JuliaDiff/ChainRules.jl/issues/319
            if !isdefined(SpecialFunctions, :ChainRulesCore)
                include_test("rulesets/packages/SpecialFunctions.jl")
            end
        end
        println()
    end
end


module AtomicGraphNets

export AGNConv, AGNPool#, AGNConvDEQ
include("layers.jl")
using .layers: AGNConv, AGNPool

include("models.jl")
export Xie_model

end


module CLIntrinsics

using MacroTools

import ..Transpiler: AbstractCLIO, EmptyCLIO
import ..Transpiler: ints, floats, numbers, Numbers, Floats, int, Ints, uchar
import ..Transpiler: fixed_array_length, is_fixedsize_array
import ..Transpiler: ret, vecs, Vecs, vector_lengths, functions

using StaticArrays, Sugar
import Sugar: typename, vecname
using SpecialFunctions: erf, erfc

# TODO, these are rather global pointers and this should be represented in the type
immutable GlobalPointer{T} end
immutable LocalPointer{T} end
immutable LocalArray{T} end
Base.eltype(::Type{GlobalPointer{T}}) where T = T
Base.eltype(::Type{LocalPointer{T}}) where T = T
Base.eltype(::Type{LocalArray{T}}) where T = T

const DevicePointer = Union{GlobalPointer, LocalPointer}
const Types = Union{vecs..., numbers..., GlobalPointer, LocalPointer}

#########
# GLOBALS
const CLK_LOCAL_MEM_FENCE = Cuint(0)
const CLK_GLOBAL_MEM_FENCE = Cuint(0)

const intrinsic_signatures = Dict{Function, Any}()

macro cl_intrinsic(expr)
    matched = @capture(
        expr,
        (func_(args__) where {T__} = body_) | (func_(args__) = body_)
    )
    @assert matched "internal error: intrinsic wasn't matched: $expr"
    ret_expr = Expr(:block)

    # it's possible to define methods in base as intrinsic.
    # if they're not in base, we need to define a function stub
    if !isdefined(Base, func)
        push!(ret_expr.args, esc(expr))
    end
    types = map(args) do arg
        @assert isa(arg, Expr) && arg.head == :(::) "wrong type declaration"
        arg.args[end]
    end
    tuple_typ = :(Tuple{$(types...)})
    if T != nothing
        tuple_typ = :($tuple_typ where {$(T...)})
    end
    push!(ret_expr.args, :(intrinsic_signatures[$func] = $tuple_typ))
    ret_expr
end

@cl_intrinsic get_num_groups(dim::Integer) = ret(Cuint)
@cl_intrinsic get_global_id(dim::Integer) = ret(Cuint)
@cl_intrinsic get_local_id(::Integer) = ret(Cuint)
@cl_intrinsic get_group_id(::Integer) = ret(Cuint)
@cl_intrinsic get_local_size(::Integer) = ret(Cuint)
@cl_intrinsic get_global_size(::Integer) = ret(Cuint)
@cl_intrinsic get_work_dim() = ret(Cuint)
@cl_intrinsic sizeof(x::Any) = ret(Cuint)

@cl_intrinsic intrinsic_select(a::T, b::T, c::Any) where T = ret(T)

for (a, b) in (
        Float32 => UInt32,
        Float64 => UInt64,
        Int32 => UInt32,
        Int64 => UInt64,
        Bool => Bool
    )
    @eval cl_select(a::$a, b::$a, c::Bool) = intrinsic_select(b, a, $b(c))
end
cl_select(a, b, c::Bool) = c ? a : b

# @cl_intrinsic clt(::T, ::T, ::Bool) where {T} = ret(T)

@cl_intrinsic barrier(::Cuint) = nothing
@cl_intrinsic mem_fence(::Cuint) = nothing

@cl_intrinsic erfc(::T) where T <: Floats = ret(T)
@cl_intrinsic erf(::T) where T <: Floats = ret(T)
@cl_intrinsic remainder(::T, ::T) where T <: Floats = ret(T)
@cl_intrinsic exp(::T) where T <: Floats = ret(T)

for N in vector_lengths
    fload = Symbol(string("vload", N))
    fstore = Symbol(string("vstore", N))
    @eval begin
        @cl_intrinsic $(fload)(i::Integer, a::GlobalPointer{T}) where {T <: Numbers} = ret(NTuple{$N, T})
        @cl_intrinsic $(fstore)(x::NTuple{$N, T}, i::Integer, a::GlobalPointer{T}) where {T <: Numbers} = nothing
    end
end

end # end CLIntrinsics

using .CLIntrinsics

const cli = CLIntrinsics

import .cli: GlobalPointer, DevicePointer

import Sugar: typename, isintrinsic

function is_native_type(m::CLMethod, T)
    T <: cli.Types || is_fixedsize_array(m, T) || T <: Tuple{T} where T <: cli.Numbers
end

function isintrinsic(m::CLMethod, func::ANY, sig_tuple::ANY)
    # constructors are intrinsic. TODO more thorow lookup to match actual inbuild constructor
    isa(func, DataType) && is_native_type(m, func) && return true
    func == tuple && return true # TODO match against all Base intrinsics?
    func == getfield && sig_tuple <: (Tuple{X, Symbol} where X) && return true
    func == getfield && sig_tuple <: (Tuple{X, Integer} where X <: Tuple) && return true
    func == muladd && sig_tuple <: (Tuple{T, T, T} where T <: AbstractFloat) && return true
    Sugar.isintrinsic(func) && return true
    #TODO better julia/cl intrinsic matching
    func == Base.select_value && return true
    # Symbol(func) == Symbol("GPUArrays.LocalMemory") && return true
    # shared intrinsic functions should all work on all native types.
    # TODO, find exceptions where this isn't true
    func in functions && all(x-> is_native_type(m, x), Sugar.to_tuple(sig_tuple)) && return true
    haskey(cli.intrinsic_signatures, func) || return false
    sig = cli.intrinsic_signatures[func]
    sig_tuple <: sig
end

Base.getindex{T}(a::cli.LocalPointer{T}, i::Integer) = cli.ret(T)
Base.getindex{T}(a::GlobalPointer{T}, i::Integer) = cli.ret(T)

Base.setindex!{T}(::cli.LocalPointer{T}, ::T, ::Integer) = nothing
Base.setindex!{T}(a::GlobalPointer{T}, value::T, i::Integer) = nothing
function Base.setindex!(a::GlobalPointer{T}, value::T2, i::Integer) where {T, T2}
    setindex!(a, T(value), i)
    nothing
end

# TODO overload SIMD.vload, so that code can run seamlessly on the CPU as well.
for VecType in (NTuple, SVector)
    for N in cli.vector_lengths
        fload = Symbol(string("vload", N))
        fstore = Symbol(string("vstore", N))
        VType = :($VecType{$N, T})
        @eval begin
            function vload{T <: cli.Numbers, IT <: Integer}(::Type{$VType}, a::GlobalPointer{$VType}, i::IT)
                $VType(cli.$(fload)(i - IT(1), GlobalPointer{T}(a)))
            end
            function vstore{T <: cli.Numbers, IT <: Integer}(x::$VType, a::GlobalPointer{$VType}, i::IT)
                cli.$(fstore)(Tuple(x), i - IT(1), GlobalPointer{T}(a))
            end
        end
    end
end

Base.getindex{T <: Vecs}(a::GlobalPointer{T}, i::Integer) = vload(T, a, i)
function Base.setindex!{T <: Vecs}(a::GlobalPointer{T}, value::T, i::Integer)
    vstore(value, a, i)
end

function supports_indexing(m::LazyMethod, ::Type{T}) where T
    is_fixedsize_array(T) || T <: DevicePointer
end

function supports_indices(m::LazyMethod, ::Type{<: GlobalPointer{T}}, index_types) where T
    is_fixedsize_array(m, T) && return false # fixed size arrays are implemented via vstore/load
    length(index_types) == 1 && index_types[1] <: Integer
end
function supports_indices(m::LazyMethod, ::Type{<: DevicePointer}, index_types)
    length(index_types) == 1 && index_types[1] <: Integer
end
function supports_indices(m::LazyMethod, ::Type{<: Tuple}, index_types)
    length(index_types) == 1 && index_types[1] <: Integer
end

function typename(io::AbstractCLIO, x::Type{GlobalPointer{T}}) where T
    tname = typename(io, T)
    # restrict should be fine for now, since we haven't implemented views yet!
    "__global $tname * "
end
function typename(io::AbstractCLIO, x::Type{cli.LocalPointer{T}}) where T
    tname = typename(io, T)
    "__local $tname * "
end

function Sugar.vecname(io::AbstractCLIO, t::Type{T}) where T
    N = fixed_array_length(T)
    ET = eltype(T)
    etname = typename(io, ET)
    return string(etname, N)
end


fname = :TRKOp
c = Combi(hessian_operator, PDataK, solve_modelKTR, preprocessKTR, decreaseKTR, TparamsKTR())
include("Template.jl")


@testset "Differentials" begin
    @testset "Wirtinger" begin
        w = Wirtinger(1+1im, 2+2im)
        @test wirtinger_primal(w) == 1+1im
        @test wirtinger_conjugate(w) == 2+2im
        @test w + w == Wirtinger(2+2im, 4+4im)

        @test w + One() == w + 1 == w + Thunk(()->1) == Wirtinger(2+1im, 2+2im)
        @test w * One() == One() * w == w
        @test w * 2 == 2 * w == Wirtinger(2 + 2im, 4 + 4im)

        # TODO: other + methods stack overflow
        @test_throws ErrorException w*w
        @test_throws ArgumentError extern(w)
        for x in w
            @test x === w
        end
        @test broadcastable(w) == w
        @test_throws MethodError conj(w)
    end
    @testset "Zero" begin
        z = Zero()
        @test extern(z) === false
        @test z + z == z
        @test z + 1 == 1
        @test 1 + z == 1
        @test z * z == z
        @test z * 1 == z
        @test 1 * z == z
        for x in z
            @test x === z
        end
        @test broadcastable(z) isa Ref{Zero}
        @test conj(z) == z
    end
    @testset "One" begin
        o = One()
        @test extern(o) === true
        @test o + o == 2
        @test o + 1 == 2
        @test 1 + o == 2
        @test o * o == o
        @test o * 1 == 1
        @test 1 * o == 1
        for x in o
            @test x === o
        end
        @test broadcastable(o) isa Ref{One}
        @test conj(o) == o
    end

    @testset "Thunk" begin
        @test @thunk(3) isa Thunk

        @testset "show" begin
            rep = repr(Thunk(rand))
            @test occursin(r"Thunk\(.*rand.*\)", rep)
        end

        @testset "Externing" begin
            @test extern(@thunk(3)) == 3
            @test extern(@thunk(@thunk(3))) == 3
        end

        @testset "calling thunks should call inner function" begin
            @test (@thunk(3))() == 3
            @test (@thunk(@thunk(3)))() isa Thunk
        end

        @testset "erroring thunks should include the source in the backtrack" begin
            expected_line = (@__LINE__) + 2  # for testing it is at right palce
            try
                x = @thunk(error())
                extern(x)
            catch err
                err isa ErrorException || rethrow()
                st = stacktrace(catch_backtrace())
                # Should be 2nd last line, as last line will be the `error` function
                stackframe = st[2]
                @test stackframe.line == expected_line
                @test stackframe.file == Symbol(@__FILE__)
            end
        end
    end

    @testset "No ambiguities in $f" for f in (+, *)
        # We don't use `Test.detect_ambiguities` as we are only interested in
        # the +, and * operations. We also would catch any that are unrelated
        # to this package. but that is not a problem. Since no such failings
        # occur in our dependencies.

        ambig_methods = [
            (m1, m2) for m1 in methods(f), m2 in methods(f) if Base.isambiguous(m1, m2)
        ]
        @test isempty(ambig_methods)
    end

    @testset "Refine Differential" begin
        @test refine_differential(typeof(1.0 + 1im), Wirtinger(2,2)) == Wirtinger(2,2)
        @test refine_differential(typeof([1.0 + 1im]), Wirtinger(2,2)) == Wirtinger(2,2)

        @test refine_differential(typeof(1.2), Wirtinger(2,2)) == 4
        @test refine_differential(typeof([1.2]), Wirtinger(2,2)) == 4

        # For most differentials, in most domains, this does nothing
        for der in (DoesNotExist(), @thunk(23), @thunk(Wirtinger(2,2)), [1 2], One(), Zero(), 0.0)
            for 𝒟 in typeof.((1.0 + 1im, [1.0 + 1im], 1.2, [1.2]))
                @test refine_differential(𝒟, der) === der
            end
        end
    end
end


function check_error(err, error_msg)
    if err != 0
        error(error_msg * " with error: $(err)")
    end
end

const FREE_FONT_LIBRARY = FT_Library[C_NULL]

function ft_init()
    FREE_FONT_LIBRARY[1] != C_NULL && error("Freetype already initalized. init() called two times?")
    err = FT_Init_FreeType(FREE_FONT_LIBRARY)
    return err == 0
end

function ft_done()
    FREE_FONT_LIBRARY[1] == C_NULL && error("Library == CNULL. FreeTypeAbstraction.done() called before init(), or done called two times?")
    err = FT_Done_FreeType(FREE_FONT_LIBRARY[1])
    FREE_FONT_LIBRARY[1] = C_NULL
    return err == 0
end

function newface(facename, faceindex::Real=0, ftlib=FREE_FONT_LIBRARY)
    face = Ref{FT_Face}()
    err = FT_New_Face(ftlib[1], facename, Int32(faceindex), face)
    check_error(err, "Couldn't load font $facename")
    return face[]
end

struct FontExtent{T}
    vertical_bearing::Vec{2, T}
    horizontal_bearing::Vec{2, T}

    advance::Vec{2, T}
    scale::Vec{2, T}
end

BroadcastStyle(::Type{<: FontExtent}) = Style{FontExtent}()
BroadcastStyle(::Style{FontExtent}, x) = Style{FontExtent}()
BroadcastStyle(x, ::Style{FontExtent}) = Style{FontExtent}()

function broadcasted(op::Function, f::FontExtent, scaling::StaticVector)
    return FontExtent(
        op.(f.vertical_bearing, scaling[1]),
        op.(f.horizontal_bearing, scaling[2]),
        op.(f.advance, scaling),
        op.(f.scale, scaling),
    )
end

function broadcasted(op::Function, f::FontExtent)
    return FontExtent(
        op.(f.vertical_bearing),
        op.(f.horizontal_bearing),
        op.(f.advance),
        op.(f.scale),
    )
end

function broadcasted(op::Function, ::Type{T}, f::FontExtent) where T
    return FontExtent(
        map(x-> op(T, x), f.vertical_bearing),
        map(x-> op(T, x), f.horizontal_bearing),
        map(x-> op(T, x), f.advance),
        map(x-> op(T, x), f.scale),
    )
end

function FontExtent(fontmetric::FreeType.FT_Glyph_Metrics, scale::T = 64.0) where T <: AbstractFloat
    return FontExtent(
        Vec{2, T}(fontmetric.vertBearingX, fontmetric.vertBearingY) ./ scale,
        Vec{2, T}(fontmetric.horiBearingX, fontmetric.horiBearingY) ./ scale,
        Vec{2, T}(fontmetric.horiAdvance, fontmetric.vertAdvance) ./ scale,
        Vec{2, T}(fontmetric.width, fontmetric.height) ./ scale
    )
end

function ==(x::FontExtent, y::FontExtent)
    return (x.vertical_bearing == y.vertical_bearing &&
            x.horizontal_bearing == y.horizontal_bearing &&
            x.advance == y.advance &&
            x.scale == y.scale)
end

function FontExtent(fontmetric::FreeType.FT_Glyph_Metrics, scale::Integer)
    return FontExtent(
        div.(Vec{2, Int}(fontmetric.vertBearingX, fontmetric.vertBearingY), scale),
        div.(Vec{2, Int}(fontmetric.horiBearingX, fontmetric.horiBearingY), scale),
        div.(Vec{2, Int}(fontmetric.horiAdvance, fontmetric.vertAdvance), scale),
        div.(Vec{2, Int}(fontmetric.width, fontmetric.height), scale)
    )
end

function bearing(extent::FontExtent)
    return Vec2f0(extent.horizontal_bearing[1],
                  -(extent.scale[2] - extent.horizontal_bearing[2]))
end

function safe_free(face)
    ptr = getfield(face, :ft_ptr)
    if ptr != C_NULL && FREE_FONT_LIBRARY[1] != C_NULL
        FT_Done_Face(face)
    end
end

function boundingbox(extent::FontExtent)
    mini = bearing(extent)
    return Rect2D(mini, Vec2f0(extent.scale))
end

mutable struct FTFont
    ft_ptr::FreeType.FT_Face
    current_pixelsize::Base.RefValue{Int}
    use_cache::Bool
    cache::Dict{Tuple{Int, Char}, FontExtent{Float32}}
    function FTFont(ft_ptr::FreeType.FT_Face, pixel_size::Int=64, use_cache::Bool=true)
        cache = Dict{Tuple{Int, Char}, FontExtent{Float32}}()
        face = new(ft_ptr, Ref(pixel_size), use_cache, cache)
        finalizer(safe_free, face)
        FT_Set_Pixel_Sizes(face, pixel_size, 0);
        return face
    end
end

use_cache(face::FTFont) = getfield(face, :use_cache)
get_cache(face::FTFont) = getfield(face, :cache)

function FTFont(path::String)
    return FTFont(newface(path))
end

# C interop
function Base.cconvert(::Type{FreeType.FT_Face}, font::FTFont)
    return font
end

function Base.unsafe_convert(::Type{FreeType.FT_Face}, font::FTFont)
    return getfield(font, :ft_ptr)
end

function Base.propertynames(font::FTFont)
    return fieldnames(FreeType.FT_FaceRec)
end

function Base.getproperty(font::FTFont, fieldname::Symbol)
    fontrect = unsafe_load(getfield(font, :ft_ptr))
    field = getfield(fontrect, fieldname)
    if field isa Ptr{FT_String}
        field == C_NULL && return ""
        return unsafe_string(field)
    # Some fields segfault with unsafe_load...Lets find out which another day :D
    elseif field isa Ptr{FreeType.LibFreeType.FT_GlyphSlotRec}
        return unsafe_load(field)
    else
        return field
    end
end

get_pixelsize(face::FTFont) = getfield(face, :current_pixelsize)[]

function set_pixelsize(face::FTFont, size::Integer)
    get_pixelsize(face) == size && return size
    err = FT_Set_Pixel_Sizes(face, size, size)
    check_error(err, "Couldn't set pixelsize")
    getfield(face, :current_pixelsize)[] = size
    return size
end

function kerning(c1::Char, c2::Char, face::FTFont)
    i1 = FT_Get_Char_Index(face, c1)
    i2 = FT_Get_Char_Index(face, c2)
    kerning2d = Ref{FreeType.FT_Vector}()
    err = FT_Get_Kerning(face, i1, i2, FreeType.FT_KERNING_DEFAULT, kerning2d)
    # Can error if font has no kerning! Since that's somewhat expected, we just return 0
    err != 0 && return Vec2f0(0)
    # 64 since metrics are in 1/64 units (units to 26.6 fractional pixels)
    divisor = 64
    return Vec2f0(kerning2d[].x / divisor, kerning2d[].y / divisor)
end

function loadchar(face::FTFont, c::Char)
    err = FT_Load_Char(face, c, FT_LOAD_RENDER)
    check_error(err, "Could not load char to render.")
end

function get_extent(face::FTFont, char::Char)
    if use_cache(face)
        get!(get_cache(face), (get_pixelsize(face), char)) do
            return internal_get_extent(face, char)
        end
    else
        return internal_get_extent(face, char)
    end
end

function internal_get_extent(face::FTFont, char::Char)
    err = FT_Load_Char(face, char, FT_LOAD_DEFAULT)
    check_error(err, "Could not load char to get extend.")
    metrics = face.glyph.metrics
    # 64 since metrics are in 1/64 units (units to 26.6 fractional pixels)
    return FontExtent(metrics, Float32(64))
end


#################### Slice Sampler ####################

#################### Types and Constructors ####################

const SliceForm = Union{Univariate, Multivariate}

mutable struct SliceTune{F<:SliceForm} <: SamplerTune
  logf::Union{Function, Missing}
  width::Union{Float64, Vector{Float64}}

  SliceTune{F}() where {F<:SliceForm} = new{F}()

  SliceTune{F}(x::Vector, width) where {F<:SliceForm} =
    SliceTune{F}(x, width, missing)

  SliceTune{F}(x::Vector, width::Real, logf::Union{Function, Missing}) where
    {F<:SliceForm} = new{F}(logf, Float64(width))

  SliceTune{F}(x::Vector, width::Vector, logf::Union{Function, Missing}) where
    {F<:SliceForm} = new{F}(logf, convert(Vector{Float64}, width))
end

const SliceUnivariate = SamplerVariate{SliceTune{Univariate}}
const SliceMultivariate = SamplerVariate{SliceTune{Multivariate}}

validate(v::SamplerVariate{SliceTune{F}}) where {F<:SliceForm} =
  validate(v, v.tune.width)

validate(v::SamplerVariate{SliceTune{F}}, width::Float64) where {F<:SliceForm} = v

function validate(v::SamplerVariate{SliceTune{F}}, width::Vector) where {F<:SliceForm}
  n = length(v)
  length(width) == n ||
    throw(ArgumentError("length(width) differs from variate length $n"))
  v
end

#################### Sampler Constructor ####################
"""
Slice(params::ElementOrVector{Symbol},
                width::ElementOrVector{T},
                ::Type{F}=Multivariate;
                transform::Bool=false) where {T<:Real, F<:SliceForm}

Construct a `Sampler` object for Slice sampling. Parameters are assumed to be
continuous, but may be constrained or unconstrained.

Returns a `Sampler{SliceTune{Univariate}}` or `Sampler{SliceTune{Multivariate}}`
type object if sampling univariately or multivariately, respectively.

  * `params`: stochastic node(s) to be updated with the sampler.

  * `width`: scaling value or vector of the same length as the combined elements of nodes `params`, defining initial widths of a hyperrectangle from which to simulate values.

  * `F` : sampler type. Options are
      * `:Univariate` : sequential univariate sampling of parameters.
      * `:Multivariate` : joint multivariate sampling.

  * `transform`: whether to sample parameters on the link-transformed scale (unconstrained parameter space). If `true`, then constrained parameters are mapped to unconstrained space according to transformations defined by the Stochastic `unlist()` function, and `width` is interpreted as being relative to the unconstrained parameter space. Otherwise, sampling is relative to the untransformed space.
"""
function Slice(params::ElementOrVector{Symbol},
                width::ElementOrVector{T},
                ::Type{F}=Multivariate;
                transform::Bool=false) where {T<:Real, F<:SliceForm}
  samplerfx = function(model::Model, block::Integer)
    block = SamplingBlock(model, block, transform)
    v = SamplerVariate(block, width)
    sample!(v, x -> logpdf!(block, x))
    relist(block, v)
  end
  Sampler(params, samplerfx, SliceTune{F}())
end

#################### Sampling Functions ####################

sample!(v::Union{SliceUnivariate, SliceMultivariate}) = sample!(v, v.tune.logf)
"""
    sample!(v::Union{SliceUnivariate, SliceMultivariate}, logf::Function)

Draw one sample from a target distribution using the Slice univariate or
multivariate sampler. Parameters are assumed to be continuous, but may be
constrained or unconstrained.

Returns `v` updated with simulated values and associated tuning parameters.
"""
function sample!(v::Union{SliceUnivariate, SliceMultivariate}, logf::Function)
    typeof(v.value[1]) <:GeneralNode ? sample_node!(v, logf) : sample_number!(v, logf)
end

function sample_node!(v::SliceUnivariate, logf::Function)
  tree = v.value[1]

  logf0 = logf(tree)
  blv = get_branchlength_vector(tree)

  n = length(blv)
  lower = blv - v.tune.width .* rand(n)
  lower[lower .< 0.0] .= 0.0
  upper = lower .+ v.tune.width

  for i in 1:n
    p0 = logf0 + log(rand())

    x = blv[i]
    blv[i] = rand(Uniform(lower[i], upper[i]))
    while true
      set_branchlength_vector!(tree, blv)
      logf0 = logf(tree)
      logf0 < p0 || break
      value = blv[i]
      if value < x
        lower[i] = value
      else
        upper[i] = value
      end
      blv[i] = rand(Uniform(lower[i], upper[i]))
    end
  end

  v
end

function sample_node!(v::SliceMultivariate, logf::Function)
  tree = v.value[1]

  p0 = logf(tree) + log(rand())
  blv = get_branchlength_vector(tree)
  org = deepcopy(blv)

  n = length(blv)
  lower = blv - v.tune.width .* rand(n)
  lower[lower .< 0.0] .= 0.0
  upper = lower .+ v.tune.width

  blv = v.tune.width .* rand(n) + lower
  set_branchlength_vector!(tree, blv)
  while logf(tree) < p0
    for i in 1:n
      value = blv[i]
      if value < org[i]
        lower[i] = value
      else
        upper[i] = value
      end
      blv[i] = rand(Uniform(lower[i], upper[i]))
    end
    set_branchlength_vector!(tree, blv)
  end

  v.value[1] = tree
  v
end

function sample_number!(v::SliceUnivariate, logf::Function)
  logf0 = logf(v.value)

  n = length(v)
  lower = v - v.tune.width .* rand(n)
  upper = lower .+ v.tune.width

  for i in 1:n
    p0 = logf0 + log(rand())

    x = v[i]
    v[i] = rand(Uniform(lower[i], upper[i]))
    while true
      logf0 = logf(v.value)
      logf0 < p0 || break
      value = v[i]
      if value < x
        lower[i] = value
      else
        upper[i] = value
      end
      v[i] = rand(Uniform(lower[i], upper[i]))
    end
  end

  v
end

function sample_number!(v::SliceMultivariate, logf::Function)
  p0 = logf(v.value) + log(rand())

  n = length(v)
  lower = v - v.tune.width .* rand(n)
  upper = lower .+ v.tune.width

  x = v.tune.width .* rand(n) + lower
  while logf(x) < p0
    for i in 1:n
      value = x[i]
      if value < v[i]
        lower[i] = value
      else
        upper[i] = value
      end
      x[i] = rand(Uniform(lower[i], upper[i]))
    end
  end
  v[:] = x

  v
end


# modified from Amanda's legacy parsing code; see more notes in wntr_dict_parser.jl

"""
Convert dictionary of water network to WaterSystems structure
"""
function dict_to_struct(data::Dict{String,Any})
    junctions = junction_to_struct(data["Junction"])
    j_dict = Dict{String, Junction}(junction.name => junction for junction in junctions)
    arcs = link_to_struct(data["Link"], j_dict)
    a_dict = Dict{String, Arc}(arc.name => arc for arc in arcs)
    res = res_to_struct(data["Reservoir"], j_dict)
    demands = demand_to_struct(data["Demand"], j_dict)
    tanks = tank_to_struct(data["Tank"], j_dict)
    patterns = pattern_to_struct(data["Pattern"])
    curves = curve_to_struct(data["Curve"])
    pipes = pipe_to_struct(data["Pipe"], a_dict)
    pumps = pump_to_struct(data["Pump"], a_dict, curves, patterns)
    valves = valve_to_struct(data["Valve"], a_dict)

    return junctions, arcs, res, demands, tanks, patterns, curves, pipes, pumps, valves
end

"""
Create array of junctions using WaterSystems.Junction type
"""
function junction_to_struct(j_dict::Dict{String, Any})
    junctions = Vector{Junction}(undef, length(j_dict))
    for (i, (name, junction)) in enumerate(j_dict)
        junctions[i] = Junction(name, junction["elevation"], junction["press_head"],
                                junction["coordinates"], nothing)
    end
    return junctions
end

"""
Create array of arcs using WaterSystems.Arc type
"""
function link_to_struct(l_dict::Dict{String, Any}, j_dict::Dict{String,Junction})
    arcs = Vector{Arc}(undef, length(l_dict))
    for (i, (name,link)) in enumerate(l_dict)
        from_name = link["connectionpoints"][:from]["name"]
        to_name = link["connectionpoints"][:to]["name"]
        arcs[i] = Arc(name, j_dict[from_name], j_dict[to_name])
    end
    return arcs
end

"""
Create array of reservoirs using WaterSystems.Reservoir type.
"""
function res_to_struct(r_vec::Vector{Any}, j_dict::Dict{String,Junction})
    reservoirs = Vector{Reservoir}(undef,length(r_vec))
    for (i, reservoir) in enumerate(r_vec)
        name = reservoir["name"]
        junction = j_dict[name]
        # assign the reservoir head pattern to the junction
        junction.pattern_name = reservoir["pattern_name"]
        reservoirs[i] = Reservoir(name, true, junction)
    end
    return reservoirs
end

"""
Create array of tanks using WaterSystems.Tank types. Only cylindrical tanks are
currently supported.
"""
function tank_to_struct(t_vec::Vector{Any}, j_dict::Dict{String,Junction})
    tanks = Vector{Tank}(undef, length(t_vec))
    for (i, tank) in enumerate(t_vec)
        name = tank["name"]
        tanks[i] = CylindricalTank(name, true, j_dict[name], tank["diameter"], tank["level"],
                                   tank["levellimits"])
    end
    return tanks
end

"""
Create array of demands using WaterSystems.WaterDemand types. Only static demands are
currently supported.
"""
function demand_to_struct(d_vec::Vector{Any}, j_dict::Dict{String,Junction})
    # Only populating base_demand and the pattern name; the actual forecast will be created
    # and added separately, following the paradigm of PowerSystems.jl. Note that the
    # base_demand and pattern values can vary wildly with no consistent scaling. Would it be
    # useful (considering the computational cost) to rescale all of the base demands and the
    # patterns? See "demand_dict!" in wntr_dict_parser.jl, JJS 12/26/19
    demands = Vector{WaterDemand}(undef, length(d_vec))
    for (i,  demand) in enumerate(d_vec)
        name = demand["name"]
        base_demand = demand["base_demand"]
        pattern_name = demand["pattern_name"]
        demands[i] = StaticDemand(name, true, j_dict[name], base_demand, pattern_name)
    end
    return demands
end

"""
Create array of patterns using WaterSystems.Pattern type.
"""
function pattern_to_struct(p_vec::Vector{Any})
    patterns = Vector{Pattern}(undef, length(p_vec))
    for (i,curve) in enumerate(p_vec)
        patterns[i] = Pattern(curve["name"], curve["multipliers"])
    end
    return patterns
end

"""
Create array of curves using WaterSystems.Curve type.
"""
function curve_to_struct(c_vec::Vector{Any})
    curves = Vector{Curve}(undef, length(c_vec))
    for (i,curve) in enumerate(c_vec)
        curves[i] = Curve(curve["name"], curve["type"], curve["points"])
    end
    return curves
end

"""
Create array of pipes using WaterSystems.Pipe types.
"""
function pipe_to_struct(pi_vec::Vector{Any}, a_dict::Dict{String,Arc})
    pipes = Vector{Pipe}(undef, length(pi_vec))
    # define arbitrary flow limits -- just remove flow limits from pipes? JJS 1/28/19
    #unid_flowlimits = CVPipe(nothing).flowlimits # neat trick to get defaults, not using
    #bidi_flowlimits = GatePipe(nothing).flowlimits
    unid_flowlimits = (min = small, max = large)
    bidi_flowlimits = (min = -large, max = large)
    for (i, pipe) in enumerate(pi_vec)
        name = pipe["name"]
        if pipe["cv"] == true
            pipes[i] = CVPipe(name, a_dict[name], true, pipe["diameter"], pipe["length"],
                              pipe["roughness"], unid_flowlimits, nothing, nothing)
        elseif pipe["control_pipe"] == true
            # add initial status (open/closed)? would need to add the field, JJS 12/26/19
            pipes[i] = GatePipe(name, a_dict[name], true, pipe["diameter"], pipe["length"],
                               pipe["roughness"], bidi_flowlimits, nothing, nothing, nothing)
        else
            pipes[i] = OpenPipe(name, a_dict[name], true, pipe["diameter"], pipe["length"],
                                pipe["roughness"], bidi_flowlimits, nothing, nothing)
        end
    end
    return pipes
end

"""
Create array of pumps using WaterSystem.Pump type.
"""
function pump_to_struct(pu_vec::Vector{Any}, a_dict::Dict{String,Arc}, c_vec::Vector{Curve},
                        pa_vec::Vector{Pattern})
    pumps = Vector{Pump}(undef, length(pu_vec))
    c_dict = Dict{String, Curve}(curve.name => curve for curve in c_vec)
    pa_dict = Dict{String, Pattern}(patt.name => patt for patt in pa_vec)
    for (i, pump) in enumerate(pu_vec)
        name = pump["name"]
        # creat EPANETPumpParams object for the pump
        if pump["efficiency"] isa String
            efficiency = c_dict[pump["efficiency"]]
        else
            efficiency = pump["efficiency"]
        end
        epanet_params = EPANETPumpParams(pump["type"], pump["power"],
                                        c_dict[pump["head_curve_name"]],
                                        efficiency)
        # calculate normalized params and create the object
         # in utils/PumpCoefs.jl
        Qbep, etabep, Gbep, Pbep = norm_pump_params(pump, c_dict)
        flowlimits = (min = 0.5*Qbep, max = 1.5*Qbep)
        norm_coefs = NormPumpParams(Qbep, etabep, Gbep, Pbep)
        # create the pump parameters object, including base price and pattern
        pump_params = PumpParams(epanet_params, norm_coefs, pump["price"],
                                 pump["price_pattern"])
        # create the pump object
        pumps[i] = Pump(name, a_dict[name], true, pump_params, flowlimits, nothing, nothing,
                        nothing)
    end
    return pumps
end

#ignoring valves for now, JJS 12/5/19
function valve_to_struct(v_vec::Vector{Any}, a_dict::Dict{String,Arc})
    if size(v_vec)[1] != 0
        @warn("There appears to be pressure/flow control valves in this network, but these valves are not currently handled by WaterSystems.jl")
    end
    return nothing
end
# function valve_to_struct(data::Vector{Any}, junctions::Array{Junction,1})
#     valves = Array{PR where {PR <: PressureReducingValve},1}(undef, length(data))
#     for (ix, v) in enumerate(data)
#         #push!(valves, PressureReducingValve(...))
#         j_from = v["connectionpoints"].from
#         j_to = v["connectionpoints"].to
#         junction_to = [junc for junc in junctions if junc.name == j_to["name"]][1]
#         junction_from = [junc for junc in junctions if junc.name == j_from["name"]][1]
#         if v["valvetype"] == "PRV"
#             valves[ix] = PressureReducingValve(v["name"], (from = junction_from, to = junction_to) ,v["status"], v["diameter"], v["pressure_drop"])
#         elseif v["valvetype"] == "PSV"
#             valves[ix] = PressureSustainingValve(v["name"], (from = junction_from, to = junction_to) ,v["status"], v["diameter"], v["pressure_drop"])
#         elseif v["valvetype"] == "PBV"
#             valves[ix] = PressureBreakerValve(v["name"], (from = junction_from, to = junction_to) ,v["status"], v["diameter"], v["pressure_drop"])
#         elseif v["valvetype"] == "FCV"
#             valves[ix] = FlowControlValve(v["name"], (from = junction_from, to = junction_to) ,v["status"], v["diameter"], v["pressure_drop"])
#         elseif v["valvetype"] == "TCV"
#             valves[ix] = ThrottleControlValve(v["name"], (from = junction_from, to = junction_to) ,v["status"], v["diameter"], v["pressure_drop"])
#         else
#             valves[ix] = GeneralPurposeValve(v["name"], (from = junction_from, to = junction_to) ,v["status"], v["diameter"], v["pressure_drop"])
#         end
#     end
#     return valves
# end


using BandedMatrices, FillArrays, Test, LinearAlgebra, SparseArrays
import BandedMatrices: _BandedMatrix

# used to test general matrix backends
struct MyMatrix{T} <: AbstractMatrix{T}
    A::Matrix{T}
end

MyMatrix{T}(::UndefInitializer, n::Int, m::Int) where T = MyMatrix{T}(Array{T}(undef, n, m))
MyMatrix(A::AbstractMatrix{T}) where T = MyMatrix{T}(Matrix{T}(A))
Base.convert(::Type{MyMatrix{T}}, A::MyMatrix{T}) where T = A
Base.convert(::Type{MyMatrix{T}}, A::MyMatrix) where T = MyMatrix(convert(AbstractArray{T}, A.A))
Base.convert(::Type{MyMatrix}, A::MyMatrix)= A
Base.convert(::Type{AbstractArray{T}}, A::MyMatrix) where T = MyMatrix(convert(AbstractArray{T}, A.A))
Base.convert(::Type{AbstractMatrix{T}}, A::MyMatrix) where T = MyMatrix(convert(AbstractArray{T}, A.A))
Base.convert(::Type{MyMatrix{T}}, A::AbstractArray{T}) where T = MyMatrix{T}(A)
Base.convert(::Type{MyMatrix{T}}, A::AbstractArray) where T = MyMatrix{T}(convert(AbstractArray{T}, A))
Base.convert(::Type{MyMatrix}, A::AbstractArray{T}) where T = MyMatrix{T}(A)
Base.getindex(A::MyMatrix, kj...) = A.A[kj...]
Base.getindex(A::MyMatrix, ::Colon, j::AbstractVector) = MyMatrix(A.A[:,j])
Base.setindex!(A::MyMatrix, v, kj...) = setindex!(A.A, v, kj...)
Base.size(A::MyMatrix) = size(A.A)
Base.similar(::Type{MyMatrix{T}}, m::Int, n::Int) where T = MyMatrix{T}(undef, m, n)
Base.similar(::MyMatrix{T}, m::Int, n::Int) where T = MyMatrix{T}(undef, m, n)
Base.similar(::MyMatrix, ::Type{T}, m::Int, n::Int) where T = MyMatrix{T}(undef, m, n)

@testset "BandedMatrix" begin
    @testset "Undef BandedMatrix" begin
        @test typeof(BandedMatrix{Float64}(undef,(10,10),(1,1))) ==
            typeof(BandedMatrix{Float64,Matrix{Float64}}(undef,(10,10),(1,1))) ==
            typeof(BandedMatrix{Float64,Matrix{Float64},Base.OneTo{Int}}(undef,(10,10),(1,1)))
        @test typeof(BandedMatrix{Int}(undef,(10,10),(1,1))) ==
            typeof(BandedMatrix{Int,Matrix{Int}}(undef,(10,10),(1,1))) ==
            typeof(BandedMatrix{Int,Matrix{Int},Base.OneTo{Int}}(undef,(10,10),(1,1)))
@test typeof(BandedMatrix{Vector{Int}}(undef,(10,10),(1,1))) ==
            typeof(BandedMatrix{Vector{Int},Matrix{Vector{Int}}}(undef,(10,10),(1,1))) ==
            typeof(BandedMatrix{Vector{Int},Matrix{Vector{Int}},Base.OneTo{Int}}(undef,(10,10),(1,1)))
    end

    @testset "Creating BandedMatrix" begin
        @test BandedMatrix(Zeros(5,5), (1,1)) == _BandedMatrix(zeros(3,5), 5, 1, 1)
        @test BandedMatrix(Zeros{Int32}(5,5), (1,1)) isa BandedMatrix{Int32, Matrix{Int32}}
        @test BandedMatrix(Zeros{Int}(5,5), (1,1)) == _BandedMatrix(zeros(Int,3,5), 5, 1, 1)
        @test BandedMatrix{Int}(Zeros(5,5), (1,1)) == _BandedMatrix(zeros(Int,3,5), 5, 1, 1)
        @test Matrix(BandedMatrix(Ones(5,5), (1,1))) == Matrix(BandedMatrix(Fill(1.0,(5,5)), (1,1))) ==
                                                        Matrix(SymTridiagonal(ones(5), ones(4)))

        @test all(BandedMatrix(0 => 1:5, 2=> 2:3, -3=> 1:7) .=== diagm(0 => 1:5, 2=> 2:3, -3=> 1:7))
        @test all(BandedMatrix{Float64}(0 => 1:5, 2=> 2:3, -3=> 1:7) .=== Matrix{Float64}(diagm(0 => 1:5, 2=> 2:3, -3=> 1:7)))
        @test all(BandedMatrix((0 => 1:5, 2=> 2:3, -3=> 1:7),(10,10)) .=== Matrix{Int}(diagm(0 => 1:5, 2=> 2:3, -3=> 1:7)))
        @test all(BandedMatrix{Float64}((0 => 1:5, 2=> 2:3, -3=> 1:7),(10,10)) .=== Matrix{Float64}(diagm(0 => 1:5, 2=> 2:3, -3=> 1:7)))
        @test all(BandedMatrix((0 => 1:5, 2=> 2:3, -3=> 1:7),(10,10),(4,3)) .=== Matrix{Int}(diagm(0 => 1:5, 2=> 2:3, -3=> 1:7)))
        @test all(BandedMatrix{Float64}((0 => 1:5, 2=> 2:3, -3=> 1:7),(10,10),(4,3)) .=== Matrix{Float64}(diagm(0 => 1:5, 2=> 2:3, -3=> 1:7)))

        matrix = MyMatrix(ones(Int64,5,5))
        @test !(matrix isa Matrix)
        @test BandedMatrix(matrix) isa BandedMatrix{Int64, Matrix{Int64}}
        @test BandedMatrix(matrix).data isa Matrix{Int64}
        @test BandedMatrix{Int64, typeof(matrix)}(matrix, (1, 1)) isa BandedMatrix{Int64, typeof(matrix)}
        @test BandedMatrix{Int64, typeof(matrix)}(matrix, (1, 1)).data isa typeof(matrix)
        @test_throws UndefRefError BandedMatrix{Vector{Float64}}(undef, (5,5), (1,1))[1,1]
    end

    @testset "BandedMatrix arithmetic" begin
        let A = brand(10,12,2,3),B = brand(10,12,3,4)
            @test Matrix(sparse(A)) ≈ Matrix(A)

            @test Matrix(A') ≈ Matrix(A)'
            @test Matrix(transpose(A)) ≈ transpose(Matrix(A))
            @test Matrix((A+im*A)') ≈ (Matrix(A)+im*Matrix(A))'
            @test Matrix(transpose(A+im*A)) ≈ transpose(Matrix(A)+im*Matrix(A))

            @test Matrix(A+B) ≈ (Matrix(A)+Matrix(B))
            @test Matrix(A-B) ≈ (Matrix(A)-Matrix(B))

            @test Matrix(A.*B) ≈ (Matrix(A).*Matrix(B))
        end

        @testset "UniformScaling" begin
            A = brand(10,10,1,2)
            @test A+I isa BandedMatrix
            @test bandwidths(A+I) == (1,2)
            @test Matrix(A+I) ≈ Matrix(A)+I
            @test Matrix(I-A) ≈ I-Matrix(A)

            # number
            @test Matrix(A/2) ≈ Matrix(A)/2
        end
    end

    @testset "BandedMatrix * Vector" begin
        let A=brand(10,12,2,3), v=rand(12), w=rand(10)
            @test A*v ≈ Matrix(A)*v
            @test A'*w ≈ Matrix(A)'*w
        end

        let A=brand(Float64,5,3,2,2), v=rand(ComplexF64,3), w=rand(ComplexF64,5)
            @test A*v ≈ Matrix(A)*v
            @test A'*w ≈ Matrix(A)'*w
        end

        let A=brand(ComplexF64,5,3,2,2), v=rand(ComplexF64,3), w=rand(ComplexF64,5)
            @test A*v ≈ Matrix(A)*v
            @test A'*w ≈ Matrix(A)'*w
        end

        let A=brand(ComplexF64,5,3,2,2), v=rand(Float64,3), w=rand(Float64,5)
            @test A*v ≈ Matrix(A)*v
            @test A'*w ≈ Matrix(A)'*w
        end
    end

    @testset "Banded * Dense" begin
        @testset "big banded * dense" begin
            A=brand(1000,1000,200,300)
            B=rand(1000,1000)
            @test A*B ≈ Matrix(A)*B
            @test B*A ≈ B*Matrix(A)
        end
        # gbmm! not yet implemented
        # @test A'*B ≈ Matrix(A)'*B
        # @test A*B' ≈ Matrix(A)*B'
        # @test A'*B' ≈ Matrix(A)'*B'

        let
            A=brand(1200,1000,200,300); B=rand(1000,1000); C=rand(1200,1200)
            @test A*B ≈ Matrix(A)*B
            @test C*A ≈ C*Matrix(A)
        end
        # gbmm! not yet implemented
        # @test A'*C ≈ Matrix(A)'*C
        # @test A*B' ≈ Matrix(A)*B'
        # @test A'*C' ≈ Matrix(A)'*C'
    end

    @testset "Banded * Banded" begin
        for n in (1,5), ν in (1,5), m in (1,5), Al in (0,1,3), Au in (0,1,3),
                Bl in (0,1,3), Bu in (0,1,3)
            A = brand(n, ν, Al, Au); B = brand(ν, m, Bl, Bu)
            C = brand(ν, n, Al, Bu); D = brand(m, ν, Al, Bu)
            @test Matrix(A*B) ≈ Matrix(A)*Matrix(B)
            @test Matrix(C'*B) ≈ Matrix(C)'*Matrix(B)
            @test Matrix(A*D') ≈ Matrix(A)*Matrix(D)'
            @test Matrix(C'*D') ≈ Matrix(C)'*Matrix(D)'
        end

        let A = brand(ComplexF64, 5, 4, 2, 3), B = brand(ComplexF64, 4, 6, 3, 1),
            C = brand(ComplexF64, 4, 5, 1, 1), D = brand(ComplexF64, 6, 4, 0, 3)
            @test Matrix(A*B) ≈ Matrix(A)*Matrix(B)
            @test Matrix(C'*B) ≈ Matrix(C)'*Matrix(B)
            @test Matrix(A*D') ≈ Matrix(A)*Matrix(D)'
            @test Matrix(C'*D') ≈ Matrix(C)'*Matrix(D)'
        end

        let A = brand(ComplexF64, 5, 4, 2, 3), B = brand(4, 6, 3, 1), C = brand(4, 5, 1, 1),
                D = brand(ComplexF64, 6, 4, 0, 3)
            @test Matrix(A*B) ≈ Matrix(A)*Matrix(B)
            @test Matrix(C'*B) ≈ Matrix(C)'*Matrix(B)
            @test Matrix(A*D') ≈ Matrix(A)*Matrix(D)'
            @test Matrix(C'*D') ≈ Matrix(C)'*Matrix(D)'
        end

        @testset "BigFloat" begin
            let A = brand(5, 5, 1, 2), B = BandedMatrix(Zeros{BigFloat}(5,5),(2,3)), D = rand(5, 5)
                for j = 1:size(B,2), k = colrange(B,j)
                    B[k,j]=randn()
                end

                x = BigFloat[1:size(B,1)...]

                @test Matrix(A)*Matrix(B) ≈ A*B
                @test Matrix(B)*Matrix(A) ≈ B*A
                @test Matrix(B)*x ≈ B*x
                @test Matrix(B*B) ≈ Matrix(B)*Matrix(B)
                @test Matrix(A)*Matrix(D) ≈ A*D
                @test Matrix(D)*Matrix(A) ≈ D*A
            end
        end

        @testset "Negative bands" begin
            for A in (brand(3,4,-1,2),brand(5,4,-1,2),
                        brand(3,4,2,-1),brand(5,4,2,-1))
                b = rand(size(A,2))
                c = rand(size(A,1))
                @test A*b ≈ Matrix(A)*b
                @test A'*c ≈ Matrix(A)'*c
            end

            let C = brand(4, 5, -1, 3), D = rand(4, 4)
                for A in (brand(3,4,1,2),brand(3,4,-1,2),brand(3,4,2,-1)),
                    B in (brand(4,5,1,2),brand(4,5,-1,2),brand(4,5,2,-1))
                    @test A*B ≈ Matrix(A)*Matrix(B)
                    @test B*C' ≈ Matrix(B)*Matrix(C)'
                    @test B'*C ≈ Matrix(B)'*Matrix(C)
                    @test B'*A' ≈ Matrix(B)'*Matrix(A)'
                end

                for A in (brand(5,4,-1,2),brand(5,4,2,-1),brand(3,4,-1,2),brand(3,4,2,-1))
                    @test A*D ≈ Matrix(A)*Matrix(D)
                end

                for B in (brand(4,3,-1,2),brand(4,3,2,-1),brand(4,5,-1,2),brand(4,5,2,-1))
                    @test D*B ≈ Matrix(D)*Matrix(B)
                end
            end
        end

        @testset "zero" begin
            let b = rand(4)
                for A in (brand(3,4,-1,0),brand(3,4,0,-1),brand(3,4,-1,-1)),
                    B in (brand(4,3,1,2),brand(4,3,-1,0),brand(4,3,-1,-1))
                    @test Matrix(A) == zeros(3,4)
                    @test A*B == zeros(3,3)
                    @test A*b == zeros(3)
                end
            end
        end

        @testset "errors in collect" begin
           let B = brand(10,10,0,4)
               @test B*[collect(1.0:10) collect(1.0:10)] ≈ Matrix(B)*[collect(1.0:10) collect(1.0:10)]
           end
        end
    end

    @testset "BandedMatrix interface" begin
        # check that col/rowstop is ≥ 0
        let A = brand(3,4,-2,2)
            @test BandedMatrices.colstop(A, 1) == BandedMatrices.colstop(A, 2) == 0
            @test BandedMatrices.colstop(A, 3) == 1
        end

        let A = brand(3,4,2,-2)
            @test BandedMatrices.rowstop(A, 1) == BandedMatrices.rowstop(A, 2) == 0
            @test BandedMatrices.rowstop(A, 3) == 1
        end

        # Test fill!
        let B = brand(10,10,1,4)
            @test_throws BandError fill!(B, 1.0)
            @test_throws BandError fill!(B, 1)
            fill!(B, 0)
            @test Matrix(B) == zeros(10,10)
        end
    end

    @testset "Conversions" begin
        @testset "banded -> some matrix" begin
            banded = brand(Int32, 10,12,2,3)

            matrix = Matrix(banded)
            @test matrix isa Matrix{Int32}
            for i in 1:length(matrix)
                @test matrix[i] == banded[i]
            end

            matrix = convert(Matrix, banded)
            @test matrix isa Matrix{Int32}
            for i in 1:length(matrix)
                @test matrix[i] == banded[i]
            end

            matrix = convert(Matrix{Int64}, banded)
            @test matrix isa Matrix{Int64}
            for i in 1:length(matrix)
                @test matrix[i] == banded[i]
            end

            matrix = convert(BandedMatrix, banded)
            @test matrix === banded

            matrix = convert(BandedMatrix{Int64}, banded)
            @test matrix isa BandedMatrix{Int64, Matrix{Int64}}
            for i in 1:length(matrix)
                @test matrix[i] == banded[i]
            end
        end

        @testset "some matrix -> banded" begin
            matrix = rand(Int32, 10, 12)

            types = Dict(BandedMatrix => Matrix{Int32},
                         BandedMatrix{Int64} => Matrix{Int64},
                         BandedMatrix{Int64, MyMatrix{Int64}} => MyMatrix{Int64})
            @testset "Matrix to $Final via $Step" for (Step, Final) in types
                banded = @inferred convert(Step, matrix)
                @test banded isa BandedMatrix{eltype(Final), Final}
                @test banded == matrix
                @test @inferred(convert(BandedMatrix, banded)) === banded
            end

            # Note: @inferred convert(MyMatrix, matrix) throws
            banded = convert(BandedMatrix{Int64, MyMatrix}, matrix)
            @test banded isa BandedMatrix{Int64, MyMatrix{Int64}}
            @test banded == matrix

            banded = convert(BandedMatrix{<:, MyMatrix}, matrix)
            @test banded isa BandedMatrix{Int32, MyMatrix{Int32}}
            @test banded == matrix

            jlarray = convert(MyMatrix, rand(Int32, 10, 12))
            types = Dict(BandedMatrix => MyMatrix{Int32},
                         BandedMatrix{Int64} => MyMatrix{Int64},
                         BandedMatrix{Int64, Matrix} => Matrix{Int64},
                         BandedMatrix{<:, Matrix} => Matrix{Int32},
                         BandedMatrix{<:, Matrix{Int32}} => Matrix{Int32},
                         BandedMatrix{<:, Matrix{Int64}} => Matrix{Int64},
                         BandedMatrix{Int64, Matrix{Int64}} => Matrix{Int64})
            @testset "MyMatrix to $Final via $Step" for (Step, Final) in types
                banded = @inferred convert(Step, jlarray)
                @test banded isa BandedMatrix{eltype(Final), Final}
                @test banded == jlarray
            end

            banded[5, 5] = typemax(Int32)
            @test_throws InexactError convert(BandedMatrix{Int16}, matrix)
            @test_throws InexactError convert(BandedMatrix{Int16, MyMatrix}, matrix)
            @test_throws InexactError convert(BandedMatrix{Int16, MyMatrix{Int16}}, matrix)

            banded = @inferred BandedMatrix{eltype(jlarray), typeof(jlarray)}(jlarray, (1, 1))
            types = [BandedMatrix, BandedMatrix{Int32},
                     BandedMatrix{<:, MyMatrix},
                     BandedMatrix{<:, MyMatrix{Int32}}]
            @testset "no-op banded to banded via $Step" for Step in types
                @test @inferred(convert(Step, banded)) === banded
            end

            types = Dict(BandedMatrix{Int64} => MyMatrix{Int64},
                         BandedMatrix{Int64, Matrix} => Matrix{Int64},
                         BandedMatrix{<:, MyMatrix{Int64}} => MyMatrix{Int64},
                         BandedMatrix{<:, Matrix} => Matrix{Int32},
                         BandedMatrix{<:, Matrix{Int32}} => Matrix{Int32},
                         BandedMatrix{<:, Matrix{Int64}} => Matrix{Int64},
                         BandedMatrix{Int64, Matrix{Int64}} => Matrix{Int64})
            @testset "banded to banded $Final via $Step" for (Step, Final) in types
                newbanded = @inferred convert(Step, banded)
                @test newbanded isa BandedMatrix{eltype(Final), Final}
                @test newbanded == banded
            end

            @testset "banded to banded" begin
                A = BandedMatrix{Int}(undef, (5,5), (1,1)); A.data[:] .= 1:length(A.data)
                @test convert(BandedMatrix, A) === convert(BandedMatrix{Int}, A) ===
                    convert(BandedMatrix{Int,Matrix{Int}}, A) === convert(BandedMatrix{Int,Matrix{Int},Base.OneTo{Int}}, A) === A
                @test convert(BandedMatrix{Float64}, A) ==
                    convert(BandedMatrix{Float64,Matrix{Float64}}, A) == convert(BandedMatrix{Float64,Matrix{Float64},Base.OneTo{Int}}, A) == A
            end
        end

        @testset "similar" begin
            banded = brand(Int32, 10, 12, 1, 2)
            @test @inferred(similar(banded)) isa BandedMatrix{Int32, Matrix{Int32}}
            @test size(similar(banded)) == size(banded)
            @test similar(banded).l == banded.l
            @test similar(banded).u == banded.u
            @test @inferred(similar(banded,(5,5))) isa BandedMatrix{Int32, Matrix{Int32}}
            @test @inferred(similar(banded,5,5)) isa BandedMatrix{Int32, Matrix{Int32}}
            @test @inferred(similar(banded,5,5,1,1)) isa BandedMatrix{Int32, Matrix{Int32}}

            banded = convert(BandedMatrix{<:, MyMatrix}, brand(Int32, 10, 12, 1, 2))
            @test banded isa BandedMatrix{Int32, MyMatrix{Int32}}
            @test @inferred(similar(banded)) isa BandedMatrix{Int32, MyMatrix{Int32}}
            @test size(similar(banded)) == size(banded)
            @test similar(banded).l == banded.l
            @test similar(banded).u == banded.u

            banded = convert(BandedMatrix{<:, MyMatrix}, brand(Int32, 10, 12, 1, 2))
            @test @inferred(similar(banded, Int64)) isa BandedMatrix{Int64, MyMatrix{Int64}}
            @test size(similar(banded, Int64)) == size(banded)
            @test similar(banded, Int64).l == banded.l
            @test similar(banded, Int64).u == banded.u

            bview = @view banded[1:5, 1:5]
            expected = convert(BandedMatrix, bview)
            @test @inferred(similar(bview)) isa BandedMatrix{Int32, MyMatrix{Int32}}
            @test @inferred(similar(bview, Int64)) isa BandedMatrix{Int64, MyMatrix{Int64}}
            @test size(similar(bview, Int64)) == size(expected)
            @test bandwidth(similar(bview, Int64), 1) == bandwidth(expected, 1)
            @test bandwidth(similar(bview, Int64), 2) == bandwidth(expected, 2)

            bview = @view banded[1:5, 6:8]
            expected = convert(BandedMatrix, bview)
            @test size(similar(bview, Int64)) == size(expected)
            @test bandwidth(similar(bview, Int64), 1) == bandwidth(expected, 1)
            @test bandwidth(similar(bview, Int64), 2) == bandwidth(expected, 2)

            bview = @view banded[6:8, 1:5]
            expected = convert(BandedMatrix, bview)
            @test size(similar(bview, Int64)) == size(expected)
            @test bandwidth(similar(bview, Int64), 1) == bandwidth(expected, 1)
            @test bandwidth(similar(bview, Int64), 2) == bandwidth(expected, 2)
        end

        @testset "banded -> sparse" begin
            B = brand(5,5,1,1)
            @test Matrix(sparse(B)) == Matrix(B)
        end
    end

    @testset "real-imag" begin
        B = brand(ComplexF64,5,5,1,1)
        @test real(B) isa BandedMatrix
        @test imag(B) isa BandedMatrix
        @test bandwidths(real(B)) == bandwidths(imag(B)) == bandwidths(B)
        @test real(B) + im*imag(B) == B
    end

    @testset "induced norm" begin
        A = brand(Float64, 12, 10, 2, 3)
        B = Matrix(A)
	    @test opnorm(A) ≈ opnorm(B)
	    @test cond(A) ≈ cond(B)
    end

    @testset "triu/tril" begin
        n = 20; A = brand(n,n,2,3); B = Matrix(A);
        @test triu(A) == triu(B)
        @test tril(A) == tril(B)
        for j = -5:5
            @test triu(A,j) == triu(B,j)
            @test tril(A,j) == tril(B,j)
        end
    end

    @testset "BandedMatrix{Any}" begin
        A = BandedMatrix{Any}(undef, (10,10), (1,1))
        @test A[1,3] === nothing
    end
end


# round to nearest ties to even
round(x::Posit8) = ccall((:p8_roundToInt, SoftPositPath), Posit8, (Posit8,),x)
round(x::Posit16) = ccall((:p16_roundToInt, SoftPositPath), Posit16, (Posit16,),x)
round(x::Posit32) = ccall((:p32_roundToInt, SoftPositPath), Posit32, (Posit32,),x)

# for Posits with 1 exponent bit
for T in (:Posit8_1, :Posit16_1, :Posit24_1)
    @eval begin
        round(x::$T) =  ccall((:pX1_roundToInt, SoftPositPath), $T, ($T,),x)
    end
end

# for Posits with 1 exponent bit
for T in (:Posit8_2, :Posit16_2, :Posit24_2)
    @eval begin
        round(x::$T) =  ccall((:pX2_roundToInt, SoftPositPath), $T, ($T,),x)
    end
end

function round(x::T, r::RoundingMode{:Down}) where {T<:AbstractPosit}
    xr = round(x)
    if xr > x
        return xr-one(T)
    else
        return xr
    end
end

function round(x::T, r::RoundingMode{:Up}) where {T<:AbstractPosit}
    xr = round(x)
    if xr < x
        return xr+one(T)
    else
        return xr
    end
end


abstract type AbstractStorageFormulation <: AbstractDeviceFormulation end
struct BookKeeping <: AbstractStorageFormulation end
struct BookKeepingwReservation <: AbstractStorageFormulation end
#################################################Storage Variables#################################

function active_power_variables!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
) where {St <: PSY.Storage}
    add_variable(
        psi_container,
        devices,
        variable_name(ACTIVE_POWER_IN, St),
        false,
        :nodal_balance_active,
        -1.0;
        lb_value = d -> 0.0,
    )
    add_variable(
        psi_container,
        devices,
        variable_name(ACTIVE_POWER_OUT, St),
        false,
        :nodal_balance_active;
        lb_value = d -> 0.0,
    )
    return
end

function reactive_power_variables!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
) where {St <: PSY.Storage}
    add_variable(
        psi_container,
        devices,
        variable_name(REACTIVE_POWER, St),
        false,
        :nodal_balance_reactive,
    )
    return
end

function energy_storage_variables!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
) where {St <: PSY.Storage}
    add_variable(
        psi_container,
        devices,
        variable_name(ENERGY, St),
        false;
        lb_value = d -> 0.0,
    )
    return
end

function storage_reservation_variables!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
) where {St <: PSY.Storage}
    add_variable(psi_container, devices, variable_name(RESERVE, St), true)
    return
end

###################################################### output power constraints#################################

function active_power_constraints!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
    model::DeviceModel{St, BookKeeping},
    ::Type{S},
    feedforward::Union{Nothing, AbstractAffectFeedForward},
) where {St <: PSY.Storage, S <: PM.AbstractPowerModel}
    constraint_data_in = Vector{DeviceRange}(undef, length(devices))
    constraint_data_out = Vector{DeviceRange}(undef, length(devices))
    for (ix, d) in enumerate(devices)
        name = PSY.get_name(d)
        in_lims = PSY.get_inputactivepowerlimits(d)
        out_lims = PSY.get_outputactivepowerlimits(d)
        constraint_data_in[ix] = DeviceRange(name, in_lims)
        constraint_data_out[ix] = DeviceRange(name, out_lims)
        add_device_services!(constraint_data_in[ix], constraint_data_out[ix], d, model)
    end

    device_range(
        psi_container,
        constraint_data_out,
        constraint_name(OUTPUT_POWER_RANGE, St),
        variable_name(ACTIVE_POWER_OUT, St),
    )

    device_range(
        psi_container,
        constraint_data_in,
        constraint_name(INPUT_POWER_RANGE, St),
        variable_name(ACTIVE_POWER_IN, St),
    )
    return
end

function active_power_constraints!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
    model::DeviceModel{St, BookKeepingwReservation},
    ::Type{S},
    feedforward::Union{Nothing, AbstractAffectFeedForward},
) where {St <: PSY.Storage, S <: PM.AbstractPowerModel}
    constraint_data_in = Vector{DeviceRange}(undef, length(devices))
    constraint_data_out = Vector{DeviceRange}(undef, length(devices))
    for (ix, d) in enumerate(devices)
        name = PSY.get_name(d)
        in_lims = PSY.get_inputactivepowerlimits(d)
        out_lims = PSY.get_outputactivepowerlimits(d)
        constraint_data_in[ix] = DeviceRange(name, in_lims)
        constraint_data_out[ix] = DeviceRange(name, out_lims)
        add_device_services!(constraint_data_in[ix], constraint_data_out[ix], d, model)
    end

    reserve_device_semicontinuousrange(
        psi_container,
        constraint_data_in,
        constraint_name(INPUT_POWER_RANGE, St),
        variable_name(ACTIVE_POWER_IN, St),
        variable_name(RESERVE, St),
    )

    reserve_device_semicontinuousrange(
        psi_container,
        constraint_data_out,
        constraint_name(OUTPUT_POWER_RANGE, St),
        variable_name(ACTIVE_POWER_OUT, St),
        variable_name(RESERVE, St),
    )
    return
end

"""
This function adds the reactive  power limits of generators when there are CommitmentVariables
"""
function reactive_power_constraints!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
    model::DeviceModel{St, D},
    ::Type{S},
    feedforward::Union{Nothing, AbstractAffectFeedForward},
) where {St <: PSY.Storage, D <: AbstractStorageFormulation, S <: PM.AbstractPowerModel}
    constraint_data = Vector{DeviceRange}(undef, length(devices))
    for (ix, d) in enumerate(devices)
        name = PSY.get_name(d)
        limits = PSY.get_reactivepowerlimits(d)
        range_data = DeviceRange(name, limits)
        constraint_data[ix] = range_data
    end

    device_range(
        psi_container,
        constraint_data,
        constraint_name(REACTIVE_RANGE, St),
        variable_name(REACTIVE_POWER, St),
    )
    return
end

########################## Make initial Conditions for a Model #############################
function initial_conditions!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
    ::Type{D},
) where {St <: PSY.Storage, D <: AbstractStorageFormulation}
    storage_energy_init(psi_container.initial_conditions, devices)
    return
end

###################################################### Energy Capacity constraints##########

function energy_capacity_constraints!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
    model::DeviceModel{St, D},
    ::Type{S},
    feedforward::Union{Nothing, AbstractAffectFeedForward},
) where {St <: PSY.Storage, D <: AbstractStorageFormulation, S <: PM.AbstractPowerModel}
    constraint_data = Vector{DeviceRange}(undef, length(devices))
    for (ix, d) in enumerate(devices)
        name = PSY.get_name(d)
        limits = PSY.get_capacity(d)
        range_data = DeviceRange(name, limits)
        add_device_services!(range_data, d, model)
        constraint_data[ix] = range_data
    end

    device_range(
        psi_container,
        constraint_data,
        constraint_name(ENERGY_CAPACITY, St),
        variable_name(ENERGY, St),
    )
    return
end

###################################################### book keeping constraints ############

function make_efficiency_data(
    devices::IS.FlattenIteratorWrapper{St},
) where {St <: PSY.Storage}
    names = Vector{String}(undef, length(devices))
    in_out = Vector{InOut}(undef, length(devices))

    for (ix, d) in enumerate(devices)
        names[ix] = PSY.get_name(d)
        in_out[ix] = PSY.get_efficiency(d)
    end

    return names, in_out
end

function energy_balance_constraint!(
    psi_container::PSIContainer,
    devices::IS.FlattenIteratorWrapper{St},
    ::Type{D},
    ::Type{S},
    feedforward::Union{Nothing, AbstractAffectFeedForward},
) where {St <: PSY.Storage, D <: AbstractStorageFormulation, S <: PM.AbstractPowerModel}
    efficiency_data = make_efficiency_data(devices)
    energy_balance(
        psi_container,
        get_initial_conditions(psi_container, ICKey(EnergyLevel, St)),
        efficiency_data,
        constraint_name(ENERGY_LIMIT, St),
        (
            variable_name(ACTIVE_POWER_OUT, St),
            variable_name(ACTIVE_POWER_IN, St),
            variable_name(ENERGY, St),
        ),
    )
    return
end


# CUBLAS.jl
#
# Julia interface to CUBLAS.  Inspired by CUDArt.jl and using Clang.jl.
#
# Author: Nick Henderson <nwh@stanford.edu>
# Created: 2014-08-27
# License: MIT
#
__precompile__(true)
module CUBLAS

importall Base.LinAlg.BLAS

using CUDArt
using CUDArt.rt.cudaStream_t

const BlasChar = Char #import Base.LinAlg.BlasChar
import Base.one
import Base.zero

export band, unband, bandex

include("util.jl")
include("libcublas_types.jl")

# get cublas status message
function statusmessage(status)
    if status == CUBLAS_STATUS_SUCCESS
        return "cublas success"
    end
    if status == CUBLAS_STATUS_NOT_INITIALIZED
        return "cublas not initialized"
    end
    if status == CUBLAS_STATUS_ALLOC_FAILED
        return "cublas alloc failed"
    end
    if status == CUBLAS_STATUS_INVALID_VALUE
        return "cublas invalid value"
    end
    if status == CUBLAS_STATUS_ARCH_MISMATCH
        return "cublas arch mismatch"
    end
    if status == CUBLAS_STATUS_MAPPING_ERROR
        return "cublas mapping error"
    end
    if status == CUBLAS_STATUS_EXECUTION_FAILED
        return "cublas execution failed"
    end
    if status == CUBLAS_STATUS_INTERNAL_ERROR
        return "cublas internal error"
    end
    if status == CUBLAS_STATUS_NOT_SUPPORTED
        return "cublas not supported"
    end
    if status == CUBLAS_STATUS_LICENSE_ERROR
        return "cublas license error"
    end
    return "cublas unknown status"
end

# error handling function
function statuscheck(status)
    if status == CUBLAS_STATUS_SUCCESS
        return nothing
    end
    # Because try/finally may disguise the source of the problem,
    # let's show a backtrace here
    warn("CUBLAS error triggered from:")
    Base.show_backtrace(STDOUT, backtrace())
    println()
    throw(statusmessage(status))
end

# find the cublas library
const libcublas = Libdl.find_library(["libcublas"], ["/usr/local/cuda"])
if isempty(libcublas)
    error("CUBLAS library cannot be found. Please make sure that CUDA is installed")
end

include("libcublas.jl")

# setup cublas handle
const cublashandle = cublasHandle_t[0]

function __init__()
    cublasCreate_v2(cublashandle)
    # destroy cublas handle at julia exit
    atexit(()->cublasDestroy_v2(cublashandle[1]))
end

include("blas.jl")
include("highlevel.jl")

end # module


function AuxiliaryElements(arg0::Orbit, arg1::jint)
    return AuxiliaryElements((Orbit, jint), arg0, arg1)
end

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

function get_alpha(obj::AuxiliaryElements)
    return jcall(obj, "getAlpha", jdouble, ())
end

function get_b(obj::AuxiliaryElements)
    return jcall(obj, "getB", jdouble, ())
end

function get_beta(obj::AuxiliaryElements)
    return jcall(obj, "getBeta", jdouble, ())
end

function get_c(obj::AuxiliaryElements)
    return jcall(obj, "getC", jdouble, ())
end

function get_class(obj::Object)
    return jcall(obj, "getClass", Class, ())
end

function get_date(obj::AuxiliaryElements)
    return jcall(obj, "getDate", AbsoluteDate, ())
end

function get_ecc(obj::AuxiliaryElements)
    return jcall(obj, "getEcc", jdouble, ())
end

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

function get_gamma(obj::AuxiliaryElements)
    return jcall(obj, "getGamma", jdouble, ())
end

function get_h(obj::AuxiliaryElements)
    return jcall(obj, "getH", jdouble, ())
end

function get_k(obj::AuxiliaryElements)
    return jcall(obj, "getK", jdouble, ())
end

function get_keplerian_period(obj::AuxiliaryElements)
    return jcall(obj, "getKeplerianPeriod", jdouble, ())
end

function get_lf(obj::AuxiliaryElements)
    return jcall(obj, "getLf", jdouble, ())
end

function get_lm(obj::AuxiliaryElements)
    return jcall(obj, "getLM", jdouble, ())
end

function get_lv(obj::AuxiliaryElements)
    return jcall(obj, "getLv", jdouble, ())
end

function get_mean_motion(obj::AuxiliaryElements)
    return jcall(obj, "getMeanMotion", jdouble, ())
end

function get_p(obj::AuxiliaryElements)
    return jcall(obj, "getP", jdouble, ())
end

function get_q(obj::AuxiliaryElements)
    return jcall(obj, "getQ", jdouble, ())
end

function get_retrograde_factor(obj::AuxiliaryElements)
    return jcall(obj, "getRetrogradeFactor", jint, ())
end

function get_sma(obj::AuxiliaryElements)
    return jcall(obj, "getSma", jdouble, ())
end

function get_vector_f(obj::AuxiliaryElements)
    return jcall(obj, "getVectorF", Vector3D, ())
end

function get_vector_g(obj::AuxiliaryElements)
    return jcall(obj, "getVectorG", Vector3D, ())
end

function get_vector_w(obj::AuxiliaryElements)
    return jcall(obj, "getVectorW", Vector3D, ())
end

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

function notify(obj::Object)
    return jcall(obj, "notify", void, ())
end

function notify_all(obj::Object)
    return jcall(obj, "notifyAll", void, ())
end

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

function wait(obj::Object)
    return jcall(obj, "wait", void, ())
end

function wait(obj::Object, arg0::jlong)
    return jcall(obj, "wait", void, (jlong,), arg0)
end

function wait(obj::Object, arg0::jlong, arg1::jint)
    return jcall(obj, "wait", void, (jlong, jint), arg0, arg1)
end