package glob

// TODO use constructor with all matchers, and to their structs private
// TODO glue multiple Text nodes (like after QuoteMeta)

import (
	"fmt"
	"github.com/gobwas/glob/match"
	"github.com/gobwas/glob/runes"
	"reflect"
)

func optimize(matcher match.Matcher) match.Matcher {
	switch m := matcher.(type) {

	case match.Any:
		if len(m.Separators) == 0 {
			return match.NewSuper()
		}

	case match.AnyOf:
		if len(m.Matchers) == 1 {
			return m.Matchers[0]
		}

		return m

	case match.List:
		if m.Not == false && len(m.List) == 1 {
			return match.NewText(string(m.List))
		}

		return m

	case match.BTree:
		m.Left = optimize(m.Left)
		m.Right = optimize(m.Right)

		r, ok := m.Value.(match.Text)
		if !ok {
			return m
		}

		leftNil := m.Left == nil
		rightNil := m.Right == nil

		if leftNil && rightNil {
			return match.NewText(r.Str)
		}

		_, leftSuper := m.Left.(match.Super)
		lp, leftPrefix := m.Left.(match.Prefix)

		_, rightSuper := m.Right.(match.Super)
		rs, rightSuffix := m.Right.(match.Suffix)

		if leftSuper && rightSuper {
			return match.NewContains(r.Str, false)
		}

		if leftSuper && rightNil {
			return match.NewSuffix(r.Str)
		}

		if rightSuper && leftNil {
			return match.NewPrefix(r.Str)
		}

		if leftNil && rightSuffix {
			return match.NewPrefixSuffix(r.Str, rs.Suffix)
		}

		if rightNil && leftPrefix {
			return match.NewPrefixSuffix(lp.Prefix, r.Str)
		}

		return m
	}

	return matcher
}

func glueMatchers(matchers []match.Matcher) match.Matcher {
	var (
		glued  []match.Matcher
		winner match.Matcher
	)
	maxLen := -1

	if m := glueAsEvery(matchers); m != nil {
		glued = append(glued, m)
		return m
	}

	if m := glueAsRow(matchers); m != nil {
		glued = append(glued, m)
		return m
	}

	for _, g := range glued {
		if l := g.Len(); l > maxLen {
			maxLen = l
			winner = g
		}
	}

	return winner
}

func glueAsRow(matchers []match.Matcher) match.Matcher {
	if len(matchers) <= 1 {
		return nil
	}

	var (
		c []match.Matcher
		l int
	)
	for _, matcher := range matchers {
		if ml := matcher.Len(); ml == -1 {
			return nil
		} else {
			c = append(c, matcher)
			l += ml
		}
	}

	return match.NewRow(l, c...)
}

func glueAsEvery(matchers []match.Matcher) match.Matcher {
	if len(matchers) <= 1 {
		return nil
	}

	var (
		hasAny    bool
		hasSuper  bool
		hasSingle bool
		min       int
		separator []rune
	)

	for i, matcher := range matchers {
		var sep []rune

		switch m := matcher.(type) {
		case match.Super:
			sep = []rune{}
			hasSuper = true

		case match.Any:
			sep = m.Separators
			hasAny = true

		case match.Single:
			sep = m.Separators
			hasSingle = true
			min++

		case match.List:
			if !m.Not {
				return nil
			}
			sep = m.List
			hasSingle = true
			min++

		default:
			return nil
		}

		// initialize
		if i == 0 {
			separator = sep
		}

		if runes.Equal(sep, separator) {
			continue
		}

		return nil
	}

	if hasSuper && !hasAny && !hasSingle {
		return match.NewSuper()
	}

	if hasAny && !hasSuper && !hasSingle {
		return match.NewAny(separator)
	}

	if (hasAny || hasSuper) && min > 0 && len(separator) == 0 {
		return match.NewMin(min)
	}

	every := match.NewEveryOf()

	if min > 0 {
		every.Add(match.NewMin(min))

		if !hasAny && !hasSuper {
			every.Add(match.NewMax(min))
		}
	}

	if len(separator) > 0 {
		every.Add(match.NewContains(string(separator), true))
	}

	return every
}

func minimizeMatchers(matchers []match.Matcher) []match.Matcher {
	var done match.Matcher
	var left, right, count int

	for l := 0; l < len(matchers); l++ {
		for r := len(matchers); r > l; r-- {
			if glued := glueMatchers(matchers[l:r]); glued != nil {
				var swap bool

				if done == nil {
					swap = true
				} else {
					cl, gl := done.Len(), glued.Len()
					swap = cl > -1 && gl > -1 && gl > cl
					swap = swap || count < r-l
				}

				if swap {
					done = glued
					left = l
					right = r
					count = r - l
				}
			}
		}
	}

	if done == nil {
		return matchers
	}

	next := append(append([]match.Matcher{}, matchers[:left]...), done)
	if right < len(matchers) {
		next = append(next, matchers[right:]...)
	}

	if len(next) == len(matchers) {
		return next
	}

	return minimizeMatchers(next)
}

func minimizeAnyOf(children []node) node {
	var nodes [][]node
	var min int
	var idx int
	for i, desc := range children {
		pat, ok := desc.(*nodePattern)
		if !ok {
			return nil
		}

		n := pat.children()
		ln := len(n)
		if len(nodes) == 0 || (ln < min) {
			min = ln
			idx = i
		}

		nodes = append(nodes, pat.children())
	}

	minNodes := nodes[idx]
	if idx+1 < len(nodes) {
		nodes = append(nodes[:idx], nodes[idx+1:]...)
	} else {
		nodes = nodes[:idx]
	}

	var commonLeft []node
	var commonLeftCount int
	for i, n := range minNodes {
		has := true
		for _, t := range nodes {
			if !reflect.DeepEqual(n, t[i]) {
				has = false
				break
			}
		}

		if has {
			commonLeft = append(commonLeft, n)
			commonLeftCount++
		} else {
			break
		}
	}

	var commonRight []node
	var commonRightCount int
	for i := min - 1; i > commonLeftCount-1; i-- {
		n := minNodes[i]
		has := true
		for _, t := range nodes {
			if !reflect.DeepEqual(n, t[len(t)-(min-i)]) {
				has = false
				break
			}
		}

		if has {
			commonRight = append(commonRight, n)
			commonRightCount++
		} else {
			break
		}
	}

	if commonLeftCount == 0 && commonRightCount == 0 {
		return nil
	}

	nodes = append(nodes, minNodes)
	nodes[len(nodes)-1], nodes[idx] = nodes[idx], nodes[len(nodes)-1]

	var result []node
	if commonLeftCount > 0 {
		result = append(result, &nodePattern{nodeImpl: nodeImpl{desc: commonLeft}})
	}

	var anyOf []node
	for _, n := range nodes {
		if commonLeftCount+commonRightCount == len(n) {
			anyOf = append(anyOf, nil)
		} else {
			anyOf = append(anyOf, &nodePattern{nodeImpl: nodeImpl{desc: n[commonLeftCount : len(n)-commonRightCount]}})
		}
	}

	anyOf = uniqueNodes(anyOf)
	if len(anyOf) == 1 {
		if anyOf[0] != nil {
			result = append(result, &nodePattern{nodeImpl: nodeImpl{desc: anyOf}})
		}
	} else {
		result = append(result, &nodeAnyOf{nodeImpl: nodeImpl{desc: anyOf}})
	}

	if commonRightCount > 0 {
		result = append(result, &nodePattern{nodeImpl: nodeImpl{desc: commonRight}})
	}

	return &nodePattern{nodeImpl: nodeImpl{desc: result}}
}

func uniqueNodes(nodes []node) (result []node) {
head:
	for _, n := range nodes {
		for _, e := range result {
			if reflect.DeepEqual(e, n) {
				continue head
			}
		}

		result = append(result, n)
	}

	return
}

func compileMatchers(matchers []match.Matcher) (match.Matcher, error) {
	if len(matchers) == 0 {
		return nil, fmt.Errorf("compile error: need at least one matcher")
	}

	if len(matchers) == 1 {
		return matchers[0], nil
	}

	if m := glueMatchers(matchers); m != nil {
		return m, nil
	}

	idx := -1
	maxLen := -1
	var val match.Matcher
	for i, matcher := range matchers {
		if l := matcher.Len(); l != -1 && l >= maxLen {
			maxLen = l
			idx = i
			val = matcher
		}
	}

	if val == nil { // not found matcher with static length
		r, err := compileMatchers(matchers[1:])
		if err != nil {
			return nil, err
		}
		return match.NewBTree(matchers[0], nil, r), nil
	}

	left := matchers[:idx]
	var right []match.Matcher
	if len(matchers) > idx+1 {
		right = matchers[idx+1:]
	}

	var l, r match.Matcher
	var err error
	if len(left) > 0 {
		l, err = compileMatchers(left)
		if err != nil {
			return nil, err
		}
	}

	if len(right) > 0 {
		r, err = compileMatchers(right)
		if err != nil {
			return nil, err
		}
	}

	return match.NewBTree(val, l, r), nil
}

func do(leaf node, s []rune) (m match.Matcher, err error) {
	switch n := leaf.(type) {
	case *nodeAnyOf:
		// todo this could be faster on pattern_alternatives_combine_lite
		if n := minimizeAnyOf(n.children()); n != nil {
			return do(n, s)
		}

		var matchers []match.Matcher
		for _, desc := range n.children() {
			if desc == nil {
				matchers = append(matchers, match.NewNothing())
				continue
			}

			m, err := do(desc, s)
			if err != nil {
				return nil, err
			}
			matchers = append(matchers, optimize(m))
		}

		return match.NewAnyOf(matchers...), nil

	case *nodePattern:
		nodes := leaf.children()
		if len(nodes) == 0 {
			return match.NewNothing(), nil
		}

		var matchers []match.Matcher
		for _, desc := range nodes {
			m, err := do(desc, s)
			if err != nil {
				return nil, err
			}
			matchers = append(matchers, optimize(m))
		}

		m, err = compileMatchers(minimizeMatchers(matchers))
		if err != nil {
			return nil, err
		}

	case *nodeList:
		m = match.NewList([]rune(n.chars), n.not)

	case *nodeRange:
		m = match.NewRange(n.lo, n.hi, n.not)

	case *nodeAny:
		m = match.NewAny(s)

	case *nodeSuper:
		m = match.NewSuper()

	case *nodeSingle:
		m = match.NewSingle(s)

	case *nodeText:
		m = match.NewText(n.text)

	default:
		return nil, fmt.Errorf("could not compile tree: unknown node type")
	}

	return optimize(m), nil
}

func compile(ast *nodePattern, s []rune) (Glob, error) {
	g, err := do(ast, s)
	if err != nil {
		return nil, err
	}

	return g, nil
}
