## UTF-32 in the native byte order, i.e. plain old character arrays ##

immutable UTF32String <: DirectIndexString
    data::Array{Char,1} # includes 32-bit NULL termination after string chars

    function UTF32String(a::Array{Char,1})
        if length(a) < 1 || a[end] != 0
            throw(ArgumentError("UTF32String data must be NULL-terminated"))
        end
        new(a)
    end
end

next(s::UTF32String, i::Int) = (s.data[i], i+1)
endof(s::UTF32String) = length(s.data) - 1
length(s::UTF32String) = length(s.data) - 1

function utf32(c::Integer...)
    a = Array(Char, length(c) + 1)
    for i = 1:length(c)
        a[i] = c[i]
    end
    a[end] = 0
    UTF32String(a)
end

utf32(x) = convert(UTF32String, x)
convert(::Type{UTF32String}, s::UTF32String) = s

function convert(::Type{UTF32String}, s::String)
    a = Array(Char, length(s) + 1)
    i = 0
    for c in s
        a[i += 1] = c
    end
    a[end] = 0 # NULL terminate
    UTF32String(a)
end

function convert(::Type{UTF32String}, data::AbstractVector{Char})
    len = length(data)
    d = Array(Char, len + 1)
    d[end] = 0 # NULL terminate
    UTF32String(copy!(d,1, data,1, len))
end

convert{T<:Union(Int32,Uint32)}(::Type{UTF32String}, data::AbstractVector{T}) =
    convert(UTF32String, reinterpret(Char, data))

convert{T<:String}(::Type{T}, v::AbstractVector{Char}) = convert(T, utf32(v))

# specialize for performance reasons:
function convert{T<:ByteString}(::Type{T}, data::AbstractVector{Char})
    s = IOBuffer(Array(Uint8,length(data)), true, true)
    truncate(s,0)
    for x in data
        print(s, x)
    end
    convert(T, takebuf_string(s))
end

convert(::Type{Array{Char,1}}, s::UTF32String) = s.data
convert(::Type{Array{Char}}, s::UTF32String) = s.data

reverse(s::UTF32String) = UTF32String(reverse!(copy(s.data), 1, length(s)))

sizeof(s::UTF32String) = sizeof(s.data) - sizeof(Char)
convert{T<:Union(Int32,Uint32,Char)}(::Type{Ptr{T}}, s::UTF32String) =
    convert(Ptr{T}, pointer(s))

function convert(T::Type{UTF32String}, bytes::AbstractArray{Uint8})
    isempty(bytes) && return UTF32String(Char[0])
    length(bytes) & 3 != 0 && throw(ArgumentError("need multiple of 4 bytes"))
    data = reinterpret(Char, bytes)    
    # check for byte-order mark (BOM):
    if data[1] == 0x0000feff        # native byte order
        d = Array(Char, length(data))
        copy!(d,1, data,2, length(data)-1)
    elseif data[1] == 0xfffe0000    # byte-swapped
        d = Array(Char, length(data))
        for i = 2:length(data)
            d[i-1] = bswap(data[i])
        end
    else
        d = Array(Char, length(data) + 1)
        copy!(d,1, data,1, length(data)) # assume native byte order
    end
    d[end] = 0 # NULL terminate
    UTF32String(d)
end

utf32(p::Ptr{Char}, len::Integer) = utf32(pointer_to_array(p, len))
utf32(p::Union(Ptr{Uint32}, Ptr{Int32}), len::Integer) = utf32(convert(Ptr{Char}, p), len)
function utf32(p::Union(Ptr{Char}, Ptr{Uint32}, Ptr{Int32}))
    len = 0
    while unsafe_load(p, len+1) != 0; len += 1; end
    utf32(p, len)
end

function map(f::Function, s::UTF32String)
    d = s.data
    out = similar(d)
    out[end] = 0

    for i = 1:(length(d)-1)
        c2 = f(d[i])
        if !isa(c2,Char)
            error("map(f,s::String) requires f to return Char; try map(f,collect(s)) or a comprehension instead")
        end
        out[i] = (c2::Char)
    end
    UTF32String(out)
end
