package plist

import (
	"errors"
	"math"
	"reflect"
	"time"
)

type TestData struct {
	Name        string
	Value       interface{}
	DecodeValue interface{} // used when the document cannot encode parts of Value
	Documents   map[int][]byte
	SkipDecode  map[int]bool
	SkipEncode  map[int]bool
}

type SparseBundleHeader struct {
	InfoDictionaryVersion string `plist:"CFBundleInfoDictionaryVersion"`
	BandSize              uint64 `plist:"band-size"`
	BackingStoreVersion   int    `plist:"bundle-backingstore-version"`
	DiskImageBundleType   string `plist:"diskimage-bundle-type"`
	Size                  uint64 `plist:"size"`
}

type EmbedA struct {
	EmbedC
	EmbedB EmbedB
	FieldA string
}

type EmbedB struct {
	FieldB string
	*EmbedC
}

type EmbedC struct {
	FieldA1 string `plist:"FieldA"`
	FieldA2 string
	FieldB  string
	FieldC  string
}

type TextMarshalingBool struct {
	b bool
}

func (b TextMarshalingBool) MarshalText() ([]byte, error) {
	if b.b {
		return []byte("truthful"), nil
	}
	return []byte("non-factual"), nil
}

func (b *TextMarshalingBool) UnmarshalText(text []byte) error {
	if string(text) == "truthful" {
		b.b = true
	}
	return nil
}

type TextMarshalingBoolViaPointer struct {
	b bool
}

func (b *TextMarshalingBoolViaPointer) MarshalText() ([]byte, error) {
	if b.b {
		return []byte("plausible"), nil
	}
	return []byte("unimaginable"), nil
}

func (b *TextMarshalingBoolViaPointer) UnmarshalText(text []byte) error {
	if string(text) == "plausible" {
		b.b = true
	}
	return nil
}

type ArrayThatSerializesAsOneObject struct {
	values []uint64
}

func (f ArrayThatSerializesAsOneObject) MarshalPlist() (interface{}, error) {
	if len(f.values) == 1 {
		return f.values[0], nil
	}
	return f.values, nil
}

func (f *ArrayThatSerializesAsOneObject) UnmarshalPlist(unmarshal func(interface{}) error) error {
	var ui uint64
	if err := unmarshal(&ui); err == nil {
		f.values = []uint64{ui}
		return nil
	}

	return unmarshal(&f.values)
}

type PlistMarshalingBoolByPointer struct {
	b bool
}

func (b *PlistMarshalingBoolByPointer) MarshalPlist() (interface{}, error) {
	if b.b {
		return int64(-1), nil
	}
	return int64(-2), nil
}

func (b *PlistMarshalingBoolByPointer) UnmarshalPlist(unmarshal func(interface{}) error) error {
	var val int64
	err := unmarshal(&val)
	if err != nil {
		return err
	}

	b.b = val == -1
	return nil
}

type BothMarshaler struct{}

func (b *BothMarshaler) MarshalPlist() (interface{}, error) {
	return map[string]string{"a": "b"}, nil
}

func (b *BothMarshaler) MarshalText() ([]byte, error) {
	return []byte("shouldn't see this"), nil
}

type BothUnmarshaler struct {
	Blah int64 `plist:"blah,omitempty"`
}

func (b *BothUnmarshaler) UnmarshalPlist(unmarshal func(interface{}) error) error {
	// no error
	return nil
}

func (b *BothUnmarshaler) UnmarshalText(text []byte) error {
	return errors.New("shouldn't hit this")
}

var xmlPreamble = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
`

var tests = []TestData{
	{
		Name:  "String",
		Value: "Hello",
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`Hello`),
			GNUStepFormat:  []byte(`Hello`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><string>Hello</string></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 85, 72, 101, 108, 108, 111, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14},
		},
	},
	{
		Name: "Basic Structure",
		Value: struct {
			Name string
		}{
			Name: "Dustin",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{Name=Dustin;}`),
			GNUStepFormat:  []byte(`{Name=Dustin;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>Name</key><string>Dustin</string></dict></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 84, 78, 97, 109, 101, 86, 68, 117, 115, 116, 105, 110, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23},
		},
	},
	{
		Name: "Basic Structure with non-exported fields",
		Value: struct {
			Name string
			age  int
		}{
			Name: "Dustin",
			age:  24,
		},
		DecodeValue: struct {
			Name string
			age  int
		}{
			Name: "Dustin",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{Name=Dustin;}`),
			GNUStepFormat:  []byte(`{Name=Dustin;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>Name</key><string>Dustin</string></dict></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 84, 78, 97, 109, 101, 86, 68, 117, 115, 116, 105, 110, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23},
		},
	},
	{
		Name: "Basic Structure with omitted fields",
		Value: struct {
			Name string
			Age  int `plist:"-"`
		}{
			Name: "Dustin",
			Age:  24,
		},
		DecodeValue: struct {
			Name string
			Age  int `plist:"-"`
		}{
			Name: "Dustin",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{Name=Dustin;}`),
			GNUStepFormat:  []byte(`{Name=Dustin;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>Name</key><string>Dustin</string></dict></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 84, 78, 97, 109, 101, 86, 68, 117, 115, 116, 105, 110, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23},
		},
	},
	{
		Name: "Basic Structure with empty omitempty fields",
		Value: struct {
			Name      string
			Age       int     `plist:"age,omitempty"`
			Slice     []int   `plist:",omitempty"`
			Bool      bool    `plist:",omitempty"`
			Uint      uint    `plist:",omitempty"`
			Float32   float32 `plist:",omitempty"`
			Float64   float64 `plist:",omitempty"`
			Stringptr *string `plist:",omitempty"`
			Notempty  uint    `plist:",omitempty"`
		}{
			Name:     "Dustin",
			Notempty: 10,
		},
		DecodeValue: struct {
			Name      string
			Age       int     `plist:"age,omitempty"`
			Slice     []int   `plist:",omitempty"`
			Bool      bool    `plist:",omitempty"`
			Uint      uint    `plist:",omitempty"`
			Float32   float32 `plist:",omitempty"`
			Float64   float64 `plist:",omitempty"`
			Stringptr *string `plist:",omitempty"`
			Notempty  uint    `plist:",omitempty"`
		}{
			Name:     "Dustin",
			Notempty: 10,
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{Name=Dustin;Notempty=10;}`),
			GNUStepFormat:  []byte(`{Name=Dustin;Notempty=<*I10>;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>Name</key><string>Dustin</string><key>Notempty</key><integer>10</integer></dict></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x1, 0x2, 0x3, 0x4, 0x54, 0x4e, 0x61, 0x6d, 0x65, 0x58, 0x4e, 0x6f, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x56, 0x44, 0x75, 0x73, 0x74, 0x69, 0x6e, 0x10, 0xa, 0x8, 0xd, 0x12, 0x1b, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24},
		},
	},
	{
		Name: "Structure with Anonymous Embeds",
		Value: EmbedA{
			EmbedC: EmbedC{
				FieldA1: "",
				FieldA2: "",
				FieldB:  "A.C.B",
				FieldC:  "A.C.C",
			},
			EmbedB: EmbedB{
				FieldB: "A.B.B",
				EmbedC: &EmbedC{
					FieldA1: "A.B.C.A1",
					FieldA2: "A.B.C.A2",
					FieldB:  "", // Shadowed by A.B.B
					FieldC:  "A.B.C.C",
				},
			},
			FieldA: "A.A",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{EmbedB={FieldA="A.B.C.A1";FieldA2="A.B.C.A2";FieldB="A.B.B";FieldC="A.B.C.C";};FieldA="A.A";FieldA2="";FieldB="A.C.B";FieldC="A.C.C";}`),
			GNUStepFormat:  []byte(`{EmbedB={FieldA=A.B.C.A1;FieldA2=A.B.C.A2;FieldB=A.B.B;FieldC=A.B.C.C;};FieldA=A.A;FieldA2="";FieldB=A.C.B;FieldC=A.C.C;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>EmbedB</key><dict><key>FieldA</key><string>A.B.C.A1</string><key>FieldA2</key><string>A.B.C.A2</string><key>FieldB</key><string>A.B.B</string><key>FieldC</key><string>A.B.C.C</string></dict><key>FieldA</key><string>A.A</string><key>FieldA2</key><string/><key>FieldB</key><string>A.C.B</string><key>FieldC</key><string>A.C.C</string></dict></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd5, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xb, 0xc, 0xd, 0xe, 0x56, 0x45, 0x6d, 0x62, 0x65, 0x64, 0x42, 0x56, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x57, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x41, 0x32, 0x56, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x42, 0x56, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x43, 0xd4, 0x2, 0x3, 0x4, 0x5, 0x7, 0x8, 0x9, 0xa, 0x58, 0x41, 0x2e, 0x42, 0x2e, 0x43, 0x2e, 0x41, 0x31, 0x58, 0x41, 0x2e, 0x42, 0x2e, 0x43, 0x2e, 0x41, 0x32, 0x55, 0x41, 0x2e, 0x42, 0x2e, 0x42, 0x57, 0x41, 0x2e, 0x42, 0x2e, 0x43, 0x2e, 0x43, 0x53, 0x41, 0x2e, 0x41, 0x50, 0x55, 0x41, 0x2e, 0x43, 0x2e, 0x42, 0x55, 0x41, 0x2e, 0x43, 0x2e, 0x43, 0x8, 0x13, 0x1a, 0x21, 0x29, 0x30, 0x37, 0x40, 0x49, 0x52, 0x58, 0x60, 0x64, 0x65, 0x6b, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x71},
		},
	},
	{
		Name:  "Arbitrary Byte Data",
		Value: []byte{'h', 'e', 'l', 'l', 'o'},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`<68656c6c 6f>`),
			GNUStepFormat:  []byte(`<68656c6c 6f>`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><data>aGVsbG8=</data></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 69, 104, 101, 108, 108, 111, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14},
		},
	},
	{
		Name:  "Arbitrary Integer Slice",
		Value: []int{'h', 'e', 'l', 'l', 'o'},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(104,101,108,108,111,)`),
			GNUStepFormat:  []byte(`(<*I104>,<*I101>,<*I108>,<*I108>,<*I111>,)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><integer>104</integer><integer>101</integer><integer>108</integer><integer>108</integer><integer>111</integer></array></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 165, 1, 2, 3, 3, 4, 16, 104, 16, 101, 16, 108, 16, 111, 8, 14, 16, 18, 20, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22},
		},
	},
	{
		Name:  "Arbitrary Integer Array",
		Value: [3]int{'h', 'i', '!'},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(104,105,33,)`),
			GNUStepFormat:  []byte(`(<*I104>,<*I105>,<*I33>,)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><integer>104</integer><integer>105</integer><integer>33</integer></array></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 163, 1, 2, 3, 16, 104, 16, 105, 16, 33, 8, 12, 14, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18},
		},
	},
	{
		Name:  "Unsigned Integers of Increasing Size",
		Value: []uint64{0xff, 0xfff, 0xffff, 0xfffff, 0xffffff, 0xfffffff, 0xffffffff, 0x7fffffffffffffff, 0xdeadbeeffacecafe},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(255,4095,65535,1048575,16777215,268435455,4294967295,9223372036854775807,16045690985305262846,)`),
			GNUStepFormat:  []byte(`(<*I255>,<*I4095>,<*I65535>,<*I1048575>,<*I16777215>,<*I268435455>,<*I4294967295>,<*I9223372036854775807>,<*I16045690985305262846>,)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><integer>255</integer><integer>4095</integer><integer>65535</integer><integer>1048575</integer><integer>16777215</integer><integer>268435455</integer><integer>4294967295</integer><integer>9223372036854775807</integer><integer>16045690985305262846</integer></array></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xa9, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0xff, 0x11, 0x0f, 0xff, 0x11, 0xff, 0xff, 0x12, 0x00, 0x0f, 0xff, 0xff, 0x12, 0x00, 0xff, 0xff, 0xff, 0x12, 0x0f, 0xff, 0xff, 0xff, 0x12, 0xff, 0xff, 0xff, 0xff, 0x13, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, 0xfa, 0xce, 0xca, 0xfe, 0x08, 0x12, 0x14, 0x17, 0x1a, 0x1f, 0x24, 0x29, 0x2e, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48},
		},
	},
	{
		Name:  "Hexadecimal Integers",
		Value: []int{'h', 'e', 'x', 'i', 'n', 't', -42},
		Documents: map[int][]byte{
			XMLFormat: []byte(xmlPreamble + `<plist version="1.0"><array><integer>0x68</integer><integer>0X65</integer><integer>0x78</integer><integer>0X69</integer><integer>0x6e</integer><integer>0X74</integer><integer>-0x2a</integer></array></plist>`),
		},
		SkipEncode: map[int]bool{XMLFormat: true},
	},
	{
		Name:  "Octal Integers (treated as Decimal)",
		Value: []int{'o', 'c', 't', 'i', 'n', 't', -42},
		Documents: map[int][]byte{
			XMLFormat: []byte(xmlPreamble + `<plist version="1.0"><array><integer>0111</integer><integer>099</integer><integer>0116</integer><integer>0105</integer><integer>0110</integer><integer>0116</integer><integer>-042</integer></array></plist>`),
		},
		SkipEncode: map[int]bool{XMLFormat: true},
	},
	{
		Name:  "Floats of Increasing Bitness",
		Value: []interface{}{float32(math.MaxFloat32), float64(math.MaxFloat64)},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(3.4028234663852886e+38,1.7976931348623157e+308,)`),
			GNUStepFormat:  []byte(`(<*R3.4028234663852886e+38>,<*R1.7976931348623157e+308>,)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><real>3.4028234663852886e+38</real><real>1.7976931348623157e+308</real></array></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 162, 1, 2, 34, 127, 127, 255, 255, 35, 127, 239, 255, 255, 255, 255, 255, 255, 8, 11, 16, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25},
		},
		// We can't store varying bitness in text formats.
		SkipDecode: map[int]bool{XMLFormat: true, OpenStepFormat: true, GNUStepFormat: true},
	},
	{
		Name:  "Boolean True",
		Value: true,
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`1`),
			GNUStepFormat:  []byte(`<*BY>`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><true/></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 9, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9},
		},
	},
	{
		Name:  "Floating-Point Value",
		Value: 3.14159265358979323846264338327950288,
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`3.141592653589793`),
			GNUStepFormat:  []byte(`<*R3.141592653589793>`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><real>3.141592653589793</real></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 35, 64, 9, 33, 251, 84, 68, 45, 24, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17},
		},
	},
	{
		Name: "Map (containing arbitrary types)",
		Value: map[string]interface{}{
			"float":  1.0,
			"uint64": uint64(1),
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{float=1;uint64=1;}`),
			GNUStepFormat:  []byte(`{float=<*R1>;uint64=<*I1>;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>float</key><real>1</real><key>uint64</key><integer>1</integer></dict></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x1, 0x2, 0x3, 0x4, 0x55, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x23, 0x3f, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1, 0x8, 0xd, 0x13, 0x1a, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x25},
		},
		// Can't lax decode strings into numerics in a map (we don't know they want numbers)
		SkipDecode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name: "Map (containing all variations of all types)",
		Value: interface{}(map[string]interface{}{
			"intarray": []interface{}{
				int(1),
				int8(8),
				int16(16),
				int32(32),
				int64(64),
				uint(2),
				uint8(9),
				uint16(17),
				uint32(33),
				uint64(65),
			},
			"floats": []interface{}{
				float32(32.0),
				float64(64.0),
			},
			"booleans": []bool{
				true,
				false,
			},
			"strings": []string{
				"Hello, ASCII",
				"Hello, 世界",
			},
			"data": []byte{1, 2, 3, 4},
			"date": time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC),
		}),
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{booleans=(1,0,);data=<01020304>;date="2013-11-27 00:34:00 +0000";floats=(32,64,);intarray=(1,8,16,32,64,2,9,17,33,65,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}`),
			GNUStepFormat:  []byte(`{booleans=(<*BY>,<*BN>,);data=<01020304>;date=<*D2013-11-27 00:34:00 +0000>;floats=(<*R32>,<*R64>,);intarray=(<*I1>,<*I8>,<*I16>,<*I32>,<*I64>,<*I2>,<*I9>,<*I17>,<*I33>,<*I65>,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>booleans</key><array><true/><false/></array><key>data</key><data>AQIDBA==</data><key>date</key><date>2013-11-27T00:34:00Z</date><key>floats</key><array><real>32</real><real>64</real></array><key>intarray</key><array><integer>1</integer><integer>8</integer><integer>16</integer><integer>32</integer><integer>64</integer><integer>2</integer><integer>9</integer><integer>17</integer><integer>33</integer><integer>65</integer></array><key>strings</key><array><string>Hello, ASCII</string><string>Hello, 世界</string></array></dict></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd6, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb, 0xc, 0xf, 0x1a, 0x58, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x73, 0x54, 0x64, 0x61, 0x74, 0x61, 0x54, 0x64, 0x61, 0x74, 0x65, 0x56, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x73, 0x58, 0x69, 0x6e, 0x74, 0x61, 0x72, 0x72, 0x61, 0x79, 0x57, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0xa2, 0x8, 0x9, 0x9, 0x8, 0x44, 0x1, 0x2, 0x3, 0x4, 0x33, 0x41, 0xb8, 0x45, 0x75, 0x78, 0x0, 0x0, 0x0, 0xa2, 0xd, 0xe, 0x22, 0x42, 0x0, 0x0, 0x0, 0x23, 0x40, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaa, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x10, 0x1, 0x10, 0x8, 0x10, 0x10, 0x10, 0x20, 0x10, 0x40, 0x10, 0x2, 0x10, 0x9, 0x10, 0x11, 0x10, 0x21, 0x10, 0x41, 0xa2, 0x1b, 0x1c, 0x5c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x41, 0x53, 0x43, 0x49, 0x49, 0x69, 0x0, 0x48, 0x0, 0x65, 0x0, 0x6c, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x2c, 0x0, 0x20, 0x4e, 0x16, 0x75, 0x4c, 0x8, 0x15, 0x1e, 0x23, 0x28, 0x2f, 0x38, 0x40, 0x43, 0x44, 0x45, 0x4a, 0x53, 0x56, 0x5b, 0x64, 0x6f, 0x71, 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7d, 0x7f, 0x81, 0x83, 0x86, 0x93, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa6},
		},
		SkipDecode: map[int]bool{OpenStepFormat: true, GNUStepFormat: true, XMLFormat: true, BinaryFormat: true},
	},
	{
		Name: "Map (containing nil)",
		Value: map[string]interface{}{
			"float":  1.5,
			"uint64": uint64(1),
			"nil":    nil,
		},
		DecodeValue: map[string]interface{}{
			"float":  1.5,
			"uint64": uint64(1),
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{float=1.5;uint64=1;}`),
			GNUStepFormat:  []byte(`{float=<*R1.5>;uint64=<*I1>;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>float</key><real>1.5</real><key>uint64</key><integer>1</integer></dict></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd2, 0x1, 0x2, 0x3, 0x4, 0x55, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x23, 0x3f, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1, 0x8, 0xd, 0x13, 0x1a, 0x23, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x25},
		},
		// Can't lax decode strings into numerics in a map (we don't know they want numbers)
		SkipDecode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name: "Pointer to structure with plist tags",
		Value: &SparseBundleHeader{
			InfoDictionaryVersion: "6.0",
			BandSize:              8388608,
			Size:                  4 * 1048576 * 1024 * 1024,
			DiskImageBundleType:   "com.apple.diskimage.sparsebundle",
			BackingStoreVersion:   1,
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{CFBundleInfoDictionaryVersion="6.0";"band-size"=8388608;"bundle-backingstore-version"=1;"diskimage-bundle-type"="com.apple.diskimage.sparsebundle";size=4398046511104;}`),
			GNUStepFormat:  []byte(`{CFBundleInfoDictionaryVersion=6.0;band-size=<*I8388608>;bundle-backingstore-version=<*I1>;diskimage-bundle-type=com.apple.diskimage.sparsebundle;size=<*I4398046511104>;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key>CFBundleInfoDictionaryVersion</key><string>6.0</string><key>band-size</key><integer>8388608</integer><key>bundle-backingstore-version</key><integer>1</integer><key>diskimage-bundle-type</key><string>com.apple.diskimage.sparsebundle</string><key>size</key><integer>4398046511104</integer></dict></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd5, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0x5f, 0x10, 0x1d, 0x43, 0x46, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x44, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x72, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x59, 0x62, 0x61, 0x6e, 0x64, 0x2d, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x10, 0x1b, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2d, 0x62, 0x61, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x10, 0x15, 0x64, 0x69, 0x73, 0x6b, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2d, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x54, 0x73, 0x69, 0x7a, 0x65, 0x53, 0x36, 0x2e, 0x30, 0x12, 0x0, 0x80, 0x0, 0x0, 0x10, 0x1, 0x5f, 0x10, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x65, 0x2e, 0x64, 0x69, 0x73, 0x6b, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x73, 0x70, 0x61, 0x72, 0x73, 0x65, 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x13, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x13, 0x33, 0x3d, 0x5b, 0x73, 0x78, 0x7c, 0x81, 0x83, 0xa6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xaf},
		},
		SkipDecode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name: "Array of byte arrays",
		Value: [][]byte{
			[]byte("Hello"),
			[]byte("World"),
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(<48656c6c 6f>,<576f726c 64>,)`),
			GNUStepFormat:  []byte(`(<48656c6c 6f>,<576f726c 64>,)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><data>SGVsbG8=</data><data>V29ybGQ=</data></array></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 162, 1, 2, 69, 72, 101, 108, 108, 111, 69, 87, 111, 114, 108, 100, 8, 11, 17, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23},
		},
	},
	{
		Name:  "Date",
		Value: time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC),
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`"2013-11-27 00:34:00 +0000"`),
			GNUStepFormat:  []byte(`<*D2013-11-27 00:34:00 +0000>`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><date>2013-11-27T00:34:00Z</date></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 51, 65, 184, 69, 117, 120, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17},
		},
	},
	{
		Name:  "Floating-Point NaN",
		Value: math.NaN(),
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`NaN`),
			GNUStepFormat:  []byte(`<*RNaN>`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><real>nan</real></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 35, 127, 248, 0, 0, 0, 0, 0, 1, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17},
		},
		SkipDecode: map[int]bool{OpenStepFormat: true, GNUStepFormat: true, XMLFormat: true, BinaryFormat: true},
	},
	{
		Name:  "Floating-Point Infinity",
		Value: math.Inf(1),
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`+Inf`),
			GNUStepFormat:  []byte(`<*R+Inf>`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><real>inf</real></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 35, 127, 240, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17},
		},
	},
	{
		Name:  "Floating-Point Negative Infinity",
		Value: math.Inf(-1),
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`-Inf`),
			GNUStepFormat:  []byte(`<*R-Inf>`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><real>-inf</real></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 35, 255, 240, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17},
		},
	},
	{
		Name:  "UTF-8 string",
		Value: []string{"Hello, ASCII", "Hello, 世界"},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`("Hello, ASCII","Hello, \U4e16\U754c",)`),
			GNUStepFormat:  []byte(`("Hello, ASCII","Hello, \U4e16\U754c",)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><string>Hello, ASCII</string><string>Hello, 世界</string></array></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 162, 1, 2, 92, 72, 101, 108, 108, 111, 44, 32, 65, 83, 67, 73, 73, 105, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 78, 22, 117, 76, 8, 11, 24, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43},
		},
	},
	{
		Name:  "An array containing more than fifteen items",
		Value: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,)`),
			GNUStepFormat:  []byte(`(<*I1>,<*I2>,<*I3>,<*I4>,<*I5>,<*I6>,<*I7>,<*I8>,<*I9>,<*I10>,<*I11>,<*I12>,<*I13>,<*I14>,<*I15>,<*I16>,)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><integer>1</integer><integer>2</integer><integer>3</integer><integer>4</integer><integer>5</integer><integer>6</integer><integer>7</integer><integer>8</integer><integer>9</integer><integer>10</integer><integer>11</integer><integer>12</integer><integer>13</integer><integer>14</integer><integer>15</integer><integer>16</integer></array></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 175, 16, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 1, 16, 2, 16, 3, 16, 4, 16, 5, 16, 6, 16, 7, 16, 8, 16, 9, 16, 10, 16, 11, 16, 12, 16, 13, 16, 14, 16, 15, 16, 16, 8, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59},
		},
	},
	{
		Name:  "TextMarshaler/TextUnmarshaler",
		Value: TextMarshalingBool{true},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`truthful`),
			GNUStepFormat:  []byte(`truthful`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><string>truthful</string></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 88, 116, 114, 117, 116, 104, 102, 117, 108, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17},
		},
		// We expect false here because the non-pointer version cannot mutate itself.
	},
	{
		Name:  "TextMarshaler/TextUnmarshaler via Pointer",
		Value: &TextMarshalingBoolViaPointer{false},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`unimaginable`),
			GNUStepFormat:  []byte(`unimaginable`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><string>unimaginable</string></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 92, 117, 110, 105, 109, 97, 103, 105, 110, 97, 98, 108, 101, 8, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21},
		},
		DecodeValue: TextMarshalingBoolViaPointer{false},
	},
	{
		Name: "Duplicated Values",
		Value: []interface{}{
			"Hello",
			float32(32.0),
			float64(32.0),
			[]byte("data"),
			float32(64.0),
			float64(64.0),
			uint64(100),
			float32(32.0),
			time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC),
			float64(32.0),
			float32(64.0),
			float64(64.0),
			"Hello",
			[]byte("data"),
			uint64(100),
			time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC),
		},
		Documents: map[int][]byte{
			BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xaf, 0x10, 0x10, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x2, 0x8, 0x3, 0x5, 0x6, 0x1, 0x4, 0x7, 0x8, 0x55, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x42, 0x0, 0x0, 0x0, 0x23, 0x40, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x44, 0x64, 0x61, 0x74, 0x61, 0x22, 0x42, 0x80, 0x0, 0x0, 0x23, 0x40, 0x50, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x64, 0x33, 0x41, 0xb8, 0x45, 0x75, 0x78, 0x0, 0x0, 0x0, 0x8, 0x1b, 0x21, 0x26, 0x2f, 0x34, 0x39, 0x42, 0x44, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4d},
		},
	},
	{
		Name: "Funny Characters",
		Value: map[string]string{
			"\a":     "\b",
			"\v":     "\f",
			"\\":     "\"",
			"\t\r":   "\n",
			"\u00C8": "wat",
			"\u0100": "hundred",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{"\a"="\b";` + "\"\t\r\"=\"\n\";" + `"\v"="\f";"\\"="\"";"\310"=wat;"\U0100"=hundred;}`),
			GNUStepFormat:  []byte(`{"\a"="\b";` + "\"\t\r\"=\"\n\";" + `"\v"="\f";"\\"="\"";"\310"=wat;"\U0100"=hundred;}`),
		},
	},
	{
		Name:  "Signed Integers",
		Value: []int64{-1, -127, -255, -32767, -65535, -9223372036854775808},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(-1,-127,-255,-32767,-65535,-9223372036854775808,)`),
			GNUStepFormat:  []byte(`(<*I-1>,<*I-127>,<*I-255>,<*I-32767>,<*I-65535>,<*I-9223372036854775808>,)`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><array><integer>-1</integer><integer>-127</integer><integer>-255</integer><integer>-32767</integer><integer>-65535</integer><integer>-9223372036854775808</integer></array></plist>`),
			BinaryFormat:   []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xa6, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x13, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0f, 0x18, 0x21, 0x2a, 0x33, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45},
		},
	},
	{
		Name: "A map with a blank key",
		Value: map[string]string{
			"": "Hello",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{""=Hello;}`),
			GNUStepFormat:  []byte(`{""=Hello;}`),
			XMLFormat:      []byte(xmlPreamble + `<plist version="1.0"><dict><key/><string>Hello</string></dict></plist>`),
			BinaryFormat:   []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 80, 85, 72, 101, 108, 108, 111, 8, 11, 12, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18},
		},
	},
	{
		Name: "CF Keyed Archiver UIDs (interface{})",
		Value: []UID{
			0xff,
			0xffff,
			0xffffff,
			0xffffffff,
			0xffffffffff,
		},
		Documents: map[int][]byte{
			XMLFormat:    []byte(xmlPreamble + `<plist version="1.0"><array><dict><key>CF$UID</key><integer>255</integer></dict><dict><key>CF$UID</key><integer>65535</integer></dict><dict><key>CF$UID</key><integer>16777215</integer></dict><dict><key>CF$UID</key><integer>4294967295</integer></dict><dict><key>CF$UID</key><integer>1099511627775</integer></dict></array></plist>`),
			BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xa5, 0x01, 0x02, 0x03, 0x04, 0x05, 0x80, 0xff, 0x81, 0xff, 0xff, 0x83, 0x00, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff, 0x87, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x0e, 0x10, 0x13, 0x18, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26},
		},
	},
	{
		Name: "CF Keyed Archiver UID (struct)",
		Value: struct {
			U UID `plist:"identifier"`
		}{
			U: 1024,
		},
		Documents: map[int][]byte{
			XMLFormat:    []byte(xmlPreamble + `<plist version="1.0"><dict><key>identifier</key><dict><key>CF$UID</key><integer>1024</integer></dict></dict></plist>`),
			BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd1, 0x01, 0x02, 0x5a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x81, 0x04, 0x00, 0x08, 0x0b, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19},
		},
	},
	{
		Name: "CF Keyed Archiver UID as Legacy Int",
		Value: struct {
			U UID `plist:"identifier"`
		}{
			U: 1024,
		},
		Documents: map[int][]byte{
			XMLFormat:    []byte(xmlPreamble + `<plist version="1.0"><dict><key>identifier</key><dict><key>CF$UID</key><integer>1024</integer></dict></dict></plist>`),
			BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd1, 0x01, 0x02, 0x5a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x81, 0x04, 0x00, 0x08, 0x0b, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19},
		},
		DecodeValue: struct {
			U uint64 `plist:"identifier"`
		}{
			U: 1024,
		},
	},
	{
		Name: "Custom Marshaller/Unmarshaller by Value",
		Value: []ArrayThatSerializesAsOneObject{
			ArrayThatSerializesAsOneObject{[]uint64{100}},
			ArrayThatSerializesAsOneObject{[]uint64{2, 4, 6, 8}},
		},
		Documents: map[int][]byte{
			GNUStepFormat: []byte(`(<*I100>,(<*I2>,<*I4>,<*I6>,<*I8>,),)`),
		},
	},
	{
		Name:  "Custom Marshaller/Unmarshaller by Pointer",
		Value: &PlistMarshalingBoolByPointer{true},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`-1`),
			GNUStepFormat:  []byte(`<*I-1>`),
		},
	},
	{
		Name:  "Type implementing both Text and Plist Marshaler",
		Value: &BothMarshaler{},
		Documents: map[int][]byte{
			GNUStepFormat: []byte(`{a=b;}`),
		},
	},
	{
		Name:  "Type implementing both Text and Plist Unmarshaler",
		Value: &BothUnmarshaler{int64(1024)},
		Documents: map[int][]byte{
			GNUStepFormat: []byte(`{blah=<*I1024>;}`),
		},
		DecodeValue: &BothUnmarshaler{int64(0)},
	},
	{
		Name: "Comments",
		Value: struct {
			A, B, C int
			S, S2   string
		}{
			1, 2, 3,
			"/not/a/comment/", "/not*a/*comm*en/t",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{
				A=1 /* A is 1 because it is the first letter */;
				B=2; // B is 2 because comment-to-end-of-line.
				C=3;
				S = /not/a/comment/;
				S2 = /not*a/*comm*en/t;
			}`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name: "Escapes",
		Value: struct {
			W, A, B, V, F, T, R, N, Hex1, Unicode1, Unicode2, Octal1 string
		}{
			"w", "\a", "\b", "\v", "\f", "\t", "\r", "\n", "\u00ab", "\u00ac", "\u00ad", "\033",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`{
				W="\w";
				A="\a";
				B="\b";
				V="\v";
				F="\f";
				T="\t";
				R="\r";
				N="\n";
				Hex1="\xAB";
				Unicode1="\u00AC";
				Unicode2="\U00AD";
				Octal1="\033";
			}`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "Empty Strings in Arrays",
		Value: []string{"A"},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`(A,,,"",)`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "Empty Data",
		Value: []byte{},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`<>`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "UTF-8 with BOM",
		Value: "Hello",
		Documents: map[int][]byte{
			OpenStepFormat: []byte("\uFEFFHello"),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "UTF-16LE with BOM",
		Value: "Hello",
		Documents: map[int][]byte{
			OpenStepFormat: []byte{0xFF, 0xFE, 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0},
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "UTF-16BE with BOM",
		Value: "Hello",
		Documents: map[int][]byte{
			OpenStepFormat: []byte{0xFE, 0xFF, 0, 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o'},
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "UTF-16LE without BOM",
		Value: "Hello",
		Documents: map[int][]byte{
			OpenStepFormat: []byte{'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0},
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "UTF-16BE without BOM",
		Value: "Hello",
		Documents: map[int][]byte{
			OpenStepFormat: []byte{0, 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o'},
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "UTF-16BE with High Characters",
		Value: "Hello, 世界",
		Documents: map[int][]byte{
			OpenStepFormat: []byte{0, '"', 0, 'H', 0, 'e', 0, 'l', 0, 'l', 0, 'o', 0, ',', 0, ' ', 0x4E, 0x16, 0x75, 0x4C, 0, '"'},
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name: "Legacy Strings File Format (No Dictionary)",
		Value: map[string]string{
			"Key":  "Value",
			"Key2": "Value2",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`"Key" = "Value";
			"Key2" = "Value2";`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name: "Strings File Shortcut Format (No Values)",
		Value: map[string]string{
			"Key":  "Key",
			"Key2": "Key2",
		},
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`"Key";
			"Key2";`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "Various Truncated Escapes",
		Value: "\x01\x02\x03\x04\x057",
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`"\x1\u02\U003\4\0057"`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "Various Case-Insensitive Escapes",
		Value: "\u00AB\uCDEF",
		Documents: map[int][]byte{
			OpenStepFormat: []byte(`"\xaB\uCdEf"`),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "Text data long enough to trigger implementation-specific reallocation", // this is for coverage :(
		Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
		Documents: map[int][]byte{
			OpenStepFormat: []byte("<0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001>"),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "Empty Text Document",
		Value: map[string]interface{}{}, // Defined to be an empty dictionary
		Documents: map[int][]byte{
			OpenStepFormat: []byte{},
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name:  "Text document consisting of only whitespace",
		Value: map[string]interface{}{}, // Defined to be an empty dictionary
		Documents: map[int][]byte{
			OpenStepFormat: []byte(" \n\t"),
		},
		SkipEncode: map[int]bool{OpenStepFormat: true},
	},
	{
		Name: "Sized integers at size boundaries",
		Value: []interface{}{
			int8(-128),
			int8(127),
			int16(-32768),
			int16(32767),
			int32(-2147483648),
			int32(2147483647),
			int64(-9223372036854775808),
			int64(9223372036854775807),
		},
		DecodeValue: []interface{}{
			// interface decoding promotes all numbers to u/int64
			int64(-128),
			uint64(127),
			int64(-32768),
			uint64(32767),
			int64(-2147483648),
			uint64(2147483647),
			int64(-9223372036854775808),
			uint64(9223372036854775807),
		},
		Documents: map[int][]byte{
			BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xa8, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x10, 0x7f, 0x13, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x11, 0x7f, 0xff, 0x13, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x12, 0x7f, 0xff, 0xff, 0xff, 0x13, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x11, 0x1a, 0x1c, 0x25, 0x28, 0x31, 0x36, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48},
		},
	},
	{
		Name: "Duplicate Dictionary Keys",
		Value: map[string]interface{}{
			"key": "second value",
		},
		Documents: map[int][]byte{
			XMLFormat:      []byte(`<plist><dict><key>key</key><string>value</string><key>key</key><string>second value</string></dict></plist>`),
			OpenStepFormat: []byte(`{"key" = "value"; "key" = "second value";}`),
			GNUStepFormat:  []byte(`{"key" = "value"; "key" = "second value";}`),
			BinaryFormat: []byte{
				'b', 'p', 'l', 'i', 's', 't', '0', '0',

				0xD2, 0x01, 0x01, 0x02, 0x03,
				0x53, 'k', 'e', 'y',
				0x55, 'v', 'a', 'l', 'u', 'e',
				0x5C, 's', 'e', 'c', 'o', 'n', 'd', ' ', 'v', 'a', 'l', 'u', 'e',

				0x08, 0x0D, 0x11, 0x17,

				0x00, 0x00, 0x00, 0x00, 0x00,
				0x00,
				0x01,
				0x01,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
				0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24,
			},
		},
		SkipEncode: map[int]bool{XMLFormat: true, OpenStepFormat: true, GNUStepFormat: true, BinaryFormat: true},
	},
}

type EverythingTestData struct {
	Intarray []uint64  `plist:"intarray"`
	Floats   []float64 `plist:"floats"`
	Booleans []bool    `plist:"booleans"`
	Strings  []string  `plist:"strings"`
	Dat      []byte    `plist:"data"`
	Date     time.Time `plist:"date"`
}

var plistValueTreeRawData = &EverythingTestData{
	Intarray: []uint64{1, 8, 16, 32, 64, 2, 9, 17, 33, 65},
	Floats:   []float64{32.0, 64.0},
	Booleans: []bool{true, false},
	Strings:  []string{"Hello, ASCII", "Hello, 世界"},
	Dat:      []byte{1, 2, 3, 4},
	Date:     time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC),
}
var plistValueTree cfValue
var plistValueTreeAsBplist = []byte{98, 112, 108, 105, 115, 116, 48, 48, 214, 1, 13, 17, 21, 25, 27, 2, 14, 18, 22, 26, 28, 88, 105, 110, 116, 97, 114, 114, 97, 121, 170, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 1, 16, 8, 16, 16, 16, 32, 16, 64, 16, 2, 16, 9, 16, 17, 16, 33, 16, 65, 86, 102, 108, 111, 97, 116, 115, 162, 15, 16, 34, 66, 0, 0, 0, 35, 64, 80, 0, 0, 0, 0, 0, 0, 88, 98, 111, 111, 108, 101, 97, 110, 115, 162, 19, 20, 9, 8, 87, 115, 116, 114, 105, 110, 103, 115, 162, 23, 24, 92, 72, 101, 108, 108, 111, 44, 32, 65, 83, 67, 73, 73, 105, 0, 72, 0, 101, 0, 108, 0, 108, 0, 111, 0, 44, 0, 32, 78, 22, 117, 76, 84, 100, 97, 116, 97, 68, 1, 2, 3, 4, 84, 100, 97, 116, 101, 51, 65, 184, 69, 117, 120, 0, 0, 0, 8, 21, 30, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 68, 71, 76, 85, 94, 97, 98, 99, 107, 110, 123, 142, 147, 152, 157, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166}
var plistValueTreeAsXML = xmlPreamble + `<plist version="1.0"><dict><key>intarray</key><array><integer>1</integer><integer>8</integer><integer>16</integer><integer>32</integer><integer>64</integer><integer>2</integer><integer>9</integer><integer>17</integer><integer>33</integer><integer>65</integer></array><key>floats</key><array><real>32</real><real>64</real></array><key>booleans</key><array><true/><false/></array><key>strings</key><array><string>Hello, ASCII</string><string>Hello, 世界</string></array><key>data</key><data>AQIDBA==</data><key>date</key><date>2013-11-27T00:34:00Z</date></dict></plist>`
var plistValueTreeAsOpenStep = `{booleans=(1,0,);data=<01020304>;date="2013-11-27 00:34:00 +0000";floats=(32,64,);intarray=(1,8,16,32,64,2,9,17,33,65,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}`
var plistValueTreeAsGNUStep = `{booleans=(<*BY>,<*BN>,);data=<01020304>;date=<*D2013-11-27 00:34:00 +0000>;floats=(<*R32>,<*R64>,);intarray=(<*I1>,<*I8>,<*I16>,<*I32>,<*I64>,<*I2>,<*I9>,<*I17>,<*I33>,<*I65>,);strings=("Hello, ASCII","Hello, \U4e16\U754c",);}`

type LaxTestData struct {
	I64 int64
	U64 uint64
	F64 float64
	B   bool
	D   time.Time
}

var laxTestData = LaxTestData{1, 2, 3.0, true, time.Date(2013, 11, 27, 0, 34, 0, 0, time.UTC)}

func setupPlistValues() {
	plistValueTree = &cfDictionary{
		keys: []string{
			"intarray",
			"floats",
			"booleans",
			"strings",
			"data",
			"date",
		},
		values: []cfValue{
			&cfArray{
				values: []cfValue{
					&cfNumber{value: 1},
					&cfNumber{value: 8},
					&cfNumber{value: 16},
					&cfNumber{value: 32},
					&cfNumber{value: 64},
					&cfNumber{value: 2},
					&cfNumber{value: 8},
					&cfNumber{value: 17},
					&cfNumber{value: 33},
					&cfNumber{value: 65},
				},
			},
			&cfArray{
				values: []cfValue{
					&cfReal{wide: false, value: 32.0},
					&cfReal{wide: true, value: 64.0},
				},
			},
			&cfArray{
				values: []cfValue{
					cfBoolean(true),
					cfBoolean(false),
				},
			},
			&cfArray{
				values: []cfValue{
					cfString("Hello, ASCII"),
					cfString("Hello, 世界"),
				},
			},
			cfData{1, 2, 3, 4},
			cfDate(time.Date(2013, 11, 27, 0, 32, 0, 0, time.UTC)),
		},
	}
}

func init() {
	setupPlistValues()

	// Pre-warm the type info struct to remove it from benchmarking
	getTypeInfo(reflect.ValueOf(plistValueTreeRawData).Type())
}
