## type aliases ##

if Int === Int32
typealias SmallSigned Union(Int8,Int16)
typealias SmallUnsigned Union(Uint8,Uint16)
else
typealias SmallSigned Union(Int8,Int16,Int32)
typealias SmallUnsigned Union(Uint8,Uint16,Uint32)
end

## integer arithmetic ##

-(x::SmallSigned) = -int(x)
-(x::SmallUnsigned) = -uint(x)

+{T<:SmallSigned}(x::T, y::T) = int(x) + int(y)
-{T<:SmallSigned}(x::T, y::T) = int(x) - int(y)
*{T<:SmallSigned}(x::T, y::T) = int(x) * int(y)

+{T<:SmallUnsigned}(x::T, y::T) = uint(x) + uint(y)
-{T<:SmallUnsigned}(x::T, y::T) = uint(x) - uint(y)
*{T<:SmallUnsigned}(x::T, y::T) = uint(x) * uint(y)

-(x::Int)     = box(Int,neg_int(unbox(Int,x)))
-(x::Uint)    = box(Uint,neg_int(unbox(Uint,x)))
-(x::Int64)   = box(Int64,neg_int(unbox(Int64,x)))
-(x::Uint64)  = box(Uint64,neg_int(unbox(Uint64,x)))
-(x::Int128)  = box(Int128,neg_int(unbox(Int128,x)))
-(x::Uint128) = box(Uint128,neg_int(unbox(Uint128,x)))

+(x::Int,     y::Int)     = box(Int,add_int(unbox(Int,x),unbox(Int,y)))
+(x::Uint,    y::Uint)    = box(Uint,add_int(unbox(Uint,x),unbox(Uint,y)))
+(x::Int64,   y::Int64)   = box(Int64,add_int(unbox(Int64,x),unbox(Int64,y)))
+(x::Uint64,  y::Uint64)  = box(Uint64,add_int(unbox(Uint64,x),unbox(Uint64,y)))
+(x::Int128,  y::Int128)  = box(Int128,add_int(unbox(Int128,x),unbox(Int128,y)))
+(x::Uint128, y::Uint128) = box(Uint128,add_int(unbox(Uint128,x),unbox(Uint128,y)))

-(x::Int,     y::Int)     = box(Int,sub_int(unbox(Int,x),unbox(Int,y)))
-(x::Uint,    y::Uint)    = box(Uint,sub_int(unbox(Uint,x),unbox(Uint,y)))
-(x::Int64,   y::Int64)   = box(Int64,sub_int(unbox(Int64,x),unbox(Int64,y)))
-(x::Uint64,  y::Uint64)  = box(Uint64,sub_int(unbox(Uint64,x),unbox(Uint64,y)))
-(x::Int128,  y::Int128)  = box(Int128,sub_int(unbox(Int128,x),unbox(Int128,y)))
-(x::Uint128, y::Uint128) = box(Uint128,sub_int(unbox(Uint128,x),unbox(Uint128,y)))

*(x::Int,     y::Int)     = box(Int,mul_int(unbox(Int,x),unbox(Int,y)))
*(x::Uint,    y::Uint)    = box(Uint,mul_int(unbox(Uint,x),unbox(Uint,y)))
*(x::Int64,   y::Int64)   = box(Int64,mul_int(unbox(Int64,x),unbox(Int64,y)))
*(x::Uint64,  y::Uint64)  = box(Uint64,mul_int(unbox(Uint64,x),unbox(Uint64,y)))

/(x::Integer, y::Integer) = float(x)/float(y)
inv(x::Integer) = float(one(x))/float(x)

isodd(n::Integer) = bool(rem(n,2))
iseven(n::Integer) = !isodd(n)

signbit(x::Integer) = x < 0
signbit(x::Unsigned) = false

flipsign(x::Int,    y::Int)    = box(Int,flipsign_int(unbox(Int,x),unbox(Int,y)))
flipsign(x::Int64,  y::Int64)  = box(Int64,flipsign_int(unbox(Int64,x),unbox(Int64,y)))
flipsign(x::Int128, y::Int128) = box(Int128,flipsign_int(unbox(Int128,x),unbox(Int128,y)))

flipsign{T<:Signed}(x::T,y::T)  = flipsign(int(x),int(y))
flipsign(x::Signed, y::Signed)  = flipsign(promote(x,y)...)
flipsign(x::Signed, y::Float32) = flipsign(x, reinterpret(Int32,y))
flipsign(x::Signed, y::Float64) = flipsign(x, reinterpret(Int64,y))
flipsign(x::Signed, y::Real)    = flipsign(x, -oftype(x,signbit(y)))

copysign(x::Signed, y::Signed)  = flipsign(x, x$y)
copysign(x::Signed, y::Float32) = copysign(x, reinterpret(Int32,y))
copysign(x::Signed, y::Float64) = copysign(x, reinterpret(Int64,y))
copysign(x::Signed, y::Real)    = copysign(x, -oftype(x,signbit(y)))

abs(x::Unsigned) = x
abs(x::Signed) = flipsign(x,x)

~(n::Integer) = -n-1

div(x::Signed, y::Unsigned) = flipsign(signed(div(unsigned(abs(x)),y)),x)
div(x::Unsigned, y::Signed) = unsigned(flipsign(signed(div(x,unsigned(abs(y)))),y))

rem(x::Signed, y::Unsigned) = flipsign(signed(rem(unsigned(abs(x)),y)),x)
rem(x::Unsigned, y::Signed) = rem(x,unsigned(abs(y)))

fld(x::Signed, y::Unsigned) = div(x,y)-(signbit(x)&(rem(x,y)!=0))
fld(x::Unsigned, y::Signed) = div(x,y)-(signbit(y)&(rem(x,y)!=0))

mod(x::Signed, y::Unsigned) = rem(y+unsigned(rem(x,y)),y)
mod(x::Unsigned, y::Signed) = rem(y+signed(rem(x,y)),y)

# Don't promote integers for div/rem/mod since there no danger of overflow,
# while there is a substantial performance penalty to 64-bit promotion.
typealias Signed64 Union(Int8,Int16,Int32,Int64)
typealias Unsigned64 Union(Uint8,Uint16,Uint32,Uint64)
typealias Integer64 Union(Signed64,Unsigned64)

div{T<:Signed64}  (x::T, y::T) = box(T,sdiv_int(unbox(T,x),unbox(T,y)))
div{T<:Unsigned64}(x::T, y::T) = box(T,udiv_int(unbox(T,x),unbox(T,y)))
rem{T<:Signed64}  (x::T, y::T) = box(T,srem_int(unbox(T,x),unbox(T,y)))
rem{T<:Unsigned64}(x::T, y::T) = box(T,urem_int(unbox(T,x),unbox(T,y)))
mod{T<:Signed64}  (x::T, y::T) = box(T,smod_int(unbox(T,x),unbox(T,y)))

mod{T<:Unsigned}(x::T, y::T) = rem(x,y)

fld{T<:Unsigned}(x::T, y::T) = div(x,y)
fld{T<:Integer }(x::T, y::T) = div(x,y)-(signbit(x$y)&(rem(x,y)!=0))

## integer bitwise operations ##

~(x::Int8 )  = box(Int8,not_int(unbox(Int8,x)))
~(x::Int16)  = box(Int16,not_int(unbox(Int16,x)))
~(x::Int32)  = box(Int32,not_int(unbox(Int32,x)))
~(x::Int64)  = box(Int64,not_int(unbox(Int64,x)))
~(x::Int128) = box(Int128,not_int(unbox(Int128,x)))

~(x::Uint8 )  = box(Uint8,not_int(unbox(Uint8,x)))
~(x::Uint16)  = box(Uint16,not_int(unbox(Uint16,x)))
~(x::Uint32)  = box(Uint32,not_int(unbox(Uint32,x)))
~(x::Uint64)  = box(Uint64,not_int(unbox(Uint64,x)))
~(x::Uint128) = box(Uint128,not_int(unbox(Uint128,x)))

(&)(x::Int8,   y::Int8 )  = box(Int8,and_int(unbox(Int8,x),unbox(Int8,y)))
(&)(x::Int16,  y::Int16)  = box(Int16,and_int(unbox(Int16,x),unbox(Int16,y)))
(&)(x::Int32,  y::Int32)  = box(Int32,and_int(unbox(Int32,x),unbox(Int32,y)))
(&)(x::Int64,  y::Int64)  = box(Int64,and_int(unbox(Int64,x),unbox(Int64,y)))
(&)(x::Int128, y::Int128) = box(Int128,and_int(unbox(Int128,x),unbox(Int128,y)))

(&)(x::Uint8,   y::Uint8 )  = box(Uint8,and_int(unbox(Uint8,x),unbox(Uint8,y)))
(&)(x::Uint16,  y::Uint16)  = box(Uint16,and_int(unbox(Uint16,x),unbox(Uint16,y)))
(&)(x::Uint32,  y::Uint32)  = box(Uint32,and_int(unbox(Uint32,x),unbox(Uint32,y)))
(&)(x::Uint64,  y::Uint64)  = box(Uint64,and_int(unbox(Uint64,x),unbox(Uint64,y)))
(&)(x::Uint128, y::Uint128) = box(Uint128,and_int(unbox(Uint128,x),unbox(Uint128,y)))

|(x::Int8,   y::Int8)   = box(Int8,or_int(unbox(Int8,x),unbox(Int8,y)))
|(x::Int16,  y::Int16)  = box(Int16,or_int(unbox(Int16,x),unbox(Int16,y)))
|(x::Int32,  y::Int32)  = box(Int32,or_int(unbox(Int32,x),unbox(Int32,y)))
|(x::Int64,  y::Int64)  = box(Int64,or_int(unbox(Int64,x),unbox(Int64,y)))
|(x::Int128, y::Int128) = box(Int128,or_int(unbox(Int128,x),unbox(Int128,y)))

|(x::Uint8,   y::Uint8)   = box(Uint8,or_int(unbox(Uint8,x),unbox(Uint8,y)))
|(x::Uint16,  y::Uint16)  = box(Uint16,or_int(unbox(Uint16,x),unbox(Uint16,y)))
|(x::Uint32,  y::Uint32)  = box(Uint32,or_int(unbox(Uint32,x),unbox(Uint32,y)))
|(x::Uint64,  y::Uint64)  = box(Uint64,or_int(unbox(Uint64,x),unbox(Uint64,y)))
|(x::Uint128, y::Uint128) = box(Uint128,or_int(unbox(Uint128,x),unbox(Uint128,y)))

($)(x::Int8,   y::Int8)   = box(Int8,xor_int(unbox(Int8,x),unbox(Int8,y)))
($)(x::Int16,  y::Int16)  = box(Int16,xor_int(unbox(Int16,x),unbox(Int16,y)))
($)(x::Int32,  y::Int32)  = box(Int32,xor_int(unbox(Int32,x),unbox(Int32,y)))
($)(x::Int64,  y::Int64)  = box(Int64,xor_int(unbox(Int64,x),unbox(Int64,y)))
($)(x::Int128, y::Int128) = box(Int128,xor_int(unbox(Int128,x),unbox(Int128,y)))

($)(x::Uint8,   y::Uint8)   = box(Uint8,xor_int(unbox(Uint8,x),unbox(Uint8,y)))
($)(x::Uint16,  y::Uint16)  = box(Uint16,xor_int(unbox(Uint16,x),unbox(Uint16,y)))
($)(x::Uint32,  y::Uint32)  = box(Uint32,xor_int(unbox(Uint32,x),unbox(Uint32,y)))
($)(x::Uint64,  y::Uint64)  = box(Uint64,xor_int(unbox(Uint64,x),unbox(Uint64,y)))
($)(x::Uint128, y::Uint128) = box(Uint128,xor_int(unbox(Uint128,x),unbox(Uint128,y)))

<<(x::Int8,   y::Int32) = box(Int8,shl_int(unbox(Int8,x),unbox(Int32,y)))
<<(x::Int16,  y::Int32) = box(Int16,shl_int(unbox(Int16,x),unbox(Int32,y)))
<<(x::Int32,  y::Int32) = box(Int32,shl_int(unbox(Int32,x),unbox(Int32,y)))
<<(x::Int64,  y::Int32) = box(Int64,shl_int(unbox(Int64,x),unbox(Int32,y)))
<<(x::Int128, y::Int32) = box(Int128,shl_int(unbox(Int128,x),unbox(Int32,y)))

<<(x::Uint8,   y::Int32) = box(Uint8,shl_int(unbox(Uint8,x),unbox(Int32,y)))
<<(x::Uint16,  y::Int32) = box(Uint16,shl_int(unbox(Uint16,x),unbox(Int32,y)))
<<(x::Uint32,  y::Int32) = box(Uint32,shl_int(unbox(Int32,x),unbox(Uint32,y)))
<<(x::Uint64,  y::Int32) = box(Uint64,shl_int(unbox(Uint64,x),unbox(Int32,y)))
<<(x::Uint128, y::Int32) = box(Uint128,shl_int(unbox(Uint128,x),unbox(Int32,y)))

>>(x::Int8,   y::Int32) = box(Int8,ashr_int(unbox(Int8,x),unbox(Int32,y)))
>>(x::Int16,  y::Int32) = box(Int16,ashr_int(unbox(Int16,x),unbox(Int32,y)))
>>(x::Int32,  y::Int32) = box(Int32,ashr_int(unbox(Int32,x),unbox(Int32,y)))
>>(x::Int64,  y::Int32) = box(Int64,ashr_int(unbox(Int64,x),unbox(Int32,y)))
>>(x::Int128, y::Int32) = box(Int128,ashr_int(unbox(Int128,x),unbox(Int32,y)))

>>(x::Uint8,   y::Int32) = box(Uint8,lshr_int(unbox(Uint8,x),unbox(Int32,y)))
>>(x::Uint16,  y::Int32) = box(Uint16,lshr_int(unbox(Uint16,x),unbox(Int32,y)))
>>(x::Uint32,  y::Int32) = box(Uint32,lshr_int(unbox(Int32,x),unbox(Uint32,y)))
>>(x::Uint64,  y::Int32) = box(Uint64,lshr_int(unbox(Uint64,x),unbox(Int32,y)))
>>(x::Uint128, y::Int32) = box(Uint128,lshr_int(unbox(Uint128,x),unbox(Int32,y)))

>>>(x::Int8,   y::Int32) = box(Int8,lshr_int(unbox(Int8,x),unbox(Int32,y)))
>>>(x::Int16,  y::Int32) = box(Int16,lshr_int(unbox(Int16,x),unbox(Int32,y)))
>>>(x::Int32,  y::Int32) = box(Int32,lshr_int(unbox(Int32,x),unbox(Int32,y)))
>>>(x::Int64,  y::Int32) = box(Int64,lshr_int(unbox(Int64,x),unbox(Int32,y)))
>>>(x::Int128, y::Int32) = box(Int128,lshr_int(unbox(Int128,x),unbox(Int32,y)))

>>>(x::Uint8,   y::Int32) = box(Uint8,lshr_int(unbox(Uint8,x),unbox(Int32,y)))
>>>(x::Uint16,  y::Int32) = box(Uint16,lshr_int(unbox(Uint16,x),unbox(Int32,y)))
>>>(x::Uint32,  y::Int32) = box(Uint32,lshr_int(unbox(Int32,x),unbox(Uint32,y)))
>>>(x::Uint64,  y::Int32) = box(Uint64,lshr_int(unbox(Uint64,x),unbox(Int32,y)))
>>>(x::Uint128, y::Int32) = box(Uint128,lshr_int(unbox(Uint128,x),unbox(Int32,y)))

bswap(x::Int8)    = x
bswap(x::Uint8)   = x
bswap(x::Int16)   = box(Int16,bswap_int(unbox(Int16,x)))
bswap(x::Uint16)  = box(Uint16,bswap_int(unbox(Uint16,x)))
bswap(x::Int32)   = box(Int32,bswap_int(unbox(Int32,x)))
bswap(x::Uint32)  = box(Uint32,bswap_int(unbox(Uint32,x)))
bswap(x::Int64)   = box(Int64,bswap_int(unbox(Int64,x)))
bswap(x::Uint64)  = box(Uint64,bswap_int(unbox(Uint64,x)))
bswap(x::Int128)  = box(Int128,bswap_int(unbox(Int128,x)))
bswap(x::Uint128) = box(Uint128,bswap_int(unbox(Uint128,x)))

count_ones(x::Int8)    = int(box(Int8,ctpop_int(unbox(Int8,x))))
count_ones(x::Uint8)   = int(box(Uint8,ctpop_int(unbox(Uint8,x))))
count_ones(x::Int16)   = int(box(Int16,ctpop_int(unbox(Int16,x))))
count_ones(x::Uint16)  = int(box(Uint16,ctpop_int(unbox(Uint16,x))))
count_ones(x::Int32)   = int(box(Int32,ctpop_int(unbox(Int32,x))))
count_ones(x::Uint32)  = int(box(Uint32,ctpop_int(unbox(Uint32,x))))
count_ones(x::Int64)   = int(box(Int64,ctpop_int(unbox(Int64,x))))
count_ones(x::Uint64)  = int(box(Uint64,ctpop_int(unbox(Uint64,x))))
count_ones(x::Int128)  = int(box(Int128,ctpop_int(unbox(Int128,x))))
count_ones(x::Uint128) = int(box(Uint128,ctpop_int(unbox(Uint128,x))))

leading_zeros(x::Int8)    = int(box(Int8,ctlz_int(unbox(Int8,x))))
leading_zeros(x::Uint8)   = int(box(Uint8,ctlz_int(unbox(Uint8,x))))
leading_zeros(x::Int16)   = int(box(Int16,ctlz_int(unbox(Int16,x))))
leading_zeros(x::Uint16)  = int(box(Uint16,ctlz_int(unbox(Uint16,x))))
leading_zeros(x::Int32)   = int(box(Int32,ctlz_int(unbox(Int32,x))))
leading_zeros(x::Uint32)  = int(box(Uint32,ctlz_int(unbox(Uint32,x))))
leading_zeros(x::Int64)   = int(box(Int64,ctlz_int(unbox(Int64,x))))
leading_zeros(x::Uint64)  = int(box(Uint64,ctlz_int(unbox(Uint64,x))))
leading_zeros(x::Int128)  = int(box(Int128,ctlz_int(unbox(Int128,x))))
leading_zeros(x::Uint128) = int(box(Uint128,ctlz_int(unbox(Uint128,x))))

trailing_zeros(x::Int8)    = int(box(Int8,cttz_int(unbox(Int8,x))))
trailing_zeros(x::Uint8)   = int(box(Uint8,cttz_int(unbox(Uint8,x))))
trailing_zeros(x::Int16)   = int(box(Int16,cttz_int(unbox(Int16,x))))
trailing_zeros(x::Uint16)  = int(box(Uint16,cttz_int(unbox(Uint16,x))))
trailing_zeros(x::Int32)   = int(box(Int32,cttz_int(unbox(Int32,x))))
trailing_zeros(x::Uint32)  = int(box(Uint32,cttz_int(unbox(Uint32,x))))
trailing_zeros(x::Int64)   = int(box(Int64,cttz_int(unbox(Int64,x))))
trailing_zeros(x::Uint64)  = int(box(Uint64,cttz_int(unbox(Uint64,x))))
trailing_zeros(x::Int128)  = int(box(Int128,cttz_int(unbox(Int128,x))))
trailing_zeros(x::Uint128) = int(box(Uint128,cttz_int(unbox(Uint128,x))))

count_zeros  (x::Integer) = count_ones(~x)
leading_ones (x::Integer) = leading_zeros(~x)
trailing_ones(x::Integer) = trailing_zeros(~x)

## integer comparisons ##

<(x::Int8,   y::Int8)   = slt_int(unbox(Int8,x),unbox(Int8,y))
<(x::Int16,  y::Int16)  = slt_int(unbox(Int16,x),unbox(Int16,y))
<(x::Int32,  y::Int32)  = slt_int(unbox(Int32,x),unbox(Int32,y))
<(x::Int64,  y::Int64)  = slt_int(unbox(Int64,x),unbox(Int64,y))
<(x::Int128, y::Int128) = slt_int(unbox(Int128,x),unbox(Int128,y))

<(x::Uint8,   y::Uint8)   = ult_int(unbox(Uint8,x),unbox(Uint8,y))
<(x::Uint16,  y::Uint16)  = ult_int(unbox(Uint16,x),unbox(Uint16,y))
<(x::Uint32,  y::Uint32)  = ult_int(unbox(Uint32,x),unbox(Uint32,y))
<(x::Uint64,  y::Uint64)  = ult_int(unbox(Uint64,x),unbox(Uint64,y))
<(x::Uint128, y::Uint128) = ult_int(unbox(Uint128,x),unbox(Uint128,y))

<=(x::Int8,   y::Int8)   = sle_int(unbox(Int8,x),unbox(Int8,y))
<=(x::Int16,  y::Int16)  = sle_int(unbox(Int16,x),unbox(Int16,y))
<=(x::Int32,  y::Int32)  = sle_int(unbox(Int32,x),unbox(Int32,y))
<=(x::Int64,  y::Int64)  = sle_int(unbox(Int64,x),unbox(Int64,y))
<=(x::Int128, y::Int128) = sle_int(unbox(Int128,x),unbox(Int128,y))

<=(x::Uint8,   y::Uint8)   = ule_int(unbox(Uint8,x),unbox(Uint8,y))
<=(x::Uint16,  y::Uint16)  = ule_int(unbox(Uint16,x),unbox(Uint16,y))
<=(x::Uint32,  y::Uint32)  = ule_int(unbox(Uint32,x),unbox(Uint32,y))
<=(x::Uint64,  y::Uint64)  = ule_int(unbox(Uint64,x),unbox(Uint64,y))
<=(x::Uint128, y::Uint128) = ule_int(unbox(Uint128,x),unbox(Uint128,y))

==(x::Signed,   y::Unsigned) = (x >= 0) & (unsigned(x) == y)
==(x::Unsigned, y::Signed  ) = (y >= 0) & (x == unsigned(y))
< (x::Signed,   y::Unsigned) = (x <  0) | (unsigned(x) <  y)
< (x::Unsigned, y::Signed  ) = (y >  0) & (x <  unsigned(y))
<=(x::Signed,   y::Unsigned) = (x <= 0) | (unsigned(x) <= y)
<=(x::Unsigned, y::Signed  ) = (y >= 0) & (x <= unsigned(y))

## integer conversions ##

const _inttypes = (Bool, Int8, Uint8, Int16, Uint16, Int32, Uint32, Char,
                   Int64, Uint64, Int128, Uint128)

for to in _inttypes, from in _inttypes
    if !(to===from) && !(to===Bool)
        if to.size < from.size
            @eval convert(::Type{$to}, x::($from)) = box($to,trunc_int($to,unbox($from,x)))
        elseif from.size < to.size || from===Bool
            if issubtype(from, Signed)
                @eval convert(::Type{$to}, x::($from)) = box($to,sext_int($to,unbox($from,x)))
            else
                @eval convert(::Type{$to}, x::($from)) = box($to,zext_int($to,unbox($from,x)))
            end
        else
            @eval convert(::Type{$to}, x::($from)) = box($to,unbox($from,x))
        end
    end
end

for to in (Int8, Int16, Int32, Int64)
    @eval begin
        convert(::Type{$to}, x::Float32) = box($to,checked_fptosi($to,unbox(Float32,x)))
        convert(::Type{$to}, x::Float64) = box($to,checked_fptosi($to,unbox(Float64,x)))
    end
end

for to in (Uint8, Uint16, Uint32, Uint64)
    @eval begin
        convert(::Type{$to}, x::Float32) = box($to,checked_fptoui($to,unbox(Float32,x)))
        convert(::Type{$to}, x::Float64) = box($to,checked_fptoui($to,unbox(Float64,x)))
    end
end

function convert(::Type{Int128}, x::FloatingPoint)
    ax = abs(x)
    top = trunc(ldexp(ax,-64))
    bot = ax - ldexp(top,64)
    n = int128(convert(Uint64,top))<<64 + int128(convert(Uint64,bot))
    return x<0 ? -n : n
end
convert(::Type{Int128}, x::Float32) = convert(Int128, float64(x))

function convert(::Type{Uint128}, x::FloatingPoint)
    ax = abs(x)
    top = trunc(ldexp(ax,-64))
    bot = ax - ldexp(top,64)
    n = uint128(convert(Uint64,top))<<64 + uint128(convert(Uint64,bot))
    return x<0 ? -n : n
end
convert(::Type{Uint128}, x::Float32) = convert(Uint128, float64(x))

convert(::Type{Char}, x::Float32) = char(convert(Int, x))
convert(::Type{Char}, x::Float64) = char(convert(Int, x))

convert(::Type{Signed}, x::Uint8  ) = convert(Int,x)
convert(::Type{Signed}, x::Uint16 ) = convert(Int,x)
convert(::Type{Signed}, x::Uint32 ) = convert(Int,x)
convert(::Type{Signed}, x::Uint64 ) = convert(Int64,x)
convert(::Type{Signed}, x::Uint128) = convert(Int128,x)
convert(::Type{Signed}, x::Float32) = convert(Int,x)
convert(::Type{Signed}, x::Float64) = convert(Int,x)
convert(::Type{Signed}, x::Char)    = convert(Int,x)

convert(::Type{Unsigned}, x::Int8   ) = convert(Uint,x)
convert(::Type{Unsigned}, x::Int16  ) = convert(Uint,x)
convert(::Type{Unsigned}, x::Int32  ) = convert(Uint,x)
convert(::Type{Unsigned}, x::Int64  ) = convert(Uint64,x)
convert(::Type{Unsigned}, x::Int128 ) = convert(Uint128,x)
convert(::Type{Unsigned}, x::Float32) = convert(Uint,x)
convert(::Type{Unsigned}, x::Float64) = convert(Uint,x)
convert(::Type{Unsigned}, x::Char)    = convert(Uint,x)

convert(::Type{Integer}, x::Float32) = convert(Int,x)
convert(::Type{Integer}, x::Float64) = convert(Int,x)

int8(x) = convert(Int8,x)
int16(x) = convert(Int16,x)
int32(x) = convert(Int32,x)
int64(x) = convert(Int64,x)
int128(x) = convert(Int128,x)

uint8(x) = convert(Uint8,x)
uint16(x) = convert(Uint16,x)
uint32(x) = convert(Uint32,x)
uint64(x) = convert(Uint64,x)
uint128(x) = convert(Uint128,x)

signed(x) = convert(Signed,x)
unsigned(x) = convert(Unsigned,x)
integer(x) = convert(Integer,x)

round(x::Integer) = x
trunc(x::Integer) = x
floor(x::Integer) = x
 ceil(x::Integer) = x

iround(x::Integer) = x
iround{T<:Integer}(::Type{T}, x::Integer) = convert(T, x)
itrunc(x::Integer) = x
ifloor(x::Integer) = x
 iceil(x::Integer) = x

## integer construction ##

macro int128_str(str)
    int128(str)
end

macro uint128_str(str)
    uint128(str)
end

macro bigint_str(str)
    BigInt(str)
end

## system word size ##

const WORD_SIZE = int(Int.size)*8

## integer promotions ##

promote_rule(::Type{Int16},  ::Type{Int8} ) = Int
promote_rule(::Type{Int32},  ::Type{Int8} ) = Int
promote_rule(::Type{Int32},  ::Type{Int16}) = Int
promote_rule(::Type{Int64},  ::Type{Int8} ) = Int64
promote_rule(::Type{Int64},  ::Type{Int16}) = Int64
promote_rule(::Type{Int64},  ::Type{Int32}) = Int64
promote_rule(::Type{Int128}, ::Type{Int8} ) = Int128
promote_rule(::Type{Int128}, ::Type{Int16}) = Int128
promote_rule(::Type{Int128}, ::Type{Int32}) = Int128
promote_rule(::Type{Int128}, ::Type{Int64}) = Int128

promote_rule(::Type{Uint16},  ::Type{Uint8} ) = Uint
promote_rule(::Type{Uint32},  ::Type{Uint8} ) = Uint
promote_rule(::Type{Uint32},  ::Type{Uint16}) = Uint
promote_rule(::Type{Uint64},  ::Type{Uint8} ) = Uint64
promote_rule(::Type{Uint64},  ::Type{Uint16}) = Uint64
promote_rule(::Type{Uint64},  ::Type{Uint32}) = Uint64
promote_rule(::Type{Uint128}, ::Type{Uint8} ) = Uint128
promote_rule(::Type{Uint128}, ::Type{Uint16}) = Uint128
promote_rule(::Type{Uint128}, ::Type{Uint32}) = Uint128
promote_rule(::Type{Uint128}, ::Type{Uint64}) = Uint128

promote_rule(::Type{Uint8}, ::Type{Int8}  ) = Int
promote_rule(::Type{Uint8}, ::Type{Int16} ) = Int
promote_rule(::Type{Uint8}, ::Type{Int32} ) = Int
promote_rule(::Type{Uint8}, ::Type{Int64} ) = Int64
promote_rule(::Type{Uint8}, ::Type{Int128}) = Int128

promote_rule(::Type{Uint16}, ::Type{Int8}  ) = Int
promote_rule(::Type{Uint16}, ::Type{Int16} ) = Int
promote_rule(::Type{Uint16}, ::Type{Int32} ) = Int
promote_rule(::Type{Uint16}, ::Type{Int64} ) = Int64
promote_rule(::Type{Uint16}, ::Type{Int128}) = Int128

if WORD_SIZE == 64
    promote_rule(::Type{Uint32}, ::Type{Int8} ) = Int
    promote_rule(::Type{Uint32}, ::Type{Int16}) = Int
    promote_rule(::Type{Uint32}, ::Type{Int32}) = Int
else
    promote_rule(::Type{Uint32}, ::Type{Int8} ) = Uint
    promote_rule(::Type{Uint32}, ::Type{Int16}) = Uint
    promote_rule(::Type{Uint32}, ::Type{Int32}) = Uint
end
promote_rule(::Type{Uint32}, ::Type{Int64} ) = Int64
promote_rule(::Type{Uint32}, ::Type{Int128}) = Int128

promote_rule(::Type{Uint64}, ::Type{Int8}  ) = Uint64
promote_rule(::Type{Uint64}, ::Type{Int16} ) = Uint64
promote_rule(::Type{Uint64}, ::Type{Int32} ) = Uint64
promote_rule(::Type{Uint64}, ::Type{Int64} ) = Uint64
promote_rule(::Type{Uint64}, ::Type{Int128}) = Int128

promote_rule(::Type{Uint128}, ::Type{Int8}  ) = Uint128
promote_rule(::Type{Uint128}, ::Type{Int16} ) = Uint128
promote_rule(::Type{Uint128}, ::Type{Int32} ) = Uint128
promote_rule(::Type{Uint128}, ::Type{Int64} ) = Uint128
promote_rule(::Type{Uint128}, ::Type{Int128}) = Uint128

## traits ##

typemin(::Type{Int8  }) = int8(-128)
typemax(::Type{Int8  }) = int8(127)
typemin(::Type{Uint8 }) = uint8(0)
typemax(::Type{Uint8 }) = uint8(255)
typemin(::Type{Int16 }) = int16(-32768)
typemax(::Type{Int16 }) = int16(32767)
typemin(::Type{Uint16}) = uint16(0)
typemax(::Type{Uint16}) = uint16(65535)
typemin(::Type{Int32 }) = int32(-2147483648)
typemax(::Type{Int32 }) = int32(2147483647)
typemin(::Type{Uint32}) = uint32(0)
typemax(::Type{Uint32}) = uint32(4294967295)
typemin(::Type{Int64 }) = -9223372036854775808
typemax(::Type{Int64 }) = 9223372036854775807
typemin(::Type{Uint64}) = uint64(0)
typemax(::Type{Uint64}) = 0xffffffffffffffff
@eval begin
    typemin(::Type{Uint128}) = uint128(0)
    typemax(::Type{Uint128}) = $(uint128(-1))
    typemin(::Type{Int128} ) = $(int128((uint128(-1))>>int32(1))+int128(1))
    typemax(::Type{Int128} ) = $(int128((uint128(-1))>>int32(1)))
end

sizeof(::Type{Int8})    = 1
sizeof(::Type{Uint8})   = 1
sizeof(::Type{Int16})   = 2
sizeof(::Type{Uint16})  = 2
sizeof(::Type{Int32})   = 4
sizeof(::Type{Uint32})  = 4
sizeof(::Type{Int64})   = 8
sizeof(::Type{Uint64})  = 8
sizeof(::Type{Int128})  = 16
sizeof(::Type{Uint128}) = 16

widen(::Type{Int8}) = Int
widen(::Type{Int16}) = Int
widen(::Type{Int32}) = Int64
widen(::Type{Int64}) = Int128
widen(::Type{Uint8}) = Uint
widen(::Type{Uint16}) = Uint
widen(::Type{Uint32}) = Uint64
widen(::Type{Uint64}) = Uint128

## float to integer coercion ##

# requires int arithmetic defined, for the loops to work

for f in (:int, :int8, :int16, :int32, :signed, :integer)
    @eval ($f)(x::FloatingPoint) = ($f)(iround(x))
end

for (f,t) in ((:uint8,:Uint8), (:uint16,:Uint16), (:uint32,:Uint32),
              (:int64,:Int64), (:uint64,:Uint64),
              (:int128,:Int128), (:uint128,:Uint128),
              (:unsigned,:Uint), (:uint,:Uint))
    @eval ($f)(x::FloatingPoint) = iround($t,x)
end

## wide multiplication, Int128 multiply and divide ##

if WORD_SIZE==32
    function widemul(u::Int64, v::Int64)
        local u0::Uint64, v0::Uint64, w0::Uint64
        local u1::Int64, v1::Int64, w1::Int64, w2::Int64, t::Int64

        u0 = u&0xffffffff; u1 = u>>32
        v0 = v&0xffffffff; v1 = v>>32
        w0 = u0*v0
        t = u1*v0 + (w0>>>32)
        w2 = t>>32
        w1 = u0*v1 + (t&0xffffffff)
        hi = u1*v1 + w2 + (w1 >> 32)
        lo = w0&0xffffffff + (w1 << 32)
        int128(hi)<<64 + int128(uint128(lo))
    end

    function widemul(u::Uint64, v::Uint64)
        local u0::Uint64, v0::Uint64, w0::Uint64
        local u1::Uint64, v1::Uint64, w1::Uint64, w2::Uint64, t::Uint64

        u0 = u&0xffffffff; u1 = u>>>32
        v0 = v&0xffffffff; v1 = v>>>32
        w0 = u0*v0
        t = u1*v0 + (w0>>>32)
        w2 = t>>>32
        w1 = u0*v1 + (t&0xffffffff)
        hi = u1*v1 + w2 + (w1 >>> 32)
        lo = w0&0xffffffff + (w1 << 32)
        uint128(hi)<<64 + uint128(lo)
    end

    function *(u::Int128, v::Int128)
        u0 = uint64(u); u1 = int64(u>>64)
        v0 = uint64(v); v1 = int64(v>>64)
        lolo = widemul(u0, v0)
        lohi = widemul(int64(u0), v1)
        hilo = widemul(u1, int64(v0))
        t = hilo + (lolo>>>64)
        w2 = t>>64
        w1 = lohi + (t&0xffffffffffffffff)
        int128(lolo&0xffffffffffffffff) + int128(w1)<<64
    end

    function *(u::Uint128, v::Uint128)
        u0 = uint64(u); u1 = uint64(u>>>64)
        v0 = uint64(v); v1 = uint64(v>>>64)
        lolo = widemul(u0, v0)
        lohi = widemul(u0, v1)
        hilo = widemul(u1, v0)
        t = hilo + (lolo>>>64)
        w2 = t>>>64
        w1 = lohi + (t&0xffffffffffffffff)
        (lolo&0xffffffffffffffff) + uint128(w1)<<64
    end

    div(x::Int128, y::Int128) = int128(div(BigInt(x),BigInt(y)))
    div(x::Uint128, y::Uint128) = uint128(div(BigInt(x),BigInt(y)))

    rem(x::Int128, y::Int128) = int128(rem(BigInt(x),BigInt(y)))
    rem(x::Uint128, y::Uint128) = uint128(rem(BigInt(x),BigInt(y)))

    mod(x::Int128, y::Int128) = int128(mod(BigInt(x),BigInt(y)))

    << (x::Int128,  y::Int32) = y == 0 ? x : box(Int128,shl_int(unbox(Int128,x),unbox(Int32,y)))
    << (x::Uint128, y::Int32) = y == 0 ? x : box(Uint128,shl_int(unbox(Uint128,x),unbox(Int32,y)))
    >> (x::Int128,  y::Int32) = y == 0 ? x : box(Int128,ashr_int(unbox(Int128,x),unbox(Int32,y)))
    >> (x::Uint128, y::Int32) = y == 0 ? x : box(Uint128,lshr_int(unbox(Uint128,x),unbox(Int32,y)))
    >>>(x::Int128,  y::Int32) = y == 0 ? x : box(Int128,lshr_int(unbox(Int128,x),unbox(Int32,y)))
    >>>(x::Uint128, y::Int32) = y == 0 ? x : box(Uint128,lshr_int(unbox(Uint128,x),unbox(Int32,y)))
else
    *(x::Int128,  y::Int128)  = box(Int128,mul_int(unbox(Int128,x),unbox(Int128,y)))
    *(x::Uint128, y::Uint128) = box(Uint128,mul_int(unbox(Uint128,x),unbox(Uint128,y)))

    div(x::Int128,  y::Int128)  = box(Int128,sdiv_int(unbox(Int128,x),unbox(Int128,y)))
    div(x::Uint128, y::Uint128) = box(Uint128,udiv_int(unbox(Uint128,x),unbox(Uint128,y)))

    rem(x::Int128,  y::Int128)  = box(Int128,srem_int(unbox(Int128,x),unbox(Int128,y)))
    rem(x::Uint128, y::Uint128) = box(Uint128,urem_int(unbox(Uint128,x),unbox(Uint128,y)))

    mod(x::Int128, y::Int128) = box(Int128,smod_int(unbox(Int128,x),unbox(Int128,y)))
end

## checked +, - and *

for T in (Int8,Int16,Int32,Int64)#,Int128) ## FIXME: #4905
    @eval begin
        checked_add(x::$T, y::$T) = box($T,checked_sadd(unbox($T,x),unbox($T,y)))
        checked_sub(x::$T, y::$T) = box($T,checked_ssub(unbox($T,x),unbox($T,y)))
    end
end
for T in (Int16,Int32)
    @eval begin
        checked_mul(x::$T, y::$T) = box($T,checked_smul(unbox($T,x),unbox($T,y)))
    end
end
for T in (Uint8,Uint16,Uint32,Uint64)#,Uint128) ## FIXME: #4905
    @eval begin
        checked_add(x::$T, y::$T) = box($T,checked_uadd(unbox($T,x),unbox($T,y)))
        checked_sub(x::$T, y::$T) = box($T,checked_usub(unbox($T,x),unbox($T,y)))
    end
end
for T in (Uint16,Uint32)
    @eval begin
        checked_mul(x::$T, y::$T) = box($T,checked_umul(unbox($T,x),unbox($T,y)))
    end
end

# checked mul is broken for 8-bit types (LLVM bug?) ## FIXME: #4905

for T in (Int8,Uint8)
    @eval function checked_mul(x::$T, y::$T)
        xy = x*y
        xy8 = convert($T,xy)
        xy == xy8 || throw(OverflowError())
        return xy8
    end
end

if WORD_SIZE == 32
for T in (Int64,Uint64)
    @eval function checked_mul(x::$T, y::$T)
        xy = int128(x)*int128(y)
        xy64 = convert($T,xy)
        xy == xy64 || throw(OverflowError())
        return xy64
    end
end
else
    checked_mul(x::Int64, y::Int64)   = box(Int64,checked_smul(unbox(Int64,x),unbox(Int64,y)))
    checked_mul(x::Uint64, y::Uint64) = box(Uint64,checked_umul(unbox(Uint64,x),unbox(Uint64,y)))
end

# checked ops are broken for 128-bit types (LLVM bug) ## FIXME: #4905

checked_add(x::Int128, y::Int128) = x + y
checked_sub(x::Int128, y::Int128) = x - y
checked_mul(x::Int128, y::Int128) = x * y

checked_add(x::Uint128, y::Uint128) = x + y
checked_sub(x::Uint128, y::Uint128) = x - y
checked_mul(x::Uint128, y::Uint128) = x * y


using Pkg
Pkg.activate("scripts")
#Pkg.add(["Plots", "CSV", "DataFrames", "SimilaritySearch", "TextSearch", "Formatting", "Latexify", "UMAP", "Glob"])
using Plots, JLD2, LinearAlgebra, Glob

files = glob("data/SpanishLang/embeddings/umap-embeddings-*.jld2")

for filename in files
    @info filename
    X, C = load(filename, "e2", "e3")

    for i in (1, 2, 3)
        min_, max_ = extrema(C[i, :])
        # @info i => (min_, max_)
        for j in 1:size(C, 2)
            C[i, j] = (C[i, j] - min_) / (max_ - min_)
        end
    end

    C = [RGB(c...) for c in eachcol(C)]

    title = last(split(replace(basename(filename), ".jld2" => ""), '-'))
    display(scatter(X[1, :], X[2, :], c=C, label="", ms=6, ma=0.7, title=title, fmt=:png))
end


function TIER_td_loss(;loss=Flux.mse, name=:Qavg, s_key=:s, a_key=:a, weight=nothing)
    (π, 𝒫, 𝒟, y; info=Dict(), z) -> begin
        Q = value(π, vcat(𝒟[s_key], z), 𝒟[a_key])

        # Store useful information
        Zygote.ignore() do
            info[name] = mean(Q)
        end

        loss(Q, y, agg = isnothing(weight) ? mean : weighted_mean(𝒟[weight]))
    end
end

function TIER_double_Q_loss(;name1=:Q1avg, name2=:Q2avg, kwargs...)
    l1 = TIER_td_loss(;name=name1, kwargs...)
    l2 = TIER_td_loss(;name=name2, kwargs...)

    (π, 𝒫, 𝒟, y; info=Dict(), z=𝒟[:z]) -> begin
        .5f0*(l1(critic(π).N1, 𝒫, 𝒟, y, info=info, z=z) + l2(critic(π).N2, 𝒫, 𝒟, y, info=info,z=z))
    end
end

TIER_TD3_actor_loss(π, 𝒫, 𝒟; info = Dict()) = -mean(value(critic(π).N1, vcat(𝒟[:s], 𝒟[:z]), action(actor(π), vcat(𝒟[:s], 𝒟[:z]))))

function TIER_TD3_target(π, 𝒫, 𝒟, γ::Float32; i, z=𝒟[:z])
    ap, _ = exploration(𝒫[:π_smooth], vcat(𝒟[:sp], z), π_on=actor(π), i=i)
    y = 𝒟[:r] .+ γ .* (1.f0 .- 𝒟[:done]) .* min.(value(critic(π), vcat(𝒟[:sp], z), ap)...)
end

TIER_action_regularization(π, 𝒟) = Flux.mse(action(actor(π), vcat(𝒟[:s], 𝒟[:z])), 𝒟[:a])
TIER_action_value_regularization(π, 𝒟) = begin
    v = value(critic(π), vcat(𝒟[:s], 𝒟[:z]), 𝒟[:a])
    v isa Tuple && (v = v[1])
    Flux.mse(v, 𝒟[:value])
end

function TIER(;π,
               observation_model,
               S,
               ΔN,
               𝒫=(;),
               A=action_space(π),
               buffer_size=1000,
               latent_dim,
               N_experience_replay,
               N_experience_obs,
               replay_store_weight = (D)->1f0,
               bayesian_inference,
               solver=TD3,
               required_columns=Symbol[],
               c_opt=(;),
               a_opt=(;),
               obs_opt=(;),
               a_loss=TIER_TD3_actor_loss,
               c_loss=TIER_double_Q_loss(),
               target_fn=TIER_TD3_target,
               zprior=MvNormal(zeros(latent_dim), I),
               kwargs...)

    required_columns = unique([:episode_end, :weight, required_columns...])
    buffer = ExperienceBuffer(S, A, buffer_size, required_columns)
    buffer.data[:z] = zeros(Float32, latent_dim, capacity(buffer))

    # This experience buffer is for recalling behavior
    buffer_er = ExperienceBuffer(S, A, N_experience_replay, [required_columns..., :value])
    buffer_er.data[:z] = zeros(Float32, latent_dim, capacity(buffer_er))

    # this experience buffer is for learning the latent embedding
    buffer_obs = ExperienceBuffer(S, A, N_experience_obs, [required_columns..., :value])
    buffer_obs.data[:z] = zeros(Float32, latent_dim, capacity(buffer_obs))

    # Buffer used to train for latent
    obs_opt = TrainingParams(;obs_opt...)
    𝒟obs = buffer_like(buffer_obs, capacity=obs_opt.batch_size)

    𝒫 = (;buffer_er, buffer_obs, obs_opt, z_dist=Any[zprior], zs=Any[zprior], observation_model, 𝒫...)

    # Define the solver
    𝒮 = solver(;π=π,
              S=S,
              𝒫=𝒫,
              buffer=buffer,
              c_opt=(;regularizer=BatchRegularizer(buffers=[buffer_er], batch_size=128, λ=0.5f0, loss=TIER_action_value_regularization, c_opt...)),
              a_opt=(;regularizer=BatchRegularizer(buffers=[buffer_er], batch_size=128, λ=0.5f0, loss=TIER_action_regularization, a_opt...)),
              extra_buffers = [buffer_er],
              buffer_fractions = [0.5, 0.5],
              a_loss=a_loss,
              c_loss=c_loss,
              target_fn=target_fn,
              kwargs...
              )

    function TIER_cb(D; 𝒮, info=Dict())
        # update the z distribution
        𝒮.𝒫.z_dist[1] = bayesian_inference(observation_model, D, 𝒮.𝒫.z_dist[1], info=info)

        # Set the agent's best estimate and record
        zbest = mean(𝒮.𝒫.z_dist[1])
        𝒮.agent.π.z = zbest
        push!(𝒮.𝒫[:zs], deepcopy(𝒮.𝒫.z_dist[1]))

        # Fill the buffer with latent estimate, value and computed weight
        D[:z] = repeat(zbest, 1, length(D[:r]))
        D[:value] = mean(value(critic(𝒮.agent.π), vcat(D[:s], D[:z]), D[:a]))
        D[:weight] .= replay_store_weight(D)

        # Add this buffer to our experience replay and observation buffers
        push_reservoir!(buffer_er, D, weighted=true)
        push_reservoir!(buffer_obs, D)

        # Train the obs model
        for j=1:obs_opt.epochs
                rand!(𝒟obs, buffer_obs, buffer, fracs=[0.5, 0.5])
                train!(Flux.params(observation_model), (;kwargs...) -> obs_opt.loss(observation_model, 𝒟obs; kwargs...), obs_opt, info=info)
        end
    end

    𝒮.post_sample_callback = TIER_cb
    𝒮
end


import MixedModels: AbstractReMat

"""
    ExpandedReMat

Represent a random effects matrix that can't be represented in the compact
format of the standard ReMat.  The z field here is the full Z' matrix, and
linear algebra methods delegate to the (transposed?) matrix.

"""

immutable ExpandedReMat{T<:AbstractFloat,V,R} <: AbstractReMat
    f::Union{NullableCategoricalVector{V,R},CategoricalVector{V,R},PooledDataVector{V,R}}
    z::AbstractMatrix{T}
    fnm::Symbol
    cnms::Vector
end

ExpandedReMat(A::AbstractReMat) = ExpandedReMat(A.f, full(A), A.fnm, A.cnms)

Base.size(A::ExpandedReMat) = size(A.z)
Base.size(A::ExpandedReMat, i::Integer) = size(A.z, i)

Base.full(r::ExpandedReMat) = isa(r.z, AbstractSparseMatrix) ? full(r.z) : r.z
Base.sparse(r::ExpandedReMat) = isa(r.z, AbstractSparseMatrix) ? r.z : sparse(r.z)


"""
    submodule CNumbers
Provides C-style numbers, whose conversion rule is different from julia's:
* Downcast to a smaller type is simply truncating, with no bounds check. (Although C11 6.3.1.3p3  permits throwing an InexactError, this may not be expected to C programmers)
* Integers promoted to int before performing arithmetic, possibly converted back later(C11 6.3 Conversions)
* Floating-point to integer conversion is plain `trunc`
Also it exports C-style lowercase type names like `float`, `int`, or `unsigned_int`.
"""

"""
    struct CNumber{T <: Number} <: Number
Wrapper of a C number, conforming to C-style integer/floating-point number conversion/arithmetic. Only intended for internal usage.
To get the wrapped value, call `value`.
"""
struct CNumber{T <: Number} <: Number
    v::T
end

"Get the wrapped value of a CNumber."
value(x::CNumber) = x.v

"Get the wrapped type of a CNumber."
basetype(::CNumber{T}) where T = T

"Integer rank is for comparing types. Defined on julia's primitive types."
function rank end

# Aliases

for (ind, (name, type)) in [
    (:bool, Bool),
    (:char, Cchar),
    (:unsigned_char, Cuchar),
    (:short, Cshort),
    (:unsigned_short, Cushort),
    (:int, Cint),
    (:unsigned_int, Cuint),
    (:long, Clong),
    (:unsigned_long, Culong),
    (:long_long, Clonglong),
    (:unsigned_long_long, Culonglong),
    (:float, Cfloat),
    (:double, Cdouble)] |> enumerate
    @eval begin
        const $name = CNumber{$type}
        # Avoid redefinition
        if !hasmethod(rank, Tuple{Type{$type}})
            rank(::Type{$type}) = $ind ÷ 2
        end
    end
end

# Convert to normal julia types (outer constructor is not defined)
Base.convert(::Type{T}, x::CNumber) where {T <: Number} = Base.convert(T, value(x))
# Convert between integer types by truncating
Base.convert(::Type{CNumber{T}}, x::CNumber{<:Integer}) where {T <: Integer} = CNumber{T}(value(x) % T)
# Convert to floating point number by direct julian conversion
Base.convert(::Type{CNumber{T}}, x::CNumber) where {T <: AbstractFloat} = CNumber{T}(Base.convert(T, value(x)))
# Convert from floating point number to integer by truncating
Base.convert(::Type{CNumber{T}}, x::CNumber{<:AbstractFloat}) where {T <: Integer} = CNumber{T}(trunc(value(x)))
# Convert to bool is !iszero
Base.convert(::Type{Bool}, x::CNumber) = !iszero(value(x))
Base.convert(::Type{bool}, x::CNumber) = bool(Base.convert(Bool, x))

"Integers with rank smaller than int are promoted to int"
promote_integer(::Type{T}) where T = rank(T) ≥ rank(Cint) ? T : Cint

signedness(::Type{<:Signed}) = true
signedness(::Type{<:Unsigned}) = false

function c_promote(::Type{T1}, ::Type{T2}) where {T1 <: AbstractFloat,T2}
    return promote_type(T1, T2)
end

function c_promote(::Type{T1}, ::Type{T2}) where {T1,T2 <: AbstractFloat}
    return promote_type(T1, T2)
end

function c_promote(::Type{T1}, ::Type{T2}) where {T1,T2}
    T3, T4 = promote_integer(T1), promote_integer(T2)
    if !(signedness(T3) ⊻ signedness(T4))
        return rank(T3) > rank(T4) ? T3 : T4
    elseif T3 <: Unsigned
        U, S = T3, T4
    else
        U, S = T4, T3
    end

    if rank(U) ≥ rank(S)
        return U
    elseif sizeof(U) < sizeof(S)
        return S
    else
        return U
    end
end

function Base.promote_rule(::Type{CNumber{T1}}, ::Type{CNumber{T2}}) where {T1,T2}
    return CNumber{c_promote(T1, T2)}
end

# Always favor CNumber over julian nubers
function Base.promote_rule(::Type{CNumber{T1}}, ::Type{T2}) where {T1, T2}
    Base.promote_rule(CNumber{T1}, CNumber{T2})
end

# Arithmetic

for op in [:+, :-, :*, :/, :%, :&, :|, :⊻, :<<, :>>] # XOR is ⊻ in julia
    @eval function Base.$op(a::CNumber{T}, b::CNumber{T}) where T
        CNumber{T}($op(value(a), value(b)))
    end
end

for op in [:+, :-, :~]
    @eval function Base.$op(a::CNumber{T}) where T
        CNumber{T}($op(value(a)))
    end
end

# Return Bool
for op in [:(==), :!=, :<, :>, :<=, :(>=)]
    @eval function Base.$op(a::CNumber, b::CNumber)
        a, b = Base.promote(a, b)
        Base.$op(value(a), value(b))
    end
end

function Base.:!(a::CNumber)
    iszero(a)
end

"@post_inc i is equivalent to i++"
macro post_inc(i)
	quote
		local v = $(esc(i))
		$(esc(i)) += 1
		v
	end
end

"@post_dec i is equivalent to i--"
macro post_dec(i)
	quote
		local v = $(esc(i))
		$(esc(i)) -= 1
		v
	end
end


mutable struct NoCrashRewardModel <: AbstractMLRewardModel
    cost_dangerous_brake::Float64 # POSITIVE NUMBER
    reward_in_target_lane::Float64 # POSITIVE NUMBER

    brake_penalty_thresh::Float64 # (POSITIVE NUMBER) if the deceleration is greater than this cost_dangerous_brake will be accured
    target_lane::Int

#####################
    reward_in_first_lane::Float64 # POSITIVE NUMBER
    first_lane::Int
    target_point::Float64
    reward_for_control::Float64
    reward_for_collision::Float64
end

mutable struct EmergencyRewardModel <: AbstractMLRewardModel

    reward_for_control::Float64
    reward_for_collision::Float64
end

mutable struct RampRewardModel <: AbstractMLRewardModel
    cost_dangerous_brake::Float64 # POSITIVE NUMBER
    #reward_in_target_lane::Float64 # POSITIVE NUMBER
    reward_in_first_lane::Float64  # POSITIVE NUMBER
    reward_not_finish::Float64   # NEGATIVE NUMBER
    brake_penalty_thresh::Float64 # (POSITIVE NUMBER) if the deceleration is greater than this cost_dangerous_brake will be accured
    target_lane::Int
    first_lane::Int
end

#XXX temporary
#NoCrashRewardModel() = NoCrashRewardModel(100.,10.,2.5,4)
NoCrashRewardModel() = NoCrashRewardModel(100.,10.,2.5,4,1.,1,2500,10,100)
lambda(rm::NoCrashRewardModel) = rm.cost_dangerous_brake/rm.reward_in_target_lane

RampRewardModel() = RampRewardModel(100.,10.,-10000.,2.5,4,1)

mutable struct NoCrashIDMMOBILModel <: AbstractMLDynamicsModel
    nb_cars::Int
    phys_param::PhysicalParam

    # behaviors::Vector{BehaviorModel}
    # behavior_probabilities::WeightVec
    behaviors::BehaviorGenerator

    adjustment_acceleration::Float64
    lane_change_rate::Float64 # in LANES PER SECOND

    p_appear::Float64 # probability of a new car appearing if the maximum number are not on the road
    appear_clearance::Float64 # minimum clearance for a car to appear

    vel_sigma::Float64 # std of new car speed about v0

    lane_terminate::Bool # if true, terminate the simulation when the car has reached the desired lane
    brake_terminate_thresh::Float64 # terminate simulation if braking is above this (always positive)
    max_dist::Float64 # terminate simulation if distance becomes greater than this
end

#XXX temporary
function NoCrashIDMMOBILModel(nb_cars::Int,
                              pp::PhysicalParam;
                              vel_sigma = 0.5,
                              lane_terminate=false,
                              p_appear=0.5,
                              brake_terminate_thresh=Inf,
                              max_dist=Inf,
                              behaviors=DiscreteBehaviorSet(IDMMOBILBehavior[IDMMOBILBehavior(x[1],x[2],x[3],idx) for (idx,x) in
                                                 enumerate(Iterators.product(["cautious","normal","aggressive"],
                                                        [pp.v_slow+0.5;pp.v_med;pp.v_fast],
                                                        [pp.l_car]))], Weights(ones(9)))
                              )

    return NoCrashIDMMOBILModel(
        nb_cars,
        pp,
        behaviors,
        1., # adjustment accel
        1.0/(2.0*pp.dt), # lane change rate
        p_appear, # p_appear
        35.0, # appear_clearance
        vel_sigma, # vel_sigma
        lane_terminate,
        brake_terminate_thresh,
        max_dist
    )
end

const NoCrashMDP{R<:AbstractMLRewardModel} = MLMDP{MLState, MLAction, NoCrashIDMMOBILModel, R}
const NoCrashPOMDP{R<:AbstractMLRewardModel} = MLPOMDP{MLState, MLAction, MLObs, NoCrashIDMMOBILModel, R}
const SuccessMDP = NoCrashMDP{TargetLaneReward}
const SuccessPOMDP = NoCrashPOMDP{TargetLaneReward}
const RampMDP = NoCrashMDP{RampRewardModel}
const RampPOMDP = NoCrashPOMDP{RampRewardModel}

const NoCrashProblem{R<:AbstractMLRewardModel} = Union{NoCrashMDP{R}, NoCrashPOMDP{R}}
const SuccessProblem = Union{SuccessMDP, SuccessPOMDP}

# TODO issue here VVV need a different way to create observation
create_action(::NoCrashProblem) = MLAction()

# action space = {a in {accelerate,maintain,decelerate}x{left_lane_change,maintain,right_lane_change} | a is safe} U {brake}
immutable NoCrashActionSpace
    NORMAL_ACTIONS::Vector{MLAction} # all the actions except brake
    acceptable::IntSet
    brake::MLAction # this action will be EITHER braking at half the dangerous brake threshold OR the braking necessary to prevent a collision at all time in the future
    ca::MLAction #control_action
end
# TODO for performance, make this a macro?
const NB_NORMAL_ACTIONS = 9

function NoCrashActionSpace(mdp::NoCrashProblem)
    accels = (-mdp.dmodel.adjustment_acceleration, 0.0, mdp.dmodel.adjustment_acceleration)
    lane_changes = (-mdp.dmodel.lane_change_rate, 0.0, mdp.dmodel.lane_change_rate)
    NORMAL_ACTIONS = MLAction[MLAction(a,l) for (a,l) in Iterators.product(accels, lane_changes)] # this should be in the problem
    return NoCrashActionSpace(NORMAL_ACTIONS, IntSet(), MLAction(), MLAction()) # note: brake will be calculated later based on the state
end

function actions(mdp::NoCrashProblem)
    return NoCrashActionSpace(mdp)
end

actions(mdp::NoCrashProblem, s::Union{MLState, MLPhysicalState}) = actions(mdp, s, actions(mdp))

function actions(mdp::NoCrashProblem, s::Union{MLState, MLPhysicalState}, as::NoCrashActionSpace)
    acceptable = IntSet()
    for i in 1:NB_NORMAL_ACTIONS
        a = as.NORMAL_ACTIONS[i]
        ego_y = s.cars[1].y
        # prevent going off the road
        if ego_y == 1. && a.lane_change < 0. || ego_y == mdp.dmodel.phys_param.nb_lanes && a.lane_change > 0.0
            continue
        end
        if a.lane_change == 0.0
            continue
        end
        # prevent running into the person in front or to the side
        #if is_safe(mdp, s, as.NORMAL_ACTIONS[i])
        #    push!(acceptable, i)
        #end

        ###### for Emergency
        push!(acceptable, i)
    end
    #brake_acc = calc_brake_acc(mdp, s)
    #brake = MLAction(brake_acc, 0)
    brake = MLAction(-4, 0.0)
    ca = control_action_emergency(mdp,s,false)

    #ca = MLAction(ca, 0.0)  ####################
    return NoCrashActionSpace(as.NORMAL_ACTIONS, acceptable, brake, ca)
end

calc_brake_acc(mdp::NoCrashProblem{NoCrashRewardModel}, s::Union{MLState, MLPhysicalState}) = min(max_safe_acc(mdp,s), -mdp.rmodel.brake_penalty_thresh/2.0)
calc_brake_acc(mdp::NoCrashProblem{TargetLaneReward}, s::Union{MLState, MLPhysicalState}) = min(max_safe_acc(mdp, s), min(-mdp.dmodel.phys_param.brake_limit/2, -mdp.dmodel.brake_terminate_thresh/2))
calc_brake_acc(mdp::NoCrashProblem{RampRewardModel}, s::Union{MLState, MLPhysicalState}) = min(max_safe_acc(mdp,s), -mdp.rmodel.brake_penalty_thresh/2.0)

iterator(as::NoCrashActionSpace) = as
Base.start(as::NoCrashActionSpace) = 1
function Base.next(as::NoCrashActionSpace, state::Integer)
    while !(state in as.acceptable)
        if state == NB_NORMAL_ACTIONS+1
            return (as.brake, state+1)
        end
        if state > NB_NORMAL_ACTIONS+1
            return (as.ca, state+1)
        end
        state += 1
    end
    return (as.NORMAL_ACTIONS[state], state+1)
end
Base.done(as::NoCrashActionSpace, state::Integer) = state > NB_NORMAL_ACTIONS+2
Base.length(as::NoCrashActionSpace) = length(as.acceptable) + 2

function rand(rng::AbstractRNG, as::NoCrashActionSpace, a::MLAction=MLAction())
    nb_acts = length(as.acceptable)+2
    index = rand(rng,1:nb_acts)
    for (i,a) in enumerate(as) # is this efficient at all ?
        if i == index
            return a
        end
    end
end

"""
Calculate the maximum safe acceleration that will allow the car to avoid a collision if the car in front slams on its brakes
"""
function max_safe_acc(mdp::NoCrashProblem, s::Union{MLState,MLObs}, lane_change::Float64=0.0)
    dt = mdp.dmodel.phys_param.dt
    v_min = mdp.dmodel.phys_param.v_min
    l_car = mdp.dmodel.phys_param.l_car
    bp = mdp.dmodel.phys_param.brake_limit
    ego = s.cars[1]

    car_in_front = 0
    smallest_gap = Inf
    # find car immediately in front
    if length(s.cars) > 1
        for i in 2:length(s.cars)#nb_cars
            if occupation_overlap(s.cars[i].y, 0.0, ego.y, lane_change) # occupying same lane
                gap = s.cars[i].x - ego.x - l_car
                if gap >= -l_car && gap < smallest_gap
                    car_in_front = i
                    smallest_gap = gap
                end
            end
        end
        # calculate necessary acceleration
        if car_in_front == 0
            return Inf
        else
            n_brake_acc = nullable_max_safe_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
            return get(n_brake_acc, -mdp.dmodel.phys_param.brake_limit)
        end
    end
    return Inf
end

"""
Return the maximum acceleration that the car behind can have on this step so that it won't hit the car in front if it slams on its brakes
"""
function max_safe_acc(gap, v_behind, v_ahead, braking_limit, dt)
    bp = braking_limit
    v = v_behind
    vo = v_ahead
    g = gap
    # VVV see mathematica notebook
    # return - (bp*dt + 2.*v - sqrt(8.*g*bp + bp^2*dt^2 - 4.*bp*dt*v + 4.*vo^2)) / (2.*dt)
    ret = 0.0
#########################

    t = vo/bp
    ret = (g+vo*t-bp*t*t/2+bp*(t-dt)*(t-dt)/2-v*dt-v*(t-dt)) / (dt*dt/2+(t-dt)*dt)
    return ret
###########################
#=
    try
        ret = - (bp*dt + 2.*v - sqrt(8.*g*bp + bp^2*dt^2 - 4.*bp*dt*v + 4.*vo^2)) / (2.*dt)
    catch ex
        @show gap
        @show v_behind
        @show v_ahead
        @show braking_limit
        @show dt
        rethrow(ex)
    end
    return ret
=#
end

"""
Return max_safe_acc or an empty Nullable if the discriminant is negative.
"""
function nullable_max_safe_acc(gap, v_behind, v_ahead, braking_limit, dt)
    bp = braking_limit
    v = v_behind
    vo = v_ahead
    g = gap
    # VVV see mathematica notebook
    #discriminant = 8.*g*bp + bp^2*dt^2 - 4.*bp*dt*v + 4.*vo^2
    discriminant = 8.*g*bp + bp^2*dt^2 - 2.*bp*dt*v + 4.*vo^2
    if discriminant >= 0.0
        return Nullable{Float64}(- (bp*dt + 2.*v - sqrt(discriminant)) / (2.*dt))
    else
        return Nullable{Float64}()
    end
end

"""
Return get a PID acc for lane change
"""
function get_PID_acc(mdp::NoCrashProblem, s::Union{MLState,MLObs})
    dt = mdp.dmodel.phys_param.dt
    v_min = mdp.dmodel.phys_param.v_min
    l_car = mdp.dmodel.phys_param.l_car
    bp = mdp.dmodel.phys_param.brake_limit
    ego = s.cars[1]

    pp = mdp.dmodel.phys_param
    neighborhood = get_neighborhood(pp, s, 1)

    car_in_front = neighborhood[2]
    if car_in_front == 0
        return 9999
    else
        smallest_gap = s.cars[car_in_front].x - ego.x - l_car
        de_acc = Gipps_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
        RSS_acc = RSS_dis_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
        if RSS_acc < de_acc
            de_acc = RSS_acc
        end
        #ttc = 4
        #desired_dis = ttc*(ego.vel-s.cars[car_in_front].vel)
        #desired_dis = 5+ego.vel*0.5
        return de_acc
        #=
        if smallest_gap > desired_dis
            return 9999
        else
            return -0.1*(desired_dis-smallest_gap)
        end
        =#
    end
end

function get_PID_lane_change_acc(mdp::NoCrashProblem, s::Union{MLState,MLObs})
    dt = mdp.dmodel.phys_param.dt
    v_min = mdp.dmodel.phys_param.v_min
    l_car = mdp.dmodel.phys_param.l_car
    bp = mdp.dmodel.phys_param.brake_limit
    ego = s.cars[1]

    pp = mdp.dmodel.phys_param
    neighborhood = get_neighborhood(pp, s, 1)

    car_in_front = neighborhood[1]
    if car_in_front == 0
        return 1.5
    else
        smallest_gap = s.cars[car_in_front].x - ego.x - l_car
        de_acc = desired_safe_dis(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
        return de_acc
    end
end

function desired_safe_dis(gap, v_behind, v_ahead, braking_limit, dt)
    bp = braking_limit
    v = v_behind
    vo = v_ahead
    g = gap
    # VVV see mathematica notebook
    p = 0.1
    g1 = ((2*v+bp*dt)^2-bp^2*dt+2*v*bp*dt-4*vo^2)/(8*bp)
    return p*(g-g1)
end

function Human_like_acc(gap, v_behind, v_ahead, braking_limit, dt)
    bp = -braking_limit
    br = bp/4
    bf = bp/2
    v = v_behind
    vo = v_ahead
    g = gap
    g1 = v_behind*1.8
    if g>g1
        g_acc = 9999
    else
        g_acc = 0.1*(g-g1)
    end

    if g_acc<-1.5
        g_acc = -1.5
    end
    return g_acc
end

function AEB_acc(gap, v_behind, v_ahead, braking_limit, dt)
    bp = braking_limit
    v = v_behind
    vo = v_ahead
    g = gap
    # VVV see mathematica notebook
    p = 0.1
    #g1 = ((2*v+bp*dt)^2-bp^2*dt+2*v*bp*dt-4*vo^2)/(8*bp)
    #g_RSS = v*dt+1/2*1*dt^2+(v+dt*1)^2/2/bp-vo^2/2/bp
    g_AEB = v_behind*0.9
    if g<g_AEB
        return -4
    else
        return 9999
    end
end

function Gipps_acc(gap, v_behind, v_ahead, braking_limit, dt)
    bp = -braking_limit
    br = bp/4
    bf = bp/2
    v = v_behind
    vo = v_ahead
    g = gap
    # VVV see mathematica notebook
    #p = 0.1
    #g1 = ((2*v+bp*dt)^2-bp^2*dt+2*v*bp*dt-4*vo^2)/(8*bp)
    m = br^2*dt^2-br*(2*gap-v*dt-vo^2/bf)
    if m > 0
        v_d = br*dt + sqrt(m)
        g_acc = (v_d-v)/dt
    else
        g_acc = -1.5
    end
    if g_acc<-1.5
        g_acc = -1.5
    end
    return g_acc
end

function RSS_dis_acc(gap, v_behind, v_ahead, braking_limit, dt)
    bp = braking_limit
    v = v_behind
    vo = v_ahead
    g = gap
    # VVV see mathematica notebook
    p = 0.1
    #g1 = ((2*v+bp*dt)^2-bp^2*dt+2*v*bp*dt-4*vo^2)/(8*bp)
    g_RSS = v*dt+1/2*1*dt^2+(v+dt*1)^2/2/bp-vo^2/2/bp
    if g<g_RSS
        return -4
    else
        return 9999
    end
end

function Detect_RSS_Braking(mdp::NoCrashProblem, s::Union{MLState,MLObs})
    dt = mdp.dmodel.phys_param.dt
    v_min = mdp.dmodel.phys_param.v_min
    l_car = mdp.dmodel.phys_param.l_car
    bp = mdp.dmodel.phys_param.brake_limit
    ego = s.cars[1]

    pp = mdp.dmodel.phys_param
    neighborhood = get_neighborhood(pp, s, 1)

    car_in_front = neighborhood[2]
    if car_in_front > 0
        smallest_gap = s.cars[car_in_front].x - ego.x - l_car
        RSS_acc = RSS_dis_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
        if RSS_acc <-2.3
            return true
        end
    end
    return false
end

"""
Test whether, if the ego vehicle takes action a, it will always be able to slow down fast enough if the car in front slams on his brakes and won't pull in front of another car so close they can't stop
"""
function is_safe(mdp::NoCrashProblem, s::Union{MLState,MLObs}, a::MLAction)
    dt = mdp.dmodel.phys_param.dt
    if a.acc >= max_safe_acc(mdp, s, a.lane_change)
        return false
    end
    ############## check whether the lane change action is reasonable!
    if a.lane_change > 0.3
        return false
    end
    vel = s.cars[1].vel
    if vel>34 && a.acc>0
        return false
    end
    if s.x < 1500 && a.lane_change!=0.0 && isinteger(s.cars[1].y)
        return false
    end

    if s.x > 2500 && a.lane_change!=0.0 && isinteger(s.cars[1].y)
        return false
    end

    # check whether we will go into anyone else's lane so close that they might hit us or we might run into them
    if isinteger(s.cars[1].y) && a.lane_change != 0.0
        l_car = mdp.dmodel.phys_param.l_car
        for i in 2:length(s.cars)
            car = s.cars[i]
            ego = s.cars[1]
            if car.x < ego.x + l_car && occupation_overlap(ego.y, a.lane_change, car.y, 0.0)  # ego is in front of car
                # New definition of safe - the car behind can brake at max braking to avoid the ego if the ego
                # slams on his brakes
                # XXX IS THIS RIGHT??
                # I think I need a better definition of "safe" here
                gap = ego.x - car.x - l_car
                if gap <= 0.0
                    return false
                end
                n_braking_acc = nullable_max_safe_acc(gap, car.vel, ego.vel, mdp.dmodel.phys_param.brake_limit, dt)

                # if isnull(n_braking_acc) || get(n_braking_acc) < -mdp.dmodel.phys_param.brake_limit
                if isnull(n_braking_acc) || get(n_braking_acc) < max_accel(mdp.dmodel.behaviors)
                    return false
                end
            end
        end
    end
    return true
end

#XXX temp
#create_state(p::NoCrashProblem) = MLState(0.0, 0.0, Array(CarState, p.dmodel.nb_cars), nothing)
create_state(p::NoCrashProblem) = MLState(0.0, 0.0, Array{CarState}(p.dmodel.nb_cars), true, nothing)
create_observation(pomdp::Union{NoCrashPOMDP,SuccessPOMDP}) = MLObs(0.0, 0.0, Array(CarStateObs, pomdp.dmodel.nb_cars), nothing)

function generate_s(mdp::NoCrashProblem, s::MLState, a::MLAction, rng::AbstractRNG)

    @if_debug dbg_rng = copy(rng)

    sp::MLState=create_state(mdp)
    pp = mdp.dmodel.phys_param
    dt = pp.dt
    nb_cars = length(s.cars)
    resize!(sp.cars, nb_cars)
    #sp.x = 0
    sp.terminal = s.terminal

    ## Calculate deltas ##
    #====================#

    dxs = Array{Float64}(nb_cars)
    dvs = Array{Float64}(nb_cars)
    dys = Array{Float64}(nb_cars)
    lcs = Array{Float64}(nb_cars)

    # agent
    dvs[1] = a.acc*dt
    dxs[1] = s.cars[1].vel*dt + a.acc*dt^2/2.
    lcs[1] = a.lane_change
    dys[1] = a.lane_change*dt

    for i in 2:nb_cars
        neighborhood = get_neighborhood(pp, s, i)

        behavior = s.cars[i].behavior

        acc = gen_accel(behavior, mdp.dmodel, s, neighborhood, i, rng)
        dvs[i] = dt*acc
        dxs[i] = (s.cars[i].vel + dvs[i]/2.)*dt

        lcs[i] = gen_lane_change(behavior, mdp.dmodel, s, neighborhood, i, rng)
        dys[i] = lcs[i] * dt
    end

    ## Consistency checking ##
    #========================#

    # first prevent lane changes into each other
    changers = IntSet()
    for i in 1:nb_cars
        if lcs[i] != 0
            push!(changers, i)
        end
    end
    sorted_changers = sort!(collect(changers), by=i->s.cars[i].x, rev=true) # this might be slow because anonymous functions are slow
    # from front to back

    if length(sorted_changers) >= 2 #something to compare
        # iterate through pairs
        iter_state = start(sorted_changers)
        j, iter_state = next(sorted_changers, iter_state)
        while !done(sorted_changers, iter_state)
            i = j
            j, iter_state = next(sorted_changers, iter_state)
            car_i = s.cars[i] # front
            car_j = s.cars[j] # back

            # check if they are both starting to change lanes on this step
            if isinteger(car_i.y) && isinteger(car_j.y)

                # check if they are near each other lanewise
                if abs(car_i.y - car_j.y) <= 2.0

                    # make sure there is a conflict longitudinally
                    # if car_i.x - car_j.x <= pp.l_car || car_i.x + dxs[i] - car_j.x + dxs[j] <= pp.l_car
                    # if car_i.x - car_j.x <= mdp.dmodel.appear_clearance # made more conservative on 8/19
                    # if car_i.x - car_j.x <= get_idm_s_star(car_j.behavior.p_idm, car_j.vel, car_j.vel-car_i.vel) # upgraded to sstar on 8/19
                    ivp = car_i.vel + dt*dvs[i]
                    jvp = car_j.vel + dt*dvs[j]
                    ixp = car_i.x + dt*(car_i.vel + ivp)/2.0
                    jxp = car_j.x + dt*(car_j.vel + jvp)/2.0
                    n_max_acc_p = nullable_max_safe_acc(ixp-jxp-pp.l_car, jvp, ivp, pp.brake_limit,dt)
                    if ixp - jxp <= pp.l_car*0.2 || car_i.x - car_j.x <= pp.l_car*0.2 # || isnull(n_max_acc_p) || get(n_max_acc_p) < -pp.brake_limit

                        # check if they are moving towards each other
                        # if dys[i]*dys[j] < 0.0 && abs(car_i.y+dys[i] - car_j.y+dys[j]) < 2.0
                        if true # prevent lockstepping 8/19 (doesn't prevent it that well)

                            # make j stay in his lane
                            dys[j] = 0.0
                            lcs[j] = 0.0
                        end
                    end
                end
            end
        end
    end

    # second, prevent cars hitting each other due to noise
    sorted = sort!(collect(1:length(s.cars)), by=i->s.cars[i].x, rev=true)

    if length(sorted) >= 2 #something to compare
        # iterate through pairs
        iter_state = start(sorted)
        j, iter_state = next(sorted, iter_state)
        while !done(sorted, iter_state)
            i = j
            j, iter_state = next(sorted, iter_state)
            if j == 1
                continue # don't check for the ego since the ego does not have noise
            end
            car_i = s.cars[i]
            car_j = s.cars[j]

            # check if they overlap longitudinally
            if car_j.x + dxs[j] > car_i.x + dxs[i] - pp.l_car

                # check if they will be in the same lane
                if occupation_overlap(car_i.y + dys[i], car_j.y + dys[j])
                    # warn and nudge behind
                    #=
                    @if_debug begin
                        println("Conflict because of noise: front:$i, back:$j")
                        Gallium.@enter generate_s(mdp, s, a, dbg_rng)
                    end
                    if i == 1
                        # warn("Car nudged because noise would cause a crash (ego in front).")
                        #error("Car nudged because noise would cause a crash (ego in front).")
                    else
                        # warn("Car nudged because noise would cause a crash.")
                        #error("Car nudged because noise would cause a crash.")
                    end
                    =#
                    dxs[j] = car_i.x + dxs[i] - car_j.x - 1.01*pp.l_car
                    dvs[j] = 2.0*(dxs[j]/dt - car_j.vel)
                end
            end
        end
    end

    ## Dynamics and Exits ##
    #======================#

    exits = IntSet()
    for i in 1:nb_cars
        car = s.cars[i]
        xp = car.x + (dxs[i] - dxs[1])
        yp = car.y + dys[i]
        # velp = max(min(car.vel + dvs[i],pp.v_max), pp.v_min)
        velp = max(car.vel + dvs[i], 0.0) # removed speed limits on 8/13
        # note lane change is updated above

        if dvs[i]/dt < -mdp.dmodel.brake_terminate_thresh
            sp.terminal = Nullable{Symbol}(:brake)
        end

        # check if a lane was crossed and snap back to it
        if isinteger(car.y)
            # prevent a multi-lane change in a single timestep
            if abs(yp-car.y) > 1.
                yp = car.y + sign(dys[i])
            end
        else # car.y is not an integer
            if floor(yp) >= ceil(car.y)
                yp = ceil(car.y)
            end
            if ceil(yp) <= floor(car.y)
                yp = floor(car.y)
            end
        end

        # if yp < 1.0 || yp > pp.nb_lanes
        #     @show i
        #     @show yp
        #     println("mdp = $mdp")
        #     println("s = $s")
        #     println("a = $a")
        # end
        #print("yp")
        #println(yp)
        @assert yp >= 1.0 && yp <= pp.nb_lanes

        if xp < 0.0 || xp >= pp.lane_length
            push!(exits, i)
        else
            sp.cars[i] = CarState(xp, yp, velp, lcs[i], car.behavior, s.cars[i].id)
        end
    end

    next_id = maximum([c.id for c in s.cars]) + 1

    deleteat!(sp.cars, exits)
    nb_cars -= length(exits)

    ## Generate new cars ##
    #=====================#

    if nb_cars < mdp.dmodel.nb_cars && rand(rng) <= mdp.dmodel.p_appear

        behavior = rand(rng, mdp.dmodel.behaviors)
        vel = typical_velocity(behavior) + randn(rng)*mdp.dmodel.vel_sigma

        clearances = Array{Float64}(pp.nb_lanes)
        fill!(clearances, Inf)
        closest_cars = Array{Int}(pp.nb_lanes)
        fill!(closest_cars, 0)
        sstar_margins = Array{Float64}(pp.nb_lanes)
        if vel > s.cars[1].vel
            # put at back
            # sstar is the sstar of the new guy
            for i in 1:length(s.cars)
                lowlane, highlane = occupation_lanes(s.cars[i].y, lcs[i])
                back = s.cars[i].x - pp.l_car
                if back < clearances[lowlane]
                    clearances[lowlane] = back
                    closest_cars[lowlane] = i
                end
                if back < clearances[highlane]
                    clearances[highlane] = back
                    closest_cars[highlane] = i
                end
            end
            for j in 1:pp.nb_lanes
                other = closest_cars[j]
                if other == 0
                    sstar = 0
                else
                    sstar = get_idm_s_star(behavior.p_idm, vel, vel-s.cars[other].vel)
                end
                sstar_margins[j] = clearances[j] - sstar
            end
        else
            for i in 1:length(s.cars)
                lowlane, highlane = occupation_lanes(s.cars[i].y, lcs[i])
                front = pp.lane_length - (s.cars[i].x + pp.l_car) # l_car is half the length of the old car plus half the length of the new one
                if front < clearances[lowlane]
                    clearances[lowlane] = front
                    closest_cars[lowlane] = i
                end
                if front < clearances[highlane]
                    clearances[highlane] = front
                    closest_cars[highlane] = i
                end
            end
            for j in 1:pp.nb_lanes
                other = closest_cars[j]
                if other == 0
                    sstar = 0
                else
                    sstar = get_idm_s_star(s.cars[other].behavior.p_idm,
                                           s.cars[other].vel,
                                           s.cars[other].vel-vel)
                end
                sstar_margins[j] = clearances[j] - sstar
            end
        end

        margin, lane = findmax(sstar_margins)

        if margin > 0.0
            if vel > s.cars[1].vel
                # at back
                push!(sp.cars, CarState(0.0, lane, vel, 0.0, behavior, next_id))
            else
                push!(sp.cars, CarState(pp.lane_length, lane, vel, 0.0, behavior, next_id))
            end
        end
    end

    if mdp.dmodel.lane_terminate && sp.cars[1].y == mdp.rmodel.target_lane
        sp.terminal = Nullable{Any}(:lane)
    elseif sp.x > mdp.dmodel.max_dist
        sp.terminal = Nullable{Any}(:distance)
    end

    # sp.crashed = is_crash(mdp, s, sp, warning=false)
    # sp.crashed = false

    @assert sp.cars[1].x == s.cars[1].x # ego should not move
    sp.x = s.x + sp.cars[1].vel*0.75
    #ca = control_action_adapt(mdp,s)
    ca = control_action_emergency(mdp,s,false)
#=
    if s.Control_Signal && (a == ca)
       sp.Control_Signal = true
    else
       sp.Control_Signal = false
    end
=#

    if a == ca && isinteger(sp.cars[1].y)
        sp.Control_Signal = true
    else
        sp.Control_Signal = false
    end

    if Detect_RSS_Braking(mdp,s)
        sp.t = 1
    else
        sp.t = 0
    end

#=
    if s.x > 2500 && isinteger(s.cars[1].y) && s.t == 0.0
        sp.Control_Signal = true
        sp.t = 1.0
    end
=#

    #if s.cars[1].y == 4.0 && s.t == 0.0
    #    sp.Control_Signal = true
    #    sp.t = 1.0
    #end
    return sp
end

function control_action(mdp::NoCrashProblem, s::Union{MLState, MLPhysicalState})
     vel = s.cars[1].vel
     acc = 0.0
     if vel < 26
         acc = 0.5
     end
     if vel > 27
         acc = -0.5
     end

     if acc >= max_safe_acc(mdp, s, 0.0)
         acc = max_safe_acc(mdp, s, 0.0)-0.1
     end
     lc = 0.0
     if is_safe(mdp,s,MLAction(acc,-mdp.dmodel.lane_change_rate)) && (s.cars[1].y != 1.0) && (s.x>500) && (s.x<2500)
         lc = -mdp.dmodel.lane_change_rate
     end
#=
     if is_safe(mdp,s,MLAction(acc,mdp.dmodel.lane_change_rate)) && (s.cars[1].y > 3.2) && (s.x>1200) && (s.x<2500)
         lc = -mdp.dmodel.lane_change_rate
     end
     if is_safe(mdp,s,MLAction(acc,mdp.dmodel.lane_change_rate)) && (s.cars[1].y > 2.2) && (s.x>2000) && (s.x<2500)
         lc = -mdp.dmodel.lane_change_rate
     end
     if is_safe(mdp,s,MLAction(acc,mdp.dmodel.lane_change_rate)) && (s.cars[1].y > 1.2) && (s.x>2250) && (s.x<2500)
         lc = -mdp.dmodel.lane_change_rate
     end
=#
     return MLAction(acc,lc)
end

function control_action_adapt2(mdp::NoCrashProblem, s::Union{MLState, MLPhysicalState})
     vel = s.cars[1].vel
     acc = 0.0
     if vel < 27
         acc = 0.5
     end
     if vel > 28
         acc = -0.5
     end
     if acc >= max_safe_acc(mdp, s, 0.0)
         acc = max_safe_acc(mdp, s, 0.0)-0.1
     end
     lc = 0.0
     if (s.cars[1].y != 1.0) && (s.x>1500) && (s.x<2500)
         if is_safe(mdp,s,MLAction(acc,-mdp.dmodel.lane_change_rate))
            lc = -mdp.dmodel.lane_change_rate
        elseif vel>22.3 && vel<31
           acc = -0.5
         end
     end
     return MLAction(acc,lc)
end

function control_action_adapt(mdp::NoCrashProblem, s::Union{MLState, MLPhysicalState})
     vel = s.cars[1].vel
     acc = 0.0
     if vel < 30
         acc = 0.5
     end
     if vel > 31
         acc = -0.5
     end
     if acc >= max_safe_acc(mdp, s, 0.0)
         acc = max_safe_acc(mdp, s, 0.0)-0.1
     end
     lc = 0.0
     pp = mdp.dmodel.phys_param
     neighborhood = get_neighborhood(pp, s, 1)
     PID_acc = get_PID_lane_change_acc(mdp,s)
     if (s.cars[1].y != 1.0) && (s.x>1500) && (s.x<2500)

         if is_safe(mdp,s,MLAction(acc,-mdp.dmodel.lane_change_rate))
            lc = -mdp.dmodel.lane_change_rate
        elseif  PID_acc < max_safe_acc(mdp, s, -mdp.dmodel.lane_change_rate) && vel<33
            acc = PID_acc
        #elseif  max_safe_acc(mdp, s, -mdp.dmodel.lane_change_rate) < 0.5 && max_safe_acc(mdp, s, 0.0)>0.5 && vel<33
    #        acc = -0.5
    #    elseif (neighborhood[1] !=0) && (neighborhood[4] != 0)
        elseif vel>22.3
            acc = -2
         end
     end
     return MLAction(acc,lc)
end

function control_action_emergency(mdp::NoCrashProblem, s::Union{MLState, MLPhysicalState}, RSS_signal::Bool)
     vel = s.cars[1].vel
     acc = 0.0
     des_vel = 19.5
     acc = (des_vel-vel)
     if acc>1.5
         acc = 1.5
     end
     if acc<-1.5
         acc = -1.5
     end
##########################
     dt = mdp.dmodel.phys_param.dt
     v_min = mdp.dmodel.phys_param.v_min
     l_car = mdp.dmodel.phys_param.l_car
     bp = mdp.dmodel.phys_param.brake_limit
     ego = s.cars[1]

     pp = mdp.dmodel.phys_param
     neighborhood = get_neighborhood(pp, s, 1)

     car_in_front = neighborhood[2]

     if car_in_front > 0
         smallest_gap = s.cars[car_in_front].x - ego.x - l_car
         #de_acc = Gipps_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
         de_acc = Human_like_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
         #RSS_acc = RSS_dis_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
         g_AEB = AEB_acc(smallest_gap, ego.vel, s.cars[car_in_front].vel, bp, dt)
#=
         if RSS_signal && RSS_acc < de_acc
             de_acc = RSS_acc
         end
=#
         if RSS_signal && g_AEB < de_acc
             de_acc = g_AEB
         end

         if de_acc < acc
             acc = de_acc
         end
     end

     if acc<-4
         acc = -4
     end

##################################

     lc = 0.0
#=
     if is_safe(mdp,s,MLAction(acc,-mdp.dmodel.lane_change_rate)) && (s.cars[1].y >2.2)
         lc = -mdp.dmodel.lane_change_rate
     end

     if is_safe(mdp,s,MLAction(acc,mdp.dmodel.lane_change_rate)) && (s.cars[1].y <1.8)
         lc = mdp.dmodel.lane_change_rate
     end
=#
     return MLAction(acc,lc)
end

function control_action_emergency(mdp::NoCrashProblem, s::Union{MLState, MLPhysicalState})
    return control_action_emergency(mdp,s,false)
end

function reward(mdp::NoCrashProblem{NoCrashRewardModel}, s::MLState, a::MLAction, sp::MLState)
    r = 0.0
#=
    if sp.cars[1].y == mdp.rmodel.target_lane
        r += mdp.rmodel.reward_in_target_lane
    end

###############################################

    if sp.cars[1].vel < 20
        r -= 10*(20-sp.cars[1].vel)
    end

    if s.Control_Signal
        r += mdp.rmodel.reward_for_control
    end

    if (s.x > mdp.rmodel.target_point) && (sp.cars[1].y != mdp.rmodel.target_lane)
           r -= mdp.rmodel.reward_in_first_lane*10
    end

    if (s.x > mdp.rmodel.target_point) && (sp.cars[1].y == mdp.rmodel.target_lane)
           r += mdp.rmodel.reward_in_first_lane
    end
#################################################

=#

###############################
    if s.Control_Signal
        r += 0
    else
        r -= mdp.rmodel.reward_for_control
    end

    if judge_collision(mdp,s)
        r -=mdp.rmodel.reward_for_collision
    end

    if a.acc < -mdp.rmodel.brake_penalty_thresh
        r -= mdp.rmodel.cost_dangerous_brake
    end
###############################

    return r
end

function reward(mdp::NoCrashProblem{RampRewardModel}, s::MLState, ::MLAction, sp::MLState)
    r = 0.0
    if sp.cars[1].y == mdp.rmodel.first_lane
        r += mdp.rmodel.reward_in_target_lane
    end
    nb_brakes = detect_braking(mdp, s, sp)
    r -= mdp.rmodel.cost_dangerous_brake*nb_brakes
    return r
end

"""
Return the number of braking actions that occured during this state transition
"""
function detect_braking(mdp::NoCrashProblem, s::MLState, sp::MLState, threshold::Float64)
    nb_brakes = 0
    nb_leaving = 0
    dt = mdp.dmodel.phys_param.dt
    for (i,c) in enumerate(s.cars)
        if length(sp.cars) >= i-nb_leaving
            cp = sp.cars[i-nb_leaving]
        else
            break
        end
        if cp.id != c.id
            nb_leaving += 1
            continue
        else
            acc = (cp.vel-c.vel)/dt
            if acc < -threshold
                nb_brakes += 1
            end
        end
    end
    # @assert nb_leaving <= 5 # sanity check - can remove this if it is violated as long as it doesn't happen all the time
    return nb_brakes
end

detect_braking(mdp::NoCrashProblem, s::MLState, sp::MLState) = detect_braking(mdp, s, sp, mdp.rmodel.brake_penalty_thresh)
detect_braking(mdp::SuccessProblem, s::MLState, sp::MLState) = detect_braking(mdp, s, sp, mdp.dmodel.brake_terminate_thresh)

"""
Return the ids of cars that brake during this state transition
"""
function braking_ids(mdp::NoCrashProblem, s::MLState, sp::MLState, threshold=mdp.rmodel.brake_penalty_thresh)
    braking = Int[]
    nb_leaving = 0
    dt = mdp.dmodel.phys_param.dt
    for (i,c) in enumerate(s.cars)
        if length(sp.cars) >= i-nb_leaving
            cp = sp.cars[i-nb_leaving]
        else
            break
        end
        if cp.id != c.id
            nb_leaving += 1
            continue
        else
            acc = (cp.vel-c.vel)/dt
            if acc < -threshold
                push!(braking, c.id)
            end
        end
    end
    # @assert nb_leaving <= 5 # sanity check - can remove this if it is violated as long as it doesn't happen all the time
    return braking
end

"""
Return the maximum braking (a positive number) by any car during the transition between s an sp
"""
function max_braking(mdp::NoCrashProblem, s::MLState, sp::MLState)
    nb_leaving = 0
    dt = mdp.dmodel.phys_param.dt
    min_acc = 0.0
    for (i,c) in enumerate(s.cars)
        if length(sp.cars) >= i-nb_leaving
            cp = sp.cars[i-nb_leaving]
        else
            break
        end
        if cp.id != c.id
            nb_leaving += 1
            continue
        else
            acc = (cp.vel-c.vel)/dt
            if acc < min_acc
                min_acc = acc
            end
        end
    end
    # @assert nb_leaving <= 5 # sanity check - can remove this if it is violated as long as it doesn't happen all the time
    return -min_acc
end

"""
Assign behaviors to a given physical state.
"""
function initial_state(mdp::NoCrashProblem, ps::MLPhysicalState, rng::AbstractRNG)
    s = MLState(ps, Array(CarState, length(ps.cars)))
    s.cars[1] = CarState(ps.cars[1], NORMAL)
    for i in 2:length(s.cars)
        behavior = rand(rng, mdp.dmodel.behaviors)
        s.cars[i] = CarState(ps.cars[i], behavior)
    end
    return s
end

function initial_state(mdp::NoCrashProblem, rng::AbstractRNG=Base.GLOBAL_RNG)
    return relaxed_initial_state(mdp, 200, rng)
end

function generate_o(mdp::NoCrashProblem, s::MLState, a::MLAction, sp::MLState)
    return MLObs(sp)
end

function generate_sor(pomdp::Union{NoCrashPOMDP, SuccessPOMDP}, s::MLState, a::MLAction, rng::AbstractRNG)
    sp, r = generate_sr(pomdp, s, a, rng)
    o = generate_o(pomdp, s, a, sp)
    return sp, o, r
end

discount(mdp::Union{MLMDP,MLPOMDP}) = mdp.discount
isterminal(mdp::Union{MLMDP,MLPOMDP},s::MLState) = !isnull(s.terminal)


using LinearAlgebra: BlasInt, checksquare
using LinearAlgebra.LAPACK: chkargsok

# po
## potrf
for (bname, fname,elty) in ((:cusolverDnSpotrf_bufferSize, :cusolverDnSpotrf, :Float32),
                            (:cusolverDnDpotrf_bufferSize, :cusolverDnDpotrf, :Float64),
                            (:cusolverDnCpotrf_bufferSize, :cusolverDnCpotrf, :ComplexF32),
                            (:cusolverDnZpotrf_bufferSize, :cusolverDnZpotrf, :ComplexF64))
    @eval begin
        function LinearAlgebra.LAPACK.potrf!(uplo::Char,
                        A::CuMatrix{$elty})
            cuuplo  = cublasfill(uplo)
            n       = checksquare(A)
            lda     = max(1, stride(A, 2))
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, cublasFillMode_t, Cint,
                          Ptr{$elty}, Cint, Ref{Cint}),
                         dense_handle(), cuuplo, n, A, lda, bufSize)

            buffer  = CuArray{$elty}(undef, bufSize[])
            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, cublasFillMode_t, Cint,
                          Ptr{$elty}, Cint, Ptr{$elty}, Cint, Ptr{Cint}),
                         dense_handle(), cuuplo, n, A, lda, buffer,
                         bufSize[], devinfo)
            info = BlasInt(_getindex(devinfo, 1))
            chkargsok(info)
            A, info
        end
    end
end

## potrs
for (fname,elty) in ((:cusolverDnSpotrs, :Float32),
                     (:cusolverDnDpotrs, :Float64),
                     (:cusolverDnCpotrs, :ComplexF32),
                     (:cusolverDnZpotrs, :ComplexF64))
    @eval begin
        function LinearAlgebra.LAPACK.potrs!(uplo::Char,
                        A::CuMatrix{$elty},
                        B::CuVecOrMat{$elty})
            cuuplo = cublasfill(uplo)
            n = checksquare(A)
            if size(B, 1) != n
                throw(DimensionMismatch("first dimension of B, $(size(B,1)), must match second dimension of A, $n"))
            end
            nrhs = size(B,2)
            lda  = max(1, stride(A, 2))
            ldb  = max(1, stride(B, 2))

            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, cublasFillMode_t, Cint, Cint,
                          Ptr{$elty}, Cint, Ptr{$elty}, Cint, Ptr{Cint}),
                         dense_handle(), cuuplo, n, nrhs, A, lda, B,
                         ldb, devinfo)
            info = _getindex(devinfo, 1)
            chkargsok(BlasInt(info))
            B
        end
    end
end

#getrf
for (bname, fname,elty) in ((:cusolverDnSgetrf_bufferSize, :cusolverDnSgetrf, :Float32),
                            (:cusolverDnDgetrf_bufferSize, :cusolverDnDgetrf, :Float64),
                            (:cusolverDnCgetrf_bufferSize, :cusolverDnCgetrf, :ComplexF32),
                            (:cusolverDnZgetrf_bufferSize, :cusolverDnZgetrf, :ComplexF64))
    @eval begin
        function getrf!(A::CuMatrix{$elty})
            m,n     = size(A)
            lda     = max(1, stride(A, 2))
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Ptr{$elty}, Cint,
                          Ref{Cint}), dense_handle(), m, n, A, lda,
                         bufSize)

            buffer  = CuArray{$elty}(undef, bufSize[])
            devipiv = CuArray{Cint}(undef, min(m,n))
            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Ptr{$elty},
                          Cint, Ptr{$elty}, Ptr{Cint}, Ptr{Cint}),
                         dense_handle(), m, n, A, lda, buffer,
                         devipiv, devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            elseif info > 0
                throw(LinearAlgebra.SingularException(info))
            end
            A, devipiv
        end
    end
end

#geqrf
for (bname, fname,elty) in ((:cusolverDnSgeqrf_bufferSize, :cusolverDnSgeqrf, :Float32),
                            (:cusolverDnDgeqrf_bufferSize, :cusolverDnDgeqrf, :Float64),
                            (:cusolverDnCgeqrf_bufferSize, :cusolverDnCgeqrf, :ComplexF32),
                            (:cusolverDnZgeqrf_bufferSize, :cusolverDnZgeqrf, :ComplexF64))
    @eval begin
        function geqrf!(A::CuMatrix{$elty})
            m, n    = size(A)
            lda     = max(1, stride(A, 2))
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)),libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Ptr{$elty}, Cint,
                          Ref{Cint}), dense_handle(), m, n, A,
                         lda, bufSize)
            buffer  = CuArray{$elty}(undef, bufSize[])
            tau  = CuArray{$elty}(undef, min(m, n))
            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)),libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Ptr{$elty},
                          Cint, Ptr{$elty}, Ptr{$elty}, Cint, Ptr{Cint}),
                         dense_handle(), m, n, A, lda, tau, buffer,
                         bufSize[], devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            end
            A, tau
        end
    end
end

#sytrf
for (bname, fname,elty) in ((:cusolverDnSsytrf_bufferSize, :cusolverDnSsytrf, :Float32),
                            (:cusolverDnDsytrf_bufferSize, :cusolverDnDsytrf, :Float64),
                            (:cusolverDnCsytrf_bufferSize, :cusolverDnCsytrf, :ComplexF32),
                            (:cusolverDnZsytrf_bufferSize, :cusolverDnZsytrf, :ComplexF64))
    @eval begin
        function sytrf!(uplo::Char,
                        A::CuMatrix{$elty})
            cuuplo = cublasfill(uplo)
            n      = size(A, 1)
            if size(A,2) != n
                throw(DimensionMismatch("SymTridiagonal matrix must be square!"))
            end
            lda = max(1, stride(A, 2))
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)),libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Ptr{$elty}, Cint,
                          Ref{Cint}), dense_handle(), n, A, lda,
                         bufSize)

            buffer  = CuArray{$elty}(undef, bufSize[])
            devipiv = CuArray{Cint}(undef, n)
            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)),libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, cublasFillMode_t, Cint,
                          Ptr{$elty}, Cint, Ptr{Cint}, Ptr{$elty}, Cint,
                          Ptr{Cint}), dense_handle(), cuuplo, n, A,
                         lda, devipiv, buffer, bufSize[], devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            elseif info > 0
                throw(LinearAlgebra.SingularException(info))
            end
            A, devipiv
        end
    end
end

#getrs
for (fname,elty) in ((:cusolverDnSgetrs, :Float32),
                     (:cusolverDnDgetrs, :Float64),
                     (:cusolverDnCgetrs, :ComplexF32),
                     (:cusolverDnZgetrs, :ComplexF64))
    @eval begin
        function getrs!(trans::Char,
                        A::CuMatrix{$elty},
                        ipiv::CuVector{Cint},
                        B::CuVecOrMat{$elty})
            cutrans = cublasop(trans)
            n = size(A, 1)
            if size(A, 2) != n
                throw(DimensionMismatch("LU factored matrix A must be square!"))
            end
            if size(B, 1) != n
                throw(DimensionMismatch("first dimension of B, $(size(B,1)), must match second dimension of A, $n"))
            end
            nrhs = size(B, 2)
            lda  = max(1, stride(A, 2))
            ldb  = max(1, stride(B, 2))

            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, cublasOperation_t, Cint, Cint,
                          Ptr{$elty}, Cint, Ptr{Cint}, Ptr{$elty}, Cint,
                          Ptr{Cint}), dense_handle(), cutrans, n, nrhs,
                         A, lda, ipiv, B, ldb, devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            end
            B
        end
    end
end

#ormqr
for (bname, fname, elty) in ((:cusolverDnSormqr_bufferSize, :cusolverDnSormqr, :Float32),
                             (:cusolverDnDormqr_bufferSize, :cusolverDnDormqr, :Float64),
                             (:cusolverDnCunmqr_bufferSize, :cusolverDnCunmqr, :ComplexF32),
                             (:cusolverDnZunmqr_bufferSize, :cusolverDnZunmqr, :ComplexF64))    @eval begin
        function ormqr!(side::Char,
                        trans::Char,
                        A::CuMatrix{$elty},
                        tau::CuVector{$elty},
                        C::CuVecOrMat{$elty})
            cutrans = cublasop(trans)
            cuside  = cublasside(side)
            if side == 'L'
                m   = size(A, 1)
                ldc = size(C, 1)
                n   = size(C, 2)
                if m > ldc
                    Ctemp = CuArray{$elty}(undef, m - ldc, n) .= 0
                    C = [C; Ctemp]
                    ldc = m
                end
                lda = m
            else
                m   = size(C, 1)
                n   = size(C, 2)
                ldc = m
                lda = n
            end
            k       = length(tau)
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)),libcusolver), cusolverStatus_t,
                          (cusolverDnHandle_t, cublasSideMode_t,
                           cublasOperation_t, Cint, Cint, Cint, Ptr{$elty},
                           Cint, Ptr{$elty}, Ptr{$elty}, Cint, Ref{Cint}),
                          dense_handle(), cuside,
                          cutrans, m, n, k, A,
                          lda, tau, C, ldc, bufSize)
            buffer  = CuArray{$elty}(undef, bufSize[])
            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)),libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, cublasSideMode_t,
                          cublasOperation_t, Cint, Cint, Cint, Ptr{$elty},
                          Cint, Ptr{$elty}, Ptr{$elty}, Cint, Ptr{$elty},
                          Cint, Ptr{Cint}),
                         dense_handle(), cuside,
                         cutrans, m, n, k, A, lda, tau, C, ldc, buffer,
                         bufSize[], devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            end
            side == 'L' ? C : C[:, 1:minimum(size(A))]
        end
    end
end

#orgqr
for (bname, fname, elty) in ((:cusolverDnSorgqr_bufferSize, :cusolverDnSorgqr, :Float32),
                             (:cusolverDnDorgqr_bufferSize, :cusolverDnDorgqr, :Float64),
                             (:cusolverDnCungqr_bufferSize, :cusolverDnCungqr, :ComplexF32),
                             (:cusolverDnZungqr_bufferSize, :cusolverDnZungqr, :ComplexF64))
    @eval begin
        function orgqr!(A::CuMatrix{$elty}, tau::CuVector{$elty})
            m = size(A , 1)
            n = min(m, size(A, 2))
            lda = max(1, stride(A, 2))
            k = length(tau)
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)), libcusolver), cusolverStatus_t,
                          (cusolverDnHandle_t, Cint, Cint, Cint, Ptr{$elty}, Cint,
                           Ptr{$elty}, Ref{Cint}),
                          dense_handle(), m, n, k, A, lda, tau, bufSize)
            buffer  = CuArray{$elty}(undef, bufSize[])
            devinfo = CuArray{Cint}(undef, 1)
            @check ccall(($(string(fname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Cint, Ptr{$elty},
                          Cint, Ptr{$elty}, Ptr{$elty}, Cint, Ptr{Cint}),
                         dense_handle(), m, n, k, A,
                         lda, tau, buffer, bufSize[], devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            end
            if n < size(A, 2)
                A[:, 1:n]
            else
                A
            end
        end
    end
end

#gebrd
for (bname, fname, elty, relty) in ((:cusolverDnSgebrd_bufferSize, :cusolverDnSgebrd, :Float32, :Float32),
                                    (:cusolverDnDgebrd_bufferSize, :cusolverDnDgebrd, :Float64, :Float64),
                                    (:cusolverDnCgebrd_bufferSize, :cusolverDnCgebrd, :ComplexF32, :Float32),
                                    (:cusolverDnZgebrd_bufferSize, :cusolverDnZgebrd, :ComplexF64, :Float64))
    @eval begin
        function gebrd!(A::CuMatrix{$elty})
            m, n    = size(A)
            lda     = max(1, stride(A, 2))
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Ref{Cint}),
                        dense_handle(), m, n, bufSize)
            buffer  = CuArray{$elty}(undef, bufSize[])
            devinfo = CuArray{Cint}(undef, 1)
            k       = min(m, n)
            D       = CuArray{$relty}(undef, k)
            E       = CuArray{$relty}(undef, k) .= 0
            TAUQ    = CuArray{$elty}(undef, k)
            TAUP    = CuArray{$elty}(undef, k)
            @check ccall(($(string(fname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Ptr{$elty},
                          Cint, Ptr{$relty}, Ptr{$relty}, Ptr{$elty},
                          Ptr{$elty}, Ptr{$elty}, Cint, Ptr{Cint}),
                         dense_handle(), m, n, A, lda, D, E, TAUQ,
                         TAUP, buffer, bufSize[], devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            end
            A, D, E, TAUQ, TAUP
        end
    end
end

for (bname, fname, elty, relty) in ((:cusolverDnSgesvd_bufferSize, :cusolverDnSgesvd, :Float32, :Float32),
                                    (:cusolverDnDgesvd_bufferSize, :cusolverDnDgesvd, :Float64, :Float64),
                                    (:cusolverDnCgesvd_bufferSize, :cusolverDnCgesvd, :ComplexF32, :Float32),
                                    (:cusolverDnZgesvd_bufferSize, :cusolverDnZgesvd, :ComplexF64, :Float64))
    @eval begin
        function gesvd!(jobu::Char,
                        jobvt::Char,
                        A::CuMatrix{$elty})
            m,n     = size(A)
            lda     = max(1, stride(A, 2))
            bufSize = Ref{Cint}(0)
            @check ccall(($(string(bname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cint, Cint, Ref{Cint}),
                        dense_handle(), m, n, bufSize)

            buffer  = CuArray{$elty}(undef, bufSize[])
            rbuffer = CuArray{$relty}(undef, 5 * min(m, n))
            devinfo = CuArray{Cint}(undef, 1)
            U       = CuArray{$elty}(undef, m, m)
            ldu     = max(1, stride(U, 2))
            S       = CuArray{$relty}(undef, min(m, n))
            Vt      = CuArray{$elty}(undef, n, n)
            ldvt    = max(1, stride(Vt, 2))
            @check ccall(($(string(fname)), libcusolver), cusolverStatus_t,
                         (cusolverDnHandle_t, Cuchar, Cuchar, Cint,
                          Cint, Ptr{$elty}, Cint, Ptr{$relty}, Ptr{$elty},
                          Cint, Ptr{$elty}, Cint, Ptr{$elty}, Cint,
                          Ptr{$relty}, Ptr{Cint}), dense_handle(),
                         jobu, jobvt, m, n, A, lda, S, U, ldu, Vt, ldvt,
                         buffer, bufSize[], rbuffer, devinfo)
            info = _getindex(devinfo, 1)
            if info < 0
                throw(ArgumentError("The $(info)th parameter is wrong"))
            end
            U, S, Vt
        end
    end
end