package cap

import (
	"bufio"
	"errors"
	"strconv"
	"strings"
)

// String converts a capability Value into its canonical text
// representation.
func (v Value) String() string {
	name, ok := names[v]
	if ok {
		return name
	}
	// Un-named capabilities are referred to numerically (in decimal).
	return strconv.Itoa(int(v))
}

// FromName converts a named capability Value to its binary
// representation.
func FromName(name string) (Value, error) {
	startUp.Do(cInit)
	v, ok := bits[name]
	if ok {
		if v >= Value(words*32) {
			return 0, ErrBadValue
		}
		return v, nil
	}
	i, err := strconv.Atoi(name)
	if err != nil {
		return 0, err
	}
	if i >= 0 && i < int(words*32) {
		return Value(i), nil
	}
	return 0, ErrBadValue
}

const (
	eBin uint = (1 << Effective)
	pBin      = (1 << Permitted)
	iBin      = (1 << Inheritable)
)

var combos = []string{"", "e", "p", "ep", "i", "ei", "ip", "eip"}

// histo generates a histogram of flag state combinations.
func (c *Set) histo(m uint, bins []int, patterns []uint, from, limit Value) uint {
	for v := from; v < limit; v++ {
		b := uint(v & 31)
		u, bit, err := bitOf(0, v)
		if err != nil {
			break
		}
		x := uint((c.flat[u][Effective]&bit)>>b) * eBin
		x |= uint((c.flat[u][Permitted]&bit)>>b) * pBin
		x |= uint((c.flat[u][Inheritable]&bit)>>b) * iBin
		bins[x]++
		patterns[uint(v)] = x
		if bins[m] <= bins[x] {
			m = x
		}
	}
	return m
}

// String converts a full capability Set into it canonical readable
// string representation (which may contain spaces).
func (c *Set) String() string {
	if c == nil || len(c.flat) == 0 {
		return "<invalid>"
	}
	bins := make([]int, 8)
	patterns := make([]uint, 32*words)

	c.mu.RLock()
	defer c.mu.RUnlock()

	// Note, in order to have a *Set pointer, startUp.Do(cInit)
	// must have been called which sets maxValues.
	m := c.histo(0, bins, patterns, 0, Value(maxValues))

	// Background state is the most popular of the named bits.
	vs := []string{"=" + combos[m]}
	for i := uint(8); i > 0; {
		i--
		if i == m || bins[i] == 0 {
			continue
		}
		var list []string
		for j, p := range patterns {
			if p != i {
				continue
			}
			list = append(list, Value(j).String())
		}
		if cf := i & ^m; cf != 0 {
			vs = append(vs, strings.Join(list, ",")+"+"+combos[cf])
		}
		if cf := m & ^i; cf != 0 {
			vs = append(vs, strings.Join(list, ",")+"-"+combos[cf])
		}
	}

	// The unnamed bits can only add to the above named ones since
	// unnamed ones are always defaulted to lowered.
	uBins := make([]int, 8)
	uPatterns := make([]uint, 32*words)
	c.histo(0, uBins, uPatterns, Value(maxValues), 32*Value(words))
	for i := uint(8); i > 1; {
		i--
		if uBins[i] == 0 {
			continue
		}
		var list []string
		for j, p := range uPatterns {
			if p != i {
				continue
			}
			list = append(list, Value(j).String())
		}
		vs = append(vs, strings.Join(list, ",")+"+"+combos[i])
	}

	return strings.Join(vs, " ")
}

// ErrBadText is returned if the text for a capability set cannot be parsed.
var ErrBadText = errors.New("bad text")

// FromText converts the canonical text representation for a Set into
// a freshly allocated Set.
func FromText(text string) (*Set, error) {
	c := NewSet()
	scanner := bufio.NewScanner(strings.NewReader(text))
	scanner.Split(bufio.ScanWords)
	chunks := 0
	for scanner.Scan() {
		chunks++
		// Parsing for xxx[-+=][eip]+
		t := scanner.Text()
		i := strings.IndexAny(t, "=+-")
		if i < 0 {
			return nil, ErrBadText
		}
		var vs []Value
		sep := t[i]
		if vals := t[:i]; vals != "all" && vals != "" {
			for _, name := range strings.Split(vals, ",") {
				v, err := FromName(name)
				if err != nil {
					return nil, ErrBadText
				}
				vs = append(vs, v)
			}
		} else if sep != '=' && vals == "" {
			return nil, ErrBadText // Only "=" supports ""=="all".
		}
		sets := t[i+1:]
		var fE, fP, fI bool
		for j := 0; j < len(sets); j++ {
			switch sets[j] {
			case 'e':
				fE = true
			case 'p':
				fP = true
			case 'i':
				fI = true
			default:
				return nil, ErrBadText
			}
		}
		if sep == '=' {
			// '=' means default to off for all named flags.
			// '=ep' means default on for named e & p.
			keep := len(vs) == 0
			c.forceFlag(Effective, fE && keep)
			c.forceFlag(Permitted, fP && keep)
			c.forceFlag(Inheritable, fI && keep)
			if keep {
				continue
			}

		}
		if fE {
			c.SetFlag(Effective, sep != '-', vs...)
		}
		if fP {
			c.SetFlag(Permitted, sep != '-', vs...)
		}
		if fI {
			c.SetFlag(Inheritable, sep == '+', vs...)
		}
	}
	if chunks == 0 {
		return nil, ErrBadText
	}
	return c, nil
}
