// Package gintersect provides methods to check whether the intersection of several globs matches a non-empty set of strings.
package gintersect

import (
	"fmt"
	"strings"
)

// Glob represents a glob.
type Glob []Token

// NewGlob constructs a Glob from the given string by tokenizing and then simplifying it, or reports errors if any.
func NewGlob(input string) (Glob, error) {
	tokens, err := Tokenize([]rune(input))
	if err != nil {
		return nil, err
	}

	tokens = Simplify(tokens)

	return Glob(tokens), nil
}

// TokenType is the type of a Token.
type TokenType uint

const (
	TTCharacter TokenType = iota
	TTDot
	TTSet
)

// Flag applies to a token.
type Flag uint

func (f Flag) String() (s string) {
	for r, flag := range flagRunes {
		if f == flag {
			s = string(r)
			break
		}
	}
	return
}

const (
	FlagNone = iota
	FlagPlus
	FlagStar
)

// Token is the element that makes up a Glob.
type Token interface {
	Type() TokenType
	Flag() Flag
	SetFlag(Flag)
	// Equal describes whether the given Token is exactly equal to this one, barring differences in flags.
	Equal(Token) bool
	String() string
}

// token is the base for all structs implementing Token.
type token struct {
	ttype TokenType
	flag  Flag
}

func (t token) Type() TokenType {
	return t.ttype
}

func (t token) Flag() Flag {
	return t.flag
}

func (t *token) SetFlag(f Flag) {
	t.flag = f
}

// character is a specific rune. It implements Token.
type character struct {
	token
	r rune
}

func NewCharacter(r rune) Token {
	return &character{
		token: token{ttype: TTCharacter},
		r:     r,
	}
}

func (c character) Equal(other Token) bool {
	if c.Type() != other.Type() {
		return false
	}

	o := other.(*character)
	return c.Rune() == o.Rune()
}

func (c character) String() string {
	return fmt.Sprintf("{character: %s flag: %s}", string(c.Rune()), c.Flag().String())
}

func (c character) Rune() rune {
	return c.r
}

// dot is any character. It implements Token.
type dot struct {
	token
}

func NewDot() Token {
	return &dot{
		token: token{ttype: TTDot},
	}
}

func (d dot) Equal(other Token) bool {
	return d.Type() == other.Type()
}

func (d dot) String() string {
	return fmt.Sprintf("{dot flag: %s}", d.Flag().String())
}

// set is a set of characters (similar to regexp character class).
// It implements Token.
type set struct {
	token
	runes map[rune]bool
}

func NewSet(runes []rune) Token {
	m := map[rune]bool{}
	for _, r := range runes {
		m[r] = true
	}
	return &set{
		token: token{ttype: TTSet},
		runes: m,
	}
}

func (s set) Equal(other Token) bool {
	if s.Type() != other.Type() {
		return false
	}

	o := other.(*set)
	r1, r2 := s.Runes(), o.Runes()

	if len(r1) != len(r2) {
		return false
	}

	for k, _ := range r1 {
		if _, ok := r2[k]; !ok {
			return false
		}
	}

	return true
}

func (s set) String() string {
	rs := make([]string, 0, 30)
	for r, _ := range s.Runes() {
		rs = append(rs, string(r))
	}
	return fmt.Sprintf("{set: %s flag: %s}", strings.Join(rs, ""), s.Flag().String())
}

func (s set) Runes() map[rune]bool {
	return s.runes
}
