###############################################################################
#
#   fmpq_rel_series.jl : Power series over flint fmpq rationals
#
###############################################################################

export fmpq_rel_series, FmpqRelSeriesRing

###############################################################################
#
#   Data type and parent object methods
#
###############################################################################

function O(a::fmpq_rel_series)
   val = pol_length(a) + valuation(a) - 1
   val < 0 && throw(DomainError())
   z = fmpq_rel_series(Array{fmpq}(0), 0, val, val)
   z.parent = parent(a)
   return z
end

elem_type(::Type{FmpqRelSeriesRing}) = fmpq_rel_series

parent_type(::Type{fmpq_rel_series}) = FmpqRelSeriesRing

base_ring(R::FmpqRelSeriesRing) = R.base_ring

var(a::FmpqRelSeriesRing) = a.S

###############################################################################
#
#   Basic manipulation
#
###############################################################################

max_precision(R::FmpqRelSeriesRing) = R.prec_max

function normalise(a::fmpq_rel_series, len::Int)
   if len > 0
      c = fmpq()
      ccall((:fmpq_poly_get_coeff_fmpq, :libflint), Void,
         (Ref{fmpq}, Ref{fmpq_rel_series}, Int), c, a, len - 1)
   end
   while len > 0 && iszero(c)
      len -= 1
      if len > 0
         ccall((:fmpq_poly_get_coeff_fmpq, :libflint), Void,
            (Ref{fmpq}, Ref{fmpq_rel_series}, Int), c, a, len - 1)
      end
   end
   return len
end

function pol_length(x::fmpq_rel_series)
   return ccall((:fmpq_poly_length, :libflint), Int, (Ref{fmpq_rel_series},), x)
end

precision(x::fmpq_rel_series) = x.prec

function polcoeff(x::fmpq_rel_series, n::Int)
   if n < 0
      return fmpq(0)
   end
   z = fmpq()
   ccall((:fmpq_poly_get_coeff_fmpq, :libflint), Void,
         (Ref{fmpq}, Ref{fmpq_rel_series}, Int), z, x, n)
   return z
end

zero(R::FmpqRelSeriesRing) = R(0)

one(R::FmpqRelSeriesRing) = R(1)

function gen(R::FmpqRelSeriesRing)
   z = fmpq_rel_series([fmpq(1)], 1, max_precision(R) + 1, 1)
   z.parent = R
   return z
end

function deepcopy_internal(a::fmpq_rel_series, dict::ObjectIdDict)
   z = fmpq_rel_series(a)
   z.prec = a.prec
   z.val = a.val
   z.parent = parent(a)
   return z
end

function renormalize!(z::fmpq_rel_series)
   i = 0
   zlen = pol_length(z)
   zval = valuation(z)
   zprec = precision(z)
   while i < zlen && iszero(polcoeff(z, i))
      i += 1
   end
   z.prec = zprec
   if i == zlen
      z.val = zprec
   else
      z.val = zval + i
      ccall((:fmpq_poly_shift_right, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int), z, z, i)
   end
   return nothing
end

###############################################################################
#
#   String I/O
#
###############################################################################

function show(io::IO, a::FmpqRelSeriesRing)
   print(io, "Univariate power series ring in ", var(a), " over ")
   show(io, base_ring(a))
end

show_minus_one(::Type{fmpq_rel_series}) = show_minus_one(fmpq)

###############################################################################
#
#   Unary operators
#
###############################################################################

function -(x::fmpq_rel_series)
   z = parent(x)()
   ccall((:fmpq_poly_neg, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}),
               z, x)
   z.prec = x.prec
   z.val = x.val
   return z
end

###############################################################################
#
#   Binary operators
#
###############################################################################

function +(a::fmpq_rel_series, b::fmpq_rel_series)
   check_parent(a, b)
   lena = pol_length(a)
   lenb = pol_length(b)
   prec = min(a.prec, b.prec)
   val = min(a.val, b.val)
   lena = min(lena, prec - a.val)
   lenb = min(lenb, prec - b.val)
   z = parent(a)()
   if a.val < b.val
      lenz = max(lena, lenb + b.val - a.val)
      ccall((:fmpq_poly_set_trunc, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, b, max(0, lenz - b.val + a.val))
      ccall((:fmpq_poly_shift_left, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, z, b.val - a.val)
      ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a, lenz)
   elseif b.val < a.val
      lenz = max(lena + a.val - b.val, lenb)
      ccall((:fmpq_poly_set_trunc, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, a, max(0, lenz - a.val + b.val))
      ccall((:fmpq_poly_shift_left, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, z, a.val - b.val)
      ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, b, lenz)
   else
      lenz = max(lena, lenb)
      ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, b, lenz)
   end
   z.prec = prec
   z.val = val
   renormalize!(z)
   return z
end

function -(a::fmpq_rel_series, b::fmpq_rel_series)
   check_parent(a, b)
   lena = pol_length(a)
   lenb = pol_length(b)
   prec = min(a.prec, b.prec)
   val = min(a.val, b.val)
   lena = min(lena, prec - a.val)
   lenb = min(lenb, prec - b.val)
   lenz = max(lena, lenb)
   z = parent(a)()
   if a.val < b.val
      lenz = max(lena, lenb + b.val - a.val)
      ccall((:fmpq_poly_set_trunc, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, b, max(0, lenz - b.val + a.val))
      ccall((:fmpq_poly_shift_left, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, z, b.val - a.val)
      ccall((:fmpq_poly_neg, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}), z, z)
      ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a, lenz)
   elseif b.val < a.val
      lenz = max(lena + a.val - b.val, lenb)
      ccall((:fmpq_poly_set_trunc, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, a, max(0, lenz - a.val + b.val))
      ccall((:fmpq_poly_shift_left, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, z, a.val - b.val)
      ccall((:fmpq_poly_sub_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, b, lenz)
   else
      lenz = max(lena, lenb)
      ccall((:fmpq_poly_sub_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, b, lenz)
   end
   z.prec = prec
   z.val = val
   renormalize!(z)
   return z
end

function *(a::fmpq_rel_series, b::fmpq_rel_series)
   check_parent(a, b)
   lena = pol_length(a)
   lenb = pol_length(b)
   aval = valuation(a)
   bval = valuation(b)
   prec = min(a.prec - aval, b.prec - bval)
   lena = min(lena, prec)
   lenb = min(lenb, prec)
   z = parent(a)()
   z.val = a.val + b.val
   z.prec = prec + z.val
   if lena == 0 || lenb == 0
      return z
   end
   lenz = min(lena + lenb - 1, prec)
   ccall((:fmpq_poly_mullow, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, b, lenz)
   return z
end

###############################################################################
#
#   Ad hoc binary operators
#
###############################################################################

function *(x::Int, y::fmpq_rel_series)
   z = parent(y)()
   z.prec = y.prec
   z.val = y.val
   ccall((:fmpq_poly_scalar_mul_si, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, y, x)
   return z
end

*(x::fmpq_rel_series, y::Int) = y * x

function *(x::fmpz, y::fmpq_rel_series)
   z = parent(y)()
   z.prec = y.prec
   z.val = y.val
   ccall((:fmpq_poly_scalar_mul_fmpz, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpz}),
               z, y, x)
   return z
end

function *(x::fmpq, y::fmpq_rel_series)
   z = parent(y)()
   z.prec = y.prec
   z.val = y.val
   ccall((:fmpq_poly_scalar_mul_fmpq, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq}),
               z, y, x)
   return z
end

*(x::fmpq_rel_series, y::fmpz) = y * x

*(x::fmpq_rel_series, y::fmpq) = y * x

*(x::fmpq_rel_series, y::Integer) = x * fmpz(y)

*(x::Integer, y::fmpq_rel_series) = fmpz(x) * y

*(x::fmpq_rel_series, y::Rational) = x * fmpq(y)

*(x::Rational, y::fmpq_rel_series) = fmpq(x) * y

+(x::fmpq_rel_series, y::Integer) = x + fmpz(y)

+(x::Integer, y::fmpq_rel_series) = fmpz(x) + y

+(x::fmpq_rel_series, y::Rational) = x + fmpq(y)

+(x::Rational, y::fmpq_rel_series) = fmpq(x) + y

-(x::fmpq_rel_series, y::Integer) = x - fmpz(y)

-(x::Integer, y::fmpq_rel_series) = fmpz(x) - y

-(x::fmpq_rel_series, y::Rational) = x - fmpq(y)

-(x::Rational, y::fmpq_rel_series) = fmpq(x) - y

###############################################################################
#
#   Shifting
#
###############################################################################

function shift_left(x::fmpq_rel_series, len::Int)
   len < 0 && throw(DomainError())
   xlen = pol_length(x)
   z = fmpq_rel_series(x)
   z.prec = x.prec + len
   z.val = x.val + len
   z.parent = parent(x)
   return z
end

function shift_right(x::fmpq_rel_series, len::Int)
   len < 0 && throw(DomainError())
   xlen = pol_length(x)
   xval = valuation(x)
   z = parent(x)()
   if len >= xlen + xval
      z.prec = max(0, x.prec - len)
      z.val = max(0, x.prec - len)
   else
      z.prec = max(0, x.prec - len)
      z.val = max(0, xval - len)
      zlen = min(xlen + xval - len, xlen)
      ccall((:fmpq_poly_shift_right, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, x, xlen - zlen)
      renormalize!(z)
   end
   return z
end

###############################################################################
#
#   Truncation
#
###############################################################################

function truncate(x::fmpq_rel_series, prec::Int)
   prec < 0 && throw(DomainError())
   xlen = pol_length(x)
   xprec = precision(x)
   xval = valuation(x)
   if xprec + xval <= prec
      return x
   end
   z = parent(x)()
   z.prec = prec
   if prec <= xval
      z = parent(x)()
      z.val = prec
      z.prec = prec
   else
      z.val = xval
      ccall((:fmpq_poly_set_trunc, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, x, min(prec - xval, xlen))
   end
   return z
end

###############################################################################
#
#   Powering
#
###############################################################################

function ^(a::fmpq_rel_series, b::Int)
   b < 0 && throw(DomainError())
   if isgen(a)
      z = parent(a)()
      z = setcoeff!(z, 0, fmpq(1))
      z.prec = a.prec + b - 1
      z.val = b
   elseif pol_length(a) == 0
      z = parent(a)()
      z.prec = b*valuation(a)
      z.val = b*valuation(a)
   elseif pol_length(a) == 1
      return parent(a)([polcoeff(a, 0)^b], 1,
                           (b - 1)*valuation(a) + precision(a), b*valuation(a))
   elseif b == 0
      return one(parent(a))
   else
      bit = ~((~UInt(0)) >> 1)
      while (UInt(bit) & b) == 0
         bit >>= 1
      end
      z = a
      bit >>= 1
      while bit != 0
         z = z*z
         if (UInt(bit) & b) != 0
            z *= a
         end
         bit >>= 1
      end
   end
   return z
end

###############################################################################
#
#   Comparison
#
###############################################################################

function ==(x::fmpq_rel_series, y::fmpq_rel_series)
   check_parent(x, y)
   prec = min(x.prec, y.prec)
   if prec <= x.val && prec <= y.val
      return true
   end
   if x.val != y.val
      return false
   end
   xlen = normalise(x, min(pol_length(x), prec - x.val))
   ylen = normalise(y, min(pol_length(y), prec - y.val))
   if xlen != ylen
      return false
   end
   return Bool(ccall((:fmpq_poly_equal_trunc, :libflint), Cint,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               x, y, xlen))
end

function isequal(x::fmpq_rel_series, y::fmpq_rel_series)
   if parent(x) != parent(y)
      return false
   end
   if x.prec != y.prec || x.val != y.val || pol_length(x) != pol_length(y)
      return false
   end
   return Bool(ccall((:fmpq_poly_equal, :libflint), Cint,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               x, y, pol_length(x)))
end

###############################################################################
#
#   Ad hoc comparisons
#
###############################################################################

function ==(x::fmpq_rel_series, y::fmpq)
   if precision(x) == 0
      return true
   elseif pol_length(x) > 1
      return false
   elseif pol_length(x) == 1
      if x.val == 0
         z = fmpq()
         ccall((:fmpq_poly_get_coeff_fmpq, :libflint), Void,
                       (Ref{fmpq}, Ref{fmpq_rel_series}, Int), z, x, 0)
         return ccall((:fmpq_equal, :libflint), Bool,
               (Ref{fmpq}, Ref{fmpq}, Int), z, y, 0)
      else
         return false
      end
   else
      return iszero(y)
   end
end

function ==(x::fmpq_rel_series, y::fmpz)
   if precision(x) == 0
      return true
   elseif pol_length(x) > 1
      return false
   elseif pol_length(x) == 1
      if x.val == 0
         z = fmpq()
         ccall((:fmpq_poly_get_coeff_fmpq, :libflint), Void,
                       (Ref{fmpq}, Ref{fmpq_rel_series}, Int), z, x, 0)
         return isone(denominator(z)) && ccall((:fmpz_equal, :libflint), Bool,
               (Ref{fmpz}, Ref{fmpz}, Int), numerator(z), y, 0)
      else
         return false
      end
   else
      return iszero(y)
   end
end

==(x::fmpz, y::fmpq_rel_series) = y == x

==(x::fmpq, y::fmpq_rel_series) = y == x

==(x::fmpq_rel_series, y::Integer) = x == fmpz(y)

==(x::Integer, y::fmpq_rel_series) = y == x

==(x::fmpq_rel_series, y::Rational{T}) where T <: Union{Int, BigInt} = x == fmpq(y)

==(x::Rational{T}, y::fmpq_rel_series) where T <: Union{Int, BigInt} = y == x

###############################################################################
#
#   Exact division
#
###############################################################################

function divexact(x::fmpq_rel_series, y::fmpq_rel_series)
   check_parent(x, y)
   iszero(y) && throw(DivideError())
   yval = valuation(y)
   xval = valuation(x)
   if yval != 0
      if xval >= yval
         x = shift_right(x, yval)
         y = shift_right(y, yval)
      end
   end
   !isunit(y) && error("Unable to invert power series")
   prec = min(x.prec - x.val, y.prec - y.val)
   z = parent(x)()
   z.val = xval - yval
   z.prec = prec + z.val
   if prec != 0
      ccall((:fmpq_poly_div_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, x, y, prec)
   end
   return z
end

###############################################################################
#
#   Ad hoc exact division
#
###############################################################################

function divexact(x::fmpq_rel_series, y::Int)
   y == 0 && throw(DivideError())
   z = parent(x)()
   z.prec = x.prec
   z.val = x.val
   ccall((:fmpq_poly_scalar_divexact_si, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, x, y)
   return z
end

function divexact(x::fmpq_rel_series, y::fmpz)
   iszero(y) && throw(DivideError())
   z = parent(x)()
   z.prec = x.prec
   z.prec = x.prec
   z.val = x.val
   ccall((:fmpq_poly_scalar_divexact_fmpz, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpz}),
               z, x, y)
   return z
end

function divexact(x::fmpq_rel_series, y::fmpq)
   iszero(y) && throw(DivideError())
   z = parent(x)()
   z.prec = x.prec
   z.prec = x.prec
   z.val = x.val
   ccall((:fmpq_poly_scalar_div_fmpq, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq}),
               z, x, y)
   return z
end

divexact(x::fmpq_rel_series, y::Integer) = divexact(x, fmpz(y))

divexact(x::fmpq_rel_series, y::Rational{T}) where T <: Union{Int, BigInt} = divexact(x, fmpq(y))

###############################################################################
#
#   Inversion
#
###############################################################################

function inv(a::fmpq_rel_series)
   iszero(a) && throw(DivideError())
   !isunit(a) && error("Unable to invert power series")
   ainv = parent(a)()
   ainv.prec = a.prec
   ainv.val = 0
   ccall((:fmpq_poly_inv_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               ainv, a, a.prec)
   return ainv
end

###############################################################################
#
#   Special functions
#
###############################################################################

function Base.exp(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in exp")
   if pol_length(a) + valuation(a) == 0 || a.prec == 1
      return parent(a)([fmpq(1)], 1, a.prec, 0)
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_exp_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   log(a::fmpq_rel_series)
> Return log$(a)$. Requires the constant term to be one.
"""
function log(a::fmpq_rel_series)
   (a.val != 0 || coeff(a, 0) != 1) && error("Constant term not one in log")
   if pol_length(a) + valuation(a) == 1 || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_log_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.prec)
   renormalize!(z)
   return z
end

doc"""
   tan(a::fmpq_rel_series)
> Return tan$(a)$. Requires a zero constant term.
"""
function tan(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in tan")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_tan_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   tanh(a::fmpq_rel_series)
> Return tanh$(a)$. Requires a zero constant term.
"""
function tanh(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in tanh")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_tanh_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   sin(a::fmpq_rel_series)
> Return sin$(a)$. Requires a zero constant term.
"""
function sin(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in sin")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_truncate, :libflint), Void,
                (Ref{fmpq_rel_series}, Int),
               z, a.prec)
   ccall((:fmpq_poly_sin_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   sinh(a::fmpq_rel_series)
> Return sinh$(a)$. Requires a zero constant term.
"""
function sinh(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in sinh")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_sinh_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   cos(a::fmpq_rel_series)
> Return cos$(a)$. Requires a zero constant term.
"""
function cos(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in cos")
   if pol_length(a) + valuation(a) == 0 || a.prec == 1
      return one(parent(a))
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_truncate, :libflint), Void,
                (Ref{fmpq_rel_series}, Int),
               z, a.prec)
   ccall((:fmpq_poly_cos_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   cosh(a::fmpq_rel_series)
> Return cosh$(a)$. Requires a zero constant term.
"""
function cosh(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in cosh")
   if pol_length(a) + valuation(a) == 0 || a.prec == 1
      return one(parent(a))
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_cosh_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   asin(a::fmpq_rel_series)
> Return asin$(a)$. Requires a zero constant term.
"""
function asin(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in asin")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_asin_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   asinh(a::fmpq_rel_series)
> Return asinh$(a)$. Requires a zero constant term.
"""
function asinh(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in asinh")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_asinh_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   atan(a::fmpq_rel_series)
> Return atan$(a)$. Requires a zero constant term.
"""
function atan(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in atan")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_atan_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   atanh(a::fmpq_rel_series)
> Return atanh$(a)$. Requires a zero constant term.
"""
function atanh(a::fmpq_rel_series)
   (a.val == 0 && pol_length(a) != 0) && error("Constant term not zero in atanh")
   if iszero(a) || a.prec < 2
      return parent(a)()
   end
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_shift_left, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.val)
   ccall((:fmpq_poly_atanh_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, z, a.prec)
   renormalize!(z)
   return z
end

doc"""
   sqrt(a::fmpq_rel_series)
> Return the power series square root of $a$. Requires a constant term equal to
> one.
"""
function sqrt(a::fmpq_rel_series)
   (a.val != 0 || coeff(a, 0) != 1) && error("Constant term not one in sqrt")
   z = parent(a)()
   z.prec = a.prec
   z.val = 0
   ccall((:fmpq_poly_sqrt_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, a.prec)
   return z
end

###############################################################################
#
#   Unsafe functions
#
###############################################################################

function zero!(z::fmpq_rel_series)
   ccall((:fmpq_poly_zero, :libflint), Void,
                (Ref{fmpq_rel_series},), z)
   z.prec = parent(z).prec_max
   return z
end

function setcoeff!(z::fmpq_rel_series, n::Int, x::fmpq)
   ccall((:fmpq_poly_set_coeff_fmpq, :libflint), Void,
                (Ref{fmpq_rel_series}, Int, Ref{fmpq}),
               z, n, x)
   return z
end

function mul!(z::fmpq_rel_series, a::fmpq_rel_series, b::fmpq_rel_series)
   lena = pol_length(a)
   lenb = pol_length(b)
   aval = valuation(a)
   bval = valuation(b)
   prec = min(a.prec - aval, b.prec - bval)
   lena = min(lena, prec)
   lenb = min(lenb, prec)
   z.val = a.val + b.val
   z.prec = prec + c.val
   lenz = min(lena + lenb - 1, prec)
   if lena <= 0 || lenb <= 0
      lenz = 0
   end
   ccall((:fmpq_poly_mullow, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               z, a, b, lenz)
   return z
end

function addeq!(a::fmpq_rel_series, b::fmpq_rel_series)
   lena = pol_length(a)
   lenb = pol_length(b)
   prec = min(a.prec, b.prec)
   val = min(a.val, b.val)
   lena = min(lena, prec - a.val)
   lenb = min(lenb, prec - b.val)
   if a.val < b.val
      z = fmpq_rel_series()
      lenz = max(lena, lenb + b.val - a.val)
      ccall((:fmpq_poly_set_trunc, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, b, max(0, lenz - b.val + a.val))
      ccall((:fmpq_poly_shift_left, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            z, z, b.val - a.val)
      ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               a, a, z, lenz)
   elseif b.val < a.val
      lenz = max(lena + a.val - b.val, lenb)
      ccall((:fmpq_poly_truncate, :libflint), Void,
            (Ref{fmpq_rel_series}, Int),
            a, max(0, lenz - a.val + b.val))
      ccall((:fmpq_poly_shift_left, :libflint), Void,
            (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
            a, a, a.val - b.val)
      ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               a, a, b, lenz)
   else
      lenz = max(lena, lenb)
      ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               a, a, b, lenz)
   end
   a.prec = prec
   a.val = val
   renormalize!(a)
   return a
end

function add!(c::fmpq_rel_series, a::fmpq_rel_series, b::fmpq_rel_series)
   lena = length(a)
   lenb = length(b)

   prec = min(a.prec, b.prec)

   lena = min(lena, prec)
   lenb = min(lenb, prec)

   lenc = max(lena, lenb)
   c.prec = prec
   ccall((:fmpq_poly_add_series, :libflint), Void,
                (Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Ref{fmpq_rel_series}, Int),
               c, a, b, lenc)
   return c
end

###############################################################################
#
#   Promotion rules
#
###############################################################################

promote_rule(::Type{fmpq_rel_series}, ::Type{T}) where {T <: Integer} = fmpq_rel_series

promote_rule(::Type{fmpq_rel_series}, ::Type{Rational{T}}) where T <: Union{Int, BigInt} = fmpq_rel_series

promote_rule(::Type{fmpq_rel_series}, ::Type{fmpz}) = fmpq_rel_series

promote_rule(::Type{fmpq_rel_series}, ::Type{fmpq}) = fmpq_rel_series

###############################################################################
#
#   Parent object call overload
#
###############################################################################

function (a::FmpqRelSeriesRing)()
   z = fmpq_rel_series()
   z.prec = a.prec_max
   z.val = a.prec_max
   z.parent = a
   return z
end

function (a::FmpqRelSeriesRing)(b::Integer)
   if b == 0
      z = fmpq_rel_series()
      z.prec = a.prec_max
   else
      z = fmpq_rel_series([fmpq(b)], 1, a.prec_max, 0)
   end
   z.parent = a
   return z
end

function (a::FmpqRelSeriesRing)(b::fmpz)
   if iszero(b)
      z = fmpq_rel_series()
      z.prec = a.prec_max
   else
      z = fmpq_rel_series([fmpq(b)], 1, a.prec_max, 0)
   end
   z.parent = a
   return z
end

function (a::FmpqRelSeriesRing)(b::fmpq)
   if iszero(b)
      z = fmpq_rel_series()
      z.prec = a.prec_max
   else
      z = fmpq_rel_series([b], 1, a.prec_max, 0)
   end
   z.parent = a
   return z
end

(a::FmpqRelSeriesRing)(b::Rational{T}) where T <: Union{Int, BigInt} = a(fmpq(b))

function (a::FmpqRelSeriesRing)(b::fmpq_rel_series)
   parent(b) != a && error("Unable to coerce power series")
   return b
end

function (a::FmpqRelSeriesRing)(b::Array{fmpq, 1}, len::Int, prec::Int, val::Int)
   z = fmpq_rel_series(b, len, prec, val)
   z.parent = a
   return z
end

(a::FmpqRelSeriesRing)(b::Array{fmpz, 1}, len::Int, prec::Int, val::Int) =
    a(map(fmpq, b), len, prec, val)

(a::FmpqRelSeriesRing)(b::Array{T, 1}, len::Int, prec::Int, val::Int) where {T <: Integer} =
    a(map(fmpq, b), len, prec, val)

(a::FmpqRelSeriesRing)(b::Array{Rational{T}, 1}, len::Int, prec::Int, val::Int) where {T <: Integer} =
    a(map(fmpq, b), len, prec, val)


#### Elementwise manipulations (scaling/clamping/type conversion) ####

# This file exists primarily to handle conversions for display and
# saving to disk. Both of these operations require Ufixed-valued
# elements, but with display we always want to convert to 8-bit
# whereas saving can handle 16-bit.
# We also can't trust that user images are clamped properly.
# Finally, this supports adjustable contrast limits.

# Structure of MapInfo subtype definitions:
#   - type definition
#   - constructors for scalars
#   - constructors for AbstractArrays
#   - similar  (syntax: similar(mapi, ToType, FromType))
#   - implementation of map() for scalars
#   - implementation of map() for AbstractArrays
# map(mapi::MapInfo{T}, x) should return an object of type T (for x not an array)
# map1(mapi::MapInfo{T}, x) is designed to allow T<:ColorValue to work on
#    scalars x::Fractional

# Dispatch-based elementwise manipulations
abstract MapInfo{T}
eltype{T}(mapi::MapInfo{T}) = T

## MapNone
# At most, this does conversions

immutable MapNone{T} <: MapInfo{T}; end

# Constructors
MapNone{T}(::Type{T}) = MapNone{T}()
MapNone{T}(val::T) = MapNone{T}()
MapNone{T}(A::AbstractArray{T}) = MapNone{T}()

similar{T}(mapi::MapNone, ::Type{T}, ::Type) = MapNone{T}()

# Implementation
map{T<:Real}(mapi::MapNone{T}, val::Real) = convert(T, val)
map{C<:ColorValue}(mapi::MapNone{C}, c::ColorValue) = convert(C, c)
map{C<:ColorValue,T}(mapi::MapNone{AlphaColorValue{C,T}}, c::AlphaColorValue) = convert(AlphaColorValue{C,T}, c)
map1(mapi::Union(MapNone{RGB24}, MapNone{ARGB32}), b::Bool) = ifelse(b, 0xffuf8, 0x00uf8)
map1(mapi::Union(MapNone{RGB24},MapNone{ARGB32}), val::Fractional) = convert(Ufixed8, val)
map1{CT<:ColorType}(mapi::MapNone{CT}, val::Fractional) = convert(eltype(CT), val)

map{T<:ColorType}(mapi::MapNone{T}, img::AbstractImageIndexed{T}) = convert(Image{T}, img)
map{C<:ColorType}(mapi::MapNone{C}, img::AbstractImageDirect{C}) = img  # ambiguity resolution
map{T}(mapi::MapNone{T}, img::AbstractArray{T}) = img

## BitShift
# This is really a "saturating bitshift", for example
#    map(BitShift{Uint8,7}(), 0xf0ff) == 0xff rather than 0xe1 even though 0xf0ff>>>7 == 0x01e1

immutable BitShift{T,N} <: MapInfo{T} end
BitShift{T}(::Type{T}, n::Int) = BitShift{T,n}()  # note that this is not type-stable

similar{S,T,N}(mapi::BitShift{S,N}, ::Type{T}, ::Type) = BitShift{T,N}()

# Implementation
immutable BS{N} end
_map{T<:Unsigned,N}(::Type{T}, ::Type{BS{N}}, val::Unsigned) = (v = val>>>N; tm = oftype(val, typemax(T)); convert(T, ifelse(v > tm, tm, v)))
_map{T<:Ufixed,N}(::Type{T}, ::Type{BS{N}}, val::Ufixed) = reinterpret(T, _map(FixedPointNumbers.rawtype(T), BS{N}, reinterpret(val)))
map{T<:Real,N}(mapi::BitShift{T,N}, val::Real) = _map(T, BS{N}, val)
map{T<:Real,N}(mapi::BitShift{Gray{T},N}, val::Gray) = Gray(_map(T, BS{N}, val.val))
map1{N}(mapi::Union(BitShift{RGB24,N},BitShift{ARGB32,N}), val::Unsigned) = _map(Uint8, BS{N}, val)
map1{N}(mapi::Union(BitShift{RGB24,N},BitShift{ARGB32,N}), val::Ufixed) = _map(Ufixed8, BS{N}, val)
map1{CT<:ColorType,N}(mapi::BitShift{CT,N}, val::Ufixed) = _map(eltype(CT), BS{N}, val)

## Clamp types
# The Clamp types just enforce bounds, but do not scale or offset

# Types and constructors
abstract AbstractClamp{T} <: MapInfo{T}
immutable ClampMin{T,From} <: AbstractClamp{T}
    min::From
end
ClampMin{T,From}(::Type{T}, min::From) = ClampMin{T,From}(min)
ClampMin{T}(min::T) = ClampMin{T,T}(min)
immutable ClampMax{T,From} <: AbstractClamp{T}
    max::From
end
ClampMax{T,From}(::Type{T}, max::From) = ClampMax{T,From}(max)
ClampMax{T}(max::T) = ClampMax{T,T}(max)
immutable ClampMinMax{T,From} <: AbstractClamp{T}
    min::From
    max::From
end
ClampMinMax{T,From}(::Type{T}, min::From, max::From) = ClampMinMax{T,From}(min,max)
ClampMinMax{T}(min::T, max::T) = ClampMinMax{T,T}(min,max)
immutable Clamp{T} <: AbstractClamp{T} end  # specialized for clamping colorvalues (e.g., 0 to 1 for RGB, also fractional)
Clamp{T}(::Type{T}) = Clamp{T}()

similar{T,F}(mapi::ClampMin, ::Type{T}, ::Type{F}) = ClampMin{T,F}(convert(F, mapi.min))
similar{T,F}(mapi::ClampMax, ::Type{T}, ::Type{F}) = ClampMax{T,F}(convert(F, mapi.max))
similar{T,F}(mapi::ClampMinMax, ::Type{T}, ::Type{F}) = ClampMin{T,F}(convert(F, mapi.min), convert(F, mapi.max))
similar{T,F}(mapi::Clamp, ::Type{T}, ::Type{F}) = Clamp{T}()

# Implementation
map{T<:Real,F<:Real}(mapi::ClampMin{T,F}, val::F) = convert(T, max(val, mapi.min))
map{T<:Real,F<:Real}(mapi::ClampMax{T,F}, val::F) = convert(T, min(val, mapi.max))
map{T<:Real,F<:Real}(mapi::ClampMinMax{T,F}, val::F) = convert(T,min(max(val, mapi.min), mapi.max))
map{T<:Fractional,F<:Real}(mapi::ClampMin{Gray{T},F}, val::F) = convert(Gray{T}, max(val, mapi.min))
map{T<:Fractional,F<:Real}(mapi::ClampMax{Gray{T},F}, val::F) = convert(Gray{T}, min(val, mapi.max))
map{T<:Fractional,F<:Real}(mapi::ClampMinMax{Gray{T},F}, val::F) = convert(Gray{T},min(max(val, mapi.min), mapi.max))
map{T<:Fractional,F<:Fractional}(mapi::ClampMin{Gray{T},F}, val::Gray{F}) = convert(Gray{T}, max(val, mapi.min))
map{T<:Fractional,F<:Fractional}(mapi::ClampMax{Gray{T},F}, val::Gray{F}) = convert(Gray{T}, min(val, mapi.max))
map{T<:Fractional,F<:Fractional}(mapi::ClampMinMax{Gray{T},F}, val::Gray{F}) = convert(Gray{T},min(max(val, mapi.min), mapi.max))
map{T<:Fractional,F<:Fractional}(mapi::ClampMin{Gray{T},Gray{F}}, val::Gray{F}) = convert(Gray{T}, max(val, mapi.min))
map{T<:Fractional,F<:Fractional}(mapi::ClampMax{Gray{T},Gray{F}}, val::Gray{F}) = convert(Gray{T}, min(val, mapi.max))
map{T<:Fractional,F<:Fractional}(mapi::ClampMinMax{Gray{T},Gray{F}}, val::Gray{F}) = convert(Gray{T},min(max(val, mapi.min), mapi.max))
map1{T<:Union(RGB24,ARGB32),F<:Fractional}(mapi::ClampMin{T,F}, val::F) = convert(Ufixed8, max(val, mapi.min))
map1{T<:Union(RGB24,ARGB32),F<:Fractional}(mapi::ClampMax{T,F}, val::F) = convert(Ufixed8, min(val, mapi.max))
map1{T<:Union(RGB24,ARGB32),F<:Fractional}(mapi::ClampMinMax{T,F}, val::F) = convert(Ufixed8,min(max(val, mapi.min), mapi.max))
map1{CT<:ColorType,F<:Fractional}(mapi::ClampMin{CT,F}, val::F) = convert(eltype(CT), max(val, mapi.min))
map1{CT<:ColorType,F<:Fractional}(mapi::ClampMax{CT,F}, val::F) = convert(eltype(CT), min(val, mapi.max))
map1{CT<:ColorType,F<:Fractional}(mapi::ClampMinMax{CT,F}, val::F) = convert(eltype(CT), min(max(val, mapi.min), mapi.max))

map{To<:Real}(::Clamp{To}, val::Real) = clamp01(To, val)
map{To<:Real}(::Clamp{Gray{To}}, val::AbstractGray) = Gray(clamp01(To, val.val))
map{To<:Real}(::Clamp{Gray{To}}, val::Real) = Gray(clamp01(To, val))
map1{CT<:AbstractRGB}(::Clamp{CT}, val::Real) = clamp01(eltype(CT), val)
map1{CT<:AbstractRGB,T}(::Clamp{AlphaColor{CT,T}}, val::Real) = clamp01(T, val)
map1{CT<:AbstractRGB,T}(::Clamp{AlphaColorValue{CT,T}}, val::Real) = clamp01(T, val)

# Also available as a stand-alone function
clamp01{T}(::Type{T}, x::Real) = convert(T, min(max(x, zero(x)), one(x)))
clamp01(x::Real) = clamp01(typeof(x), x)
for CV in subtypes(AbstractRGB)
    @eval begin
        clamp01{T}(x::$CV{T}) = clamp01($CV{T}, x)
        clamp01{To}(::Type{$CV{To}}, x::AbstractRGB) = $CV{To}(clamp01(To, x.r), clamp01(To, x.g), clamp01(To, x.b))
        clamp01{T}(x::AlphaColor{$CV{T},T}) = clamp01(AlphaColor{$CV{T},T}, x)
        clamp01{T}(x::AlphaColorValue{$CV{T},T}) = clamp01(AlphaColorValue{$CV{T},T}, x)
        clamp01{To,CV2<:AbstractRGB}(::Type{AlphaColor{$CV{To},To}}, x::AlphaColor{CV2}) =
            AlphaColor{$CV{To},To}(clamp01(To, x.c.r), clamp01(To, x.c.g), clamp01(To, x.c.b), clamp01(To, x.alpha))
        clamp01{To,CV2<:AbstractRGB}(::Type{AlphaColorValue{$CV{To},To}}, x::AlphaColorValue{CV2}) =
            AlphaColorValue{$CV{To},To}(clamp01(To, x.c.r), clamp01(To, x.c.g), clamp01(To, x.c.b), clamp01(To, x.alpha))
        clamp01{To,CV2<:AbstractRGB}(::Type{AlphaColor{$CV{To},To}}, x::AlphaColorValue{CV2}) =
            AlphaColor{$CV{To},To}(clamp01(To, x.c.r), clamp01(To, x.c.g), clamp01(To, x.c.b), clamp01(To, x.alpha))
        clamp01{To,CV2<:AbstractRGB}(::Type{AlphaColorValue{$CV{To},To}}, x::AlphaColor{CV2}) =
            AlphaColorValue{$CV{To},To}(clamp01(To, x.c.r), clamp01(To, x.c.g), clamp01(To, x.c.b), clamp01(To, x.alpha))
    end
end

# clamp is generic for any colorspace; this version does the right thing for any RGB type
clamp(x::AbstractRGB) = clamp01(x)
for CV in subtypes(AbstractRGB)
    @eval begin
        clamp{T}(x::AlphaColorValue{$CV{T},T}) = clamp01(x)
        clamp{T}(x::AlphaColor{$CV{T},T}) = clamp01(x)
    end
end

## ScaleMinMax
# This clamps, subtracts the min value, then scales

immutable ScaleMinMax{To,From,S<:FloatingPoint} <: MapInfo{To}
    min::From
    max::From
    s::S

    function ScaleMinMax(min, max, s)
        min >= max && error("min must be smaller than max")
        new(min, max, s)
    end
end

ScaleMinMax{To,From}(::Type{To}, min::From, max::From, s::FloatingPoint) = ScaleMinMax{To,From,typeof(s)}(min, max, s)
ScaleMinMax{To<:Union(Fractional,ColorType),From}(::Type{To}, mn::From, mx::From) = ScaleMinMax(To, mn, mx, 1.0f0/(convert(Float32, mx)-convert(Float32, mn)))

# ScaleMinMax constructors that take AbstractArray input
ScaleMinMax{To,From<:Real}(::Type{To}, img::AbstractArray{From}, mn::Real, mx::Real) = ScaleMinMax(To, convert(From,mn), convert(From,mx), 1.0f0/(convert(Float32, convert(From, mx))-convert(Float32,convert(From, mn))))
ScaleMinMax{To,From<:Real}(::Type{To}, img::AbstractArray{Gray{From}}, mn::Real, mx::Real) = ScaleMinMax(To, convert(From,mn), convert(From,mx), 1.0f0/(convert(Float32, convert(From,mx))-convert(Float32, convert(From,mn))))
ScaleMinMax{To,From<:Real,R<:Real}(::Type{To}, img::AbstractArray{From}, mn::Gray{R}, mx::Gray{R}) = ScaleMinMax(To, convert(From,mn.val), convert(From,mx.val), 1.0f0/(convert(Float32, convert(From,mx.val))-convert(Float32, convert(From,mn.val))))
ScaleMinMax{To,From<:Real,R<:Real}(::Type{To}, img::AbstractArray{Gray{From}}, mn::Gray{R}, mx::Gray{R}) = ScaleMinMax(To, convert(From,mn.val), convert(From,mx.val), 1.0f0/(convert(Float32, convert(From,mx.val))-convert(Float32, convert(From,mn.val))))
ScaleMinMax{To}(::Type{To}, img::AbstractArray) = ScaleMinMax(To, img, minfinite(img), maxfinite(img))
ScaleMinMax{To,CV<:AbstractRGB}(::Type{To}, img::AbstractArray{CV}) = (imgr = reinterpret(eltype(CV), img); ScaleMinMax(To, minfinite(imgr), maxfinite(imgr)))

similar{T,F,To,From,S}(mapi::ScaleMinMax{To,From,S}, ::Type{T}, ::Type{F}) = ScaleMinMax{T,F,S}(convert(F,mapi.min), convert(F.mapi.max), mapi.s)

# Implementation
function map{To<:Real,From<:Union(Real,Gray)}(mapi::ScaleMinMax{To,From}, val::From)
    t = ifelse(val  < mapi.min, zero(From), ifelse(val  > mapi.max, mapi.max-mapi.min, val -mapi.min))
    convert(To, mapi.s*t)
end
function map{To<:Real,From<:Union(Real,Gray)}(mapi::ScaleMinMax{To,From}, val::Union(Real,ColorType))
    map(mapi, convert(From, val))
end
function map1{To<:Union(RGB24,ARGB32),From<:Real}(mapi::ScaleMinMax{To,From}, val::From)
    t = ifelse(val  < mapi.min, zero(From), ifelse(val  > mapi.max, mapi.max-mapi.min, val -mapi.min))
    convert(Ufixed8, mapi.s*t)
end
function map1{To<:ColorType,From<:Real}(mapi::ScaleMinMax{To,From}, val::From)
    t = ifelse(val  < mapi.min, zero(From), ifelse(val  > mapi.max, mapi.max-mapi.min, val -mapi.min))
    convert(eltype(To), mapi.s*t)
end
function map1{To<:Union(RGB24,ARGB32),From<:Real}(mapi::ScaleMinMax{To,From}, val::Union(Real,ColorType))
    map1(mapi, convert(From, val))
end
function map1{To<:ColorType,From<:Real}(mapi::ScaleMinMax{To,From}, val::Union(Real,ColorType))
    map1(mapi, convert(From, val))
end

## ScaleSigned
# Multiplies by a scaling factor and then clamps to the range [-1,1].
# Intended for positive/negative coloring

immutable ScaleSigned{T, S<:FloatingPoint} <: MapInfo{T}
    s::S
end
ScaleSigned{T}(::Type{T}, s::FloatingPoint) = ScaleSigned{T, typeof(s)}(s)

ScaleSigned{T}(::Type{T}, img::AbstractArray) = ScaleSigned(T, 1.0f0/maxabsfinite(img))
ScaleSigned(img::AbstractArray) = ScaleSigned(Float32, img)

similar{T,To,S}(mapi::ScaleSigned{To,S}, ::Type{T}, ::Type) = ScaleSigned{T,S}(mapi.s)

map{T}(mapi::ScaleSigned{T}, val::Real) = convert(T, clamppm(mapi.s*val))
function map{C<:Union(RGB24, RGB{Ufixed8})}(mapi::ScaleSigned{C}, val::Real)
    x = clamppm(mapi.s*val)
    g = Ufixed8(abs(x))
    ifelse(x >= 0, C(g, zero(Ufixed8), g), C(zero(Ufixed8), g, zero(Ufixed8)))
end

clamppm(x::Real) = ifelse(x >= 0, min(x, one(x)), max(x, -one(x)))

## ScaleAutoMinMax
# Works only on whole arrays, not values

immutable ScaleAutoMinMax{T} <: MapInfo{T} end
ScaleAutoMinMax{T}(::Type{T}) = ScaleAutoMinMax{T}()
ScaleAutoMinMax() = ScaleAutoMinMax{Ufixed8}()

similar{T}(mapi::ScaleAutoMinMax, ::Type{T}, ::Type) = ScaleAutoMinMax{T}()

# Conversions to RGB{T}, RGBA{T}, RGB24, ARGB32,
# for grayscale, AbstractRGB, and abstract ARGB inputs.
# This essentially "vectorizes" map over a single pixel's color channels using map1
for SI in (MapInfo, AbstractClamp)
    for ST in subtypes(SI)
        ST.abstract && continue
        ST == ScaleSigned && continue  # ScaleSigned gives an RGB from a scalar, so don't "vectorize" it
        @eval begin
            # Grayscale and GrayAlpha inputs
            map(mapi::$ST{RGB24}, g::Gray) = map(mapi, g.val)
            map(mapi::$ST{RGB24}, g::Real) = (x = map1(mapi, g); convert(RGB24, RGB{Ufixed8}(x,x,x)))
            function map(mapi::$ST{RGB24}, g::FloatingPoint)
                if isfinite(g)
                    x = map1(mapi, g)
                    convert(RGB24, RGB{Ufixed8}(x,x,x))
                else
                    RGB24(0)
                end
            end
            map{T}(mapi::$ST{RGB24}, g::GrayAlpha{T}) = map(mapi, g.c.val)
            map(mapi::$ST{ARGB32}, g::Gray) = map(mapi, g.val)
            function map(mapi::$ST{ARGB32}, g::Real)
                x = map1(mapi, g)
                convert(ARGB32, ARGB{Ufixed8}(x,x,x,0xffuf8))
            end
            function map{T}(mapi::$ST{ARGB32}, g::GrayAlpha{T})
                x = map1(mapi, g.c.val)
                convert(ARGB32, ARGB{Ufixed8}(x,x,x,map1(mapi, g.alpha)))
            end
        end
        for O in (:RGB, :BGR)
            @eval begin
                map{T}(mapi::$ST{$O{T}}, g::Gray) = map(mapi, g.val)
                function map{T}(mapi::$ST{$O{T}}, g::Real)
                    x = map1(mapi, g)
                    $O{T}(x,x,x)
                end
            end
        end
        for OA in (:RGBA, :ARGB, :BGRA)
            exAlphaGray = ST == MapNone ? :nothing : quote
                function map{T,S}(mapi::$ST{$OA{T}}, g::GrayAlpha{S})
                    x = map1(mapi, g.c.val)
                    $OA{T}(x,x,x,map1(mapi, g.alpha))
                end  # avoids an ambiguity warning with MapNone definitions
            end
            @eval begin
                map{T}(mapi::$ST{$OA{T}}, g::Gray) = map(mapi, g.val)
                function map{T}(mapi::$ST{$OA{T}}, g::Real)
                    x = map1(mapi, g)
                    $OA{T}(x,x,x)
                end
                $exAlphaGray
            end
        end
        @eval begin
            # AbstractRGB and abstract ARGB inputs
            map(mapi::$ST{RGB24}, rgb::AbstractRGB) =
                convert(RGB24, RGB{Ufixed8}(map1(mapi, rgb.r), map1(mapi, rgb.g), map1(mapi, rgb.b)))
            map{C<:AbstractRGB, TC}(mapi::$ST{RGB24}, argb::AbstractAlphaColorValue{C,TC}) =
                convert(RGB24, RGB{Ufixed8}(map1(mapi, argb.c.r), map1(mapi, argb.c.g),
                                            map1(mapi, argb.c.b)))
            map{C<:AbstractRGB, TC}(mapi::$ST{ARGB32}, argb::AbstractAlphaColorValue{C,TC}) =
                convert(ARGB32, ARGB{Ufixed8}(map1(mapi, argb.c.r), map1(mapi, argb.c.g),
                                              map1(mapi, argb.c.b), map1(mapi, argb.alpha)))
            map(mapi::$ST{ARGB32}, rgb::AbstractRGB) =
                convert(ARGB32, ARGB{Ufixed8}(map1(mapi, rgb.r), map1(mapi, rgb.g), map1(mapi, rgb.b)))
        end
        for O in (:RGB, :BGR)
            @eval begin
                map{T}(mapi::$ST{$O{T}}, rgb::AbstractRGB) =
                    $O{T}(map1(mapi, rgb.r), map1(mapi, rgb.g), map1(mapi, rgb.b))
                map{T,C<:AbstractRGB, TC}(mapi::$ST{$O{T}}, argb::AbstractAlphaColorValue{C,TC}) =
                    $O{T}(map1(mapi, argb.c.r), map1(mapi, argb.c.g), map1(mapi, argb.c.b))
            end
        end
        for OA in (:RGBA, :ARGB, :BGRA)
            @eval begin
                map{T, C<:AbstractRGB, TC}(mapi::$ST{$OA{T}}, argb::AbstractAlphaColorValue{C,TC}) =
                    $OA{T}(map1(mapi, argb.c.r), map1(mapi, argb.c.g),
                            map1(mapi, argb.c.b), map1(mapi, argb.alpha))
                map{T}(mapi::$ST{$OA{T}}, argb::ARGB32) = map(mapi, convert(RGBA{Ufixed8}, argb))
                map{T}(mapi::$ST{$OA{T}}, rgb::AbstractRGB) =
                    $OA{T}(map1(mapi, rgb.r), map1(mapi, rgb.g), map1(mapi, rgb.b))
                map{T}(mapi::$ST{$OA{T}}, rgb::RGB24) = map(mapi, convert(RGB{Ufixed8}, argb))
            end
        end
    end
end

# # Apply to any ColorType
# map(f::Callable, x::ColorValue) = f(x)
# map(mapi, x::ColorValue) = map(mapi, convert(RGB, x))
# map{C<:ColorValue, TC}(f::Callable, x::AbstractAlphaColorValue{C, TC}) = f(convert(ARGB, x))
# map{C<:ColorValue, TC}(mapi, x::AbstractAlphaColorValue{C, TC}) = map(mapi, convert(ARGB, x))

## Fallback definitions of map() for array types

function map{T}(mapi::MapInfo{T}, img::AbstractArray)
    out = similar(img, T)
    map!(mapi, out, img)
end

map{C<:ColorType,R<:Real}(mapi::MapNone{C}, img::AbstractImageDirect{R}) = mapcd(mapi, img)  # ambiguity resolution
map{C<:ColorType,R<:Real}(mapi::MapInfo{C}, img::AbstractImageDirect{R}) = mapcd(mapi, img)
function mapcd{C<:ColorType,R<:Real}(mapi::MapInfo{C}, img::AbstractImageDirect{R})
    # For this case we have to check whether color is defined along an array axis
    cd = colordim(img)
    if cd > 0
        dims = setdiff(1:ndims(img), cd)
        out = similar(img, C, size(img)[dims])
        map!(mapi, out, img, TypeConst{cd})
    else
        out = similar(img, C)
        map!(mapi, out, img)
    end
    out   # note this isn't type-stable
end

function map{T<:ColorType}(mapi::MapInfo{T}, img::AbstractImageIndexed)
    out = Image(Array(T, size(img)), properties(img))
    map!(mapi, out, img)
end

map!{T,T1,T2,N}(mapi::MapInfo{T1}, out::AbstractArray{T,N}, img::AbstractArray{T2,N}) =
    _map_a!(mapi, out, img)
@ngenerate N typeof(out) function _map_a!{T,T1,T2,N}(mapi::MapInfo{T1}, out::AbstractArray{T,N}, img::AbstractArray{T2,N})
    mi = take(mapi, img)
    dimg = data(img)
    dout = data(out)
    size(dout) == size(dimg) || throw(DimensionMismatch())
    @nloops N i dout begin
        @inbounds @nref(N, dout, i) = map(mi, @nref(N, dimg, i))
    end
    out
end

take(mapi::MapInfo, img::AbstractArray) = mapi
take{T}(mapi::ScaleAutoMinMax{T}, img::AbstractArray) = ScaleMinMax(T, img)

# Indexed images (colormaps)
map!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::AbstractImageIndexed{T1,N}) =
    _mapindx!(mapi, out, img)
@ngenerate N typeof(out) function _mapindx!{T,T1,N}(mapi::MapInfo{T}, out::AbstractArray{T,N}, img::AbstractImageIndexed{T1,N})
    dimg = data(img)
    dout = data(out)
    cmap = map(mapi, img.cmap)
    @nloops N i dout begin
        @inbounds @nref(N, dout, i) = cmap[@nref(N, dimg, i)]
    end
    out
end

# For when color is encoded along dimension CD
# NC is the number of color channels
# This is a very flexible implementation: color can be stored along any dimension, and it handles conversions to
# many different colorspace representations.
for (CT, NC) in ((Union(AbstractRGB,RGB24), 3), (Union(RGBA,ARGB,ARGB32), 4), (Union(GrayAlpha,AGray32), 2))
    for N = 1:4
        N1 = N+1
        @eval begin
function map!{T<:$CT,T1,T2,CD}(mapi::MapInfo{T}, out::AbstractArray{T1,$N}, img::AbstractArray{T2,$N1}, ::Type{TypeConst{CD}})
    mi = take(mapi, img)
    dimg = data(img)
    dout = data(out)
    # Set up the index along the color axis
    # We really only need dimension CD, but this will suffice
    @nexprs $NC k->(@nexprs $N1 d->(j_k_d = k))
    # Loop over all the elements in the output, performing the conversion on each color component
    @nloops $N i dout d->(d<CD ? (@nexprs $NC k->(j_k_d = i_d)) : (@nexprs $NC k->(j_k_{d+1} = i_d))) begin
        @inbounds @nref($N, dout, i) = @ncall $NC T k->(map1(mi, @nref($N1, dimg, j_k)))
    end
    out
end
        end
    end
end

#### MapInfo defaults
# Each "client" can define its own methods. "clients" include Ufixed, RGB24/ARGB32, and ImageMagick

const bitshiftto8 = ((Ufixed10, 2), (Ufixed12, 4), (Ufixed14, 6), (Ufixed16, 8))

# typealias GrayType{T<:Fractional} Union(T, Gray{T})
typealias GrayArray{T<:Fractional} Union(AbstractArray{T}, AbstractArray{Gray{T}})
# note, though, that we need to override for AbstractImage in case the "colorspace" property is defined differently

# mapinfo{T<:Union(Real,ColorType)}(::Type{T}, img::AbstractArray{T}) = MapNone(img)
mapinfo{T<:Ufixed}(::Type{T}, img::AbstractArray{T}) = MapNone(img)
mapinfo{T<:FloatingPoint}(::Type{T}, img::AbstractArray{T}) = MapNone(img)

# Grayscale methods
mapinfo(::Type{Ufixed8}, img::GrayArray{Ufixed8}) = MapNone{Ufixed8}()
mapinfo(::Type{Gray{Ufixed8}}, img::GrayArray{Ufixed8}) = MapNone{Gray{Ufixed8}}()
mapinfo(::Type{GrayAlpha{Ufixed8}}, img::AbstractArray{GrayAlpha{Ufixed8}}) = MapNone{GrayAlpha{Ufixed8}}()
for (T,n) in bitshiftto8
    @eval mapinfo(::Type{Ufixed8}, img::GrayArray{$T}) = BitShift{Ufixed8,$n}()
    @eval mapinfo(::Type{Gray{Ufixed8}}, img::GrayArray{$T}) = BitShift{Gray{Ufixed8},$n}()
    @eval mapinfo(::Type{GrayAlpha{Ufixed8}}, img::AbstractArray{GrayAlpha{$T}}) = BitShift{GrayAlpha{Ufixed8},$n}()
end
mapinfo{T<:Ufixed,F<:FloatingPoint}(::Type{T}, img::GrayArray{F}) = ClampMinMax(T, zero(F), one(F))
mapinfo{T<:Ufixed,F<:FloatingPoint}(::Type{Gray{T}}, img::GrayArray{F}) = ClampMinMax(Gray{T}, zero(F), one(F))
mapinfo{T<:FloatingPoint, R<:Real}(::Type{T}, img::AbstractArray{R}) = MapNone(T)

mapinfo(::Type{RGB24}, img::Union(AbstractArray{Bool}, BitArray)) = MapNone{RGB24}()
mapinfo(::Type{ARGB32}, img::Union(AbstractArray{Bool}, BitArray)) = MapNone{ARGB32}()
mapinfo{F<:Fractional}(::Type{RGB24}, img::GrayArray{F}) = ClampMinMax(RGB24, zero(F), one(F))
mapinfo{F<:Fractional}(::Type{ARGB32}, img::AbstractArray{F}) = ClampMinMax(ARGB32, zero(F), one(F))

# Color->Color methods
mapinfo(::Type{RGB{Ufixed8}}, img) = MapNone{RGB{Ufixed8}}()
mapinfo(::Type{RGBA{Ufixed8}}, img) = MapNone{RGBA{Ufixed8}}()
for (T,n) in bitshiftto8
    @eval mapinfo(::Type{RGB{Ufixed8}}, img::AbstractArray{RGB{$T}}) = BitShift{RGB{Ufixed8},$n}()
    @eval mapinfo(::Type{RGBA{Ufixed8}}, img::AbstractArray{RGBA{$T}}) = BitShift{RGBA{Ufixed8},$n}()
end
mapinfo{F<:Fractional}(::Type{RGB{Ufixed8}}, img::AbstractArray{RGB{F}}) = Clamp(RGB{Ufixed8})
mapinfo{F<:Fractional}(::Type{RGBA{Ufixed8}}, img::AbstractArray{RGBA{F}}) = Clamp(RGBA{Ufixed8})

# Color->RGB24/ARGB32
mapinfo(::Type{RGB24}, img::AbstractArray{RGB24}) = MapNone{RGB24}()
mapinfo(::Type{ARGB32}, img::AbstractArray{ARGB32}) = MapNone{ARGB32}()
for C in tuple(subtypes(AbstractRGB)..., Gray)
    @eval mapinfo(::Type{RGB24}, img::AbstractArray{$C{Ufixed8}}) = MapNone{RGB24}()
    @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$C{Ufixed8}}) = MapNone{ARGB32}()
    for (T, n) in bitshiftto8
        @eval mapinfo(::Type{RGB24}, img::AbstractArray{$C{$T}}) = BitShift{RGB24, $n}()
        @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$C{$T}}) = BitShift{ARGB32, $n}()
    end
    @eval mapinfo{F<:FloatingPoint}(::Type{RGB24}, img::AbstractArray{$C{F}}) = ClampMinMax(RGB24, zero(F), one(F))
    @eval mapinfo{F<:FloatingPoint}(::Type{ARGB32}, img::AbstractArray{$C{F}}) = ClampMinMax(ARGB32, zero(F), one(F))
    for AC in subtypes(AbstractAlphaColorValue)
        length(AC.parameters) == 2 || continue
        @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$AC{$C{Ufixed8},Ufixed8}}) = MapNone{ARGB32}()
        @eval mapinfo(::Type{RGB24}, img::AbstractArray{$AC{$C{Ufixed8},Ufixed8}}) = MapNone{RGB24}()
        for (T, n) in bitshiftto8
            @eval mapinfo(::Type{ARGB32}, img::AbstractArray{$AC{$C{$T},$T}}) = BitShift{ARGB32, $n}()
            @eval mapinfo(::Type{RGB24}, img::AbstractArray{$AC{$C{$T},$T}}) = BitShift{RGB24, $n}()
        end
        @eval mapinfo{F<:FloatingPoint}(::Type{ARGB32}, img::AbstractArray{$AC{$C{F},F}}) = ClampMinMax(ARGB32, zero(F), one(F))
        @eval mapinfo{F<:FloatingPoint}(::Type{RGB24}, img::AbstractArray{$AC{$C{F},F}}) = ClampMinMax(RGB24, zero(F), one(F))
    end
end

mapinfo{CT<:ColorType}(::Type{RGB24},  img::AbstractArray{CT}) = MapNone{RGB24}()
mapinfo{CT<:ColorType}(::Type{ARGB32}, img::AbstractArray{CT}) = MapNone{ARGB32}()

# Uint32 conversions will use ARGB32 for images that have an alpha channel,
# and RGB24 when not
mapinfo{CV<:Union(Fractional,ColorValue)}(::Type{Uint32}, img::AbstractArray{CV}) = mapinfo(RGB24, img)
mapinfo{CV<:AbstractAlphaColorValue}(::Type{Uint32}, img::AbstractArray{CV}) = mapinfo(ARGB32, img)
mapinfo(::Type{Uint32}, img::Union(AbstractArray{Bool},BitArray)) = mapinfo(RGB24, img)
mapinfo(::Type{Uint32}, img::AbstractArray{Uint32}) = MapNone{Uint32}()

# ImageMagick client is defined in io.jl

# Backwards-compatibility
uint32color(img) = map(mapinfo(Uint32, img), img)
uint32color!(buf, img::AbstractArray) = map!(mapinfo(Uint32, img), buf, img)
uint32color!(buf, img::AbstractArray, mi::MapInfo) = map!(mi, buf, img)
uint32color!{T,N}(buf::Array{Uint32,N}, img::AbstractImageDirect{T,N}) =
    map!(mapinfo(Uint32, img), buf, img)
uint32color!{T,N,N1}(buf::Array{Uint32,N}, img::AbstractImageDirect{T,N1}) =
    map!(mapinfo(Uint32, img), buf, img, TypeConst{colordim(img)})
uint32color!{T,N}(buf::Array{Uint32,N}, img::AbstractImageDirect{T,N}, mi::MapInfo) =
    map!(mi, buf, img)
uint32color!{T,N,N1}(buf::Array{Uint32,N}, img::AbstractImageDirect{T,N1}, mi::MapInfo) =
    map!(mi, buf, img, TypeConst{colordim(img)})

sc(img::AbstractArray) = map(ScaleMinMax(Ufixed8, img), img)
sc(img::AbstractArray, mn::Real, mx::Real) = map(ScaleMinMax(Ufixed8, img, mn, mx), img)

for (fn,T) in ((:float32, Float32), (:float64, Float64), (:ufixed8, Ufixed8),
               (:ufixed10, Ufixed10), (:ufixed12, Ufixed12), (:ufixed14, Ufixed14),
               (:ufixed16, Ufixed16))
    @eval begin
        function $fn{C<:ColorType}(A::AbstractArray{C})
            newC = eval(C.name.name){$T}
            convert(Array{newC}, A)
        end
        $fn{C<:ColorType}(img::AbstractImage{C}) = shareproperties(img, $fn(data(img)))
    end
end

ufixedsc{T<:Ufixed}(::Type{T}, img::AbstractImageDirect) = map(mapinfo(T, img), img)
ufixed8sc(img::AbstractImageDirect) = ufixedsc(Ufixed8, img)


using Distributions, JLD, CSV, DataFrames,Pkg, DataStructures

austin_stations_pre = CSV.File("raw/austin-stations.csv") |> DataFrame

# Yeesian has his coords in 3-stations.csv in this string format:
# "GeoInterface.Point([-77.00514742400335,38.83089499410552])"
formatted_coordinates = [string("GeoInterface.Point([", austin_stations_pre["LONGITUDE"][i], ", ", austin_stations_pre["LATITUDE"][i], "])") for i in 1:size(austin_stations_pre["LONGITUDE"], 1)]

austin_stations_post = DataFrame(geometry = formatted_coordinates,
                                 OBJECTID_1 = austin_stations_pre["OBJECTID_1"]
                                 )

CSV.write("formatted/austin_stations_formatted.csv", austin_stations_post)


module SpikingRNNs
using LinearAlgebra,Statistics,StatsBase,Random,Distributions
using SparseArrays
using Colors # to save spike rasters as png
using DSP # this is to have convolutions to analyze spike trains
using ExtendableSparse # to optimize adding elements in sparse matrices

# all type declarations should go here :-/

abstract type NeuronType end
abstract type SynapticKernel end
abstract type NTConductance <:NeuronType end
abstract type SpikeGenFunction end

# Base elements

# neuron parameterns and behavior
Base.broadcastable(t::NeuronType)=Ref(t)

# Dynamical state and memory allocation for a particular group of neurons
# n is always the number of neurons, and neurontype is the neuron type
abstract type PopulationState{NT<:NeuronType} end
nneurons(ps::PopulationState) = ps.n

# Everything related to connection (including plasticity, etc)
abstract type Connection{N} end
abstract type PlasticityRule end

struct NoPlasticity <: PlasticityRule end
reset!(::NoPlasticity) = nothing

struct FakeConnection{N,PL<:NTuple{N,PlasticityRule}} <: Connection{N}
  weights::SparseMatrixCSC{Float64,Int64}
  plasticities::PL
  function FakeConnection()
    w = sparse(fill(NaN,1,1))
    return new{0,NTuple{0,NoPlasticity}}(w,())
  end
end

struct BaseConnection{N,PL<:NTuple{N,PlasticityRule}} <: Connection{N}
  weights::SparseMatrixCSC{Float64,Int64}
  plasticities::PL
end
function BaseConnection(w::SparseMatrixCSC)
  return BaseConnection{0,NTuple{0,NoPlasticity}}(w,())
end

# weights evolve according to plasticity rules and pre-post activity
# BUT neurons do not exchange signals of any kind
# this works well in tandem with input neurons with specific spike times
# to test plasticity rules
struct ConnectionPlasticityTest{N,PL<:NTuple{N,PlasticityRule}} <: Connection{N}
  weights::SparseMatrixCSC{Float64,Int64}
  plasticities::PL
end
function ConnectionPlasticityTest(weights::SparseMatrixCSC,
    (plasticities::PlasticityRule)...)
  return ConnectionPlasticityTest(weights,plasticities)
end
function reset!(conn::ConnectionPlasticityTest)
  reset!.(conn.plasticities)
  return nothing
end

@inline function n_plasticity_rules(::Connection{N}) where N
  return N
end

struct PSSimpleInput{In} <: PopulationState{In}
  neurontype::In # not really a neuron, but I keep the name for consistency
  n::Int64
  function PSSimpleInput(in::N) where N<:NeuronType
    return new{N}(in,1)
  end
end

function rand_pop_label()
  return Symbol(randstring(3))
end

abstract type AbstractPopulation{N,PS} end

# this is for populations without incoming synapses (so no presynaptic
# population and no connections). Useful for input units.
struct UnconnectedPopulation{N,PS} <: AbstractPopulation{N,PS}
  label::Symbol
  state::PS
end
function UnconnectedPopulation(ps::PS;
    label::Union{Nothing,String}=nothing) where PS<:PopulationState
  label = isnothing(label) ? rand_pop_label() : Symbol(label)
  return UnconnectedPopulation{0,PS}(label,ps)
end

struct Population{N,PS<:PopulationState,
    TC<:NTuple{N,Connection},
    TP<:NTuple{N,PopulationState}} <:AbstractPopulation{N,PS}
  label::Symbol # I use symbols because it might be a DataFrame column name
  state::PS
  connections::TC
  pre_states::TP
end
nneurons(p::Population) = nneurons(p.state)

function Population(state::PopulationState,
    (conn_pre::Tuple{C,PS} where {C<:Connection,PS<:PopulationState})... ;
    label::Union{Nothing,String}=nothing)
  connections = Tuple(getindex.(conn_pre,1))
  pre_states = Tuple(getindex.(conn_pre,2))
  label = isnothing(label) ? rand_pop_label() : Symbol(label)
  return Population(label,state,connections,pre_states)
end

@inline function n_prepops(p::Population{N,PS,TC,TP}) where {N,PS,TC,TP}
  return N
end
@inline function n_prepops(p::AbstractPopulation{N,PS}) where {N,PS}
  return N
end

abstract type AbstractNetwork end
struct RecurrentNetwork{N,TP<:NTuple{N,AbstractPopulation}} <: AbstractNetwork
  dt::Float64
  populations::TP
end

function RecurrentNetwork(dt,(pops::P where P<:AbstractPopulation)...)
  RecurrentNetwork(dt,pops)
end

# fallback functions

# updates the state of the population locally, depending on
# some input gathered in previous steps
# usually it's simply the vector p.input
function local_update!(tnow::Real,dt::Real,p::PopulationState)
  return nothing
end
function local_update!(tnow::Real,dt::Real,p::AbstractPopulation)
  return local_update!(tnow,dt,p.state)
end

# reset the input gathered in the previous steps
# usually it's simply the vector p.input
@inline function reset_input!(ps::PopulationState)
  fill!(ps.input,0.0)
  return nothing
end
function reset_input!(p::AbstractPopulation)
  return reset_input!(p.state)
end
# or sometimes there are spikes
# stored in the BitVector called `isfiring`
@inline function reset_spikes!(spk::BitArray{1})
  fill!(spk,false)
	return nothing
end
@inline function reset_spikes!(popst::PopulationState)
	return reset_spikes!(popst.isfiring)
end

# Computes the input, considering each connection and each presynaptic population
function forward_signal!(tnow::Real,dt::Real,p_post::PopulationState,c::Connection,p_pre::PopulationState)
  return nothing
end
function forward_signal!(tnow::Real,dt::Real,p::AbstractPopulation)
  for i in 1:n_prepops(p)
    forward_signal!(tnow,dt,p.state,p.connections[i],p.pre_states[i])
  end
  return nothing
end
function plasticity_update!(tnow::Real,dt::Real,p::AbstractPopulation)
  for i in 1:n_prepops(p)
    for r in 1:n_plasticity_rules(p.connections[i])
      plasticity_update!(tnow,dt,p.state,p.connections[i],
        p.pre_states[i],p.connections[i].plasticities[r])
    end
  end
  return nothing
end

# this is the network iteration
function dynamics_step!(t_now::Float64,ntw::RecurrentNetwork)
  reset_input!.(ntw.populations)
  forward_signal!.(t_now,ntw.dt,ntw.populations)
  local_update!.(t_now,ntw.dt,ntw.populations)
  plasticity_update!.(t_now,ntw.dt,ntw.populations)
  return nothing
end

dynamics_step!(ntw::RecurrentNetwork) = dynamics_step!(NaN,ntw)

##

@inline hardbounds(x::R,low::R,high::R) where R = min(high,max(x,low))

##

include("firingneurons_shared.jl")
include("firingneurons_plasticity_shared.jl")
include("firingneurons_heterosynaptic_plasticity.jl")
include("firingneurons_structural_plasticity.jl")
include("connectivity_shared.jl")
include("rate_models.jl")
include("lif_current.jl")
include("lif_exponential.jl")
include("lif_conductance.jl")
include("inputs_shared.jl")
include("recorders_shared.jl")

#=
include("./hawkes.jl")
=#

end # of SpikingRNNs module


doc"""
`Molecule(; atoms=[], params...)`

creates an molecules with `atoms`.
"""
type Molecule <: Associative{Symbol,Any}
    name  ::AbstractString
    atoms ::Atoms                # atoms
    basis ::Basis                # basis
    g     ::MultiGrid            # integration grid
    d     ::Dict{Symbol,Any}     # additional properties
end

Base.delete!(m::Molecule, key)                                    = delete!(m.d, key)
Base.done(m::Molecule, i)                                         = done(m.d, i)
Base.empty!(m::Molecule)                                          = empty!(m.d)
Base.copy(m::Molecule)                                            = Atom(at.Z,at.R,copy(m.d))
Base.get(m::Molecule, key, default)                               = get(m.d, key, default)
Base.get(default::Union{DataType,Function}, m::Molecule, key)     = get(default, m.d, key)
Base.get!(m::Molecule, key0, default)                             = get!(m.d, key0, default)
Base.get!(default::Union{DataType,Function}, m::Molecule, key0)   = get!(default, m.d, key0)
Base.getindex(m::Molecule, key)                                   = getindex(m.d, key)
Base.getkey(m::Molecule, key, default)                            = getkey(m.d, key, default)
Base.haskey(m::Molecule, key)                                     = haskey(m.d, key)
Base.isempty(m::Molecule)                                         = isempty(m.d)
Base.length(m::Molecule)                                          = length(m.d)
Base.next(m::Molecule, i)                                         = next(m.d, i)
Base.pop!(m::Molecule, key)                                       = pop!(m.d, key)
Base.pop!(m::Molecule, key, default)                              = pop!(m.d, key, default)
Base.setindex!(m::Molecule, v0, key0)                             = setindex!(m.d, v0, key0)
Base.similar(m::Molecule)                                         = similar(m.d)
Base.sizehint!(m::Molecule, newsz)                                = sizehint!(m.d, newsz)
Base.start(m::Molecule)                                           = start(m.d)
Base.merge!(m::Molecule, d::Dict)                                 = begin merge!(m.d, d); d end
Base.merge(m::Molecule, d::Dict)                                  = Atom(merge(m.d, d))
Base.writemime(io::IO, mime::MIME"text/plain", m::Molecule)       = show_symbol_dict(io,m)
Base.summary(m::Molecule)                                         = string(typeof(m),"($(m.name))")
Base.show(io::IO, m::Molecule)                                    = print(io,summary(at))

function Base.call(::Type{Molecule};
                    name  ::AbstractString = "",
                    atoms ::Atoms          = Atom[],
                    params...)
    m = Molecule(name,atoms,NAOBasis(),MultiGrid(),Dict{Symbol,Any}(params))

    if !isempty(atoms)
        for at in atoms
            if !hasbasis(at)
                dft!(at)
                splineradial!(at)
                naobasis!(at)
            end

            if !hasgrid(at)
                makegrid!(at)
            end
        end

        delleypartition!(atoms)
        makegrid!(m)
        makebasis!(m)
    end

    m
end

function makegrid!(m::Molecule)
    m.g = multigrid(m.atoms)
end

function makebasis!(m::Molecule)
    m.basis = vcat([at[:basis] for at in m.atoms]...)
end

print_stats(m::Molecule) = print_stats(m[:stats])

function bondslice(atoms, ind)
    u = [atoms[ind[i+1]].R - atoms[ind[i]].R for i in 1:length(ind)-1]
    d = [norm(r) for r in u]
    u ./= d
    d = cumsum(d)
    function r(λ)
        i = findfirst(d .> λ)
        if i == 0; i = length(u) end
        atoms[ind[i+1]].R + (λ - d[i])*u[i]
    end
    d, f -> (λ -> f(r(λ))[])
end

bondslice(atoms) = bondslice(atoms, 1:length(atoms))


# note: the functions (in cross_validation.jl have major redundancies with runmodel_multi.jl
# we may want to improve this

function dtm(dtmtable::DTMTable, settingsVector::Vector{ModelSettings};file::String=joinpath(mktempdir(), defaultModelNameWtihCSVext))
    # set rnd state to make this function reporducible (irrespective of trn/val idx)
    # Random.srand should not depend on sett.seed as we do not 'store' the original seed in the resulting Excel file.
    nSettings = length(settingsVector)

    intDatahash = floor(Int, .25 * hash(2231, hash(dtmtable.features, hash(dtmtable.numerator, hash(dtmtable.denominator, hash(dtmtable.weight))))))
    Random.seed!(intDatahash)

   # initialize variables
    path_and_fn_wo_extension = "some"
    path_and_fn_wo_extension, ext = splitext(file)
    header = Vector{String}(undef, 1)
    header_settings = Vector{String}(undef, 1)
    i = 1

        # run an initial dummy model to get the 'structure' (ie header and size) of the output statistics
    dummySett = deepcopy(settingsVector[1])
    dummySett.print_details = false;dummySett.writeTree = false;dummySett.writeSasCode = false;dummySett.writeIterationMatrix = false;dummySett.writeResult = false;dummySett.writeStatistics = false;dummySett.boolCreateZipFile = false;dummySett.writeCsharpCode = false;dummySett.writeVbaCode = false;dummySett.saveJLDFile = false;dummySett.saveResultAsJLDFile = false;dummySett.showProgressBar_time = false;dummySett.prroduceEstAndLeafMatrices = false;dummySett.write_dot_graph = false;
    path_and_fn_wo_extension_mod = string(path_and_fn_wo_extension, "_0")
    fnmod = string(path_and_fn_wo_extension_mod, ext)
    dummySett.iterations = min(10, dummySett.iterations) # is this a good idea? I think we should be fine.
    somestrings, model = run_model_actual(dtmtable, dummySett, fnmod)
    desc, numbrs, desc_settingsvec, settingsvec, selectedPerformanceMeasure = get_stats(model, perfMeasure=settingsVector[1].performanceMeasure)
            # add first column
    pushfirst!(numbrs, i)
    pushfirst!(settingsvec, string(i))
    pushfirst!(desc, "Number")
    pushfirst!(desc_settingsvec, "Number")
    header = deepcopy(desc)
    header_settings = deepcopy(desc_settingsvec)
    allstats = Array{Float64,2}(undef, length(numbrs), nSettings)
    allsettings = Array{Any,2}(undef, length(settingsvec), nSettings)
    allmodels = Vector{Any}(undef, nSettings)
        # define default stats if the model fails
    defaulted_stats = deepcopy(numbrs)
    defaulted_settings = deepcopy(settingsvec)
    emptyModel = deepcopy(EmtpyDTModel())
    fill!(defaulted_stats, 0.0)

        # create a dictionary which contains all data
    di = Dict("ext" => ext, "emptyModel" => emptyModel, "defaulted_settings" => defaulted_settings, "header_settings" => header_settings, "header" => header, "intDatahash" => intDatahash, "settingsVector" => deepcopy(settingsVector), "dtmtable" => dtmtable, "path_and_fn_wo_extension" => path_and_fn_wo_extension, "defaulted_stats" => defaulted_stats)
        # send data to all workers: without this command we will have a lot of overhead to send the data to the processes; but the data is constant and only needs to be sent ONCE!)
    sendto_module(DecisionTrees, Distributed.workers(), local_data_dict=deepcopy(di))
        # if Distributed.nprocs()==1
    local_data_dict = di # this is needed in case there are no workers
        # end
        # run all models in parallel
        # @warn("this is currently rather slow as the local_data_dict might be transferred to each worker for each iteration -> improve this!.... ? global const variable....?")
    pmapresult = Distributed.pmap(iLoop->singleRunDtm(iLoop, local_data_dict), 1:length(settingsVector))

        # 2. run models
    for pmap_entry in pmapresult# this_sample in cvsampler
        numbrs = pmap_entry[1]
        settingsvec = pmap_entry[2]
        i = pmap_entry[3]
        mdl = pmap_entry[4]
            # append stats
        allstats[:,i] .= deepcopy(numbrs)
        allsettings[:,i] .= deepcopy(settingsvec)
        allmodels[i] = mdl
    end

        # 3. aggregate some statistics
    statsdf = DataFrame(transpose(allstats), :auto)
    settsdf = DataFrame(permutedims(allsettings, [2,1]), :auto)
    DataFrames.rename!(settsdf, Symbol.(header_settings))

    fld, namestr = splitdir(path_and_fn_wo_extension)
    filen = string(path_and_fn_wo_extension, "_multistats.xlsx")
    if isfile(filen)
        @info "Deleting $(filen)."
        rm(filen)
    end

        # calc some 'stats of stats'
    col_rng_stats = 2:size(statsdf, 2)
    means = map(x->Statistics.mean(statsdf[!,x]), col_rng_stats)
    medians = map(x->Statistics.median(statsdf[!,x]), col_rng_stats)
    mins = map(x->minimum(statsdf[!,x]), col_rng_stats)
    maxs = map(x->maximum(statsdf[!,x]), col_rng_stats)
    stds = map(x->StatsBase.std(statsdf[!,x]), col_rng_stats)
    sums = map(x->sum(statsdf[!,x]), col_rng_stats)

    stats_of_stats = hcat(means, medians, mins, maxs, stds, sums)
    titles = ["Mean","Median","Min","Max","Std Dev","Sum"]
    stats_of_stats = hcat(titles, transpose(stats_of_stats))
    stats_of_stats = vcat(repeat([""], 1, size(stats_of_stats, 2)), stats_of_stats)

    allstats_with_stats = vcat(transpose(allstats), stats_of_stats)
        # NOTE! statsdf is RE defined here!
    statsdf = DataFrame(allstats_with_stats, :auto)
    DataFrames.rename!(statsdf, Symbol.(header))

        # define Exceldata
    sh1 = ExcelSheet("settings", settsdf)
    sh2 = ExcelSheet("stats", statsdf)
    xld = ExcelData(ExcelSheet[sh1,sh2], Array{Chart,1}(undef, 0))
        # write data
    try
        println("Writing summary statistics to file:\r\n$(filen)")
        @time writeStatistics(xld, filen, true, false)
    catch e
        @show e
        @warn("DTM: Failed to create Excel Statistics file. You may want to check the PyCall installation and whether the required Python packages are installed. \r\n $(filen)")
    end

    return statsdf, settsdf, allmodels
end

function singleRunDtm(i::Int, local_data_dict::Dict)
    di = local_data_dict
    defaulted_settings = di["defaulted_settings"]
    defaulted_stats = di["defaulted_stats"]
    emptyModel = di["emptyModel"]

    try
        ext = di["ext"]
        header_settings = di["header_settings"]
        header = di["header"]
        intDatahash = di["intDatahash"]
        settingsVector = di["settingsVector"]
        dtmtable = di["dtmtable"]
        path_and_fn_wo_extension = di["path_and_fn_wo_extension"]

        sett = settingsVector[i]

        # set seed
        sett.seed = floor(Int, 0.31 * hash(intDatahash, hash(99812, hash(i))))
        # define path
        path_and_fn_wo_extension_mod = string(path_and_fn_wo_extension, "_", i)
        fnmod = string(path_and_fn_wo_extension_mod, ext)
        # run model
        somestrings, model = run_model_actual(dtmtable, sett, fnmod)
        desc, numbrs, desc_settingsvec, settingsvec, selectedPerformanceMeasure = get_stats(model, perfMeasure=sett.performanceMeasure)
        # add index as first column
        pushfirst!(numbrs, i)
        pushfirst!(settingsvec, string(i))
        pushfirst!(desc, "Number")
        pushfirst!(desc_settingsvec, "Number")
        # check if header and settings header are as expected
        if !all(header[2:end] .== desc[2:end])
            @warn("Model run $(i) returned an unexpected results vector:")
            @show desc
            @show header
        end
        if !all(header_settings[2:end] .== desc_settingsvec[2:end])
            @warn("Model run $(i) returned an unexpected results vector:")
            @show desc_settingsvec
            @show header_settings
        end
        return numbrs, settingsvec, i, model
    catch eri
        @warn("DTM: Model $(i) failed.")
        println(eri)
        return defaulted_stats, defaulted_settings, i, emptyModel
    end

    return nothing
end	 # singleRunDtm

function get_model_stats(resulting_model::DTModel, defaulted_modelstats_df::DataFrame, i::Int)
	thesemodelstats = deepcopy(resulting_model.modelstats)
	nm = DataFrame(ones(Int, size(thesemodelstats, 1), 1) .* i, :auto)
	DataFrames.names!(nm, Symbol[symbol("ModelNumber")])
	return  thesemodelstats = hcat(nm, thesemodelstats)
end

function createGridSearchSettings(sett::ModelSettings;args...)
    settingsVector = [sett]
	seen = []
	for (smember, valuelist) in args
		if in(smember, seen)
			error("DTM: (createGridSearchSettings) You provided multiple values for the field $(smember)")
		end
        push!(seen, smember)
        try
            sz = size(valuelist)
            @assert length(sz) == 1 "DTM: expected a vector for $(smember). You provided $(typeof(valuelist)). \r\n$(valuelist)"
            n = sz[1]
            @assert n > 0

            settingsVectorTemplate = deepcopy(settingsVector)

            for i = 1:n
                # set value for this 'batch'
                v = valuelist[i]
                thisSettingsBatch = deepcopy(settingsVectorTemplate)
                for kk = 1:length(thisSettingsBatch)
                    s = thisSettingsBatch[kk]
                    # updateSettingsMod!(s,smember=)
                    oldValue = getfield(s, smember)
                    if typeof(v) != typeof(oldValue)
                        valConverted = convertFromString(oldValue, string(v)) # tbd maybe string(v) could/should be replaced with v here
                        setfield!(s, smember, valConverted)
                    else
                        setfield!(s, smember, v)
                    end
                    res = checkIfSettingsAreValid(s)
                    @assert res == nothing "DTM: Some settings are invalid! Abort."
                end
                if i == 1
                    settingsVector = deepcopy(thisSettingsBatch)
                else
                    append!(settingsVector, thisSettingsBatch)
                end
            end
        catch e
            @show smember, size(valuelist)
            @show valuelist
            @show e
            throw("DTM: unable to create settings vector.")
        end
	end
	@assert length(unique(map(x->x.performanceMeasure, settingsVector))) == 1 "DTM: Perf measure must be the same for the entire settings Vector"
	println("DTM: Size of settings vector is $(length(settingsVector))")
    return settingsVector
end


function add_scal(n)

  reps = parse(Int64, ENV["LAMP_REPS"])

  A = randn(n,n)
  B = randn(n,n)

  Benchmarker.add_data(csv, "add", Benchmarker.measure(reps, add, A, B))
  Benchmarker.add_data(csv, "scal", Benchmarker.measure(reps, scal, A))

end

function add(A::DenseArray{Float64, 2}, B::DenseArray{Float64, 2})
  K = copy(A)
  X = copy(B)
  start::Float64 = 0.0
  finish::Float64 = 0.0
  Benchmarker.cachescrub()
  Benchmarker.gcscrub()
  start = time_ns()

  K = X + K

  finish = time_ns()
  return ((finish-start)*1e-9, K)
end

function scal(A::DenseArray{Float64, 2})
  K = copy(A)
  start::Float64 = 0.0
  finish::Float64 = 0.0
  Benchmarker.cachescrub()
  Benchmarker.gcscrub()
  start = time_ns()

  K = 3.0 * K

  finish = time_ns()
  return ((finish-start)*1e-9, K)
end


"""
Generic implementation of an unconstrained single-field FE space
Private fields and type parameters
"""
struct UnconstrainedFESpace <: SingleFieldFESpace
  nfree::Int
  ndirichlet::Int
  cell_dofs::AbstractArray
  cell_basis::CellField
  cell_dof_basis::CellDofBasis
  dirichlet_dof_tag::Vector{Int8}
  dirichlet_cells::Vector{Int}
  ntags::Int
end

# FESpace interface

constraint_style(::Type{<:UnconstrainedFESpace}) = Val{false}()

function num_free_dofs(f::UnconstrainedFESpace)
  f.nfree
end

function get_cell_basis(f::UnconstrainedFESpace)
  f.cell_basis
end

function zero_free_values(f::UnconstrainedFESpace)
  zeros(num_free_dofs(f))
end

# SingleFieldFESpace interface

function get_cell_dofs(f::UnconstrainedFESpace)
  f.cell_dofs
end

function get_cell_dof_basis(f::UnconstrainedFESpace)
  f.cell_dof_basis
end

function num_dirichlet_dofs(f::UnconstrainedFESpace)
  f.ndirichlet
end

function num_dirichlet_tags(f::UnconstrainedFESpace)
  f.ntags
end

function zero_dirichlet_values(f::UnconstrainedFESpace)
  zeros(num_dirichlet_dofs(f))
end

function get_dirichlet_dof_tag(f::UnconstrainedFESpace)
  f.dirichlet_dof_tag
end

function scatter_free_and_dirichlet_values(f::UnconstrainedFESpace,free_values,dirichlet_values)
  cell_dofs = get_cell_dofs(f)
  LocalToGlobalPosNegArray(cell_dofs,free_values,dirichlet_values)
end

function gather_free_and_dirichlet_values!(free_vals,dirichlet_vals,f::UnconstrainedFESpace,cell_vals)

  cell_dofs = get_cell_dofs(f)
  cache_vals = array_cache(cell_vals)
  cache_dofs = array_cache(cell_dofs)
  cells = 1:length(cell_vals)

  _free_and_dirichlet_values_fill!(
    free_vals,
    dirichlet_vals,
    cache_vals,
    cache_dofs,
    cell_vals,
    cell_dofs,
    cells)

  (free_vals,dirichlet_vals)
end

function gather_dirichlet_values!(dirichlet_vals,f::UnconstrainedFESpace,cell_vals)

  cell_dofs = get_cell_dofs(f)
  cache_vals = array_cache(cell_vals)
  cache_dofs = array_cache(cell_dofs)
  free_vals = zero_free_values(f)
  cells = f.dirichlet_cells

  _free_and_dirichlet_values_fill!(
    free_vals,
    dirichlet_vals,
    cache_vals,
    cache_dofs,
    cell_vals,
    cell_dofs,
    cells)

  dirichlet_vals
end

function  _free_and_dirichlet_values_fill!(
  free_vals,
  dirichlet_vals,
  cache_vals,
  cache_dofs,
  cell_vals,
  cell_dofs,
  cells)

  for cell in cells
    vals = getindex!(cache_vals,cell_vals,cell)
    dofs = getindex!(cache_dofs,cell_dofs,cell)
    for (i,dof) in enumerate(dofs)
      val = vals[i]
      if dof > 0
        free_vals[dof] = val
      elseif dof < 0
        dirichlet_vals[-dof] = val
      else
        @unreachable "dof ids either positibe or negative, not zero"
      end
    end
  end

end


# This file was generated, do not modify it. # hide
knn = machine(KnnPipe, Xtrain, ytrain)
multi = machine(MnPipe, Xtrain, ytrain)


export SCSMatrix, SCSData, SCSSettings, SCSSolution, SCSInfo, SCSCone, SCSVecOrMatOrSparse

SCSVecOrMatOrSparse = Union{VecOrMat, SparseMatrixCSC{Float64,Int}}

immutable SCSMatrix
    values::Ptr{Cdouble}
    rowval::Ptr{Int}
    colptr::Ptr{Int}
    m::Int
    n::Int
end

immutable SCSSettings
    normalize::Int # boolean, heuristic data rescaling: 1
    scale::Cdouble # if normalized, rescales by this factor: 5
    rho_x::Cdouble # x equality constraint scaling: 1e-3
    max_iters::Int # maximum iterations to take: 2500
    eps::Cdouble # convergence tolerance: 1e-3
    alpha::Cdouble # relaxation parameter: 1.8
    cg_rate::Cdouble # for indirect, tolerance goes down like (1/iter)^cg_rate: 2
    verbose::Int # boolean, write out progress: 1
    warm_start::Int # boolean, warm start (put initial guess in Sol struct): 0
end

immutable SCSData
    # A has m rows, n cols
    m::Int
    n::Int
    A::Ptr{SCSMatrix}
    # b is of size m, c is of size n
    b::Ptr{Cdouble}
    c::Ptr{Cdouble}
    stgs::Ptr{SCSSettings}
end

immutable SCSSolution
    x::Ptr{Void}
    y::Ptr{Void}
    s::Ptr{Void}
end

immutable SCSInfo
    iter::Int
    # We need to allocate 32 bytes for a character string, so we allocate 256 bits
    # of integer instead
    # TODO: Find a better way to do this
    status1::Int128
    status2::Int128

    statusVal::Int
    pobj::Cdouble
    dobj::Cdouble
    resPri::Cdouble
    resDual::Cdouble
    resInfeas::Cdouble
    resUnbdd::Cdouble
    relGap::Cdouble
    setupTime::Cdouble
    solveTime::Cdouble
end

immutable SCSCone
    f::Int # number of linear equality constraints
    l::Int # length of LP cone
    q::Ptr{Int} # array of second-order cone constraints
    qsize::Int # length of SOC array
    s::Ptr{Int} # array of semi-definite constraints
    ssize::Int # length of SD array
    ep::Int # number of primal exponential cone triples
    ed::Int # number of dual exponential cone triples
    p::Ptr{Cdouble} # array of power cone params
    psize::Int # length of power cone array
end

# TODO needs to be updated for newest constants
const status_map = Dict{Int, Symbol}(
    1 => :Optimal,
    -2 => :Infeasible,
    -1 => :Unbounded,
    -3 => :Indeterminate,
    -4 => :Error
)

type Solution
    x::Array{Float64, 1}
    y::Array{Float64, 1}
    s::Array{Float64, 1}
    status::Symbol
    ret_val::Int

    function Solution(x::Array{Float64, 1}, y::Array{Float64, 1}, s::Array{Float64, 1}, ret_val::Int)
        if haskey(status_map, ret_val)
            return new(x, y, s, status_map[ret_val], ret_val)
        else
            return new(x, y, s, :UnknownError, ret_val)
        end
    end
end
immutable Direct end
immutable Indirect end