module Grisu
export print_shortest
export @grisu_ccall, NEG, DIGITS, BUFLEN, LEN, POINT

import Base.show, Base.print, Base.showcompact

const NEG    = Array(Bool)
const DIGITS = Array(Uint8,309+17)
const BUFLEN = int32(length(DIGITS)+1)
const LEN    = Array(Int32)
const POINT  = Array(Int32)

macro grisu_ccall(value, mode, ndigits)
    quote
        ccall((:grisu, :libgrisu), Void,
              (Float64, Int32, Int32, Ptr{Uint8}, Int32,
               Ptr{Bool}, Ptr{Int32}, Ptr{Int32}),
              $(esc(value)), $(esc(mode)), $(esc(ndigits)),
              DIGITS, BUFLEN, NEG, LEN, POINT)
    end
end

const SHORTEST        = int32(0) # shortest exact representation for doubles
const SHORTEST_SINGLE = int32(1) # shortest exact representation for singles
const FIXED           = int32(2) # fixed number of trailing decimal points
const PRECISION       = int32(3) # fixed precision regardless of magnitude

# wrapper for the core grisu function, primarily for debugging
function grisu(x::Float64, mode::Integer, ndigits::Integer)
    if !isfinite(x); error("non-finite value: $x"); end
    if ndigits < 0; error("negative digits requested"); end
    @grisu_ccall x mode ndigits
    NEG[1], DIGITS[1:LEN[1]], int(POINT[1])
end

grisu(x::Float64) = grisu(x, SHORTEST, int32(0))
grisu(x::Float32) = grisu(float64(x), SHORTEST_SINGLE, int32(0))
grisu(x::Real) = grisu(float(x))

function grisu_fix(x::Real, n::Integer)
    if n > 17; n = 17; end
    grisu(float64(x), FIXED, int32(n))
end
function grisu_sig(x::Real, n::Integer)
    if n > 309; n = 309; end
    grisu(float64(x), PRECISION, int32(n))
end

_show(io::IO, x::FloatingPoint, mode::Int32, n::Int, t) =
    _show(io, x, mode, n, t, "NaN", "Inf")
_show(io::IO, x::Float32, mode::Int32, n::Int, t) =
    _show(io, x, mode, n, t, "NaN32", "Inf32")
_show(io::IO, x::Float16, mode::Int32, n::Int, t) =
    _show(io, x, mode, n, t, "NaN16", "Inf16")

function _show(io::IO, x::FloatingPoint, mode::Int32, n::Int, typed, nanstr, infstr)
    if isnan(x) return write(io, typed ? nanstr : "NaN"); end
    if isinf(x)
        if x < 0 write(io,'-') end
        write(io, typed ? infstr : "Inf")
        return
    end
    if typed && isa(x,Float16) write(io, "float16("); end
    @grisu_ccall x mode n
    pdigits = pointer(DIGITS)
    neg = NEG[1]
    len = int(LEN[1])
    pt  = int(POINT[1])
    if mode == PRECISION
        while len > 1 && DIGITS[len] == '0'
            len -= 1
        end
    end
    if neg write(io,'-') end
    if pt <= -4 || pt > 6 # .00001 to 100000.
        # => #.#######e###
        write(io, pdigits, 1)
        write(io, '.')
        if len > 1
            write(io, pdigits+1, len-1)
        else
            write(io, '0')
        end
        write(io, typed && isa(x,Float32) ? 'f' : 'e')
        write(io, dec(pt-1))
        if typed && isa(x,Float16) write(io, ")"); end
        return
    elseif pt <= 0
        # => 0.00########
        write(io, "0.")
        while pt < 0
            write(io, '0')
            pt += 1
        end
        write(io, pdigits, len)
    elseif pt >= len
        # => ########00.0
        write(io, pdigits, len)
        while pt > len
            write(io, '0')
            len += 1
        end
        write(io, ".0")
    else # => ####.####
        write(io, pdigits, pt)
        write(io, '.')
        write(io, pdigits+pt, len-pt)
    end
    if typed && isa(x,Float32) write(io, "f0") end
    if typed && isa(x,Float16) write(io, ")"); end
    nothing
end

show(io::IO, x::Float64) = _show(io, x, SHORTEST, 0, true)
show(io::IO, x::Float32) = _show(io, x, SHORTEST_SINGLE, 0, true)
show(io::IO, x::Float16) = _show(io, x, PRECISION, 5, true)

print(io::IO, x::Float32) = _show(io, x, SHORTEST_SINGLE, 0, false)
print(io::IO, x::Float16) = _show(io, x, PRECISION, 5, false)

showcompact(io::IO, x::Float64) = _show(io, x, PRECISION, 6, false)
showcompact(io::IO, x::Float32) = _show(io, x, PRECISION, 6, false)
showcompact(io::IO, x::Float16) = _show(io, x, PRECISION, 5, false)

# normal:
#   0 < pt < len        ####.####           len+1
#   pt <= 0             .000########        len-pt+1
#   len <= pt (dot)     ########000.        pt+1
#   len <= pt (no dot)  ########000         pt
# exponential:
#   pt <= 0             ########e-###       len+k+2
#   0 < pt              ########e###        len+k+1

function _print_shortest(io::IO, x::FloatingPoint, dot::Bool, mode::Int32, n::Int)
    if isnan(x); return write(io, "NaN"); end
    if x < 0 write(io,'-') end
    if isinf(x); return write(io, "Inf"); end
    @grisu_ccall x mode n
    pdigits = pointer(DIGITS)
    len = int(LEN[1])
    pt  = int(POINT[1])
    e = pt-len
    k = -9<=e<=9 ? 1 : 2
    if -pt > k+1 || e+dot > k+1
        # => ########e###
        write(io, pdigits+0, len)
        write(io, 'e')
        write(io, dec(e))
        return
    elseif pt <= 0
        # => .000########
        write(io, '.')
        while pt < 0
            write(io, '0')
            pt += 1
        end
        write(io, pdigits+0, len)
    elseif e >= dot
        # => ########000.
        write(io, pdigits+0, len)
        while e > 0
            write(io, '0')
            e -= 1
        end
        if dot
            write(io, '.')
        end
    else # => ####.####
        write(io, pdigits+0, pt)
        write(io, '.')
        write(io, pdigits+pt, len-pt)
    end
    nothing
end

print_shortest(io::IO, x::Float64, dot::Bool) = _print_shortest(io, x, dot, SHORTEST, 0)
print_shortest(io::IO, x::Float32, dot::Bool) = _print_shortest(io, x, dot, SHORTEST_SINGLE, 0)
print_shortest(io::IO, x::Float16, dot::Bool=false) = _print_shortest(io, x, dot, PRECISION, 5)
print_shortest(io::IO, x::Union(FloatingPoint,Integer)) = print_shortest(io, float(x), false)

end # module
