// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.

package fnmatch

import (
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
)

const (
	NoEscape = (1 << iota)
	PathName
	CaseFold
)

func Convert(pattern string, flags int) (*regexp.Regexp, error) {
	any := "."

	switch runtime.GOOS {
	case "windows":
		flags |= NoEscape | CaseFold
		pattern = filepath.FromSlash(pattern)
		if flags&PathName != 0 {
			any = `[^\\]`
		}
	case "darwin":
		flags |= CaseFold
		fallthrough
	default:
		if flags&PathName != 0 {
			any = `[^/]`
		}
	}

	if flags&NoEscape != 0 {
		pattern = strings.Replace(pattern, `\`, `\\`, -1)
	} else {
		pattern = strings.Replace(pattern, `\*`, "[:escapedstar:]", -1)
		pattern = strings.Replace(pattern, `\?`, "[:escapedques:]", -1)
		pattern = strings.Replace(pattern, `\.`, "[:escapeddot:]", -1)
	}

	// Characters that are special in regexps but not in glob, must be
	// escaped.
	for _, char := range []string{`.`, `+`, `$`, `^`, `(`, `)`, `|`} {
		pattern = strings.Replace(pattern, char, `\`+char, -1)
	}

	pattern = strings.Replace(pattern, `**`, `[:doublestar:]`, -1)
	pattern = strings.Replace(pattern, `*`, any+`*`, -1)
	pattern = strings.Replace(pattern, `[:doublestar:]`, `.*`, -1)
	pattern = strings.Replace(pattern, `?`, any, -1)

	pattern = strings.Replace(pattern, `[:escapedstar:]`, `\*`, -1)
	pattern = strings.Replace(pattern, `[:escapedques:]`, `\?`, -1)
	pattern = strings.Replace(pattern, `[:escapeddot:]`, `\.`, -1)

	pattern = `^` + pattern + `$`
	if flags&CaseFold != 0 {
		pattern = `(?i)` + pattern
	}
	return regexp.Compile(pattern)
}

// Match matches the pattern against the string, with the given flags, and
// returns true if the match is successful.
func Match(pattern, s string, flags int) (bool, error) {
	exp, err := Convert(pattern, flags)
	if err != nil {
		return false, err
	}
	return exp.MatchString(s), nil
}
