// Copyright 2014 Wandoujia Inc. All Rights Reserved.
// Licensed under the MIT (MIT-LICENSE.txt) license.

package rdb

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"math"
	"strconv"
	"strings"
	"testing"
)

func AssertNoError(t *testing.T, err error) {
	if err == nil {
		return
	}

	t.Fatal(err)
}

func Assert(t *testing.T, b bool) {
	if b {
		return
	}
	t.Fatal("assertion failed")
}

func DecodeHexRdb(t *testing.T, s string, n int) map[string]*Entry {
	p, err := hex.DecodeString(strings.NewReplacer("\t", "", "\r", "", "\n", "", " ", "").Replace(s))
	AssertNoError(t, err)
	r := bytes.NewReader(p)
	l := NewLoader(r)
	AssertNoError(t, l.LoadHeader())
	entries := make(map[string]*Entry)
	var i int = 0
	for {
		e, err := l.LoadEntry()
		AssertNoError(t, err)
		if e == nil {
			break
		}
		Assert(t, e.DB == 0)
		entries[string(e.Key)] = e
		i++
	}
	AssertNoError(t, l.LoadChecksum())
	Assert(t, r.Len() == 0)
	Assert(t, len(entries) == i && i == n)
	return entries
}

func getobj(t *testing.T, entries map[string]*Entry, key string) (*Entry, interface{}) {
	e := entries[key]
	Assert(t, e != nil)
	val, err := DecodeDump(e.ValDump)
	AssertNoError(t, err)
	return e, val
}

/*
#!/bin/bash
./redis-cli flushall
for i in 1 255 256 65535 65536 2147483647 2147483648 4294967295 4294967296 -2147483648; do
    ./redis-cli set string_${i} ${i}
done
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadIntString(t *testing.T) {
	s := `
        524544495330303036fe00000a737472696e675f323535c1ff00000873747269
        6e675f31c0010011737472696e675f343239343936373239360a343239343936
        373239360011737472696e675f343239343936373239350a3432393439363732
        39350012737472696e675f2d32313437343833363438c200000080000c737472
        696e675f3635353335c2ffff00000011737472696e675f323134373438333634
        380a32313437343833363438000c737472696e675f3635353336c20000010000
        0a737472696e675f323536c100010011737472696e675f323134373438333634
        37c2ffffff7fffe49d9f131fb5c3b5
    `
	values := []int{1, 255, 256, 65535, 65536, 2147483647, 2147483648, 4294967295, 4294967296, -2147483648}
	entries := DecodeHexRdb(t, s, len(values))
	for _, value := range values {
		key := fmt.Sprintf("string_%d", value)
		_, obj := getobj(t, entries, key)
		val := obj.(String)
		Assert(t, bytes.Equal([]byte(val), []byte(strconv.Itoa(value))))
	}
}

/*
#!/bin/bash
./redis-cli flushall
./redis-cli set string_ttls string_ttls
./redis-cli expireat string_ttls 1500000000
./redis-cli set string_ttlms string_ttlms
./redis-cli pexpireat string_ttlms 1500000000000
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadStringTTL(t *testing.T) {
	s := `
        524544495330303036fe00fc0098f73e5d010000000c737472696e675f74746c
        6d730c737472696e675f74746c6d73fc0098f73e5d010000000b737472696e67
        5f74746c730b737472696e675f74746c73ffd15acd935a3fe949
    `
	expireat := uint64(1500000000000)
	entries := DecodeHexRdb(t, s, 2)
	keys := []string{"string_ttls", "string_ttlms"}
	for _, key := range keys {
		e, obj := getobj(t, entries, key)
		val := obj.(String)
		Assert(t, bytes.Equal([]byte(val), []byte(key)))
		Assert(t, e.ExpireAt == expireat)
	}
}

/*
#!/bin/bash
s="01"
for ((i=0;i<15;i++)); do
    s=$s$s
done
./redis-cli flushall
./redis-cli set string_long $s
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadLongString(t *testing.T) {
	s := `
        524544495330303036fe00000b737472696e675f6c6f6e67c342f28000010000
        02303130e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff
        01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff
        01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff
        01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff
        01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff
        01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff
        01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff
        01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0
        ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01e0ff01
        e0ff01e0ff01e0ff01e0ff01e03201013031ffdfdb02bd6d5da5e6
    `
	entries := DecodeHexRdb(t, s, 1)
	_, obj := getobj(t, entries, "string_long")
	val := []byte(obj.(String))
	for i := 0; i < (1 << 15); i++ {
		var c uint8 = '0'
		if i%2 != 0 {
			c = '1'
		}
		Assert(t, val[i] == c)
	}
}

/*
#!/bin/bash
./redis-cli flushall
for ((i=0;i<256;i++)); do
    ./redis-cli rpush list_lzf 0
    ./redis-cli rpush list_lzf 1
done
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadListZipmap(t *testing.T) {
	s := `
        524544495330303036fe000a086c6973745f6c7a66c31f440b040b0400000820
        0306000200f102f202e0ff03e1ff07e1ff07e1d90701f2ffff6a1c2d51c02301
        16
    `
	entries := DecodeHexRdb(t, s, 1)
	_, obj := getobj(t, entries, "list_lzf")
	val := obj.(List)
	Assert(t, len(val) == 512)
	for i := 0; i < 256; i++ {
		var s string = "0"
		if i%2 != 0 {
			s = "1"
		}
		Assert(t, string(val[i]) == s)
	}
}

/*
#!/bin/bash
./redis-cli flushall
for ((i=0;i<32;i++)); do
    ./redis-cli rpush list ${i}
done
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadList(t *testing.T) {
	s := `
        524544495330303036fe0001046c69737420c000c001c002c003c004c005c006
        c007c008c009c00ac00bc00cc00dc00ec00fc010c011c012c013c014c015c016
        c017c018c019c01ac01bc01cc01dc01ec01fff756ea1fa90adefe3
    `
	entries := DecodeHexRdb(t, s, 1)
	_, obj := getobj(t, entries, "list")
	val := obj.(List)
	Assert(t, len(val) == 32)
	for i := 0; i < 32; i++ {
		Assert(t, string(val[i]) == strconv.Itoa(i))
	}
}

/*
#!/bin/bash
./redis-cli flushall
for ((i=0;i<16;i++)); do
    ./redis-cli sadd set1 ${i}
done
for ((i=0;i<32;i++)); do
    ./redis-cli sadd set2 ${i}
done
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadSetAndSetIntset(t *testing.T) {
	s := `
        524544495330303036fe0002047365743220c016c00dc01bc012c01ac004c014
        c002c017c01dc01cc013c019c01ec008c006c000c001c007c00fc009c01fc00e
        c003c00ac015c010c00bc018c011c00cc0050b04736574312802000000100000
        0000000100020003000400050006000700080009000a000b000c000d000e000f
        00ff3a0a9697324d19c3
    `
	entries := DecodeHexRdb(t, s, 2)

	_, obj1 := getobj(t, entries, "set1")
	val1 := obj1.(Set)
	set1 := make(map[string]bool)
	for _, mem := range val1 {
		set1[string(mem)] = true
	}
	Assert(t, len(set1) == 16)
	Assert(t, len(set1) == len(val1))
	for i := 0; i < 16; i++ {
		_, ok := set1[strconv.Itoa(i)]
		Assert(t, ok)
	}

	_, obj2 := getobj(t, entries, "set2")
	val2 := obj2.(Set)
	set2 := make(map[string]bool)
	for _, mem := range val2 {
		set2[string(mem)] = true
	}
	Assert(t, len(set2) == 32)
	Assert(t, len(set2) == len(val2))
	for i := 0; i < 32; i++ {
		_, ok := set2[strconv.Itoa(i)]
		Assert(t, ok)
	}
}

/*
#!/bin/bash
./redis-cli flushall
for ((i=0;i<16;i++)); do
    ./redis-cli hset hash1 ${i}
done
for ((i=-16;i<16;i++)); do
    ./redis-cli hset hash2 ${i}
done
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadHashAndHashZiplist(t *testing.T) {
	s := `
        524544495330303036fe000405686173683220c00dc00dc0fcc0fcc0ffc0ffc0
        04c004c002c002c0fbc0fbc0f0c0f0c0f9c0f9c008c008c0fac0fac006c006c0
        00c000c001c001c0fec0fec007c007c0f6c0f6c00fc00fc009c009c0f7c0f7c0
        fdc0fdc0f1c0f1c0f2c0f2c0f3c0f3c00ec00ec003c003c00ac00ac00bc00bc0
        f8c0f8c00cc00cc0f5c0f5c0f4c0f4c005c0050d056861736831405151000000
        4d000000200000f102f102f202f202f302f302f402f402f502f502f602f602f7
        02f702f802f802f902f902fa02fa02fb02fb02fc02fc02fd02fd02fe0d03fe0d
        03fe0e03fe0e03fe0f03fe0fffffa423d3036c15e534
    `
	entries := DecodeHexRdb(t, s, 2)

	_, obj1 := getobj(t, entries, "hash1")
	val1 := obj1.(Hash)
	hash1 := make(map[string]string)
	for _, ent := range val1 {
		hash1[string(ent.Field)] = string(ent.Value)
	}
	Assert(t, len(hash1) == 16)
	Assert(t, len(hash1) == len(val1))
	for i := 0; i < 16; i++ {
		s := strconv.Itoa(i)
		Assert(t, hash1[s] == s)
	}

	_, obj2 := getobj(t, entries, "hash2")
	val2 := obj2.(Hash)
	hash2 := make(map[string]string)
	for _, ent := range val2 {
		hash2[string(ent.Field)] = string(ent.Value)
	}
	Assert(t, len(hash2) == 32)
	Assert(t, len(hash2) == len(val2))
	for i := -16; i < 16; i++ {
		s := strconv.Itoa(i)
		Assert(t, hash2[s] == s)
	}
}

/*
#!/bin/bash
./redis-cli flushall
for ((i=0;i<16;i++)); do
    ./redis-cli zadd zset1 ${i} ${i}
done
for ((i=0;i<32;i++)); do
    ./redis-cli zadd zset2 -${i} ${i}
done
./redis-cli save && xxd -p -c 32 dump.rdb
*/
func TestLoadZSetAndZSetZiplist(t *testing.T) {
	s := `
        524544495330303036fe0003057a7365743220c016032d3232c00d032d3133c0
        1b032d3237c012032d3138c01a032d3236c004022d34c014032d3230c002022d
        32c017032d3233c01d032d3239c01c032d3238c013032d3139c019032d3235c0
        1e032d3330c008022d38c006022d36c000022d30c001022d31c007022d37c009
        022d39c00f032d3135c01f032d3331c00e032d3134c003022d33c00a032d3130
        c015032d3231c010032d3136c00b032d3131c018032d3234c011032d3137c00c
        032d3132c005022d350c057a736574314051510000004d000000200000f102f1
        02f202f202f302f302f402f402f502f502f602f602f702f702f802f802f902f9
        02fa02fa02fb02fb02fc02fc02fd02fd02fe0d03fe0d03fe0e03fe0e03fe0f03
        fe0fffff2addedbf4f5a8f93
    `
	entries := DecodeHexRdb(t, s, 2)

	_, obj1 := getobj(t, entries, "zset1")
	val1 := obj1.(ZSet)
	zset1 := make(map[string]float64)
	for _, ent := range val1 {
		zset1[string(ent.Member)] = ent.Score
	}
	Assert(t, len(zset1) == 16)
	Assert(t, len(zset1) == len(val1))
	for i := 0; i < 16; i++ {
		s := strconv.Itoa(i)
		score, ok := zset1[s]
		Assert(t, ok)
		Assert(t, math.Abs(score-float64(i)) < 1e-10)
	}

	_, obj2 := getobj(t, entries, "zset2")
	val2 := obj2.(ZSet)
	zset2 := make(map[string]float64)
	for _, ent := range val2 {
		zset2[string(ent.Member)] = ent.Score
	}
	Assert(t, len(zset2) == 32)
	Assert(t, len(zset2) == len(val2))
	for i := 0; i < 32; i++ {
		s := strconv.Itoa(i)
		score, ok := zset2[s]
		Assert(t, ok)
		Assert(t, math.Abs(score+float64(i)) < 1e-10)
	}
}
