# This is part of Quiz.jl. License is MIT.

"""
`struct qT <: qElem`\n
Quiz generic text element.
"""
struct qT <: qElem
    val::AbstractString
end

# Type functor: full extraction and assembly
"""
`(x::qT)()`\n
`qT` functor that returns its value as a `string`.
"""
(x::qT)() = string(x.val)

# the: convenient value extracting function
the(x::qT) = x()

# export
export qT


#----------------------------------------------------------------------------------------------#
#                                       qC -- Constants                                        #
#----------------------------------------------------------------------------------------------#

"""
`struct qC <: qElem`\n
Quiz constant element.
"""
struct qC <: qElem
    nam::Symbol
    val::N64
    uni::Unitful.Units
    qC(n::Symbol, x::Number) = new(n, val_unit(x)...)
    qC(n::Symbol, v::Number, u::Unitful.Units) = begin
        val, uni = val_unit(v)
        nou = unit(1) # a non-unit
        new(n, val, uni == nou ? u : (u == nou ? uni : u*uni))
    end
end

# Type functor: full extraction and assembly
"""
`(x::qC)()`\n
`qC` functor that returns its value as a `Pair{Symbol, Quantity}`.
"""
(x::qC)() = x.nam => Quantity{typeof(x.val), dimension(x.uni), typeof(x.uni)}(x.val * x.uni)

# the: convenient value extracting function
the(x::qC) = x().second

# iE: convenient constant instantiation expression
iE(x::qC) = begin a, b = x(); :($a = $b); end

# export
export qC


#----------------------------------------------------------------------------------------------#
#                                       qV -- Variables                                        #
#----------------------------------------------------------------------------------------------#

"""
`struct qV <: qElem`\n
Quiz variant (or variable) element.
"""
struct qV <: qElem
    nam::Symbol
    val::Array{<:N64, 1}
    uni::Unitful.Units
    qV(n::Symbol, v::AbstractVector{<:Number}) = begin
        vSep = map(x -> val_unit(x), v)
        uSet = [Set(i[2] for i in vSep)...]
        if length(uSet) == 1
            new(n, [i[1] for i in vSep], uSet[1])
        else
            throw(ArgumentError("non-uniform units on input"))
        end
    end
    qV(n::Symbol, v::AbstractVector{<:Number}, u::Unitful.Units) = begin
        vSep = map(x -> val_unit(x), v)
        uSet = [Set(i[2] for i in vSep)...]
        if length(uSet) == 1
            Uni, nou = uSet[1], unit(1)
            UNI = Uni == nou ? u : (u == nou ? Uni : u*Uni)
            new(n, [i[1] for i in vSep], UNI)
        else
            throw(ArgumentError("non-uniform units on input"))
        end
    end
end

# Type functor: full extraction and assembly
"""
`(x::qV)()`\n
`qV` functor that returns its value as a `Pair{Symbol, Vector{Quantity}}`.
"""
function (x::qV)()
    TYP, DIM, UNI = typeof(x.val[1]), dimension(x.uni), typeof(x.uni)
    x.nam => [ Quantity{TYP, DIM, UNI}(i * x.uni) for i in x.val ]
end

"""
`(x::qV)(i::Integer)`\n
`qV` functor that returns its `i`th value as a `Pair{Symbol, Quantity}`.
"""
function (x::qV)(i::Integer)
    X = x()
    X.first => X.second[i]
end

# the: convenient value extracting function
the(x::qV) = x().second

# iE: convenient constant instantiation expression
iE(x::qV) = begin a, b = x(); :($a = rand($b)); end

# Extending rand(::qV)
import Random.rand
rand(x::qV) = rand(the(x))

# qC(::qV)
qC(v::qV) = qC(v.nam, rand(v))

# export
export qV


#----------------------------------------------------------------------------------------------#
#                                        qA -- Answers                                         #
#----------------------------------------------------------------------------------------------#

"""
`struct qA <: qElem`\n
Quiz answer element.
"""
struct qA <: qElem
    wgt::N64
    ask::AbstractString
    nam::Symbol
    uni::Unitful.Units
end

# export
export qA
