// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"math"
	"math/rand"
	"strings"
	"unicode"
	"unicode/utf16"
	"unicode/utf8"

	"golang.org/x/text/language"
	"golang.org/x/text/unicode/norm"
)

// TODO: replace with functionality in language package.
// parent computes the parent language for the given language.
// It returns false if the parent is already root.
func parent(locale string) (parent string, ok bool) {
	if locale == "und" {
		return "", false
	}
	if i := strings.LastIndex(locale, "-"); i != -1 {
		return locale[:i], true
	}
	return "und", true
}

// rewriter is used to both unique strings and create variants of strings
// to add to the test set.
type rewriter struct {
	seen     map[string]bool
	addCases bool
}

func newRewriter() *rewriter {
	return &rewriter{
		seen: make(map[string]bool),
	}
}

func (r *rewriter) insert(a []string, s string) []string {
	if !r.seen[s] {
		r.seen[s] = true
		a = append(a, s)
	}
	return a
}

// rewrite takes a sequence of strings in, adds variants of the these strings
// based on options and removes duplicates.
func (r *rewriter) rewrite(ss []string) []string {
	ns := []string{}
	for _, s := range ss {
		ns = r.insert(ns, s)
		if r.addCases {
			rs := []rune(s)
			rn := rs[0]
			for c := unicode.SimpleFold(rn); c != rn; c = unicode.SimpleFold(c) {
				rs[0] = c
				ns = r.insert(ns, string(rs))
			}
		}
	}
	return ns
}

// exemplarySet holds a parsed set of characters from the exemplarCharacters table.
type exemplarySet struct {
	typ       exemplarType
	set       []string
	charIndex int // cumulative total of phrases, including this set
}

type phraseGenerator struct {
	sets [exN]exemplarySet
	n    int
}

func (g *phraseGenerator) init(id string) {
	ec := exemplarCharacters
	loc := language.Make(id).String()
	// get sets for locale or parent locale if the set is not defined.
	for i := range g.sets {
		for p, ok := loc, true; ok; p, ok = parent(p) {
			if set, ok := ec[p]; ok && set[i] != "" {
				g.sets[i].set = strings.Split(set[i], " ")
				break
			}
		}
	}
	r := newRewriter()
	r.addCases = *cases
	for i := range g.sets {
		g.sets[i].set = r.rewrite(g.sets[i].set)
	}
	// compute indexes
	for i, set := range g.sets {
		g.n += len(set.set)
		g.sets[i].charIndex = g.n
	}
}

// phrase returns the ith phrase, where i < g.n.
func (g *phraseGenerator) phrase(i int) string {
	for _, set := range g.sets {
		if i < set.charIndex {
			return set.set[i-(set.charIndex-len(set.set))]
		}
	}
	panic("index out of range")
}

// generate generates inputs by combining all pairs of examplar strings.
// If doNorm is true, all input strings are normalized to NFC.
// TODO: allow other variations, statistical models, and random
// trailing sequences.
func (g *phraseGenerator) generate(doNorm bool) []Input {
	const (
		M         = 1024 * 1024
		buf8Size  = 30 * M
		buf16Size = 10 * M
	)
	// TODO: use a better way to limit the input size.
	if sq := int(math.Sqrt(float64(*limit))); g.n > sq {
		g.n = sq
	}
	size := g.n * g.n
	a := make([]Input, 0, size)
	buf8 := make([]byte, 0, buf8Size)
	buf16 := make([]uint16, 0, buf16Size)

	addInput := func(str string) {
		buf8 = buf8[len(buf8):]
		buf16 = buf16[len(buf16):]
		if len(str) > cap(buf8) {
			buf8 = make([]byte, 0, buf8Size)
		}
		if len(str) > cap(buf16) {
			buf16 = make([]uint16, 0, buf16Size)
		}
		if doNorm {
			buf8 = norm.NFD.AppendString(buf8, str)
		} else {
			buf8 = append(buf8, str...)
		}
		buf16 = appendUTF16(buf16, buf8)
		a = append(a, makeInput(buf8, buf16))
	}
	for i := 0; i < g.n; i++ {
		p1 := g.phrase(i)
		addInput(p1)
		for j := 0; j < g.n; j++ {
			p2 := g.phrase(j)
			addInput(p1 + p2)
		}
	}
	// permutate
	rnd := rand.New(rand.NewSource(int64(rand.Int())))
	for i := range a {
		j := i + rnd.Intn(len(a)-i)
		a[i], a[j] = a[j], a[i]
		a[i].index = i // allow restoring this order if input is used multiple times.
	}
	return a
}

func appendUTF16(buf []uint16, s []byte) []uint16 {
	for len(s) > 0 {
		r, sz := utf8.DecodeRune(s)
		s = s[sz:]
		r1, r2 := utf16.EncodeRune(r)
		if r1 != 0xFFFD {
			buf = append(buf, uint16(r1), uint16(r2))
		} else {
			buf = append(buf, uint16(r))
		}
	}
	return buf
}
