text:
# License for this file: MIT (expat)
# Copyright 2021, DLR Institute of System Dynamics and Control
#
# This file is part of module ModiaResult
#
# Functions that are exported from ModiaResult

import DataFrames
import Unitful

baseType(::Type{T}) where {T}  = T
numberType(value) = baseType(eltype(value))

function ifNecessaryStripUnits(s1,s2)
    if   numberType(s1) <: Unitful.AbstractQuantity &&
       !(numberType(s2) <: Unitful.AbstractQuantity)
        # s1 has units and s2 has no units -> remove units in s1
        s1 = Unitful.ustrip.(s1)
    elseif !(numberType(s1) <: Unitful.AbstractQuantity) &&
             numberType(s2) <: Unitful.AbstractQuantity
        # s1 has no units and s2 has units -> remove units in s2
        s2 = Unitful.ustrip.(s2)
    end
    return (s1,s2)
end

# Provide Vector type with one value and fixed length
struct OneValueVector{T} <: AbstractArray{T, 1}
    value::T
    nvalues::Int
    OneValueVector{T}(value,nvalues) where {T} = new(value,nvalues)
end
OneValueVector(value,nvalues) = OneValueVector{typeof(value)}(value,nvalues)

Base.getindex(v::OneValueVector, i::Int)  = v.value
Base.size(v::OneValueVector)              = (v.nvalues,)
Base.IndexStyle(::Type{<:OneValueVector}) = IndexLinear()

interpolate(tsig, sig, isig, t) = begin
                                      sig_ipo = sig[isig-1] + (sig[isig] - sig[isig-1])*((t - tsig[isig-1])/(tsig[isig] - tsig[isig-1]))
                                      isfinite(sig_ipo) ? sig_ipo : sig[isig]
                                  end

"""
    (result1s, result2s, sameTimeRange) = makeSameTimeAxis(result1, result2;
                                                           names     = missing,
                                                           timeName1 = "time",
                                                           timeName2 = "time")

The function makes the same time axis for the signals in result1 and result2 identified
by the signal `select::Vector{String}`. It is required that `result1` and `result2` support functions

- `hasSignal(result, name)`
- `rawSignal(result, name)`
- `names(result)`

and that the time axis is identified by name `timeName1` and `timeName2` respectively.
If `select == missing`, then the intersection of the `select` names of `result1` and of `result2`
is used (but without signals `timeName1`, `timeName2).

The function returns a dictionaries of type `DictionaryResult` of the signals
`select` that are both in `result1` and
in `result2` so that all signals have the same time axes.

Notes:

- If for example `select = ["n1", "n2", "n3", "n4"]` and `"n1"`, `"n3"` and `"n4"`are in `result1`
  and `"n2", "n3", "n4"` are in `result2` then `result1s` and `result2s` contain names
  `"n3", "n4"`.

- If additional time instants are introduced in a time axis, then the corresponding
  signal values are computed by linear interpolation.

- If possible, **references** are returned and **not copies** of the signals (for example, if the
  time axes of `result1` and `result2` are identical, then `result1s` is a reference of
  `result1` and `result2s` is a reference of `result2`).

- If the first and last time point of result1 and result2 are identical,
  then `sameTimeRange = true`. If this is not the case, `sameTimeRange = false` and
  time vectors in `result1s, result2s` contain the time values
  that are both in time vectors `result1` and `result2`.
  If there is no common time range, an error occurs.
"""
function makeSameTimeAxis(result1, result2;
                          select = missing,
                          timeName1::String = "time",
                          timeName2::String = "time")

    # Determine the names that are both in result1 and in result2
    names_common::Vector{String} = ismissing(select) ?
        setdiff(intersect(names(result1), names(result2)), [timeName1, timeName2]) :
        intersect(select, names(result1), names(result2))
    if length(names_common) == 0
        @error "result1 and result2 have no names in common from $names"
    end

    # Check that timeName1 and timeName2 are in the results
    if !hasSignal(result1, timeName1)
        @error "result1 has no signal with name $timeName1"
    elseif !hasSignal(result2, timeName2)
        @error "result2 has no signal with name $timeName2"
    end

    # Get time axes
    (t1, t1b, t1Name, ipo1) = rawSignal(result1, timeName1)
    (t2, t2b, t2Name, ipo2) = rawSignal(result2, timeName2)
    if length(t1) != 1
        @error "Only one segment is currently supported. Signal $timeName1 has " * length(t1) * " segments."
    elseif length(t2) != 1
        @error "Only one segment is currently supported. Signal $timeName2 has " * length(t2) * " segments."
    end
    t1 = t1[1]
    t2 = t2[1]

    if !(typeof(t1) <: AbstractVector)
        type_t1 = typeof(t1)
        @error "The time signal $timeName1 of result1 is no vector (has type $type_t1)"
    elseif !(typeof(t2) <: AbstractVector)
        type_t2 = typeof(t2)
        @error "The time signal $timeName2 of result2 is no vector (has type $type_t2)"
    end
    (t1,t2) = ifNecessaryStripUnits(t1,t2)

    # Generate output DataFrames
    result1s = DataFrames.DataFrame()
    result2s = DataFrames.DataFrame()

    # Handle case, if the time axes are the same
    if t1 == t2
        nt = length(t1)
        (tt1, tt1b, tt1Name, dummy) = rawSignal(result1, timeName1)
        if length(tt1) != length(t1)
            @error "Only one time signal is currently supported, but $result1 has several time signals."
        end
        result1s[!,timeName1] = tt1
        (tt2, tt2b, tt2Name, dummy) = rawSignal(result2, timeName2)
        if length(tt2) != length(t2)
            @error "Only one time signal is currently supported, but $result2 has several time signals."
        end
        result2s[!,timeName2] = tt2
        for name in names_common
            (sig1, tt1c, tt1cName, sigIpo1) = rawSignal(result1, name)
            result1s[!,name] = sig1
            (sig2, tt2c, ttt2Name, sigIpo2) = rawSignal(result2, name)
            result2s[!,name] = sig2
        end
        return (result1s, result2s, true)
    end

    # Determine common time range
    nt1 = length(t1)
    nt2 = length(t2)
    if t1[end] < t2[1] || t1[1] > t2[end]
        t1_begin = t1[1]
        t1_end   = t1[end]
        t2_begin = t2[1]
        t2_end   = t2[end]
        @error "result1 has time range [$t1_begin,$t1_end] and result2 has time range [$t1_begin,$t1_end]"
    end

    if t1[1] <= t2[1]
        i1_begin = findlast(v -> v <= t2[1], t1)
        i2_begin = 1
        t_begin  = t2[1]
    else
        i2_begin = findlast(v -> v <= t1[1], t2)
        i1_begin = 1
        t_begin  = t1[1]
    end

    # Allocate memory for common time axis
    nt  = nt1 + nt2
    t_common = similar(t1, promote_type(eltype(t1), eltype(t2)), nt)
    t  = t_begin
    i1 = i1_begin
    i2 = i2_begin
    i  = 1
    while true
        t_common[i] = t

        if t >= t1[i1]
            i1 += 1
        end

        if t >= t2[i2]
            i2 += 1
        end

        i += 1
        if i > nt
            break
        elseif i1 > nt1 || i2 > nt2
            deleteat!(t_common, i:nt)
            break
        end

        t = t1[i1] <= t2[i2] ? t1[i1] : t2[i2]
    end
    result1s[!,timeName1] = t_common
    result2s[!,timeName2] = t_common
    nt = length(t_common)
    sameTimeRange = t1[1] == t2[1] && t1[end] == t2[end]

    # Make same time axis
    for name in names_common
        # Allocate memory for signals in result1s, result2s
        (sig1, t1, t1Name, ipo1) = rawSignal(result1, name)
        (sig2, t2, t2Name, ipo2) = rawSignal(result2, name)

        if length(sig1) == 0
            @error "$name in result1 has no values"
        end
        if length(sig2) == 0
            @error "$name in result2 has no values"
        end

        if typeof(sig1[1]) <: Number && typeof(sig2[1]) <: Number
            sig1_common = similar(sig1, promote_type(eltype(sig1), eltype(sig2)), nt)
        elseif typeof(sig1[1]) <: AbstractArray && typeof(sig2[1]) <: AbstractArray
            if size(sig1[1]) != size(sig2[1])
                size1 = size(sig1[1])
                size2 = size(sig2[1])
                @error "$name array in result1 has element size $size1 and in result2 element size $size2"
            end
            sig1_common = similar(sig1, Array{promote_type(eltype(sig1[1]), eltype(sig2[1])), ndims(sig1[1])}, nt)
        else
            type1 = typeof(sig1[1])
            type2 = typeof(sig2[1])
            @error "$name in result1 has element type $type1 and in result2 element type $type2"
        end
        sig2_common = similar(sig1_common)

        # Make same time axis for signals sig1 and sig2
        t  = t_begin
        i  = 1
        i1 = i1_begin
        i2 = i2_begin
        while true
            #println("... i = $i, i1 = $i1, i2 = $i2, t = $t, t1[i1] = ", t1[i1], ", t2[i2] = ", t2[i2])

            if t < t1[i1]
                sig1_common[i] = interpolate(t1,sig1,i1,t)
            else
                sig1_common[i] = sig1[i1]
                i1 += 1
            end

            if t < t2[i2]
                sig2_common[i] = interpolate(t2,sig2,i2,t)
            else
                sig2_common[i] = sig2[i2]
                i2 += 1
            end

            i += 1
            if i > nt
                break
            end

            t = t1[i1] <= t2[i2] ? t1[i1] : t2[i2]
        end

        result1s[!,name] = sig1_common
        result2s[!,name] = sig2_common
    end
    return (result1s, result2s, sameTimeRange)
end

"""
    (success, diff, diff_names, max_error, within_tolerance) =
                      compareResults(result, reference;
                                     tolerance     = 1e-3,
                                     names         = missing,
                                     timeResult    = "time",
                                     timeReference = "time")

Compare the signals identified by `names::Vector{String}` in `result`
with the reference reults `reference` using the relative `tolerance`.
The time axis is identified by name `timeResult` and `timeReference` respectively.
If `names == missing`, then the intersection of the `names` of `result` and of `reference`
is used (but without signals `timeResult`, `timeReference).

Input arguments `result`, `reference` can be dictionaries, DataFrames or
objects that support functions

- `ModiaPlot.hasSignal(result, name)`
- `rawSignal(result, name)`
- `names(result)`

# Output arguments

- `success::Bool`: = true, if all compared signals are the same within the given tolerance (see below),.
- `diff::DataFrame`: The difference of the compared signals (using `timeResult` as time-axis).
- `diff_names::Vector{String}`: The names of the signals that are compared.
- `max_error::Vector{Float64}`: max_error[i] is the maximum error in signal of diff_names[i].
- `within_tolerance::BitVector`: withinTolerance[i] = true, if signal of diff_names[i] is within the tolerance (see below).

# Computation of the outputs

Comparison is made in the following way (assuming `result` and `reference`
have the same time axis and are DataFrames objects):

```julia
smallestNominal = 1e-10
nt = DataFrames.nrow(reference)
for (i,name) in enumerate(diff_names)
    diff[i]             = result[!,name] - reference[!,name]
    max_error[i]        = maximum( abs.(result[!,name] - reference[!,name]) )
    nominal[i]          = max( sum( abs.(reference[!,name])/nt ), smallestNominal )
    within_tolerance[i] = max_error[i] <= nominal[i]*tolerance
end
success = all(within_tolerance)
```

# Notes

Before the comparison, the function makes the same time axis for the signals in
`result` and `reference` and selects the maximum time range that is both in `result` and in
`reference`. If additional time instants are introduced in a time axis,
then the corresponding signal values are computed by linear interpolation.
This linear interpolation can introduce a significant error.
It is therefore highly recommended to use the same time axis.
"""
function compareResults(result, reference;
                        tolerance::Float64 = 1e-3,
                        names::Union{Missing,Vector{String}} = missing,
                        timeResult::String = "time",
                        timeReference::String = "time")
    # Check tolerance
    if tolerance <= 0.0
        @error "compareResults: Input argument tolerance = $tolerance <= 0.0"
    end

    # Make same time axis
    (result1s, result2s) = makeSameTimeAxis(result, reference;
                                            names = names,
                                            timeName1 = timeResult,
                                            timeName2 = timeReference)

    # Compute difference and tolerance
    diff_names = setdiff(DataFrames.names(result1s), [timeResult])
    nnames = length(diff_names)
    diff = DataFrames.DataFrame()
    diff[!,timeResult] = result1s[!,timeResult]
    smallestNominal = 1e-10
    nt = DataFrames.nrow(result1s)
    max_error = zeros(nnames)
    nominal   = zeros(nnames)
    within_tolerance = fill(false, nnames)
    for (i,name) in enumerate(diff_names)
        s1 = result1s[!,name]
        s2 = result2s[!,name]
        (s1,s2) = ifNecessaryStripUnits(s1,s2)

        # Difference of the signals
        diff[!,name] = s1 - s2

        # Error
        max_error[i] = Unitful.ustrip.( maximum( abs.(s1 - s2) ) )
        nominal[i]   = max( Unitful.ustrip(sum( abs.(s2)/nt )), smallestNominal )
        within_tolerance[i] = max_error[i] <= nominal[i]*tolerance
    end
    success = all(within_tolerance)

    return (success, diff, diff_names, max_error, within_tolerance)
end

max_stars_repo_name: ModiaSim/ModiaResult

max_issues_repo_name: ModiaSim/ModiaResult

max_forks_repo_name: ModiaSim/ModiaResult


text:
struct TensorProduct <: QuadratureName end

const tensor_product = TensorProduct()

function Quadrature(p::Polytope,::TensorProduct,degrees)
  @assert is_n_cube(p) """\n
  Tensor product quadrature rule only for n-cubes.
  """
  @assert length(degrees) == num_dims(p)
  _tensor_product_legendre(degrees)
end

function Quadrature(p::Polytope,name::TensorProduct,degree::Integer)
  degrees = ntuple(i->degree,Val(num_dims(p)))
  Quadrature(p,name,degrees)
end

# Low level constructor

function _tensor_product_legendre(degrees)
    D = length(degrees)
    T = Float64
    npoints = [ ceil(Int,(degrees[i]+1.0)/2.0) for i in 1:D ]
    quads = [ gauss( eltype(Point{D,T}), npoints[i] ) for i in 1:D ]
    for i in 1:D
      quads[i][1] .+= 1;
      quads[i][1] .*= 1.0/2.0
      quads[i][2] .*= 1.0/2.0
    end
    (coords, weights) = _tensor_product(Point{D,T},quads,npoints)
    GenericQuadrature(coords,weights,"Tensor product of 1d Gauss-Legendre quadratures of degrees $degrees")
end

# Helpers

function _tensor_product(::Type{Point{D,T}},quads,npoints) where {D,T}
  @assert length(quads) == D
  @assert length(npoints) == D
  cis = CartesianIndices(tuple(npoints...))
  n = prod(npoints)
  coords = Vector{Point{D,T}}(undef,n)
  weights = Vector{T}(undef,n)
  _tensor_product!(quads,coords,weights,cis)
  (coords, weights)
end

function _tensor_product(::Type{Point{0,T}},quads,npoints) where T
  coords = [zero(Point{0,T})]
  weights = [one(T)]
  coords, weights
end

function _tensor_product!(quads,coords,weights,cis)
  p = zero(Mutable(eltype(coords)))
  D = length(p)
  lis = LinearIndices(cis)
  for ci in cis
    p[:] = 0.0
    w = 1.0
    for d in 1:D
      xi = quads[d][1][ci[d]]
      wi = quads[d][2][ci[d]]
      p[d] = xi
      w *= wi
    end
    li = lis[ci]
    coords[li] = p
    weights[li] = w
  end
end

ext: jl


text:
###
### intersect
###
for R in (:StepMRange,:LinMRange,:StepMRangeLen)
    @eval begin
        Base.intersect(r::$(R){T}, i::T) where {T} = intersect(i, r)
        Base.intersect(r::$(R){<:Real}, i::Real) = intersect(eltype(r)(i), r)
        Base.intersect(i::Real, r::$(R){Real}) = intersect(eltype(r)(i), r)
        function Base.intersect(i::T, r::$(R){T}) where {T}
            return i in r ? mrange(i, stop=i, length=1) : mrange(i, stop=i, length=0)
        end
    end
end

for R in (:StepSRange,:LinSRange,:StepSRangeLen)
    @eval begin
        Base.intersect(r::$(R){T}, i::T) where {T} = intersect(i, r)
        Base.intersect(r::$(R){<:Real}, i::Real) = intersect(eltype(r)(i), r)
        Base.intersect(i::Real, r::$(R){Real}) = intersect(eltype(r)(i), r)
        function Base.intersect(i::T, r::$(R){T}) where {T}
            return i in r ? srange(i, stop=i, length=1) : srange(i, stop=i, length=0)
        end
    end
end

Base.intersect(r::OneToRange, s::OneToRange) = OneTo(min(last(r),last(s)))
Base.intersect(r::OneToRange, s::OneTo) = OneTo(min(last(r),last(s)))
Base.intersect(r::OneTo, s::OneToRange) = OneTo(min(last(r),last(s)))

function Base.intersect(r::AbstractUnitRange{<:Integer}, s::AbstractStepRange{<:Integer})
    if isempty(s)
        range(first(r), length=0)
    elseif step(s) < 0
        intersect(r, reverse(s))
    else
        sta = first(s)
        ste = step(s)
        sto = last(s)
        lo = first(r)
        hi = last(r)
        i0 = max(sta, lo + mod(sta - lo, ste))
        i1 = min(sto, hi - mod(hi - sta, ste))
        i0:ste:i1
    end
end

function Base.intersect(r::AbstractStepRange{<:Integer}, s::AbstractUnitRange{<:Integer})
    if step(r) < 0
        return reverse(intersect(s, reverse(r)))
    else
        return intersect(s, r)
    end
end

Base.intersect(r::AbstractStepRange, s::StepRange) = _intersect(r, s)
Base.intersect(r::StepRange,         s::AbstractStepRange) = _intersect(r, s)
Base.intersect(r::AbstractStepRange, s::AbstractStepRange) = _intersect(r, s)

function _intersect(r, s)
    if isempty(r) || isempty(s)
        return range(first(r), step=step(r), length=0)
    elseif step(s) < zero(step(s))
        return intersect(r, reverse(s))
    elseif step(r) < zero(step(r))
        return reverse(intersect(reverse(r), s))
    end

    start1 = first(r)
    step1 = step(r)
    stop1 = last(r)
    start2 = first(s)
    step2 = step(s)
    stop2 = last(s)
    a = lcm(step1, step2)

    g, x, y = gcdx(step1, step2)

    if !iszero(rem(start1 - start2, g))
        # Unaligned, no overlap possible.
        return range(start1, step=a, length=0)
    end

    z = div(start1 - start2, g)
    b = start1 - x * z * step1
    # Possible points of the intersection of r and s are
    # ..., b-2a, b-a, b, b+a, b+2a, ...
    # Determine where in the sequence to start and stop.
    m = max(start1 + mod(b - start1, a), start2 + mod(b - start2, a))
    n = min(stop1 - mod(stop1 - b, a), stop2 - mod(stop2 - b, a))
    range(m, step=a, stop=n)
end

###
### findin
###
function _find_first_in(x, xo::O, y, yo::O) where {O<:Ordering}
    for x_i in x
        out = find_first(==(x_i), y, yo)
        isnothing(out) || return out
    end
    return nothing
end

function _find_first_in(x, xo::Ordering, y, yo::Ordering)
    for x_i in x
        out = find_last(==(x_i), y, yo)
        isnothing(out) || return out
    end
    return nothing
end

function _find_last_in(x, xo::O, y, yo::O) where {O<:Ordering}
    for x_i in reverse(x)
        out = find_first(==(x_i), y, yo)
        isnothing(out) || return out
    end
    return nothing
end

function _find_last_in(x, xo::Ordering, y, yo::Ordering)
    for x_i in reverse(x)
        out = find_last(==(x_i), y, yo)
        isnothing(out) || return out
    end
    return nothing
end

# finds the step of indices in y given x
_find_step_in(sx::T, xo::O, sy::T, yo::O) where {T<:Integer,O<:Ordering} = sx * gcd(sx, sy)
_find_step_in(sx::T, xo::Ordering, sy::T, yo::Ordering) where {T<:Integer} = -sx * gcd(sx, sy)

function _find_step_in(sx::T, xo, sy::T, yo) where {T<:AbstractFloat}
    return _find_step_in(rationalize(sx), rationalize(sy))
end

function _find_step_in(sx::T, xo::Ordering, sy::T, yo::Ordering) where {T<:Rational}
    if denominator(sx) != denominator(sy)
        return _find_step_in(denominator(sy) * sx, xo, denominator(sx) * sy, yo)
    else
        return -Rational(numerator(sx) * gcd(numerator(sx), numerator(sy)), denominator(sx))
    end
end

function _find_step_in(sx::T, xo::O, sy::T, yo::O) where {T<:Rational,O<:Ordering}
    if denominator(sx) != denominator(sy)
        return _find_step_in(denominator(sy) * sx, xo, denominator(sx) * sy, yo)
    else
        return Rational(numerator(sx) * gcd(numerator(sx), numerator(sy)), denominator(sx))
    end
end

###
### findin
###
function _findin(x, xo, y, yo)
    xnew, ynew = promote(x, y)
    return _findin(xnew, xo, ynew, yo)
end

function _findin(x::OneToMRange, xo, y::OneToMRange, yo)
    return OneToMRange(_find_last_in(x, xo, y, yo))
end
function _findin(x::OneTo, xo, y::OneTo, yo)
    return OneTo(_find_last_in(x, xo, y, yo))
end
function _findin(x::OneToSRange, xo, y::OneToSRange, yo)
    return OneToSRange(_find_last_in(x, xo, y, yo))
end

function _findin(x::UnitRange{<:Integer}, xo, y::UnitRange{<:Integer}, yo)
    return UnitRange(_find_first_in(x, xo, y, yo), _find_last_in(x, xo, y, yo))
end
function _findin(x::UnitMRange{<:Integer}, xo, y::UnitMRange{<:Integer}, yo)
    return UnitMRange(_find_first_in(x, xo, y, yo), _find_last_in(x, xo, y, yo))
end
function _findin(x::UnitSRange{<:Integer}, xo, y::UnitSRange{<:Integer}, yo)
    return UnitSRange(_find_first_in(x, xo, y, yo), _find_last_in(x, xo, y, yo))
end

function _findin(x::UnitRange, xo, y::UnitRange, yo)
    iszero(rem(first(x) - first(y), 1)) || return similar_type(x)(fi, li)
    return UnitRange(_find_first_in(x, xo, y, yo), _find_last_in(x, xo, y, yo))
end
function _findin(x::UnitMRange, xo, y::UnitMRange, yo)
    iszero(rem(first(x) - first(y), 1)) || return similar_type(x)(fi, li)
    return UnitMRange(_find_first_in(x, xo, y, yo), _find_last_in(x, xo, y, yo))
end
function _findin(x::UnitSRange, xo, y::UnitSRange, yo)
    iszero(rem(first(x) - first(y), 1)) || return similar_type(x)(fi, li)
    return UnitSRange(_find_first_in(x, xo, y, yo), _find_last_in(x, xo, y, yo))
end

function _findin(x::StepMRange, xo, y::StepMRange, yo)
    fi = _find_first_in(x, xo, y, yo)
    li = _find_last_in(x, xo, y, yo)
    sxy = _find_step_in(step(x), xo, step(y), yo)
    if !iszero(rem(ordmin(x, xo) - ordmin(y, yo), div(sxy, step(x))))
        # Unaligned, no overlap possible.
        return mrange(fi, step=sxy, length=0)
    else
        return mrange(fi, step=sxy, stop=li)
    end
end

function _findin(x::StepSRange, xo, y::StepSRange, yo)
    fi = _find_first_in(x, xo, y, yo)
    li = _find_last_in(x, xo, y, yo)
    sxy = _find_step_in(step(x), xo, step(y), yo)

    if !iszero(rem(ordmin(x, xo) - ordmin(y, yo), div(sxy, step(x))))
        # Unaligned, no overlap possible.
        return srange(fi, step=sxy, length=0)
    else
        return srange(fi, step=sxy, stop=li)
    end
end

function _findin(x::StepRange, xo, y::StepRange, yo)
    fi = _find_first_in(x, xo, y, yo)
    li = _find_last_in(x, xo, y, yo)
    sxy = _find_step_in(step(x), xo, step(y), yo)
    if !iszero(rem(ordmin(x, xo) - ordmin(y, yo), div(sxy, step(x))))
        # Unaligned, no overlap possible.
        return range(fi, step=sxy, length=0)
    else
        return range(fi, step=sxy, stop=li)
    end
end


text:
# Autogenerated wrapper script for HarfBuzz_jll for x86_64-w64-mingw32
export libharfbuzz, libharfbuzz_gobject, libharfbuzz_icu, libharfbuzz_subset

using Glib_jll
using FreeType2_jll
using Graphite2_jll
using Libffi_jll
using Gettext_jll
using Fontconfig_jll
using Cairo_jll
using ICU_jll
JLLWrappers.@generate_wrapper_header("HarfBuzz")
JLLWrappers.@declare_library_product(libharfbuzz, "libharfbuzz-0.dll")
JLLWrappers.@declare_library_product(libharfbuzz_gobject, "libharfbuzz-gobject-0.dll")
JLLWrappers.@declare_library_product(libharfbuzz_icu, "libharfbuzz-icu-0.dll")
JLLWrappers.@declare_library_product(libharfbuzz_subset, "libharfbuzz-subset-0.dll")
function __init__()
    JLLWrappers.@generate_init_header(Glib_jll, FreeType2_jll, Graphite2_jll, Libffi_jll, Gettext_jll, Fontconfig_jll, Cairo_jll, ICU_jll)
    JLLWrappers.@init_library_product(
        libharfbuzz,
        "bin\\libharfbuzz-0.dll",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libharfbuzz_gobject,
        "bin\\libharfbuzz-gobject-0.dll",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libharfbuzz_icu,
        "bin\\libharfbuzz-icu-0.dll",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@init_library_product(
        libharfbuzz_subset,
        "bin\\libharfbuzz-subset-0.dll",
        RTLD_LAZY | RTLD_DEEPBIND,
    )

    JLLWrappers.@generate_init_footer()
end  # __init__()


text:
function huecolors(n::Integer=100;alpha=0.8,saturation=1,brightness=1,precolors=[HSVA(0,1,0,alpha)],sufcolors=[])
    hc = [HSVA(360(i-1)/n,saturation,brightness,alpha) for i in 1:n]
    prepend!(hc,precolors)
    append!(hc,sufcolors)
    hc
end

"Generates linearly interpolated `ColorGradient`"
function cgrad(start::T,stop::T;length=100) where T<:Colorant
    cgrad(range(start,stop,length=length))
end

"Generates linearly interpolated `ColorGradient` between key colors."
function cgrad(x::T...;length=100) where T<:Colorant
    cgrad(range(x...,length=length))
end

"""
Generates linearly interpolated colors between key colors inclusively.

return an `Array` of colors.
"""
function range(x::T...;length=100) where T<:Colorant
    nk = Base.length(x)
    sn = round(Int,length/(nk-1))
    segs = map(i->range(x[i],x[i+1],length=sn),1:nk-1)
    cs = push!(mapreduce(i->i[1:end-1],append!,segs),segs[end][end])
end

"Predefined ColorMaps"
ColorMaps=Dict()

function plotcolormap(cm;title="",xlabel="",ylabel="",markersize=12,shape=:circle)
    if shape == :circle
        yx = sincos.(2π*range(0,1,length=length(cm)))
        x = map(i->i[2],yx)
        y = map(i->i[1],yx)
        marker = :circle
        ylims=[-1.2,1.2]
    else
        x = range(0,1,length=length(cm))
        y = ones(length(cm))
        marker = :rect
        ylims=[0.5,1.5]
    end
    scatter(x,y,aspectratio=:equal,color=cm,markersize=markersize,marker=marker,ylims=ylims,
    markerstrokewidth=0,legend=false,xlabel=xlabel,ylabel=ylabel,title=title)
end


text:
module OpenAIGymAPI

export
  GymClient,
  env_list_all,
  env_create,
  env_reset,
  env_step,
  env_action_space_info,
  env_action_space_sample,
  env_action_space_contains,
  env_observation_space_info,
  env_monitor_start,
  env_monitor_close,
  env_close,
  upload,
  shutdown_server,
  parse_server_error_or_raise_for_status,
  get_request,
  post_request

include("api.jl")
include("utils.jl")

end # module OpenAIGymAPI