text:
#== # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
#
# Description
#
#   Functions to print the tables.
#
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ==#

export pretty_table

################################################################################
#                                    Macros
################################################################################

"""
    macro _draw_line(io, left, intersection, right, row, border_crayon, num_cols, cols_width, show_row_number, row_number_width)

This macro draws a vertical table line. The `left`, `intersection`, `right`, and
`row` are the characters that will be used to draw the table line.

"""
macro _draw_line(io, left, intersection, right, row, border_crayon, num_cols,
                 cols_width, show_row_number, row_number_width)

    return quote
        local io               = $(esc(io))
        local left             = $(esc(left))
        local intersection     = $(esc(intersection))
        local right            = $(esc(right))
        local row              = $(esc(row))
        local border_crayon    = $(esc(border_crayon))
        local num_cols         = $(esc(num_cols))
        local cols_width       = $(esc(cols_width))
        local show_row_number  = $(esc(show_row_number))
        local row_number_width = $(esc(row_number_width))

        @_ps(io, border_crayon, left)

        if show_row_number
            print(io, border_crayon, row^(row_number_width+2))
            print(io, border_crayon, intersection)
        end

        @inbounds for i = 1:num_cols
            # Check the alignment and print.
            @_ps(io, border_crayon, row^(cols_width[i]+2))

            i != num_cols && @_ps(io, border_crayon, intersection)
        end

        @_ps(io, border_crayon, right)
        println(io)
    end
end

"""
    macro _str_aligned(data, alignment, field_size)

This macro returns a string of `data` with alignment `alignment` in a field with
size `field_size`. `alignment` can be `:l` or `:L` for left alignment, `:c` or
`:C` for center alignment, or `:r` or `:R` for right alignment. It defaults to
`:r` if `alignment` is any other symbol.

"""
macro _str_aligned(data, alignment, field_size)
    quote
        ldata = $(esc(data))
        la = $(esc(alignment))
        lfs = $(esc(field_size))
        ds  = length(ldata)
        Δ = lfs - ds

        Δ < 0 && error("The field size must be bigger than the data size.")

        if la == :l || la == :L
            ldata * " "^Δ
        elseif la == :c || la == :C
            left  = div(Δ,2)
            right = Δ-left
            " "^left * ldata * " "^right
        else
            " "^Δ * ldata
        end
    end
end

"""
    macro _ps(io, crayon, str)

Print `str` to `io` using `crayon` and reset the style at the end. Notice that
if `io` does not support colors, then no escape sequence will be printed.

"""
macro _ps(io, crayon, str)
    return quote
        io_has_color = get($(esc(io)), :color, false)

        if io_has_color
            print($(esc(io)), $(esc(crayon)), $(esc(str)), $(esc(_reset_crayon)))
        else
            print($(esc(io)), $(esc(str)))
        end
    end
end

################################################################################
#                               Public Functions
################################################################################

"""
    function pretty_table(data::AbstractMatrix{T1}, header::AbstractVecOrMat{T2}; kwargs...) where {T1,T2}

Print to `io` the matrix `data` with header `header` using the format `tf` (see
`PrettyTableFormat`). If `io` is omitted, then it defaults to `stdout`. If
`header` is empty, then it will be automatically filled with "Col. i" for the
*i*-th column.

The `header` can be a `Vector` or a `Matrix`. If it is a `Matrix`, then each row
will be a header line. The first line is called *header* and the others are
called *sub-headers* .

    function pretty_table([io::IO,] data::AbstractMatrix{T}, tf::PrettyTableFormat = unicode; ...) where T

Print to `io` the matrix `data` using the format `tf` (see `PrettyTableFormat`).
If `io` is omitted, then it defaults to `stdout`. The header will be
automatically filled with "Col. i" for the *i*-th column.

    function pretty_table([io::IO,] table, tf::PrettyTableFormat = unicode; ...)

Print to `io` the table `table` using the format `tf` (see `PrettyTableFormat`).
In this case, `table` must comply with the API of **Tables.jl**. If `io` is
omitted, then it defaults to `stdout`.

# Keywords

* `border_crayon`: Crayon to print the border.
* `header_crayon`: Crayon to print the header.
* `subheaders_crayon`: Crayon to print sub-headers.
* `rownum_header_crayon`: Crayon for the header of the column with the row
                          numbers.
* `text_crayon`: Crayon to print default text.
* `alignment`: Select the alignment of the columns (see the section `Alignment`).
* `formatter`: See the section `Formatter`.
* `highlighters`: A tuple with a list of highlighters (see the section
                  `Highlighters`).
* `hlines`: A vector of `Int` indicating row numbers in which an additional
            horizontal line should be drawn after the row. Notice that numbers
            lower than 1 and equal or higher than the number of rows will be
            neglected.
* `linebreaks`: If `true`, then `\n` will break the line inside the cells.
                (**Default** = `false`)
* `noheader`: If `true`, then the header will not be printed. Notice that all
              keywords and parameters related to the header and sub-headers will
              be ignored. (**Default** = `false`)
* `same_column_size`: If `true`, then all the columns will have the same size.
                      (**Default** = `false`)
* `show_row_number`: If `true`, then a new column will be printed showing the
                     row number. (**Default** = `false`)

The keywords `header_crayon` and `subheaders_crayon` can be a `Crayon` or a
`Vector{Crayon}`. In the first case, the `Crayon` will be applied to all the
elements. In the second, each element can have its own crayon, but the length of
the vector must be equal to the number of columns in the data.

# Crayons

A `Crayon` is an object that handles a style for text printed on terminals. It
is defined in the package
[Crayons.jl](https://github.com/KristofferC/Crayons.jl). There are many options
available to customize the style, such as foreground color, background color,
bold text, etc.

A `Crayon` can be created in two different ways:

```julia-repl
julia> Crayon(foreground = :blue, background = :black, bold = :true)

julia> crayon"blue bg:black bold"
```

For more information, see the package documentation.

# Alignment

The keyword `alignment` can be a `Symbol` or a vector of `Symbol`.

If it is a symbol, we have the following behavior:

* `:l` or `:L`: the text of all columns will be left-aligned;
* `:c` or `:C`: the text of all columns will be center-aligned;
* `:r` or `:R`: the text of all columns will be right-aligned;
* Otherwise it defaults to `:r`.

If it is a vector, then it must have the same number of symbols as the number of
columns in `data`. The *i*-th symbol in the vector specify the alignment of the
*i*-th column using the same symbols as described previously.

# Formatter

The keyword `formatter` can be used to pass functions to format the values in
the columns. It must be a `Dict{Number,Function}()`. The key indicates the
column number in which its elements will be converted by the function in the
value of the dictionary. The function must have the following signature:

    f(value, i)

in which `value` is the data and `i` is the row number. It must return the
formatted value.

For example, if we want to multiply all values in odd rows of the column 2 by π,
then the formatter should look like:

    Dict(2 => (v,i)->isodd(i) ? v*π : v)

If the key `0` is present, then the corresponding function will be applied to
all columns that does not have a specific key.

# Highlighters

A set of highlighters can be passed as a `Tuple` to the `highlighter` keyword.
Each highlighter is an instance of the structure `Highlighter` that contains
three fields:

* `f`: Function with the signature `f(data,i,j)` in which should return `true`
       if the element `(i,j)` in `data` must be highlighted, or `false`
       otherwise.
* `crayon`: Crayon with the style of a highlighted element.

The function `f` has the following signature:

    f(data, i, j)

in which `data` is a reference to the data that is being printed, `i` and `j`
are the element coordinates that are being tested. If this function returns
`true`, then the highlight style will be applied to the `(i,j)` element.
Otherwise, the default style will be used.

Notice that if multiple highlighters are valid for the element `(i,j)`, then the
applied style will be equal to the first match considering the order in the
Tuple `highlighters`.

"""
pretty_table(data::AbstractMatrix{T1}, header::AbstractVecOrMat{T2},
             tf::PrettyTableFormat = unicode; kwargs...) where {T1,T2} =
    pretty_table(stdout, data, header, tf; kwargs...)

function pretty_table(io::IO, data::AbstractMatrix{T1},
                      header::AbstractVecOrMat{T2},
                      tf::PrettyTableFormat = unicode; kwargs...) where {T1,T2}

    isempty(header) && ( header = ["Col. " * string(i) for i = 1:size(data,2)] )
    _pretty_table(io, data, header, tf; kwargs...)
end

pretty_table(data::AbstractMatrix{T}, tf::PrettyTableFormat = unicode; kwargs...) where T =
    pretty_table(stdout, data, tf; kwargs...)

pretty_table(io::IO, data::AbstractMatrix{T}, tf::PrettyTableFormat = unicode; kwargs...) where T =
    pretty_table(io, data, [], tf; kwargs...)

pretty_table(table, tf::PrettyTableFormat = unicode; kwargs...) =
    pretty_table(stdout, table, tf; kwargs...)

function pretty_table(io::IO, table, tf::PrettyTableFormat = unicode; kwargs...)
    !Tables.istable(table) && error("table must be compliant with the Table.jl.")

    # Get the data.
    data = Tables.columns(table)

    # Get the table schema to obtain the columns names.
    sch = Tables.schema(table)

    if sch == nothing
        num_cols, num_rows = size(data)
        header = ["Col. " * string(i) for i = 1:num_cols]
    else
        header = [ reshape( [sch.names...], (1,:) );
                   reshape( [sch.types...], (1,:) )]
    end

    _pretty_table(io, data, header, tf; kwargs...)
end

################################################################################
#                              Private Functions
################################################################################

# This is the low level function that prints the table. In this case, `data`
# must be accessed by `[i,j]` and the size of the `header` must be equal to the
# number of columns in `data`.
function _pretty_table(io, data, header, tf::PrettyTableFormat = unicode;
                       border_crayon::Crayon = Crayon(),
                       header_crayon::Union{Crayon,Vector{Crayon}} = Crayon(bold = true),
                       subheader_crayon::Union{Crayon,Vector{Crayon}} = Crayon(foreground = :dark_gray),
                       rownum_header_crayon::Crayon = Crayon(bold = true),
                       text_crayon::Crayon = Crayon(),
                       alignment::Union{Symbol,Vector{Symbol}} = :r,
                       formatter::Dict = Dict(),
                       highlighters::Tuple = (),
                       hlines::Vector{Int} = Int[],
                       linebreaks::Bool = false,
                       noheader::Bool = false,
                       same_column_size::Bool = false,
                       show_row_number::Bool = false)

    # Get information about the table we have to print.
    num_rows, num_cols = size(data)

    num_rows < 1 && error("The table must contain at least 1 row.")

    header_size = size(header)

    # The way we get the number of columns of the header depends on its
    # dimension, because the header can be a vector or a matrix.

    if length(header_size) == 2
        header_num_rows, header_num_cols = size(header)
    else
        header_num_rows = 1
        header_num_cols = length(header)
    end

    !noheader && num_cols != header_num_cols &&
    error("The header length must be equal to the number of columns.")

    # Transform some keywords that are single elements to vectors.
    if !noheader
        if typeof(header_crayon) == Crayon
            header_crayon = [header_crayon for i = 1:num_cols]
        else
            length(header_crayon) != num_cols &&
            error("The length of `header_crayon` must be the same as the number of columns.")
        end

        if typeof(subheader_crayon) == Crayon
            subheader_crayon = [subheader_crayon for i = 1:num_cols]
        else
            length(subheader_crayon) != num_cols &&
            error("The length of `subheader_crayon` must be the same as the number of columns.")
        end
    end

    if typeof(alignment) == Symbol
        alignment = [alignment for i = 1:num_cols]
    else
        length(alignment) != num_cols && error("The length of `alignment` must be the same as the number of rows.")
    end

    # Get the string which is printed when `print` is called in each element of
    # the matrix.
    header_str       = Matrix{String}(undef, header_num_rows, num_cols)
    data_str         = Matrix{Vector{AbstractString}}(undef, num_rows, num_cols)
    num_lines_in_row = ones(Int, num_rows)
    cols_width       = zeros(Int, num_cols)

    @inbounds @views for i = 1:num_cols
        fi = haskey(formatter, i) ? formatter[i] :
                (haskey(formatter, 0) ? formatter[0] : nothing)

        if !noheader
            for j = 1:header_num_rows
                header_str[j,i] = escape_string(sprint(print, header[(i-1)*header_num_rows + j]))

                # Compute the minimum column size to print this string.
                cell_width = length(header_str[j,i])

                # Check if we need to increase the columns size.
                cols_width[i] < cell_width && (cols_width[i] = cell_width)
            end
        end

        for j = 1:num_rows
            data_ij = fi != nothing ? fi(data[j,i], j) : data[j,i]
            data_str_ij = sprint(print, data_ij)

            if linebreaks
                # Get the tokens for each line.
                tokens        = escape_string.(split(data_str_ij, '\n'))
                data_str[j,i] = tokens
                num_lines_ij  = length(tokens)

                # Check if we must update the number of lines in this row.
                num_lines_in_row[j] < num_lines_ij && (num_lines_in_row[j] = num_lines_ij)

                # Compute the maximum length to compute the column size.
                cell_width = maximum(length.(tokens))
            else
                data_str_ij_esc = escape_string(data_str_ij)
                data_str[j,i]   = [data_str_ij_esc]
                cell_width      = length(data_str_ij_esc)
            end

            # Check if we need to increase the columns size.
            cols_width[i] < cell_width && (cols_width[i] = cell_width)
        end
    end

    # The row number width depends on how many digits the total number of rows
    # has and the length of the header "Row".
    row_number_width = max(3,floor(Int, log10(num_rows)) + 1)

    # If the user wants all the columns with the same size, then select the
    # larger.
    same_column_size && (cols_width = [maximum(cols_width) for i = 1:num_cols])

    # Let's create a `IOBuffer` to write everything and then transfer to `io`.
    io_has_color = get(io, :color, false)
    buf_io       = IOBuffer()
    buf          = IOContext(buf_io, :color => io_has_color)

    # Top table line
    # ==========================================================================

    tf.top_line && @_draw_line(buf, tf.up_left_corner, tf.up_intersection,
                               tf.up_right_corner, tf.row, border_crayon,
                               num_cols, cols_width, show_row_number,
                               row_number_width)

    # Header
    # ==========================================================================

    # Header and sub-header texts
    # --------------------------------------------------------------------------

    if !noheader
        @inbounds @views for i = 1:header_num_rows
            @_ps(buf, border_crayon, tf.column)

            if show_row_number
                # The text "Row" must appear only on the first line.

                if i == 1
                    header_row_i_str = " " * @_str_aligned("Row", :r, row_number_width) * " "
                    @_ps(buf, rownum_header_crayon, header_row_i_str)
                else
                    @_ps(buf, rownum_header_crayon, " "^(row_number_width+1))
                end

                @_ps(buf, border_crayon, tf.column)
            end

            for j = 1:num_cols
                header_i_str = " " * @_str_aligned(header_str[i,j], alignment[j], cols_width[j]) * " "

                # Check if we are printing the header or the sub-headers and select
                # the styling accordingly.
                crayon = (i == 1) ? header_crayon[j] : subheader_crayon[j]

                @_ps(buf, crayon,        header_i_str)
                @_ps(buf, border_crayon, tf.column)
            end

            i != header_num_rows && println(buf)
        end

        println(buf)

        # Bottom header line
        #-----------------------------------------------------------------------

        @_draw_line(buf, tf.left_intersection, tf.middle_intersection,
                    tf.right_intersection, tf.row, border_crayon, num_cols,
                    cols_width, show_row_number, row_number_width)
    end

    # Data
    # ==========================================================================

    @inbounds @views for i = 1:num_rows
        for l = 1:num_lines_in_row[i]
            @_ps(buf, border_crayon, tf.column)

            if show_row_number
                if l == 1
                    row_number_i_str = " " * @_str_aligned(string(i), :r, row_number_width) * " "
                else
                    row_number_i_str = " " * @_str_aligned("", :r, row_number_width) * " "
                end

                @_ps(buf, text_crayon,   row_number_i_str)
                @_ps(buf, border_crayon, tf.column)
            end

            for j = 1:num_cols
                if length(data_str[i,j]) >= l
                    data_ij_str = " " * @_str_aligned(data_str[i,j][l], alignment[j], cols_width[j]) * " "
                else
                    data_ij_str = " " * @_str_aligned("", alignment[j], cols_width[j]) * " "
                end

                # If we have highlighters defined, then we need to verify if this
                # data should be highlight.
                printed = false
                crayon  = text_crayon

                for h in highlighters
                    if h.f(data, i, j)
                        crayon = h.crayon
                        @_ps(buf, crayon, data_ij_str)
                        printed = true
                        break
                    end
                end

                !printed && @_ps(buf, text_crayon, data_ij_str)

                @_ps(buf, border_crayon, tf.column)
            end

            println(buf)
        end

        # Check if we must draw a horizontal line here.
        i != num_rows && i in hlines &&
        @_draw_line(buf, tf.left_intersection, tf.middle_intersection,
                    tf.right_intersection, tf.row, border_crayon, num_cols,
                    cols_width, show_row_number, row_number_width)

    end

    # Bottom table line
    # ==========================================================================

    tf.bottom_line && @_draw_line(buf, tf.bottom_left_corner,
                                  tf.bottom_intersection,
                                  tf.bottom_right_corner, tf.row, border_crayon,
                                  num_cols, cols_width, show_row_number,
                                  row_number_width)

    # Print the buffer
    # ==========================================================================
    print(io, String(take!(buf_io)))

    nothing
end


text:
using DataFrames

module TextAnalysis
    using SparseArrays
    using Printf
    using LinearAlgebra

    using Languages
    using DataFrames
    using WordTokenizers

    import DataFrames.DataFrame
    import Base.depwarn

    export AbstractDocument, Document
    export FileDocument, StringDocument, TokenDocument, NGramDocument
    export GenericDocument
    export Corpus, DirectoryCorpus
    export stemmer_types, Stemmer
    export DocumentTermMatrix
    export text, tokens, ngrams
    export text!, tokens!, ngrams!
    export documents
    export language, name, author, timestamp
    export languages, names, authors, timestamps
    export language!, name!, author!, timestamp!
    export languages!, names!, authors!, timestamps!
    export ngram_complexity
    export lexicon, update_lexicon!, lexical_frequency, lexicon_size
    export inverse_index, update_inverse_index!, index_size
    export remove_corrupt_utf8
    export remove_corrupt_utf8!
    export remove_punctuation, remove_numbers, remove_case, remove_whitespace
    export remove_punctuation!, remove_numbers!, remove_case!, remove_whitespace!
    export remove_nonletters, remove_nonletters!
    export remove_words, remove_stop_words, remove_articles
    export remove_words!, remove_stop_words!, remove_articles!
    export remove_definite_articles, remove_indefinite_articles
    export remove_definite_articles!, remove_indefinite_articles!
    export remove_prepositions, remove_pronouns, stem, tag_pos
    export remove_prepositions!, remove_pronouns!, stem!, tag_pos!
    export remove_html_tags, remove_html_tags!
    export prepare!
    export frequent_terms, sparse_terms
    export remove_frequent_terms!, remove_sparse_terms!
    export dtv, each_dtv, dtm, tdm
    export TextHashFunction, index_hash, cardinality, hash_function, hash_function!
    export hash_dtv, each_hash_dtv, hash_dtm, hash_tdm
    export standardize!
    export tf, tf_idf, lsa, lda, summarize
    export tf!, tf_idf!, lsa!, lda!
    export remove_patterns!, remove_patterns

    export strip_patterns, strip_corrupt_utf8, strip_case, stem_words, tag_part_of_speech, strip_whitespace, strip_punctuation
    export strip_numbers, strip_non_letters, strip_indefinite_articles, strip_definite_articles, strip_articles
    export strip_prepositions, strip_pronouns, strip_stopwords, strip_sparse_terms, strip_frequent_terms, strip_html_tags
    export SentimentAnalyzer

    include("tokenizer.jl")
    include("ngramizer.jl")
    include("document.jl")
    include("hash.jl")
    include("corpus.jl")
    include("metadata.jl")
    include("preprocessing.jl")
    # Load libstemmer from our deps.jl
    const depsjl_path = joinpath(dirname(@__FILE__), "..", "deps", "deps.jl")
    if !isfile(depsjl_path)
        error("Snowball Stemmer not installed properly, run Pkg.build(\"TextAnalysis\"), restart Julia and try again")
    end
    include(depsjl_path)

    include("stemmer.jl")
    include("dtm.jl")
    include("tf_idf.jl")
    include("lsa.jl")
    include("lda.jl")
    include("summarizer.jl")
    include("show.jl")
    include("sentiment.jl")
    include("deprecations.jl")
end

ext: jl


text:
@testset "Polygons" begin
  xvert = [-1.0,1.0,0.5,-1.0]
  yvert = [-1.0,-1.0,1.0,1.0]
  p = SchwarzChristoffel.Polygon(xvert,yvert)
  @test length(SchwarzChristoffel.vertex(p)) == 4
  z = ComplexF64(0.2+im*0)
  @test SchwarzChristoffel.isinpoly(z,p)
  z = ComplexF64(-1+im*0)
  @test SchwarzChristoffel.isinpoly(z,p,1e-8)
  beta = SchwarzChristoffel.interiorangle(p)
  @test beta[1] ≈ 0.5
end

@testset "NACA airfoil" begin

  w = SchwarzChristoffel.naca4(0.04,0.4,0.12;len=1)
  p = SchwarzChristoffel.Polygon(w)
  @test p.angle[1] ≈ 0.1130140167872078

end


text:
##########
##########
# Creation of data to test workflow
##########
##########
# AUTHOR: Cole B. Brookson
# DATE OF CREATION: 2021-11-17
##########
##########

# set up =======================================================================

# functions to create species ==================================================

# pick 10 traits - 7 continuous, 3 categorical
# assign 100 prey species trait values randomly
# assign the predator "matching" foraging traits
# break them down into vulnerability traits and foraging traits

"""
    assign_trait_values(
        organism_type,
        organism_id)

Takes in the type of organism and it's unique id and uses the percentage of its
own body size as defined, to return a list of the various traits for that
organism to be passed to the next list.

"""
function assign_trait_values(
    organism_type::String,
    organism_id::Int,
    lower_prey_limit::Float64 = 0.05,
    upper_prey_limit::Float64 = 0.65
    )

    # determine producer or consumer
    if organism_type == "producer"
        # scale for producers should be 0.34 (Brose 2019 paper on body size)
        body_size = rand(Exponential(0.34), 1)
        # set prey limits - empty for producers
        prey_limits = Array([NaN, NaN])
    else # everything here down is for consumers
        # scale for consumers should be 800 (Brose 2019 paper on body size)
        body_size = rand(Exponential(20), 1)
        # set prey limits - empty for producers
        lower_limit = body_size*lower_prey_limit
        upper_limit = body_size*upper_prey_limit
        prey_limits = [lower_limit[1], upper_limit[1]]

    end
    # habitat values get set by same process for both consumers and producers

    # randomly select habitat midpoint
    habitat_midpoint = round(rand(Uniform(0,1), 1)[1], digits = 3)
    # define vector of the midpoint and upper/lower habitat bounds
    habitat = [habitat_midpoint, 0, 0]
    if habitat_midpoint >= 0.4 # for lower bound
        habitat[2] = round((habitat_midpoint - 0.4), digits = 3)
    else
        habitat[2] = 0
    end
    if habitat_midpoint <= 0.6 # for upper bound
        habitat[3] = round((habitat_midpoint + 0.4), digits = 2)
    else
        habitat[3] = 1
    end
    #nutritional value
    nutritional_value = rand(Beta(5, 2))

    #habitat = [habitat_midpoint, 0, 1]
    trait_list = [organism_id, organism_type, round(body_size[1], digits = 2),
                    round(prey_limits[1], digits = 2),
                    round(prey_limits[2], digits = 2), habitat[1],
                    round(habitat[2], digits = 2),
                    round(habitat[3], digits = 2),
                    round(nutritional_value, digits = 2)]

    return trait_list
end

max_stars_repo_name: colebrookson/high-dim-trait-niche

max_issues_repo_name: colebrookson/high-dim-trait-niche

max_forks_repo_name: colebrookson/high-dim-trait-niche


text:
module ArgTools

export
    arg_read,  ArgRead,  arg_readers,
    arg_write, ArgWrite, arg_writers,
    arg_isdir, arg_mkdir, @arg_test

import Base: AbstractCmd, CmdRedirect, Process

if isdefined(Base.Filesystem, :prepare_for_deletion)
    using Base.Filesystem: prepare_for_deletion
else
    function prepare_for_deletion(path::AbstractString)
        try prepare_for_deletion_core(path)
        catch
        end
    end
    function prepare_for_deletion_core(path::AbstractString)
        isdir(path) || return
        chmod(path, filemode(path) | 0o333)
        for name in readdir(path)
            path′ = joinpath(path, name)
            prepare_for_deletion_core(path′)
        end
    end
end

## main API ##

"""
    ArgRead = Union{AbstractString, AbstractCmd, IO}

The `ArgRead` types is a union of the types that the `arg_read` function knows
how to convert into readable IO handles. See [`arg_read`](@ref) for details.
"""
const ArgRead = Union{AbstractString, AbstractCmd, IO}

"""
    ArgWrite = Union{AbstractString, AbstractCmd, IO}

The `ArgWrite` types is a union of the types that the `arg_write` function knows
how to convert into writeable IO handles, except for `Nothing` which `arg_write`
handles by generating a temporary file. See [`arg_write`](@ref) for details.
"""
const ArgWrite = Union{AbstractString, AbstractCmd, IO}

"""
    arg_read(f::Function, arg::ArgRead) -> f(arg_io)

The `arg_read` function accepts an argument `arg` that can be any of these:

- `AbstractString`: a file path to be opened for reading
- `AbstractCmd`: a command to be run, reading from its standard output
- `IO`: an open IO handle to be read from

Whether the body returns normally or throws an error, a path which is opened
will be closed before returning from `arg_read` and an `IO` handle will be
flushed but not closed before returning from `arg_read`.
"""
arg_read(f::Function, arg::ArgRead) = open(f, arg)
arg_read(f::Function, arg::IO) = f(arg)

"""
    arg_write(f::Function, arg::ArgWrite) -> arg
    arg_write(f::Function, arg::Nothing) -> tempname()

The `arg_read` function accepts an argument `arg` that can be any of these:

- `AbstractString`: a file path to be opened for writing
- `AbstractCmd`: a command to be run, writing to its standard input
- `IO`: an open IO handle to be written to
- `Nothing`: a temporary path should be written to

If the body returns normally, a path that is opened will be closed upon
completion; an IO handle argument is left open but flushed before return. If the
argument is `nothing` then a temporary path is opened for writing and closed
open completion and the path is returned from `arg_write`. In all other cases,
`arg` itself is returned. This is a useful pattern since you can consistently
return whatever was written, whether an argument was passed or not.

If there is an error during the evaluation of the body, a path that is opened by
`arg_write` for writing will be deleted, whether it's passed in as a string or a
temporary path generated when `arg` is `nothing`.
"""
function arg_write(f::Function, arg::AbstractString)
    try open(f, arg, write=true)
    catch
        rm(arg, force=true)
        rethrow()
    end
    return arg
end

function arg_write(f::Function, arg::AbstractCmd)
    open(f, arg, write=true)
    return arg
end

function arg_write(f::Function, arg::Nothing)
    file, io = mktemp()
    try f(io)
    catch
        close(io)
        rm(file, force=true)
        rethrow()
    end
    close(io)
    return file
end

function arg_write(f::Function, arg::IO)
    try f(arg)
    finally
        flush(arg)
    end
    return arg
end

"""
    arg_isdir(f::Function, arg::AbstractString) -> f(arg)

The `arg_isdir` function takes `arg` which must be the path to an existing
directory (an error is raised otherwise) and passes that path to `f` finally
returning the result of `f(arg)`. This is definitely the least useful tool
offered by `ArgTools` and mostly exists for symmetry with `arg_mkdir` and to
give consistent error messages.
"""
function arg_isdir(f::Function, arg::AbstractString)
    isdir(arg) || error("arg_isdir: $(repr(arg)) not a directory")
    return f(arg)
end

"""
    arg_mkdir(f::Function, arg::AbstractString) -> arg
    arg_mkdir(f::Function, arg::Nothing) -> mktempdir()

The `arg_mkdir` function takes `arg` which must either be one of:

- a path to an already existing empty directory,
- a non-existent path which can be created as a directory, or
- `nothing` in which case a temporary directory is created.

In all cases the path to the directory is returned. If an error occurs during
`f(arg)`, the directory is returned to its original state: if it already existed
but was empty, it will be emptied; if it did not exist it will be deleted.
"""
function arg_mkdir(f::Function, arg::Union{AbstractString, Nothing})
    existed = false
    if arg === nothing
        arg = mktempdir()
    else
        st = stat(arg)
        if !ispath(st)
            mkdir(arg)
        elseif !isdir(st)
            error("arg_mkdir: $(repr(arg)) not a directory")
        else
            isempty(readdir(arg)) ||
                error("arg_mkdir: $(repr(arg)) directory not empty")
            existed = true
        end
    end
    try f(arg)
    catch
        if existed
            for name in readdir(arg)
                path = joinpath(arg, name)
                prepare_for_deletion(path)
                rm(path, force=true, recursive=true)
            end
        else
            prepare_for_deletion(arg)
            rm(arg, force=true, recursive=true)
        end
        rethrow()
    end
    return arg
end

## test utilities ##

const ARG_READERS = [
    String      => path -> f -> f(path)
    Cmd         => path -> f -> f(`cat $path`)
    CmdRedirect => path -> f -> f(pipeline(path, `cat`))
    IOStream    => path -> f -> open(f, path)
    Process     => path -> f -> open(f, `cat $path`)
]

const ARG_WRITERS = [
    String      => path -> f -> f(path)
    Cmd         => path -> f -> f(`tee $path`)
    CmdRedirect => path -> f -> f(pipeline(`cat`, path))
    IOStream    => path -> f -> open(f, path, write=true)
    Process     => path -> f -> open(f, pipeline(`cat`, path), write=true)
]

@assert all(t <: ArgRead  for t in map(first, ARG_READERS))
@assert all(t <: ArgWrite for t in map(first, ARG_WRITERS))

"""
    arg_readers(arg :: AbstractString, [ type = ArgRead ]) do arg::Function
        ## pre-test setup ##
        @arg_test arg begin
            arg :: ArgRead
            ## test using `arg` ##
        end
        ## post-test cleanup ##
    end

The `arg_readers` function takes a path to be read and a single-argument do
block, which is invoked once for each test reader type that `arg_read` can
handle. If the optional `type` argument is given then the do block is only
invoked for readers that produce arguments of that type.

The `arg` passed to the do block is not the argument value itself, because some
of test argument types need to be initialized and finalized for each test case.
Consider an open file handle argument: once you've used it for one test, you
can't use it again; you need to close it and open the file again for the next
test. This function `arg` can be converted into an `ArgRead` instance using
`@arg_test arg begin ... end`.
"""
function arg_readers(
    body::Function,
    path::AbstractString,
    type::Type = ArgRead,
)
    for (t, reader) in ARG_READERS
        t <: type || continue
        body(reader(path))
    end
end

"""
    arg_writers([ type = ArgWrite ]) do path::String, arg::Function
        ## pre-test setup ##
        @arg_test arg begin
            arg :: ArgWrite
            ## test using `arg` ##
        end
        ## post-test cleanup ##
    end

The `arg_writers` function takes a do block, which is invoked once for each test
writer type that `arg_write` can handle with a temporary (non-existent) `path`
and `arg` which can be converted into various writable argument types which
write to `path`. If the optional `type` argument is given then the do block is
only invoked for writers that produce arguments of that type.

The `arg` passed to the do block is not the argument value itself, because some
of test argument types need to be initialized and finalized for each test case.
Consider an open file handle argument: once you've used it for one test, you
can't use it again; you need to close it and open the file again for the next
test. This function `arg` can be converted into an `ArgWrite` instance using
`@arg_test arg begin ... end`.

There is also an `arg_writers` method that takes a path name like `arg_readers`:

    arg_writers(path::AbstractString, [ type = ArgWrite ]) do arg::Function
        ## pre-test setup ##
        @arg_test arg begin
            # here `arg :: ArgWrite`
            ## test using `arg` ##
        end
        ## post-test cleanup ##
    end

This method is useful if you need to specify `path` instead of using path name
generated by `tempname()`. Since `path` is passed from outside of `arg_writers`,
the path is not an argument to the do block in this form.
"""
function arg_writers(
    body::Function,
    type::Type = ArgWrite,
)
    for (t, writer) in ARG_WRITERS
        t <: type || continue
        path = tempname()
        try body(path, writer(path))
        finally
            rm(path, force=true)
        end
    end
end

function arg_writers(
    body::Function,
    path::AbstractString,
    type::Type = ArgWrite,
)
    for (t, writer) in ARG_WRITERS
        t <: type || continue
        body(writer(path))
    end
end

"""
    @arg_test arg1 arg2 ... body

The `@arg_test` macro is used to convert `arg` functions provided by
`arg_readers` and `arg_writers` into actual argument values. When you write
`@arg_test arg body` it is equivalent to `arg(arg -> body)`.
"""
macro arg_test(args...)
    arg_test(args...)
end

function arg_test(var::Symbol, args...)
    var = esc(var)
    body = arg_test(args...)
    :($var($var -> $body))
end

arg_test(ex::Expr) = esc(ex)

end # module


text:
using SupervisedLearning

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

msg("SupervisedModel: constructors")


text:
include("calendar.jl")
include("vaporpressure.jl")
include("weather.jl")
include("sun.jl")


text:
# # A non-complete list of quantum gates
using Yao, Yao.ConstGate, YaoSym
using YaoExtensions
#using Latexify

pretty_print_number(x; lengthonly=false) = pretty_print_number(stdout, x; lengthonly=lengthonly)
function pretty_print_number(io::IO, x; lengthonly=false)
    sx = string(x)
    lengthonly || print(io, sx)
    return length(sx)
end

function pretty_print_number(io::IO, x::AbstractFloat; lengthonly=false)
    closest_int = round(Int, x)
    if isapprox(x, closest_int, atol=1e-12)
        si = string(closest_int)
        lengthonly || print(io, si)
        return length(si)
    else
        sx = string(x)
        lengthonly || print(io, sx)
        return length(sx)
    end
end

function pretty_print_number(io::IO, x::Complex; atol::Real = 1e-12, lengthonly=false)
    l = 0
    if !isapprox(real(x), 0, atol=atol)
        l += pretty_print_number(io, real(x), lengthonly=lengthonly)
    end
    if !isapprox(imag(x), 0, atol=atol)
        if !isapprox(real(x), 0, atol=atol)
            lengthonly || print(imag(x) > 0 ? "+" : "")
            l += 1
        end
        l += pretty_print_number(io, imag(x), lengthonly=lengthonly)
        lengthonly || print(io, "I")
        l += 1
    else
        if isapprox(real(x), 0, atol=atol)
            lengthonly || print(io, "0")
            l += 1
        end
    end
    return l
end

pretty_print_matrix(m) = pretty_print_matrix(stdout, m)
function pretty_print_matrix(io::IO, m)
    minlen = maximum(pretty_print_number.(m, lengthonly=true))+1
    for i in 1:size(m,1)
        print(io, "[")
        for j in 1:size(m,2)
            l = pretty_print_number(m[i,j])
            print(" "^(minlen-l-(j==size(m,1))))
        end
        println(io, "]")
    end
end

@vars θ
for (sym, CONSTRUCTOR, T) in [(:X, :X, Int), (:Y, :Y, Complex{Int}), (:Z, :Z, Int),
                    (:T, :T, ComplexF16), (:S, :S, ComplexF16), (:CNOT, :(control(2,2,1=>X)), Int),
                    (:Toffoli, :(control(3,(3,2), 1=>X)), Int), (:SWAP, :SWAP, Int),
                    #(:(√X), :(YaoExtensions.SqrtX), ComplexF16), (:(√Y), :(YaoExtensions.SqrtY), ComplexF16),
                    (:(√W), :(rot((X+Y)/sqrt(2), π/2)), ComplexF16),
                    (:(Rx), :(Rx(θ)), Basic), (:(Ry), :(Ry(θ)), Basic),
                    ]
    g = @eval $CONSTRUCTOR
    println("$sym := $(CONSTRUCTOR)")
    pretty_print_matrix(Matrix(mat(T, g)))
    println()
    #println(latexify(Matrix(mat(T, g))))
end


text:
# MCP example by Josephy.

@testset "mcp_josephy" begin

function f!(F, x)
    F[1]=3*x[1]^2+2*x[1]*x[2]+2*x[2]^2+x[3]+3*x[4]-6
    F[2]=2*x[1]^2+x[1]+x[2]^2+3*x[3]+2*x[4]-2
    F[3]=3*x[1]^2+x[1]*x[2]+2*x[2]^2+2*x[3]+3*x[4]-1
    F[4]=x[1]^2+3*x[2]^2+2*x[3]+3*x[4]-3
end

function j!(J, x)
    J[1,1] = 6*x[1]+2*x[2]
    J[1,2] = 2*x[1]+4*x[2]
    J[1,3] = 1
    J[1,4] = 3
    J[2,1] = 4*x[1]+1
    J[2,2] = 2*x[2]
    J[2,3] = 3
    J[2,4] = 2
    J[3,1] = 6*x[1]+x[2]
    J[3,2] = x[1]+4*x[2]
    J[3,3] = 2
    J[3,4] = 3
    J[4,1] = 2*x[1]
    J[4,2] = 6*x[2]
    J[4,3] = 2
    J[4,4] = 3
end

solution = [ 1.22474487, 0., 0., 0.5 ]

df = OnceDifferentiable(f!, j!, rand(4), rand(4))

# Test smooth reformulation with trust region

r = mcpsolve(df, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, j!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :smooth, autodiff = true)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

# Test minmax reformulation with trust region

r = mcpsolve(df, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, j!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

r = mcpsolve(f!, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], reformulation = :minmax, autodiff = true)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

# Test with Newton method

r = mcpsolve(df, [0.00, 0.00, 0.00, 0.00], [1e20, 1e20, 1e20, 1e20], [1.25, 0.00, 0.00, 0.50], method = :newton)
@test converged(r)
@test norm(r.zero - solution) < 1e-8

end


text:
macro cc_cmd(exprs...) return clang_cmd(__module__, __source__, :c, exprs...) end
macro  c_cmd(exprs...) return clang_cmd(__module__, __source__, :c, exprs...) end
macro  c_str(exprs...) return clang_str(__module__, __source__, :c, exprs...) end

Base.String(::Type{Context{:c}}) = "C"

const C_PRIMS   = ("void", "bool", "char", "short", "int", "long", "float", "double", "signed", "unsigned", "_Bool", "_Complex", "const", "restrict", "volatile")
const C_PRIM    = Regex(raw"^\s*((?:"*join(C_PRIMS, '|')*raw")(?:\s+(?:"*join(C_PRIMS, '|')*raw"))*)\s*$")
const C_USER    = r"^\s*(?:(const|restrict|volatile)\s+)?(?:(struct|union|enum)\s+)?(\w+)(?:\s+(const|restrict|volatile))?\s*$"
const C_POINTER = r"^(.+)\s*\*(?:\s*(const|restrict|volatile))?\s*$"
const C_ARRAY   = r"^(.+)\s*\[(\d+)\]\s*$"
const C_TYPES   = raw"\w+(?:(?:[*\s]+)\w+)*(?:[*\s]+)?"
const C_MODULE  = r"\s*(\w+)\.(.*)$"
const C_TYPEVAR = Regex(raw"^\s*("*C_TYPES*raw")\s*$")
const C_FUNCPTR = Regex(raw"^\s*("*C_TYPES*raw")\s*\(\*\)\(([^)]*)\)(const)?(?:\s+__attribute__\(\(([^)]+)\)\))?\s*$")
function getref(ctx::Type{Context{:c}}, str::AbstractString, mod::Union{Symbol, Expr, Nothing} = nothing)
	function decorate(t, set)
		t = "volatile" in set ? :(Cvolatile{$(t)}) : t
		t = "restrict" in set ? :(Crestrict{$(t)}) : t
		t = "const"    in set ?    :(Cconst{$(t)}) : t
		return t
	end

	function sign(t, set)
		t =   "signed" in set ?   :(signed($(t))) : t
		t = "unsigned" in set ? :(unsigned($(t))) : t
		return t
	end

	function array(t, szs = [])
		m = match(C_ARRAY, t)
		if !isnothing(m)
			push!(szs, parse(Int, m.captures[2]))
			return array(m.captures[1], szs)
		end

		isempty(szs) && return nothing

		inner = getref(ctx, t, mod)
		isnothing(inner) && return nothing

		for sz in szs
			inner = decorate(:(Carray{$(inner), $(sz)}), ())
		end
		return inner
	end

	arr = array(str)
	isnothing(arr) || return arr

	m = match(C_POINTER, str)
	if !isnothing(m)
		inner = getref(ctx, m.captures[1], mod)
		isnothing(inner) || return decorate(:(Cptr{$(inner)}), (m.captures[2],))
	end

	m = match(C_FUNCPTR, str)
	if !isnothing(m)
		rettype  = getref(ctx, m.captures[1], mod)
		argtypes = isempty(m.captures[2]) ? () : map(arg -> getref(ctx, arg, mod), split(m.captures[2], ','))
		decor    = (m.captures[3],)
		conv     = isnothing(m.captures[4]) ? () : (QuoteNode(Symbol(m.captures[4])),)

		isnothing(rettype) || any(isnothing, argtypes) || return decorate(:(Cptr{Cfunction{$(rettype), Tuple{$(argtypes...)}, $(conv...)}}), decor)
	end

	m = match(C_PRIM, str)
	if !isnothing(m)
		prims = split(m.captures[1])

		if "_Complex" in prims
			if "double" in prims
				if "long" in prims
					return decorate(:(Ccomplex{Cdouble}), prims)
				else
					return decorate(:(Ccomplex{Clongdouble}), prims)
				end
			elseif "float" in prims
				return decorate(:(Ccomplex{Cfloat}), prims)
			end
		elseif "double" in prims
			if "long" in prims
				return decorate(:(Cdouble), prims)
			else
				return decorate(:(Clongdouble), prims)
			end
		elseif "float" in prims
			return decorate(:(Cfloat), prims)
		elseif "void" in prims
			return decorate(:(Cvoid), prims)
		elseif "bool" in prims || "_Bool" in prims
			return decorate(:(Cbool), prims)
		elseif "char" in prims
			return decorate(sign(:(Cchar), prims), prims)
		elseif "short" in prims
			return decorate(sign(:(Cshort), prims), prims)
		elseif "int" in prims
			return decorate(sign(:(Cint), prims), prims)
		elseif "long" in prims
			if count(isequal("long"), prims) > 1
				return decorate(sign(:(Clonglong), prims), prims)
			else
				return decorate(sign(:(Clong), prims), prims)
			end
		elseif "signed" in prims
			return decorate(:(signed(Cint)), prims)
		elseif "unsigned" in prims
			return decorate(:(unsigned(Cint)), prims)
		end
	end

	m = match(C_USER, str)
	if !isnothing(m)
		decor = (m.captures[1], m.captures[4])
		if isnothing(m.captures[2])
			sym = getname(ctx, "$(m.captures[3])")
			mac = Symbol("@", sym)

			expr = :(@macrocall)
			expr.args[1] = isnothing(mod) ? mac : :($(mod).$(mac))

			expr = isnothing(mod) ?
				:(@static @isdefined($(mac)) ? $(esc(expr)) : $(esc(sym))) :
				:(@static isdefined($(mod), $(QuoteNode(mac))) ? $(esc(expr)) : $(esc(:($(mod).$(sym)))))
		else
			expr = getname(ctx, "$(m.captures[2]) $(m.captures[3])")
			expr = isnothing(mod) ? expr : :($(mod).$(expr))
			expr = esc(expr)
		end
		return decorate(expr, decor)
	end

	strip(str) == "..." && return :(Cvariadic)

	m = match(C_MODULE, str)
	if !isnothing(m)
		str = m.captures[2]
		m   = Symbol(m.captures[1])
		return getref(ctx, str, isnothing(mod) ? m : :($(mod).$(m)))
	end

	return nothing
end

function getbuiltintype(ctx::Type{Context{:c}}, type::CXType)
	n = string(type)
	s = clang_Type_getSizeOf(type)
	a = clang_Type_getAlignOf(type)
	return getbuiltintype(ctx, type.kind, n, s, a)
end

function getbuiltintype(ctx::Type{Context{:c}}, k::CXTypeKind, n::String, s::Integer, a::Integer)
	if k == CXType_Void
		result = :(Cvoid)
	elseif k in (
		CXType_Char_S,
		CXType_SChar,
	)
		result = :(Cchar)
	elseif k == CXType_Short
		result = :(Cshort)
	elseif k == CXType_Int
		result = :(Cint)
	elseif k == CXType_Long
		result = :(Clong)
	elseif k == CXType_LongLong
		result = :(Clonglong)
	elseif k == CXType_Bool && s == sizeof(Bool)
		result = :(Bool)
	elseif k in (
		CXType_Char_U,
		CXType_UChar,
	) || (k == CXType_Bool && s == sizeof(Cuchar))
		result = :(Cuchar)
	elseif k == CXType_UShort || (k == CXType_Bool && s == sizeof(Cushort))
		result = :(Cushort)
	elseif k == CXType_UInt || (k == CXType_Bool && s == sizeof(Cuint))
		result = :(Cuint)
	elseif k == CXType_ULong || (k == CXType_Bool && s == sizeof(Culong))
		result = :(Culong)
	elseif k == CXType_ULongLong || (k == CXType_Bool && s == sizeof(Culonglong))
		result = :(Culonglong)
	elseif k == CXType_Float
		result = :(Cfloat)
	elseif k == CXType_Double
		result = :(Cdouble)
	elseif k == CXType_LongDouble
		result = :(Clongdouble)
	elseif k == CXType_WChar
		result = :(Cwchar_t)
	else
		# create correct size and alignment to mimic the type
		(a in (1, 2, 4, 8, 16) && (s÷a)*a == s) || error("Unhandled sizeof ($(s)) or alignof ($(a)) for compiler built-in `$(n)`")

		t =
			a == 1 ? :(Cmem8) :
			a == 2 ? :(Cmem16) :
			a == 4 ? :(Cmem32) :
			a == 8 ? :(Cmem64) : :(Cmem128)

		if s÷a == 1
			if startswith(n, "_Atomic(")
				n = n[9:end-1]
				t = getref(ctx, n)
				isnothing(t) && error("Unhandled atomic type $(n)")
				return :(Threads.Atomic{$(t)})
			else
				return t
			end
		end
		return :(Carray{$(t), $(s÷a)})
	end
end

function gettype(ctx::Type{Context{:c}}, type::CXType; kwargs...)
	if type.kind in (
		CXType_Void, CXType_Bool,
		CXType_Char_S, CXType_Char_U,
		CXType_SChar, CXType_UChar,
		CXType_Short, CXType_UShort,
		CXType_Int, CXType_UInt,
		CXType_Long, CXType_ULong,
		CXType_LongLong, CXType_ULongLong,
		CXType_Float, CXType_Double, CXType_LongDouble,
		CXType_WChar,
	)
		result = getbuiltintype(ctx, type)
	elseif @isdefined(CXType_Atomic) && type.kind == CXType_Atomic  # libclang 9 lacks atomic
		atomictype = clang_Type_getValueType(type)
		atomictype = gettype(ctx, atomictype; kwargs...)
		result = :(Threads.Atomic{$(atomictype)})
	elseif !@isdefined(CXType_Atomic) && type.kind == CXType_Unexposed && startswith(string(type), "_Atomic(")  # fallback for libclang 9 atomic, etc.
		result = getbuiltintype(ctx, type)
	elseif type.kind in (
		CXType_Typedef,
		CXType_Record,
		CXType_Elaborated,
		CXType_Enum,
	)
		decl = clang_getTypeDeclaration(type)
		loc = clang_getCursorLocation(decl)
		isbuiltin = isnothing(getlocation(loc))

		if isbuiltin
			result = getbuiltintype(ctx, type)
		else
			t = clang_getCursorType(decl)
			result = gettype(ctx, string(t); kwargs...)
		end
	elseif type.kind == CXType_Complex
		cplxtype = clang_getElementType(type)
		cplxtype = gettype(ctx, cplxtype; kwargs...)
		result = :(Ccomplex{$(cplxtype)})
	elseif type.kind == CXType_Pointer
		ptrtype = clang_getPointeeType(type)
		ptrtype = gettype(ctx, ptrtype; kwargs...)
		result = :(Cptr{$(ptrtype)})
	elseif type.kind == CXType_ConstantArray
		num = clang_getNumElements(type)
		arrtype = clang_getElementType(type)
		arrtype = gettype(ctx, arrtype; kwargs...)
		result = :(Carray{$(arrtype), $(num)})
	elseif type.kind == CXType_IncompleteArray
		arrtype = clang_getElementType(type)
		arrtype = gettype(ctx, arrtype; kwargs...)
		result = :(Carray{$(arrtype), 0})
	elseif type.kind in (
		CXType_FunctionProto,
		CXType_FunctionNoProto,
	)
		rettype = clang_getResultType(type)
		rettype = gettype(ctx, rettype; kwargs...)

		num = clang_getNumArgTypes(type)
		argtypes = map(1:num) do ind
			argtype = clang_getArgType(type, ind-1)
			return gettype(ctx, argtype; kwargs...)
		end
		argtypes = Bool(clang_isFunctionTypeVariadic(type)) ? [argtypes..., Cvariadic] : argtypes

		conv = clang_getFunctionTypeCallingConv(type)
		if conv == CXCallingConv_C
			conv = :(cdecl)
		else
			error("Unhandled calling convention $(conv)")
		end

		result = :(Cfunction{$(rettype), Tuple{$(argtypes...)}, $(QuoteNode(conv))})
	else
		error("Unhandle type $(type.kind): $(string(type))")
	end

	if Bool(clang_isVolatileQualifiedType(type))
		# TODO: not handled in get/set property etc. yet
		# result = :(Cvolatile{$(result)})
	end

	if Bool(clang_isRestrictQualifiedType(type))
		# TODO: not handled in get/set property etc. yet
		# result = :(Crestrict{$(result)})
	end

	# NOTE: always want Cconst around everything else to make life easier in get/set
	if Bool(clang_isConstQualifiedType(type))
		result = :(Cconst{$(result)})
	end

	return result
end

function getexprs(ctx::Context{:c}, cursor::CXCursor)
	exprs = []

	getblock(ctx).flags.skip && return exprs
	getblock(ctx).flags.defer && return exprs

	if cursor.kind == CXCursor_TranslationUnit
		append!(exprs, getexprs_tu(ctx, cursor))

		for (_, child) in ctx.macros
			append!(exprs, getexprs_macro(ctx, child))
		end
	elseif cursor.kind == CXCursor_TypedefDecl
		append!(exprs, getexprs_typedef(ctx, cursor))
	elseif cursor.kind in (
		CXCursor_StructDecl,
		CXCursor_UnionDecl,
		CXCursor_EnumDecl,
	)
		for child in children(cursor)
			if child.kind in (
				CXCursor_StructDecl,
				CXCursor_UnionDecl,
				CXCursor_EnumDecl,
				CXCursor_TypedefDecl,
			)
				append!(exprs, getexprs(ctx, child))
			end
		end

		append!(exprs, getexprs_opaque(ctx, cursor))
	elseif cursor.kind in (
		CXCursor_VarDecl,
		CXCursor_FunctionDecl,
	)
		for child in children(cursor)
			if child.kind in (
				CXCursor_StructDecl,
				CXCursor_UnionDecl,
				CXCursor_EnumDecl,
				CXCursor_TypedefDecl,
				CXCursor_ParmDecl,
			)
				append!(exprs, getexprs(ctx, child))
			end
		end

		append!(exprs, getexprs_binding(ctx, cursor))
	elseif cursor.kind == CXCursor_ParmDecl
		for child in children(cursor)
			if child.kind in (
				CXCursor_StructDecl,
				CXCursor_UnionDecl,
				CXCursor_EnumDecl,
				CXCursor_TypedefDecl,
			)
				append!(exprs, getexprs(ctx, child))
			end
		end
	elseif cursor.kind == CXCursor_InclusionDirective
		append!(exprs, getexprs_include(ctx, cursor))
	elseif cursor.kind == CXCursor_MacroDefinition
		getblock(ctx).flags.nomacro || push!(ctx.macros, string(cursor) => cursor)
	elseif !(cursor.kind in (
		CXCursor_FieldDecl,
		CXCursor_EnumConstantDecl,
		CXCursor_ParmDecl,
		CXCursor_MacroInstantiation,
	))
		getblock(ctx).flags.notify && @warn "Skipping unsupported $(cursor.kind) expression:  $(string(cursor))"  (_module = ctx.mod)  (_file = String(getblock(ctx).loc.file))  (_line = getblock(ctx).loc.line)
	end

	return exprs
end

function getexprs_typedef(ctx::Context{:c}, cursor::CXCursor)
	getblock(ctx).flags.notype && return ()

	name  = gettype(ctx, string(cursor))
	jlsym = getjl(ctx, string(cursor))
	docs  = getdocs(ctx, cursor)

	type = clang_getTypedefDeclUnderlyingType(cursor)
	if type.kind == CXType_Elaborated
		# get from elaborated to actual record definition
		decl = clang_getTypeDeclaration(type)
		type = clang_getCursorType(decl)
		docs = jlsym == getjl(ctx, string(decl)) ? nothing : docs  # avoid "replacing docs" warnings when julia-ized names are the same if struct/union/enum was stripped
	end
	tname = gettype(ctx, type)
	builtin = nothing
	exports = ()

	decl = clang_getTypeDeclaration(type)
	loc = clang_getCursorLocation(decl)
	isbuiltin = isnothing(getlocation(loc))
	if isbuiltin && type.kind in (
		CXType_Typedef,
		CXType_Elaborated,
		CXType_Record,
		CXType_Enum,
	)
		newname = gettype(ctx, string(type))
		jlname = getjl(ctx, string(type))
		builtin = :(const $(newname) = $(tname))
		tname = newname
		exports = ((tname, jlname, nothing),)
	end

	# nested anonymous opaque type had this typedef's name propagated to it so no expr needed
	return tname == name ? () : getexprs(ctx, ((name, jlsym, docs), exports...),
		builtin,
		:(const $(name) = $(tname)),
	)
end

function getexprs_opaque(ctx::Context{:c}, cursor::CXCursor)
	exprs = []

	getblock(ctx).flags.notype && return exprs

	type = clang_getCursorType(cursor)
	decl = clang_getTypeDeclaration(type)

	size = clang_Type_getSizeOf(type)
	isdecl = Bool(clang_isCursorDefinition(cursor))
	isanon = Bool(clang_Cursor_isAnonymous(cursor))

	kind =
		cursor.kind == CXCursor_EnumDecl ? Cenum :
		cursor.kind == CXCursor_StructDecl ? Cstruct : Cunion
	(name, absname) = isanon ?
		(gettype(ctx, type), kind) :
		(gettype(ctx, type, isbitstype = true), gettype(ctx, type))
	(jlsym, jlabssym) = isanon ?
		(getjl(ctx, string(type)), nothing) :
		(nothing, getjl(ctx, string(type)))

	def = clang_getCursorDefinition(cursor)
	loc = getlocation(clang_getCursorLocation(def))

	if !isanon && (isnothing(loc) || getblock(ctx).flags.libc || (loc.file == header(ctx) || haskey(ctx.hdrs, loc.file)))
		append!(exprs, getexprs(ctx, ((absname, jlabssym, nothing),),
			:(abstract type $(absname) <: $(kind) end),
		))
	end

	if isdecl
		docs = getdocs(ctx, cursor)
		common = (
			:(CBinding.bitstype(::Type{$(name)}) = $(name)),
			!isanon ? :(CBinding.bitstype(::Type{$(absname)}) = $(name)) : nothing,
		)

		if cursor.kind == CXCursor_EnumDecl
			append!(exprs, getexprs_enum(ctx, cursor, name, absname, jlsym, jlabssym, docs, common))
		else
			append!(exprs, getexprs_aggregate(ctx, cursor, name, absname, jlsym, jlabssym, docs, common))
		end
	end

	return exprs
end

function getexprs_enum(ctx::Context{:c}, cursor::CXCursor, name, absname, jlsym, jlabssym, docs, common)
	type = clang_getCursorType(cursor)
	size = clang_Type_getSizeOf(type)
	inttype = clang_getEnumDeclIntegerType(cursor)
	itype = gettype(ctx, inttype)

	values = map(filter(child -> child.kind == CXCursor_EnumConstantDecl, children(cursor))) do val
		j = Symbol(string(val))
		v = inttype.kind in (
			CXType_UChar,
			CXType_Char_U,
			CXType_UShort,
			CXType_UInt,
			CXType_ULong,
			CXType_ULongLong,
		) ? clang_getEnumConstantDeclUnsignedValue(val) : clang_getEnumConstantDeclValue(val)
		d = getdocs(ctx, val)

		return (j, v, d)
	end

	return getexprs(ctx, (
		(!isnothing(jlabssym) ? ((absname, jlabssym, docs),) : ())...,
		(name, jlsym, docs),
		map(((j, v, d),) -> (esc(getname(ctx, j)), esc(j), d), values)...,
	),
		:(primitive type $(name) <: $(absname) $(size*8) end),
		:((::$(Type){$(name)})($(esc(:val))::Integer = zero($(itype))) = Core.Intrinsics.bitcast($(name), convert($(itype), $(esc(:val))))),

		# values = Tuple{
		# 	Tuple{:name, value},  # name and literal value
		# }
		:(CBinding.values(::Type{$(name)}) = Tuple{$(map(((j, v, d),) -> :(Tuple{$(QuoteNode(getname(ctx, j))), $(v)}), values)...)}),
		:(Base.Enums.basetype(::Type{$(name)}) = $(itype)),
		common...,
		map(((j, v, d),) -> :(const $(esc(getname(ctx, j))) = $(name)($(v))), values)...,
	)
end

function getexprs_aggregate(ctx::Context{:c}, cursor::CXCursor, name, absname, jlsym, jlabssym, docs, common)
	type = clang_getCursorType(cursor)
	size = clang_Type_getSizeOf(type)

	# children are ordered with type defined before the field using it, so we iterate in reverse to add the field first and then not add the type's fields in that case
	fields = []
	lastField = nothing
	function getfields(kids)
		for c in reverse(eachindex(kids))
			child = kids[c]
			if child.kind == CXCursor_FieldDecl
				t = clang_getCursorType(child)
				d = clang_getTypeDeclaration(t)

				if t.kind in (
					CXType_Void, CXType_Bool,
					CXType_Char_S, CXType_Char_U,
					CXType_SChar, CXType_UChar,
					CXType_Short, CXType_UShort,
					CXType_Int, CXType_UInt,
					CXType_Long, CXType_ULong,
					CXType_LongLong, CXType_ULongLong,
					CXType_Float, CXType_Double, CXType_LongDouble,
					CXType_Complex,
				)
					lastField = nothing
				elseif t.kind in (
					CXType_Typedef,
					CXType_Elaborated,
				)
					lastField = clang_getTypeDeclaration(clang_getCursorType(clang_getTypeDeclaration(t)))
				elseif t.kind in (
					CXType_ConstantArray,
					CXType_IncompleteArray,
				)
					lastField = clang_getTypeDeclaration(clang_getElementType(t))
				elseif t.kind == CXType_Pointer
					lastField = clang_getTypeDeclaration(clang_getPointeeType(t))
				else
					error("Unhandled field nested type $(t.kind)")
				end

				n = string(child)
				isempty(n) && continue

				b = -1
				o = clang_Type_getOffsetOf(type, n)
				if Bool(clang_Cursor_isBitField(child))
					b = clang_getFieldDeclBitWidth(child)
					b > 0 || continue
				end

				t = gettype(ctx, t)
				push!(fields, :(Tuple{$(QuoteNode(Symbol(n))), Tuple{$(t), bitstype($(t)), integraltype($(t)), $(o÷8), $(o & 0b111), $(b)}}))
			elseif child.kind in (
				CXCursor_StructDecl,
				CXCursor_UnionDecl,
			)
				if Bool(clang_Cursor_isAnonymous(child))
					if isnothing(lastField) || !Bool(clang_equalCursors(lastField, child))
						getfields(children(child))
					end
				end
			end
		end
	end
	getfields(children(cursor))
	reverse!(fields)

	return getexprs(ctx, (
		(!isnothing(jlabssym) ? ((absname, jlabssym, docs),) : ())...,
		(name, jlsym, docs),
	),
		:(
			struct $(name) <: $(absname)
				mem::NTuple{$(size), UInt8}

				$(name)(::UndefInitializer, $(esc(:mem))::NTuple{$(size), UInt8}) = new($(esc(:mem)))
			end
		),

		# fields = Tuple{
		# 	# a field's name, type, type with const/volatile/restrict removed, integral equivalent, bytes+bits offset, and bitfield size (-1 for non-bitfield)
		# 	Tuple{:name, Tuple{FieldType, UnqualifiedFieldType, IntegerType, bytes, bits, size}},
		# }
		:(CBinding.fields(::Type{$(name)}) = Tuple{$(fields...)}),
		common...,
	)
end

function getexprs_binding(ctx::Context{:c}, cursor::CXCursor)
	exprs = []

	getblock(ctx).flags.nofunc && cursor.kind == CXCursor_FunctionDecl && return exprs
	getblock(ctx).flags.novar && cursor.kind == CXCursor_VarDecl && return exprs

	exposed = clang_Cursor_getStorageClass(cursor)
	visible = clang_getCursorVisibility(cursor)
	linkage = clang_getCursorLinkage(cursor)
	isinlined = cursor.kind == CXCursor_FunctionDecl && Bool(clang_Cursor_isFunctionInlined(cursor))
	if visible in (
		CXVisibility_Default,
		CXVisibility_Protected,
	) && (
		isinlined || (
			exposed in (
				CX_SC_None,
				CX_SC_Extern,
			) && linkage in (
				CXLinkage_External,
			)
		)
	)
		name = string(cursor)
		binding = Symbol(:Cbinding_, name)
		binding in ctx.bindings && return exprs
		push!(ctx.bindings, binding)
		binding = esc(binding)

		jlsym = getjl(ctx, name)
		sym = gettype(ctx, name)
		docs = getdocs(ctx, cursor)

		type = clang_getCursorType(cursor)
		if cursor.kind == CXCursor_VarDecl
			lib = getlib(ctx, name)
			append!(exprs, getexprs(ctx, ((sym, jlsym, docs),),
				:(struct $(binding){$(getjl(ctx, :name))} <: Cbinding{$(gettype(ctx, type)), $(QuoteNode(Symbol(lib))), $(QuoteNode(Symbol(name)))} end),
				:(const $(sym) = $(binding){$(QuoteNode(Symbol(name)))}()),
			))
		else
			if isinlined
				if !getblock(ctx).flags.wrap
					getblock(ctx).flags.notify && @warn "Skipping inline function `$(name)` (enable wrapping with the 'w' string macro option)"  (_module = ctx.mod)  (_file = String(getblock(ctx).loc.file))  (_line = getblock(ctx).loc.line)
					return exprs
				end

				unname = "cbinding__$(name)"
				code = getcode(ctx, cursor)
				code = replace(code, r"(^|\s)inline\s" => s"\1")
				code = replace(code, r"(^|\s)static\s" => s"\1")
				code = replace(code, r"(^|\s)extern\s" => s"\1")
				code = replace(code, r"(\W)"*name*"(" => SubstitutionString("\\1$(unname)("))
				code = "$(lstrip(code)) __attribute__((alias($(repr(name)))));"
				push!(getblock(ctx).inlines, code)

				name = unname
				lib  = getlib(ctx)
				push!(exprs,
					:(include_dependency($(String(lib.value)))),
				)
			else
				lib = getlib(ctx, name)
			end

			rettype = clang_getResultType(type)

			num = clang_Cursor_getNumArguments(cursor)
			args = map(1:num) do ind
				arg = clang_Cursor_getArgument(cursor, ind-1)

				argname = string(arg)
				argname = isempty(argname) ? getjl(ctx, "arg$(ind)") : gettype(ctx, argname)
				argtype = clang_getCursorType(arg)

				return argname => gettype(ctx, argtype)
			end
			args = Bool(clang_Cursor_isVariadic(cursor)) ? [args..., :($(getjl(ctx, :vararg))...) => :(Cvariadic)] : args

			conv = clang_getFunctionTypeCallingConv(type)
			if conv == CXCallingConv_C
				conv = :(cdecl)
			else
				error("Unhandled calling convention $(conv)")
			end

			func = getjl(ctx, :func)
			append!(exprs, getexprs(ctx, ((sym, jlsym, docs),),
				:(struct $(binding){$(getjl(ctx, :name))} <: Cbinding{Cfunction{$(gettype(ctx, rettype)), Tuple{$(map(last, args)...)}, $(QuoteNode(conv))}, $(lib), $(QuoteNode(Symbol(name)))} end),
				:(const $(sym) = $(binding){$(QuoteNode(Symbol(name)))}()),
				:(($(func)::$(binding))($(map(first, args)...),) = funccall($(func), $(map(first, args)...),)),
			))
		end
	end

	return exprs
end

function getexprs_macro(ctx::Context{:c}, cursor::CXCursor)
	exprs = []

	expr = Bool(clang_Cursor_isMacroFunctionLike(cursor)) ? nothing : ""
	for (ind, token) in enumerate(tokenize(ctx.tu[], cursor))
		ind == 1 && continue  # first token is the macro name itself, so skip it
		expr === nothing && break

		str = string(ctx.tu[], token)
		kind = clang_getTokenKind(token)
		if kind == CXToken_Literal
			int = match(r"^(0|0x|0b)?([\da-f]+)([ul]*)$", lowercase(str))
			float = match(r"^(\d*\.)?(\d*)(e[-+]?\d+)?([fl])?$", lowercase(str))
			if !isnothing(int)
				(pre, val, suf) = int.captures
				isUnsigned = occursin('u', suf)
				howLong = count(isequal('l'), suf)

				typ = howLong == 0 ? Cint : (howLong == 1 ? Clong : Clonglong)
				typ = isUnsigned || !isnothing(pre) ? unsigned(typ) : typ
				pre = isnothing(pre) ? "" : (pre == "0" ? "0o" : pre)

				str = repr(parse(isUnsigned ? UInt128 : Int128, pre*val))
			elseif !isnothing(float)
				(val1, val2, exp, suf) = float.captures
				val = val1*val2
				val = val[end] == '.' ? val*"0" : val
				val = val[1] == '.' ? "0"*val : val

				if suf == "l"
					expr = nothing  # TODO: convert long double literals
					continue
				end
				typ = suf == "f" ? Cfloat : Cdouble
				exp = isnothing(exp) ? "" : exp

				str = repr(parse(Cdouble, val*exp))
			end

			if endswith(expr, "\")") && startswith(str, '"')
				expr *= "*"
			end
			expr *= "("*str*")"
		elseif kind == CXToken_Punctuation
			expr *=
				str == "->" ? "." :
				str == "[" ? "[(" :
				str == "]" ? ")+1]" :
				str
		elseif kind == CXToken_Identifier
			if endswith(expr, '.')
				expr *= str
			else
				expr *= "("*String(getname(ctx, string(ctx.mod)*"."*str))*")"
			end
		elseif kind == CXToken_Keyword
			# TODO: convert casts (int const **) to c"int const **"
			expr = nothing
		else
			expr = nothing
		end
	end

	expr = isnothing(expr) ? nothing : "quote ; $(join(expr)) ; end"
	expr = isnothing(expr) ? nothing : Meta.parse(expr, raise = false)
	expr = isnothing(expr) || expr.head in (:error, :incomplete) ? nothing : expr

	if isnothing(expr)
		getblock(ctx).flags.notify && @warn "Skipping macro `$(string(cursor))`"  (_module = ctx.mod)  (_file = String(getblock(ctx).loc.file))  (_line = getblock(ctx).loc.line)
	else
		name = getname(ctx, string(cursor))
		sym = esc(Symbol("@", name))
		jlsym = esc(Symbol("@", string(cursor)))
		docs = getdocs(ctx, cursor)

		append!(exprs, getexprs(ctx, ((sym, jlsym, docs),), :(macro $(esc(name))() ; return $(esc(expr)) ; end)))
	end

	return exprs
end


text:
struct Archer
    name::String
    health::Int
    arrows::Int
end

struct Pikeman
    name::String
    health::Int
end

struct Knight
    name::String
    health::Int
    mounted::Bool
end

iswinner(a::Archer, b::Pikeman) = true
iswinner(a::Archer, b::Knight) = false
iswinner(a::Knight, b::Pikeman) = false

function iswinner(a, b)
    if typeof(a) == typeof(b)
        false
    else
        !iswinner(b, a)
    end
end

function battle(a, b)
    if iswinner(a, b)
        println(a.name, " defeated ", b.name)
    elseif iswinner(b, a)
        println(b.name, " defeated ", a.name)
    else
        println(a.name, " and ", b.name, " destroyed each other")
    end
end

max_stars_repo_name: ordovician/BattleSim

max_issues_repo_name: ordovician/BattleSim

max_forks_repo_name: ordovician/BattleSim


text:
using Statistics
"""
    CrossEntropyLoss(dims=1)
    (l::CrossEntropyLoss)(scores, answers; average=true)
Calculates negative log likelihood error on your predicted scores.
`answers` should be integers corresponding to correct class indices.
If an answer is 0, loss from that answer will not be included.
This is usefull feature when you are working with unequal length sequences.

if dims==1
* size(scores) = C,[B,T1,T2,...]
* size(answers)= [B,T1,T2,...]
elseif dims==2
* size(scores) = [B,T1,T2,...],C
* size(answers)= [B,T1,T2,...]
"""
struct CrossEntropyLoss <: Loss
    dims::Integer
end
CrossEntropyLoss(;dims=1) = CrossEntropyLoss(dims)
(l::CrossEntropyLoss)(y,answers::Array{<:Integer}; average=true) = nllmask(y, answers; dims=l.dims, average=average)

"""
    BCELoss(average=true)
    (l::BCELoss)(scores, answers)
    Computes binary cross entropy given scores(predicted values) and answer labels. answer values should be {0,1}, then it returns negative of
    mean|sum(answers * log(p) + (1-answers)*log(1-p)) where p is equal to 1/(1 + exp.(scores)). See also LogisticLoss.
"""
struct BCELoss <: Loss end
(l::BCELoss)(y,answers::Array{<:Integer})=bce(y,answers)

"""
    LogisticLoss(average=true)
    (l::LogisticLoss)(scores, answers)
    Computes logistic loss given scores(predicted values) and answer labels. answer values should be {-1,1}, then it returns mean|sum(log(1 +
    exp(-answers*scores))). See also `BCELoss`.
"""
struct LogisticLoss <: Loss end
(l::LogisticLoss)(y,answers::Array{<:Integer})=logistic(y,answers)

####
#### Utils
####
function nllmask(y,a::AbstractArray{<:Integer}; dims=1, average=true)
    indices = findindices(y, a, dims=dims)
    lp = logp(y,dims=dims)[indices]
    average ? -mean(lp) : -sum(lp)
end

function findindices(y,a::AbstractArray{<:Integer}; dims=1)
    n       = length(a)
    nonmask = a .> 0
    indices = Vector{Int}(undef,sum(nonmask))
    if dims == 1                   # instances in first dimension
        y1 = size(y,1)
        y2 = div(length(y),y1)
        if n != y2; throw(DimensionMismatch()); end
        k = 1
        @inbounds for (j,v) in enumerate(nonmask)
            !v && continue
            indices[k] = (j-1)*y1 + a[j]
            k += 1
        end
    elseif dims == 2               # instances in last dimension
        y2 = size(y,ndims(y))
        y1 = div(length(y),y2)
        if n != y1; throw(DimensionMismatch()); end
        k = 1
        @inbounds for (j,v) in enumerate(nonmask)
            !v && continue
            indices[k] = (a[j]-1)*y1 + j
            k += 1
        end
    else
        error("findindices only supports dims = 1 or 2")
    end
    return indices
end


text:
# This file pulls the dependencies using git branches for development

using Pkg
exapfspec = PackageSpec(url="https://github.com/exanauts/ExaPF.jl.git", rev="proxal")
exatronspec = PackageSpec(url="https://github.com/exanauts/ExaTron.jl.git", rev="fp/proxal")
Pkg.add([exapfspec, exatronspec])


text:
#src # Unstructured meshes with HOHQMesh.jl

# Trixi.jl supports numerical approximations on unstructured quadrilateral meshes
# with the [`UnstructuredMesh2D`](@ref) mesh type.

# The purpose of this tutorial is to demonstrate how to use the `UnstructuredMesh2D`
# functionality of Trixi.jl. This begins by running and visualizing an available unstructured
# quadrilateral mesh example. Then, the tutorial will demonstrate how to
# conceptualize a problem with curved boundaries, generate
# a curvilinear mesh using the available software in the Trixi.jl ecosystem,
# and then run a simulation using Trixi.jl on said mesh.

# Unstructured quadrilateral meshes can be made
# with the [High-Order Hex-Quad Mesh (HOHQMesh) generator](https://github.com/trixi-framework/HOHQMesh)
# created and developed by David Kopriva.
# HOHQMesh is a mesh generator specifically designed for spectral element methods.
# It provides high-order boundary curve information (needed to accurately set boundary conditions)
# and elements can be larger (due to the high accuracy of the spatial approximation)
# compared to traditional finite element mesh generators.
# For more information about the design and features of HOHQMesh one can refer to its
# [official documentation](https://trixi-framework.github.io/HOHQMesh/).

# HOHQMesh is incorporated into the Trixi.jl framework via the registered Julia package
# [HOHQMesh.jl](https://github.com/trixi-framework/HOHQMesh.jl).
# This package provides a Julia wrapper for the HOHQMesh generator that allows users to easily create mesh
# files without the need to build HOHQMesh from source. To install the HOHQMesh package execute
# ```julia
# import Pkg; Pkg.add("HOHQMesh")
# ```
# Now we are ready to generate an unstructured quadrilateral mesh that can be used by Trixi.jl.

# ## Running and visualizing an unstructured simulation

# Trixi.jl supports solving hyperbolic problems on several mesh types.
# There is a default example for this mesh type that can be executed by

using Trixi
redirect_stdout(devnull) do # code that prints annoying stuff we don't want to see here #hide #md
trixi_include(default_example_unstructured())
end #hide #md

# This will compute a smooth, manufactured solution test case for the 2D compressible Euler equations
# on the curved quadrilateral mesh described in the
# [Trixi documentation](https://trixi-framework.github.io/Trixi.jl/stable/meshes/unstructured_quad_mesh/).

# Apart from the usual error and timing output provided by the Trixi.jl run, it is useful to visualize and inspect
# the solution. One option available in the Trixi.jl framework to visualize the solution on
# unstructured quadrilateral meshes is post-processing the
# Trixi.jl output file(s) with the [`Trixi2Vtk`](https://github.com/trixi-framework/Trixi2Vtk.jl) tool
# and plotting them with [ParaView](https://www.paraview.org/download/).

# To convert the HDF5-formatted `.h5` output file(s) from Trixi into VTK format execute the following

using Trixi2Vtk
redirect_stdout(devnull) do # code that prints annoying stuff we don't want to see here #hide #md
trixi2vtk("out/solution_000180.h5", output_directory="out")
end #hide #md

# Note this step takes about 15-30 seconds as the package `Trixi2Vtk` must be precompiled and executed for the first time
# in your REPL session. The `trixi2vtk` command above will convert the solution file at the final time into a `.vtu` file
# which can be read in and visualized with ParaView. Optional arguments for `trixi2vtk` are: (1) Pointing to the `output_directory`
# where the new files will be saved; it defaults to the current directory. (2) Specifying a higher number of
# visualization nodes. For instance, if we want to use 12 uniformly spaced nodes for visualization we can execute

redirect_stdout(devnull) do # code that prints annoying stuff we don't want to see here #hide #md
trixi2vtk("out/solution_000180.h5", output_directory="out", nvisnodes=12)
end #hide #md

# By default `trixi2vtk` sets `nvisnodes` to be the same as the number of nodes specified in
# the `elixir` file used to run the simulation.

# Finally, if you want to convert all the solution files to VTK execute

redirect_stdout(devnull) do # code that prints annoying stuff we don't want to see here #hide #md
trixi2vtk("out/solution_000*.h5", output_directory="out", nvisnodes=12)
end #hide #md

# then it is possible to open the `.pvd` file with ParaView and create a video of the simulation.

# ## Creating a mesh using HOHQMesh

# The creation of an unstructured quadrilateral mesh using HOHQMesh.jl is driven by a **control file**. In this file the user dictates
# the domain to be meshed, prescribes any desired boundary curvature, the polynomial order of said boundaries, etc.
# In this tutorial we cover several basic features of the possible control inputs. For a complete discussion
# on this topic see the [HOHQMesh control file documentation](https://trixi-framework.github.io/HOHQMesh/the-control-file/).

# To begin, we provide a complete control file in this tutorial. After this we give a breakdown
# of the control file components to explain the chosen parameters.

# Suppose we want to create a mesh of a domain with straight sided
# outer boundaries and a curvilinear "ice cream cone" shaped object at its center.

# ![mesh_boundary_cartoon](https://user-images.githubusercontent.com/25242486/129603954-9788500d-bba8-49be-8e6f-7555099dbf7c.png)

# The associated `ice_cream_straight_sides.control` file is created below.
open("out/ice_cream_straight_sides.control", "w") do io
  println(io, raw"""
\begin{CONTROL_INPUT}
    \begin{RUN_PARAMETERS}
        mesh file name   = ice_cream_straight_sides.mesh
        plot file name   = ice_cream_straight_sides.tec
        stats file name  = none
        mesh file format = ISM-v2
        polynomial order = 4
        plot file format = skeleton
    \end{RUN_PARAMETERS}

    \begin{BACKGROUND_GRID}
        x0 = [-8.0, -8.0, 0.0]
        dx = [1.0, 1.0, 0.0]
        N  = [16,16,1]
    \end{BACKGROUND_GRID}

    \begin{SPRING_SMOOTHER}
        smoothing            = ON
        smoothing type       = LinearAndCrossBarSpring
        number of iterations = 25
    \end{SPRING_SMOOTHER}

\end{CONTROL_INPUT}

\begin{MODEL}

    \begin{INNER_BOUNDARIES}

        \begin{CHAIN}
            name = IceCreamCone
            \begin{END_POINTS_LINE}
                name = LeftSlant
                xStart = [-2.0, 1.0, 0.0]
                xEnd   = [ 0.0, -3.0, 0.0]
            \end{END_POINTS_LINE}

            \begin{END_POINTS_LINE}
                name = RightSlant
                xStart = [ 0.0, -3.0, 0.0]
                xEnd   = [ 2.0, 1.0, 0.0]
            \end{END_POINTS_LINE}

            \begin{CIRCULAR_ARC}
                name        = IceCream
                units       = degrees
                center      = [ 0.0, 1.0, 0.0]
                radius      = 2.0
                start angle = 0.0
                end angle   = 180.0
            \end{CIRCULAR_ARC}
        \end{CHAIN}

    \end{INNER_BOUNDARIES}

\end{MODEL}
\end{FILE}
""")
end

# The first three blocks of information are wrapped within a `CONTROL_INPUT` environment block as they define the
# core components of the quadrilateral mesh that will be generated.

# The first block of information in `RUN_PARAMETERS` is
# ```
# \begin{RUN_PARAMETERS}
#    mesh file name   = ice_cream_straight_sides.mesh
#    plot file name   = ice_cream_straight_sides.tec
#    stats file name  = none
#    mesh file format = ISM-v2
#    polynomial order = 4
#    plot file format = skeleton
# \end{RUN_PARAMETERS}
# ```

# The mesh and plot file names will be the files created by HOHQMesh once successfully executed. The stats file name is
# available if you wish to also save a collection of mesh statistics. For this example it is deactivated.
# These file names given within `RUN_PARAMETERS` **should match** that of the control file, and although this is not required by
# HOHQMesh, it is a useful style convention.
# The mesh file format `ISM-v2` in the format currently required by Trixi. The `polynomial order` prescribes the order
# of an interpolant constructed on the Chebyshev-Gauss-Lobatto nodes that is used to represent any curved boundaries on a particular element.
# The plot file format of `skeleton` means that visualizing the plot file will only draw the element boundaries (and no internal nodes).
# Alternatively, the format can be set to `sem` to visualize the interior nodes of the approximation as well.

# The second block of information in `BACKGOUND_GRID` is
# ```
# \begin{BACKGROUND_GRID}
#   x0 = [-8.0, -8.0, 0.0]
#   dx = [1.0, 1.0, 0.0]
#   N  = [16,16,1]
# \end{BACKGROUND_GRID}
# ```

# This lays a grid of Cartesian elements for the domain beginning at the point `x0` as its bottom-left corner.
# The value of `dx`, which could differ in each direction if desired, controls the step size taken in each Cartesian direction.
# The values in `N` set how many Cartesian box elements are set in each coordinate direction.
# The above parameters define a $16\times 16$ element square mesh on $[-8,8]^2$.
# Further, this sets up four outer boundaries of the domain that are given the default names: `Top, Left, Bottom, Right`.

# The third block of information in `SPRING_SMOOTHER` is
# ```
# \begin{SPRING_SMOOTHER}
#    smoothing            = ON
#    smoothing type       = LinearAndCrossBarSpring
#    number of iterations = 25
# \end{SPRING_SMOOTHER}
# ```

# Once HOHQMesh generates the mesh, a spring-mass-dashpot model is created to smooth the mesh and create "nicer" quadrilateral elements.
# The [default parameters of Hooke's law](https://trixi-framework.github.io/HOHQMesh/the-control-input/#the-smoother)
# for the spring-mass-dashpot model have been selected after a fair amount of experimentation across many meshes.
# If you wish to deactivate this feature you can set `smoothing = OFF` (or remove this block from the control file).

# After the `CONTROL_INPUT` environment block comes the `MODEL` environment block. It is here where the user prescribes curved boundary information with either:
# * An `OUTER_BOUNDARY` (covered in the next section of this tutorial).
# * One or more `INNER_BOUNDARIES`.

# There are several options to describe the boundary curve data to HOHQMesh like splines or parametric curves.

# For the example `ice_cream_straight_sides.control` we define three internal boundaries; two straight-sided and
# one as a circular arc.
# Within the HOHQMesh control input each curve must be assigned to a `CHAIN` as shown below in the complete
# `INNER_BOUNDARIES` block.
# ```
# \begin{INNER_BOUNDARIES}
#
#    \begin{CHAIN}
#    name = IceCreamCone
#    \begin{END_POINTS_LINE}
#       name = LeftSlant
#       xStart = [-2.0, 1.0, 0.0]
#       xEnd   = [ 0.0, -3.0, 0.0]
#    \end{END_POINTS_LINE}
#
#    \begin{END_POINTS_LINE}
#       name = RightSlant
#       xStart = [ 0.0, -3.0, 0.0]
#       xEnd   = [ 2.0, 1.0, 0.0]
#    \end{END_POINTS_LINE}
#
#    \begin{CIRCULAR_ARC}
#       name        = IceCream
#       units       = degrees
#       center      = [ 0.0, 1.0, 0.0]
#       radius      = 2.0
#       start angle = 0.0
#       end angle   = 180.0
#    \end{CIRCULAR_ARC}
#    \end{CHAIN}
#
# \end{INNER_BOUNDARIES}
# ```

# It is important to note there are two `name` quantities one for the `CHAIN` and one for the `PARAMETRIC_EQUATION_CURVE`.
# The name for the `CHAIN` is used internally by HOHQMesh, so if you have multiple `CHAIN`s they **must be given a unique name**.
# The name for the `PARAMETRIC_EQUATION_CURVE` will be printed to the appropriate boundaries within the `.mesh` file produced by
# HOHQMesh.

# We create the mesh file `ice_cream_straight_sides.mesh` and its associated file for plotting
# `ice_cream_straight_sides.tec` by using HOHQMesh.jl's function `generate_mesh`.
using HOHQMesh
control_file = joinpath("out", "ice_cream_straight_sides.control")
output = generate_mesh(control_file);

# The mesh file `ice_cream_straight_sides.mesh` and its associated file for plotting
# `ice_cream_straight_sides.tec` are placed in the `out` folder.
# The resulting mesh generated by HOHQMesh.jl is given in the following figure.

# ![mesh_straight_sides](https://user-images.githubusercontent.com/25242486/129603958-08e4b874-53d5-4511-9a54-6daf4c21edca.png)

# We note that Trixi.jl uses the boundary name information from the control file
# to assign boundary conditions in an elixir file.
# Therefore, the name should start with a letter and consist only of alphanumeric characters and underscores. Please note that the name will be treated as case sensitive.

# ## Example simulation on `ice_cream_straight_sides.mesh`

# With this newly generated mesh we are ready to run a Trixi.jl simulation on an unstructured quadrilateral mesh.
# For this we must create a new elixir file.

# The elixir file given below creates an initial condition for a
# uniform background flow state with a free stream Mach number of 0.3.
# A focus for this part of the tutorial is to specify the boundary conditions and to construct the new mesh from the
# file that was generated in the previous exercise.

# It is straightforward to set the different boundary
# condition types in an elixir by assigning a particular function to a boundary name inside a
# Julia dictionary, `Dict`, variable. Observe that the names of these boundaries match those provided by HOHQMesh
# either by default, e.g. `Bottom`, or user assigned, e.g. `IceCream`. For this problem setup use
# * Freestream boundary conditions on the four box edges.
# * Free slip wall boundary condition on the interior curved boundaries.

# To construct the unstructured quadrilateral mesh from the HOHQMesh file we point to the appropriate location
# with the variable `mesh_file` and then feed this into the constructor for the [`UnstructuredMesh2D`](@ref) type in Trixi.jl

# ```julia
# # create the unstructured mesh from your mesh file
# using Trixi
# mesh_file = joinpath("out", "ice_cream_straight_sides.mesh")
# mesh = UnstructuredMesh2D(mesh_file);
# ```

# The complete elixir file for this simulation example is given below.

using OrdinaryDiffEq, Trixi

equations = CompressibleEulerEquations2D(1.4) # set gas gamma = 1.4

## freestream flow state with Ma_inf = 0.3
@inline function uniform_flow_state(x, t, equations::CompressibleEulerEquations2D)

  ## set the freestream flow parameters
  rho_freestream = 1.0
  u_freestream = 0.3
  p_freestream = inv(equations.gamma)

  theta = 0.0 # zero angle of attack
  si, co = sincos(theta)
  v1 = u_freestream * co
  v2 = u_freestream * si

  prim = SVector(rho_freestream, v1, v2, p_freestream)
  return prim2cons(prim, equations)
end

## initial condition
initial_condition = uniform_flow_state

## boundary condition types
boundary_condition_uniform_flow = BoundaryConditionDirichlet(uniform_flow_state)

## boundary condition dictionary
boundary_conditions = Dict( :Bottom     => boundary_condition_uniform_flow,
                            :Top        => boundary_condition_uniform_flow,
                            :Right      => boundary_condition_uniform_flow,
                            :Left       => boundary_condition_uniform_flow,
                            :LeftSlant  => boundary_condition_slip_wall,
                            :RightSlant => boundary_condition_slip_wall,
                            :IceCream   => boundary_condition_slip_wall );

## DGSEM solver.
##    1) polydeg must be >= the polynomial order set in the HOHQMesh control file to guarantee
##       freestream preservation. As a extra task try setting polydeg=3
##    2) VolumeIntegralFluxDifferencing with central volume flux is activated
##       for dealiasing
volume_flux = flux_ranocha
solver = DGSEM(polydeg=4, surface_flux=flux_hll,
               volume_integral=VolumeIntegralFluxDifferencing(volume_flux))

## create the unstructured mesh from your mesh file
mesh_file = joinpath("out", "ice_cream_straight_sides.mesh")
mesh = UnstructuredMesh2D(mesh_file)

## Create semidiscretization with all spatial discretization-related components
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
                                    boundary_conditions=boundary_conditions)

## Create ODE problem from semidiscretization with time span from 0.0 to 2.0
tspan = (0.0, 2.0)
ode = semidiscretize(semi, tspan)

## Create the callbacks to output solution files and adapt the time step
summary_callback = SummaryCallback()
save_solution = SaveSolutionCallback(interval=10,
                                     save_initial_solution=true,
                                     save_final_solution=true)
stepsize_callback = StepsizeCallback(cfl=1.0)

callbacks = CallbackSet(summary_callback, save_solution, stepsize_callback)

redirect_stdout(devnull) do # code that prints annoying stuff we don't want to see here #hide #md
## Evolve ODE problem in time using `solve` from OrdinaryDiffEq
sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false),
            dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
            save_everystep=false, callback=callbacks);
## print the timer summary
summary_callback()
end #hide #md

# Visualization of the solution is carried out in a similar way as above. That is, one converts the `.h5`
# output files with `trixi2vtk` and then plot the solution in ParaView. An example plot of the pressure
# at the final time is shown below.

# ![simulation_straight_sides](https://user-images.githubusercontent.com/25242486/129733926-6ef80676-779b-4f1e-9826-3ebf750cf382.png)

# ## Making a mesh with a curved outer boundary

# Let us modify the mesh from the previous task and place a circular outer boundary instead
# of straight-sided outer boundaries.
# Note, the "ice cream cone" shape is still placed at the center of the domain.

# We create the new control file `ice_cream_curved_sides.control` file below and will then highlight the
# major differences compared to `ice_cream_straight_sides.control`.
open("out/ice_cream_curved_sides.control", "w") do io
  println(io, raw"""
\begin{CONTROL_INPUT}
    \begin{RUN_PARAMETERS}
        mesh file name   = ice_cream_curved_sides.mesh
        plot file name   = ice_cream_curved_sides.tec
        stats file name  = none
        mesh file format = ISM-v2
        polynomial order = 4
        plot file format = skeleton
    \end{RUN_PARAMETERS}

    \begin{BACKGROUND_GRID}
        background grid size = [1.0, 1.0, 0.0]
    \end{BACKGROUND_GRID}

    \begin{SPRING_SMOOTHER}
        smoothing            = ON
        smoothing type       = LinearAndCrossBarSpring
        number of iterations = 25
    \end{SPRING_SMOOTHER}

\end{CONTROL_INPUT}

\begin{MODEL}

    \begin{OUTER_BOUNDARY}
        \begin{PARAMETRIC_EQUATION_CURVE}
            name = OuterCircle
            xEqn = x(t) = 8.0*sin(2.0*pi*t)
            yEqn = y(t) = 8.0*cos(2.0*pi*t)
            zEqn = z(t) = 0.0
        \end{PARAMETRIC_EQUATION_CURVE}

    \end{OUTER_BOUNDARY}

    \begin{INNER_BOUNDARIES}

        \begin{CHAIN}
            name = IceCreamCone
            \begin{END_POINTS_LINE}
                name = LeftSlant
                xStart = [-2.0, 1.0, 0.0]
                xEnd   = [ 0.0, -3.0, 0.0]
            \end{END_POINTS_LINE}

            \begin{END_POINTS_LINE}
                name = RightSlant
                xStart = [ 0.0, -3.0, 0.0]
                xEnd   = [ 2.0, 1.0, 0.0]
            \end{END_POINTS_LINE}

            \begin{CIRCULAR_ARC}
                name        = IceCream
                units       = degrees
                center      = [ 0.0, 1.0, 0.0]
                radius      = 2.0
                start angle = 0.0
                end angle   = 180.0
            \end{CIRCULAR_ARC}
        \end{CHAIN}

    \end{INNER_BOUNDARIES}

\end{MODEL}
\end{FILE}
""")
end

# The first alteration is that we have altered the second block of information
# `BACKGOUND_GRID` within the `CONTROL_INPUT` to be
# ```
# \begin{BACKGROUND_GRID}
#    background grid size = [1.0, 1.0, 0.0]
# \end{BACKGROUND_GRID}
# ```
# This mesh control file has an outer boundary that determines the extent of the domain to be meshed.
# Therefore, we only need to supply the `background grid size` to the `BACKGROUND_GRID` control input.

# The second alteration is that the `MODEL` now contains information for an `OUTER_BOUNDARY`.
# In this case it is a circle of radius `8` centered at `[0.0, 0.0, 0.0]` written as a set of
# `PARAMETRIC_EQUATION_CURVE`s.
# ```
#    \begin{OUTER_BOUNDARY}
#
#       \begin{PARAMETRIC_EQUATION_CURVE}
#          name = OuterCircle
#          xEqn = x(t) = 8.0*sin(2.0*pi*t)
#          yEqn = y(t) = 8.0*cos(2.0*pi*t)
#          zEqn = z(t) = 0.0
#       \end{PARAMETRIC_EQUATION_CURVE}
#
#    \end{OUTER_BOUNDARY}
# ```
# Just as with the inner boundary curves, we must assign a name to the `OUTER_BOUNDARY`. It will be included
# in the generated `.mesh` file and is used within the Trixi.jl elixir file to set boundary conditions.

# Again, we create the `.mesh` and `.tec` files with HOHQMesh.jl's function `generate_mesh`
control_file = joinpath("out", "ice_cream_curved_sides.control")
output = generate_mesh(control_file);
# The files are placed in the `out` folder.

# The resulting mesh generated by HOHQMesh.jl is given in the following figure.

# ![mesh_curved_sides](https://user-images.githubusercontent.com/25242486/129603957-6a92618f-9ed8-4072-b6ab-05533bea746a.png)

# ## Running Trixi.jl on `ice_cream_curved_sides.mesh`

# We can reuse much of the elixir file to setup the uniform flow over an ice cream cone from the
# previous part of this tutorial. The only component of the elixir file that must be changed is the boundary condition
# dictionary because we now have a boundary named `OuterCirle` instead of four edges of a bounding box.

## boundary condition dictionary
boundary_conditions = Dict( :OuterCircle => boundary_condition_uniform_flow,
                            :LeftSlant   => boundary_condition_slip_wall,
                            :RightSlant  => boundary_condition_slip_wall,
                            :IceCream    => boundary_condition_slip_wall );

# Also, we must update the construction of the mesh from our new mesh file `ice_cream_curved_sides.mesh` that
# is located in the `out` folder.

## create the unstructured mesh from your mesh file
mesh_file = joinpath("out", "ice_cream_curved_sides.mesh")
mesh = UnstructuredMesh2D(mesh_file);

# We can then post-process the solution file at the final time on the new mesh with `Trixi2Vtk` and visualize with ParaView.

# ![simulation_curved_sides](https://user-images.githubusercontent.com/25242486/129733924-778795c1-9119-419a-8b89-bcbe13e33cd7.png)


text:
using Pkg
Pkg.activate(pwd())

# # Loops
# ## `for` and `while` loops

i = 1

while i <= 5
    @show i
    i += 1
end

#+

a, b, c = 1, "hello", :world;

@show (a, b, c);

#+

for i in 1:5
    @show i
end

#+

for i = 1:5
    @show i
end

#+

persons = ["Alice", "Bob", "Carla", "Daniel"];

for name in persons
    println("Hi, my name is $name.")
end

#+

persons = Dict("Alice" => 10, "Bob" => 23, "Carla" => 14, "Daniel" => 34);

for (name, age) in persons
    println("Hi, my name is $name and I am $age old.")
end

# ### Exercise:
# Use `for` or `while` loop to print all integers between `1` and `100` which can be
# divided by both `3` and `7`.
#
# **Hint:** use the `mod` function.
#
# ---
# ### Solution:

i = 21
if mod(i, 3) == 0 && mod(i, 7) == 0
    println("$(i) is divisible by 3 and 7")
end

#+

i = 21
mod(i, 3) == mod(i, 7) == 0 && println("$(i) is divisible by 3 and 7")

#+

for i in 1:100
    mod(i, 3) == mod(i, 7) == 0 && @show i
end

#+

i = 0;
while i <= 100
    i += 1
    mod(i, 3) == mod(i, 7) == 0 && @show i
end

# ---
#
# ## `break` and `continue`

for i in 1:10
    i == 4 && break
    @show i
end

#+

for i in 1:10
    mod(i, 2) == 0 || continue
    @show i
end

# ## Exercise:
# Rewrite the code from the exercise above. Use a combination of the `while` loop and the
# keyword `continue` to print all integers between `1` and `100` divisible by both `3` and
# `7`. In the declaration of the `while` loop use the `true` value instead of a condition.
# Use the `break` keyword and a proper condition to terminate the loop.
#
# ---
# ### Solution:

i = 0;

while true
    i += 1
    i > 100 && break
    mod(i, 3) == mod(i, 7) == 0 || continue
    @show i
end

# ---
#
# ## Nested loops

for i in 1:3
    for j in i:3
        @show (i, j)
    end
end

#+

for i in 1:3, j in i:3
    @show (i, j)
end

#+

for i in 1:3
    for j in i:10
        j > 3 && break
        @show (i, j)
    end
end

#+

for i in 1:3, j in i:10
    j > 3 && break
    @show (i, j)
end

# Exercise:
# Use nested loops to create a matrix with elements given by the formula
# $$
# A_{i, j} = \frac{1}{2}\exp\left\{\frac{1}{2} (x_{i}^2 - y_{j}^2) \right\}
# \quad i \in \{1, 2, 3\}, \; j \in  \{1, 2, 3, 4\},
# $$
# where $x \in \{0.4, 2.3, 4.6\}$ and $y \in \{1.4, -3.1, 2.4, 5.2\}$.
#
# **Bonus:** create the same matrix in a more effective way.
#
# ---
# ### Solution:

x = [0.4, 2.3, 4.6]
y = [1.4, -3.1, 2.4, 5.2]
A = zeros(Float64, length(x), length(y))

for i in 1:length(x), j in 1:length(y)
    A[i, j] = exp((x[i]^2 - y[j]^2)/2)/2
end
A

#+

y_row = y'
A = @. exp((x^2 - y_row^2)/2)/2

# ---
#
# ## List comprehension

X = [0.4, 2.3, 4.6];
Y = [1.4, -3.1, 2.4, 5.2];

A = [exp((x^2 - y^2)/2)/2 for x in X, y in Y]

#+

A = Float32[exp((x^2 - y^2)/2)/2 for x in X, y in Y]

#+

[(x, y, x + y)  for x in 1:10, y in 1:10 if x + y < 5]

# Exercise:
# Use the list comprehension to create a vector of all integers from `1` to `100` divisible
# by `3` and `7` simultaneously. What is the sum of all these integers?
#
# ---
# ### Solution:

v = [i for i in 1:100 if mod(i, 3) == mod(i, 7) == 0]
sum(v)

# ---
#
# Generator expressions

gen = (1/n^2 for n in 1:1000);
sum(gen)

#+

sum(1/n^2 for n in 1:1000)

#+

[(i,j) for i in 1:3, j in 1:2]

#+

gen = ((i,j) for i in 1:3, j in 1:2);
collect(gen)

#+

gen = ((i,j) for i in 1:3 for j in 1:i);
collect(gen)

#+

gen = ((i,j) for i in 1:3 for j in 1:i if i+j == 4);
collect(gen)

# ## Exercise:
# Use a generator to sum the square of all integers from `1` to `100`, which are divisible
# by `3` and `7` simultaneously.
#
# ---
# ### Solution:

gen = (i^2 for i in 1:100 if mod(i, 3) == mod(i, 7) == 0);
typeof(gen)

#+

sum(gen)

#+

sum(i^2 for i in 1:100 if mod(i, 3) == mod(i, 7) == 0)

# ---
#
# Iterators

A = [2.3 4.5; 6.7 7.1]

for i in 1:length(A)
    println("i = $(i) and A[i] = $(A[i])")
end

#+

for (i, val) in enumerate(A)
    println("i = $(i) and A[i] = $(val)")
end

#+

for col in eachcol(A)
    println("col = $(col)")
end

#+

for (i, j, k) in zip([1, 4, 2, 5], 2:12, (:a, :b, :c))
    @show (i, j, k)
end

#+

for (i, vals) in enumerate(zip([1, 4, 2, 5], 2:12, (:a, :b, :c)))
    @show (i, vals)
end

# ## Exercise:
# Create a matrix with elements given by the following formula
# $$
# A_{i, j} = \frac{1}{2}\exp\left\{\frac{1}{2} (x_{i}^2 - y_{j}^2) \right\}
# \quad i \in \{1, 2, 3\}, \; j \in  \{1, 2, 3, 4\}
# $$
# where $x \in \{0.4, 2.3, 4.6\}$, $y \in \{1.4, -3.1, 2.4, 5.2\}$. Compute the sum of
# all elements in each row and print the following message:
#
# > Sum of all elements in a row `i` is `i_sum`
#
# where `i` represents row's number and `i_sum` the sum of all elements in this row. Do the
# same for each column and print the following message:
#
# > Sum of all elements in a column `i` is `i_sum`
#
# **Hint:** use iterators `eachcol` and `eachrow`.
#
# ---
# ### Solution:

for (i, row) in enumerate(eachrow(A))
    println("Sum of all elements in a row $(i) is $(sum(row))")
end

#+

for (i, row) in enumerate(eachcol(A))
    println("Sum of all elements in a column $(i) is $(sum(row))")
end


text:
function build_robot!(vis, model::Particle;
    i=1,
    r=0.1,
    tl=1.0,
    particle_color=Colors.RGBA(0.0, 0.0, 0.0, tl))

    setobject!(vis["particle_$i"],
        GeometryBasics.Sphere(Point3f0(0),
        convert(Float32, r)),
        MeshPhongMaterial(color=particle_color))
end

function set_robot!(vis, model::Particle, q; i=1)
    settransform!(vis["particle_$i"],
        compose(Translation(q...), LinearMap(RotY(0.0))))
end

function visualize!(vis, model::Particle, q;
    i=1,
    r=0.1,
    tl=1.0,
    particle_color=Colors.RGBA(0.0, 0.0, 0.0, tl),
    Δt=0.1,
    fixed_camera=true)

    shift = [0.0; 0.0; r]

    default_background!(vis)

    build_robot!(vis, model,
        i=i,
        r=r,
        tl=tl,
        particle_color=particle_color)

    anim = MeshCat.Animation(convert(Int, floor(1.0 / Δt)))

    T = length(q)
    for t = 1:T-1
        MeshCat.atframe(anim, t) do
            set_robot!(vis, model, q[t] + shift, i=i)
        end
    end

    if fixed_camera
        MeshCat.settransform!(vis["/Cameras/default"],
        MeshCat.compose(MeshCat.Translation(0.0, -50.0, -1.0), MeshCat.LinearMap(Rotations.RotZ(-pi / 2.0))))
        setprop!(vis["/Cameras/default/rotated/<object>"], "zoom", 25)
    end

    MeshCat.setanimation!(vis, anim)
end