/*
NAME
  meta_test.go

AUTHOR
  Saxon Nelson-Milton <saxon@ausocean.org>

LICENSE
  meta_test.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean)

  It is free software: you can redistribute it and/or modify them
  under the terms of the GNU General Public License as published by the
  Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  It is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 for more details.

  You should have received a copy of the GNU General Public License
  along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
*/

package meta

import (
	"bytes"
	"encoding/binary"
	"reflect"
	"testing"
)

const (
	tstKey1  = "loc"
	tstData1 = "a,b,c"
	tstKey2  = "ts"
	tstData2 = "12345678"
	tstData3 = "d,e,f"
)

// TestAddAndGet ensures that we can add metadata and then successfully get it.
func TestAddAndGet(t *testing.T) {
	meta := New()
	meta.Add(tstKey1, tstData1)
	meta.Add(tstKey2, tstData2)
	if data, ok := meta.Get(tstKey1); !ok {
		t.Errorf("Could not get data for key: %v\n", tstKey1)
		if data != tstData1 {
			t.Error("Did not get expected data")
		}
	}

	if data, ok := meta.Get(tstKey2); !ok {
		t.Errorf("Could not get data for key: %v", tstKey2)
		if data != tstData2 {
			t.Error("Did not get expected data")
		}
	}
}

// TestUpdate checks that we can use Meta.Add to actually update metadata
// if it already exists in the Meta map.
func TestUpdate(t *testing.T) {
	meta := New()
	meta.Add(tstKey1, tstData1)
	meta.Add(tstKey1, tstData3)

	if data, ok := meta.Get(tstKey1); !ok {
		t.Errorf("Could not get data for key: %v\n", tstKey1)
		if data != tstData2 {
			t.Error(`Data did not correctly update for key "loc"`)
		}
	}
}

// TestAll ensures we can get a correct map using Meta.All() after adding some data
func TestAll(t *testing.T) {
	meta := New()
	tstMap := map[string]string{
		tstKey1: tstData1,
		tstKey2: tstData2,
	}

	meta.Add(tstKey1, tstData1)
	meta.Add(tstKey2, tstData2)
	metaMap := meta.All()

	if !reflect.DeepEqual(metaMap, tstMap) {
		t.Errorf("Map not correct. Got: %v, want: %v", metaMap, tstMap)
	}
}

// TestGetAbsentKey ensures that we get the expected error when we try to get with
// key that does not yet exist in the Meta map.
func TestGetAbsentKey(t *testing.T) {
	meta := New()

	if _, ok := meta.Get(tstKey1); ok {
		t.Error("Get for absent key incorrectly returned'ok'")
	}
}

// TestDelete ensures we can remove a data entry in the Meta map.
func TestDelete(t *testing.T) {
	meta := New()
	meta.Add(tstKey1, tstData1)
	meta.Delete(tstKey1)
	if _, ok := meta.Get(tstKey1); ok {
		t.Error("Get incorrectly returned okay for absent key")
	}
}

// TestEncode checks that we're getting the correct byte slice from Meta.Encode().
func TestEncode(t *testing.T) {
	meta := New()
	meta.Add(tstKey1, tstData1)
	meta.Add(tstKey2, tstData2)

	dataLen := len(tstKey1+tstData1+tstKey2+tstData2) + 3
	header := [4]byte{
		0x00,
		0x10,
	}
	binary.BigEndian.PutUint16(header[2:4], uint16(dataLen))
	expectedOut := append(header[:], []byte(
		tstKey1+"="+tstData1+"\t"+
			tstKey2+"="+tstData2)...)

	got := meta.Encode()
	if !bytes.Equal(expectedOut, got) {
		t.Errorf("Did not get expected out. \nGot : %v, \nwant: %v\n", got, expectedOut)
	}
}

// TestGetFrom checks that we can correctly obtain a value for a partiular key
// from a string of metadata using the ReadFrom func.
func TestGetFrom(t *testing.T) {
	tstMeta := append([]byte{0x00, 0x10, 0x00, 0x12}, "loc=a,b,c\tts=12345"...)

	tests := []struct {
		key  string
		want string
	}{
		{
			"loc",
			"a,b,c",
		},
		{
			"ts",
			"12345",
		},
	}

	for _, test := range tests {
		got, err := Get(test.key, []byte(tstMeta))
		if err != nil {
			t.Errorf("Unexpected err: %v\n", err)
		}
		if got != test.want {
			t.Errorf("Did not get expected out. \nGot : %v, \nwant: %v\n", got, test.want)
		}
	}
}

// TestGetAll checks that meta.GetAll can correctly get all metadata
// from descriptor data.
func TestGetAll(t *testing.T) {
	tstMeta := append([]byte{0x00, 0x10, 0x00, 0x12}, "loc=a,b,c\tts=12345"...)
	want := [][2]string{
		{
			"loc",
			"a,b,c",
		},
		{
			"ts",
			"12345",
		},
	}
	got, err := GetAll(tstMeta)
	if err != nil {
		t.Errorf("Unexpected error: %v\n", err)
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Did not get expected out. \nGot : %v, \nWant: %v\n", got, want)
	}
}

// TestGetAllAsMap checks that GetAllAsMap will correctly return a map of meta
// keys and values from a slice of meta.
func TestGetAllAsMap(t *testing.T) {
	tstMeta := append([]byte{0x00, 0x10, 0x00, 0x12}, "loc=a,b,c\tts=12345"...)
	want := map[string]string{
		"loc": "a,b,c",
		"ts":  "12345",
	}
	got, err := GetAllAsMap(tstMeta)
	if err != nil {
		t.Errorf("Unexpected error: %v\n", err)
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Did not get expected out. \nGot : %v, \nWant: %v\n", got, want)
	}
}

// TestKeys checks that we can successfully get keys from some metadata using
// the meta.Keys method.
func TestKeys(t *testing.T) {
	tstMeta := append([]byte{0x00, 0x10, 0x00, 0x12}, "loc=a,b,c\tts=12345"...)
	want := []string{"loc", "ts"}
	got, err := Keys(tstMeta)
	if err != nil {
		t.Errorf("Unexpected error: %v\n", err)
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Did not get expected out. \nGot : %v, \nWant: %v\n", got, want)
	}
}

// TestNewFromMap checks that we can successfully create a new data struct from a map.
func TestNewFromMap(t *testing.T) {
	want := map[string]string{
		"loc": "a,b,c",
		"ts":  "12345",
	}

	meta := NewFromMap(want)

	got := meta.All()

	if !reflect.DeepEqual(got, want) {
		t.Errorf("Did not get expected out. \nGot : %v, \nWant: %v\n", got, want)
	}
}

// TestEncodeAsString checks that metadata is correctly encoded as a string.
func TestEncodeAsString(t *testing.T) {
	meta := NewFromMap(map[string]string{
		"loc": "a,b,c",
		"ts":  "12345",
	})

	got := meta.EncodeAsString()
	want1 := "loc=a,b,c\tts=12345"
	want2 := "ts=12345\tloc=a,b,c"

	if got != want1 && got != want2 {
		t.Errorf("Did not get expected out. \nGot : %v, \nWant: %v or %v\n", got, want1, want2)
	}
}

// TestDeleteOrder checks that the order of keys is correct after a deletion.
func TestDeleteOrder(t *testing.T) {
	tests := []struct {
		delKey string
		want   []string
	}{
		{
			"key1",
			[]string{"key2", "key3", "key4"},
		},
		{
			"key2",
			[]string{"key1", "key3", "key4"},
		},
		{
			"key3",
			[]string{"key1", "key2", "key4"},
		},
		{
			"key4",
			[]string{"key1", "key2", "key3"},
		},
	}

	for _, test := range tests {
		t.Logf("deleting %s", test.delKey)
		meta := NewWith([][2]string{
			{"key1", "val1"},
			{"key2", "val2"},
			{"key3", "val3"},
			{"key4", "val4"},
		})
		meta.Delete(test.delKey)

		got := meta.order
		want := test.want
		if !reflect.DeepEqual(got, want) {
			t.Errorf("Did not get expected out. \nGot:  %v, \nWant: %v\n", got, want)
		}
	}
}