// Copyright 2014 Google Inc. All rights reserved.
//
// 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 pathtools

import (
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

var pwd, _ = os.Getwd()

var globTestCases = []struct {
	pattern  string
	matches  []string
	excludes []string
	dirs     []string
	err      error
}{
	// Current directory tests
	{
		pattern: "*",
		matches: []string{"a", "b", "c", "d.ext", "e.ext"},
		dirs:    []string{"."},
	},
	{
		pattern: "*.ext",
		matches: []string{"d.ext", "e.ext"},
		dirs:    []string{"."},
	},
	{
		pattern: "*/a",
		matches: []string{"a/a", "b/a"},
		dirs:    []string{".", "a", "b", "c"},
	},
	{
		pattern: "*/*/a",
		matches: []string{"a/a/a"},
		dirs:    []string{".", "a", "b", "c", "a/a", "a/b", "c/f", "c/g", "c/h"},
	},
	{
		pattern: "*/a/a",
		matches: []string{"a/a/a"},
		dirs:    []string{".", "a", "b", "c", "a/a"},
	},

	// ./ directory tests
	{
		pattern: "./*",
		matches: []string{"a", "b", "c", "d.ext", "e.ext"},
		dirs:    []string{"."},
	},
	{
		pattern: "./*.ext",
		matches: []string{"d.ext", "e.ext"},
		dirs:    []string{"."},
	},
	{
		pattern: "./*/a",
		matches: []string{"a/a", "b/a"},
		dirs:    []string{".", "a", "b", "c"},
	},
	{
		pattern: "./[ac]/a",
		matches: []string{"a/a"},
		dirs:    []string{".", "a", "c"},
	},

	// subdirectory tests
	{
		pattern: "c/*/*.ext",
		matches: []string{"c/f/f.ext", "c/g/g.ext"},
		dirs:    []string{"c", "c/f", "c/g", "c/h"},
	},
	{
		pattern: "a/*/a",
		matches: []string{"a/a/a"},
		dirs:    []string{"a", "a/a", "a/b"},
	},

	// absolute tests
	{
		pattern: filepath.Join(pwd, "testdata/c/*/*.ext"),
		matches: []string{
			filepath.Join(pwd, "testdata/c/f/f.ext"),
			filepath.Join(pwd, "testdata/c/g/g.ext"),
		},
		dirs: []string{
			filepath.Join(pwd, "testdata/c"),
			filepath.Join(pwd, "testdata/c/f"),
			filepath.Join(pwd, "testdata/c/g"),
			filepath.Join(pwd, "testdata/c/h"),
		},
	},

	// no-wild tests
	{
		pattern: "a",
		matches: []string{"a"},
		dirs:    nil,
	},
	{
		pattern: "a/a",
		matches: []string{"a/a"},
		dirs:    nil,
	},

	// clean tests
	{
		pattern: "./c/*/*.ext",
		matches: []string{"c/f/f.ext", "c/g/g.ext"},
		dirs:    []string{"c", "c/f", "c/g", "c/h"},
	},
	{
		pattern: "c/../c/*/*.ext",
		matches: []string{"c/f/f.ext", "c/g/g.ext"},
		dirs:    []string{"c", "c/f", "c/g", "c/h"},
	},

	// recursive tests
	{
		pattern: "**/a",
		matches: []string{"a", "a/a", "a/a/a", "b/a"},
		dirs:    []string{".", "a", "a/a", "a/b", "b", "c", "c/f", "c/g", "c/h"},
	},
	{
		pattern: "a/**/a",
		matches: []string{"a/a", "a/a/a"},
		dirs:    []string{"a", "a/a", "a/b"},
	},
	{
		pattern: "a/**/*",
		matches: []string{"a/a", "a/b", "a/a/a", "a/b/b"},
		dirs:    []string{"a", "a/a", "a/b"},
	},

	// absolute recursive tests
	{
		pattern: filepath.Join(pwd, "testdata/**/*.ext"),
		matches: []string{
			filepath.Join(pwd, "testdata/d.ext"),
			filepath.Join(pwd, "testdata/e.ext"),
			filepath.Join(pwd, "testdata/c/f/f.ext"),
			filepath.Join(pwd, "testdata/c/g/g.ext"),
		},
		dirs: []string{
			filepath.Join(pwd, "testdata"),
			filepath.Join(pwd, "testdata/a"),
			filepath.Join(pwd, "testdata/a/a"),
			filepath.Join(pwd, "testdata/a/b"),
			filepath.Join(pwd, "testdata/b"),
			filepath.Join(pwd, "testdata/c"),
			filepath.Join(pwd, "testdata/c/f"),
			filepath.Join(pwd, "testdata/c/g"),
			filepath.Join(pwd, "testdata/c/h"),
		},
	},

	// recursive error tests
	{
		pattern: "**/**/*",
		err:     GlobMultipleRecursiveErr,
	},
	{
		pattern: "a/**/**/*",
		err:     GlobMultipleRecursiveErr,
	},
	{
		pattern: "**/a/**/*",
		err:     GlobMultipleRecursiveErr,
	},
	{
		pattern: "**/**/a/*",
		err:     GlobMultipleRecursiveErr,
	},
	{
		pattern: "a/**",
		err:     GlobLastRecursiveErr,
	},
	{
		pattern: "**/**",
		err:     GlobLastRecursiveErr,
	},

	// exclude tests
	{
		pattern:  "*.ext",
		excludes: []string{"d.ext"},
		matches:  []string{"e.ext"},
		dirs:     []string{"."},
	},
	{
		pattern:  "*/*",
		excludes: []string{"a/b"},
		matches:  []string{"a/a", "b/a", "c/c", "c/f", "c/g", "c/h"},
		dirs:     []string{".", "a", "b", "c"},
	},
	{
		pattern:  "*/*",
		excludes: []string{"a/b", "c/c"},
		matches:  []string{"a/a", "b/a", "c/f", "c/g", "c/h"},
		dirs:     []string{".", "a", "b", "c"},
	},
	{
		pattern:  "*/*",
		excludes: []string{"c/*", "*/a"},
		matches:  []string{"a/b"},
		dirs:     []string{".", "a", "b", "c"},
	},
	{
		pattern:  "*/*",
		excludes: []string{"*/*"},
		matches:  nil,
		dirs:     []string{".", "a", "b", "c"},
	},

	// absolute exclude tests
	{
		pattern:  filepath.Join(pwd, "testdata/c/*/*.ext"),
		excludes: []string{filepath.Join(pwd, "testdata/c/*/f.ext")},
		matches: []string{
			filepath.Join(pwd, "testdata/c/g/g.ext"),
		},
		dirs: []string{
			filepath.Join(pwd, "testdata/c"),
			filepath.Join(pwd, "testdata/c/f"),
			filepath.Join(pwd, "testdata/c/g"),
			filepath.Join(pwd, "testdata/c/h"),
		},
	},
	{
		pattern:  filepath.Join(pwd, "testdata/c/*/*.ext"),
		excludes: []string{filepath.Join(pwd, "testdata/c/f/*.ext")},
		matches: []string{
			filepath.Join(pwd, "testdata/c/g/g.ext"),
		},
		dirs: []string{
			filepath.Join(pwd, "testdata/c"),
			filepath.Join(pwd, "testdata/c/f"),
			filepath.Join(pwd, "testdata/c/g"),
			filepath.Join(pwd, "testdata/c/h"),
		},
	},

	// recursive exclude tests
	{
		pattern:  "*.ext",
		excludes: []string{"**/*.ext"},
		matches:  nil,
		dirs:     []string{"."},
	},
	{
		pattern:  "*/*",
		excludes: []string{"**/b"},
		matches:  []string{"a/a", "b/a", "c/c", "c/f", "c/g", "c/h"},
		dirs:     []string{".", "a", "b", "c"},
	},
	{
		pattern:  "*/*",
		excludes: []string{"a/**/*"},
		matches:  []string{"b/a", "c/c", "c/f", "c/g", "c/h"},
		dirs:     []string{".", "a", "b", "c"},
	},
	{
		pattern:  "**/*",
		excludes: []string{"**/*"},
		matches:  nil,
		dirs:     []string{".", "a", "a/a", "a/b", "b", "c", "c/f", "c/g", "c/h"},
	},
	{
		pattern:  "*/*/*",
		excludes: []string{"a/**/a"},
		matches:  []string{"a/b/b", "c/f/f.ext", "c/g/g.ext", "c/h/h"},
		dirs:     []string{".", "a", "b", "c", "a/a", "a/b", "c/f", "c/g", "c/h"},
	},
	{
		pattern:  "*/*/*",
		excludes: []string{"**/a"},
		matches:  []string{"a/b/b", "c/f/f.ext", "c/g/g.ext", "c/h/h"},
		dirs:     []string{".", "a", "b", "c", "a/a", "a/b", "c/f", "c/g", "c/h"},
	},
	{
		pattern:  "c/*/*.ext",
		excludes: []string{"c/**/f.ext"},
		matches:  []string{"c/g/g.ext"},
		dirs:     []string{"c", "c/f", "c/g", "c/h"},
	},

	// absoulte recursive exclude tests
	{
		pattern:  filepath.Join(pwd, "testdata/c/*/*.ext"),
		excludes: []string{filepath.Join(pwd, "testdata/**/f.ext")},
		matches: []string{
			filepath.Join(pwd, "testdata/c/g/g.ext"),
		},
		dirs: []string{
			filepath.Join(pwd, "testdata/c"),
			filepath.Join(pwd, "testdata/c/f"),
			filepath.Join(pwd, "testdata/c/g"),
			filepath.Join(pwd, "testdata/c/h"),
		},
	},

	// clean exclude tests
	{
		pattern:  "./c/*/*.ext",
		excludes: []string{"./c/*/f.ext"},
		matches:  []string{"c/g/g.ext"},
		dirs:     []string{"c", "c/f", "c/g", "c/h"},
	},
	{
		pattern:  "c/*/*.ext",
		excludes: []string{"./c/*/f.ext"},
		matches:  []string{"c/g/g.ext"},
		dirs:     []string{"c", "c/f", "c/g", "c/h"},
	},
	{
		pattern:  "./c/*/*.ext",
		excludes: []string{"c/*/f.ext"},
		matches:  []string{"c/g/g.ext"},
		dirs:     []string{"c", "c/f", "c/g", "c/h"},
	},

	// non-existant non-wild path tests
	{
		pattern: "d/*",
		matches: nil,
		dirs:    []string{"."},
	},
	{
		pattern: "d",
		matches: nil,
		dirs:    []string{"."},
	},
	{
		pattern: "a/d/*",
		matches: nil,
		dirs:    []string{"a"},
	},
	{
		pattern: "a/d",
		matches: nil,
		dirs:    []string{"a"},
	},
	{
		pattern: "a/a/d/*",
		matches: nil,
		dirs:    []string{"a/a"},
	},
	{
		pattern: "a/a/d",
		matches: nil,
		dirs:    []string{"a/a"},
	},
	{
		pattern: "a/d/a/*",
		matches: nil,
		dirs:    []string{"a"},
	},
	{
		pattern: "a/d/a",
		matches: nil,
		dirs:    []string{"a"},
	},
	{
		pattern: "a/d/a/*/a",
		matches: nil,
		dirs:    []string{"a"},
	},
	{
		pattern: "a/d/a/**/a",
		matches: nil,
		dirs:    []string{"a"},
	},

	// recursive exclude error tests
	{
		pattern:  "**/*",
		excludes: []string{"**/**/*"},
		err:      GlobMultipleRecursiveErr,
	},
	{
		pattern:  "**/*",
		excludes: []string{"a/**/**/*"},
		err:      GlobMultipleRecursiveErr,
	},
	{
		pattern:  "**/*",
		excludes: []string{"**/a/**/*"},
		err:      GlobMultipleRecursiveErr,
	},
	{
		pattern:  "**/*",
		excludes: []string{"**/**/a/*"},
		err:      GlobMultipleRecursiveErr,
	},
	{
		pattern:  "**/*",
		excludes: []string{"a/**"},
		err:      GlobLastRecursiveErr,
	},
	{
		pattern:  "**/*",
		excludes: []string{"**/**"},
		err:      GlobLastRecursiveErr,
	},
}

func TestGlob(t *testing.T) {
	os.Chdir("testdata")
	defer os.Chdir("..")
	for _, testCase := range globTestCases {
		matches, dirs, err := GlobWithExcludes(testCase.pattern, testCase.excludes)
		if err != testCase.err {
			t.Errorf(" pattern: %q", testCase.pattern)
			if testCase.excludes != nil {
				t.Errorf("excludes: %q", testCase.excludes)
			}
			t.Errorf("   error: %s", err)
			continue
		}

		if !reflect.DeepEqual(matches, testCase.matches) {
			t.Errorf("incorrect matches list:")
			t.Errorf(" pattern: %q", testCase.pattern)
			if testCase.excludes != nil {
				t.Errorf("excludes: %q", testCase.excludes)
			}
			t.Errorf("     got: %#v", matches)
			t.Errorf("expected: %#v", testCase.matches)
		}
		if !reflect.DeepEqual(dirs, testCase.dirs) {
			t.Errorf("incorrect dirs list:")
			t.Errorf(" pattern: %q", testCase.pattern)
			if testCase.excludes != nil {
				t.Errorf("excludes: %q", testCase.excludes)
			}
			t.Errorf("     got: %#v", dirs)
			t.Errorf("expected: %#v", testCase.dirs)
		}
	}
}