package main

import (
	"fmt"
	"path"
	"strings"
)

type Login struct {
	user   string
	groups []string
}

type ACL []ACLEntry

type ACLEntry struct {
	// The authenticated user (or ANONYMOUS if not authenticated) must match this string
	user string
	// For each of this groups, the authenticated user must belong to one group that matches
	reqGroups []string
	// The action requested must match one of these strings
	actions []string
	// The requested target must match this string. The special word SELF is replaced in the pattern by the user's dn before matching
	target string
	// All attributes requested must match one of these patterns
	attributes []string
	// All attributes requested must not match any of these patterns
	exclAttributes []string
}

func splitNoEmpty(s string) []string {
	tmp := strings.Split(s, " ")
	ret := []string{}
	for _, s := range tmp {
		if len(s) > 0 {
			ret = append(ret, s)
		}
	}
	return ret
}

func ParseACL(def []string) (ACL, error) {
	acl := []ACLEntry{}
	for _, item := range def {
		parts := strings.Split(item, ":")
		if len(parts) != 5 {
			return nil, fmt.Errorf("Invalid ACL entry: %s", item)
		}
		attr, exclAttr := []string{}, []string{}
		for _, s := range splitNoEmpty(parts[4]) {
			if s[0] == '!' {
				exclAttr = append(exclAttr, s[1:])
			} else {
				attr = append(attr, s)
			}
		}
		item_def := ACLEntry{
			user:           parts[0],
			reqGroups:      splitNoEmpty(parts[1]),
			actions:        splitNoEmpty(parts[2]),
			target:         parts[3],
			attributes:     attr,
			exclAttributes: exclAttr,
		}
		acl = append(acl, item_def)
	}
	return acl, nil
}

func (acl ACL) Check(login *Login, action string, target string, attributes []string) bool {
	for _, item := range acl {
		if item.Check(login, action, target, attributes) {
			return true
		}
	}
	return false
}

func (entry *ACLEntry) Check(login *Login, action string, target string, attributes []string) bool {
	if !match(entry.user, login.user) {
		return false
	}

	for _, grp := range entry.reqGroups {
		if !matchAny(grp, login.groups) {
			return false
		}
	}

	matchTarget := match(entry.target, target)
	if !matchTarget && len(target) >= len(login.user) {
		start := len(target) - len(login.user)
		if target[start:] == login.user {
			matchTarget = match(entry.target, target[:start]+"SELF")
		}
	}
	if !matchTarget {
		return false
	}

	if !anyMatch(entry.actions, action) {
		return false
	}

	for _, attrib := range attributes {
		if !anyMatch(entry.attributes, attrib) {
			return false
		}
	}

	for _, exclAttr := range entry.exclAttributes {
		if matchAny(exclAttr, attributes) {
			return false
		}
	}

	return true
}

func match(pattern string, val string) bool {
	rv, err := path.Match(strings.ToLower(pattern), strings.ToLower(val))
	return err == nil && rv
}

func matchAny(pattern string, vals []string) bool {
	for _, val := range vals {
		if match(pattern, val) {
			return true
		}
	}
	return false
}

func anyMatch(patterns []string, val string) bool {
	for _, pattern := range patterns {
		if match(pattern, val) {
			return true
		}
	}
	return false
}
