package gitattributes

import (
	"path/filepath"
	"strings"
)

const (
	patternDirSep  = "/"
	zeroToManyDirs = "**"
)

// Pattern defines a gitattributes pattern.
type Pattern interface {
	// Match matches the given path to the pattern.
	Match(path []string) bool
}

type pattern struct {
	domain  []string
	pattern []string
}

// ParsePattern parses a gitattributes pattern string into the Pattern
// structure.
func ParsePattern(p string, domain []string) Pattern {
	return &pattern{
		domain:  domain,
		pattern: strings.Split(p, patternDirSep),
	}
}

func (p *pattern) Match(path []string) bool {
	if len(path) <= len(p.domain) {
		return false
	}
	for i, e := range p.domain {
		if path[i] != e {
			return false
		}
	}

	if len(p.pattern) == 1 {
		// for a simple rule, .gitattribute matching rules differs from
		// .gitignore and only the last part of the path is considered.
		path = path[len(path)-1:]
	} else {
		path = path[len(p.domain):]
	}

	pattern := p.pattern
	var match, doublestar bool
	var err error
	for _, part := range path {
		// path is deeper than pattern
		if len(pattern) == 0 {
			return false
		}

		// skip empty
		if pattern[0] == "" {
			pattern = pattern[1:]
		}

		// eat doublestar
		if pattern[0] == zeroToManyDirs {
			pattern = pattern[1:]
			if len(pattern) == 0 {
				return true
			}
			doublestar = true
		}

		switch {
		case strings.Contains(pattern[0], "**"):
			return false

		// keep going down the path until we hit a match
		case doublestar:
			match, err = filepath.Match(pattern[0], part)
			if err != nil {
				return false
			}

			if match {
				doublestar = false
				pattern = pattern[1:]
			}

		default:
			match, err = filepath.Match(pattern[0], part)
			if err != nil {
				return false
			}
			if !match {
				return false
			}
			pattern = pattern[1:]
		}
	}

	if len(pattern) > 0 {
		return false
	}
	return match
}
