package gzip

import (
	"net/http"
	"path"

	"github.com/mholt/caddy/middleware"
)

// Filter determines if a request should be gzipped.
type Filter interface {
	// ShouldCompress tells if compression gzip compression
	// should be done on the request.
	ShouldCompress(*http.Request) bool
}

// ExtFilter is Filter for file name extensions.
type ExtFilter struct {
	// Exts is the file name extensions to accept
	Exts Set
}

// textExts is a list of extensions for text related files.
var textExts = []string{
	".html", ".htm", ".css", ".json", ".php", ".js", ".txt", ".md", ".xml",
}

// extWildCard is the wildcard for extensions.
const extWildCard = "*"

// DefaultExtFilter creates a default ExtFilter with
// file extensions for text types.
func DefaultExtFilter() ExtFilter {
	e := ExtFilter{make(Set)}
	for _, ext := range textExts {
		e.Exts.Add(ext)
	}
	return e
}

func (e ExtFilter) ShouldCompress(r *http.Request) bool {
	ext := path.Ext(r.URL.Path)
	return e.Exts.Contains(extWildCard) || e.Exts.Contains(ext)
}

// PathFilter is Filter for request path.
type PathFilter struct {
	// IgnoredPaths is the paths to ignore
	IgnoredPaths Set
}

// ShouldCompress checks if the request path matches any of the
// registered paths to ignore. If returns false if an ignored path
// is found and true otherwise.
func (p PathFilter) ShouldCompress(r *http.Request) bool {
	return !p.IgnoredPaths.ContainsFunc(func(value string) bool {
		return middleware.Path(r.URL.Path).Matches(value)
	})
}

// Set stores distinct strings.
type Set map[string]struct{}

// Add adds an element to the set.
func (s Set) Add(value string) {
	s[value] = struct{}{}
}

// Remove removes an element from the set.
func (s Set) Remove(value string) {
	delete(s, value)
}

// Contains check if the set contains value.
func (s Set) Contains(value string) bool {
	_, ok := s[value]
	return ok
}

// ContainsFunc is similar to Contains. It iterates all the
// elements in the set and passes each to f. It returns true
// on the first call to f that returns true and false otherwise.
func (s Set) ContainsFunc(f func(string) bool) bool {
	for k, _ := range s {
		if f(k) {
			return true
		}
	}
	return false
}