/*
 * Minio Go Library for Amazon S3 Compatible Cloud Storage (C) 2016 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 (
	"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 string, compareValue string) bool {
			return strings.ToUpper(setValue) == strings.ToUpper(compareValue)
		}, "Bar", `[bar]`},
		// Test to check match function doing prefix check.
		{func(setValue string, 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)
		}
	}
}
