// spintax is an text generator from Spintax template
package spintax

import (
	"fmt"
	"math/rand"
	"strings"
)

var primes = []int{101, 1009, 10007, 100003, 1000003}

type (
	// Exp is an Spintax expression.
	// Spin returns all pars cat together
	Exp []Spintax
	// Alt is an alternative expression
	// Spin returns one of options
	Alt []Spintax
	// Str is an simple string
	Str string

	Spintax interface {
		// Returns one of options
		Spin() string
		// Counts total number of options
		Count() int
		// Returns template
		String() string
		// Returns all possible options
		All() []string
		// Iter gives options one by one
		Iter() <-chan string
	}
)

// Parse parses template
func Parse(exp string) (Spintax, error) {
	e, i, err := parseExp(exp, 0)
	if i != len(exp) {
		return nil, fmt.Errorf("brackets balance error: %v", i)
	}
	return e, err
}

func parseExp(e string, i int) (Spintax, int, error) {
	var r Exp
	var err error
	s := i
loop:
	for i < len(e) {
		c := e[i]
		switch c {
		case '{':
			if s != i {
				r = append(r, Str(e[s:i]))
			}
			var alt Spintax
			alt, i, err = parseAlt(e, i)
			if err != nil {
				return nil, i, err
			}
			i++
			s = i
			if alt != nil {
				r = append(r, alt)
			}
		case '|', '}':
			break loop
		default:
			i++
		}
	}
	if s != i {
		r = append(r, Str(e[s:i]))
	}
	if r == nil {
		return Str(""), i, nil
	}
	if len(r) == 1 {
		return r[0], i, nil
	}
	return r, i, nil
}

func parseAlt(e string, i int) (Spintax, int, error) {
	var r Alt
	var exp Spintax
	var err error
	d := 0
	for i < len(e) {
		if e[i] == '}' {
			d--
			break
		}
		if e[i] == '|' || e[i] == '{' {
			if e[i] == '{' {
				d++
			}
			i++
		}
		exp, i, err = parseExp(e, i)
		if err != nil {
			return nil, i, err
		}
		r = append(r, exp)
	}
	if d != 0 {
		return nil, i, fmt.Errorf("brackets balance error at pos %v", i)
	}
	if len(r) == 1 {
		return r[0], i, nil
	}
	return r, i, nil
}

func (e Exp) Spin() string {
	var b strings.Builder
	for _, e := range e {
		b.WriteString(e.Spin())
	}
	return b.String()
}

func (a Alt) Spin() string {
	e := a[rand.Intn(len(a))]
	return e.Spin()
}

func (s Str) Spin() string { return string(s) }

func (e Exp) Count() int {
	s := 1
	for _, e := range e {
		s *= e.Count()
	}
	return s
}

func (a Alt) Count() int {
	s := 0
	for _, e := range a {
		s += e.Count()
	}
	return s
}

func (s Str) Count() int { return 1 }

func (e Exp) String() string {
	var b strings.Builder
	for _, e := range e {
		b.WriteString(e.String())
	}
	return b.String()
}

func (a Alt) String() string {
	var b strings.Builder
	b.WriteString("{")
	for i, e := range a {
		if i != 0 {
			b.WriteString("|")
		}
		b.WriteString(e.String())
	}
	b.WriteString("}")
	return b.String()
}

func (s Str) String() string { return string(s) }

func (e Exp) All() []string {
	if len(e) == 1 {
		return e[0].All()
	}

	f := e[0].All()
	t := e[1:].All()

	var r []string
	for _, f := range f {
		for _, t := range t {
			r = append(r, f+t)
		}
	}

	return r
}

func (a Alt) All() []string {
	var r []string

	for _, e := range a {
		all := e.All()
		r = append(r, all...)
	}

	return r
}

func (s Str) All() []string { return []string{string(s)} }

func (e Exp) Iter() <-chan string {
	c := make(chan string, 1)
	go func() {
		l := len(e)
		t := make([]string, l)
		s := make([]<-chan string, l)
		var b strings.Builder
		var ok bool

		for j := 0; j < l; j++ {
			s[j] = e[j].Iter()
			t[j] = <-s[j]
		}

		for {
			b.Reset()

			for _, s := range t {
				b.WriteString(s)
			}
			c <- b.String()

			var j int
			for ; j < l; j++ {
				t[j], ok = <-s[j]
				if ok {
					break
				}
				s[j] = e[j].Iter()
				t[j], ok = <-s[j]
				if !ok {
					panic("no data at renewed Iter")
				}
			}
			if j == l {
				break
			}
		}

		close(c)
	}()
	return c
}

func (a Alt) Iter() <-chan string {
	c := make(chan string, 1)
	go func() {
		l := len(a)
		var p int = 1
		for _, P := range primes {
			if l%P == 0 {
				continue
			}
			p = P
			break
		}
		n := 0
		for i := l / 2; n < l; i = (i + p) % l {
			e := a[i]
			sub := e.Iter()
			for l := range sub {
				c <- l
			}
			n++
		}
		close(c)
	}()
	return c
}

func (s Str) Iter() <-chan string {
	c := make(chan string, 1)
	c <- string(s)
	close(c)
	return c
}
