package rules

import (
	"errors"
	"strings"

	"github.com/vulcand/predicate"
)

type treeBuilder func() *tree

// ParseDomains extract domains from rule
func ParseDomains(rule string) ([]string, error) {
	parser, err := newParser()
	if err != nil {
		return nil, err
	}

	parse, err := parser.Parse(rule)
	if err != nil {
		return nil, err
	}

	buildTree, ok := parse.(treeBuilder)
	if !ok {
		return nil, errors.New("cannot parse")
	}

	return lower(parseDomain(buildTree())), nil
}

// ParseHostSNI extracts the HostSNIs declared in a rule
// This is a first naive implementation used in TCP routing
func ParseHostSNI(rule string) ([]string, error) {
	parser, err := newTCPParser()
	if err != nil {
		return nil, err
	}

	parse, err := parser.Parse(rule)
	if err != nil {
		return nil, err
	}

	buildTree, ok := parse.(treeBuilder)
	if !ok {
		return nil, errors.New("cannot parse")
	}

	return lower(parseDomain(buildTree())), nil
}

func lower(slice []string) []string {
	var lowerStrings []string
	for _, value := range slice {
		lowerStrings = append(lowerStrings, strings.ToLower(value))
	}
	return lowerStrings
}

func parseDomain(tree *tree) []string {
	switch tree.matcher {
	case "and", "or":
		return append(parseDomain(tree.ruleLeft), parseDomain(tree.ruleRight)...)
	case "Host", "HostSNI":
		return tree.value
	default:
		return nil
	}
}

func andFunc(left, right treeBuilder) treeBuilder {
	return func() *tree {
		return &tree{
			matcher:   "and",
			ruleLeft:  left(),
			ruleRight: right(),
		}
	}
}

func orFunc(left, right treeBuilder) treeBuilder {
	return func() *tree {
		return &tree{
			matcher:   "or",
			ruleLeft:  left(),
			ruleRight: right(),
		}
	}
}

func newParser() (predicate.Parser, error) {
	parserFuncs := make(map[string]interface{})

	for matcherName := range funcs {
		matcherName := matcherName
		fn := func(value ...string) treeBuilder {
			return func() *tree {
				return &tree{
					matcher: matcherName,
					value:   value,
				}
			}
		}
		parserFuncs[matcherName] = fn
		parserFuncs[strings.ToLower(matcherName)] = fn
		parserFuncs[strings.ToUpper(matcherName)] = fn
		parserFuncs[strings.Title(strings.ToLower(matcherName))] = fn
	}

	return predicate.NewParser(predicate.Def{
		Operators: predicate.Operators{
			AND: andFunc,
			OR:  orFunc,
		},
		Functions: parserFuncs,
	})
}

func newTCPParser() (predicate.Parser, error) {
	parserFuncs := make(map[string]interface{})

	// FIXME quircky way of waiting for new rules
	matcherName := "HostSNI"
	fn := func(value ...string) treeBuilder {
		return func() *tree {
			return &tree{
				matcher: matcherName,
				value:   value,
			}
		}
	}
	parserFuncs[matcherName] = fn
	parserFuncs[strings.ToLower(matcherName)] = fn
	parserFuncs[strings.ToUpper(matcherName)] = fn
	parserFuncs[strings.Title(strings.ToLower(matcherName))] = fn

	return predicate.NewParser(predicate.Def{
		Operators: predicate.Operators{
			OR: orFunc,
		},
		Functions: parserFuncs,
	})
}
