text:
"""
    _D_method(parameters::Parameters1PL, latent::Latent1D)
"""
function _D_method(parameters::Parameters1PL, latent::Latent1D)
    return _item_expected_information(parameters, latent)
end

"""
    _D_method(parameters::Parameters2PL, latent::Latent1D)
"""
function _D_method(parameters::Parameters2PL, latent::Latent1D)
    return LinearAlgebra.det(_item_expected_information(parameters, latent))
end

"""
    D_method(item::AbstractItem, examinee::AbstractExaminee)

# Description
Computes the determinant of the expected information matrix for an item and an examinee.

# Arguments
- **`item::AbstractItem`**: The item.
- **`examinee::AbstractExaminee`**: The examinee at which computing the information.

# Output
It returns a `Float64` scalar.
"""
function D_method(item::AbstractItem, examinee::AbstractExaminee)
    return _D_method(item.parameters, examinee.latent)
end


text:
function day7()
    nums = parse.(Int, split(readline("../data/input7.txt"), ","))

    med = round(Int, median(nums))
    cost1 = sum(abs(med - n) for n in nums)

    μ = mean(nums)
    μ_floor = floor(Int, μ)
    μ_ceil = ceil(Int, μ)
    cost21 = sum(sum(1:abs(μ_floor - n)) for n in nums)
    cost22 = sum(sum(1:abs(μ_ceil - n)) for n in nums)

    return cost1, min(cost21, cost22)
end

ext: jl

max_stars_repo_name: lucaferranti/AoC

max_issues_repo_name: lucaferranti/AoC

max_forks_repo_name: lucaferranti/AoC


text:
# typealias AVec{T} AbstractVector{T}
# typealias AVecF AVec{Float64}
# typealias AVecI AVec{Int}
# typealias VecF Vector{Float64}
# typealias VecI Vector{Int}

# typealias AMat{T} AbstractMatrix{T}
# typealias AMatF AMat{Float64}
# typealias AMatI AMat{Int}
# typealias MatF Matrix{Float64}
# typealias MatI Matrix{Int}

# typealias ABytes AVec{UInt8}
# typealias Bytes Vector{UInt8}

# typealias AbstractVector{Int} AVecI
# typealias FloatIterable AVecF

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

# function foreach(A::AbstractArray, f::Function, fs::Function...)
#   for x in A
#     f(x)
#   end
#   for g in fs
#     foreach(A, g)
#   end
#   A
# end

function getith(iterator, idx::Int)
  for (i,x) in enumerate(iterator)
    if i == idx
      return Nullable(x)
    end
  end
  Nullable{Any}()
end

unzip(x) = [p[1] for p in x], [p[2] for p in x]
# sizes(x) = map(size, x)
mapf(functions, obj) = map(f->f(obj), functions)

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

# row(M::AMat, i::Integer) = rowvec_view(M, i)
# col(M::AMat, i::Integer) = view(M, :, i)
# column(M::AMat, i::Integer) = view(M, :, i)

# rows(M::AMat, rng::AVecI) = view(M, rng, :)
# cols(M::AMat, rng::AVecI) = view(M, :, rng)
# columns(M::AMat, rng::AVecI) = view(M, :, rng)

# row!{T}(M::AMat{T}, i::Integer, v::AVec{T}) = (M[i,:] = v; nothing)
# col!{T}(M::AMat{T}, i::Integer, v::AVec{T}) = (M[:,i] = v; nothing)
# column!{T}(M::AMat{T}, i::Integer, v::AVec{T}) = (M[:,i] = v; nothing)

# nrows(a::AbstractArray) = size(a,1)
# ncols(a::AbstractArray) = size(a,2)

# addOnes{T<:Number}(v::AVec{T}) = vcat(v, one(T)) #hcat(v, ones(length(v)))
# addOnes{T<:Number}(m::AMat{T}) = hcat(m, ones(T, nrows(m)))

# mat(m::AMat) = m
# mat(v::AVec) = reshape_view(v, (length(v),))

# --------------------------------------------------------------------------------------

getPctOfInt(pct::Real, T::Integer) = round(Int, max(0., min(1., pct)) * T)

function splitRange(T::Integer, pct::Real)
  lastin = getPctOfInt(pct,T)
  1:lastin, lastin+1:T
end

function splitMatrixRows(m::AbstractMatrix, pct::Real)
  rng1, rng2 = splitRange(nrows(m), pct)
  rows(m,rng1), rows(m,rng2)
end

stringfloat(v::AbstractFloat, prec::Integer = 3) = format(v, precision = prec)
stringfloats{T<:Number}(v::AbstractVector{T}, prec::Integer = 3) = string("[", join(map(x->stringfloat(x,prec), v), ", "), "]")


text:
using Images, ImageFiltering
using FileIO

"""
    images_to_maps(...)

* has nearly the same inputs as convert_image_to_world but there is a directorypath instead of a filepath of one image and
* a targetpath, where you save the 12 maps
"""
function images_to_maps(dirsourcepath, targetpath = joinpath(@__DIR__, "..","input","world","monthly_maps"),transpose_image = true,  blurred=1, nlongitude=128, limit_ocean = 10, limit_land= 190, limit_seaice=205 )

    if isdir(dirsourcepath)
        for (root, dirs, files) in walkdir(dirsourcepath)
            for file in files
                if occursin("world", string(file))
                    filepath = joinpath(@__DIR__, root, file)
                    world = convert_image_to_world(filepath, transpose_image, blurred, nlongitude, limit_ocean, limit_land, limit_seaice)
                    save_world_by_month(world, string(file), targetpath)
                end
            end
        end
    end
end

"""
    convert_image_to_world(...)

* imagefile is the filapath of that lanscape we want to convert into our map with dicrete values for each landcover
* transpose_image is nessessary because without transposing each image from NASA website, we would create a
    symmetrically mirrored map
* imglong and imglat are the longitude and latitude of the input image
* nlongitude is the preferred longitude for the nearest neighbor interpolation
* limit_[...] is the limiting value for the classification from grayscale per landcover,
    0 <= ocean (5) <= limit_ocean <= land (1) <= limit_land <= ice (2) <= limit_seaice <= snow (3) <= 255
"""
function convert_image_to_world(imagefile, transpose_image=true, blurred=1, nlongitude=128, limit_ocean = 10, limit_land= 190, limit_seaice=210)

    world = FileIO.load(imagefile)
    if transpose_image
        world = transpose(world)
    end
    (longitude, latitude) = size(world)

    if blurred!=0
        world = imfilter(world, Kernel.gaussian(blurred))
    end

    # from RGB values to grayscale
    grayworld = Gray.(world)
    grayworld = real.(grayworld) .*255 # to get common grayscale from 0 (black) to 255 (white)
    geography = zeros(Int, longitude, latitude)

    for lat = 1:latitude
        for long = 1:longitude
            if grayworld[long, lat] >= 0 && grayworld[long, lat] <= limit_ocean # ocean
                geography[long, lat] = 5
            elseif grayworld[long, lat] > limit_ocean && grayworld[long, lat] <= limit_land # land
                geography[long, lat] = 1
            elseif grayworld[long, lat] > limit_land && grayworld[long, lat] <= limit_seaice # sea ice
                geography[long, lat] = 2
            elseif grayworld[long, lat] > limit_seaice && grayworld[long, lat] <= 256 # snow cover (snow has the highest albedo value)
                geography[long, lat] = 3
            end
        end
    end

    geography[:,1] .= mode_for_poles(geography[:,1])
    geography[:,latitude] .= mode_for_poles(geography[:,latitude])

    return nn_interpolation(geography, nlongitude)
end

"""
    save_world_by_month(...)

* saves converted maps by their filename.
* thats important because our simulation starts at 21th March, so March's map should be the first one we
*       read out, April's the second and so on
* we label these maps at the end of their filename with those numbers in directory target_dirpath
"""
function save_world_by_month(array_in, img_filename, target_dirpath)
    if !isdir(joinpath(@__DIR__,target_dirpath))
        mkdir(joinpath(@__DIR__,target_dirpath))
    end

    # Because our simulation begins at 21 March, we have to label March with 1
    months = Dict([("jan", 11), ("feb", 12), ("mar", 1), ("apr", 2), ("may", 3), ("jun", 4), ("jul", 5), ("aug", 6), ("sep", 7), ("oct", 8), ("nov", 9), ("dec", 10)])

    for (key, val) in months
        if occursin(string(key), img_filename)
            (scaledlong, scaledlat) = size(array_in)

            new_filename = string("The_World_from_image", scaledlong, "x", scaledlat,"_", val, ".dat")
            new_filepath = string(joinpath(@__DIR__,target_dirpath, new_filename))
            if !isfile(new_filepath)
                touch(new_filepath)
            end

            open(new_filepath,"w") do file
                for lat = 1:scaledlat
                    for long = 1:scaledlong
                        write(file,string(array_in[long, lat]))
                    end
                    write(file, "\n")
                end
            end
        end
    end
end

function mode_for_poles(a)
    isempty(a) && throw(ArgumentError("mode is not defined for empty collections"))
    len = length(a)
    r0 = 1
    r1 = 8
    cnts = zeros(Int, 8)
    for i = 1:len
        x = a[i]
        if r0 <= x <= r1
            c = (cnts[x] += 1)
        end
    end
    return findfirst(x -> x == maximum(cnts), cnts)
end


text:
export Line, Paragraph, Body
using AutoHashEquals
export LinePrefix
"""
Wrapper class for a prefix vector.
Wrapping used for interning.
"""
struct LinePrefix{I}
    prefix::Vector{I}
end
==(x::LinePrefix,y::LinePrefix) =
    x.prefix == y.prefix
Base.hash(x::LinePrefix,h::UInt) =
    hash(x.prefix,h)
Base.lastindex(x::LinePrefix) =
    lastindex(x.prefix)
Base.length(x::LinePrefix) =
    length(x.prefix)
Base.isempty(x::LinePrefix) =
    isempty(x.prefix)
Base.iterate(x::LinePrefix, a...) =
    iterate(x.prefix, a...)
Base.getindex(x::LinePrefix, a...) =
    getindex(x.prefix, a...)
Base.convert(::Type{Vector{I}}, x::LinePrefix{J}) where {I,J} =
    convert(Vector{I}, x.prefix)
Base.convert(::Type{LinePrefix{J}}, x::Vector{I}) where {I,J} =
    LinePrefix{J}(convert(Vector{J}, x))
Base.pushfirst!(v::LinePrefix, x) where {J} =
    pushfirst!(v.prefix,x)
Base.push!(v::LinePrefix, x) where {J} =
    push!(v.prefix,x)

@auto_hash_equals struct Line{I,T}
    prefix::LinePrefix{I}
    tokens::Vector{T}
    function Line{I,T}(prefix::Vector, t::Vector, newline::AbstractString)::Line{I,T} where {I,T}
        new{I,T}( prefix,
                   newline !="" ? vcat(t, Token(:whitespace, newline)) : t)::Line{I,T}
    end
    Line{I}(t::Vector{T}) where {I,T} =
        new{I,T}(I[],t)
    Line{I,T}(t::Vector{<:T}) where {I,T} =
        new{I,T}(I[],convert(Vector{T}, t))
    function Line{I,T}(prefix::Vector{I}, t::Vector{T}) where {I,T}
        new{I,T}(LinePrefix{I}(prefix), t)
    end
    function Line(prefix::Vector{I}, t::Vector{T}) where {I,T}
        new{I,T}(LinePrefix{I}(prefix), t)
    end
end
Line(t::Vector{T}) where {T} =
    Line{NamedString}(t)
emptyLine(x::Vararg{T}) where T =
    Line{T,T}([ Token(:whitespace,"") ],
              T[x...])

import Base: convert
Base.convert(::Type{Line{I,T}}, x::Line{J,S}) where {I,J,S,T} =
    Line(convert(Vector{I}, x.prefix), convert(Vector{T}, x.tokens))
Base.convert(::Type{Line{I,T}}, x::Vector) where {I,T} =
    Line(I[], convert(Vector{T}, x))

function Base.show(io::IO, i::Line{I,T}) where {I,T}
    if !isempty(i.prefix) && variable(i.prefix[1]) == :headline
        level = parse(Int, value(i.prefix[1]))
        wikihead = repeat("=", level)
        print(io, wikihead, " ")
        tail = Token[]
        for x in i.tokens
            if !isequal(x, Token(:whitespace,"\n"))
                print(io, x)
            else
                push!(tail,x)
            end
        end
        print(io, wikihead)
        for x in tail
            print(io, x)
        end
    else
        for x in i.prefix
            print(io, x.value === missing ? "" : x)
        end
        for x in i.tokens
            print(io, x)
        end
    end
end

Paragraph{I,T} = Vector{Line{I,T}}
Paragraph(x::Paragraph) = x
## Base.show(io::IO, v::Type{Paragraph{T}}) where T = print(io, "Paragraph{$T}")
Base.show(io::IO, v::AbstractVector{<:Line}) =
    for x in v
        print(io,x)
    end

Body{I,T} = Vector{Paragraph{I,T}}
## Base.show(io::IO, v::Type{Body{T}}) where T = print(io, "Body{$T}")
Base.show(io::IO, v::Body) =
    for x in v
        print(io,x)
    end
# Base.show(io::IO, m::MIME"text/markdown", x::Token) =
#     if x.name in [ :literal, :delimiter, :whitespace ]
#         print(io, x.value)
#     else
#         print(io,"""[$(x.value)]($(x.name) "$(x.name)")""")
#     end
# Base.show(io::IO, m::MIME"text/markdown", x::Line) = println(io,m,x.indent,x.tokens...)
# Base.show(io::IO, m::MIME"text/markdown", x::Vector{Line}) = println(io,m,x...)
# Base.show(io::IO, m::MIME"text/markdown", x::Vector{Vector{Line}}) = println(io,m,x...)

export is_type, is_heading, is_template, is_template_line, is_line, LineContent
LineContent = AbstractToken

is_heading(f=x->true, T = Line{NamedString,LineContent}) =
    IteratorParser(T,"heading") do x
        !isempty(x.prefix) &&
            variable(x.prefix[end])==:headline &&
            f(x.prefix[end])
    end
is_type(t::Type) =
    IteratorParser(t, string(t))

"""
is not a headline
"""
is_line(t::Type{<:Line}) =
    IteratorParser(t,"Line") do x
        ( isempty(x.prefix) ||
          variable(x.prefix[end])!=:headline)
    end
is_line() = is_line(Line{NamedString,LineContent})


text:
"""
addfield!(vtkfile, prefix::Union{String,Nothing}, f, dataspace)

Add a field or fields `f`, optionally prefixing the name with `prefix` to the
VTK file `vtkfile`, interpolating to `dataspace`.

`f` can be any of the following:
- a scalar or vector field (if no `prefix` is provided, then the field will be named `"data"`)
- a composite field, which will be named accordingly
- a `NamedTuple` of fields
"""
function addfield!(vtkfile, prefix, fields::NamedTuple, dataspace)
    for (key, val) in pairs(fields)
        name = string(key)
        if !isnothing(prefix)
            name = prefix * "." * name
        end
        addfield!(vtkfile, name, val, dataspace)
    end
end
addfield!(vtkfile, prefix, fields::Fields.FieldVector, dataspace) =
    addfield!(vtkfile, prefix, Fields._values(fields), dataspace)

addfield!(vtkfile, prefix, field::Fields.Field, dataspace) =
    addfield!(vtkfile, prefix, field, dataspace, eltype(field))

# composite field
function addfield!(vtkfile, prefix, field, dataspace, ::Type{T}) where {T}
    for i in 1:fieldcount(T)
        name = string(fieldname(T, i))
        if !isnothing(prefix)
            name = prefix * "." * name
        end
        addfield!(vtkfile, name, getproperty(field, i), dataspace)
    end
end

# scalars
function addfield!(vtkfile, name, field, dataspace, ::Type{T}) where {T <: Real}
    interp = Operators.Interpolate(dataspace)
    ifield = interp.(field)
    if isnothing(name)
        name = "data"
    end
    vtkfile[name] = vec(parent(ifield))
end

# vectors
function addfield!(
    vtkfile,
    name,
    field,
    dataspace,
    ::Type{T},
) where {T <: Geometry.AxisVector}
    interp = Operators.Interpolate(dataspace)
    # should we convert then interpolate, or vice versa?
    ifield = interp.(Geometry.Cartesian123Vector.(field))
    if isnothing(name)
        name = "data"
    end
    vtkfile[name] = (
        vec(parent(ifield.components.data.:1)),
        vec(parent(ifield.components.data.:2)),
        vec(parent(ifield.components.data.:3)),
    )
end


text:
export Cell

mutable struct Cell <: Obj
    input::Float64
    output::Float64
    state::Array{Float64}
    params::Array{Float64}
    conns::Array{Conn}
    interface::Bool # is input or output cell
end

function Cell(cfg::Dict, params::Array{Float64};
              interface::Bool=false)
    Cell(0.0, 0.0, zeros(cfg["n_cell_state"]), params,
         Array{Conn}(undef, 0), interface)
end

function get_params(c::Cell, n::Int64)
    c.params
end


text:
#
# Selling tickets to a sports match
#
using SDDP, GLPKMathProgInterface, JuMP, SDDiP, Base.Test
using Ipopt

struct AllBlacksData2
    T::Int                        # Number of time periods
    N::Int                        # Number of seats
    R::Array{Float64,2}           # R_ij = price of seat i at time j
    s::Int                        # Number of scenarios
    offer::Array{Vector{Int},2}   # offer_ij = a vector of scenarios length s for whether an offer for seat i will come at time j
end

function makedata(T, N, R, s)
    offers = [rand([0, 1], s) for n in 1:N, t in 1:T]
    AllBlacksData2(T, N, R, s, offers)
end

srand(11111)
data = makedata(3, 2, [3 3 6; 3 3 6], 3)

m=SDDPModel(stages=data.T, objective_bound=100.0, sense=:Max, solver=GLPKSolverMIP()) do sp, stage

    # Seat remaining?
    @binarystate(sp, x[i=1:data.N], x0==1, Bin)

    # Action: accept offer, or don't accept offer
    # We are allowed to accpect some of the seats offered but not others in this formulation
    @variable(sp, 0 <= accept_offer[i=1:data.N] <= 1, Int)

    # Balance on seats
    @constraint(sp, balance[i=1:data.N], x0[i] - x[i] == accept_offer[i])

    for i in 1:data.N
      # Can't sell a seat if there is no offer for it
      @rhsnoise(sp, ω = data.offer[i, stage], accept_offer[i] <= ω)
    end

    @stageobjective(sp, sum(data.R[i, stage] * accept_offer[i] for i=1:data.N))

    # Call for using a Lagrangian solver
    setSDDiPsolver!(sp, method=LevelMethod(quadsolver=IpoptSolver(print_level=0)))
    # setSDDiPsolver!(sp, method=SubgradientMethod())

end

srand(11111)
solution = solve(m, max_iterations=10)
@test isapprox(getbound(m), 8.0, atol=1e-3)


text:
using Test
using Catlab.Graphs
using Catlab.CategoricalAlgebra
using Catlab.Present
using Catlab.Theories
using Catlab.CategoricalAlgebra.CSetDataStructures: struct_acset

include(joinpath(@__DIR__, "../src/CSetAutomorphisms.jl"))

# Helper functions for writing tests
function xs(x::Int)::Symbol
  return Symbol("x$x")
end
function xs(xx::AbstractVector{Int})::Vector{Symbol}
  return [Symbol("x$x") for x in xx]
end
function es(x::Int)::Symbol
  return Symbol("e$x")
end
function es(xx::AbstractVector{Int})::Vector{Symbol}
  [Symbol("e$x") for x in xx]
end

"""
Create a CSet type specified by a graph
Vertices are x₁,x₂,..., edges are e₁, e₂,...
all edges are indexed
"""
function graph_to_cset(grph::StructACSet, name::Symbol)::StructACSet
  pres = Presentation(FreeSchema)
  xobs = [Ob(FreeSchema,xs(i)) for i in 1:nv(grph)]
  for x in xobs
    add_generator!(pres, x)
  end
  for (i,(src, tgt)) in enumerate(zip(grph[:src], grph[:tgt]))
    add_generator!(pres, Hom(es(i), xobs[src], xobs[tgt]))
  end

  expr = struct_acset(name, StructACSet, pres, index=es(1:ne(grph)))
  eval(expr)
  csettype = eval(name)
  return Base.invokelatest(csettype)
end

"""Create n copies of a CSet based on a graph schema"""
function init_graphs(name::Symbol, schema::StructACSet, consts::Vector{Int},
                     n::Int=2)::Vector{StructACSet}
  cset = graph_to_cset(schema, name)
  for (i, con) in enumerate(consts)
    add_parts!(cset, Symbol("x$i"), con)
  end
  return [deepcopy(cset) for _ in 1:n]
end

"""Confirm canonical hash tracks with whether two ACSets are iso"""
function test_iso(a::StructACSet,b::StructACSet, eq::Bool=true)::Test.Pass
  tst = a -> eq ? a : !a
  @test tst(is_isomorphic(a,b))
  @test a != b  # confirm they're not literally equal
  @test tst(canonical_hash(a) == canonical_hash(b))
end

G,H = Graph(4), Graph(4);
add_edges!(G,[1,2,4,4,3],[2,4,3,3,2]);
add_edges!(H,[2,3,1,4,4],[1,1,4,3,3]);
test_iso(G,H) # 196 automorphisms

Triangle = Graph(3) # f;g = h
add_edges!(Triangle, [1,1,2], [2,3,3]) # f,h,g

G,H = init_graphs(:Tri, Triangle,[2,2,2])
for i in 1:3 set_subpart!(G, Symbol("e$i"), [1,1]) end
for i in 1:3 set_subpart!(H, Symbol("e$i"), [2,2]) end
test_iso(G, H)

Loop = Graph(1)
add_edge!(Loop, 1, 1)
G, H = init_graphs(:Loo, Loop, [3])
set_subpart!(G, Symbol("e1"), [3,2,1])
set_subpart!(H, Symbol("e1"), [1,3,2])
test_iso(G, H)

cyclel, cycler = Graph(3), Graph(3)
add_edges!(cyclel,[1,2,3],[2,3,1])
add_edges!(cycler,[3,2,1],[2,1,3])
test_iso(cyclel, cycler)

# For performance reasons, this no longer is viable.
# Directed example from Hartke and Radcliffe exposition of Nauty
# G, H = Graph(9), Graph(9)
# add_edges!(G,[1,1,2,2,3,3,4,4,5,6,7,8],
#              [7,8,5,6,6,8,5,7,9,9,9,9])
# add_edges!(H,[1,1,3,3,7,7,9,9,2,4,6,8],
#              [2,4,8,6,6,2,4,8,5,5,5,5])
# test_iso(G, H)

Loop2 = Graph(1)
add_edges!(Loop2, [1,1],[1,1])

G,H = init_graphs(:Loo2, Loop2, [2])
set_subpart!(G, :e1, [2,1])
set_subpart!(G, :e2, [2,1])
set_subpart!(H, :e1, [1,1])
set_subpart!(H, :e2, [2,2])
test_iso(G, H, false)

# ACSet tests
@present TheoryDecGraph(FreeSchema) begin
  E::Ob
  V::Ob
  src::Hom(E,V)
  tgt::Hom(E,V)

  X::AttrType
  dec::Attr(E,X)
end

@acset_type Labeled(TheoryDecGraph)

G = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,2,3,4]
  tgt = [2,3,4,1]
  dec = ["a","b","c","d"]
end;

H = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,3,2,4]
  tgt = [3,2,4,1]
  dec = ["a","b","c","d"]
end;

test_iso(G,H) # vertices permuted

I = @acset Labeled{String} begin
V = 4
E = 4
src = [1,2,3,4]
tgt = [2,3,4,1]
dec = ["b","c","d","a"]
end;

test_iso(G,I) # labels permuted

N = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,2,3,4]
  tgt = [2,3,4,1]
  dec = ["a","a","b","c"]
end;

test_iso(G,N, false) # label mismatch

K = @acset Labeled{String} begin
  V = 4
  E = 4
  src = [1,3,2,4]
  tgt = [2,3,4,1]
  dec = ["a","d","b","c"]
end;

test_iso(G,K,false) # vertex mismatch

G1 = @acset Labeled{String} begin
  V = 1
  E = 1
  src = [1]
  tgt = [1]
  dec = ["a"]
end;

H1 = @acset Labeled{String} begin
  V = 1
  E = 1
  src = [1]
  tgt = [1]
  dec = ["b"]
end;

test_iso(G1, H1, false) # label values different


text:
using BinaryBuilder

# Collection of sources required to build MySQL
sources = [
    "https://downloads.mariadb.com/Connectors/c/connector-c-3.0.3/mariadb-connector-c-3.0.3-src.tar.gz" =>
    "210f0ee3414b235d3db8e98e9e5a0a98381ecf771e67ca4a688036368984eeea",
     "https://dev.mysql.com/get/Downloads/Connector-C/mysql-connector-c-6.1.11-macos10.12-x86_64.tar.gz" =>
    "c97d76936c6caf063778395e7ca15862770a1ab77c1731269408a8d5c0eb4b93",
]

# Bash recipe for building across all platforms
script = raw"""
if [ $target == "x86_64-apple-darwin14" ]; then
    cd $WORKSPACE/srcdir
    mkdir $prefix/lib
    cp mysql-connector-c-6.1.11-macos10.12-x86_64/lib/libmysqlclient.18.dylib $prefix/lib/libmariadb.dylib
else
    cd $WORKSPACE/srcdir
    cd mariadb-connector-c-3.0.3-src/
    cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=$prefix -DCMAKE_TOOLCHAIN_FILE=/opt/$target/$target.toolchain
    make && make install
    mv $prefix/lib/mariadb/* $prefix/lib/.
fi
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = [
    Linux(:i686, :glibc),
    Linux(:x86_64, :glibc),
    Linux(:aarch64, :glibc),
    Linux(:armv7l, :glibc),
    Linux(:powerpc64le, :glibc),
    MacOS()
]

# The products that we will ensure are always built
products(prefix) = Product[
    LibraryProduct(prefix, "libmariadb", :libmariadb)
]

# Dependencies that must be installed before this package can be built
dependencies = [

]

# Parse out some command-line arguments
BUILD_ARGS = ARGS

# This sets whether we should build verbosely or not
verbose = "--verbose" in BUILD_ARGS
BUILD_ARGS = filter!(x -> x != "--verbose", BUILD_ARGS)

# This flag skips actually building and instead attempts to reconstruct a
# build.jl from a GitHub release page.  Use this to automatically deploy a
# build.jl file even when sharding targets across multiple CI builds.
only_buildjl = "--only-buildjl" in BUILD_ARGS
BUILD_ARGS = filter!(x -> x != "--only-buildjl", BUILD_ARGS)

if !only_buildjl
    # If the user passed in a platform (or a few, comma-separated) on the
    # command-line, use that instead of our default platforms
    if length(BUILD_ARGS) > 0
        platforms = platform_key.(split(BUILD_ARGS[1], ","))
    end
    info("Building for $(join(triplet.(platforms), ", "))")

    # Build the given platforms using the given sources
    autobuild(pwd(), "MySQL", platforms, sources, script, products;
                                      dependencies=dependencies, verbose=verbose)
    @show readdir("products")
else
    # If we're only reconstructing a build.jl file on Travis, grab the information and do it
    if !haskey(ENV, "TRAVIS_REPO_SLUG") || !haskey(ENV, "TRAVIS_TAG")
        error("Must provide repository name and tag through Travis-style environment variables!")
    end
    repo_name = ENV["TRAVIS_REPO_SLUG"]
    tag_name = ENV["TRAVIS_TAG"]
    product_hashes = product_hashes_from_github_release(repo_name, tag_name; verbose=verbose)
    bin_path = "https://github.com/$(repo_name)/releases/download/$(tag_name)"
    dummy_prefix = Prefix(pwd())
    print_buildjl(pwd(), products(dummy_prefix), product_hashes, bin_path)

    if verbose
        info("Writing out the following reconstructed build.jl:")
        print_buildjl(STDOUT, product_hashes; products=products(dummy_prefix), bin_path=bin_path)
    end
end


text:
__precompile__()

module NetCDF

using Formatting
using Base.Cartesian

include("netcdf_c.jl")

import Base.show

export NcDim,NcVar,NcFile,ncread,ncread!,ncwrite,nccreate,ncsync,ncinfo,ncclose,ncputatt,
    NC_BYTE,NC_SHORT,NC_INT,NC_FLOAT,NC_DOUBLE,NC_STRING,ncgetatt,NC_NOWRITE,NC_WRITE,NC_CHAR,
    NC_CLOBBER,NC_NOCLOBBER,NC_CLASSIC_MODEL,NC_64BIT_OFFSET,NC_NETCDF4,NC_UNLIMITED,
    nc_char2string, nc_string2char

NC_VERBOSE = false
#Some constants

global const nctype2jltype = Dict(
    NC_BYTE => Int8,
    NC_UBYTE => UInt8,
    NC_SHORT => Int16,
    NC_INT => Int32,
    NC_INT64 => Int64,
    NC_FLOAT => Float32,
    NC_DOUBLE => Float64,
    NC_CHAR => UInt8,
    NC_STRING => String)

global const nctype2string = Dict(
  NC_BYTE => "BYTE",
  NC_UBYTE => "UBYTE",
  NC_SHORT => "SHORT",
  NC_INT => "INT",
  NC_INT64 => "INT64",
  NC_FLOAT => "FLOAT",
  NC_DOUBLE => "DOUBLE",
  NC_STRING => "STRING",
  NC_CHAR => "CHAR")

function jl2nc(t::DataType)
    popfirst!(collect(keys(nctype2jltype))[findall(e->(t <: e), collect(values(nctype2jltype)))])
end
jl2nc(t::Type{UInt8}) = NC_UBYTE

getJLType(t::DataType) = t
getJLType(t::Int) = nctype2jltype[t]

getNCType(t::DataType) = jl2nc(t)
getNCType(t::Int) = Int(t)

"""
    NcDim

Represents a NetCDF dimension of name `name` optionally holding the dimension values.
"""
mutable struct NcDim
  ncid::Int32
  dimid::Int32
  varid::Int32
  name::String
  dimlen::UInt
  vals::AbstractArray
  atts::Dict
  unlim::Bool
end

"""
    NcDim(name::String,dimlength::Integer)

Creates a NetCDF dimension object named `name` with length `dimlength` in memory.

### Keyword arguments

* `values=[]` optional for adding dimension values which can be written to a dimension variable
* `atts` a Dict of attributes associated to the dimension variable
* `unlimited` is this the record dimension, defaults to `false`
"""
function NcDim(name::AbstractString,dimlength::Integer;values::Union{AbstractArray,Number}=[],atts::Dict=Dict{Any,Any}(),unlimited=false)
    length(values) > 0 && length(values) != dimlength && error("Dimension value vector must have the same length as dimlength!")
    NcDim(-1,-1,-1,string(name),dimlength,values,atts,unlimited)
end

"""
NcDim(name::String,values::AbstractVector)

Creates a NetCDF dimension object named `name` and associated `values` in memory.

### Keyword arguments

* `atts` a Dict of attributes associated to the dimension variable
* `unlimited` is this the record dimension, defaults to `false`
"""
NcDim(name::AbstractString,values::AbstractArray;atts::Dict=Dict{Any,Any}(),unlimited=false) =
    NcDim(name,length(values),values=values,atts=atts,unlimited=unlimited)
NcDim(name::AbstractString,values::AbstractArray,atts::Dict;unlimited=false) =
    NcDim(name,length(values),values=values,atts=atts,unlimited=unlimited)

"""
    NcVar

`NcVar{T,N,M}` represents a NetCDF variable. It is a subtype of AbstractArray{T,N}, so normal indexing using `[]`
will work for reading and writing data to and from a NetCDF file. `NcVar` objects are returned by `NetCDF.open`, by
indexing an `NcFile` object (e.g. `myfile["temperature"]`) or, when creating a new file, by its constructor. The type parameter `M`
denotes the NetCDF data type of the variable, which may or may not correspond to the Julia Data Type.
"""
mutable struct NcVar{T,N,M} <: AbstractArray{T,N}
    ncid::Int32
    varid::Int32
    ndim::Int32
    natts::Int32
    nctype::Int32
    name::String
    dimids::Vector{Int32}
    dim::Vector{NcDim}
    atts::Dict
    compress::Int32
    chunksize::NTuple{N,Int32}
end

Base.convert(::Type{NcVar{T,N,M}},v::NcVar{S,N,M}) where {S,T,N,M}=NcVar{T,N,M}(v.ncid,v.varid,v.ndim,v.natts,v.nctype,v.name,v.dimids,v.dim,v.atts,v.compress,v.chunksize)
Base.convert(::Type{NcVar{T}},v::NcVar{S,N,M}) where {S,T,N,M}=NcVar{T,N,M}(v.ncid,v.varid,v.ndim,v.natts,v.nctype,v.name,v.dimids,v.dim,v.atts,v.compress,v.chunksize)

"""
    NcVar(name::AbstractString,dimin::Union{NcDim,Array{NcDim,1}}

Creates a new NetCDF variable `name` in memory. The variable is associated to a
list of NetCDF dimensions specified by `dimin`.

### Keyword arguments

* `atts` Dictionary representing the variables attributes
* `t` either a Julia type, (one of `Int16, Int32, Float32, Float64, String`) or a NetCDF data type (`NC_SHORT, NC_INT, NC_FLOAT, NC_DOUBLE, NC_CHAR, NC_STRING`) determines the data type of the variable. Defaults to -1
* `compress` Integer which sets the compression level of the variable for NetCDF4 files. Defaults to -1 (no compression). Compression levels of 1..9 are valid
"""
function NcVar(name::AbstractString,dimin::Union{NcDim,Array{NcDim,1}};atts::Dict=Dict{Any,Any}(),t::Union{DataType,Integer}=Float64,compress::Integer=-1,chunksize=ntuple(i->zero(Int32),isa(dimin,NcDim) ? 1 : length(dimin)))
    dim = isa(dimin,NcDim) ? NcDim[dimin] : dimin
    return NcVar{getJLType(t),length(dim),getNCType(t)}(-1,-1,length(dim),length(atts), getNCType(t),name,Array{Int32}(undef,length(dim)),dim,atts,compress,chunksize)
end
NcVar(name::AbstractString,dimin::Union{NcDim,Array{NcDim,1}},atts,t::Union{DataType,Integer}=Float64) =
    NcVar{getJLType(t),length(dimin),getNCType(t)}(-1,-1,length(dimin),length(atts), getNCType(t),name,Array{Int}(undef,length(dimin)),dimin,atts,-1,ntuple(i->zero(Int32),length(dimin)))

#Array methods
@generated function Base.size(a::NcVar{T,N}) where {T,N}
    :(@ntuple($N,i->Int(a.dim[i].dimlen)))
end
const IndR  = Union{Integer,UnitRange,Colon}
const ArNum = Union{AbstractArray,Number}

IndexStyle(::NcVar) = IndexCartesian()
Base.getindex(v::NcVar{T,0}) where {T} = readvar(v)
Base.getindex(v::NcVar{T,1},i1::IndR) where {T} = readvar(v,i1)
Base.getindex(v::NcVar{T,2},i1::IndR,i2::IndR) where {T} = readvar(v,i1,i2)
Base.getindex(v::NcVar{T,3},i1::IndR,i2::IndR,i3::IndR) where {T} = readvar(v,i1,i2,i3)
Base.getindex(v::NcVar{T,4},i1::IndR,i2::IndR,i3::IndR,i4::IndR) where {T} = readvar(v,i1,i2,i3,i4)
Base.getindex(v::NcVar{T,5},i1::IndR,i2::IndR,i3::IndR,i4::IndR,i5::IndR) where {T} = readvar(v,i1,i2,i3,i4,i5)
Base.getindex(v::NcVar{T,6},i1::IndR,i2::IndR,i3::IndR,i4::IndR,i5::IndR,i6::IndR) where {T} = readvar(v,i1,i2,i3,i4,i5,i6)

Base.setindex!(v::NcVar{T,0},x::ArNum) where {T} = putvar(v,x)
Base.setindex!(v::NcVar{T,1},x::ArNum,i1::IndR) where {T} = putvar(v,x,i1)
Base.setindex!(v::NcVar{T,2},x::ArNum,i1::IndR,i2::IndR) where {T} = putvar(v,x,i1,i2)
Base.setindex!(v::NcVar{T,3},x::ArNum,i1::IndR,i2::IndR,i3::IndR) where {T} = putvar(v,x,i1,i2,i3)
Base.setindex!(v::NcVar{T,4},x::ArNum,i1::IndR,i2::IndR,i3::IndR,i4::IndR) where {T} = putvar(v,x,i1,i2,i3,i4)
Base.setindex!(v::NcVar{T,5},x::ArNum,i1::IndR,i2::IndR,i3::IndR,i4::IndR,i5::IndR) where {T} = putvar(v,x,i1,i2,i3,i4,i5)
Base.setindex!(v::NcVar{T,6},x::ArNum,i1::IndR,i2::IndR,i3::IndR,i4::IndR,i5::IndR,i6::IndR) where {T} = putvar(v,x,i1,i2,i3,i4,i5,i6)

"""
    NcFile

Represents a link to a NetCDF file. Is returned by `NetCDF.open` or by `NetCDF.create`.
"""
mutable struct NcFile
    ncid::Int32
    nvar::Int32
    ndim::Int32
    ngatts::Int32
    vars::Dict{String,NcVar}
    dim::Dict{String,NcDim}
    gatts::Dict
    nunlimdimid::Int32
    name::String
    omode::UInt16
    in_def_mode::Bool
end
#Define getindex method to retrieve a variable
Base.getindex(nc::NcFile,i::AbstractString) = haskey(nc.vars,i) ? nc.vars[i] : error("NetCDF file $(nc.name) does not have a variable named $(i)")

include("netcdf_helpers.jl")

const currentNcFiles = Dict{String,NcFile}()

readvar!(nc::NcFile, varname::AbstractString, retvalsa::AbstractArray;start::Vector=defaultstart(nc[varname]),count::Vector=defaultcount(nc[varname])) =
    readvar!(nc[varname],retvalsa,start=start,count=count)

"""
    NetCDF.readvar!(v::NcVar, d::Array;start::Vector=ones(UInt,ndims(d)),count::Vector=size(d))

Reads the values from the file associated to the `NcVar` object `v` into the array `d`. By default the whole variable is read

### Keyword arguments

* `start` Vector of length `ndim(v)` setting the starting index for each dimension
* `count` Vector of length `ndim(v)` setting the count of values to be read along each dimension. The value -1 is treated as a special case to read all values from this dimension

### Example

Assume `v` is a NetCDF variable with dimensions (3,3,10).

    x = zeros(3,1,10)
    NetCDF.readvar(v, x, start=[1,2,1], count=[3,1,-1])

This reads all values from the first and last dimension and only the second value from the second dimension.
"""
function readvar!(v::NcVar, retvalsa::AbstractArray;start::Vector=defaultstart(v),count::Vector=defaultcount(v))

    isa(retvalsa,Array) || Base.iscontiguous(retvalsa) || error("Can only read into contiguous pieces of memory")

    length(start) == v.ndim || error("Length of start ($(length(start))) must equal the number of variable dimensions ($(v.ndim))")
    length(count) == v.ndim || error("Length of start ($(length(count))) must equal the number of variable dimensions ($(v.ndim))")

    p = preparestartcount(start, count, v)

    length(retvalsa) != p && error("Size of output array ($(length(retvalsa))) does not equal number of elements to be read ($p)!")

    nc_get_vara_x!(v, gstart, gcount, retvalsa)

    retvalsa
end

readvar(nc::NcFile, varname::AbstractString;start::Vector=defaultstart(nc[varname]),count::Vector=defaultcount(nc[varname])) =
    readvar(nc[varname],start=start,count=count)

"""
    NetCDF.readvar(v::NcVar;start::Vector=ones(UInt,ndims(d)),count::Vector=size(d))

Reads the values from the file associated to the `NcVar` object `v` and returns them. By default the whole variable is read

### Keyword arguments

* `start` Vector of length `ndim(v)` setting the starting index for each dimension
* `count` Vector of length `ndim(v)` setting the count of values to be read along each dimension. The value -1 is treated as a special case to read all values from this dimension

### Example

Assume `v` is a NetCDF variable with dimensions (3,3,10).

    x = NetCDF.readvar(v, start=[1,2,1], count=[3,1,-1])

This reads all values from the first and last dimension and only the second value from the second dimension.
"""
function readvar(v::NcVar{T,N};start::Vector=defaultstart(v),count::Vector=defaultcount(v)) where {T,N}
    s = [count[i]==-1 ? size(v,i)-start[i]+1 : count[i] for i=1:length(count)]
    retvalsa = Array{T}(undef,s...)
    readvar!(v, retvalsa, start=start, count=count)
    return retvalsa
end

"""
    NetCDF.readvar{T,N}(v::NcVar{T,N},I::Union{Integer, UnitRange, Colon}...)

Reads data from a NetCDF file with array-style indexing. `Integer`s and `UnitRange`s and `Colon`s are valid indices for each dimension.
"""
function readvar(v::NcVar{T,N},I::IndR...) where {T,N}
    count=ntuple(i->counti(I[i],v.dim[i].dimlen),length(I))
    retvalsa = Array{T}(undef,count...)
    readvar!(v, retvalsa, I...)
    return retvalsa
end

# Here are some functions for array-style indexing readvar
#For single indices
@generated function readvar(v::NcVar{T,N},I::Integer...) where {T,N}
    N==length(I) || error("Dimension mismatch")
    if N==0
      checkbounds(v,I...)
      gstart[1]=0
      nc_get_var1_x(v,gstart,T)::T
    else
      quote
        checkbounds(v,I...)
        @nexprs $N i->gstart[v.ndim+1-i]=I[i]-1
        nc_get_var1_x(v,gstart,T)::T
      end
    end
end

firsti(i::Integer,l::Integer) = i-1
counti(i::Integer,l::Integer) = 1
firsti(r::UnitRange,l::Integer) = first(r)-1
counti(r::UnitRange,l::Integer) = length(r)
firsti(r::Colon,l::Integer) = 0
counti(r::Colon,l::Integer) = Int(l)

# For ranges
"""
    NetCDF.readvar!{T,N}(v::NcVar{T,N},d::AbstractArray, I::Union{Integer, UnitRange, Colon}...)

Reads data from a NetCDF file with array-style indexing and writes them to d. `Integer`s and `UnitRange`s and `Colon`s are valid indices for each dimension.
"""
@generated function readvar!(v::NcVar{T,N}, retvalsa::AbstractArray,I::IndR...) where {T,N}

    N == length(I) || error("Dimension mismatch")

    quote
        checkbounds(v, I...)

        isa(retvalsa, Array) || Base.iscontiguous(retvalsa) || error("Can only read into contiguous pieces of memory")

        @nexprs $N i->gstart[v.ndim+1-i]=firsti(I[i],v.dim[i].dimlen)
        @nexprs $N i->gcount[v.ndim+1-i]=counti(I[i],v.dim[i].dimlen)
        p=1
        @nexprs $N i->p=p*gcount[v.ndim+1-i]
        length(retvalsa) != p && error(string("Size of output array ($(length(retvalsa))) does not equal number of elements to be read (",p,")!"))
        nc_get_vara_x!(v,gstart,gcount,retvalsa)
        retvalsa
    end
end

for (t,ending,arname) in funext
    fname = Symbol("nc_get_vara_$ending")
    fname1 = Symbol("nc_get_var1_$ending")
    arsym = Symbol(arname)
    @eval nc_get_vara_x!(v::NcVar{$t},start::Vector{UInt},count::Vector{UInt},retvalsa::AbstractArray{$t})=$fname(v.ncid,v.varid,start,count,retvalsa)
    @eval nc_get_var1_x(v::NcVar{$t},start::Vector{UInt},::Type{$t})=begin $fname1(v.ncid,v.varid,start,$(arsym)); $(arsym)[1] end
end

nc_get_vara_x!(v::NcVar{UInt8,N,NC_CHAR},start::Vector{UInt},count::Vector{UInt},retvalsa::AbstractArray{UInt8}) where {N} =
    nc_get_vara_text(v.ncid,v.varid,start,count,retvalsa)
nc_get_var1_x!(v::NcVar{UInt8,N,NC_CHAR},start::Vector{UInt},retvalsa::AbstractArray{UInt8}) where {N} =
    nc_get_var1_text(v.ncid,v.varid,start,retvalsa)

function nc_get_vara_x!(v::NcVar{String,N,NC_STRING},start::Vector{UInt},count::Vector{UInt},retvalsa::AbstractArray{String}) where N
    @assert length(retvalsa)==prod(view(count,1:N))
    retvalsa_c=fill(Ptr{UInt8}(0),length(retvalsa))
    nc_get_vara_string(v.ncid,v.varid,start,count,retvalsa_c)
    for i=1:length(retvalsa)
        retvalsa[i]=unsafe_string(retvalsa_c[i])
    end
    nc_free_string(length(retvalsa_c),retvalsa_c)
    retvalsa
end

function nc_get_var1_x(v::NcVar{String,N,NC_STRING},start::Vector{UInt},::Type{String}) where N
    retvalsa_c=fill(Ptr{UInt8}(0),1)
    nc_get_var1_string(v.ncid,v.varid,start,retvalsa_c)
    retval=unsafe_string(retvalsa_c[1])
    nc_free_string(1,retvalsa_c)
    retval
end

function putatt(ncid::Integer,varid::Integer,atts::Dict)
    for a in atts
        name=a[1]
        val=a[2]
        nc_put_att(ncid,varid,name,val)
    end
end

function putatt(nc::NcFile, atts::Dict)
    putatt(nc.ncid, NC_GLOBAL, atts)
    merge!(nc.gatts, getatts(nc.ncid,NC_GLOBAL,collect(keys(atts))))
end

function putatt(var::NcVar, atts::Dict)
    putatt(var.ncid, var.varid, atts)
    merge!(var.atts, getatts(var.ncid,var.varid,collect(keys(atts))))
end

"""
    NetCDF.putatt(nc::NcFile, varname::String, atts::Dict)

Writes the attributes defined in `atts` to the variable `varname` in the NetCDF file
`nc`. Existing attributes are overwritten. If varname is not a valid variable name,
a global attribute will be written.
"""
function putatt(nc::NcFile,varname::AbstractString,atts::Dict)
    chdef = false
    if !nc.in_def_mode
        chdef = true
        nc_redef(nc.ncid)
    end
    if haskey(nc.vars,varname)
        putatt(nc.vars[varname],atts)
    else
        putatt(nc,atts)
    end
    chdef && nc_enddef(nc.ncid)
end

"""
    ncputatt(nc::String,varname::String,atts::Dict)

Writes the attributes defined in `atts` to the variable `varname` for the given NetCDF file name
`nc`. Existing attributes are overwritten. If varname is not a valid variable name,
a global attribute will be written.
"""
function ncputatt(nc::AbstractString,varname::AbstractString,atts::Dict)
    nc = haskey(currentNcFiles,abspath(nc)) ? currentNcFiles[abspath(nc)] : open(nc,mode=NC_WRITE)
    if (nc.omode == NC_NOWRITE)
        fil = nc.name
        close(nc)
        println("reopening file in WRITE mode")
        open(fil, mode=NC_WRITE)
    end
    putatt(nc, varname, atts)
end

putvar(nc::NcFile,varname::AbstractString,vals::AbstractArray;start=ones(Int,length(size(vals))),count=[size(vals)...]) =
    putvar(nc[varname], vals, start=start, count=count)

"""
    NetCDF.putvar(v::NcVar,vals::Array;start::Vector=ones(Int,length(size(vals))),count::Vector=[size(vals)...])

Writes the values from the array `vals` to a netcdf file. `v` is the NcVar handle of the respective variable and `vals` an array
with the same dimension as the variable in the netcdf file.

### Keyword arguments

* `start` Vector of length `ndim(v)` setting the starting index for each dimension
* `count` Vector of length `ndim(v)` setting the count of values to be read along each dimension. The value -1 is treated as a special case to read all values from this dimension

"""
function putvar(v::NcVar,vals::AbstractArray;start::Vector=ones(Int,length(size(vals))),count::Vector=[size(vals)...])
    isa(vals,Array) || Base.iscontiguous(vals) || error("Can only write from contiguous pieces of memory")
    p=preparestartcount(start, count, v)
    nc_put_vara_x(v, gstart, gcount, vals)
end

"""
    NetCDF.putvar(v::NcVar, val, i...)

Writes the value(s) `val` to the variable `v` while the indices are given in in an array-style indexing manner.
"""
@generated function putvar(v::NcVar{T,N},val::Any,I::IndR...) where {T,N}

    N==length(I) || error("Dimension mismatch")
    quote
        isa(val,Number) || isa(val,Array) || Base.iscontiguous(val) || error("Can only write from contiguous pieces of memory")
        @nexprs $N i->gstart[v.ndim+1-i]=firsti(I[i],v.dim[i].dimlen)
        @nexprs $N i->gcount[v.ndim+1-i]=counti(I[i],v.dim[i].dimlen)
        checkboundsNC(v)
        p=1
        @nexprs $N i->p=p*gcount[v.ndim+1-i]
        length(val) != p && error(string("Size of output array ($(length(retvalsa))) does not equal number of elements to be read (",p,")!"))
        nc_put_vara_x(v,gstart,gcount,val)
    end
end

@generated function putvar(v::NcVar{T,N}, val::Any, I::Integer...) where {T,N}

    N == length(I) || error("Dimension mismatch")
    if N==0
      quote
        gstart[1]=0
        nc_put_var1_x(v,gstart,val[1])
      end
    else
      quote
        @nexprs $N i->gstart[v.ndim+1-i]=I[i]-1
        @nall($N,d->((I[d]<=v.dim[d].dimlen && I[d]>0) || v.dim[d].unlim)) || throw(BoundsError(v,I))
        nc_put_var1_x(v,gstart,val[1])
      end
    end

end

for (t, ending, arname) in funext
    fname = Symbol("nc_put_vara_$ending")
    fname1 = Symbol("nc_put_var1_$ending")
    arsym = Symbol(arname)
    @eval nc_put_vara_x(v::NcVar,start::Vector{UInt}, count::Vector{UInt}, vals::AbstractArray{$t})=$fname(v.ncid,v.varid,start,count,vals)
    @eval nc_put_var1_x(v::NcVar,start::Vector{UInt},val::$t)=begin $(arsym)[1]=val; $fname1(v.ncid,v.varid,start,$(arsym)) end
end

nc_put_vara_x(v::NcVar{UInt8,N,NC_CHAR},start::Vector{UInt}, count::Vector{UInt}, vals::AbstractArray{UInt8}) where {N}=nc_put_vara_text(v.ncid,v.varid,start,count,vals)
nc_put_var1_x(v::NcVar{UInt8,N,NC_CHAR},start::Vector{UInt},val::UInt8) where {N}=begin vals=UInt8[val]; nc_put_var1_text(v.ncid,v.varid,start,count,vals) end

function nc_put_vara_x(v::NcVar{String,N,NC_STRING}, start, count,vals::AbstractArray{String}) where N
    vals_p = map(x->pointer(x),vals)
    nc_put_vara_string(v.ncid,v.varid,start,count,vals_p)
end

function nc_put_var1_x(v::NcVar{String,N,NC_STRING},start::Vector{UInt},val::String) where N
  val_p = [pointer(val)]
  nc_put_var1_string(v.ncid,v.varid,start,val_p)
end

function Base.push!(v::NcVar,a::AbstractArray)
    sold = size(v)
    N = ndims(v)
    iunlim = findall(map(x->x.unlim,v.dim))
    length(iunlim) == 1 || error("You can only push to a NetCDF variable with one unlimited dimension")
    st = fill(1, N)
    st[iunlim[1]] = sold[iunlim[1]] + 1
    co = fill(-1, N)
    if ndims(v) == ndims(a)
        co[iunlim[1]] = size(a, iunlim[1])
    elseif ndims(v) == ndims(a) + 1
        co[iunlim[1]] = 1
    else
        error("You can only push variables that have equal or one fewer dimension than the NetCDF Variable")
    end
    NetCDF.putvar(v, a, start=st, count=co)
end

function Base.push!(v::NcVar{T,1}, a::Number) where T
    push!(v, collect(a))
end

"""
    ncsync()

Synchronizes the changes made all open NetCDF files and writes changes to the disk.
"""
function ncsync()
    for ncf in currentNcFiles
        nc_sync(ncf[2].ncid)
    end
end

"""
    NetCDF.sync(nc::NcFile)

Synchronizes the changes made to the file and writes changes to the disk.
"""
function sync(nc::NcFile)
    nc_sync(nc.ncid)
end

#Function to close netcdf files
"""
    ncclose(filename::String)

Closes the file and writes changes to the disk. If argument is omitted, all open files are closed.
"""
function ncclose(fil::AbstractString)
    if (haskey(currentNcFiles,abspath(fil)))
        close(currentNcFiles[abspath(fil)])
    else
        println("File $fil not currently opened.")
    end
end

function ncclose()
    for f in keys(currentNcFiles)
        ncclose(f)
    end
end

function setcompression(v::NcVar,mode)
    if v.compress > -1
        if (NC_NETCDF4 & mode) == 0
            warn("Compression only possible for NetCDF4 file format. Compression will be ingored.")
            v.compress = -1
        else
            v.compress = max(v.compress,9)
            nc_def_var_deflate(v.ncid, v.varid, Int32(1), Int32(1), v.compress);
        end
    end
end

"""
    NetCDF.create(name::String,varlist::Array{NcVar};gatts::Dict=Dict{Any,Any}(),mode::UInt16=NC_NETCDF4)

Creates a new NetCDF file. Here, `name`
 is the name of the file to be created and `varlist` an array of `NcVar` holding the variables that should appear in the file.

### Keyword arguments

* `gatts` a Dict containing global attributes of the NetCDF file
* `mode` NetCDF file type (NC_NETCDF4, NC_CLASSIC_MODEL or NC_64BIT_OFFSET), defaults to NC_NETCDF4
"""
function create(name::AbstractString,varlist::Array{NcVar};gatts::Dict=Dict{Any,Any}(),mode::UInt16=NC_NETCDF4)

    #Create the file
    id = nc_create(name,mode)
    # Collect Dimensions and set NetCDF ID
    vars = Dict{String,NcVar}()
    dims = Set{NcDim}()
    for v in varlist
        v.ncid=id
        for d in v.dim
            push!(dims,d)
        end
    end
    nunlim = 0
    ndim = Int32(length(dims))

    #Create the NcFile Object
    nc = NcFile(id,Int32(length(vars)),ndim,zero(Int32),vars,Dict{String,NcDim}(),Dict{Any,Any}(),zero(Int32),name,NC_WRITE,true)

    for d in dims
        create_dim(nc, d)
        if (length(d.vals)>0) & (!haskey(nc.vars,d.name))
            elt = eltype(d.vals)
            push!(varlist,NcVar{elt,1,jl2nc(elt)}(id,varida[1],1,length(d.atts),jl2nc(elt),d.name,[d.dimid],[d],d.atts,-1,(zero(Int32),)))
        end
    end

    # Create variables in the file
    for v in varlist
        create_var(nc,v,mode)
    end

    # Put global attributes
    if !isempty(gatts)
        putatt(nc,gatts)
    end

    # Leave define mode
    nc_enddef(nc)

    currentNcFiles[abspath(nc.name)] = nc

    for d in nc.dim
        #Write dimension variable
        if length(d[2].vals) > 0
            putvar(nc,d[2].name,d[2].vals)
        end
    end

    return(nc)
end

create(name::AbstractString,varlist::NcVar...;gatts::Dict=Dict{Any,Any}(),mode::UInt16=NC_NETCDF4) =
    create(name,NcVar[varlist[i] for i=1:length(varlist)];gatts=gatts,mode=mode)

"""
    NetCDF.close(nc::NcFile)

closes a NetCDF file handle
"""
function close(nco::NcFile)
    #Close file
    nc_close(nco.ncid)
    delete!(currentNcFiles, abspath(nco.name))
    return nco.ncid
end

"""
    NetCDF.open(fil::AbstractString,v::AbstractString)

opens a NetCDF variable `v` in the NetCDF file `fil` and returns an `NcVar` handle that implements the AbstractArray interface for reading and writing.

### Keyword arguments

* `mode` mode in which the file is opened, defaults to `NC_NOWRITE`, choose `NC_WRITE` for write access
* `readdimvar` determines if dimension variables will be read into the file structure, default is `false`
"""
function open(fil::AbstractString,v::AbstractString; mode::Integer=NC_NOWRITE, readdimvar::Bool=false)
    nc=open(fil,mode=mode,readdimvar=readdimvar)
    nc.vars[v]
end

"""
    NetCDF.open(fil::AbstractString)

opens the NetCDF file `fil` and returns a `NcFile` handle.

### Keyword arguments

* `mode` mode in which the file is opened, defaults to `NC_NOWRITE`, choose `NC_WRITE` for write access
* `readdimvar` determines if dimension variables will be read into the file structure, default is `false`
"""
function open(fil::AbstractString; mode::Integer=NC_NOWRITE, readdimvar::Bool=false)

    if haskey(currentNcFiles,abspath(fil))
        if currentNcFiles[abspath(fil)].omode == mode
            return(currentNcFiles[abspath(fil)])
        else
            nc=currentNcFiles[abspath(fil)]
            nc_close(nc.ncid)
            id=nc_open(fil,mode)
            nc.ncid=id
            return(nc)
        end
    end
    # Open netcdf file
    ncid = nc_open(fil,mode)

    #Get initial information
    ndim,nvar,ngatt,nunlimdimid = nc_inq(ncid)

    #Create ncdf object
    ncf = NcFile(ncid,Int32(nvar-ndim),ndim,ngatt,Dict{String,NcVar}(),Dict{String,NcDim}(),Dict{Any,Any}(),nunlimdimid,abspath(fil),mode,false)

    #Read global attributes
    ncf.gatts=getatts_all(ncid,NC_GLOBAL,ngatt)

    #Read dimensions
    for dimid = 0:ndim-1
        (name,dimlen)=nc_inq_dim(ncid,dimid)
        ncf.dim[name]=NcDim(ncid,dimid,-1,name,dimlen,[],Dict{Any,Any}(),dimid==nunlimdimid ? true : false)
    end

    #Read variable information
    for varid = 0:(nvar-1)
        (name,nctype,dimids,natts,vndim,isdimvar,chunksize) = nc_inq_var(ncf,varid)
        if (isdimvar)
            ncf.dim[name].varid=varid
        end
        atts = getatts_all(ncid,varid,natts)
        vdim = Array{NcDim}(undef,length(dimids))
        for (i, did) in enumerate(dimids)
            vdim[i] = ncf.dim[getdimnamebyid(ncf,did)]
        end
        ncf.vars[name]=NcVar{nctype2jltype[nctype],Int(vndim),Int(nctype)}(ncid,Int32(varid),vndim,natts,nctype,name,dimids[vndim:-1:1],vdim[vndim:-1:1],atts,0,chunksize)
    end
    readdimvar == true && _readdimvars(ncf)
    currentNcFiles[abspath(ncf.name)] = ncf
    return ncf
end

# Define some high-level functions
"""
    ncread(filename, varname)

reads the values of the variable varname from file filename and returns the values in an array.

### Keyword arguments

* `start` Vector of length `ndim(v)` setting the starting index for each dimension
* `count` Vector of length `ndim(v)` setting the count of values to be read along each dimension. The value -1 is treated as a special case to read all values from this dimension

### Example

To read the second slice of a 3D NetCDF variable one can write:

    ncread("filename","varname", start=[1,1,2], count = [-1,-1,1])

"""
function ncread(fil::AbstractString,vname::AbstractString;start::Array{T}=Array{Int}(undef,0),count::Array{T}=Array{Int}(undef,0)) where T<:Integer
    nc = haskey(currentNcFiles,abspath(fil)) ? currentNcFiles[abspath(fil)] : open(fil)
    length(start)==0 && (start=defaultstart(nc[vname]))
    length(count)==0 && (count=defaultcount(nc[vname]))
    x  = readvar(nc[vname],start=start,count=count)
    return x
end
ncread(fil::AbstractString,vname::AbstractString,start::Array{T,1},count::Array{T,1}) where {T<:Integer}=ncread(fil,vname,start=start,count=count)

"""
ncread!(filename, varname, d)

reads the values of the variable varname from file filename and writes the results to the pre-allocated array `d`

### Keyword arguments

* `start` Vector of length `ndim(v)` setting the starting index for each dimension
* `count` Vector of length `ndim(v)` setting the count of values to be read along each dimension. The value -1 is treated as a special case to read all values from this dimension

### Example

To read the second slice of a 3D NetCDF variable one can write:

d = zeros(10,10,1)
ncread!("filename","varname", d, start=[1,1,2], count = [-1,-1,1])

"""
function ncread!(fil::AbstractString,vname::AbstractString,vals::AbstractArray;start::Vector{Int}=ones(Int,ndims(vals)),count::Vector{Int}=[size(vals,i) for i=1:ndims(vals)])
    nc = haskey(currentNcFiles,abspath(fil)) ? currentNcFiles[abspath(fil)] : open(fil)
    x  = readvar!(nc,vname,vals,start=start,count=count)
    return x
end

"""
    ncinfo()

prints information on the variables, dimension and attributes conatained in the file
"""
function ncinfo(fil::AbstractString)
    nc = haskey(currentNcFiles,abspath(fil)) ? currentNcFiles[abspath(fil)] : open(fil)
    return(nc)
end

iswritable(nc::NcFile) = (nc.omode & NC_WRITE) != zero(UInt16)

#High-level functions for writing data to a file
"""
    ncwrite(x::Array,fil::AbstractString,vname::AbstractString)

Writes the array `x` to the file `fil` and variable `vname`.

### Keyword arguments

* `start` Vector of length `ndim(v)` setting the starting index for writing for each dimension
* `count` Vector of length `ndim(v)` setting the count of values to be written along each dimension. The value -1 is treated as a special case to write all values from this dimension. This is usually inferred by the given array size.
"""
function ncwrite(x::Array,fil::AbstractString,vname::AbstractString;start=ones(Int,length(size(x))),count=[size(x)...])
    nc = haskey(currentNcFiles,abspath(fil)) ? currentNcFiles[abspath(fil)] : open(fil,mode=NC_WRITE)
    if (nc.omode==NC_NOWRITE)
        close(nc)
        println("reopening file in WRITE mode")
        open(fil,mode=NC_WRITE)
    end
    putvar(nc,vname,x,start=start,count=count)
end
ncwrite(x::Array,fil::AbstractString,vname::AbstractString,start::Array)=ncwrite(x,fil,vname,start=start)

"""
    ncgetatt(filename, varname, attname)

This reads a NetCDF attribute `attname` from the specified file and variable. To read global attributes, set varname to `Global`.
"""
function ncgetatt(fil::AbstractString,vname::AbstractString,att::AbstractString)
    nc= haskey(currentNcFiles,abspath(fil)) ? currentNcFiles[abspath(fil)] : open(fil)
    return ( haskey(nc.vars,vname) ? get(nc.vars[vname].atts,att,nothing) : get(nc.gatts,att,nothing) )
end

#High-level function for creating files and variables
#
# if the file does not exist, it will be created
# if the file already exists, the variable will be added to the file

function create_dim(nc,dim)
    nc_redef(nc)
    nc_def_dim(nc.ncid,dim.name,dim.dimlen,dima);
    dim.dimid=dima[1];
    nc.dim[dim.name]=dim;
end

function create_var(nc,v,mode)
    nc_redef(nc)

    v.dimids=Int32[v.dim[i].dimid for i=1:length(v.dim)]
    for i=1:v.ndim
        dumids[i] = v.dimids[v.ndim+1-i]
    end
    nc_def_var(nc.ncid,v.name,v.nctype,v.ndim,dumids,vara)
    v.varid=vara[1];
    if any(i->i>0,v.chunksize)
        for i=1:v.ndim
            chunk_sizea[i] = v.chunksize[i]
        end
        nc_def_var_chunking(nc.ncid, v.varid, NC_CHUNKED, chunk_sizea)
    end
    nc.vars[v.name] = v
    putatt(nc.ncid,v.varid,v.atts)
    setcompression(v,mode)
end

"""
    nccreate (filename, varname, dimensions ...)

Create a variable in an existing netcdf file or generates a new file. `filename` and `varname` are strings.
After that follows a list of dimensions. Each dimension entry starts with a dimension name (a String), and
may be followed by a dimension length, an array with dimension values or a Dict containing dimension attributes.
Then the next dimension is entered and so on. Have a look at examples/high.jl for an example use.

###Keyword arguments

- **atts** Dict of attribute names and values to be assigned to the variable created
- **gatts** Dict of attribute names and values to be written as global attributes
- **compress** Integer [0..9] setting the compression level of the file, only valid if mode=NC_NETCDF4
- **t** variable type, currently supported types are: const NC_BYTE, NC_CHAR, NC_SHORT, NC_INT, NC_FLOAT, NC_LONG, NC_DOUBLE
- **mode** file creation mode, only valid when new file is created, choose one of: NC_NETCDF4, NC_CLASSIC_MODEL, NC_64BIT_OFFSET
"""
function nccreate(fil::AbstractString,varname::AbstractString,dims...;atts::Dict=Dict{Any,Any}(),gatts::Dict=Dict{Any,Any}(),compress::Integer=-1,t::Union{DataType,Integer}=NC_DOUBLE,mode::UInt16=NC_NETCDF4,chunksize=(0,))
    # Checking dims argument for correctness
    dim = parsedimargs(dims)
    # Check chunksize
    chunksize = chunksize[1]==0 ? ntuple(i->0,length(dim)) : chunksize
    # create the NcVar object
    v = NcVar(varname,dim,atts=atts,compress=compress,t=t,chunksize=chunksize)
    # Test if the file already exists
    if isfile(fil)
        nc = haskey(currentNcFiles,abspath(fil)) ? currentNcFiles[abspath(fil)] : open(fil,mode=NC_WRITE)
        if nc.omode==NC_NOWRITE
            close(nc)
            println("reopening file in WRITE mode")
            open(fil,mode=NC_WRITE)
        end
        v.ncid = nc.ncid
        haskey(nc.vars,varname) && error("Variable $varname already exists in file $fil")
        # Check if dimensions exist, if not, create
        # Remember if dimension was created

        dcreate = falses(length(dim))
        for i=1:length(dim)
            if !haskey(nc.dim,dim[i].name)
                create_dim(nc,dim[i])
                v.dimids[i] = dim[i].dimid
                if length(dim[i].vals)>0
                    elt = eltype(dim[i].vals)
                    create_var(nc,NcVar{elt,1,jl2nc(elt)}(nc.ncid,0,1,length(dim[i].atts),jl2nc(elt),dim[i].name,[dim[i].dimid],[dim[i]],dim[i].atts,-1,(0,)),mode)
                end
                dcreate[i] = true
            else
                v.dimids[i] = nc.dim[dim[i].name].dimid
                v.dim[i] = nc.dim[dim[i].name]
                dcreate[i] = false
            end
        end

        # Create variable
        create_var(nc, v, mode)
        nc_enddef(nc)
        for i = 1:length(dim)
            if dcreate[i] & !isempty(dim[i].vals)
                ncwrite(dim[i].vals,fil,dim[i].name)
            end
        end
    else
        nc = create(fil,v,gatts=gatts,mode=mode | NC_NOCLOBBER)
        for d in dim
            !isempty(d.vals) && ncwrite(d.vals,fil,d.name)
        end
    end
    return v
end

#show{T<:Any,N}(io::IO,a::NcVar{T,N})=println(io,a.name)
#showcompact{T<:Any,N}(io::IO,a::NcVar{T,N})=println(io,a.name)
function show(io::IO,nc::NcFile)
    nrow,ncol=Base.displaysize(io)
    hline = repeat("-",ncol)
    l1=div(ncol,3)
    l2=2*l1
    println(io,"")
    println(io,"##### NetCDF File #####")
    println(io,"")
    println(io,nc.name)
    println(io,"")
    println(io,"##### Dimensions #####")
    println(io,"")
    println(io,tolen("Name",l2),tolen("Length",l1))
    println(io,hline)
    for d in nc.dim
        println(io,tolen(d[2].name,l2),tolen(d[2].unlim ? string("UNLIMITED (" ,d[2].dimlen," currently)") : d[2].dimlen,l1))
    end
    l1 = div(ncol,5)
    l2 = 2 * l1
    println(io,"")
    println(io,"##### Variables #####")
    println(io,"")
    println(io,tolen("Name",l2),tolen("Type",l1),tolen("Dimensions",l2))
    println(hline)
    for v in nc.vars
        s1 = string(tolen(v[2].name,l2))
        s2 = string(tolen(nctype2string[Int(v[2].nctype)],l1))
        s3 = ""
        for d in v[2].dim
            s3 = string(s3,d.name," ")
        end
        println(s1,s2,tolen(s3,l2))
    end
    l1 = div(ncol,4)
    l2 = 2 * l1
    println(io,"")
    println(io,"##### Attributes #####")
    println(io,"")
    println(io,tolen("Variable",l1),tolen("Name",l1),tolen("Value",l2))
    println(io,hline)
    for a in nc.gatts
        println(io,tolen("global",l1),tolen(a[1],l1),tolen(a[2],l2))
    end
    for v in nc.vars
        for a in v[2].atts
            println(io,tolen(v[2].name,l1),tolen(a[1],l1),tolen(a[2],l2))
        end
    end
end

tolen(s::Any, l::Number) = tolen(string(s), round(Int,l))
function tolen(s::AbstractString, l::Integer)
    ls = length(s)
    if ls<l
        return rpad(s,l)
    elseif ls>l
        return string(s[1:prevind(s,l-1)],"..")
    else
        return s
    end
end

end # Module


text:
using TensorCast
using Test

using StaticArrays

@testset "ex-@shape" begin include("shape.jl") end
@testset "@reduce" begin include("reduce.jl")  end
@testset "@cast"   begin include("casting.jl") end
@testset "@check!" begin include("check.jl")   end
@testset "@mul"    begin include("mul.jl")     end

@testset "helper functions" begin include("parse.jl") end
@testset "data functions"   begin include("cat.jl")   end

@testset "advertising" begin
    @testset "new readme" begin

        mat = (1:4)' .+ rand(2,4)
        @cast rows[r][c] := mat[r,c]
        @cast cols[♜][🚣] := rows[🚣][♜]  # unicode 👍
        @reduce sum_r[c] := sum(r) mat[r,c]
        @test sum_r == sum(rows) # true

        B = rand(2*5, 3)
        @cast A[i,j,k] := B[(i,k),j]  i:2
        @test size(A) == (2,3,5)
        @cast A[i,j,k] = B[(i,k),j];

        imgs = [ rand(8,8) for i=1:16 ];

        @cast G[(i,I), (j,J)] := imgs[(I,J)][i,j] J:4
        @cast G[ i\I,   j\J ] = imgs[ I\J ][i,j] # in-place

        G = rand(16,32);

        @reduce H[a, b] := maximum(α,β)  G[α\a, β\b]  α:2,β:2
        @test size(G) == 2 .* size(H)

        @reduce H4[a, b] := maximum(α:4,β:4)  G[α\a, β\b]
        @test size(G) == 4 .* size(H4)

        W = randn(2,3,5,7);
        @cast Z[_,i,_,k] := W[2,k,4,i]  # equivalent to Z[1,i,1,k] on left
        @test size(Z) == (1,7,1,3)

        B = [ i .* ones(2,2) for i=1:4 ]
        @reduce A[μ,ν,J] := prod(i:2) B[(i,J)][μ,ν]
        @test size(A) == (2, 2, 2)

    end
    @testset "old readme" begin

        B = rand(3,4,5);
        @cast A[(i,j),k] := B[i,j,k]  # new matrix from tensor B

        B = rand(3*5,4);
        A = zeros(3,4,5);
        @cast A[i,j,k] = B[(i,k),j]   # write into an existing tensor A

        B = rand(3,4,5);
        @cast A[(i,j,k)] == B[i,j,k]  # reshaped view A = vec(B)

        B = [rand(3) for i=1:4];
        @cast A[i,j] := B[i][j]       # hcat a vector of vectors
        @test size(A) == (4,3)

        B = [rand(7) for i=1:3, k=1:4];
        A = zeros(3,7,4);
        @cast A[i,j,k] = B[i,k][j]    # write into A

        B = rand(2,3);
        @cast A[i][j] == B[j,i]       # create views A = collect(eachcol(B))

        B = [rand(3) for i=1:4];
        A = @cast [(i,j)] := B[j][i]  # vcat a vector of vectors
        @test size(A) == (12,)

        B = [rand(3,4,5,6) for i=1:7];
        A = @cast [(i,j),l][k,m] := B[i][j,k,l,m]; # glue then slice then reshape
        @test size(A) == (21,5)

        B = rand(2*5, 3);
        @cast A[i,j,k] := B[(i,k),j]  i:2  # could give (i:2, j:3, k:5)
        @test size(A) == (2,3,5)

        @cast A[i,j,k] := B[(i,k),j]  (i:2, j:3, k:5)
        @test size(A) == (2,3,5)

        @pretty @cast A[(i,j)] = B[i,j]
        # copyto!(A, B)

        @pretty @cast A[k][i,j] == B[i,(j,k)]  k:length(C)
        # begin
        #     local caterpillar = (size(B, 1), :, length(C))  # your animal may vary
        #     A = sliceview(reshape(B, (caterpillar...,)), (:, :, *))
        # end

        # using TestImages, ImageView, FileIO
        # V = testimage.(["mandril_gray", "cameraman", "lena_gray_512"])
        #
        # @cast M[i,(j,J)] := V[J][i,j]
        #
        # imshow(M)

        # using Flux, ImageView, FileIO, JuliennedArrays
        # imgs = Flux.Data.MNIST.images()[1:32] # vector of matrices
        #
        # @cast A[(i,I),(j,J)] := imgs[(I,J)][i,j] J:8 # eight columns
        #
        # imshow(A)

        M = rand(3,4)
        @cast S[i][j] == M[i,j]       # S = julienne(M, (*,:)) creates views, S[i] == M[i,:]
        @cast Z[i,j] := S[i][j]       # Z = align(S, (*,:)) makes a copy
        @test size(Z) == (3,4)

        B = [rand(2,3) for k=1:4, l=1:5];
        @cast A[i,j,k,l] := B[k,l][i,j]
        @test size(A) == (2,3,4,5)

        using StaticArrays
        M = rand(1:99, 2,3)

        @cast S[k]{i} == M[i,k]  i:2  # S = reinterpret(SVector{2,Int}, vec(M)) needs the 2
        @cast N[k,i] == S[k]{i}       # such slices can be reinterpreted back again

        M[1,2]=42; N[2,1]==42          # all views of the original matrix
        @test N[2,1]==42

    end
    @testset "string macros" begin

        A = rand(2,10); B = rand(10,10);

        reduce" C_ii := sum_k A_1k * log(A_2i * B_ik) "
        @test C isa Diagonal

    end
end


text:
#####################
# Correlation calculated as given in:
# Reference
# Biddle Consulting Group (2019) http://www.biddle.com/documents/bcg_comp_chapter2.pdf
using Statistics # library for the function mean(X), the mean value of the vector X.

# We can use any symbol for our functions.
# Let's us the mathematical symbols for  square root and sum.
√ = sqrt
∑ = sum

# Given the vectors,
X1 = [2.5, 3.6, 1.2, 0.8, 4.0, 3.4]
X2 = [1.2, 1.0, 1.8, 0.9, 3.0, 2.2]
X3 = [8.0, 15.0, 12.0, 6.0, 8.0, 10.0]

# We want to find  the correlations between the variable vectors:
# X1 and X2, X1 and X3, X2 and X3 using correlation
# coefficient approach.

# First we implement the funktion for sample standard deviation as:
"""
    S(X)
Compute the sample standard deviation of X

# Example
```julia-repl
julia> X1 = [2.5, 3.6, 1.2, 0.8, 4.0, 3.4]
6-element Array{Float64,1}:
 2.5
 3.6
 1.2
 0.8
 4.0
 3.4
julia> S(X1)
1.3272779161376365
```
"""
S(X)= √( ∑( map(i -> (X[i]-mean(X))^2, 1:length(X))) / (length(X)-1))

# Next we implement the biased estimate of covariance as:
"""
    S(X, Y)
Compute the biased estimate of covariance betweem X and Y

# Example
```julia-repl
julia> X1 = [2.5, 3.6, 1.2, 0.8, 4.0, 3.4]
6-element Array{Float64,1}:
 2.5
 3.6
 1.2
 0.8
 4.0
 3.4

 julia> X2 = [1.2, 1.0, 1.8, 0.9, 3.0, 2.2]
 6-element Array{Float64,1}:
  1.2
  1.0
  1.8
  0.9
  3.0
  2.2

 julia> S(X1, X2)
 0.5736666666666667
```
"""
S(X,Y)= ∑( map(i -> ((X[i]-mean(X))*(Y[i]-mean(Y))), 1:length(X))) / (length(X)-1)

ρ(X,Y)=S(X,Y)/(S(X)*S(Y))

print("\n\nCorrelation between X1 anx X2 is ", ρ(X1, X2))
print("\n\nCorrelation between X1 anx X3 is ", ρ(X1, X3))
print("\n\nCorrelation between X2 anx X3 is ", ρ(X2, X3))
print("\n\n")


text:
result = [6.83 205 6.83 6 4; 204 4 4.83 9.83 9; 2.83 2 7 11.9 17; 2 0 4 207 221; 2.83 NaN 5.66 209 413]
points = [0.0 0 0 0 2; 0 0 0 0 0; 0 0 0 0 0; 0 1 0 0 0; 0 0 0 0 0]
initial = [8.0 8 8 8 4; 8 8 8 8 8; 8 8 8 8 8; 0 0 8 8 8; 0 0 8 8 8]
friction = [1.0 200 1 1 1; 200 1 1 4 4; 1 1 4 4 4; 1 1 3 200 200; 1 Inf 3 200 4]

lpoints = repeat(points, 10, 10)
linitial = repeat(initial, 10, 10)
lfriction = repeat(friction, 10, 10)

llpoints = repeat(points, 100, 100)
llinitial = repeat(initial, 100, 100)
llfriction = repeat(friction, 100, 100)

lllpoints = repeat(points, 1000, 1000)
lllinitial = repeat(initial, 1000, 1000)
lllfriction = repeat(friction, 1000, 1000)

# @test result == spread(points, initial, friction)
spread(points, initial, friction, res=2);

x = [5, 2, 6, 8] .* 100
y = [2, 6, 7, 8] .* 100
points = zeros(1000, 1000)
points[CartesianIndex.(x, y)] .= 1

friction = ones(1000, 1000)
initial = zeros(1000, 1000)
friction[580:600, begin:450] .= Inf

spread2(points, initial, friction)


text:
# This file contains types and structures for productivity DEA model
"""
    AbstractProductivityDEAlModel
An abstract type representing a productivity DEA model.
"""
abstract type AbstractProductivityDEAModel end

"""
    nbos(model::AbstractProductivityDEAModel)
Return number of observations of a productivity DEA model.
# Examples
```jldoctest

```
"""
nobs(model::AbstractProductivityDEAModel) = model.n

"""
    ninputs(model::AbstractProductivityDEAModel)
Return number of inputs of a productivity DEA model.
## Examples
```jldoctest

```
"""
ninputs(model::AbstractProductivityDEAModel) = model.m

"""
    noutputs(model::AbstractProductivityDEAModel)
Return number of outputs of a productivity DEA model.
# Examples
```jldoctest

```
"""
noutputs(model::AbstractProductivityDEAModel) = model.s

"""
    nperiods(model::AbstractProductivityDEAModel)
Return number of time periods of a productivity DEA model.
# Examples
```jldoctest

```
"""
nperiods(model::AbstractProductivityDEAModel) = model.periods

"""
    prodchange(model::AbstractProductivityDEAModel)
Return productivity change of a productivity change DEA model.

# Optional Arguments
- `type=Prod`: component of productivity change to return.

Type specification:
- `:Prod`: productivity change.
- `:EC`: efficiency change.
- `:TC`: technological change.

# Examples
```jldoctest
julia> X = Array{Float64,3}(undef, 5, 1, 2);

julia> X[:, :, 1] = [2; 3; 5; 4; 4];

julia> X[:, :, 2] = [1; 2; 4; 3; 4];

julia> Y = Array{Float64,3}(undef, 5, 1, 2);

julia> Y[:, :, 1] = [1; 4; 6; 3; 5];

julia> Y[:, :, 2] = [1; 4; 6; 3; 3];

julia> mprod = malmquist(X, Y);

julia> prodchange(mprod)
5×1 Array{Float64,2}:
 2.0
 1.5
 1.25
 1.3333333333333333
 0.6000000000000001

julia> prodchange(mprod, :EC)
5×1 Array{Float64,2}:
 1.3333333333333333
 1.0
 0.8333333333333334
 0.8888888888888888
 0.4

julia> prodchange(mprod, :TC)
5×1 Array{Float64,2}:
 1.5
 1.5
 1.5
 1.5
 1.5

```
"""
function prodchange(model::AbstractProductivityDEAModel, type::Symbol = :Prod)::Matrix

    if type == :Prod
        return model.Prod
    end

    if type == :EC
        if isdefined(model, :EC)
            return model.EC
        end
    end

    if type == :TC
        if isdefined(model, :TC)
            return model.TC
        end
    end

    error(typeof(model), " has no productivity change component ", string(type))

end