using MsgPack
using MsgPack: pack, unpack, Ext
using Test
using Random

ck_pack(a, b) = pack(a) == b && unpack(b) == a
round_trip(x) = unpack(pack(x))

# nil
@test ck_pack(nothing, [0xc0])

# bool
@test ck_pack(false, [0xc2])
@test ck_pack(true, [0xc3])

# positive fixint
@test ck_pack(5, [0x05])
# UInt8
@test ck_pack(128, [0xcc,0x80])
# UInt16
@test ck_pack(2^8, [0xcd,0x01,0x00])
# UInt32
@test ck_pack(2^16, [0xce,0x00,0x01,0x00,0x00])
# UInt64
@test ck_pack(2^32, [0xcf,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00])
@test ck_pack((UInt64(2)^64-1), [0xcf,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff])

# negative fixint
@test ck_pack(-5, [0xfb])
@test ck_pack(-32, [0xe0])
# Int8
@test ck_pack(-33, [0xd0,0xdf])
@test ck_pack(-2^7, [0xd0,0x80])
# Int16
@test ck_pack(-2^8, [0xd1,0xff,0x00])
@test ck_pack(-2^15, [0xd1,0x80,0x00])
# Int32
@test ck_pack(-2^16, [0xd2,0xff,0xff,0x00,0x00])
@test ck_pack(-2^31, [0xd2,0x80,0x00,0x00,0x00])
# Int64
@test ck_pack(-2^32, [0xd3,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00])
@test ck_pack(-2^63, [0xd3,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00])

# Float32
@test ck_pack((Float32(5.876)), [0xca, 0x40, 0xbc, 0x08, 0x31])
# Float64
@test ck_pack(0.654789321, [0xcb, 0x3f, 0xe4, 0xf4, 0x08, 0xbb, 0xee, 0xe1, 0xa8])

# fixstr
@test ck_pack("Меня зовут Шон",
              [0xba,0xd0,0x9c,0xd0,0xb5,0xd0,0xbd,0xd1,0x8f,
               0x20,0xd0,0xb7,0xd0,0xbe,0xd0,0xb2,0xd1,0x83,
               0xd1,0x82,0x20,0xd0,0xa8,0xd0,0xbe,0xd0,0xbd])
# str8 - currently unimplemented
# str16
s = randstring(2^8)
@test ck_pack(s, vcat(0xda, 0x01, 0x00, codeunits(s)))
s = randstring(2^16 - 1)
@test ck_pack(s, vcat(0xda, 0xff, 0xff, codeunits(s)))
# str32
s = randstring(2^16)
@test ck_pack(s, vcat(0xdb, 0x00, 0x01, 0x00, 0x00, codeunits(s)))
s = ""

# bin8
b = rand(UInt8, 121)
@test ck_pack(b, vcat(0xc4, 0x79, b))
# bin16
b = rand(UInt8, 2^11)
@test ck_pack(b, vcat(0xc5, 0x08, 0x00, b))
# bin32
b = rand(UInt8, 2^20)
@test ck_pack(b, vcat(0xc6, 0x00, 0x10, 0x00, 0x00, b))
b = []

# fixarray
@test ck_pack(Any[1, (Float32(2)), UInt8[0x22, 0x54]],
              [0x93, 0x01, 0xca, 0x40, 0x00, 0x00, 0x00, 0xc4, 0x02, 0x22, 0x54])
@test ck_pack(Any[nothing, "heya", true, false],
              [0x94, 0xc0, 0xa4, 0x68, 0x65, 0x79, 0x61, 0xc3, 0xc2])
@test ck_pack(Any[true, UInt8[0xff, 0xde, 0x11], [1, 2, 3]],
              [0x93, 0xc3, 0xc4, 0x03, 0xff, 0xde, 0x11, 0x93, 0x01, 0x02, 0x03])

# tuple
@test pack(Any[(1, [true, false], UInt8[0xff, 0xde])]) ==
              [0x91, 0x93, 0x01, 0x92, 0xc3, 0xc2, 0xc4, 0x02, 0xff, 0xde]
@test pack(("hi", ((Float32(2)), 0xff))) ==
              [0x92, 0xa2, 0x68, 0x69, 0x92, 0xca, 0x40, 0x00, 0x00, 0x00, 0xcc, 0xff]

# fixmap
@test ck_pack((Dict(1=>2, "hi"=>"mom")),
              [0x82,0xa2,0x68,0x69,0xa3,0x6d,0x6f,0x6d,0x01,0x02])


# Ext

# fixext 1
@test ck_pack(Ext(22, [0x61]), [0xd4, 0x16, 0x61])
# fixext 2
@test ck_pack(Ext(-32, [0x61, 0x61], impltype=true), [0xd5, 0xe0, 0x61, 0x61])
# fixext 4
@test ck_pack(
    Ext(22, [0x61, 0x61, 0x61, 0x61], impltype=true),
    [0xd6, 0x16, 0x61, 0x61, 0x61, 0x61]
)
# fixext 8
@test ck_pack(
    Ext(22, [0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61]),
    [0xd7, 0x16, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61]
)
# fixext 16
@test ck_pack(
    Ext(22, [
        0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
        0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61
    ]),
    [
        0xd8, 0x16,
        0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
        0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61
    ]
)

# no elements
@test ck_pack(Ext(-32, UInt8[], impltype=true), [0xc7, 0x00, 0xe0])
# ext 8
@test ck_pack(
    Ext(22, [0x61, 0x61, 0x61, 0x61, 0x61, 0x61]),
    [0xc7, 0x06, 0x16, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61]
)
# ext 16
b = rand(UInt8, 2^14)
@test ck_pack(Ext(78, b), vcat(0xc8, 0x40, 0x00, 0x4e, b))
# ext 32
b = rand(UInt8, 2^19)
@test ck_pack(
    Ext(-123, b, impltype=true),
    vcat(0xc9, 0x00, 0x08, 0x00, 0x00, 0x85, b)
)

# Custom type

struct A
    a::Int
    b::String
end

Base.:(==)(x::A, y::A) = x.a == y.a && x.b == y.b

MsgPack.register(A, 4)


struct B{T}
    a::T
end

Base.:(==)(x::B{T}, y::B{T}) where T = x.a == y.a

MsgPack.register(B, 5)

# Test generic encoding.

let
    x = [A(2, "hi"), A(3, "you")]
    @test round_trip(x) == x
end

let
    x = Dict(1 => A(3, "you"), 2 => A(2, "hi"))
    @test round_trip(x) == x
end


let
    x = [B(2), B(3)]
    @test round_trip(x) == x
end

let
    x = Dict(1 => B("you"), 2 => B("hi"))
    @test round_trip(x) == x
end


# Test custom encoding.

function MsgPack.encode(x::A)::Vector{UInt8}
    println("Use custom encoder for A")
    tmp = x.a, x.b
    MsgPack.pack(tmp)
end

function MsgPack.decode(::Type{A}, x::Vector{UInt8})::A
    println("Use custom decoder for A")
    a, b = MsgPack.unpack(x)
    A(a, b)
end

let
    x = [A(2, "hi"), A(3, "you")]
    @test round_trip(x) == x
end

let
    x = Dict(1 => A(3, "you"), 2 => A(2, "hi"))
    @test round_trip(x) == x
end


function MsgPack.encode(x::B)::Vector{UInt8}
    println("Use custom encoder for B")
    MsgPack.pack(x.a)
end

function MsgPack.decode(::Type{B}, x::Vector{UInt8})::B
    println("Use custom decoder for B")
    a = MsgPack.unpack(x)
    B(a)
end

let
    x = [B(2), B(3)]
    @test round_trip(x) == x
end

let
    x = Dict(1 => B("you"), 2 => B("hi"))
    @test round_trip(x) == x
end


