/*
 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
 * Copyright 2015-2017 MinIO, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package set

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"testing"
)

// NewStringSet() is called and the result is validated.
func TestNewStringSet(t *testing.T) {
	if ss := NewStringSet(); !ss.IsEmpty() {
		t.Fatalf("expected: true, got: false")
	}
}

// CreateStringSet() is called and the result is validated.
func TestCreateStringSet(t *testing.T) {
	ss := CreateStringSet("foo")
	if str := ss.String(); str != `[foo]` {
		t.Fatalf("expected: %s, got: %s", `["foo"]`, str)
	}
}

// CopyStringSet() is called and the result is validated.
func TestCopyStringSet(t *testing.T) {
	ss := CreateStringSet("foo")
	sscopy := CopyStringSet(ss)
	if !ss.Equals(sscopy) {
		t.Fatalf("expected: %s, got: %s", ss, sscopy)
	}
}

// StringSet.Add() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetAdd(t *testing.T) {
	testCases := []struct {
		value          string
		expectedResult string
	}{
		// Test first addition.
		{"foo", `[foo]`},
		// Test duplicate addition.
		{"foo", `[foo]`},
		// Test new addition.
		{"bar", `[bar foo]`},
	}

	ss := NewStringSet()
	for _, testCase := range testCases {
		ss.Add(testCase.value)
		if str := ss.String(); str != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, str)
		}
	}
}

// StringSet.Remove() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetRemove(t *testing.T) {
	ss := CreateStringSet("foo", "bar")
	testCases := []struct {
		value          string
		expectedResult string
	}{
		// Test removing non-existen item.
		{"baz", `[bar foo]`},
		// Test remove existing item.
		{"foo", `[bar]`},
		// Test remove existing item again.
		{"foo", `[bar]`},
		// Test remove to make set to empty.
		{"bar", `[]`},
	}

	for _, testCase := range testCases {
		ss.Remove(testCase.value)
		if str := ss.String(); str != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, str)
		}
	}
}

// StringSet.Contains() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetContains(t *testing.T) {
	ss := CreateStringSet("foo")
	testCases := []struct {
		value          string
		expectedResult bool
	}{
		// Test to check non-existent item.
		{"bar", false},
		// Test to check existent item.
		{"foo", true},
		// Test to verify case sensitivity.
		{"Foo", false},
	}

	for _, testCase := range testCases {
		if result := ss.Contains(testCase.value); result != testCase.expectedResult {
			t.Fatalf("expected: %t, got: %t", testCase.expectedResult, result)
		}
	}
}

// StringSet.FuncMatch() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetFuncMatch(t *testing.T) {
	ss := CreateStringSet("foo", "bar")
	testCases := []struct {
		matchFn        func(string, string) bool
		value          string
		expectedResult string
	}{
		// Test to check match function doing case insensive compare.
		{func(setValue, compareValue string) bool {
			return strings.EqualFold(setValue, compareValue)
		}, "Bar", `[bar]`},
		// Test to check match function doing prefix check.
		{func(setValue, compareValue string) bool {
			return strings.HasPrefix(compareValue, setValue)
		}, "foobar", `[foo]`},
	}

	for _, testCase := range testCases {
		s := ss.FuncMatch(testCase.matchFn, testCase.value)
		if result := s.String(); result != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, result)
		}
	}
}

// StringSet.ApplyFunc() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetApplyFunc(t *testing.T) {
	ss := CreateStringSet("foo", "bar")
	testCases := []struct {
		applyFn        func(string) string
		expectedResult string
	}{
		// Test to apply function prepending a known string.
		{func(setValue string) string { return "mybucket/" + setValue }, `[mybucket/bar mybucket/foo]`},
		// Test to apply function modifying values.
		{func(setValue string) string { return setValue[1:] }, `[ar oo]`},
	}

	for _, testCase := range testCases {
		s := ss.ApplyFunc(testCase.applyFn)
		if result := s.String(); result != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, result)
		}
	}
}

// StringSet.Equals() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetEquals(t *testing.T) {
	testCases := []struct {
		set1           StringSet
		set2           StringSet
		expectedResult bool
	}{
		// Test equal set
		{CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar"), true},
		// Test second set with more items
		{CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar", "baz"), false},
		// Test second set with less items
		{CreateStringSet("foo", "bar"), CreateStringSet("bar"), false},
	}

	for _, testCase := range testCases {
		if result := testCase.set1.Equals(testCase.set2); result != testCase.expectedResult {
			t.Fatalf("expected: %t, got: %t", testCase.expectedResult, result)
		}
	}
}

// StringSet.Intersection() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetIntersection(t *testing.T) {
	testCases := []struct {
		set1           StringSet
		set2           StringSet
		expectedResult StringSet
	}{
		// Test intersecting all values.
		{CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar")},
		// Test intersecting all values in second set.
		{CreateStringSet("foo", "bar", "baz"), CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar")},
		// Test intersecting different values in second set.
		{CreateStringSet("foo", "baz"), CreateStringSet("baz", "bar"), CreateStringSet("baz")},
		// Test intersecting none.
		{CreateStringSet("foo", "baz"), CreateStringSet("poo", "bar"), NewStringSet()},
	}

	for _, testCase := range testCases {
		if result := testCase.set1.Intersection(testCase.set2); !result.Equals(testCase.expectedResult) {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, result)
		}
	}
}

// StringSet.Difference() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetDifference(t *testing.T) {
	testCases := []struct {
		set1           StringSet
		set2           StringSet
		expectedResult StringSet
	}{
		// Test differing none.
		{CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar"), NewStringSet()},
		// Test differing in first set.
		{CreateStringSet("foo", "bar", "baz"), CreateStringSet("foo", "bar"), CreateStringSet("baz")},
		// Test differing values in both set.
		{CreateStringSet("foo", "baz"), CreateStringSet("baz", "bar"), CreateStringSet("foo")},
		// Test differing all values.
		{CreateStringSet("foo", "baz"), CreateStringSet("poo", "bar"), CreateStringSet("foo", "baz")},
	}

	for _, testCase := range testCases {
		if result := testCase.set1.Difference(testCase.set2); !result.Equals(testCase.expectedResult) {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, result)
		}
	}
}

// StringSet.Union() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetUnion(t *testing.T) {
	testCases := []struct {
		set1           StringSet
		set2           StringSet
		expectedResult StringSet
	}{
		// Test union same values.
		{CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar")},
		// Test union same values in second set.
		{CreateStringSet("foo", "bar", "baz"), CreateStringSet("foo", "bar"), CreateStringSet("foo", "bar", "baz")},
		// Test union different values in both set.
		{CreateStringSet("foo", "baz"), CreateStringSet("baz", "bar"), CreateStringSet("foo", "baz", "bar")},
		// Test union all different values.
		{CreateStringSet("foo", "baz"), CreateStringSet("poo", "bar"), CreateStringSet("foo", "baz", "poo", "bar")},
	}

	for _, testCase := range testCases {
		if result := testCase.set1.Union(testCase.set2); !result.Equals(testCase.expectedResult) {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, result)
		}
	}
}

// StringSet.MarshalJSON() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetMarshalJSON(t *testing.T) {
	testCases := []struct {
		set            StringSet
		expectedResult string
	}{
		// Test set with values.
		{CreateStringSet("foo", "bar"), `["bar","foo"]`},
		// Test empty set.
		{NewStringSet(), "[]"},
	}

	for _, testCase := range testCases {
		if result, _ := testCase.set.MarshalJSON(); string(result) != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, string(result))
		}
	}
}

// StringSet.UnmarshalJSON() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetUnmarshalJSON(t *testing.T) {
	testCases := []struct {
		data           []byte
		expectedResult string
	}{
		// Test to convert JSON array to set.
		{[]byte(`["bar","foo"]`), `[bar foo]`},
		// Test to convert JSON string to set.
		{[]byte(`"bar"`), `[bar]`},
		// Test to convert JSON empty array to set.
		{[]byte(`[]`), `[]`},
		// Test to convert JSON empty string to set.
		{[]byte(`""`), `[]`},
	}

	for _, testCase := range testCases {
		var set StringSet
		set.UnmarshalJSON(testCase.data)
		if result := set.String(); result != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, result)
		}
	}
}

// StringSet.String() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetString(t *testing.T) {
	testCases := []struct {
		set            StringSet
		expectedResult string
	}{
		// Test empty set.
		{NewStringSet(), `[]`},
		// Test set with empty value.
		{CreateStringSet(""), `[]`},
		// Test set with value.
		{CreateStringSet("foo"), `[foo]`},
	}

	for _, testCase := range testCases {
		if str := testCase.set.String(); str != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, str)
		}
	}
}

// StringSet.ToSlice() is called with series of cases for valid and erroneous inputs and the result is validated.
func TestStringSetToSlice(t *testing.T) {
	testCases := []struct {
		set            StringSet
		expectedResult string
	}{
		// Test empty set.
		{NewStringSet(), `[]`},
		// Test set with empty value.
		{CreateStringSet(""), `[]`},
		// Test set with value.
		{CreateStringSet("foo"), `[foo]`},
		// Test set with value.
		{CreateStringSet("foo", "bar"), `[bar foo]`},
	}

	for _, testCase := range testCases {
		sslice := testCase.set.ToSlice()
		if str := fmt.Sprintf("%s", sslice); str != testCase.expectedResult {
			t.Fatalf("expected: %s, got: %s", testCase.expectedResult, str)
		}
	}
}

func TestStringSet_UnmarshalJSON(t *testing.T) {
	type args struct {
		data         []byte
		expectResult []string
	}
	tests := []struct {
		name    string
		set     StringSet
		args    args
		wantErr bool
	}{
		{
			name: "test strings",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`["foo","bar"]`),
				expectResult: []string{"foo", "bar"},
			},
			wantErr: false,
		},
		{
			name: "test string",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`"foo"`),
				expectResult: []string{"foo"},
			},
			wantErr: false,
		},
		{
			name: "test bools",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`[false,true]`),
				expectResult: []string{"false", "true"},
			},
			wantErr: false,
		},
		{
			name: "test bool",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`false`),
				expectResult: []string{"false"},
			},
			wantErr: false,
		},
		{
			name: "test ints",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`[1,2]`),
				expectResult: []string{"1", "2"},
			},
			wantErr: false,
		},
		{
			name: "test int",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`1`),
				expectResult: []string{"1"},
			},
			wantErr: false,
		},
		{
			name: "test floats",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`[1.1,2.2]`),
				expectResult: []string{"1.1", "2.2"},
			},
			wantErr: false,
		},
		{
			name: "test float",
			set:  NewStringSet(),
			args: args{
				data:         []byte(`1.1`),
				expectResult: []string{"1.1"},
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := tt.set.UnmarshalJSON(tt.args.data); (err != nil) != tt.wantErr {
				t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr)
			}
			slice := tt.set.ToSlice()
			sort.Slice(slice, func(i, j int) bool {
				return slice[i] < slice[j]
			})
			sort.Slice(tt.args.expectResult, func(i, j int) bool {
				return tt.args.expectResult[i] < tt.args.expectResult[j]
			})
			if !reflect.DeepEqual(slice, tt.args.expectResult) {
				t.Errorf("StringSet() get %v, want %v", tt.set.ToSlice(), tt.args.expectResult)
			}
		})
	}
}
