text:
# HeaderSearchOptions
function GetResourceDir(x::HeaderSearchOptions)
    @check_ptrs x
    n = clang_HeaderSearchOptions_GetResourceDirLength(x)
    dir = Vector{Cuchar}(undef, n)
    clang_HeaderSearchOptions_GetResourceDir(x, dir, n)
    return String(dir)
end

function GetResourceDir(x::HeaderSearchOptions, dir::String)
    @check_ptrs x
    clang_HeaderSearchOptions_GetResourceDir(x, dir, length(dir))
    return nothing
end

function PrintStats(x::HeaderSearchOptions)
    @check_ptrs x
    return clang_HeaderSearchOptions_PrintStats(x)
end

ext: jl


text:
#TODO: Inflation-index the wealth distributions in figuresByCategory

##Graph types and constants
const COLORS = ["DodgerBlue", "OrangeRed", "Green",
  "Brown", "Black", "BlueViolet"]

const PlotContainer = Union{Plot,Gadfly.Compose.Context,Vector{Plot}} #holds graphs
const ASSET_YEARS = [2015, 2010, 2005, 2000, 1997]
const ASSET_YEARS_FULL = collect(1997:2015)
const WEALTH_FOCAL_FIELD = :adjnetassets

const RETURN_FOCAL_FIELD = :return
const RETURN_YEARS = ASSET_YEARS
const RETURN_YEARS_FULL = collect(2000:2015)

const BIN_ON_FIELDS = [:fisyr, :categorynamefiltered]

#const FIGURES_MIN_ASSETS = 1_500_000.

#contains the functions for writing the grpah by year
function wealthFiguresByYear(data::NCCSData; assetYears::Vector{Int} = ASSET_YEARS,
    outputPath::String = OUTPUT_PATH, focalField::Symbol=WEALTH_FOCAL_FIELD#=,
    figuresMinAssets::Float64 = FIGURES_MIN_ASSETS=#)

  local plotNames::Vector{String} = Vector{String}()
  local plots::Vector{PlotContainer} = Vector{PlotContainer}()
  local subdf::SubDataFrame
  #local presubdf::SubDataFrame =  view(data.df, data.df[focalField] .≥ figuresMinAssets,:)

  for y::Int ∈ assetYears #main wealth distribution
    subdf = view(data.df, data.df[:fisyr] .== y,:) #make a view for performance reasons

    push!(plotNames, "Wealth Distribution of Non-Profits ($y)")
    push!(plots, plot(subdf, x=:adjnetassets, color=:charitytype,
      Guide.title("Wealth Distribution of Non-Profits ($y)"),
      Guide.xlabel("Wealth"),Guide.ylabel("Density"),
      Scale.x_log10,
      Geom.histogram(bincount=50, density=true, position=:dodge)))

    push!(plotNames, "Wealth Distribution of Non-Profits ($y)")
    push!(plots, plot(subdf,
      x=focalField, color=:categorynamefiltered,
      Guide.title("Wealth Distribution of Non-Profits ($y)"),
      Guide.xlabel("Wealth"), Guide.ylabel("Amount"),  Scale.x_log10,
      Geom.line, Stat.histogram(bincount=50, density=false),
      Coord.cartesian(ymin=0.0, ymax = 1000.0)))

    push!(plotNames, "Wealth Distribution of Private Charities ($y)")
    push!(plots, plot(subdf[subdf[:charitytype] .== :pc,:],
      x=focalField, color=:categorynamefiltered,
      Guide.title("Wealth Distribution of Non-Profits ($y)"),
      Guide.xlabel("Wealth"), Guide.ylabel("Amount"),
      Scale.x_log10, Geom.line,
      Stat.histogram(bincount=25, density=false),
      Coord.cartesian(ymin=0.0, ymax = 1000.0)))

    push!(plotNames, "Wealth Distribution of Private Foundations ($y)")
    push!(plots, plot(subdf[subdf[:charitytype] .== :pf,:],
      x=focalField, color=:categorynamefiltered,
      Guide.title("Wealth Distribution of Non-Profits ($y)"),
      Guide.xlabel("Wealth"), Guide.ylabel("Amount"),
      Scale.x_log10, Geom.line,
      Stat.histogram(bincount=25, density=false),
      Coord.cartesian(ymin=0.0, ymax = 1000.0)))

    #=push!(plotNames, "Log Wealth Distribution of Non-Profits ($y)")
    push!(plots, plot(subdf,
    x=Symbol('l',focalfield), color=:charitytype,
      Guide.title("Log Wealth Distribution of Non-Profits ($y)"),
      Guide.xlabel("Wealth"),Guide.ylabel("Density"),
      Scale.x_log10,
      Geom.histogram(bincount=50, density=true)))=#
  end

  for i ∈ 1:length(plots) #write the graphs
    draw(SVG("$outputPath\\$(plotNames[i]).svg", 9inch, 7inch), plots[i])
    #println("Graph $(plotNames[i]) written.")
  end

  println("Cross-sectional Wealth Graphs by Year Drawn")
  return nothing
end

#contains the functions for writing the grpah by year
function returnFiguresByYear(data::NCCSData; returnYears::Vector{Int} = RETURN_YEARS,
    outputPath::String = OUTPUT_PATH, focalField::Symbol=RETURN_FOCAL_FIELD)

  local plotNames::Vector{String} = Vector{String}()
  local plots::Vector{PlotContainer} = Vector{PlotContainer}()
  local subdf::SubDataFrame
  local presubdf::SubDataFrame = view(data.df, (!ismissing).(data.df[focalField]))
  presubdf = view(presubdf, (!ismissing).(presubdf[:categorynamefiltered]))

  for y::Int ∈ returnYears #main wealth distribution
    subdf = view(presubdf, presubdf[:fisyr] .== y,:) #make a view for performance reasons

    #println("\n####\nYear: $y; rows: $(size(subdf,1)),
    #  rowspf: rows: $(sum(subdf[:charitytype] .== :pf)),
    #  rowspc: rows: $(sum(subdf[:charitytype] .== :pc)))")

    push!(plotNames, "Return Distribution of Non-Profits ($y) by Type")
    push!(plots, plot(subdf, x=focalField, color=:charitytype,
      Guide.title("Return Distribution of Non-Profits ($y) by Type"),
      Guide.xlabel("Return"),Guide.ylabel("Density"),
      Geom.histogram(bincount=25, density=true, position=:dodge),
      Coord.cartesian(xmin=-0.5, xmax=0.5)))

    push!(plotNames, "Return Distribution of Non-Profits ($y) by Category")
    push!(plots, plot(subdf,
      x=focalField, color=:categorynamefiltered,
      Guide.title("Return Distribution of Non-Profits ($y) by Category"),
      Guide.xlabel("Return"), Guide.ylabel("Amount"),
      Geom.line, Stat.histogram(bincount=25, density=false),
      Coord.cartesian(ymin=0.0, ymax = 1000.0, xmin=-0.5, xmax=0.5)))

    push!(plotNames, "Return Distribution of Private Charities ($y)")
    push!(plots, plot(subdf[subdf[:charitytype] .== :pc,:],
      x=focalField, color=:categorynamefiltered,
      Guide.title("Return Distribution of Non-Profits ($y)"),
      Guide.xlabel("Return"), Guide.ylabel("Amount"),
      Geom.line,
      Stat.histogram(bincount=25, density=false),
      Coord.cartesian(ymin=0.0, ymax = 1000.0, xmin=-0.5, xmax=0.5)))

    push!(plotNames, "Return Distribution of Private Foundations ($y)")
    push!(plots, plot(subdf[subdf[:charitytype] .== :pf,:],
      x=focalField, color=:categorynamefiltered,
      Guide.title("Return Distribution of Non-Profits ($y)"),
      Guide.xlabel("Return"), Guide.ylabel("Amount"),
      Geom.line,
      Stat.histogram(bincount=25, density=false),
      Coord.cartesian(ymin=0.0, ymax = 1000.0, xmin=-0.5, xmax=0.5)))

    #=push!(plotNames, "Log Wealth Distribution of Non-Profits ($y)")
    push!(plots, plot(subdf,
    x=Symbol('l',focalfield), color=:charitytype,
      Guide.title("Log Wealth Distribution of Non-Profits ($y)"),
      Guide.xlabel("Wealth"),Guide.ylabel("Density"),
      Scale.x_log10,
      Geom.histogram(bincount=50, density=true)))=#
  end

  for i ∈ 1:length(plots) #write the graphs
    draw(SVG("$outputPath\\$(plotNames[i]).svg", 9inch, 7inch), plots[i])
    #println("Graph $(plotNames[i]) written.")
  end

  println("Cross-sectional Return Graphs by Category Drawn")
  return nothing

end

#contains the functions for writing the graphs by category
function wealthFiguresByCategory(data::NCCSData; assetYears::Vector{Int} = ASSET_YEARS,
    outputPath::String = OUTPUT_PATH,
    minPointsPerCategory::Int = MIN_POINTS_PER_CATEGORY,
    focalField::Symbol=WEALTH_FOCAL_FIELD#=,
    figuresMinAssets::Float64 = FIGURES_MIN_ASSETS=#)

  local plotNames::Vector{String} = Vector{String}()
  local plots::Vector{PlotContainer} = Vector{PlotContainer}()
  local subdf::SubDataFrame
  local presubdf::SubDataFrame
  local categoriesUsed::Vector{Symbol}

  #get only the categories with valid values
  presubdf = view(data.df, ((y::Int)-> y ∈ assetYears).(data.df[:fisyr]),:)
  #presubdf = view(presubdf, presubdf[focalField] .≥ figuresMinAssets)

  push!(plotNames, "Wealth Distribution of Non-Profits by Year")
  push!(plots, plot(presubdf,
    x=focalField, color=:fisyr,
    Guide.title("Wealth of Non-Profits by Year"),
    Guide.xlabel("Wealth"), Guide.ylabel("Density"),
    Scale.x_log10, Geom.line,
    Stat.histogram(bincount=25, density=true),
    Coord.cartesian(ymin=0.0, ymax = 2., xmin=6.)),)

  push!(plotNames, "Wealth Distribution of Public Charities by Year")
  push!(plots, plot(presubdf[presubdf[:charitytype] .== :pc, :],
    x=focalField, color=:fisyr,
    Guide.title("Wealth of Public Charities by Year"),
    Guide.xlabel("Wealth"), Guide.ylabel("Density"),
    Scale.x_log10, Geom.line,
    Stat.histogram(bincount=25, density=true),
    Coord.cartesian(ymin=0.0, ymax = 2., xmin=6.)))

  push!(plotNames, "Wealth Distribution of Private Foundations by Year")
  push!(plots, plot(presubdf[presubdf[:charitytype] .== :pf,:],
    x=focalField, color=:fisyr,
    Guide.title("Wealth of Private Foundations by Year"),
    Guide.xlabel("Wealth"), Guide.ylabel("Density"),
    Scale.x_log10, Geom.line,
    Stat.histogram(bincount=25, density=true),
    Coord.cartesian(ymin=0.0, ymax = 2., xmin=6.)))

  #filter out funds with no category
  presubdf = view(presubdf, (!ismissing).(presubdf[:categorynamefiltered]),:)
  categoriesUsed =
    unique(data.df[(!ismissing).(data.df[:categorynamefiltered]), :categorynamefiltered])

  for c::MSymbol ∈ categoriesUsed

    #filter to the category and exclude non-studied asset years
    subdf = view(presubdf, Vector{Bool}(presubdf[:categorynamefiltered] .== c), :)

    #we need to make sure we have a viable sample
    if size(subdf,1) ≥ minPointsPerCategory
      push!(plotNames, "categories\\Wealth Distribution of Non-Profit ($c)")
      push!(plots, plot(subdf,
        x=focalField, color=:fisyr,
        Guide.title("Wealth Distribution of Non-Profits ($c)"),
        Guide.xlabel("Wealth"), Guide.ylabel("Amount"),
        Scale.x_log10, Geom.line,
        Stat.histogram(bincount=25, density=false),
        Coord.cartesian(ymin=0.0, ymax = 100.0)))
    end

    if size(subdf[subdf[:charitytype] .== :pc,:],1) ≥ minPointsPerCategory
      push!(plotNames, "categories\\Wealth Distribution of Public Charities ($c)")
      push!(plots, plot(subdf[subdf[:charitytype] .== :pc,:],
        x=focalField, color=:fisyr,
        Guide.title("Wealth Distribution of Non-Profits ($c)"),
        Guide.xlabel("Wealth"), Guide.ylabel("Amount"),
        Scale.x_log10, Geom.line,
        Stat.histogram(bincount=25, density=false)#=,
        Coord.cartesian(ymin=0.0, ymax = 100.0))=#))
    end

    if size(subdf[subdf[:charitytype] .== :pf,:],1) ≥ minPointsPerCategory
      push!(plotNames, "categories\\Wealth Distribution of Private Foundations ($c)")
      push!(plots, plot(subdf[subdf[:charitytype] .== :pf,:],
        x=:adjnetassets, color=:fisyr,
        Guide.title("Wealth Distribution of Non-Profits ($c)"),
        Guide.xlabel("Wealth"), Guide.ylabel("Amount"),
        Scale.x_log10, Geom.line,
        Stat.histogram(bincount=25, density=false)#=,
        Coord.cartesian(ymin=0.0, ymax = 100.0))=#))
    end
  end

  for i ∈ 1:length(plots) #write the graphs
    draw(SVG("$outputPath\\$(plotNames[i]).svg", 9inch, 7inch), plots[i])
    #println("Graph $(plotNames[i]) written.")
  end

  println("Cross-sectional Wealth Graphs by Category Drawn.")

  return nothing
end

#contains the functions for writing the graphs by category
function returnFiguresByCategory(data::NCCSData;
    returnYears::Vector{Int} = RETURN_YEARS,
    outputPath::String = OUTPUT_PATH,
    minPointsPerCategory::Int = MIN_POINTS_PER_CATEGORY,
    focalField::Symbol=RETURN_FOCAL_FIELD)

  local plotNames::Vector{String} = Vector{String}()
  local plots::Vector{PlotContainer} = Vector{PlotContainer}()
  local subdf::SubDataFrame
  local presubdf::SubDataFrame
  local categoriesUsed::Vector{Symbol}

  #get only the categories with valid values
  presubdf = view(data.df, ((y::Int)-> y ∈ returnYears).(data.df[:fisyr]),:)
  presubdf = view(presubdf, (!ismissing).(presubdf[:return]))

  push!(plotNames, "Return Distribution of Non-Profits by Year")
  push!(plots, plot(presubdf,
    x=focalField, color=:fisyr,
    Guide.title("Return of Non-Profits by Year"),
    Guide.xlabel("Return"), Guide.ylabel("Amount"),
     Geom.line,
    Stat.histogram(bincount=25, density=true),
    Coord.cartesian(ymin=0.0, ymax = 2., xmin=-0.5, xmax=0.5)))

  push!(plotNames, "Return Distribution of Public Charities by Year")
  push!(plots, plot(presubdf[presubdf[:charitytype] .== :pc, :],
    x=focalField, color=:fisyr,
    Guide.title("Return of Public Charities by Year"),
    Guide.xlabel("Return"), Guide.ylabel("Amount"),
     Geom.line,
    Stat.histogram(bincount=25, density=true),
    Coord.cartesian(ymin=0.0, ymax = 2., xmin=-0.5, xmax=0.5)))

  push!(plotNames, "Return Distribution of Private Foundations by Year")
  push!(plots, plot(presubdf[presubdf[:charitytype] .== :pf,:],
    x=focalField, color=:fisyr,
    Guide.title("Return of Private Foundations by Year"),
    Guide.xlabel("Return"), Guide.ylabel("Amount"),
     Geom.line,
    Stat.histogram(bincount=25, density=true),
    Coord.cartesian(ymin=0.0, ymax = 2., xmin=-0.5, xmax=0.5)))

  #filter out funds with no category
  presubdf = view(presubdf, (!ismissing).(presubdf[:categorynamefiltered]),:)
  categoriesUsed =
    unique(data.df[(!ismissing).(data.df[:categorynamefiltered]), :categorynamefiltered])

  for c::MSymbol ∈ categoriesUsed

    #filter to the category and exclude non-studied asset years
    subdf = view(presubdf, Vector{Bool}(presubdf[:categorynamefiltered] .== c), :)

    #we need to make sure we have a viable sample
    if size(subdf,1) ≥ minPointsPerCategory
      push!(plotNames, "categories\\Return Distribution of Non-Profit ($c)")
      push!(plots, plot(subdf,
        x=focalField, color=:fisyr,
        Guide.title("Return Distribution of Non-Profits ($c)"),
        Guide.xlabel("Return"), Guide.ylabel("Amount"),
         Geom.line,
        Stat.histogram(bincount=25, density=false),
        Coord.cartesian(xmin=-0.5, xmax=0.5)))
    end

    if size(subdf[subdf[:charitytype] .== :pc,:],1) ≥ minPointsPerCategory
      push!(plotNames, "categories\\Return Distribution of Public Charities ($c)")
      push!(plots, plot(subdf[subdf[:charitytype] .== :pc,:],
        x=focalField, color=:fisyr,
        Guide.title("Return Distribution of Non-Profits ($c)"),
        Guide.xlabel("Return"), Guide.ylabel("Amount"),
         Geom.line,
        Stat.histogram(bincount=25, density=false),
        Coord.cartesian(xmin=-0.5, xmax=0.5)))
    end

    if size(subdf[subdf[:charitytype] .== :pf,:],1) ≥ minPointsPerCategory
      push!(plotNames, "categories\\Return Distribution of Private Foundations ($c)")
      push!(plots, plot(subdf[subdf[:charitytype] .== :pf,:],
        x=:adjnetassets, color=:fisyr,
        Guide.title("Return Distribution of Non-Profits ($c)"),
        Guide.xlabel("Return"), Guide.ylabel("Amount"),
         Geom.line,
        Stat.histogram(bincount=25, density=false),
        Coord.cartesian(xmin=-0.5, xmax=0.5)))
    end
  end

  for i ∈ 1:length(plots) #write the graphs
    draw(SVG("$outputPath\\$(plotNames[i]).svg", 9inch, 7inch), plots[i])
    #println("Graph $(plotNames[i]) written.")
  end

  println("Cross-sectional Graphs by Category Drawn.")

  return nothing
end

function binForFigures(data::NCCSData;
  returnYears::Vector{Int} = RETURN_YEARS_FULL,
  binOnFields::Vector{Symbol}=BIN_ON_FIELDS,
  focalField::Symbol=RETURN_FOCAL_FIELD)::DataFrame

  #pre-filter the dataframe
  local sdf::SubDataFrame
  local ssdf::SubDataFrame
  local presubdf::SubDataFrame = view(data.df, (y::Int -> y ∈ returnYears).(data.df[:fisyr]), :)
  presubdf = view(presubdf, (!ismissing).(presubdf[focalField]), :)

  for f ∈ binOnFields #get rid of missing values
    presubdf = view(presubdf, (!ismissing).(presubdf[f]), :)
  end

  outdf::DataFrame = unique(presubdf[binOnFields])
  nrows::Int = size(outdf, 1)

  local N::Vector{MInt} = Vector{MInt}(undef, nrows)
  local mean::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  local stddev::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  #local meanm1::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  #local meanp1::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  local p5::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  local p25::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  local p50::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  local p75::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)
  local p95::Vector{MFloat64} = Vector{MFloat64}(undef, nrows)

  sort!(outdf, binOnFields)

  local firstBinValue::Any = outdf[1, binOnFields[1]]
  local sdf = view(presubdf, presubdf[binOnFields[1]] .== firstBinValue, :)
  for i::Int ∈ 1:nrows

    #The below snippit provides an initial level of filtering for performance purposes
    if firstBinValue ≠ outdf[i,binOnFields[1]]
      firstBinValue = outdf[i,binOnFields[1]]
      sdf = view(presubdf, presubdf[binOnFields[1]] .== firstBinValue, :)
    end

    if length(binOnFields) > 1
      for f::Symbol ∈ binOnFields[2:end] #form the bin
        ssdf = view(presubdf, sdf[f] .== outdf[i,f], :)
      end
    else
      ssdf=sdf
    end

    focal::Vector = ssdf[focalField]

    #compute the appropriate statistics
    N[i] = length(focal)

    if N[i] .≥ 10 #NOTE: this is a hardcoded cutoff
      mean[i] = StatsBase.mean(focal)
      stddev[i] = StatsBase.std(focal)
      p5[i] = StatsBase.quantile(focal, .05)
      p25[i] = StatsBase.quantile(focal, .25)
      p50[i] = StatsBase.quantile(focal, 0.5)
      p75[i] = StatsBase.quantile(focal, 0.75)
      p95[i] = StatsBase.quantile(focal, 0.95)
    end

  end

  #make the dataframe
  outdf[:N] = N
  outdf[:mean] = mean
  outdf[:stddev] = stddev
  outdf[:meanm1] = outdf[:mean] .- outdf[:stddev]
  outdf[:meanp1] = outdf[:mean] .+ outdf[:stddev]
  outdf[:p5] = p5
  outdf[:p25] = p25
  outdf[:p50] = p50
  outdf[:p75] = p75
  outdf[:p95] = p95

  return outdf

end

#contains the functions for writing the graphs by category
function returnFiguresBinned(data::NCCSData;
    returnYears::Vector{Int} = RETURN_YEARS_FULL,
    outputPath::String = OUTPUT_PATH,
    focalField::Symbol=RETURN_FOCAL_FIELD)

  local aggdf::DataFrame = binForFigures(data, returnYears=returnYears,
    focalField=focalField, binOnFields = [:fisyr, :categorynamefiltered])

  local plotNames::Vector{String} = Vector{String}()
  local plots::Vector{PlotContainer} = Vector{PlotContainer}()

  aggdf = aggdf[((!ismissing).(aggdf[:categorynamefiltered])) .& ((!ismissing).(aggdf[:mean])), :]

  sort!(aggdf, [:fisyr, :categorynamefiltered])
  push!(plotNames, "Mean Returns by Category")
  push!(plots, plot(aggdf,
    x=:categorynamefiltered, y=:mean, ymin = :meanm1, ymax = :meanp1,
    Stat.x_jitter(range=0.5), color=:fisyr,
    Guide.title("Mean Returns by Category"),
    Guide.xlabel("Category"), Guide.ylabel("Return"),
    Geom.point, Geom.errorbar
    #Stat.histogram(bincount=50, density=true),
    #Coord.cartesian(ymin=0.0, ymax = 3.)
  ))

  push!(plotNames, "Mean Returns by Year")
  push!(plots, plot(aggdf,
    x=:fisyr, y=:mean, ymin =:meanm1, ymax = :meanp1,
    Stat.x_jitter(range=0.5), color=:categorynamefiltered,
    Guide.title("Mean Returns by Year"),
    Guide.xlabel("Year"), Guide.ylabel("Return"),
    Geom.point, Geom.errorbar,
    #Stat.histogram(bincount=50, density=true),
    Coord.cartesian(xmin=1999, xmax=2016)))

  sort!(aggdf, [:fisyr, :categorynamefiltered])
  push!(plotNames, "Median Returns by Category")
  push!(plots, plot(aggdf,
    x=:categorynamefiltered, y=:p50, ymin = :p25, ymax = :p75,
    Stat.x_jitter(range=0.5), color=:fisyr,
    Guide.title("Median Returns by Category"),
    Guide.xlabel("Category"), Guide.ylabel("Return"),
    Geom.point, Geom.errorbar
    #Stat.histogram(bincount=50, density=true),
    #Coord.cartesian(ymin=0.0, ymax = 3.)
  ))

  push!(plotNames, "Median Returns by Year")
  push!(plots, plot(aggdf,
    x=:fisyr, y=:p50, ymin = :p25, ymax = :p75,
    Stat.x_jitter(range=0.5), color=:categorynamefiltered,
    Guide.title("Median Returns by Year"),
    Guide.xlabel("Year"), Guide.ylabel("Return"),
    Geom.point, Geom.errorbar,
    #Stat.histogram(bincount=50, density=true),
    Coord.cartesian(xmin=1999, xmax = 2016)
  ))

  for i ∈ 1:length(plots) #write the graphs
    draw(SVG("$outputPath\\$(plotNames[i]).svg", 9inch, 7inch), plots[i])
    #println("Graph $(plotNames[i]) written.")
  end

  println("Binned return graphs drawn.")

  return nothing
end

function makeDescriptiveFigures(data::NCCSData; assetYears::Vector{Int} = ASSET_YEARS)::Nothing

  Gadfly.push_theme(:default)

  futures::Vector{Future} = Vector{Future}()

  #push!(futures, @spawn wealthFiguresByYear(data))
  #push!(futures, @spawn wealthFiguresByCategory(data))
  #push!(futures, @spawn returnFiguresByYear(data))
  #push!(futures, @spawn returnFiguresByCategory(data))
  push!(futures, @spawn returnFiguresBinned(data))

  (fetch).(futures)

  return nothing
end

#launching point for the descriptive output
function makeDescriptive(;refreshFilter::Bool = true,
    refreshDescriptiveFigures::Bool = true,
    refreshDescriptiveTables::Bool = true, writePrimaryCSV::Bool = false)

    data::NCCSData = constructPrimaryOutput(refreshFilter=refreshFilter,
      writePrimaryCSV=writePrimaryCSV)

    refreshDescriptiveFigures ? makeDescriptiveFigures(data) : nothing
    refreshDescriptiveTables ? makeDescriptiveTables(data) : nothing

    return nothing
end

max_stars_repo_name: clintonTE/CCA

max_issues_repo_name: clintonTE/CCA

max_forks_repo_name: clintonTE/CCA


text:
"""
    𝑛(v::Vector{T}) where T<:Number

Scales a length-``n`` vector ``v``  such that the last component
of the vector is one, provided that the last component is not zero. If the last
component is zero then the vector is left unchanged.

# Details
Suppose the length-``n`` vector ``v`` represents the homogeneous coordinates  of
a point in a projective space. The corresponding Cartesian coordinates  usually
just the first ``n-1`` numbers of homogeneous coordinates divided by  the last
component. So if the last component is one, then the first  ``n-1`` homogeneous
coordinates can be interpreted as Cartesian.  The exceptional case is when the
last component of the homogenenous coordinates is zero. These homogeneous
coordinates are associated with so-called *points at infinity* and have no
Cartesian counterparts.

# Example
```julia
h = [4, 4 , 2]
c = 𝑛(h)

3-element Array{Float64,1}:
 2.0
 2.0
 1.0
```

"""
function 𝑛(v::AbstractArray)
    if v[end] != 0 && v[end] != 1
        v .= v ./ v[end]
    else
        v
    end
end

function 𝑛(v::SVector)
    if v[end] != 0 && v[end] != 1
        v / v[end]
    else
        v
    end
end

function hom⁻¹(v::SVector)
    if isapprox(v[end], 0.0; atol = 1e-14)
        pop(v)
    else
        pop(v / v[end])
    end
end

function hom(v::SVector)
    push(v,1)
end

function ∂hom⁻¹(𝐧::SVector)
    k = length(𝐧)
    𝐞ₖ = push(zeros(SVector{k-1}),1.0)
    𝐈 = SMatrix{3,3}(1.0I)
    1/𝐧[k]*𝐈 - 1/𝐧[k]^2 * 𝐧 * 𝐞ₖ'
end

function ∂𝑛(𝐧::AbstractArray)
    k = length(𝐧)
    𝐞ₖ = fill(0.0,(k,1))
    𝐞ₖ[k] = 1
    1/𝐧[k]*Matrix{Float64}(I, k, k) - 1/𝐧[k]^2 * 𝐧 * 𝐞ₖ'
end

function smallest_eigenpair(A::AbstractArray)
    F = eigen(A)
    index = argmin(F.values)
    (F.values[index], F.vectors[:,index])
end

function smallest_eigenpair(A::AbstractArray,B::AbstractArray)
    F = eigfact(A,B)
    index = indmin(F[:values])
    (F[:values][index], F[:vectors][:,index])
end

function vec2antisym(v::AbstractArray)
    if length(v) != 3
         throw(ArgumentError("The operation is only defined for a length-3 vector."))
    end
    𝐒  = @SMatrix [   0  -v[3]    v[2] ;
                    v[3]    0    -v[1] ;
                   -v[2]  v[1]      0]

end


text:
# misc utils for reading input files

"""
  Prints an options dictionary to the specified IO, sorting the keys first

  **Inputs**

   * f: IO to print to
   * opts: options dictionary
"""
function printOpts(f::IO, opts::Dict)

  kvals = sort(collect(keys(opts)))

  println(f, "Options dictionary has ", length(kvals), " keys:")
  for k in kvals
    println(f, "  ", k, " => ", opts[k])
  end

  return nothing
end

"""
  Method that prints to STDOUT

  **Inputs**

   * opts: options dictionary
"""
function printOpts(opts::Dict)

  printOpts(STDOUT, opts)
end


text:
using MyExample
using Test

@testset "MyExample.jl" begin
    #2x + 3y
    @test my_f(2, 1) == 7
    @test my_f(2, 3) == 13
    @test my_f(1, 3) == 11
end

@testset "Derivative Tests" begin
    @test derivative_of_my_f(2, 1) == 2
end


text:
__precompile__()

module LLVMTools
@reexport using SIMD
import SIMD:llvmins, llvmwrap, llvmconst, llvmtype, suffix
export va_start, va_end, va_copy, prefetch, pcmarker, bitreverse, ctlz,
       cttz, gcroot, gcread, gcwrite, returnaddress, frameaddress, stacksave,
       stackrestore, readcyclecounter, clear_cache, bitreverse, ctpop

# TODO: Clean up with e.g. ccall("llvm.clear_cache", llvmcall,
#                                Nothing, (Ptr{Nothing}, Ptr{Nothing},
#                                x, y)

# Variable Argument Handling Intrinsics
# https://llvm.org/docs/LangRef.html#variable-argument-handling-intrinsics
@inline function va_start(arglist::Ptr{T}) where T
    Base.llvmcall(("declare void @llvm.va_start(i8*)",
                   """
                   %ptr = inttoptr i64 %0 to i8*
                   call void @llvm.va_start(i8* %ptr)
                   ret void
                   """),
                  Nothing, Tuple{UInt64},
                  UInt64(arglist))
end

@inline function va_end(arglist::Ptr{T}) where T
    Base.llvmcall(("declare void @llvm.va_end(i8*)",
                   """
                   %ptr = inttoptr i64 %0 to i8*
                   call void @llvm.va_end(i8* %ptr)
                   ret void
                   """),
                  Nothing, Tuple{UInt64},
                  UInt64(arglist))
end

@inline function va_end(destarglist::Ptr{T}, srcarglist::Ptr{T}) where T
    Base.llvmcall(("declare void @llvm.va_copy(i8*)",
                   """
                   %ptr1 = inttoptr i64 %0 to i8*
                   %ptr2 = inttoptr i64 %1 to i8*
                   call void @llvm.va_end(i8* %ptr1, i8* %ptr2)
                   ret void
                   """),
                  Nothing, Tuple{UInt64, UInt64},
                  UInt64(destarglist), UInt64(srcarglist))
end

# TODO: Accurate Garbage Collection Intrinsics
# https://llvm.org/docs/LangRef.html#accurate-garbage-collection-intrinsics

@inline function gcroot(ptrloc::Ptr{Ptr{S}}, metadata::Ptr{T}) where {S,T}
    Base.llvmcall(("declare void @llvm.gcroot(i8**, i8*)",
                   """
                   %ptr1 = inttoptr i64 %0 to i8**
                   %ptr2 = inttoptr i64 %1 to i8*
                   call void @llvm.gcroot(i8** %ptr1, i8* %ptr2)
                   ret void
                   """),
                  Nothing, Tuple{UInt64, UInt64},
                  UInt64(ptrloc), UInt64(metadata))
end

@inline function gcread(objptr::Ptr{S}, ptr::Ref{Ptr{T}}) where {S,T}
    Base.llvmcall(("declare void @llvm.gcread(i8*, i8**)",
                   """
                   %ptr1 = inttoptr i64 %0 to i8*
                   %ptr2 = inttoptr i64 %1 to i8**
                   %ptr = call i8* @llvm.gcread(i8* %ptr1, i8** %ptr2)
                   %3 = ptrtoint i8* %ptr to i64
                   ret i64 %3
                   """),
                  UInt64, Tuple{UInt64, UInt64},
                  UInt64(objptr), UInt64(ptr)) |> Ptr{Nothing}
end

@inline function gcwrite(p1::Ptr{T}, obj::Ptr{S}, p2::Ref{Ptr{Z}}) where {S,T,Z}
    Base.llvmcall(("declare void @llvm.gcwrite(i8*, i8*, i8**)",
                   """
                   %ptr1 = inttoptr i64 %0 to i8*
                   %ptr2 = inttoptr i64 %1 to i8*
                   %ptr3 = inttoptr i64 %2 to i8**
                   call void @llvm.gcwrite(i8* %ptr1, i8* %ptr2, i8** %ptr3)
                   ret void
                   """),
                  Nothing, Tuple{UInt64, UInt64, UInt64},
                  UInt64(p1), UInt64(obj), UInt64(p2))
end

# TODO: Code Generator Intrinsics
# https://llvm.org/docs/LangRef.html#id1374

@inline function returnaddress(::Type{Val{level}}) where level
    Base.llvmcall(("declare i8* @llvm.returnaddress(i32)",
                   """
                   %2 = call i8* @llvm.returnaddress(i32 %0)
                   %3 = ptrtoint i8* %2 to i64
                   ret i64 %3
                   """),
                   UInt64, Tuple{UInt32,},
                   UInt32(level)) |> Ptr{Nothing}
end

# LLVM ERROR: Program used external function 'llvm.addressofreturnaddress' which could not be resolved!
#@inline function addressofreturnaddress()
#    Base.llvmcall(("declare i8* @llvm.addressofreturnaddress()",
#                   """
#                   %1 = call i8* @llvm.addressofreturnaddress()
#                   %addr = ptrtoint i8* %1 to i64
#                   ret i64 %addr
#                   """),
#                  UInt64, Nothing) |> Ptr{Nothing}
#end

@inline function frameaddress(::Type{Val{level}}) where level
    Base.llvmcall(("declare i8* @llvm.frameaddress(i32)",
                   """
                   %2 = call i8* @llvm.frameaddress(i32 %0)
                   %3 = ptrtoint i8* %2 to i64
                   ret i64 %3
                   """),
                   UInt64, Tuple{UInt32,},
                   UInt32(level)) |> Ptr{Nothing}
end

# TODO: ‘llvm.localescape’ and ‘llvm.localrecover’ Intrinsics
# TODO: ‘llvm.read_register’ and ‘llvm.write_register’ Intrinsics

@inline function stacksave()
    Base.llvmcall(("declare i8* @llvm.stacksave()",
                   """
                   %1 = call i8* @llvm.stacksave()
                   %2 = ptrtoint i8* %1 to i64
                   ret i64 %2
                   """),
                  UInt64, Nothing) |> Ptr{Nothing}
end

@inline function stackrestore(ptr::Ptr{T}) where T
    Base.llvmcall(("declare void @llvm.stackrestore(i8*)",
                   """
                   %ptr = inttoptr i64 %0 to i8*
                   call void @llvm.stackrestore(i8* %ptr)
                   ret void
                   """),
                  Nothing, Tuple{UInt64},
                  UInt64(ptr))
end

#TODO: ‘llvm.get.dynamic.area.offset’ Intrinsic

@inline function prefetch(address::Ptr{T}, ::Type{Val{rw}}, ::Type{Val{locality}}, ::Type{Val{cachetype}}) where T
    Base.llvmcall((""" declare void @llvm.prefetch(i8*, i32, i32, i32) """,
                   """
                   %ptr = inttoptr i64 %0 to i8*
                   call void @llvm.prefetch(i8* %ptr, i32 %1, i32 %2, i32 %3)
                   ret void
                   """),
                  Nothing, Tuple{UInt64, Int32, Int32, Int32},
                  UInt64(address), Int32(rw), Int32(locality), Int32(cachetype))
end

@inline function pcmarker(id::Integer)
    Base.llvmcall((""" declare void @llvm.pcmarker(i32) """,
                   """
                   call void @llvm.pcmarker(i32 %0)
                   ret void
                   """),
                  Nothing, Tuple{Int32},
                  Int32(id))
end

@inline function readcyclecounter()
    Base.llvmcall(("declare i64 @llvm.readcyclecounter()",
                   """
                   %1 = call i64 @llvm.readcyclecounter()
                   ret i64 %1
                   """),
                  UInt64, Nothing)
end

@inline function clear_cache(p1::Ptr{T}, p2::Ptr{T}) where T
    Base.llvmcall(("declare void @llvm.clear_cache(i8*, i8*)",
                   """
                   %ptr1 = inttoptr i64 %0 to i8*
                   %ptr2 = inttoptr i64 %1 to i8*
                   call void @llvm.clear_cache(i8* %ptr1, i8* %ptr2)
                   ret void
                   """),
                  Nothing, Tuple{UInt64, UInt64},
                  UInt64(p1), UInt64(p1))
end

# TODO: Standard C Library Intrinsics
# https://llvm.org/docs/LangRef.html#standard-c-library-intrinsics

# TODO: Bit Manipulation Intrinsics
# https://llvm.org/docs/LangRef.html#bit-manipulation-intrinsics
@generated function bitreverse(id::T) where T<:Union{Int16, UInt16, Int32, UInt32, Int64, UInt64}
    typ = llvmtype(T)
    dec = "declare $typ @llvm.bitreverse.$typ($typ)"
    instrs = String[]
    push!(instrs, "%2 = call $typ @llvm.bitreverse.$typ($typ %0)")
    push!(instrs, "ret $typ %2")
    quote
        $(Expr(:meta, :inline))
        Base.llvmcall(($dec, $(join(instrs, "\n"))),
                       $T, Tuple{$T}, id)
    end
end

# Already in base Julia
#@generated function lbswap(id::T) where T<:Union{Int16, UInt16, Int32, UInt32, Int64, UInt64}
#    typ = llvmtype(T)
#    dec = "declare $typ @llvm.bswap.$typ($typ)"
#    instrs = String[]
#    push!(instrs, "%2 = call $typ @llvm.bswap.$typ($typ %0)")
#    push!(instrs, "ret $typ %2")
#    quote
#        $(Expr(:meta, :inline))
#        Base.llvmcall(($dec, $(join(instrs, "\n"))),
#                       $T, Tuple{$T}, id)
#    end
#end

@generated function ctpop(id::T) where T<:Union{Int8,UInt8,Int16,UInt16,Int32,UInt32,Int64,UInt64,UInt128,Int128}
    typ = llvmtype(T)
    dec = "declare $typ @llvm.ctpop.$typ($typ)"
    instrs = String[]
    push!(instrs, "%2 = call $typ @llvm.ctpop.$typ($typ %0)")
    push!(instrs, "ret $typ %2")
    quote
        $(Expr(:meta, :inline))
        Base.llvmcall(($dec, $(join(instrs, "\n"))),
                       $T, Tuple{$T}, id)
    end
end

const ctlz = Core.Intrinsics.ctlz_int
const cttz = Core.Intrinsics.cttz_int

# TODO: Arithmetic with Overflow Intrinsics
# https://llvm.org/docs/LangRef.html#arithmetic-with-overflow-intrinsics

# TODO: Specialised Arithmetic Intrinsics
# https://llvm.org/docs/LangRef.html#id1629

# TODO: Experimental Vector Reduction Intrinsics
# https://llvm.org/docs/LangRef.html#experimental-vector-reduction-intrinsics

# TODO: Half Precision Floating Point Intrinsics
# https://llvm.org/docs/LangRef.html#half-precision-floating-point-intrinsics

# TODO: ...
# https://llvm.org/docs/LangRef.html

end


text:
"""
Macros to make the Julia REPL more dynamic and improve developer productivity

See the documentation for ``@repl``.

# FastRepl Development
Use it on itself:
```julia
Base._require(Base.identify_package(Main, "FastRepl"))
module _FastRepl using FastRepl end
_FastRepl.FastRepl.@repl using FastRepl; register_auto()
```
"""
module FastRepl

import Base.show

export @repl, @reset, register_auto, unregister_auto

include("register.jl")
include("macros.jl")

struct NoLongerDefined end
const no_longer_defined = NoLongerDefined()
function Base.show(io::IO, ::MIME"text/plain", v::NoLongerDefined)
    Base.show(io, v)
end
function Base.show(io::IO, ::NoLongerDefined)
    print(io, "<no longer defined>")
end

function _fast_deinit_()
    unregister_auto(quiet=true)
end

end


text:
incx = abs(incx)
dy = iy - ix
dz = iz - ix
@inbounds for i = ix:incx:ix+(n-1)*incx
    x[i] = y[dy+i] + z[dz+i]*a
end


text:
#==============================================================================#
# CloudHSMV2.jl
#
# This file is generated from:
# https://github.com/aws/aws-sdk-js/blob/master/apis/cloudhsmv2-2017-04-28.normal.json
#==============================================================================#

__precompile__()

module CloudHSMV2

using AWSCore

"""
    using AWSSDK.CloudHSMV2.copy_backup_to_region
    copy_backup_to_region([::AWSConfig], arguments::Dict)
    copy_backup_to_region([::AWSConfig]; DestinationRegion=, BackupId=)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "CopyBackupToRegion", arguments::Dict)
    cloudhsmv2([::AWSConfig], "CopyBackupToRegion", DestinationRegion=, BackupId=)

# CopyBackupToRegion Operation

# Arguments

## `DestinationRegion = ::String` -- *Required*

## `BackupId = ::String` -- *Required*

# Returns

`CopyBackupToRegionResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/CopyBackupToRegion)
"""
@inline copy_backup_to_region(aws::AWSConfig=default_aws_config(); args...) = copy_backup_to_region(aws, args)

@inline copy_backup_to_region(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "CopyBackupToRegion", args)

@inline copy_backup_to_region(args) = copy_backup_to_region(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.create_cluster
    create_cluster([::AWSConfig], arguments::Dict)
    create_cluster([::AWSConfig]; SubnetIds=, HsmType=, <keyword arguments>)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "CreateCluster", arguments::Dict)
    cloudhsmv2([::AWSConfig], "CreateCluster", SubnetIds=, HsmType=, <keyword arguments>)

# CreateCluster Operation

Creates a new AWS CloudHSM cluster.

# Arguments

## `SubnetIds = [::String, ...]` -- *Required*
The identifiers (IDs) of the subnets where you are creating the cluster. You must specify at least one subnet. If you specify multiple subnets, they must meet the following criteria:

*   All subnets must be in the same virtual private cloud (VPC).

*   You can specify only one subnet per Availability Zone.

## `HsmType = ::String` -- *Required*
The type of HSM to use in the cluster. Currently the only allowed value is `hsm1.medium`.

## `SourceBackupId = ::String`
The identifier (ID) of the cluster backup to restore. Use this value to restore the cluster from a backup instead of creating a new cluster. To find the backup ID, use [DescribeBackups](@ref).

# Returns

`CreateClusterResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/CreateCluster)
"""
@inline create_cluster(aws::AWSConfig=default_aws_config(); args...) = create_cluster(aws, args)

@inline create_cluster(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "CreateCluster", args)

@inline create_cluster(args) = create_cluster(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.create_hsm
    create_hsm([::AWSConfig], arguments::Dict)
    create_hsm([::AWSConfig]; ClusterId=, AvailabilityZone=, <keyword arguments>)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "CreateHsm", arguments::Dict)
    cloudhsmv2([::AWSConfig], "CreateHsm", ClusterId=, AvailabilityZone=, <keyword arguments>)

# CreateHsm Operation

Creates a new hardware security module (HSM) in the specified AWS CloudHSM cluster.

# Arguments

## `ClusterId = ::String` -- *Required*
The identifier (ID) of the HSM's cluster. To find the cluster ID, use [DescribeClusters](@ref).

## `AvailabilityZone = ::String` -- *Required*
The Availability Zone where you are creating the HSM. To find the cluster's Availability Zones, use [DescribeClusters](@ref).

## `IpAddress = ::String`
The HSM's IP address. If you specify an IP address, use an available address from the subnet that maps to the Availability Zone where you are creating the HSM. If you don't specify an IP address, one is chosen for you from that subnet.

# Returns

`CreateHsmResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmInvalidRequestException`, `CloudHsmResourceNotFoundException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/CreateHsm)
"""
@inline create_hsm(aws::AWSConfig=default_aws_config(); args...) = create_hsm(aws, args)

@inline create_hsm(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "CreateHsm", args)

@inline create_hsm(args) = create_hsm(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.delete_cluster
    delete_cluster([::AWSConfig], arguments::Dict)
    delete_cluster([::AWSConfig]; ClusterId=)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "DeleteCluster", arguments::Dict)
    cloudhsmv2([::AWSConfig], "DeleteCluster", ClusterId=)

# DeleteCluster Operation

Deletes the specified AWS CloudHSM cluster. Before you can delete a cluster, you must delete all HSMs in the cluster. To see if the cluster contains any HSMs, use [DescribeClusters](@ref). To delete an HSM, use [DeleteHsm](@ref).

# Arguments

## `ClusterId = ::String` -- *Required*
The identifier (ID) of the cluster that you are deleting. To find the cluster ID, use [DescribeClusters](@ref).

# Returns

`DeleteClusterResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/DeleteCluster)
"""
@inline delete_cluster(aws::AWSConfig=default_aws_config(); args...) = delete_cluster(aws, args)

@inline delete_cluster(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "DeleteCluster", args)

@inline delete_cluster(args) = delete_cluster(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.delete_hsm
    delete_hsm([::AWSConfig], arguments::Dict)
    delete_hsm([::AWSConfig]; ClusterId=, <keyword arguments>)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "DeleteHsm", arguments::Dict)
    cloudhsmv2([::AWSConfig], "DeleteHsm", ClusterId=, <keyword arguments>)

# DeleteHsm Operation

Deletes the specified HSM. To specify an HSM, you can use its identifier (ID), the IP address of the HSM's elastic network interface (ENI), or the ID of the HSM's ENI. You need to specify only one of these values. To find these values, use [DescribeClusters](@ref).

# Arguments

## `ClusterId = ::String` -- *Required*
The identifier (ID) of the cluster that contains the HSM that you are deleting.

## `HsmId = ::String`
The identifier (ID) of the HSM that you are deleting.

## `EniId = ::String`
The identifier (ID) of the elastic network interface (ENI) of the HSM that you are deleting.

## `EniIp = ::String`
The IP address of the elastic network interface (ENI) of the HSM that you are deleting.

# Returns

`DeleteHsmResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/DeleteHsm)
"""
@inline delete_hsm(aws::AWSConfig=default_aws_config(); args...) = delete_hsm(aws, args)

@inline delete_hsm(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "DeleteHsm", args)

@inline delete_hsm(args) = delete_hsm(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.describe_backups
    describe_backups([::AWSConfig], arguments::Dict)
    describe_backups([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "DescribeBackups", arguments::Dict)
    cloudhsmv2([::AWSConfig], "DescribeBackups", <keyword arguments>)

# DescribeBackups Operation

Gets information about backups of AWS CloudHSM clusters.

This is a paginated operation, which means that each response might contain only a subset of all the backups. When the response contains only a subset of backups, it includes a `NextToken` value. Use this value in a subsequent `DescribeBackups` request to get more backups. When you receive a response with no `NextToken` (or an empty or null value), that means there are no more backups to get.

# Arguments

## `NextToken = ::String`
The `NextToken` value that you received in the previous response. Use this value to get more backups.

## `MaxResults = ::Int`
The maximum number of backups to return in the response. When there are more backups than the number you specify, the response contains a `NextToken` value.

## `Filters = ::Dict{String,String}`
One or more filters to limit the items returned in the response.

Use the `backupIds` filter to return only the specified backups. Specify backups by their backup identifier (ID).

Use the `clusterIds` filter to return only the backups for the specified clusters. Specify clusters by their cluster identifier (ID).

Use the `states` filter to return only backups that match the specified state.

## `SortAscending = ::Bool`

# Returns

`DescribeBackupsResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/DescribeBackups)
"""
@inline describe_backups(aws::AWSConfig=default_aws_config(); args...) = describe_backups(aws, args)

@inline describe_backups(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "DescribeBackups", args)

@inline describe_backups(args) = describe_backups(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.describe_clusters
    describe_clusters([::AWSConfig], arguments::Dict)
    describe_clusters([::AWSConfig]; <keyword arguments>)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "DescribeClusters", arguments::Dict)
    cloudhsmv2([::AWSConfig], "DescribeClusters", <keyword arguments>)

# DescribeClusters Operation

Gets information about AWS CloudHSM clusters.

This is a paginated operation, which means that each response might contain only a subset of all the clusters. When the response contains only a subset of clusters, it includes a `NextToken` value. Use this value in a subsequent `DescribeClusters` request to get more clusters. When you receive a response with no `NextToken` (or an empty or null value), that means there are no more clusters to get.

# Arguments

## `Filters = ::Dict{String,String}`
One or more filters to limit the items returned in the response.

Use the `clusterIds` filter to return only the specified clusters. Specify clusters by their cluster identifier (ID).

Use the `vpcIds` filter to return only the clusters in the specified virtual private clouds (VPCs). Specify VPCs by their VPC identifier (ID).

Use the `states` filter to return only clusters that match the specified state.

## `NextToken = ::String`
The `NextToken` value that you received in the previous response. Use this value to get more clusters.

## `MaxResults = ::Int`
The maximum number of clusters to return in the response. When there are more clusters than the number you specify, the response contains a `NextToken` value.

# Returns

`DescribeClustersResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/DescribeClusters)
"""
@inline describe_clusters(aws::AWSConfig=default_aws_config(); args...) = describe_clusters(aws, args)

@inline describe_clusters(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "DescribeClusters", args)

@inline describe_clusters(args) = describe_clusters(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.initialize_cluster
    initialize_cluster([::AWSConfig], arguments::Dict)
    initialize_cluster([::AWSConfig]; ClusterId=, SignedCert=, TrustAnchor=)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "InitializeCluster", arguments::Dict)
    cloudhsmv2([::AWSConfig], "InitializeCluster", ClusterId=, SignedCert=, TrustAnchor=)

# InitializeCluster Operation

Claims an AWS CloudHSM cluster by submitting the cluster certificate issued by your issuing certificate authority (CA) and the CA's root certificate. Before you can claim a cluster, you must sign the cluster's certificate signing request (CSR) with your issuing CA. To get the cluster's CSR, use [DescribeClusters](@ref).

# Arguments

## `ClusterId = ::String` -- *Required*
The identifier (ID) of the cluster that you are claiming. To find the cluster ID, use [DescribeClusters](@ref).

## `SignedCert = ::String` -- *Required*
The cluster certificate issued (signed) by your issuing certificate authority (CA). The certificate must be in PEM format and can contain a maximum of 5000 characters.

## `TrustAnchor = ::String` -- *Required*
The issuing certificate of the issuing certificate authority (CA) that issued (signed) the cluster certificate. This can be a root (self-signed) certificate or a certificate chain that begins with the certificate that issued the cluster certificate and ends with a root certificate. The certificate or certificate chain must be in PEM format and can contain a maximum of 5000 characters.

# Returns

`InitializeClusterResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/InitializeCluster)
"""
@inline initialize_cluster(aws::AWSConfig=default_aws_config(); args...) = initialize_cluster(aws, args)

@inline initialize_cluster(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "InitializeCluster", args)

@inline initialize_cluster(args) = initialize_cluster(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.list_tags
    list_tags([::AWSConfig], arguments::Dict)
    list_tags([::AWSConfig]; ResourceId=, <keyword arguments>)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "ListTags", arguments::Dict)
    cloudhsmv2([::AWSConfig], "ListTags", ResourceId=, <keyword arguments>)

# ListTags Operation

Gets a list of tags for the specified AWS CloudHSM cluster.

This is a paginated operation, which means that each response might contain only a subset of all the tags. When the response contains only a subset of tags, it includes a `NextToken` value. Use this value in a subsequent `ListTags` request to get more tags. When you receive a response with no `NextToken` (or an empty or null value), that means there are no more tags to get.

# Arguments

## `ResourceId = ::String` -- *Required*
The cluster identifier (ID) for the cluster whose tags you are getting. To find the cluster ID, use [DescribeClusters](@ref).

## `NextToken = ::String`
The `NextToken` value that you received in the previous response. Use this value to get more tags.

## `MaxResults = ::Int`
The maximum number of tags to return in the response. When there are more tags than the number you specify, the response contains a `NextToken` value.

# Returns

`ListTagsResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/ListTags)
"""
@inline list_tags(aws::AWSConfig=default_aws_config(); args...) = list_tags(aws, args)

@inline list_tags(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "ListTags", args)

@inline list_tags(args) = list_tags(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.tag_resource
    tag_resource([::AWSConfig], arguments::Dict)
    tag_resource([::AWSConfig]; ResourceId=, TagList=)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "TagResource", arguments::Dict)
    cloudhsmv2([::AWSConfig], "TagResource", ResourceId=, TagList=)

# TagResource Operation

Adds or overwrites one or more tags for the specified AWS CloudHSM cluster.

# Arguments

## `ResourceId = ::String` -- *Required*
The cluster identifier (ID) for the cluster that you are tagging. To find the cluster ID, use [DescribeClusters](@ref).

## `TagList = [[ ... ], ...]` -- *Required*
A list of one or more tags.
```
 TagList = [[
        "Key" => <required> ::String,
        "Value" => <required> ::String
    ], ...]
```

# Returns

`TagResourceResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/TagResource)
"""
@inline tag_resource(aws::AWSConfig=default_aws_config(); args...) = tag_resource(aws, args)

@inline tag_resource(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "TagResource", args)

@inline tag_resource(args) = tag_resource(default_aws_config(), args)

"""
    using AWSSDK.CloudHSMV2.untag_resource
    untag_resource([::AWSConfig], arguments::Dict)
    untag_resource([::AWSConfig]; ResourceId=, TagKeyList=)

    using AWSCore.Services.cloudhsmv2
    cloudhsmv2([::AWSConfig], "UntagResource", arguments::Dict)
    cloudhsmv2([::AWSConfig], "UntagResource", ResourceId=, TagKeyList=)

# UntagResource Operation

Removes the specified tag or tags from the specified AWS CloudHSM cluster.

# Arguments

## `ResourceId = ::String` -- *Required*
The cluster identifier (ID) for the cluster whose tags you are removing. To find the cluster ID, use [DescribeClusters](@ref).

## `TagKeyList = [::String, ...]` -- *Required*
A list of one or more tag keys for the tags that you are removing. Specify only the tag keys, not the tag values.

# Returns

`UntagResourceResponse`

# Exceptions

`CloudHsmInternalFailureException`, `CloudHsmServiceException`, `CloudHsmResourceNotFoundException`, `CloudHsmInvalidRequestException` or `CloudHsmAccessDeniedException`.

See also: [AWS API Documentation](https://docs.aws.amazon.com/goto/WebAPI/cloudhsmv2-2017-04-28/UntagResource)
"""
@inline untag_resource(aws::AWSConfig=default_aws_config(); args...) = untag_resource(aws, args)

@inline untag_resource(aws::AWSConfig, args) = AWSCore.Services.cloudhsmv2(aws, "UntagResource", args)

@inline untag_resource(args) = untag_resource(default_aws_config(), args)

end # module CloudHSMV2

#==============================================================================#
# End of file
#==============================================================================#


text:
module SignedDistanceFields

using LinearAlgebra
using StaticArrays

export space, Shd_Id, SDF, Sphere, Ball, Plane, RepQ, Trans, SUnion, SCut, to_code, exec, func, @func

const vec3 = SVector{3,Float64}
const space = vec3
const Shd_Id = UInt16

include("functionbuilder.jl")

abstract type SDF end

include("simplegeometry.jl")
include("higherordercombinators.jl")

end # module


text:
module Assignments

include("sudoku.jl")
include("newton_raphson.jl")

end


text:
using StanModels, CSV

df = CSV.read(joinpath(@__DIR__, "..", "..", "data", "milk.csv"), delim=';')
dcc = filter(row -> !(row[:neocortex_perc] == "NA"), df)
dcc[!, :kcal_per_g] = convert(Vector{Float64}, dcc[!, :kcal_per_g])
dcc[!, :neocortex_perc] = parse.(Float64, dcc[!, :neocortex_perc])

# Show first 5 rows

first(dcc, 5)

# Define the Stan language model

m5_5s = "
data{
    int N;
    vector[N] kcal_per_g;
    vector[N] neocortex_perc;
}
parameters{
    real a;
    real bn;
    real sigma;
}
model{
    vector[N] mu = a + bn * neocortex_perc;
    sigma ~ uniform( 0 , 1 );
    bn ~ normal( 0 , 1 );
    a ~ normal( 0 , 100 );
    kcal_per_g ~ normal( mu , sigma );
}
";

# Define the Stanmodel and set the output format to :mcmcchains.

sm = SampleModel("m5.5s", m5_5s);

# Input data for cmdstan

m5_5_data = Dict("N" => size(dcc, 1),
  "kcal_per_g" => dcc[!, :kcal_per_g],
  "neocortex_perc" => dcc[!, :neocortex_perc]);

# Sample using cmdstan

(sample_file, log_file) = stan_sample(sm, data=m5_5_data);

# Rethinking results

rethinking_results = "
       mean   sd  5.5% 94.5%
 a     0.04 0.15 -0.21  0.29
 bN    0.13 0.22 -0.22  0.49
 sigma 1.00 0.16  0.74  1.26
"

# Describe the draws
if !(sample_file == nothing)
  chn = read_samples(sm)
  describe(chn)
end


text:
# Test the integrate function
using Test
using SAC

@testset "Integration" begin
    let sample_data = SAC.sample()

        # Don't accept wrong arguments
        @test_throws ArgumentError SAC.integrate(sample_data, :weird_method)

        # Compare against output from SAC
        for (method, name) in zip((:trapezium, :rectangle), ("trapezoidal", "rectangular"))
            local file = joinpath(dirname(@__FILE__), "data", "test_integrate_data_method_$(name).sac")
            isfile(file) || error("Test data file '$(file)' for differentiate does not exist")
            local reference_data = SAC.read(file)
            local test_data = SAC.integrate(SAC.rmean(sample_data), method)
            @test test_data.t ≈ reference_data.t
            @test test_data.b ≈ reference_data.b
        end
    end
end


text:
module GDF_test

# greet() = print("Hello World!")

# include("tables.jl")
include("io.jl")

export GeoTable, gdf_from_geojson, read_gjt, read_shp
end # module


text:
mutable struct apriltag_detections_t <: LCMType
  timestamp::Int64
  num_detections::Int32
  detections::Vector{apriltag_t}
end

@lcmtypesetup(apriltag_detections_t,
  detections => (num_detections,)
)


text:
struct RangeConstraintSpecInternal
    constraint_infos::Vector{<:AbstractRangeConstraintInfo}
    constraint_name::Symbol
    variable_name::Symbol
    bin_variable_names::Vector{Symbol}
end

function RangeConstraintSpecInternal(
    constraint_infos::Vector{DeviceRangeConstraintInfo},
    constraint_name::Symbol,
    variable_name::Symbol,
)
    return RangeConstraintSpecInternal(
        constraint_infos,
        constraint_name,
        variable_name,
        Vector{Symbol}(),
    )
end

@doc raw"""
Constructs min/max range constraint from device variable.

# Constraints
If min and max within an epsilon width:

``` variable[name, t] == limits.max ```

Otherwise:

``` limits.min <= variable[name, t] <= limits.max ```

where limits in constraint_infos.

# LaTeX

`` x = limits^{max}, \text{ for } |limits^{max} - limits^{min}| < \varepsilon ``

`` limits^{min} \leq x \leq limits^{max}, \text{ otherwise } ``
"""
function device_range!(psi_container::PSIContainer, inputs::RangeConstraintSpecInternal)
    time_steps = model_time_steps(psi_container)
    variable = get_variable(psi_container, inputs.variable_name)
    ub_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    lb_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "lb")
    names = [get_component_name(x) for x in inputs.constraint_infos]
    con_ub = add_cons_container!(psi_container, ub_name, names, time_steps)
    con_lb = add_cons_container!(psi_container, lb_name, names, time_steps)

    for constraint_info in inputs.constraint_infos, t in time_steps
        ci_name = get_component_name(constraint_info)
        expression_ub = JuMP.AffExpr(0.0, variable[ci_name, t] => 1.0)
        for val in constraint_info.additional_terms_ub
            JuMP.add_to_expression!(
                expression_ub,
                get_variable(psi_container, val)[ci_name, t],
            )
        end
        expression_lb = JuMP.AffExpr(0.0, variable[ci_name, t] => 1.0)
        for val in constraint_info.additional_terms_lb
            JuMP.add_to_expression!(
                expression_lb,
                get_variable(psi_container, val)[ci_name, t],
                -1.0,
            )
        end
        con_ub[ci_name, t] = JuMP.@constraint(
            psi_container.JuMPmodel,
            expression_ub <= constraint_info.limits.max
        )
        con_lb[ci_name, t] = JuMP.@constraint(
            psi_container.JuMPmodel,
            expression_lb >= constraint_info.limits.min
        )
    end

    return
end

@doc raw"""
Constructs min/max range constraint from device variable and on/off decision variable.

# Constraints
If device min = 0:

``` varcts[name, t] <= limits.max*varbin[name, t]) ```

``` varcts[name, t] >= 0.0 ```

Otherwise:

``` varcts[name, t] <= limits.max*varbin[name, t] ```

``` varcts[name, t] >= limits.min*varbin[name, t] ```

where limits in constraint_infos.

# LaTeX

`` 0 \leq x^{cts} \leq limits^{max} x^{bin}, \text{ for } limits^{min} = 0 ``

`` limits^{min} x^{bin} \leq x^{cts} \leq limits^{max} x^{bin}, \text{ otherwise } ``
"""
function device_semicontinuousrange!(
    psi_container::PSIContainer,
    inputs::RangeConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    varcts = get_variable(psi_container, inputs.variable_name)
    @assert length(inputs.bin_variable_names) == 1
    varbin = get_variable(psi_container, inputs.bin_variable_names[1])
    ub_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    lb_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "lb")
    names = [get_component_name(x) for x in inputs.constraint_infos]
    #MOI has a semicontinous set, but after some tests is not clear most MILP solvers support it.
    #In the future this can be updated
    con_ub = add_cons_container!(psi_container, ub_name, names, time_steps)
    con_lb = add_cons_container!(psi_container, lb_name, names, time_steps)

    for constraint_info in inputs.constraint_infos, t in time_steps
        ci_name = get_component_name(constraint_info)
        if JuMP.has_lower_bound(varcts[ci_name, t])
            JuMP.set_lower_bound(varcts[ci_name, t], 0.0)
        end
        expression_ub = JuMP.AffExpr(0.0, varcts[ci_name, t] => 1.0)
        for val in constraint_info.additional_terms_ub
            JuMP.add_to_expression!(
                expression_ub,
                get_variable(psi_container, val)[ci_name, t],
            )
        end
        expression_lb = JuMP.AffExpr(0.0, varcts[ci_name, t] => 1.0)
        for val in constraint_info.additional_terms_lb
            JuMP.add_to_expression!(
                expression_lb,
                get_variable(psi_container, val)[ci_name, t],
                -1.0,
            )
        end
        con_ub[ci_name, t] = JuMP.@constraint(
            psi_container.JuMPmodel,
            expression_ub <= constraint_info.limits.max * varbin[ci_name, t]
        )
        con_lb[ci_name, t] = JuMP.@constraint(
            psi_container.JuMPmodel,
            expression_lb >= constraint_info.limits.min * varbin[ci_name, t]
        )
    end

    return
end

#This function looks suspicious and repetitive. Needs verification
@doc raw"""
Constructs min/max range constraint from device variable and on/off decision variable.

# Constraints
If device min = 0:

``` varcts[name, t] <= limits.max * (1 - varbin[name, t]) ```

``` varcts[name, t] >= 0.0 ```

Otherwise:

``` varcts[name, t] <= limits.max * (1 - varbin[name, t]) ```

``` varcts[name, t] >= limits.min * (1 - varbin[name, t]) ```

where limits in constraint_infos.

# LaTeX

`` 0 \leq x^{cts} \leq limits^{max} (1 - x^{bin} ), \text{ for } limits^{min} = 0 ``

`` limits^{min} (1 - x^{bin} ) \leq x^{cts} \leq limits^{max} (1 - x^{bin} ), \text{ otherwise } ``
"""
function reserve_device_semicontinuousrange!(
    psi_container::PSIContainer,
    inputs::RangeConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    varcts = get_variable(psi_container, inputs.variable_name)
    @assert length(inputs.bin_variable_names) == 1
    varbin = get_variable(psi_container, inputs.bin_variable_names[1])

    ub_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    lb_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "lb")
    names = [get_component_name(x) for x in inputs.constraint_infos]
    #MOI has a semicontinous set, but after some tests is not clear most MILP solvers support it.
    #In the future this can be updated
    con_ub = add_cons_container!(psi_container, ub_name, names, time_steps)
    con_lb = add_cons_container!(psi_container, lb_name, names, time_steps)

    for constraint_info in inputs.constraint_infos, t in time_steps
        ci_name = get_component_name(constraint_info)
        if JuMP.has_lower_bound(varcts[ci_name, t])
            JuMP.set_lower_bound(varcts[ci_name, t], 0.0)
        end
        expression_ub = JuMP.AffExpr(0.0, varcts[ci_name, t] => 1.0)
        for val in constraint_info.additional_terms_ub
            JuMP.add_to_expression!(
                expression_ub,
                get_variable(psi_container, val)[ci_name, t],
            )
        end
        expression_lb = JuMP.AffExpr(0.0, varcts[ci_name, t] => 1.0)
        for val in constraint_info.additional_terms_lb
            JuMP.add_to_expression!(
                expression_lb,
                get_variable(psi_container, val)[ci_name, t],
                -1.0,
            )
        end
        con_ub[ci_name, t] = JuMP.@constraint(
            psi_container.JuMPmodel,
            expression_ub <= constraint_info.limits.max * (1 - varbin[ci_name, t])
        )
        con_lb[ci_name, t] = JuMP.@constraint(
            psi_container.JuMPmodel,
            expression_lb >= constraint_info.limits.min * (1 - varbin[ci_name, t])
        )
    end
    return
end

@doc raw"""
Constructs min/max range constraint from device variable and on/off decision variable.

# Constraints

``` varcts[name, t] <= (limits.max-limits.min)*varbin[name, t])
        - max(limits.max - lag_ramp_limits.startup, 0) * var_on[name, t] ```

``` varcts[name, t] <= (limits.max-limits.min)*varbin[name, t])
        - max(limits.max - lag_ramp_limits.shutdown, 0) * var_off[name, t] ```

where limits and lag_ramp_limits is in range_data.

# LaTeX

`` x^{cts} \leq (limits^{max}-limits^{min}) x^{bin} - max(limits^{max} - lag^{startup}, 0) x^{on} ``

`` x^{cts} \leq (limits^{max}-limits^{min}) x^{bin} - max(limits^{max} - lag^{shutdown}, 0) x^{off}``

# Arguments
* psi_container::PSIContainer : the psi_container model built in PowerSimulations
* range_data::Vector{DeviceRange} : contains names and vector of min/max
* cons_name::Symbol : name of the constraint
* var_name::Symbol : the name of the continuous variable
* binvar_names::Symbol : the names of the binary variables
"""
function device_multistart_range!(
    psi_container::PSIContainer,
    inputs::RangeConstraintSpecInternal,
)
    time_steps = model_time_steps(psi_container)
    varp = get_variable(psi_container, inputs.variable_name)
    @assert length(inputs.bin_variable_names) == 3
    varstatus = get_variable(psi_container, inputs.bin_variable_names[1])
    varon = get_variable(psi_container, inputs.bin_variable_names[2])
    varoff = get_variable(psi_container, inputs.bin_variable_names[3])

    on_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "lb")
    off_name = middle_rename(inputs.constraint_name, PSI_NAME_DELIMITER, "ub")
    names = [get_component_name(x) for x in inputs.constraint_infos]
    con_on = add_cons_container!(psi_container, on_name, names, time_steps)
    con_off = add_cons_container!(psi_container, off_name, names, time_steps)

    for constraint_info in inputs.constraint_infos, t in time_steps
        if JuMP.has_lower_bound(varp[get_component_name(constraint_info), t])
            JuMP.set_lower_bound(varp[get_component_name(constraint_info), t], 0.0)
        end
        expression_products =
            JuMP.AffExpr(0.0, varp[get_component_name(constraint_info), t] => 1.0)
        for val in constraint_info.additional_terms_ub
            JuMP.add_to_expression!(
                expression_products,
                get_variable(psi_container, val)[get_component_name(constraint_info), t],
            )
        end
        con_on[get_component_name(constraint_info), t] = JuMP.@constraint(
            psi_container.JuMPmodel,
            expression_products <=
            (constraint_info.limits.max - constraint_info.limits.min) *
            varstatus[get_component_name(constraint_info), t] -
            max(constraint_info.limits.max - constraint_info.lag_ramp_limits.startup, 0) * varon[get_component_name(constraint_info), t]
        )
        if t == length(time_steps)
            continue
        else
            con_off[get_component_name(constraint_info), t] = JuMP.@constraint(
                psi_container.JuMPmodel,
                expression_products <=
                (constraint_info.limits.max - constraint_info.limits.min) *
                varstatus[get_component_name(constraint_info), t] -
                max(
                    constraint_info.limits.max - constraint_info.lag_ramp_limits.shutdown,
                    0,
                ) * varoff[get_component_name(constraint_info), t + 1]
            )
        end
    end

    return
end

@doc raw"""
Constructs min/max range constraint from device variable and on/off decision variable.

# Constraints

``` max(limits.max - lag_ramp_limits.shutdown, 0) var_off[name, 1] <= initial_power[ix].value
        - (limits.max - limits.min)initial_status[ix].value  ```

where limits in range_data.

# LaTeX

`` max(limits^{max} - lag^{shutdown}, 0) x^{off} \leq initial_condition^{power} - (limits^{max} - limits^{min}) initial_condition^{status}``

# Arguments
* psi_container::PSIContainer : the psi_container model built in PowerSimulations
* range_data::Vector{DeviceRange} : contains names and vector of min/max
* initial_conditions::Matrix{InitialCondition} :
* cons_name::Symbol : name of the constraint
* var_name::Symbol : name of the shutdown variable
"""
function device_multistart_range_ic!(
    psi_container::PSIContainer,
    range_data::Vector{DeviceMultiStartRangeConstraintsInfo},
    initial_conditions::Matrix{InitialCondition},## 1 is initial power, 2 is initial status
    cons_name::Symbol,
    var_name::Symbol,
)
    time_steps = model_time_steps(psi_container)
    varstop = get_variable(psi_container, var_name)

    set_name = [device_name(ic) for ic in initial_conditions[:, 1]]
    con = add_cons_container!(psi_container, cons_name, set_name)

    for (ix, ic) in enumerate(initial_conditions[:, 1])
        name = device_name(ic)
        data = range_data[ix]
        val = max(data.limits.max - data.lag_ramp_limits.shutdown, 0)
        con[name] = JuMP.@constraint(
            psi_container.JuMPmodel,
            val * varstop[get_component_name(data), 1] <=
            initial_conditions[ix, 2].value * (data.limits.max - data.limits.min) -
            ic.value
        )
    end
    return
end


text:
module SearchLightOracle

import DataFrames, Logging
import SearchLight
import SearchLight: storableFields, fields_to_store_directly

using Oracle, Dates

const DEFAULT_PORT = 1521

const COLUMN_NAME_FIELD_NAME = :column_name

function SearchLight.column_field_name()
  COLUMN_NAME_FIELD_NAME
end

const DatabaseHandle = Oracle.Connection

const CONNECTIONS = DatabaseHandle[]

const TYPE_MAPPINGS = Dict{Symbol,Symbol}( # Julia / Postgres
  :char       => :CHAR                  ,
  :string     => Symbol("VARCHAR(255)") ,
  :text       => Symbol("VARCHAR(2000)"),
  :integer    => :Number                ,
  :int        => :Number                ,
  :float      => :Number                ,
  :decimal    => :Number                ,
  :datetime   => :DATE                  ,
  :timestamp  => :TIMESTAMP             ,
  :time       => :TIMESTAMP             ,
  :date       => :DATE                  ,
  :binary     => :BLOB                  ,
  :boolean    => :SMALLINT              ,
  :bool       => :SMALLINT              ,
  :dbid       => Symbol("NUMBER(10)"))

#
# Connection
#

"""
    connect(conn_data::Dict)::DatabaseHandle

Connects to the database and returns a handle.
"""
function SearchLight.connect(conn_data::Dict = SearchLight.config.db_config_settings)::DatabaseHandle

  host = ""
  port = ""
  database = ""
  username = ""
  password = ""
  connectionString = ""

  haskey(conn_data, "host")     ? host = conn_data["host"] : throw(SearchLight.Exceptions.InvalidConnectionItem("host is not there or empty"))
  haskey(conn_data, "port")     ? port = conn_data["port"] : throw(SearchLight.Exceptions.InvalidConnectionItem("port is not there or empty"))
  haskey(conn_data, "database") ? database = conn_data["database"] : throw(SearchLight.Exceptions.InvalidConnectionItem("database is not there or empty"))
  haskey(conn_data, "username") ? username = conn_data["username"] : throw(SearchLight.Exceptions.InvalidConnectionItem("username is not there or empty"))
  haskey(conn_data, "password") ? password = conn_data["password"] : throw(SearchLight.Exceptions.InvalidConnectionItem("password is not there or empty"))

  connectionString = "//$host:$port/$database"

  push!(CONNECTIONS, Oracle.Connection(username, password, connectionString))[end]
end

function Base.count(m::Type{T}, q::SearchLight.SQLQuery = SearchLight.SQLQuery())::Int where {T<:SearchLight.AbstractModel}
  count_column = SearchLight.SQLColumn("COUNT(*) AS __cid", raw = true)
  q = SearchLight.clone(q, :columns, push!(q.columns, count_column))

  SearchLight.finddf(m, q)[1, Symbol("__cid")]
end

function SearchLight.disconnect(connection::Oracle.Connection)
    Oracle.close(connection)
    nothing
end

function SearchLight.connection()
  isempty(CONNECTIONS) && throw(SearchLight.Exceptions.NotConnectedException())
  CONNECTIONS[end]
end

function SearchLight.Migration.drop_migrations_table(table_name::String = SearchLight.config.db_migrations_table_name) :: Nothing

  SearchLight.Migration.drop_table(table_name)

  nothing
end

function SearchLight.Migration.drop_table(table_name::Union{String,Symbol}) :: Nothing
  queryString = string("""SELECT  table_name FROM USER_TABLES t WHERE table_name = '$(uppercase(string(table_name)))'""")
  if !isempty(SearchLight.query(queryString))
      ## Drop table
      Oracle.execute(SearchLight.connection(),"DROP TABLE $(uppercase(string(table_name)))")
      @info "Droped table $table_name"
      ## Drop pk() sequence
      queryString = "SELECT sequence_name FROM user_sequences WHERE SEQUENCE_name = '$(uppercase(sequence_name_pk(table_name)))'"
      if !isempty(SearchLight.query(queryString))
        SearchLight.Migration.drop_sequence(uppercase(sequence_name_pk(table_name)))
      else
        @info "No sequence to drop while dropping table"
      end
  else
      @info "Nothing to drop"
  end

  nothing
end

"""
create_migrations_table -- creates the needed migration table

  Runs a SQL DB query that creates the table `table_name` with the structure needed to be used as the DB migrations table.
  The table should contain one column, `version`, unique, as a string of maximum 30 chars long.
"""
function SearchLight.Migration.create_migrations_table(table_name::String = SearchLight.config.db_migrations_table_name) :: Nothing

  queryString = string("select table_name from user_tables where table_name = upper('$table_name')")
  if isempty(SearchLight.query(queryString))
    stmt = Oracle.Stmt(SearchLight.connection(),"CREATE TABLE $table_name (version varchar2(30))")
    Oracle.execute(stmt)
    @info "Created table $table_name"
  else
    @info "Migration table exists."
  end

  nothing
end

function matchall(r::Regex, string::Union{SubString{String},String})
  matches = collect(eachmatch(r,string))
  [match.match for match in matches]
end

function column_names_from_select(sql::String)
  result = String[]
  closures = []
  replacement_string = "XXYXX"
  # matches the statement between select .... from
  rawmatch = match(r"(?i)(?s)(?<=Select).*?(?=From)", sql).match
  if rawmatch !== nothing
    match_without_linebr = string(strip(replace(rawmatch,r"\R"=>"")))
    closure_items = reverse(matchall(r"\(.+\)", match_without_linebr))
    closure_replaced = replace(match_without_linebr, r"\(.+\)" => replacement_string)
    splitted_withoutClosures = string.(split(closure_replaced,","))
    items = String[]
    for item in splitted_withoutClosures
      tmpItem = last(matchall(r"(\w+)",string(item)))
      if occursin(replacement_string,tmpItem)
        push!(items,replace(item,replacement_string => pop!(closure_items)))
      else
        push!(items,tmpItem)
      end
    end
    result = string.(items)
  end
  result
end

function SearchLight.columns(m::Type{T})::DataFrames.DataFrame where {T<:SearchLight.AbstractModel}
  SearchLight.query(table_columns_sql(SearchLight.table(m)), internal = true)
end

"""
    table_columns_sql(table_name::String)::String

Returns the adapter specific query for SELECTing table columns information corresponding to `table_name`.
"""
function table_columns_sql(table_name::String) :: String
  "SELECT column_name FROM user_tab_cols WHERE TABLE_NAME = '$table_name'"
end

function SearchLight.delete_all(m::Type{T}; truncate::Bool = true, reset_sequence::Bool = true, cascade::Bool = false)::Nothing where {T<:SearchLight.AbstractModel}
  if truncate
    sql = "TRUNCATE $(SearchLight.table(m))"
    cascade ? sql *= " CASCADE" : ""
  else
    sql = "DELETE FROM $(SearchLight.table(m))"
  end

  SearchLight.query(sql)

  SearchLight.Migration.reset_sequence(sequence_name_pk(m))

  nothing
end

function SearchLight.delete(m::T)::T where {T<:SearchLight.AbstractModel}
  SearchLight.ispersisted(m) || throw(SearchLight.Exceptions.NotPersistedException(m))
  pk_field = Symbol(SearchLight.primary_key_name(typeof(m)))

  "DELETE FROM $(SearchLight.table(typeof(m))) WHERE $(SearchLight.primary_key_name(typeof(m))) = '$(values(getfield(m,pk_field)))'" |> SearchLight.query

  # reinitialize the id
  setfield!(m,pk_field, SearchLight.DbId())

  m
end

function SearchLight.query(sql::String, conn::DatabaseHandle = SearchLight.connection(); internal = false) :: DataFrames.DataFrame
    #initializing the dataframe
    df = DataFrames.DataFrame()
    #preparing the statement
    stmt = Oracle.Stmt(conn, sql)
    #execute the query statement
    result = if SearchLight.config.log_queries && ! internal
        @info sql
        stmt.info.is_query == true ? Oracle.query(stmt) : @time Oracle.execute(stmt)
    else
        stmt.info.is_query == true ? Oracle.query(stmt) : Oracle.execute(stmt)
    end

    ## if the statement is an insert-stmt bring back the actual val of the sequence
    if isInsertStmt(sql)
       id_result, id_name = current_value_seq(conn, sql)
       if id_name != ""
          df = id_result |> DataFrames.DataFrame
          DataFrames.rename!(df,[1=>id_name])
       end
    end

    #Until SearchLight will for its own support transactions every transaction will commited
    stmt.info.is_query == false && Oracle.commit(SearchLight.connection())

    #get back the original column_names for the dataframe
    if stmt.info.is_query
      #get the dataframe
      df = DataFrames.DataFrame(result)
      realColumn_names = column_names_from_select(sql)
      colNames_df = names(df)
      indices = findall(x->uppercase(x) in colNames_df, realColumn_names)
      DataFrames.rename!(df,[index => Symbol(realColumn_names[index]) for index in indices])
    end
     ## each statment should be closed
     Oracle.close(stmt)
     typeof(result) != Oracle.ResultSet || !isempty(df) ? df : result |> DataFrames.DataFrame
end

function SearchLight.to_find_sql(m::Type{T}, q::SearchLight.SQLQuery, joins::Union{Nothing,Vector{SearchLight.SQLJoin{N}}} = nothing)::String where {T<:SearchLight.AbstractModel, N<:Union{Nothing,SearchLight.AbstractModel}}
  sql::String = ( string("$(SearchLight.to_select_part(m, q.columns, joins)) $(SearchLight.to_from_part(m)) $(SearchLight.to_join_part(m, joins)) $(SearchLight.to_where_part(q.where)) ",
                      "$(SearchLight.to_group_part(q.group)) $(SearchLight.to_having_part(q.having)) $(SearchLight.to_order_part(m, q.order)) ",
                      "$(SearchLight.to_limit_part(q.limit)) $(SearchLight.to_offset_part(q.offset))")) |> strip
  replace(sql, r"\s+"=>" ")
end

function SearchLight.to_from_part(m::Type{T})::String where {T<:SearchLight.AbstractModel}
  "FROM " * SearchLight.escape_column_name(SearchLight.table(m), SearchLight.connection())
end

function SearchLight.to_join_part(m::Type{T}, joins::Union{Nothing,Vector{SearchLight.SQLJoin{N}}} = nothing)::String where {T<:SearchLight.AbstractModel, N<:Union{Nothing,SearchLight.AbstractModel}}
  joins === nothing && return ""

  join(map(x -> string(x), joins), " ")
end

function SearchLight.to_where_part(w::Vector{SearchLight.SQLWhereEntity})::String
  where = isempty(w) ?
          "" :
          string("WHERE ",
                (string(first(w).condition) == "AND" ? "TRUE " : "FALSE "),
                join(map(wx -> string(wx), w), " "))

  replace(where, r"WHERE TRUE AND "i => "WHERE ")
end

function SearchLight.to_group_part(g::Vector{SearchLight.SQLColumn}) :: String
  isempty(g) ?
    "" :
    string(" GROUP BY ", join(map(x -> string(x), g), ", "))
end

function SearchLight.to_having_part(h::Vector{SearchLight.SQLWhereEntity}) :: String
  having =  isempty(h) ?
            "" :
            string("HAVING ",
                  (string(first(h).condition) == "AND" ? "TRUE " : "FALSE "),
                  join(map(w -> string(w), h), " "))

  replace(having, r"HAVING TRUE AND "i => "HAVING ")
end

function SearchLight.to_order_part(m::Type{T}, o::Vector{SearchLight.SQLOrder})::String where {T<:SearchLight.AbstractModel}
  isempty(o) ?
    "" :
    string("ORDER BY ",
            join(map(x -> string((! SearchLight.is_fully_qualified(x.column.value) ?
                                    SearchLight.to_fully_qualified(m, x.column) :
                                    x.column.value), " ", x.direction),
                      o), ", "))
end

function SearchLight.to_limit_part(l::SearchLight.SQLLimit) :: String
  l.value != "ALL" ? string("LIMIT ", string(l)) : ""
end

function SearchLight.to_offset_part(o::Int) :: String
  o != 0 ? string("OFFSET ", string(o)) : ""
end

function SearchLight.update_query_part(m::T)::String where {T<:SearchLight.AbstractModel}

  uf = fields_to_store_directly(typeof(m))

  update_values = join(map(x -> "$(string(SearchLight.SQLColumn(uf[x]))) = $(string(SearchLight.to_sqlinput(m, Symbol(x), getfield(m, Symbol(x)))) )", collect(keys(uf))), ", ")

  " $update_values WHERE $(SearchLight.table(typeof(m))).$(SearchLight.primary_key_name(typeof(m))) = '$(m.id.value)'"
end

function SearchLight.column_data_to_column_name(column::SearchLight.SQLColumn, column_data::Dict{Symbol,Any}) :: String
  "$(SearchLight.to_fully_qualified(column_data[:column_name], column_data[:table_name])) AS $(isempty(column_data[:alias]) ? SearchLight.to_sql_column_name(column_data[:column_name], column_data[:table_name]) : column_data[:alias] )"
end

### fallback function if storableFields not defined in the module
function storableFields(m::Type{T})::Dict{String,String} where {T<:SearchLight.AbstractModel}
    tmpStorage = Dict{String,String}()
    for field in SearchLight.persistable_fields(m)
      push!(tmpStorage, field => field)
    end
    return tmpStorage
end

"""
  Only direct storable fields will be returnd by this function.
  The fields with an AbstractModel-field or array will be stored temporarly
  in the saving method and saved after returning the parent struct.
"""
function fields_to_store_directly(m::Type{T}) where {T<:SearchLight.AbstractModel}

  storage_fields = storableFields(m)
  fields_and_types = SearchLight.to_string_dict(m)
  uf=Dict{String,String}()

  for (key,value) in storage_fields
    if !(fields_and_types[key]<:SearchLight.AbstractModel || fields_and_types[key]<:Array{<:SearchLight.AbstractModel,1})
      push!(uf,key => value)
    end
  end

  return uf
end

function SearchLight.to_store_sql(m::T; conflict_strategy = :error)::String where {T<:SearchLight.AbstractModel}

  uf = fields_to_store_directly(typeof(m))

  sql = if ! SearchLight.ispersisted(m) || (SearchLight.ispersisted(m) && conflict_strategy == :update)
    key = getkey(uf, SearchLight.primary_key_name(m), nothing)
    key !== nothing && pop!(uf, key)

    id_column = SearchLight.pk(m) != "" ?  SearchLight.pk(m) * ", " : ""
    id_value = id_column != "" ?  sequence_name_pk(m)*".nextval, " : ""

    fields = id_column * join(SearchLight.SQLColumn(uf),", ")
    vals = id_value * join( map(x -> string(SearchLight.to_sqlinput(m, Symbol(x), getfield(m, Symbol(x)))), collect(keys(uf))), ", ")

    "INSERT INTO $(SearchLight.table(typeof(m))) ( $fields ) VALUES ( $vals )" *
        if ( conflict_strategy == :error ) ""
        elseif ( conflict_strategy == :ignore ) " ON CONFLICT DO NOTHING"
        end
  else
    prepare_update_part(m)
  end

  return sql
end

function prepare_update_part(m::T)::String where {T<:SearchLight.AbstractModel}

  result = ""
  sub_abstracts = SearchLight.array_sub_abstract_models(m)

  if !isempty(sub_abstracts)
    result = join(prepare_update_part.(sub_abstracts),";",";")
    result *= ";"
  end
  result *= "UPDATE $(SearchLight.table(typeof(m))) SET $(SearchLight.update_query_part(m))"
end

function SearchLight.Migration.create_table(f::Function, name::Union{String,Symbol}, options::Union{String,Symbol} = "") :: Nothing
  SearchLight.query(create_table_sql(f, string(name), options), internal = true)
  SearchLight.Migration.create_sequence(sequence_name_pk(name))
  SearchLight.query("select $(sequence_name_pk(name)).nextval from dual")

  nothing
end

function SearchLight.Migration.add_index(table_name::Union{String,Symbol}, column_name::Union{String,Symbol}; name::Union{String,Symbol} = "", unique::Bool = false, order::Union{String,Symbol} = :none) :: Nothing
  name = isempty(name) ? SearchLight.index_name(table_name, column_name) : name
  SearchLight.query("CREATE $(unique ? "UNIQUE" : "") INDEX $(name) ON $table_name ($column_name)", internal = true)

  nothing
end

function SearchLight.Migration.add_column(table_name::Union{String,Symbol}, name::Union{String,Symbol}, column_type::Union{String,Symbol}; default::Union{String,Symbol,Nothing} = nothing, limit::Union{Int,Nothing} = nothing, not_null::Bool = false) :: Nothing
  SearchLight.query("ALTER TABLE $table_name ADD $(SearchLight.Migration.column(name, column_type, default = default, not_null = not_null))", internal = true)

  nothing
end

function SearchLight.Migration.remove_column(table_name::Union{String,Symbol}, name::Union{String,Symbol}, options::Union{String,Symbol} = "") :: Nothing
  SearchLight.query("ALTER TABLE $table_name DROP COLUMN $name $options", internal = true)

  nothing
end

function SearchLight.Migration.remove_index(name::Union{String,Symbol}, options::Union{String,Symbol} = "") :: Nothing
  SearchLight.query("DROP INDEX $name $options", internal = true)

  nothing
end

function SearchLight.Migration.constraint(table_name::Union{String,Symbol}, column_name::Union{String,Symbol}) :: String
  string("CONSTRAINT ", SearchLight.index_name(table_name, column_name))
end

function SearchLight.Migration.nextval(table_name::Union{String,Symbol}, column_name::Union{String,Symbol}) :: String
  "NEXTVAL('$(sequence_name(table_name, column_name) )')"
end

function SearchLight.Migration.column_id_sequence(table_name::Union{String,Symbol}, column_name::Union{String,Symbol})
  throw(SearchLight.Exceptions.NotImplementedInAdapter("function column_id_sequence", "is not implemented in SearchLightOracle"))
end

function create_table_sql(f::Function, name::String, options::String = "") :: String
  "CREATE TABLE $name (" * join(f()::Vector{String}, ", ") * ") $options" |> strip
end

function SearchLight.Migration.column(name::Union{String,Symbol}, column_type::Union{String,Symbol}, options::Any = ""; default::Any = nothing, limit::Union{Int,Nothing,String} = nothing, not_null::Bool = false) :: String
  "$name $(TYPE_MAPPINGS[column_type] |> string) " *
    (default === nothing ? "" : " DEFAULT $default ") *
    (not_null ? " NOT NULL " : "") *
    string(options)
end

function sequence_name(table_name::Union{String,Symbol}, column_name::Union{String,Symbol}) :: String
  string(table_name) * "__" * "seq_" * string(column_name)
end

function SearchLight.Migration.create_sequence(name::Union{String,Symbol}) :: Nothing
  SearchLight.query("CREATE SEQUENCE $name")
  nothing
end

function SearchLight.Migration.create_sequence(table_name::Union{String,Symbol}, column_name::Union{String,Symbol}) :: Nothing
  res_column_name = column_name == "" ? string(SearchLight.primary_key_name) : column_name
  SearchLight.Migration.create_sequence(sequence_name(table_name, res_column_name))
  nothing
end

function SearchLight.Migration.remove_sequence(name::Union{String,Symbol}, options::Union{String,Symbol}) :: Nothing
  SearchLight.query("DROP SEQUENCE $name $options", internal = true)

  nothing
end

function SearchLight.Migration.remove_sequence(name::Union{String,Symbol}) :: Nothing
  SearchLight.Migration.remove_sequence(name,"")
end

function SearchLight.Migration.column_id(name::Union{String,Symbol} = "id", options::Union{String,Symbol} = ""; constraint::Union{String,Symbol} = "", nextval::Union{String,Symbol} = "") :: String
  "$name NUMBER(10) NOT NULL $options"
end

function SearchLight.Migration.reset_sequence(sequence_name)
  sql = "alter sequence $sequence_name restart start with 1"
  SearchLight.query(sql)
end

"""
  escape_column_name(c::String, conn::DatabaseHandle)::String

  Escapes the column name.

  # Examples
  ```julia
  julia>
  ```
"""
function SearchLight.escape_column_name(c::String, conn::DatabaseHandle = SearchLight.connection()) :: String
  join([cx for cx in split(c, '.')], '.')
end

"""
  escape_value{T}(v::T, conn::DatabaseHandle)::T

  Escapes the value `v` using native features provided by the database backend if available.

  # Examples
  ```julia
  julia>
  ```
"""
function SearchLight.escape_value(v::T, conn::DatabaseHandle = SearchLight.connection())::T where {T}
  isa(v, Number) ? v : "'$(replace(string(v), "'"=>"\\'"))'"
end

"""
current_value_seq -- value of a given sequence

  Returns the actual value of a given insert statement, theoreticaly also from a select statemnt.
  The prerequisite for this is the calling of nextval for this particulary squence in the actual session
"""
function current_value_seq(conn::SearchLightOracle.DatabaseHandle, sql::String)
  m  = match(r"(?i)\w+__SEQ_\w+_PK\.NEXTVAL",sql)
  id_name = ""
  sequenceName = m !== nothing ?  split(m.match,".")[1] : nothing
  if sequenceName !== nothing
    closures = eachmatch(r"\((.*?)\)", sql)
    matches  = [exp.match for exp in closures]
    matchStrings = [strip(w, [' ', '(', ')']) for w in matches]
    closureFields = split.(matchStrings,",")
    index_id = findfirst(x->occursin(sequenceName,x), closureFields[2])
    id_name = index_id !== nothing ? strip(closureFields[1][index_id]) : ""

    sql = "select $sequenceName.currval from dual"
    stmt = Oracle.Stmt(conn,sql)
    result = Oracle.query(stmt)
  else
    result = nothing
  end
  return result, id_name
end

########################################################################
#                                                                      #
#           Utility funcitions                                         #
#                                                                      #
########################################################################

function connectionInfo()::Dict{String,Any}

    infoStringDict = Dict(
        ["host"     => "select sys_context('USERENV', 'IP_ADDRESS') ip_adress from dual",
        "username" => "select user from dual",
        "database" => "select ora_database_name from dual"])

    result = Dict([info => getEnvironmentInfo(sql) for (info,sql) in infoStringDict])

end

function getEnvironmentInfo(sql::String)
    df = SearchLight.query(sql) |> DataFrames.DataFrame
    return df[1,1]
end

"""
  function sequence_name_pk(m::T) where {T<:AbstractModel}
Oracle doesn't support the returning of a value direct within the insert or update statement.
Therefor it is nesessary to use it directly in preparing insert or update statement
"""

function sequence_name_pk(m::T) where {T<:SearchLight.AbstractModel}
  sequence_name_pk(typeof(m))
end

function sequence_name_pk(m::Type{T}) where {T<:SearchLight.AbstractModel}
  sequence_name_pk(SearchLight.Inflector.to_plural(string(m)))
end

function sequence_name_pk(table::Union{String,Symbol})
  rawTable = SearchLight.strip_module_name(table)
  default_sequence = uppercase(string(rawTable))
  default_sequence *= "__SEQ_"
  default_sequence *= uppercase(SearchLight.Inflector.tosingular(string(rawTable))) * "_PK"
end

function isInsertStmt(sql::String)::Bool
  m =  match(r"(?i)insert", sql)
  m !== nothing ? true : false
end

"""
Returns the columnnames in a select statment case sensitive

  It is meant to be a workaround that Oracle gaves back column names
  as uppercase strings. For the column names of the dataframes it is
  nesessary to bring that back to the original
"""

### Generator

function SearchLight.Generator.FileTemplates.adapter_default_config()
  """
  $(SearchLight.config.app_env):
    adapter:  Oracle
    host:     127.0.0.1
    port:     1521
    database: yourdb
    username: your_username
    password: your_password
  """
end

end # End of Modul