"""
`Colorant{T,N}` is the abstract super-type of all types in ColorTypes,
and refers to both (opaque) colors and colors-with-transparency (alpha
channel) information.  `T` is the element type (extractable with
`eltype`) and `N` is the number of *meaningful* entries (extractable
with `length`), i.e., the number of arguments you would supply to the
constructor.
"""
abstract type Colorant{T,N} end

# Colors (without transparency)
"""
`Color{T,N}` is the abstract supertype for a color (or
grayscale) with no transparency.
"""
abstract type Color{T, N} <: Colorant{T,N} end

"""
`AbstractGray{T}` is an abstract supertype for gray types which corresponds to
real numbers, where `0` means black and `1` means white.

!!! compat "ColorTypes v0.12"
    Prior to ColorTypes v0.12, `AbstractGray{T}` was the alias for `Color{T,1}`;
    after ColorTypes v0.12, `Color{T,1}` does not necessarily correspond to the
    black-gray-white colors.
"""
abstract type AbstractGray{T} <: Color{T,1} end

"""
`AbstractRGB{T}` is an abstract supertype for red/green/blue color types that
can be constructed as `C(r, g, b)` and for which the elements can be
extracted as `red(c)`, `green(c)`, `blue(c)`. You should *not* make
assumptions about internal storage order, the number of fields, or the
representation. One `AbstractRGB` color-type, `RGB24`, is not
parametric and does not have fields named `r`, `g`, `b`.
"""
abstract type AbstractRGB{T} <: Color{T,3} end

# Types with transparency
"""
`TransparentColor{C,T,N}` is the abstract type for any
color-with-transparency.  The `C` parameter refers to the type of the
pure color (without transparency) and can be extracted with
`color_type`. `T` is the element type of both `C` and the `alpha`
channel, and `N` has the same meaning as in `Colorant` (and is 1 larger
than the corresponding color type).

All transparent types should support two modes of construction:

    P(color, alpha)
    P(component1, component2, component3, alpha) (assuming a 3-component color)

For a `Colorant` `c`, the color component can be extracted with
`color(c)`, and the alpha channel with `alpha(c)`. Note that types
such as `ARGB32` do not have a field named `alpha`.

Most concrete types, like `RGB`, have both `ARGB` and `RGBA`
transparent analogs.  These two indicate different internal storage
order (see `AlphaColor` and `ColorAlpha`, and the `alphacolor` and
`coloralpha` functions).
"""
abstract type TransparentColor{C<:Color,T,N} <: Colorant{T,N} end

"""
`AlphaColor` is an abstract supertype for types like `ARGB`, where the
alpha channel comes first in the internal storage order. **Note** that
the constructor order is still `(color, alpha)`.
"""
abstract type AlphaColor{C<:Color,T,N} <: TransparentColor{C,T,N} end

"""
`ColorAlpha` is an abstract supertype for types like `RGBA`, where the
alpha channel comes last in the internal storage order.
"""
abstract type ColorAlpha{C<:Color,T,N} <: TransparentColor{C,T,N} end

# These are types we'll dispatch on.
Color3{T}                          = Color{T,3}
TransparentGray{C<:AbstractGray,T} = TransparentColor{C,T,2}
Transparent3{C<:Color3,T}          = TransparentColor{C,T,4}
TransparentRGB{C<:AbstractRGB,T}   = TransparentColor{C,T,4}
ColorantNormed{T<:Normed,N}        = Colorant{T,N}
AbstractAGray{C<:AbstractGray,T}   = AlphaColor{C,T,2}
AbstractGrayA{C<:AbstractGray,T}   = ColorAlpha{C,T,2}
AbstractARGB{C<:AbstractRGB,T}     = AlphaColor{C,T,4}
AbstractRGBA{C<:AbstractRGB,T}     = ColorAlpha{C,T,4}

# With reordered type parameters (also useful for dispatch)
ColorantN{N,T}                  = Colorant{T,N}
ColorN{N,T}                     = Color{T,N}
TransparentColorN{N,C<:Color,T} = TransparentColor{C,T,N}
AlphaColorN{N,C<:Color,T}       = AlphaColor{C,T,N}
ColorAlphaN{N,C<:Color,T}       = ColorAlpha{C,T,N}

"""
`RGB` is the standard Red-Green-Blue (sRGB) colorspace.  Values of the
individual color channels range from 0 (black) to 1 (saturated). If
you want "Integer" storage types (e.g., 255 for full color), use `N0f8(1)`
instead (see FixedPointNumbers).
"""
struct RGB{T<:Fractional} <: AbstractRGB{T}
    r::T # Red [0,1]
    g::T # Green [0,1]
    b::T # Blue [0,1]

    RGB{T}(r::T, g::T, b::T) where {T <: Fractional} = new{T}(r, g, b)
end

"""
`BGR` is a variant of `RGB` with the opposite storage order.  Note
that the constructor is still called in the order `BGR(r, g, b)`.
This storage order is noteworthy because on little-endian machines,
`BGRA` (with transparency) can be `reinterpret`ed to the `UInt32`
color format used by libraries such as Cairo and OpenGL.
"""
struct BGR{T<:Fractional} <: AbstractRGB{T}
    b::T
    g::T
    r::T

    BGR{T}(r::T, g::T, b::T) where {T <: Fractional} = new{T}(b, g, r)
end

"""
`XRGB` is a variant of `RGB` which has a padding element inserted at
the beginning. In some applications it may have useful
memory-alignment properties.

Like all other AbstractRGB objects, the constructor is still called
`XRGB(r, g, b)`.
"""
struct XRGB{T<:Fractional} <: AbstractRGB{T}
    alphadummy::T
    r::T
    g::T
    b::T

    XRGB{T}(r::T, g::T, b::T) where {T <: Fractional} = new{T}(oneunit(T), r, g, b)
end

"""
`RGBX` is a variant of `RGB` which has a padding element inserted at
the end. In some applications it may have useful
memory-alignment properties.

Like all other AbstractRGB objects, the constructor is still called
`RGBX(r, g, b)`.
"""
struct RGBX{T<:Fractional} <: AbstractRGB{T}
    r::T
    g::T
    b::T
    alphadummy::T

    RGBX{T}(r::T, g::T, b::T) where {T <: Fractional} = new{T}(r, g, b, oneunit(T))
end

"`HSV` is the Hue-Saturation-Value colorspace."
struct HSV{T<:AbstractFloat} <: Color{T,3}
    h::T # Hue in [0,360]
    s::T # Saturation in [0,1]
    v::T # Value in [0,1]
end

"`HSB` (Hue-Saturation-Brightness) is an alias for `HSV`."
const HSB = HSV

"`HSL` is the Hue-Saturation-Lightness colorspace."
struct HSL{T<:AbstractFloat} <: Color{T,3}
    h::T # Hue in [0,360]
    s::T # Saturation in [0,1]
    l::T # Lightness in [0,1]
end

"`HSI` is the Hue-Saturation-Intensity colorspace."
struct HSI{T<:AbstractFloat} <: Color{T,3}
    h::T # Hue in [0,360]
    s::T # Saturation in [0,1]
    i::T # Intensity in [0,1]
end

"""
`XYZ` is the CIE 1931 XYZ colorspace. It is a linear colorspace,
meaning that mathematical operations such as addition, subtraction,
and scaling make "colorimetric sense" in this colorspace.
"""
struct XYZ{T<:AbstractFloat} <: Color{T,3}
    x::T
    y::T
    z::T
end

"`xyY` is the CIE 1931 xyY (chromaticity + luminance) space"
struct xyY{T<:AbstractFloat} <: Color{T,3}
    x::T
    y::T
    Y::T
end

"`Lab` is the CIELAB colorspace."
struct Lab{T<:AbstractFloat} <: Color{T,3}
    l::T # Lightness in [0,100]
    a::T # Red/Green
    b::T # Blue/Yellow
end

"`LCHab` is the Luminance-Chroma-Hue, Polar-Lab colorspace"
struct LCHab{T<:AbstractFloat} <: Color{T,3}
    l::T # Lightness in [0,100]
    c::T # Chroma
    h::T # Hue in [0,360]
end

"`Luv` is the CIELUV colorspace"
struct Luv{T<:AbstractFloat} <: Color{T,3}
    l::T # Lightness in [0,100]
    u::T # Red/Green
    v::T # Blue/Yellow
end

"`LCHuv` is the Luminance-Chroma-Hue, Polar-Luv colorspace"
struct LCHuv{T<:AbstractFloat} <: Color{T,3}
    l::T # Lightness in [0,100]
    c::T # Chroma
    h::T # Hue in [0,360]
end

"`Oklab` is the Oklab colorspace."
struct Oklab{T<:AbstractFloat} <: Color{T,3}
    l::T # Lightness in [0,100]
    a::T # Red/Green
    b::T # Blue/Yellow
end

"`LCHOklab` is the Luminance-Chroma-Hue, Polar-Oklab colorspace"
struct LCHOklab{T<:AbstractFloat} <: Color{T,3}
    l::T # Lightness in [0,100]
    c::T # Chroma
    h::T # Hue in [0,360]
end

"`DIN99` is the (L99, a99, b99) adaptation of CIELAB"
struct DIN99{T<:AbstractFloat} <: Color{T,3}
    l::T # L99
    a::T # a99
    b::T # b99
end

"`DIN99d` is the (L99d, a99d, b99d) improvement on DIN99"
struct DIN99d{T<:AbstractFloat} <: Color{T,3}
    l::T # L99d
    a::T # a99d
    b::T # b99d
end

"`DIN99o` is the (L99o, a99o, b99o) adaptation of CIELAB"
struct DIN99o{T<:AbstractFloat} <: Color{T,3}
    l::T # L99o
    a::T # a99o
    b::T # b99o
end

"""
`LMS` is the Long-Medium-Short colorspace based on activation of the
three cone photoreceptors.  Like `XYZ`, this is a linear color space.
"""
struct LMS{T<:AbstractFloat} <: Color{T,3}
    l::T # Long
    m::T # Medium
    s::T # Short
end

"`YIQ` is a color encoding, for example used in NTSC transmission."
struct YIQ{T<:AbstractFloat} <: Color{T,3}
    y::T
    i::T
    q::T
end

"`YCbCr` is the Y'CbCr color encoding often used in digital photography or video"
struct YCbCr{T<:AbstractFloat} <: Color{T,3}
    y::T
    cb::T
    cr::T
end

"""
`RGB24` uses a `UInt32` representation of color, 0xAARRGGBB, where
R=red, G=green, B=blue and A is irrelevant. This format is often used
by external libraries such as Cairo.

`RGB24` colors do not have fields named `r`, `g`, `b`, but you can
still extract the individual components with `red(c)`, `green(c)`,
`blue(c)`.  You can construct them directly from a `UInt32`, or as
`RGB(r, g, b)`.
"""
struct RGB24 <: AbstractRGB{N0f8}
    color::UInt32

    RGB24(col::UInt32, ::Type{Val{true}}) = new(col)
end
# The Val{true} constructor solves a consistency problem, one that's
# most obvious for Gray24: Gray24(x) interprets x as a value between 0
# and 1; since a UInt32 can have value 0 or 1 (among other
# possibilities), having Gray24(x::UInt32) mean something entirely
# different (interpreting the UInt32 as a bit pattern rather than a
# value) would be inconsistent, confusing, and error-prone. Because we
# can construct an RGB24 from a grayscale value (just repeating the
# value for all 3 color channels), the same problem extends to all the
# "24/32" color types.

# However, some constructors "build" RGB24s as a UInt32 bit pattern,
# so we need some way of turning a UInt32 directly into an RGB24.
# This is the role of the Val{true} constructor. This is an internal
# implementation detail, and user code should not use it directly:
# use reinterpret(RGB24, x::UInt32) instead. (The Val{true}
# constructor is used to implement reinterpret, see traits.jl.)
function RGB24(r::N0f8, g::N0f8, b::N0f8)
    r32 = UInt32(reinterpret(r)) << 16
    g32 = UInt32(reinterpret(g)) << 8
    b32 = UInt32(reinterpret(b))
    reinterpret(RGB24, r32 | g32 | b32)
end

"""
`ARGB32` uses a `UInt32` representation of color, 0xAARRGGBB, where
R=red, G=green, B=blue and A is the alpha channel. This format is
often used by external libraries such as Cairo.  On a little-endian
machine, this type has the exact same storage format as `BGRA{N0f8}`.

`ARGB32` colors do not have fields named `alpha`, `r`, `g`, `b`, but
you can still extract the individual components with `alpha(c)`,
`red(c)`, `green(c)`, `blue(c)`.  You can construct them directly from
a `UInt32`, or as `ARGB32(r, g, b, alpha)`.
"""
struct ARGB32 <: AbstractARGB{RGB24, N0f8}
    color::UInt32

    ARGB32(col::UInt32, ::Type{Val{true}}) = new(col)
end
function ARGB32(r::N0f8, g::N0f8, b::N0f8, alpha::N0f8 = N0f8(1))
    a32 = UInt32(reinterpret(alpha)) << 24
    r32 = UInt32(reinterpret(r)) << 16
    g32 = UInt32(reinterpret(g)) << 8
    b32 = UInt32(reinterpret(b))
    reinterpret(ARGB32, a32 | r32 | g32 | b32)
end
function ARGB32(c::RGB24, alpha::Real = N0f8(1))
    checkval(ARGB32, alpha)
    a32 = UInt32(reinterpret(_rem(alpha, N0f8))) << 24
    reinterpret(ARGB32, a32 | (c.color & 0xFFFFFF))
end

"""
`Gray` is a grayscale object. You can extract its value with `gray(c)`.
"""
struct Gray{T<:Union{Fractional,Bool}} <: AbstractGray{T}
    val::T
end

"""
`Gray24` uses a `UInt32` representation of color, 0xAAIIIIII, where
I=intensity (grayscale value) and A is irrelevant. Each II pair is
assumed to be the same.  This format is often used by external
libraries such as Cairo.

You can extract the single gray value with `gray(c)`.  You can
construct them directly from a `UInt32`, or as `Gray24(i)`. Note that
`i` is interpreted on a scale from 0 (black) to 1 (white).
"""
struct Gray24 <: AbstractGray{N0f8}
    color::UInt32

    Gray24(c::UInt32, ::Type{Val{true}}) = new(c)
end
Gray24(val::N0f8) = reinterpret(Gray24, reinterpret(val) * 0x010101)

"""
`AGray32` uses a `UInt32` representation of color, 0xAAIIIIII, where
I=intensity (grayscale value) and A=alpha. Each II pair is
assumed to be the same.  This format is often used by external
libraries such as Cairo.

You can extract the single gray value with `gray(c)` and the alpha as
`alpha(c)`.  You can construct them directly from a `UInt32`, or as
`AGray32(i,alpha)`. Note that `i` and `alpha` are interpreted on a
scale from 0 (black) to 1 (white).
"""
struct AGray32 <: AbstractAGray{Gray24, N0f8}
    color::UInt32

    AGray32(c::UInt32, ::Type{Val{true}}) = new(c)
end
function AGray32(val::N0f8, alpha::N0f8 = N0f8(1))
    a32 = UInt32(reinterpret(alpha)) << 24
    reinterpret(AGray32, a32 | reinterpret(val) * 0x010101)
end
function AGray32(g::Gray24, alpha::Real= N0f8(1))
    checkval(AGray32, alpha)
    a32 = UInt32(reinterpret(_rem(alpha, N0f8))) << 24
    reinterpret(AGray32, a32 | (g.color & 0xffffff))
end

# Generated code:
#   - TransparentColor definitions (e.g., ARGB and RGBA) with inner constructors
#   - `export`s
#   - `coloralpha(::Color)` and `alphacolor(::Color)` traits for corresponding types

# Note: with the exceptions of `alphacolor` and `coloralpha`, all
# traits in the rest of this file are intended just for internal use

# Provide the field names in the order expected by the constructor
colorfields(::Type{C}) where {C<:Color} = (fieldnames(C)...,)
colorfields(::Type{C}) where {C<:XRGB} = (:r, :g, :b)
colorfields(::Type{C}) where {C<:RGBX} = (:r, :g, :b)
colorfields(::Type{C}) where {C<:BGR } = (:r, :g, :b)
colorfields(::Type{P}) where {P<:TransparentColor} = tuple(colorfields(color_type(P))..., :alpha)
colorfields(c::Colorant) = colorfields(typeof(c))

eltype_default(::Type) = Float32
eltype_default(::Type{C}) where {C<:AbstractRGB } = N0f8
eltype_default(::Type{C}) where {C<:AbstractGray} = N0f8
eltype_default(::Type{C}) where {C<:TransparentColor} = eltype_default(color_type(C))

# TODO: Generalize the promotion rules so that they are reasonable even for custom types.
@inline function promote_args_type(::Type{C}, args...) where C <: Color
    T = promote_type(map(typeof, args)...)
    _promote_args_type(eltype_default(C), T)
end
@inline function promote_args_type(::Type{C}, args...) where {C <: TransparentColor}
    Ta = typeof(last(args)) # alpha
    T = _promote_wol(map(typeof, args)...)
    if T <: Union{Integer, FixedPoint} && Ta <: Integer
        _promote_args_type(eltype_default(C), T)
    else
        _promote_args_type(eltype_default(C), promote_type(T, Ta))
    end
end
# a variant of `promote_type` that ignores the last (i.e. alpha) element
@inline _promote_wol(t, tail...) = length(tail) == 1 ? t : promote_type(t, _promote_wol(tail...))

_promote_args_type(::Type{Tdef}, ::Type{T}) where {Tdef<:AbstractFloat, T<:AbstractFloat} = T
_promote_args_type(::Type{Tdef}, ::Type{T}) where {Tdef<:AbstractFloat, T<:Real} = Tdef
_promote_args_type(::Type{Tdef}, ::Type{T}) where {Tdef<:FixedPoint, T<:Fractional} = T
_promote_args_type(::Type{Tdef}, ::Type{T}) where {Tdef<:FixedPoint, T<:Real} = Tdef
_promote_args_type(::Type{Tdef}, ::Type{T}) where {Tdef, T} = promote_type(Tdef, T)

# Generate transparent versions
macro make_alpha(C, acol, cola, fields, constrfields, ub, elty)
    # ub = upper-bound on T in C{T}
    # elty = default element type when supplied with Integer arguments
    fields = fields.args
    constrfields = constrfields.args
    N = length(fields) + 1
    Tfields = Expr[:($f::T) for f in fields]
    Targs   = Expr[:($f::T) for f in constrfields]
    Tconstr = Expr(:<:, :T, ub)
    esc(quote
        struct $acol{$Tconstr} <: AlphaColor{$C{T}, T, $N}
            alpha::T
            $(Tfields...)

            $acol{T}($(Targs...), alpha::T=oneunit(T)) where {$Tconstr} = new{T}(alpha, $(fields...))
        end
        struct $cola{$Tconstr} <: ColorAlpha{$C{T}, T, $N}
            $(Tfields...)
            alpha::T

            $cola{T}($(Targs...), alpha::T=oneunit(T)) where {$Tconstr} = new{T}($(fields...), alpha)
        end
        export $acol, $cola
        alphacolor(::Type{C}) where {C<:$C} = $acol
        coloralpha(::Type{C}) where {C<:$C} = $cola
    end)
end

const color3types = map(s->getfield(ColorTypes,s),
                        filter(names(ColorTypes, all=false)) do s
                            isdefined(ColorTypes, s) || return false
                            t = getfield(ColorTypes, s)
                            isa(t, Type{<:Color3}) && !isabstracttype(t)
                        end |> unique
                        )

# The above should have filtered out every non-DataType that's not also a
# wrapped UnionAll-wrapped DataType.
const parametric3 = filter(x->!isa(x, DataType) || !isempty(x.parameters), color3types)

# For searchability, explicitly enumerate the type names. (issue #16)
for (C, acol, cola) in [(DIN99d, :ADIN99d, :DIN99dA),
                        (DIN99o, :ADIN99o, :DIN99oA),
                        (DIN99, :ADIN99, :DIN99A),
                        (HSI, :AHSI, :HSIA),
                        (HSL, :AHSL, :HSLA),
                        (HSV, :AHSV, :HSVA),
                        (LCHab, :ALCHab, :LCHabA),
                        (LCHuv, :ALCHuv, :LCHuvA),
                        (LCHOklab, :ALCHOklab, :LCHOklabA),
                        (LMS, :ALMS, :LMSA),
                        (Lab, :ALab, :LabA),
                        (Luv, :ALuv, :LuvA),
                        (Oklab, :AOklab, :OklabA),
                        (XYZ, :AXYZ, :XYZA),
                        (YCbCr, :AYCbCr, :YCbCrA),
                        (YIQ, :AYIQ, :YIQA),
                        (xyY, :AxyY, :xyYA),
                        (BGR, :ABGR, :BGRA),
                        (RGB, :ARGB, :RGBA),
                        (Gray, :AGray, :GrayA)]
    fn  = Expr(:tuple, fieldnames(C)...)
    cfn = Expr(:tuple, colorfields(C)...)
    elty = eltype_default(C)
    ub   = elty <: FixedPoint ? Fractional : AbstractFloat
    Csym = nameof(C)
    @eval @make_alpha $Csym $acol $cola $fn $cfn $ub $elty
end

const GrayLike = Union{Number, AbstractGray}

_real(x) = real(x)
@noinline function _real(@nospecialize(x::Colorant))
    throw(ArgumentError("""
        Color objects other than `AbstractGray` cannot be used as if they were `Real` arguments."""))
end
_real(x::AbstractGray) = real(x)

function (::Type{C})() where {N, C <: ColorantN{N}}
    d0 = zero(eltype_default(C))
    dx = C <: TransparentColor ? oneunit(eltype_default(C)) : d0
    C(ntuple(_ -> d0, Val(N - 1))..., dx)
end

(::Type{C})(x         ) where {C <: Color    } = _new_colorant(C, x)
(::Type{C})(x, y      ) where {C <: ColorN{2}} = _new_colorant(C, x, y)
(::Type{C})(x, y, z   ) where {C <: ColorN{3}} = _new_colorant(C, x, y, z)
(::Type{C})(x, y, z, w) where {C <: ColorN{4}} = _new_colorant(C, x, y, z, w)

(::Type{C})(x                  ) where {C <: TransparentColor    } = _new_colorant(C, x)
(::Type{C})(x,          alpha  ) where {C <: TransparentColor    } = _new_colorant(C, x, alpha)
(::Type{C})(x, y,       alpha=1) where {C <: TransparentColorN{3}} = _new_colorant(C, x, y, alpha)
(::Type{C})(x, y, z,    alpha=1) where {C <: TransparentColorN{4}} = _new_colorant(C, x, y, z, alpha)
(::Type{C})(x, y, z, w, alpha=1) where {C <: TransparentColorN{5}} = _new_colorant(C, x, y, z, w, alpha)

(::Type{C})(g::GrayLike) where {C <: TransparentColorN{2}} = _new_colorant(C, g, 1)
(::Type{C})(g::GrayLike) where {C <: AbstractRGB} = _new_colorant(C, g, g, g)
(::Type{C})(g::GrayLike) where {C <: TransparentRGB} = _new_colorant(C, g, g, g, oneunit(g))
(::Type{C})(g::GrayLike, alpha) where {C <: TransparentRGB} = _new_colorant(C, g, g, g, alpha)

function _new_colorant(::Type{C}, args::Vararg{Any,N}) where {N, C <: ColorantN{N}}
    rargs = _real.(args)
    base_colorant_type(C){promote_args_type(C, rargs...)}(rargs...)
end

function _new_colorant(::Type{C}, c::Colorant) where {N, C <: ColorantN{N}}
    convert(C, c)
end

function _new_colorant(::Type{C}, c::Colorant, alpha) where {N, C <: TransparentColorN{N}}
    # TODO: Make the following a single `convert` call
    cc = convert(base_color_type(C), color(c))
    convert(C, cc, alpha)
end

function (::Type{C})(x::UInt32) where C <: Union{RGB24, Gray24, ARGB32, AGray32}
    x <= UInt32(1) || throw_bit_pattern_error(C, x)
    reinterpret(C, C <: Color ? (-x) & 0xffffff : (-x) | 0xff000000)
end
function (::Type{C})(x::GrayLike) where C <: Union{RGB24, Gray24, ARGB32, AGray32}
    checkval(C, real(x))
    v = _rem(real(x), N0f8)
    return C <: Union{RGB24, ARGB32} ? C(v, v, v) : C(v)
end

# T might be a Normed, and so some inputs will result in an error.
# Try to make it a nice error.
function _new_colorant(::Type{C}, args::Vararg{GrayLike,N}) where {N, T, C <: Color{T,N}}
    isconcretetype(C) || throw(MethodError(C, args))
    r = real.(args)
    checkval(C, r...)
    C(_rem.(r, T)...)
end
function _new_colorant(::Type{TC}, args::Vararg{GrayLike,N}) where {N, T, C, TC <: TransparentColor{C,T,N}}
    r = real.(args)
    checkval(TC, r...)
    TC(_rem.(r, T)...)
end
function _new_colorant(::Type{TC}, args::Vararg{GrayLike,2}) where {T, C, TC <: TransparentColor{C,T,2}}
    r = real.(args)
    checkval(TC, r...)
    TC(_rem.(r, T)...)
end

"""
    alphacolor(::Type{<:Colorant})
    alphacolor(::Colorant)

Return the corresponding transparent color type/instance with storage order
(alpha, color).

# Examples
```jldocest; setup = :(using ColorTypes)
julia> alphacolor(RGB)
ARGB

julia> alphacolor(RGBA{Float32})
ARGB

julia> alphacolor(Gray(0.8)) === AGray(0.8, 1.0)
true
```
"""
alphacolor(::Type{C}) where {C<:AlphaColor} = base_colorant_type(C)
alphacolor(::Type{C}) where {C<:ColorAlpha} = alphacolor(base_color_type(C))

"""
    coloralpha(::Type{<:Colorant})
    coloralpha(::Colorant)

Return the corresponding transparent color type/instance with storage order
(color, alpha).

# Examples
```jldocest; setup = :(using ColorTypes)
julia> coloralpha(RGB)
RGBA

julia> coloralpha(ARGB{Float32})
RGBA

julia> coloralpha(Gray(0.8)) === GrayA(0.8, 1.0)
true
```
"""
coloralpha(::Type{C}) where {C<:ColorAlpha} = base_colorant_type(C)
coloralpha(::Type{C}) where {C<:AlphaColor} = coloralpha(base_color_type(C))

alphacolor(::Type{C}) where {C<:Gray24} = AGray32
alphacolor(::Type{C}) where {C<:RGB24} = ARGB32
alphacolor(::Type{C}) where {C<:XRGB} = ARGB
alphacolor(::Type{C}) where {C<:RGBX} = ARGB
coloralpha(::Type{C}) where {C<:XRGB} = RGBA
coloralpha(::Type{C}) where {C<:RGBX} = RGBA

### Validating the inputs for Normed constructors

# Throw helpful errors in case of trouble. The inlining here is
# carefully designed to reduce the impact on runtime performance, and
# we also avoid splatting.

@inline function isok(::Type{T}, x) where T<:Normed
    Δ = eps(T)/2 # as long as the number rounds to a valid number, that's OK
    (-Δ <= x) & (x < typemax(T)+Δ)
end
@inline function isok(::Type{T}, x, y) where T<:Normed
    Δ = eps(T)/2
    @fastmath n, m = minmax(x, y)
    (-Δ <= n) & (m < typemax(T)+Δ)
end

@inline function checkval(::Type{C}, a) where {T<:Normed, C<:Colorant{T}}
    isok(T, a) || throw_colorerror(C, (a,))
end
@inline function checkval(::Type{C}, a, b) where {T<:Normed, C<:Colorant{T}}
    isok(T, a, b) || throw_colorerror(C, (a, b))
end
@inline function checkval(::Type{C}, a, b, c) where {T<:Normed, C<:Colorant{T}}
    isok(T, a, b) & isok(T, b, c) || throw_colorerror(C, (a, b, c))
end
@inline function checkval(::Type{C}, a, b, c, d) where {T<:Normed, C<:Colorant{T}}
    Δ = eps(T)/2
    @fastmath min_ab, max_ab = minmax(a, b)
    @fastmath min_cd, max_cd = minmax(c, d)
    @fastmath n = min(min_ab, min_cd)
    @fastmath m = max(max_ab, max_cd)
    (-Δ <= n) & (m < typemax(T)+Δ) || throw_colorerror(C, (a, b, c, d))
end
@inline function checkval(::Type{C}, a, b, c, d, e) where {T<:Normed, C<:Colorant{T}}
    isok(T, a, b) & isok(T, c, d) & isok(T, e) || throw_colorerror(C, (a, b, c, d, e))
end

checkval(::Type{C}, args::Vararg{T}) where {T<:Normed, C<:Colorant{T}} = nothing
checkval(::Type{C}, args...) where {C<:Colorant} = nothing

function throw_colorerror_(::Type{T}, values) where T<:Normed
    Tmin = repr(typemin(T), context=:compact=>true)
    Tmax = repr(typemax(T), context=:compact=>true)
    bitstring = sizeof(T) == 1 ? "an 8-bit" : "a $(8*sizeof(T))-bit"
    throw(ArgumentError("""
component type $T is $bitstring type representing $(2^(8*sizeof(T))) values from $Tmin to $Tmax,
  but the values $values do not lie within this range.
  See the READMEs for FixedPointNumbers and ColorTypes for more information."""))
end

function throw_colorerror(::Type{C}, values::Tuple{Vararg{Integer}}) where C<:Colorant{N0f8}
    # Let's try to read the user's mind
    all(x->0<=x<=255, values) || throw_colorerror_(N0f8, values)
    if length(values) == 1
        vstr = "$(values[1]) is an integer"
    else
        vstr = "$values are integers"
    end
    Cstr = colorant_string_with_eltype(C)
    args = join(map(v -> "$v/255", values), ", ")
    throw(ArgumentError("""
The components of $(nameof(C)) are normalized to the range 0-1,
  but $vstr in the range 0-255.
  Consider dividing your input values by 255, for example: $Cstr($args)
  The component type N0f8 is an 8-bit type representing 256 values from 0 to 1.
  You can also use `reinterpret(N0f8, x % UInt8)` to encode the input into N0f8.
  See the READMEs for FixedPointNumbers and ColorTypes for more information."""))
end
function throw_colorerror(::Type{C}, values::Tuple) where {T<:Normed, C<:Colorant{T}}
    throw_colorerror_(T, values)
end

function throw_bit_pattern_error(@nospecialize(C), value)
    hex = string(value, base=16, pad=8)
    throw(ArgumentError("""
$C cannot be constructed or converted directly from a UInt32 input as a bit pattern.
  Use `reinterpret($C, 0x$hex)` instead."""))
end

_rem(x, ::Type{T}) where {T<:Normed} = x % T
_rem(x, ::Type{T}) where {T}         = convert(T, x)

struct ColorTypeResolutionError <: Exception
    func::Symbol
    msg::String
    C1
    C2
end

function Base.showerror(io::IO, ex::ColorTypeResolutionError)
    print(io, "in ", ex.func, ", ", ex.msg, ' ', ex.C1, " and ", ex.C2)
end


using Reactive, GLAbstraction, GeometryTypes
import Reactive: edges, nodes
# Base.step() = Reactive.run(1)
Reactive.stop()

function test1(; use_async = true)
    # Reactive.run_async(use_async)
    a = Signal(0.0)

    b = map(/, a, Signal(23.0))
    c = map(/, a, Signal(8.0))
    f = foldp(+, 0.0, b)

    d = map(Vec3f0, b)
    e = map(Vec3f0, c)
    g = map(Vec3f0, f)

    m = map(translationmatrix, d)
    m2 = map(translationmatrix, e)

    m3 = map(*, m, m2)
    # I don't know why, but Mat*Vec is broken right now
    result = map(m3, g) do a, b
         r = a * Vec4f0(b, 1)
         Vec3f0(r[1], r[2], r[3])
    end
    total_time = 0.0

    # warm the cache
    push!(a, 0.1) # note causes the result to be slightly different, noticable for small N
    Reactive.run_till_now()
    println("post warmup, length(nodes): ", length(nodes))

    for i=1:N
        tic()
        push!(a, i)
        Reactive.run(1) # only needed for async
        total_time += toq()
    end

    @show(total_time)
    @show(total_time/N)
    value(result), total_time
end

function bf(a,c)
    a/c
end

function test2()
    total_time = 0.0
    a = 0.0
    accum = 0.0
    function ff(x)
        accum += x
    end
    local result
    for i=1:N
        tic()

        a = i
        b = bf(a, 23.0)
        c = bf(a, 8.0)
        f = ff(b)
        d = Vec3f0(b)
        e = Vec3f0(c)
        g = Vec3f0(f)

        m = translationmatrix(d)
        m2 = translationmatrix(e)

        m3 = m*m2
        r = m3 * Vec4f0(g, 1)
        result = Vec3f0(r[1], r[2], r[3])
        total_time += toq()
    end

    @show(total_time)
    @show(total_time/N)
    result, total_time
end

N = 10^6
react_res, react_time = test1(use_async=true)
regular_res, regular_time = test2()
@show react_res regular_res
react_time/regular_time


"""
    ODESolvers

Ordinary differential equation solvers
"""
module ODESolvers

using GPUifyLoops
using StaticArrays
using Requires
@init @require CUDAnative = "be33ccc6-a3ff-5ff2-a52e-74243cff1e17" begin
  using .CUDAnative
end

using ..SpaceMethods
using ..LinearSolvers
using ..MPIStateArrays: device, realview

export solve!, updatedt!, gettime

abstract type AbstractODESolver end
"""
    gettime(solver::AbstractODESolver)

Returns the current simulation time of the ODE solver `solver`
"""
gettime(solver::AbstractODESolver) = solver.t

"""
    getdt(solver::AbstractODESolver)

Returns the current simulation time step of the ODE solver `solver`
"""
getdt(solver::AbstractODESolver) = solver.dt

function dostep! end

"""
    updatedt!(solver::AbstractODESolver, dt)

Change the time step size to `dt` for the ODE solver `solver`.
"""
updatedt!(solver::AbstractODESolver, dt) =
  error("Variable time stepping not implemented for $(typeof(solver))")

"""
    updatetime!(solver::AbstractODESolver, time)

Change the current time to `time` for the ODE solver `solver`.
"""
updatetime!(solver::AbstractODESolver, time) =
  error("Variable time stepping not implemented for $(typeof(solver))")

isadjustable(solver::AbstractODESolver) = true

# {{{ run!
"""
    solve!(Q, solver::AbstractODESolver; timeend,
           stopaftertimeend=true, numberofsteps, callbacks)

Solves an ODE using the `solver` starting from a state `Q`. The state `Q` is
updated inplace. The final time `timeend` or `numberofsteps` must be specified.

A series of optional callback functions can be specified using the tuple
`callbacks`; see [`GenericCallbacks`](@ref).
"""
function solve!(Q, solver::AbstractODESolver, p=nothing; timeend::Real=Inf,
                adjustfinalstep=true, numberofsteps::Integer=0, callbacks=())

  @assert isfinite(timeend) || numberofsteps > 0
  if adjustfinalstep && !isadjustable(solver)
    error("$solver does not support time step adjustments. Can only be used with `adjustfinalstep=false`.")
  end
  t0 = gettime(solver)

  # Loop through an initialize callbacks (if they need it)
  foreach(callbacks) do cb
    try
      cb(true)
    catch
    end
  end

  step = 0
  time = t0
  while time < timeend
    step += 1

    time = dostep!(Q, solver, p, timeend, adjustfinalstep)

    # FIXME: Determine better way to handle postcallback behavior
    # Current behavior:
    #   retval = 1 exit after all callbacks
    #   retval = 2 exit immediately
    retval = 0
    for (i, cb) in enumerate(callbacks)
      # FIXME: Consider whether callbacks need anything, or if function closure
      #        can be used for everything
      thisretval = cb()
      thisretval = (thisretval == nothing) ? 0 : thisretval
      !(thisretval in (0, 1, 2)) &&
      error("callback #$(i) returned invalid value. It should return either:
            `nothing` (continue time stepping)
            `0`       (continue time stepping)
            `1`       (stop time stepping after all callbacks)
            `2`       (stop time stepping immediately)")
      retval = max(thisretval, retval)
      retval == 2 && return gettime(solver)
    end
    retval == 1 && return gettime(solver)

    # Figure out if we should stop
    if numberofsteps == step
      return gettime(solver)
    end
  end
  gettime(solver)
end
# }}}

include("LowStorageRungeKuttaMethod.jl")
include("StrongStabilityPreservingRungeKuttaMethod.jl")
include("AdditiveRungeKuttaMethod.jl")
include("MultirateInfinitesimalStepMethod.jl")
include("MultirateRungeKuttaMethod.jl")

end # module


push!(LOAD_PATH, "./src/")

using AdaGram
using AdaGram.ArgParse
using Distributed

s = ArgParseSettings()

@add_arg_table s begin
  "model"
    help = "path to serialized model"
    arg_type = AbstractString
    required = true
  "text"
    help = "text to measure likelihood"
    arg_type = AbstractString
    required = true
  "--window"
    help = "window size"
    arg_type = Int
    default = 5
  "--workers"
    help = "number of workers"
    arg_type = Int
    default = 1
  "--minprob"
    help = "minimum probability of a prototype"
    arg_type = Float64
    default = 0.005
  "--batch"
    help = "size of buffer read into memory"
    arg_type = Int
    default = 16777216
  "--log"
    help = "save intermediate averages to the file"
    arg_type = AbstractString
end

args = parse_args(ARGS, s)

addprocs(args["workers"])
@everywhere using AdaGram

vm, dict = load_model(args["model"])
println(parallel_likelihood(vm, dict, args["text"], args["window"], args["minprob"];
  batch=args["batch"], log=args["log"]))


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENSE in the project root.
# ------------------------------------------------------------------
# TODO: review
"""
    CookieCutter(master, others)

A cookie-cutter simulation solver.

## Parameters

* `master` - Master simulation solver (a.k.a. facies solver)
* `others` - A list of pairs mapping categories to solvers

## Examples

Simulate lithology facies with image quilting and fill property
with direct Gaussian simulation:

```julia
julia> fsolver  = ImgQuilt(:facies => (TI=Strebelle, template=(30,30,1)))
julia> psolver₀ = DirectGaussSim(:property => (variogram=SphericalVariogram(range=10.),))
julia> psolver₁ = DirectGaussSim(:property => (variogram=SphericalVariogram(range=20.),))
julia> solver   = CookieCutter(fsolver, [0 => psolver₀, 1 => psolver₁])
```
"""
struct CookieCutter <: AbstractSimulationSolver
  master::AbstractSimulationSolver
  others::Dict{Number,AbstractSimulationSolver}
end

function solve(problem::SimulationProblem, solver::CookieCutter)
  # retrieve problem info
  pdata   = data(problem)
  pdomain = domain(problem)
  pvars   = variables(problem)
  preals  = nreals(problem)
  pmapper = mapper(problem)

  # master variable
  mvars = variables(solver.master)
  @assert length(mvars) == 1 "one single variable must be specified in master solver"
  mvar = mvars[1]
  @assert mvar ∈ keys(pvars) "invalid variable in master solver"
  mtype = pvars[mvar]

  # other variables
  ovars = Tuple(var => V for (var, V) in pvars if var ≠ mvar)
  @assert length(ovars) > 0 "cookie-cutter requires problem with more than one target variable"

  # define master and others problem
  if hasdata(problem)
    mproblem = SimulationProblem(pdata, pdomain, mvar, preals, mapper=pmapper)
    oproblem = SimulationProblem(pdata, pdomain, first.(ovars), preals, mapper=pmapper)
  else
    mproblem = SimulationProblem(pdomain, mvar => mtype, preals, mapper=pmapper)
    oproblem = SimulationProblem(pdomain, ovars, preals, mapper=pmapper)
  end

  # realizations of master variable
  msol = solve(mproblem, solver.master)
  mreals = msol[mvar]

  # pre-allocate memory for realizations
  reals = Dict(var => [Vector{V}(undef, nelms(pdomain)) for i in 1:preals] for (var,V) in ovars)

  # solve other problems
  for (mval, osolver) in solver.others
    osol = solve(oproblem, osolver)

    # cookie-cutter step
    for (var, V) in ovars
      vreals = osol[var]
      for i in 1:preals
        mask = mreals[i] .== mval
        reals[var][i][mask] .= vreals[i][mask]
      end
    end
  end

  realizations = merge(reals, Dict(mvar => mreals))

  SimulationSolution(pdomain, realizations)
end

# ------------
# IO methods
# ------------
function Base.show(io::IO, solver::CookieCutter)
  print(io, "CookieCutter")
end

function Base.show(io::IO, ::MIME"text/plain", solver::CookieCutter)
  mvar = variables(solver.master)[1]
  println(io, solver)
  println(io, "  └─", mvar, " ⇨ ", solver.master)
  for (val, osolver) in solver.others
    println(io, "    └─", val, " ⇨ ", osolver)
  end
end


function Tuple(arg0::Vector{jdouble})
    return Tuple((Vector{jdouble},), arg0)
end

function abs(obj::Tuple)
    return jcall(obj, "abs", Tuple, ())
end

function acos(obj::Tuple)
    return jcall(obj, "acos", Tuple, ())
end

function acosh(obj::Tuple)
    return jcall(obj, "acosh", Tuple, ())
end

function add(obj::Tuple, arg0::Tuple)
    return jcall(obj, "add", Tuple, (Tuple,), arg0)
end

function add(obj::Tuple, arg0::jdouble)
    return jcall(obj, "add", Tuple, (jdouble,), arg0)
end

function asin(obj::Tuple)
    return jcall(obj, "asin", Tuple, ())
end

function asinh(obj::Tuple)
    return jcall(obj, "asinh", Tuple, ())
end

function atan(obj::Tuple)
    return jcall(obj, "atan", Tuple, ())
end

function atan2(obj::Tuple, arg0::Tuple)
    return jcall(obj, "atan2", Tuple, (Tuple,), arg0)
end

function atanh(obj::Tuple)
    return jcall(obj, "atanh", Tuple, ())
end

function cbrt(obj::Tuple)
    return jcall(obj, "cbrt", Tuple, ())
end

function ceil(obj::Tuple)
    return jcall(obj, "ceil", Tuple, ())
end

function copy_sign(obj::Tuple, arg0::Tuple)
    return jcall(obj, "copySign", Tuple, (Tuple,), arg0)
end

function copy_sign(obj::Tuple, arg0::jdouble)
    return jcall(obj, "copySign", Tuple, (jdouble,), arg0)
end

function cos(obj::Tuple)
    return jcall(obj, "cos", Tuple, ())
end

function cosh(obj::Tuple)
    return jcall(obj, "cosh", Tuple, ())
end

function divide(obj::Tuple, arg0::Tuple)
    return jcall(obj, "divide", Tuple, (Tuple,), arg0)
end

function divide(obj::Tuple, arg0::jdouble)
    return jcall(obj, "divide", Tuple, (jdouble,), arg0)
end

function equals(obj::Tuple, arg0::Object)
    return jcall(obj, "equals", jboolean, (Object,), arg0)
end

function exp(obj::Tuple)
    return jcall(obj, "exp", Tuple, ())
end

function expm1(obj::Tuple)
    return jcall(obj, "expm1", Tuple, ())
end

function floor(obj::Tuple)
    return jcall(obj, "floor", Tuple, ())
end

function get_component(obj::Tuple, arg0::jint)
    return jcall(obj, "getComponent", jdouble, (jint,), arg0)
end

function get_components(obj::Tuple)
    return jcall(obj, "getComponents", Vector{jdouble}, ())
end

function get_dimension(obj::Tuple)
    return jcall(obj, "getDimension", jint, ())
end

function get_field(obj::Tuple)
    return jcall(obj, "getField", Field, ())
end

function get_real(obj::Tuple)
    return jcall(obj, "getReal", jdouble, ())
end

function hash_code(obj::Tuple)
    return jcall(obj, "hashCode", jint, ())
end

function hypot(obj::Tuple, arg0::Tuple)
    return jcall(obj, "hypot", Tuple, (Tuple,), arg0)
end

function linear_combination(obj::Tuple, arg0::Tuple, arg1::Tuple, arg2::Tuple, arg3::Tuple)
    return jcall(obj, "linearCombination", Tuple, (Tuple, Tuple, Tuple, Tuple), arg0, arg1, arg2, arg3)
end

function linear_combination(obj::Tuple, arg0::Tuple, arg1::Tuple, arg2::Tuple, arg3::Tuple, arg4::Tuple, arg5::Tuple)
    return jcall(obj, "linearCombination", Tuple, (Tuple, Tuple, Tuple, Tuple, Tuple, Tuple), arg0, arg1, arg2, arg3, arg4, arg5)
end

function linear_combination(obj::Tuple, arg0::Tuple, arg1::Tuple, arg2::Tuple, arg3::Tuple, arg4::Tuple, arg5::Tuple, arg6::Tuple, arg7::Tuple)
    return jcall(obj, "linearCombination", Tuple, (Tuple, Tuple, Tuple, Tuple, Tuple, Tuple, Tuple, Tuple), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
end

function linear_combination(obj::Tuple, arg0::Vector{Tuple}, arg1::Vector{Tuple})
    return jcall(obj, "linearCombination", Tuple, (Vector{Tuple}, Vector{Tuple}), arg0, arg1)
end

function linear_combination(obj::Tuple, arg0::Vector{jdouble}, arg1::Vector{Tuple})
    return jcall(obj, "linearCombination", Tuple, (Vector{jdouble}, Vector{Tuple}), arg0, arg1)
end

function linear_combination(obj::Tuple, arg0::jdouble, arg1::Tuple, arg2::jdouble, arg3::Tuple)
    return jcall(obj, "linearCombination", Tuple, (jdouble, Tuple, jdouble, Tuple), arg0, arg1, arg2, arg3)
end

function linear_combination(obj::Tuple, arg0::jdouble, arg1::Tuple, arg2::jdouble, arg3::Tuple, arg4::jdouble, arg5::Tuple)
    return jcall(obj, "linearCombination", Tuple, (jdouble, Tuple, jdouble, Tuple, jdouble, Tuple), arg0, arg1, arg2, arg3, arg4, arg5)
end

function linear_combination(obj::Tuple, arg0::jdouble, arg1::Tuple, arg2::jdouble, arg3::Tuple, arg4::jdouble, arg5::Tuple, arg6::jdouble, arg7::Tuple)
    return jcall(obj, "linearCombination", Tuple, (jdouble, Tuple, jdouble, Tuple, jdouble, Tuple, jdouble, Tuple), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7)
end

function log(obj::Tuple)
    return jcall(obj, "log", Tuple, ())
end

function log10(obj::Tuple)
    return jcall(obj, "log10", Tuple, ())
end

function log1p(obj::Tuple)
    return jcall(obj, "log1p", Tuple, ())
end

function multiply(obj::Tuple, arg0::Tuple)
    return jcall(obj, "multiply", Tuple, (Tuple,), arg0)
end

function multiply(obj::Tuple, arg0::jdouble)
    return jcall(obj, "multiply", Tuple, (jdouble,), arg0)
end

function multiply(obj::Tuple, arg0::jint)
    return jcall(obj, "multiply", Tuple, (jint,), arg0)
end

function negate(obj::Tuple)
    return jcall(obj, "negate", Tuple, ())
end

function new_instance(obj::Tuple, arg0::jdouble)
    return jcall(obj, "newInstance", Tuple, (jdouble,), arg0)
end

function pow(obj::Tuple, arg0::Tuple)
    return jcall(obj, "pow", Tuple, (Tuple,), arg0)
end

function pow(obj::Tuple, arg0::jdouble)
    return jcall(obj, "pow", Tuple, (jdouble,), arg0)
end

function pow(obj::Tuple, arg0::jint)
    return jcall(obj, "pow", Tuple, (jint,), arg0)
end

function reciprocal(obj::Tuple)
    return jcall(obj, "reciprocal", Tuple, ())
end

function remainder(obj::Tuple, arg0::Tuple)
    return jcall(obj, "remainder", Tuple, (Tuple,), arg0)
end

function remainder(obj::Tuple, arg0::jdouble)
    return jcall(obj, "remainder", Tuple, (jdouble,), arg0)
end

function rint(obj::Tuple)
    return jcall(obj, "rint", Tuple, ())
end

function root_n(obj::Tuple, arg0::jint)
    return jcall(obj, "rootN", Tuple, (jint,), arg0)
end

function scalb(obj::Tuple, arg0::jint)
    return jcall(obj, "scalb", Tuple, (jint,), arg0)
end

function signum(obj::Tuple)
    return jcall(obj, "signum", Tuple, ())
end

function sin(obj::Tuple)
    return jcall(obj, "sin", Tuple, ())
end

function sin_cos(obj::Tuple)
    return jcall(obj, "sinCos", FieldSinCos, ())
end

function sinh(obj::Tuple)
    return jcall(obj, "sinh", Tuple, ())
end

function sqrt(obj::Tuple)
    return jcall(obj, "sqrt", Tuple, ())
end

function subtract(obj::Tuple, arg0::Tuple)
    return jcall(obj, "subtract", Tuple, (Tuple,), arg0)
end

function subtract(obj::Tuple, arg0::jdouble)
    return jcall(obj, "subtract", Tuple, (jdouble,), arg0)
end

function tan(obj::Tuple)
    return jcall(obj, "tan", Tuple, ())
end

function tanh(obj::Tuple)
    return jcall(obj, "tanh", Tuple, ())
end

function to_degrees(obj::Tuple)
    return jcall(obj, "toDegrees", Tuple, ())
end

function to_radians(obj::Tuple)
    return jcall(obj, "toRadians", Tuple, ())
end


module ExactDiagonalization

using Arpack: eigs
using LinearAlgebra: Eigen
using Printf: @printf, @sprintf
using Base.Iterators: product
using SparseArrays: SparseMatrixCSC
using QuantumLattices: EnumerativeVectorSpace, VectorSpace, DulPermutations, Combinations
using QuantumLattices: OperatorUnit, OperatorProd, OperatorSum, Operator, Operators, MatrixRepresentation, Transformation
using QuantumLattices: AbstractLattice, Bonds, Hilbert, Fock, Spin, FockTerm, SpinTerm, Term, Metric, OIDToTuple, Table, Boundary, Generator, Image, Engine
using QuantumLattices: reparameter, id, idtype, expand, plain, creation, rank

import LinearAlgebra: eigen
import QuantumLattices: add!, ⊕, ⊗, parameternames, contentnames, getcontent, dtype, kind, update!, statistics, matrix, dimension, Parameters

export Sector, BinaryBasis, BinaryBasisRange, BinaryBases, TargetSpace
export EDMatrix, EDMatrixRepresentation, SectorFilter, EDKind, ED
export productable, sumable

"""
    abstract type Sector <: OperatorUnit

A sector of the Hilbert space which form the bases of an irreducible representation of the Hamiltonian of a quantum lattice system.
"""
abstract type Sector <: OperatorUnit end

"""
    TargetSpace{S<:Sector} <: EnumerativeVectorSpace{S}

The target Hilbert space in which the exact diagonalization method is performed, which could be the direct sum of several sectors.
"""
struct TargetSpace{S<:Sector} <: EnumerativeVectorSpace{S}
    sectors::Vector{S}
end
@inline contentnames(::Type{<:TargetSpace}) = (:table,)
@inline getcontent(target::TargetSpace, ::Val{:table}) = target.sectors
function add!(target::TargetSpace, sector::Sector)
    @assert all(map(sec->sumable(sec, sector), target.sectors)) "add! error: could not be direct summed."
    push!(target.sectors, sector)
    return target
end
function add!(target::TargetSpace, another::TargetSpace)
    for sector in another
        add!(target, sector)
    end
    return target
end

"""
    ⊕(sector::Sector, sectors::Union{Sector, TargetSpace}...) -> TargetSpace
    ⊕(target::TargetSpace, sectors::Union{Sector, TargetSpace}...) -> TargetSpace

Get the direct sum of sectors and target spaces.
"""
@inline function ⊕(sector::Sector, sectors::Union{Sector, TargetSpace}...)
    result = TargetSpace(sector)
    map(op->add!(result, op), sectors)
    return result
end
@inline function ⊕(target::TargetSpace, sectors::Union{Sector, TargetSpace}...)
    result = TargetSpace(copy(target.sectors))
    map(op->add!(result, op), sectors)
    return result
end

"""
    TargetSpace(sector::Sector, sectors::Sector...)

Construct a target space from sectors.
"""
@inline function TargetSpace(sector::Sector, sectors::Sector...)
    result = TargetSpace([sector])
    for sec in sectors
        add!(result, sec)
    end
    return result
end

# Binary bases commonly used in canonical fermionic and hardcore bosonic quantum lattice systems
"""
    BinaryBasis{I<:Unsigned}

Binary basis represented by an unsigned integer.
"""
struct BinaryBasis{I<:Unsigned}
    rep::I
    BinaryBasis(i::Integer) = (rep = Unsigned(i); new{typeof(rep)}(rep))
end
@inline Base.:(==)(basis₁::BinaryBasis, basis₂::BinaryBasis) = basis₁.rep == basis₂.rep
@inline Base.isequal(basis₁::BinaryBasis, basis₂::BinaryBasis) = isequal(basis₁.rep, basis₂.rep)
@inline Base.:<(basis₁::BinaryBasis, basis₂::BinaryBasis) = basis₁.rep < basis₂.rep
@inline Base.isless(basis₁::BinaryBasis, basis₂::BinaryBasis) = isless(basis₁.rep, basis₂.rep)
@inline Base.one(basis::BinaryBasis) = one(typeof(basis))
@inline Base.one(::Type{BinaryBasis{I}}) where {I<:Unsigned} = BinaryBasis(one(I))
@inline Base.zero(basis::BinaryBasis) = zero(typeof(basis))
@inline Base.zero(::Type{BinaryBasis{I}}) where {I<:Unsigned} = BinaryBasis(zero(I))
@inline Base.show(io::IO, basis::BinaryBasis) = @printf io "%s" string(basis.rep, base=2)
@inline Base.eltype(basis::BinaryBasis) = eltype(typeof(basis))
@inline Base.eltype(::Type{<:BinaryBasis}) = Int
@inline Base.IteratorSize(::Type{<:BinaryBasis}) = Base.SizeUnknown()

"""
    iterate(basis::BinaryBasis, state=nothing)

Iterate over the numbers of the occupied single-particle orbitals.
"""
function Base.iterate(basis::BinaryBasis, state=nothing)
    (pos, rep) = isnothing(state) ? (0, basis.rep) : (state[1], state[2])
    while rep>0
        pos += 1
        isodd(rep) && return (pos, (pos, rep÷2))
        rep ÷= 2
    end
    return nothing
end

"""
    one(basis::BinaryBasis, state::Integer) -> BinaryBasis

Get a new basis with the specified single-particle state occupied.
"""
@inline Base.one(basis::BinaryBasis, state::Integer) = BinaryBasis(basis.rep | one(basis.rep)<<(state-1))

"""
    isone(basis::BinaryBasis, state::Integer) -> Bool

Judge whether the specified single-particle state is occupied for a basis.
"""
@inline Base.isone(basis::BinaryBasis, state::Integer) = (basis.rep & one(basis.rep)<<(state-1))>0

"""
    zero(basis::BinaryBasis, state::Integer) -> BinaryBasis

Get a new basis with the specified single-particle state unoccupied.
"""
@inline Base.zero(basis::BinaryBasis, state::Integer) = BinaryBasis(basis.rep & ~(one(basis.rep)<<(state-1)))

"""
    iszero(basis::BinaryBasis, state::Integer) -> Bool

Judge whether the specified single-particle state is unoccupied for a basis.
"""
@inline Base.iszero(basis::BinaryBasis, state::Integer) = !isone(basis, state)

"""
    count(basis::BinaryBasis) -> Int
    count(basis::BinaryBasis, start::Integer, stop::Integer) -> Int

Count the number of occupied single-particle states.
"""
@inline Base.count(basis::BinaryBasis) = count(basis, 1, ndigits(basis.rep, base=2))
@inline function Base.count(basis::BinaryBasis, start::Integer, stop::Integer)
    result = 0
    for i = start:stop
        isone(basis, i) && (result += 1)
    end
    return result
end

"""
    ⊗(basis₁::BinaryBasis, basis₂::BinaryBasis) -> BinaryBasis

Get the direct product of two binary bases.
"""
@inline ⊗(basis₁::BinaryBasis, basis₂::BinaryBasis) = BinaryBasis(basis₁.rep|basis₂.rep)

"""
    BinaryBasis(states; filter=index->true)
    BinaryBasis{I}(states; filter=index->true) where {I<:Unsigned}

Construct a binary basis with the given occupied orbitals.
"""
@inline BinaryBasis(states; filter=index->true) = BinaryBasis{typeof(Unsigned(first(states)))}(states; filter=filter)
function BinaryBasis{I}(states; filter=index->true) where {I<:Unsigned}
    rep, eye = zero(I), one(I)
    for (index, state) in enumerate(states)
        filter(index) && (rep += eye<<(state-1))
    end
    return BinaryBasis(rep)
end

"""
    BinaryBasisRange{I<:Unsigned} <: VectorSpace{BinaryBasis{I}}

A continuous range of binary basis.
"""
struct BinaryBasisRange{I<:Unsigned} <: VectorSpace{BinaryBasis{I}}
    slice::UnitRange{I}
end
@inline Base.issorted(bbr::BinaryBasisRange) = true
@inline dimension(bbr::BinaryBasisRange) = length(bbr.slice)
@inline Base.getindex(bbr::BinaryBasisRange, i::Integer) = BinaryBasis(bbr.slice[i])

"""
    BinaryBases{B<:BinaryBasis, T<:AbstractVector{B}} <: Sector

A set of binary bases.
"""
struct BinaryBases{B<:BinaryBasis, T<:AbstractVector{B}} <: Sector
    id::Vector{Tuple{B, Float64}}
    table::T
end
@inline Base.issorted(bs::BinaryBases) = true
@inline Base.length(bs::BinaryBases) = dimension(bs)
@inline dimension(bs::BinaryBases) = length(bs.table)
@inline Base.:(==)(bs₁::BinaryBases, bs₂::BinaryBases) = isequal(bs₁.id, bs₂.id)
@inline Base.isequal(bs₁::BinaryBases, bs₂::BinaryBases) = isequal(bs₁.id, bs₂.id)
@inline Base.getindex(bs::BinaryBases, i::Integer) = bs.table[i]
@inline Base.eltype(bs::BinaryBases) = eltype(typeof(bs))
@inline Base.eltype(::Type{<:BinaryBases{B}}) where {B<:BinaryBasis} = B
@inline Base.iterate(bs::BinaryBases, state=1) = state>dimension(bs) ? nothing : (bs.table[state], state+1)
function Base.repr(bs::BinaryBases)
    result = String[]
    for (rep, nparticle) in bs.id
        if isnan(nparticle)
            push!(result, @sprintf "2^%s" count(rep))
        else
            push!(result, @sprintf "C(%s, %s)" count(rep) Int(nparticle))
        end
    end
    return join(result, " ⊗ ")
end
function Base.show(io::IO, bs::BinaryBases)
    @printf io "%s:\n" repr(bs)
    for i = 1:dimension(bs)
        @printf io "  %s\n" bs[i]
    end
end
@inline Base.findfirst(b::BinaryBasis, bs::BinaryBases) = searchsortedfirst(bs.table, b)
@inline Base.findfirst(b::BinaryBasis, bs::BinaryBases{<:BinaryBasis, <:BinaryBasisRange}) = Int(b.rep+1)

"""
    ⊗(bs₁::BinaryBases, bs₂::BinaryBases) -> BinaryBases

Get the direct product of two sets of binary bases.
"""
function ⊗(bs₁::BinaryBases, bs₂::BinaryBases)
    @assert productable(bs₁, bs₂) "⊗ error: the input two sets of bases cannot be direct producted."
    table = Vector{promote_type(eltype(bs₁), eltype(bs₂))}(undef, dimension(bs₁)*dimension(bs₂))
    count = 1
    for (b₁, b₂) in product(bs₁, bs₂)
        table[count] = b₁⊗b₂
        count += 1
    end
    return BinaryBases(sort!([bs₁.id; bs₂.id]; by=first), sort!(table))
end

"""
    productable(bs₁::BinaryBases, bs₂::BinaryBases) -> Bool

Judge whether two sets of binary bases could be direct producted.
"""
function productable(bs₁::BinaryBases, bs₂::BinaryBases)
    for (irr₁, irr₂) in product(bs₁.id, bs₂.id)
        isequal(irr₁[1].rep & irr₂[1].rep, 0) || return false
    end
    return true
end

"""
    sumable(bs₁::BinaryBases, bs₂::BinaryBases) -> Bool

Judge whether two sets of binary bases could be direct summed.

Strictly speaking, two sets of binary bases could be direct summed if and only if they have no intersection. The time complexity to check the intersection is O(n log n), which costs a lot when the dimension of the binary bases is huge. It is also possible to judge whether they could be direct summed by close investigations on their ids, i.e. the single-particle states and occupation number. It turns out that this is a multi-variable pure integer linear programming problem. In the future, this function would be implemented based on this observation. At present, the direct summability should be handled by the users in priori.
"""
@inline sumable(bs₁::BinaryBases, bs₂::BinaryBases) = true

"""
    BinaryBases(nstate::Integer)
    BinaryBases(states)

Construct a set of binary bases that does not preserve the particle number conservation.
"""
@inline BinaryBases(nstate::Integer) = BinaryBases([(BinaryBasis(1:nstate), NaN)], BinaryBasisRange(UInt(0):UInt(2^nstate-1)))
@inline BinaryBases(states) = BinaryBases(Tuple(states))
function BinaryBases(states::NTuple{N, Integer}) where N
    states = NTuple{N, eltype(states)}(sort!(collect(states); rev=true))
    com = DulPermutations{N}((false, true))
    table = Vector{BinaryBasis{typeof(Unsigned(first(states)))}}(undef, length(com))
    for (i, poses) in enumerate(com)
        table[i] = BinaryBasis(states; filter=index->poses[index])
    end
    return BinaryBases([(BinaryBasis(states), NaN)], table)
end

"""
    BinaryBases(nstate::Integer, nparticle::Integer)
    BinaryBases(states, nparticle::Integer)

Construct a set of binary bases that preserves the particle number conservation.
"""
@inline BinaryBases(nstate::Integer, nparticle::Integer) = BinaryBases(1:nstate, Val(nparticle))
@inline BinaryBases(states, nparticle::Integer) = BinaryBases(states, Val(nparticle))
function BinaryBases(states, ::Val{N}) where N
    com = Combinations{N}(sort!(collect(states); rev=true))
    table = Vector{BinaryBasis{typeof(Unsigned(first(states)))}}(undef, length(com))
    for (i, poses) in enumerate(com)
        table[end+1-i] = BinaryBasis(poses)
    end
    return BinaryBases([(BinaryBasis(states), Float64(N))], table)
end

# CSC-formed sparse matrix representation of an operator
"""
    matrix(op::Operator, braket::NTuple{2, BinaryBases}, table; dtype=valtype(op)) -> SparseMatrixCSC{dtype, Int}

Get the CSC-formed sparse matrix representation of an operator.

Here, `table` specifies the order of the operator ids.
"""
function matrix(op::Operator, braket::NTuple{2, BinaryBases}, table; dtype=valtype(op))
    bra, ket = braket[1], braket[2]
    ndata, intermediate = 1, zeros(ket|>eltype, rank(op)+1)
    data, indices, indptr = zeros(dtype, dimension(ket)), zeros(Int, dimension(ket)), zeros(Int, dimension(ket)+1)
    sequence = NTuple{rank(op), Int}(table[op[i]] for i in reverse(1:rank(op)))
    iscreation = NTuple{rank(op), Bool}(oid.index.iid.nambu==creation for oid in reverse(id(op)))
    for i = 1:dimension(ket)
        flag = true
        indptr[i] = ndata
        intermediate[1] = ket[i]
        for j = 1:rank(op)
            isone(intermediate[j], sequence[j])==iscreation[j] && (flag = false; break)
            intermediate[j+1] = iscreation[j] ? one(intermediate[j], sequence[j]) : zero(intermediate[j], sequence[j])
        end
        if flag
            nsign = 0
            statistics(eltype(op))==:f && for j = 1:rank(op)
                nsign += count(intermediate[j], 1, sequence[j]-1)
            end
            indices[ndata] = findfirst(intermediate[end], bra)
            data[ndata] = op.value*(-1)^nsign
            ndata += 1
        end
    end
    indptr[end] = ndata
    return SparseMatrixCSC(dimension(bra), dimension(ket), indptr, indices[1:ndata-1], data[1:ndata-1])
end

# Generic exact diagonalization method
"""
    EDMatrix{S<:Sector, M<:SparseMatrixCSC} <: OperatorProd{M, Tuple{S, S}}

Matrix representation of quantum operators between a ket and a bra Hilbert space.
"""
struct EDMatrix{S<:Sector, M<:SparseMatrixCSC} <: OperatorProd{M, Tuple{S, S}}
    bra::S
    ket::S
    matrix::M
end
@inline parameternames(::Type{<:EDMatrix}) = (:sector, :value)
@inline getcontent(m::EDMatrix, ::Val{:id}) = (m.bra, m.ket)
@inline getcontent(m::EDMatrix, ::Val{:value}) = m.matrix
@inline dtype(::Type{<:EDMatrix{<:Sector, M}}) where {M<:SparseMatrixCSC} = eltype(M)
@inline EDMatrix(m::SparseMatrixCSC, braket::NTuple{2, Sector}) = EDMatrix(braket[1], braket[2], m)
@inline Base.promote_rule(M::Type{<:EDMatrix}, N::Type{<:Number}) = reparameter(M, :value, reparameter(valtype(M), 1, promote_type(dtype(M), N)))

"""
    EDMatrix(sector::Sector, m::SparseMatrixCSC)

Construct a matrix representation when the ket and bra spaces share the same bases.
"""
@inline EDMatrix(sector::Sector, m::SparseMatrixCSC) = EDMatrix(sector, sector, m)

"""
    EDMatrixRepresentation{S<:Sector, T} <: MatrixRepresentation

Exact matrix representation of a quantum lattice system on a target Hilbert space.
"""
struct EDMatrixRepresentation{S<:Sector, T} <: MatrixRepresentation
    brakets::Vector{NTuple{2, S}}
    table::T
end
@inline function Base.valtype(::Type{<:EDMatrixRepresentation{S}}, M::Type{<:Operator}) where {S<:Sector}
    M = EDMatrix{S, SparseMatrixCSC{valtype(M), Int}}
    return OperatorSum{M, idtype(M)}
end
@inline Base.valtype(R::Type{<:EDMatrixRepresentation}, M::Type{<:Operators}) = valtype(R, eltype(M))
function (representation::EDMatrixRepresentation)(m::Operator; kwargs...)
    result = zero(valtype(representation, m))
    for braket in representation.brakets
        add!(result, EDMatrix(matrix(m, braket, representation.table; kwargs...), braket))
    end
    return result
end

"""
    EDMatrixRepresentation(target::TargetSpace, table)

Construct a exact matrix representation.
"""
@inline EDMatrixRepresentation(target::TargetSpace, table) = EDMatrixRepresentation([(sector, sector) for sector in target], table)

"""
    SectorFilter{S} <: Transformation

Filter the target bra and ket Hilbert spaces.
"""
struct SectorFilter{S} <: Transformation
    brakets::Set{NTuple{2, S}}
end
@inline Base.valtype(::Type{<:SectorFilter}, M::Type{<:OperatorSum{<:EDMatrix}}) = M
@inline (sectorfileter::SectorFilter)(m::EDMatrix) = id(m)∈sectorfileter.brakets ? m : 0
@inline SectorFilter(sector::Sector, sectors::Sector...) = SectorFilter((sector, sector), map(op->(op, op), sectors)...)
@inline SectorFilter(braket::NTuple{2, Sector}, brakets::NTuple{2, Sector}...) = SectorFilter(push!(Set{typeof(braket)}(), braket, brakets...))

"""
    EDKind{K}

The kind of the exact diagonalization method applied to a quantum lattice system.
"""
struct EDKind{K} end
@inline EDKind(K::Symbol) = EDKind{K}()
@inline EDKind(::Type{T}) where {T<:Term} = error("EDKind error: not defined for $(kind(T)).")
@inline EDKind(::Type{<:FockTerm}) = EDKind(:FED)
@inline EDKind(::Type{<:SpinTerm}) = EDKind(:SED)
@inline @generated function EDKind(::Type{TS}) where {TS<:Tuple{Vararg{Term}}}
    exprs = []
    for i = 1:fieldcount(TS)
        push!(exprs, :(typeof(EDKind(fieldtype(TS, $i)))))
    end
    return Expr(:call, Expr(:call, :reduce, :promote_type, Expr(:tuple, exprs...)))
end

"""
    Metric(::EDKind{:FED}, hilbert::Hilbert{<:Fock}) -> OIDToTuple
    Metric(::EDKind{:SED}, hilbert::Hilbert{<:Spin}) -> OIDToTuple

Get the oid-to-tuple metric for a free fermionic/bosonic system or a free phononic system.
"""
@inline @generated Metric(::EDKind{:FED}, hilbert::Hilbert{<:Fock}) = OIDToTuple(:spin, fieldnames(keytype(hilbert))..., :orbital)
@inline @generated Metric(::EDKind{:SED}, hilbert::Hilbert{<:Spin}) = OIDToTuple(fieldnames(keytype(hilbert))..., :orbital)

"""
    ED{L<:AbstractLattice, G<:Generator, M<:Image} <: Engine

Exact diagonalization method of a quantum lattice system.
"""
struct ED{K, L<:AbstractLattice, G<:Generator, M<:Image} <: Engine
    lattice::L
    H::G
    Hₘ::M
    function ED{K}(lattice::AbstractLattice, H::Generator, mr::EDMatrixRepresentation) where K
        @assert isa(K, EDKind) "ED error: wrong kind."
        Hₘ = mr(H)
        new{K, typeof(lattice), typeof(H), typeof(Hₘ)}(lattice, H, Hₘ)
    end
end
@inline kind(ed::ED) = kind(typeof(ed))
@inline kind(::Type{<:ED{K}}) where K = K
@inline Base.valtype(::Type{<:ED{K, <:AbstractLattice, G} where K}) where {G<:Generator} = valtype(eltype(G))
@inline statistics(ed::ED) = statistics(typeof(ed))
@inline statistics(::Type{<:ED{K, <:AbstractLattice, G} where K}) where {G<:Generator} = statistics(eltype(eltype(G)))
@inline function update!(ed::ED; kwargs...)
    if length(kwargs)>0
        update!(ed.H; kwargs...)
        update!(ed.Hₘ, ed.H)
    end
    return ed
end
@inline Parameters(ed::ED) = Parameters(ed.H)

"""
    ED(lattice::AbstractLattice, hilbert::Hilbert, terms::Tuple{Vararg{Term}}, targetspace::TargetSpace; boundary::Boundary=plain)

Construct the exact diagonalization method for a quantum lattice system.
"""
function ED(lattice::AbstractLattice, hilbert::Hilbert, terms::Tuple{Vararg{Term}}, targetspace::TargetSpace; boundary::Boundary=plain)
    K = EDKind(typeof(terms))
    H = Generator(terms, Bonds(lattice), hilbert; half=false, boundary=boundary)
    mr = EDMatrixRepresentation(targetspace, Table(hilbert, Metric(K, hilbert)))
    return ED{K}(lattice, H, mr)
end

"""
    matrix(ed::ED, sector::Sector; kwargs...) -> EDMatrix
    matrix(ed::ED, sector=first(ed.Hₘ.transformation.brakets); kwargs...) -> EDMatrix

Get the sparse matrix representation of a quantum lattice system in a sector of the target space.
"""
@inline matrix(ed::ED, sector::Sector; kwargs...) = matrix(ed, (sector, sector); kwargs...)
@inline matrix(ed::ED, braket::NTuple{2, Sector}=first(ed.Hₘ.transformation.brakets); kwargs...) = expand(SectorFilter(braket)(ed.Hₘ))[braket]

"""
    eigen(m::EDMatrix; nev=6, which=:SR, tol=0.0, maxiter=300, sigma=nothing, v₀=dtype(m)[]) -> Eigen

Solve the eigen problem by the restarted Lanczos method provided by the Arpack package.
"""
@inline function eigen(m::EDMatrix; nev=6, which=:SR, tol=0.0, maxiter=300, sigma=nothing, v₀=dtype(m)[])
    eigensystem = eigs(m.matrix; nev=nev, which=which, tol=tol, maxiter=maxiter, sigma=sigma, ritzvec=true, v0=v₀)
    return Eigen(eigensystem[1], eigensystem[2])
end

end # module


# model initializations (normal or xaiver)

# xavier initialization
function initx(d...; ftype=Float32)
    if gpu() >=0
        KnetArray{ftype}(xavier(d...))
    else
        Array{ftype}(xavier(d...))
    end
end

# random normal initialization
function initr(d...; ftype=Float32, GPUFEATS=false)
    if GPUFEATS && gpu() >=0
        KnetArray{ftype}(0.1*randn(d...))
    else
        Array{ftype}(0.1*randn(d...))
    end
end

# zero initialization
function initzeros(d...; ftype=Float32, GPUFEATS=false)
    if GPUFEATS && gpu() >=0
        KnetArray{ftype}(zeros(d...))
    else
        Array{ftype}(zeros(d...))
    end
end

# initialize buffer lstm
# Possible bufemebed: fvec o bvec o wvec o upos (o xpos o feats)
function initbuf(bufembed, bufhidden; buftype=:lstm, numLayers=1)
    r, wr = rnninit(bufembed, bufhidden, rnnType=buftype, numLayers=numLayers)
end

# commonly used decision module
function mlp(w,x; pdrop=(0,0))
    x = dropout(x,pdrop[1])
    for i=1:2:length(w)-2
        x = relu.(w[i]*x .+ w[i+1])
        x = dropout(x,pdrop[2])
    end
    return w[end-1]*x .+ w[end]
end


module GeneralizedSDistributions

import Distributions: UnivariateDistribution,
                      ContinuousUnivariateDistribution,
                      DiscreteUnivariateDistribution
import Distributions: rand, sampler, logpdf, cdf, quantile, minimum, maximum, insupport,
                      mean, var, std, modes, mode, skewness, kurtosis, entropy, mgf, cf,
                      pdf, params

import LsqFit: curve_fit, coef
import HypergeometricFunctions: _₂F₁
import QuadGK: quadgk
import OrdinaryDiffEq: ODEProblem, solve, AutoTsit5, Rosenbrock23

export GSDist

include("GSDist.jl")
include("show.jl")
include("utils.jl")

end


using Caesar, RoME, IncrementalInference
using DelimitedFiles

# Visualization
using Cairo, Fontconfig
using Colors, Gadfly
using RoMEPlotting, KernelDensityEstimatePlotting

#Array for synthetic data
posData = zeros(10,2);
posData[:,1] = collect(-4.5:4.5);

testDir = joinpath(ENV["HOME"],"data","synthetic","10dB");
csvWaveData = zeros(8192,10);

# gps covariance
rtkCov = Matrix(Diagonal([0.1;0.1].^2))

## build the factor graph
N = 100
fg = initfg()
addVariable!(fg, :l1, Point2)

for i in 1:10
    sym = Symbol("x$i")
    addVariable!(fg, sym, Point2)
    setVal!(fg, sym, zeros(2,N))
    pp = PriorPoint2(MvNormal(posData[i,1:2], rtkCov) )
    addFactor!(fg, [sym;], pp)
end

dataFile = joinpath(testDir,"1.txt")
csvWaveData = readdlm(dataFile,',',Float64,'\n')
#
cfgFile = joinpath(ENV["HOME"],"data","sas","SAS2D.yaml");
cfgd=loadConfigFile(cfgFile)
chirpFile = joinpath(ENV["HOME"],"data","sas","chirp250.txt");
sas2d = prepareSAS2DFactor(10, csvWaveData, rangemodel=:Correlator, cfgd=cfgd, chirpFile=chirpFile)

#
addFactor!(fg, [:l1;:x1;:x2;:x3;:x4;:x5;:x6;:x7;:x8;:x9;:x10], sas2d, autoinit=false)
#, threadmodel=SingleThreaded )

writeGraphPdf(fg, engine="fdp")

getSolverParams(fg).drawtree = true
getSolverParams(fg).showtree = false

tree = solveTree!(fg)

# drawTree(tree, show=true)

# forward SAS case
# for ti in 1:Threads.nthreads() reset!(sas2d.threadreuse[ti].dbg) end

@time predL1 = IncrementalInference.approxConv(fg, ls(fg, :l1)[1], :l1, N=N)
# setVal!(fg, :l1, zeros(2,N))

predL1 = getVal(fg,:l1)
pllim = 800
X1 = getVal(fg, :x1)
pl = Gadfly.plot(
  Gadfly.layer(x=predL1[1,:][-pllim .< predL1[1,:] .< pllim],y=predL1[2,:][-pllim .< predL1[2,:] .< pllim],   Gadfly.Geom.hexbin(xbincount=200, ybincount=200)),
  Gadfly.layer(x=posData[1,:],y=posData[2,:], Gadfly.Geom.point)
); # for skipping Juno display

pl |> PDF("/tmp/test.pdf"); @async run(`evince /tmp/test.pdf`)
pl |> SVG("/tmp/test.svg", 35cm, 25cm); @async run(`firefox /tmp/test.svg`)

## backwards case

# backwards leave one out case
setVal!(fg, :l1, 1.0*randn(2,N))
setVal!(fg, :x2, rand(MvNormal(posData[2,1:2], 1.0*Matrix{Float64}(Diagonal([1.0;1.0]))),N))
# for ti in 1:Threads.nthreads() reset!(sas2d.threadreuse[ti].dbg) end

@time predX2 = IncrementalInference.approxConv(fg, ls(fg, :l1)[1], :x2, N=N)

pl = Gadfly.plot(
layer(x=predX2[1,:], y=predX2[2,:], Geom.histogram2d(xbincount=500,ybincount=500)),
layer(x=posData[1:5,1],y=posData[1:5,2], Geom.point)
)

pl.coord = Coord.Cartesian(xmin=0,xmax=20,ymin=-50,ymax=-35)
pl |> SVG("/tmp/test.svg", 35cm, 25cm)

pl_wait = plotKDE(kde!(predX2))
pl.layers = [pl.layers; pl_wait.layers]

pl |> SVG("/tmp/test.svg", 35cm, 25cm)

0


#
# Describes trajectories for all `AbstractOrbit`s.
#

"""
An alias for a `Vector` of `AbstractOrbits`.
"""
const Trajectory{T} = Vector{T} where T <: AbstractOrbit

Base.show(io::IO, traj::Trajectory{<:CartesianOrbit}) = println(io, "Cartesian Two-body trajectory with ", length(traj), " steps")
Base.show(io::IO, traj::Trajectory{<:KeplerianOrbit}) = println(io, "Keplerian Two-body trajectory with ", length(traj), " steps")
Base.show(io::IO, ::MIME"text/plain", traj::Trajectory) = show(io,traj)


module Evolutionary

using Random

export Strategy, strategy, inverse, mutationwrapper,
# ES mutations
isotropic, anisotropic, isotropicSigma, anisotropicSigma,
# GA mutations
flip, domainrange, inversion, insertion, swap2, scramble, shifting,
# ES recombinations
average, marriage, averageSigma1, averageSigmaN,
# GA recombinations
singlepoint, twopoint, uniform,
discrete, waverage, intermediate, line,
pmx, ox1, cx, ox2, pos,
# GA selections
ranklinear, uniformranking, roulette, sus, tournament, #truncation
# Optimization methods
es, cmaes, ga

const Strategy = Dict{Symbol,Any}
const Individual = Union{Vector, Matrix, Function, Nothing}

# Wrapping function for strategy
function strategy(; kwargs...)
    result = Dict{Symbol,Any}()
    for (k, v) in kwargs
        result[k] = v
    end
    return result
end

# Inverse function for reversing optimization direction
function inverseFunc(f::Function)
    function fitnessFunc(x::T) where {T <: Vector}
        return 1.0/(f(x)+eps())
    end
    return fitnessFunc
end

# Obtain individual
function getIndividual(init::Individual, N::Int)
    if isa(init, Vector)
        @assert length(init) == N "Dimensionality of initial population must be $(N)"
        individual = init
    elseif isa(init, Matrix)
        @assert size(init, 1) == N "Dimensionality of initial population must be $(N)"
        populationSize = size(init, 2)
        individual = init[:, 1]
    elseif isa(init, Function) # Creation function
        individual = init(N)
    else
        individual = ones(N)
    end
    return  individual
end

# Collecting interim values
function keep(interim, v, vv, col)
    if interim
        if !haskey(col, v)
            col[v] = typeof(vv)[]
        end
        push!(col[v], vv)
    end
end

# Print a bar
function bar(n,total)
    percent = n/total*100.
    size = percent/2
    text = ""
    print("\r Progress:|")
    for a = 0:size
        text *="█"
    end
    printstyled(text, color=:red)
    text = ""
    for a = 1:(50.0 - size)
        text *="█"
    end
    printstyled(text, color=:white)
    print("| $(round(percent))%")
end

# ES & GA recombination functions
include("recombinations.jl")

# ES & GA mutation functions
include("mutations.jl")

# GA selection functions
include("selections.jl")

# Evolution Strategy
include("es.jl")
include("cmaes.jl")

# Genetic Algorithms
include("ga.jl")

end


immutable SimulationPeriod <: Period
  value :: Float64
  function SimulationPeriod(value::Number=0)
    new(value)
  end
end

immutable SimulationInstant <: Dates.Instant
  periods :: SimulationPeriod
end

immutable SimulationTime <: TimeType
  instant :: SimulationInstant
end

function SimulationTime(value::Number=0)
  SimulationTime(SimulationInstant(SimulationPeriod(value)))
end

(==)(x::SimulationTime, y::SimulationTime) = x.instant.periods.value == y.instant.periods.value

function isless(t1::SimulationTime, t2::SimulationTime) :: Bool
  t1.instant.periods.value < t2.instant.periods.value
end

(+)(t::SimulationTime, p::SimulationPeriod)=SimulationTime(t.instant.periods.value + p.value)

function show(io::IO, t::SimulationTime)
  print(io, "$(t.instant.periods.value)")
end


export
    TrajdataEditMode,

    remove_offroad_vehicles!,
    remove_unaligned_vehicles!,
    interpolate_to_timestep!

type TrajdataEditMode
    roadway::Roadway
    scenes::Vector{Scene}
    time::Vector{Float64}
end
function Base.convert(::Type{TrajdataEditMode}, trajdata::Trajdata)
    roadway = trajdata.roadway
    scenes = Scene[get!(Scene(carsinframe(trajdata, frame)), trajdata, frame) for frame in 1 : nframes(trajdata)]
    time = Float64[frame.t for frame in trajdata.frames]
    TrajdataEditMode(roadway, scenes, time)
end
function Base.convert(::Type{Trajdata}, tdem::TrajdataEditMode)
    roadway = tdem.roadway
    vehicles = Dict{Int, VehicleDef}()
    states = Array{TrajdataState}(sum(s->length(s), tdem.scenes))
    frames = Array{TrajdataFrame}(length(tdem.scenes))

    states_index = 0
    for (frame_index, scene) in enumerate(tdem.scenes)
        lo = states_index + 1

        for veh in scene
            if !haskey(vehicles, veh.def.id)
                vehicles[veh.def.id] = veh.def
            else
                @assert(vehicles[veh.def.id] == veh.def)
            end
            states[states_index+=1] = TrajdataState(veh.def.id, veh.state)
        end
        frames[frame_index] = TrajdataFrame(lo, states_index, tdem.time[frame_index])
    end

    Trajdata(roadway, vehicles, states, frames)
end

function remove_offroad_vehicles!(tdem::TrajdataEditMode, threshold_lane_lateral_offset::Float64=2.5)
    for scene in tdem.scenes
        i = 0
        while i < length(scene)
            i += 1
            veh = scene[i]
            if abs(veh.state.posF.t) > threshold_lane_lateral_offset
                deleteat!(scene, i)
                i -= 1
            end
        end
    end
    tdem
end
function remove_unaligned_vehicles!(tdem::TrajdataEditMode, threshold_lane_angle::Float64=deg2rad(45))
    for scene in tdem.scenes
        i = 0
        while i < length(scene)
            i += 1
            veh = scene[i]
            if abs(veh.state.posF.ϕ) > threshold_lane_angle
                deleteat!(scene, i)
                i -= 1
            end
        end
    end
    tdem
end

function interpolate_to_timestep!(tdem::TrajdataEditMode, timestep::Float64)
    time = collect(tdem.time[1]:timestep:tdem.time[end])
    scenes = Array{Scene}(length(time))
    scenes[1] = tdem.scenes[1]

    frame_old = 1
    for i in 2 : length(time)
        t = time[i]

        # move frame_old to one past the time, if it exists
        while tdem.time[frame_old] < t
            frame_old += 1
        end

        # interpolation value ∈ [0,1]
        γ = (t - tdem.time[frame_old-1]) / (tdem.time[frame_old] - tdem.time[frame_old-1])

        scene_lo = tdem.scenes[frame_old-1]
        scene_hi = tdem.scenes[frame_old]

        # interpolate all vehicles that are in both scenes
        # if a vehicle is not in both scenes, do not inlude it
        scenes[i] = Scene(min(length(scene_lo), length(scene_hi)))
        for veh_lo in scene_lo
            id = veh_lo.def.id
            veh_index = get_index_of_first_vehicle_with_id(scene_hi, id)
            if veh_index != 0
                veh_hi = scene_hi[veh_index]
                @assert(veh_lo.def == veh_hi.def)
                veh_interp = Vehicle(lerp(veh_lo.state, veh_hi.state, γ, tdem.roadway), veh_lo.def)
                push!(scenes[i], veh_interp)
            end
        end
    end

    tdem.time = time
    tdem.scenes = scenes
    tdem
end

# smooth trajectories

# remove ghost cars


# # `System` library management with `PowerSystemCaseBuilder.jl`

# **Originally Contributed by**: Clayton Barrows

# ## Introduction

# [PowerSystemCaseBuilder.jl](github.com/NREL-SIIP/PowerSystemCaseBuilder.jl) provides a utility to manage a library of `System`s.
# The package has utilities to list the available system data and to create instances of each system. By keeping track of which systems
# have been constructed locally, it makes the re-instantiation of systems efficient by utilizing the serialization features and avoiding
# the parsing process for systems that have been previously constructed.

# ### Dependencies
using PowerSystemCaseBuilder

# ### List all systems in library
show_systems()

# ### Systems can be listed by category
# the available categories can be displayed with:
show_categories()

# the available systems can be displayed with:
show_systems(SIIPExampleSystems)

# ### Create a `System`
# *The first time this is run, it will parse csv data. Subsequent executions will rely on serialized data and will execute much faster*
sys = build_system(SIIPExampleSystems, "5_bus_hydro_ed_sys")


# /!\ LazyBridgeOptimizer is still experimental and is meant to be used by JuMP. Use the SingleBridgeOptimizer in the solver tests as explained in the MOI documentation.
"""
    LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer

The `LazyBridgeOptimizer` combines several bridges, which are added using the [`add_bridge`](@ref) function.
Whenever a constraint is added, it only attempts to bridge it if it is not supported by the internal model (hence its name `Lazy`).
When bridging a constraint, it selects the minimal number of bridges needed.
For instance, a constraint `F`-in-`S` can be bridged into a constraint `F1`-in-`S1` (supported by the internal model) using bridge 1 or
bridged into a constraint `F2`-in-`S2` (unsupported by the internal model) using bridge 2 which can then be
bridged into a constraint `F3`-in-`S3` (supported by the internal model) using bridge 3,
it will choose bridge 1 as it allows to bridge `F`-in-`S` using only one bridge instead of two if it uses bridge 2 and 3.
"""
mutable struct LazyBridgeOptimizer{OT<:MOI.ModelLike} <: AbstractBridgeOptimizer
    model::OT   # Internal model
    con_to_name::Dict{CI, String}
    name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing}
    # Constraint Index of bridged constraint -> Bridge.
    # It is set to `nothing` when the constraint is deleted.
    bridges::Vector{Union{Nothing, AbstractBridge}}
    # Constraint Index of bridged constraint -> Constraint type.
    constraint_types::Vector{Tuple{DataType, DataType}}
    # For `SingleVariable` constraints: (variable, set type) -> bridge
    single_variable_constraints::Dict{Tuple{Int64, DataType}, AbstractBridge}
    bridgetypes::Vector{Any} # List of types of available bridges
    dist::Dict{Tuple{DataType, DataType}, Int}      # (F, S) -> Number of bridges that need to be used for an `F`-in-`S` constraint
    best::Dict{Tuple{DataType, DataType}, DataType} # (F, S) -> Bridge to be used for an `F`-in-`S` constraint
end
function LazyBridgeOptimizer(model::MOI.ModelLike)
    return LazyBridgeOptimizer{typeof(model)}(
        model, Dict{CI, String}(), nothing,
        Union{Nothing, AbstractBridge}[], Tuple{DataType, DataType}[],
        Dict{Tuple{Int64, DataType}, AbstractBridge}(),
        Any[], Dict{Tuple{DataType, DataType}, Int}(),
        Dict{Tuple{DataType, DataType}, DataType}())
end

function _dist(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})
    if MOI.supports_constraint(b.model, F, S)
        return 0
    else
        return get(b.dist, (F, S), typemax(Int))
    end
end

# Update `b.dist` and `b.dest` for constraint types in `constraints`
function update_dist!(b::LazyBridgeOptimizer, constraints)
    # Bellman-Ford algorithm
    changed = true # Has b.dist changed in the last iteration ?
    while changed
        changed = false
        for BT in b.bridgetypes
            for (F, S) in constraints
                if MOI.supports_constraint(BT, F, S) && all(C -> supports_constraint_no_update(b, C[1], C[2]), added_constraint_types(BT, F, S))
                    # Number of bridges needed using BT
                    dist = 1 + mapreduce(
                        C -> _dist(b, C[1], C[2]), +,
                        added_constraint_types(BT, F, S), init = 0)
                    # Is it better that what can currently be done ?
                    if dist < _dist(b, F, S)
                        b.dist[(F, S)] = dist
                        b.best[(F, S)] = concrete_bridge_type(BT, F, S)
                        changed = true
                    end
                end
            end
        end
    end
end

function fill_required_constraints!(required::Set{Tuple{DataType, DataType}}, b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})
    if supports_constraint_no_update(b, F, S)
        return # The constraint is supported
    end
    if (F, S) in required
        return # The requirements for this constraint have already been added or are being added
    end
    # The constraint is not supported yet, add in `required` the required constraint types to bridge it
    push!(required, (F, S))
    for BT in b.bridgetypes
        if MOI.supports_constraint(BT, F, S)
            for C in added_constraint_types(BT, F, S)
                fill_required_constraints!(required, b, C[1], C[2])
            end
        end
    end
end

# Compute dist[(F, S)] and best[(F, S)]
function update_constraint!(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})
    required = Set{Tuple{DataType, DataType}}()
    fill_required_constraints!(required, b, F, S)
    update_dist!(b, required)
end

"""
    add_bridge(b::LazyBridgeOptimizer, BT::Type{<:AbstractBridge})

Enable the use of the bridges of type `BT` by `b`.
"""
function add_bridge(b::LazyBridgeOptimizer, BT::Type{<:AbstractBridge})
    push!(b.bridgetypes, BT)
    # Some constraints (F, S) in keys(b.best) may now be bridged
    # with a less briges than `b.dist[(F, S)] using `BT`
    update_dist!(b, keys(b.best))
end

# It only bridges when the constraint is not supporting, hence the name "Lazy"
function is_bridged(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})
    !MOI.supports_constraint(b.model, F, S)
end
# Same as supports_constraint but do not trigger `update_constraint!`. This is
# used inside `update_constraint!`.
function supports_constraint_no_update(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})
    return MOI.supports_constraint(b.model, F, S) || (F, S) in keys(b.best)
end
function supports_bridging_constraint(b::LazyBridgeOptimizer, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet})
    update_constraint!(b, F, S)
    return (F, S) in keys(b.best)
end
function bridge_type(b::LazyBridgeOptimizer{BT}, F::Type{<:MOI.AbstractFunction}, S::Type{<:MOI.AbstractSet}) where BT
    update_constraint!(b, F, S)
    result = get(b.best, (F, S), nothing)
    if result === nothing
        throw(MOI.UnsupportedConstraint{F, S}())
    end
    return result
end


function GeometricMean()
    return GeometricMean(())
end

function GeometricMean(arg0::GeometricMean)
    return GeometricMean((GeometricMean,), arg0)
end

function GeometricMean(arg0::SumOfLogs)
    return GeometricMean((SumOfLogs,), arg0)
end

function aggregate(obj::GeometricMean, arg0::GeometricMean)
    return jcall(obj, "aggregate", void, (GeometricMean,), arg0)
end

function clear(obj::GeometricMean)
    return jcall(obj, "clear", void, ())
end

function copy(obj::GeometricMean)
    return jcall(obj, "copy", GeometricMean, ())
end

function evaluate(obj::GeometricMean, arg0::Vector{jdouble}, arg1::jint, arg2::jint)
    return jcall(obj, "evaluate", jdouble, (Vector{jdouble}, jint, jint), arg0, arg1, arg2)
end

function get_n(obj::GeometricMean)
    return jcall(obj, "getN", jlong, ())
end

function get_result(obj::GeometricMean)
    return jcall(obj, "getResult", jdouble, ())
end

function increment(obj::GeometricMean, arg0::jdouble)
    return jcall(obj, "increment", void, (jdouble,), arg0)
end


ztype(op::libgb.GrB_UnaryOp) = tojuliatype(ptrtogbtype[libgb.GxB_UnaryOp_ztype(op)])
xtype(op::libgb.GrB_UnaryOp) = tojuliatype(ptrtogbtype[libgb.GxB_UnaryOp_xtype(op)])
Base.show(io::IO, ::MIME"text/plain", u::libgb.GrB_UnaryOp) = gxbprint(io, u)

xtype(op::libgb.GrB_BinaryOp) = tojuliatype(ptrtogbtype[libgb.GxB_BinaryOp_xtype(op)])
ytype(op::libgb.GrB_BinaryOp) = tojuliatype(ptrtogbtype[libgb.GxB_BinaryOp_ytype(op)])
ztype(op::libgb.GrB_BinaryOp) = tojuliatype(ptrtogbtype[libgb.GxB_BinaryOp_ztype(op)])
Base.show(io::IO, ::MIME"text/plain", u::libgb.GrB_BinaryOp) = gxbprint(io, u)

operator(monoid::libgb.GrB_Monoid) = libgb.GxB_Monoid_operator(monoid)
xtype(monoid::libgb.GrB_Monoid) = xtype(operator(monoid))
ytype(monoid::libgb.GrB_Monoid) = ytype(operator(monoid))
ztype(monoid::libgb.GrB_Monoid) = ztype(operator(monoid))
Base.show(io::IO, ::MIME"text/plain", m::libgb.GrB_Monoid) = gxbprint(io, m)

multiplyop(rig::libgb.GrB_Semiring) = libgb.GxB_Semiring_multiply(rig)
addop(rig::libgb.GrB_Semiring) = libgb.GxB_Semiring_add(rig)
xtype(rig::libgb.GrB_Semiring) = xtype(multiplyop(rig))
ytype(rig::libgb.GrB_Semiring) = ytype(multiplyop(rig))
ztype(rig::libgb.GrB_Semiring) = ztype(addop(rig))
Base.show(io::IO, ::MIME"text/plain", s::libgb.GrB_Semiring) = gxbprint(io, s)


function GeoMagneticElements(arg0::Vector3D)
    return GeoMagneticElements((Vector3D,), arg0)
end

function get_declination(obj::GeoMagneticElements)
    return jcall(obj, "getDeclination", jdouble, ())
end

function get_field_vector(obj::GeoMagneticElements)
    return jcall(obj, "getFieldVector", Vector3D, ())
end

function get_horizontal_intensity(obj::GeoMagneticElements)
    return jcall(obj, "getHorizontalIntensity", jdouble, ())
end

function get_inclination(obj::GeoMagneticElements)
    return jcall(obj, "getInclination", jdouble, ())
end

function get_total_intensity(obj::GeoMagneticElements)
    return jcall(obj, "getTotalIntensity", jdouble, ())
end

function to_string(obj::GeoMagneticElements)
    return jcall(obj, "toString", JString, ())
end


abstract type RandomGalaxy{G <: AbstractFloat, T <: Integer} end

type SphericalGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    r::Array{G, 1}
    vr::Array{G, 1}
    vt::Array{G, 1}
    nobs::T
    #SphericalGalaxy(r, vr, vt, nobs) = (length(r) == nobs && length(vr) == nobs && length(vt) == nobs) ? error("length of r, vr, and vt must equal nobs") : new(r, vr, vt, nobs)
end

SphericalGalaxy(sg1::SphericalGalaxy{G, T}, sg2::SphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = SphericalGalaxy([sg1.r; sg2.r], [sg1.vr; sg2.vr], [sg1.vt; sg2.vt], sg1.nobs + sg2.nobs)
SphericalGalaxy(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}) where G <: AbstractFloat = SphericalGalaxy(r, vr, vt, length(r))

type MetallicSphericalGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    r::Array{G, 1}
    vr::Array{G, 1}
    vt::Array{G, 1}
    m::Array{G, 1}
    nobs::T
    #SphericalGalaxy(r, vr, vt, nobs) = (length(r) == nobs && length(vr) == nobs && length(vt) == nobs) ? error("length of r, vr, and vt must equal nobs") : new(r, vr, vt, nobs)
end

MetallicSphericalGalaxy(msg1::MetallicSphericalGalaxy{G, T}, msg2::MetallicSphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = MetallicSphericalGalaxy([msg1.r; msg2.r], [msg1.vr; msg2.vr], [msg1.vt; msg2.vt], [msg1.m; msg2.m], msg1.nobs + msg2.nobs)
MetallicSphericalGalaxy(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}, m::Array{G, 1}) where G <: AbstractFloat = MetallicSphericalGalaxy(r, vr, vt, m, length(r))
MetallicSphericalGalaxy(sg::SphericalGalaxy{G, T}, m::Array{G,1}) where {G <: AbstractFloat, T <: Integer} = MetallicSphericalGalaxy(sg.r, sg.vr, sg.vt, m, sg.nobs)

type PlaneGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    r::Array{G, 1}
    v::Array{G, 1}
    nobs::T
end

PlaneGalaxy(dg1::PlaneGalaxy{G, T}, dg2::PlaneGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = PlaneGalaxy([dg1.r; dg2.r], [dg1.v; dg2.v], dg1.nobs + dg2.nobs)
PlaneGalaxy(r::Array{G, 1}, v::Array{G, 1}) where G <: AbstractFloat = PlaneGalaxy(r, v, length(r))

type MetallicPlaneGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    r::Array{G, 1}
    v::Array{G, 1}
    m::Array{G, 1}
    nobs::T
end

MetallicPlaneGalaxy(mdg1::PlaneGalaxy{G, T}, mdg2::PlaneGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = MetallicPlaneGalaxy([mdg1.r; mdg2.r], [mdg1.v; mdg2.v], [mdg1.m; mdg2.m], dg1.nobs + dg2.nobs)
MetallicPlaneGalaxy(r::Array{G, 1}, v::Array{G, 1}, m::Array{G, 1}) where G <: AbstractFloat = PlaneGalaxy(r, v, m, length(r))
MetallicPlaneGalaxy(mdg::PlaneGalaxy{G, T}, m::Array{G,1}) where {G <: AbstractFloat, T <: Integer} = MetallicPlaneGalaxy(mdg.r, mdg.v, m, mdg.nobs)

type EuclideanGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    x::Array{G, 1}
    y::Array{G, 1}
    z::Array{G, 1}
    vx::Array{G, 1}
    vy::Array{G, 1}
    vz::Array{G, 1}
    nobs::T
end

EuclideanGalaxy(eg1::EuclideanGalaxy{G, T}, eg2::EuclideanGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = Euclideanalaxy([eg1.x; eg2.x], [eg1.y; eg2.y], [eg1.z; eg2.z], [eg1.vx; eg2.vx], [eg1.vy; eg2.vy], [eg1.vz; eg2.vz], eg1.nobs + eg2.nobs)
EuclideanGalaxy(x::Array{G, 1}, y::Array{G, 1}, z::Array{G, 1}, vx::Array{G, 1}, vy::Array{G, 1}, vz::Array{G, 1}) where G <: AbstractFloat = EuclideanGalaxy(x, y, z, vx, vy, vz, length(x))

type MetallicEuclideanGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    x::Array{G, 1}
    y::Array{G, 1}
    z::Array{G, 1}
    vx::Array{G, 1}
    vy::Array{G, 1}
    vz::Array{G, 1}
    m::Array{G, 1}
    nobs::T
end

MetallicEuclideanGalaxy(meg1::MetallicEuclideanGalaxy{G, T}, meg2::MetallicEuclideanGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = MetallicEuclideanGalaxy([meg1.x; meg2.x], [meg1.y; meg2.y],[meg1.z; meg2.z], [meg1.vx; meg2.vx], [meg1.vy; meg2.vy], [meg1.vz; meg2.vz], [meg1.m; meg2.m], meg1.nobs + meg2.nobs)
MetallicEuclideanGalaxy(x::Array{G, 1}, y::Array{G, 1}, z::Array{G, 1}, vx::Array{G, 1}, vy::Array{G, 1}, vz::Array{G, 1}, m::Array{G, 1}) where G <: AbstractFloat= MetallicEuclideanGalaxy(x, y, z, vx, vy, vz, length(x))
MetallicEuclideanGalaxy{G <: AbstractFloat, T <: Integer}(eg::EuclideanGalaxy{G, T}, m::Array{G, 1}) = MetallicEuclideanGalaxy(eg.x, eg.y, eg.z, eg.vx, eg.vy, eg.vz, m, eg.nobs)

type PartialEuclideanGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    x::Array{G, 1}
    y::Array{G, 1}
    vz::Array{G, 1}
    nobs::T
end

PartialEuclideanGalaxy(peg1::PartialEuclideanGalaxy{G, T}, peg2::PartialEuclideanGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = PartialEuclideanGalaxy([peg1.x; peg2.x], [peg1.y; peg2.y], [peg1.vz; peg2.vz], peg1.nobs + peg2.nobs)
PartialEuclideanGalaxy(x::Array{G, 1}, y::Array{G, 1}, vz::Array{G, 1}) where G <: AbstractFloat = PartialEuclideanGalaxy(x, y, vz, length(x))
PartialEuclideanGalaxy(eg::EuclideanGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = PartialEuclideanGalaxy(eg.x, eg.y, eg.vz, eg.nobs)

type MetallicPartialEuclideanGalaxy{G <: AbstractFloat, T <: Integer} <: RandomGalaxy{G, T}
    x::Array{G, 1}
    y::Array{G, 1}
    vz::Array{G, 1}
    m::Array{G, 1}
    nobs::T
end

MetallicPartialEuclideanGalaxy(mpeg1::MetallicPartialEuclideanGalaxy{G, T}, mpeg2::MetallicPartialEuclideanGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer} = MetallicPartialEuclideanGalaxy([mpeg1.x; mpeg2.x], [mpeg1.y; mpeg2.y], [mpeg1.vz; mpeg2.vz],  [mpeg1.m; mpeg2.m], mpeg1.nobs + mpeg2.nobs)
MetallicPartialEuclideanGalaxy(x::Array{G, 1}, y::Array{G, 1}, vz::Array{G, 1}, m::Array{G, 1}) where G <: AbstractFloat = MetallicPartialEuclideanGalaxy(x, y, vz, m, length(x))
MetallicPartialEuclideanGalaxy(peg::PartialEuclideanGalaxy{G, T}, m::Array{G, 1}) where {G <: AbstractFloat, T <: Integer} = MetallicPartialEuclideanGalaxy(peg.x, peg.y, peg.vz, m, peg.nobs)
MetallicPartialEuclideanGalaxy(eg::EuclideanGalaxy{G, T}, m::Array{G, 1}) where {G <: AbstractFloat, T <: Integer} = PartialEuclideanGalaxy(eg.x, eg.y, eg.vz, m, eg.nobs)

"""
Generate a random PlaneGalaxy star from  a SphericalGalaxy star
"""
function sample_plane(r::G, vr::G, vt::G) where G <: AbstractFloat
    u = rand()
    s = 2.0 * sqrt(u - u * u)

    velPhi = 2pi * rand()
    velSign = sign(rand() - 0.5)

    return r * s, -s * cos(velPhi) * vt + (1.0 - 2.0 * u) * velSign * vr
end

function sample_plane(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}, nobs::T) where {G <: AbstractFloat, T <: Integer}
    rp = Array{G}(nobs)
    vp = Array{G}(nobs)
    for (ii, (ri, vri, vti)) in enumerate(zip(r, vr, vt))
        rp[ii], vp[ii] = sample_plane(ri, vri, vti)
    end
    return rp, vp
end

function sample_plane(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}) where G <: AbstractFloat
    return sample_plane(r, vr, vt, length(r))
end

function sample_plane(sg::SphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer}
    return PlaneGalaxy(sample_plane(sg.r, sg.vr, sg.vt, sg.nobs)..., sg.nobs)
end

function sample_plane(msg::MetallicSphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer}
    return MetallicPlaneGalaxy(sample_plane(msg.r, msg.vr, msg.vt, msg.nobs)..., msg.m, msg.nobs)
end

"""
Generate a random EuclideanGalaxy star from  a SphericalGalaxy star
"""
function sample_euclidean(r::G, vr::G, vt::G) where G <: AbstractFloat
    theta = acos(1.0 - 2.0 * rand())
    phi = 2.0 * pi * rand()

    #velocity = sqrt(vr^2 + vt^2)
    velSign = sign(rand() - 0.5)
    velPhi = 2.0 * pi * rand()

    x = r * sin(theta) * cos(phi)
    y = r * sin(theta) * sin(phi)
    z = r * cos(theta)

    vz2 = velSign * vr
    vx2 = vt * cos(velPhi)
    vy2 = vt * sin(velPhi)

    vx = cos(theta) * cos(phi) * vx2 - sin(phi) * vy2 + sin(theta) * cos(phi) * vz2
    vy = cos(theta) * sin(phi) * vx2 + cos(phi) * vy2 + sin(theta) * sin(phi) * vz2
    vz = -sin(theta) * vx2 + cos(theta) * vz2
    return x, y, z, vx, vy, vz
end

function sample_euclidean(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}, nobs::T) where {G <: AbstractFloat, T <: Integer}
    x = Array{G}(nobs)
    y = Array{G}(nobs)
    z = Array{G}(nobs)
    vx = Array{G}(nobs)
    vy = Array{G}(nobs)
    vz = Array{G}(nobs)
    for (ii, (ri, vri, vti)) in enumerate(zip(r, vr, vt))
        x[ii], y[ii], z[ii], vx[ii], vy[ii], vz[ii] = sample_euclidean(ri, vri, vti)
    end
    return x, y, z, vx, vy, vz
end

function sample_euclidean(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}) where G <: AbstractFloat
    return sample_euclidean(r, vr, vt, length(r))
end

"""
Generate a EuclideanGalaxy from a SphericalGalaxy
"""
function sample_euclidean(sg::SphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer}
    return EuclideanGalaxy(sample_euclidean(sg.r, sg.vr, sg.vt, sg.nobs)..., sg.nobs)
end

function sample_euclidean(msg::MetallicSphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer}
    return MetallicEuclideanGalaxy(sample_euclidean(msg.r, msg.vr, msg.vt, msg.nobs)..., msg.m, msg.nobs)
end

"""
Generate a random PartialEuclideanGalaxy star from  a SphericalGalaxy star
"""
function sample_partial_euclidean(r::G, vr::G, vt::G) where G <: AbstractFloat
    theta = acos(1.0 - 2.0 * rand())
    phi = 2pi * rand()

    velocity = sqrt(vr^2 + vt^2)
    velSign = sign(rand() - 0.5)
    velPhi = 2pi * rand()

    x = r * sin(theta) * cos(phi)
    y = r * sin(theta) * sin(phi)

    vz2 = velSign * vr
    vx2 = vt * cos(velPhi)

    vz = -sin(theta) * vx2 + cos(theta) * vz2
    return x, y, vz
end

function sample_partial_euclidean(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}, nobs::T) where {G <: AbstractFloat, T <: Integer}
    x = Array{G}(nobs)
    y = Array{G}(nobs)
    vz = Array{G}(nobs)
    for (ii, (ri, vri, vti)) in enumerate(zip(r, vr, vt))
        x[ii], y[ii], vz[ii] = sample_partial_euclidean(ri, vri, vti)
    end
    return x, y, vz
end

function sample_partial_euclidean(r::Array{G, 1}, vr::Array{G, 1}, vt::Array{G, 1}) where G <: AbstractFloat
    return sample_euclidean(r, vr, vt, length(r))
end

"""
Generate a PartialEuclideanGalaxy from a SphericalGalaxy
"""
function sample_partial_euclidean(sg::SphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer}
    return PartialEuclideanGalaxy(sample_partial_euclidean(sg.r, sg.vr, sg.vt, sg.nobs)..., sg.nobs)
end

function sample_partial_euclidean(msg::MetallicSphericalGalaxy{G, T}) where {G <: AbstractFloat, T <: Integer}
    return MetallicPartialEuclideanGalaxy(sample_partial_euclidean(msg.r, msg.vr, msg.vt, msg.nobs)..., msg.m, msg.nobs)
end