package language

import (
	"fmt"
	"strconv"
	"strings"
	"testing"
)

const onePlusEpsilon = "1.00000000000000000000000000000001"

func TestGetPluralSpec(t *testing.T) {
	tests := []struct {
		src  string
		spec *PluralSpec
	}{
		{"pl", pluralSpecs["pl"]},
		{"en", pluralSpecs["en"]},
		{"en-US", pluralSpecs["en"]},
		{"en_US", pluralSpecs["en"]},
		{"en-GB", pluralSpecs["en"]},
		{"zh-CN", pluralSpecs["zh"]},
		{"zh-TW", pluralSpecs["zh"]},
		{"pt-BR", pluralSpecs["pt"]},
		{"pt_BR", pluralSpecs["pt"]},
		{"pt-PT", pluralSpecs["pt-pt"]},
		{"pt_PT", pluralSpecs["pt-pt"]},
		{"zh-Hans-CN", pluralSpecs["zh"]},
		{"zh-Hant-TW", pluralSpecs["zh"]},
		{"zh-CN", pluralSpecs["zh"]},
		{"zh-TW", pluralSpecs["zh"]},
		{"zh-Hans", pluralSpecs["zh"]},
		{"zh-Hant", pluralSpecs["zh"]},
		{"ko-KR", pluralSpecs["ko"]},
		{"ko_KR", pluralSpecs["ko"]},
		{"ko-KP", pluralSpecs["ko"]},
		{"ko_KP", pluralSpecs["ko"]},
		{"en-US-en-US", pluralSpecs["en"]},
		{"th", pluralSpecs["th"]},
		{"th-TH", pluralSpecs["th"]},
		{"hr", pluralSpecs["hr"]},
		{"bs", pluralSpecs["bs"]},
		{"sr", pluralSpecs["sr"]},
		{"ti", pluralSpecs["ti"]},
		{"vi", pluralSpecs["vi"]},
		{"vi-VN", pluralSpecs["vi"]},
		{"mk", pluralSpecs["mk"]},
		{"mk-MK", pluralSpecs["mk"]},
		{"lv", pluralSpecs["lv"]},
		{"lv-LV", pluralSpecs["lv"]},
		{".en-US..en-US.", nil},
		{"zh, en-gb;q=0.8, en;q=0.7", nil},
		{"zh,en-gb;q=0.8,en;q=0.7", nil},
		{"xx, en-gb;q=0.8, en;q=0.7", nil},
		{"xx,en-gb;q=0.8,en;q=0.7", nil},
		{"xx-YY,xx;q=0.8,en-US,en;q=0.8,de;q=0.6,nl;q=0.4", nil},
		{"/foo/es/en.json", nil},
		{"xx-Yyen-US", nil},
		{"en US", nil},
		{"", nil},
		{"-", nil},
		{"_", nil},
		{".", nil},
		{"-en", nil},
		{"_en", nil},
		{"-en-", nil},
		{"_en_", nil},
		{"xx", nil},
	}
	for _, test := range tests {
		spec := getPluralSpec(test.src)
		if spec != test.spec {
			t.Errorf("getPluralSpec(%q) = %q expected %q", test.src, spec, test.spec)
		}
	}
}

type pluralTest struct {
	num    interface{}
	plural Plural
}

func appendIntegerTests(tests []pluralTest, plural Plural, examples []string) []pluralTest {
	for _, ex := range expandExamples(examples) {
		i, err := strconv.ParseInt(ex, 10, 64)
		if err != nil {
			panic(err)
		}
		tests = append(tests, pluralTest{ex, plural}, pluralTest{i, plural})
	}
	return tests
}

func appendDecimalTests(tests []pluralTest, plural Plural, examples []string) []pluralTest {
	for _, ex := range expandExamples(examples) {
		tests = append(tests, pluralTest{ex, plural})
	}
	return tests
}

func expandExamples(examples []string) []string {
	var expanded []string
	for _, ex := range examples {
		if parts := strings.Split(ex, "~"); len(parts) == 2 {
			for ex := parts[0]; ; ex = increment(ex) {
				expanded = append(expanded, ex)
				if ex == parts[1] {
					break
				}
			}
		} else {
			expanded = append(expanded, ex)
		}
	}
	return expanded
}

func increment(dec string) string {
	runes := []rune(dec)
	carry := true
	for i := len(runes) - 1; carry && i >= 0; i-- {
		switch runes[i] {
		case '.':
			continue
		case '9':
			runes[i] = '0'
		default:
			runes[i]++
			carry = false
		}
	}
	if carry {
		runes = append([]rune{'1'}, runes...)
	}
	return string(runes)
}

//
// Below here are tests that were manually written before tests were automatically generated.
// These are kept around as sanity checks for our code generation.
//

func TestArabic(t *testing.T) {
	tests := []pluralTest{
		{0, Zero},
		{"0", Zero},
		{"0.0", Zero},
		{"0.00", Zero},
		{1, One},
		{"1", One},
		{"1.0", One},
		{"1.00", One},
		{onePlusEpsilon, Other},
		{2, Two},
		{"2", Two},
		{"2.0", Two},
		{"2.00", Two},
		{3, Few},
		{"3", Few},
		{"3.0", Few},
		{"3.00", Few},
		{10, Few},
		{"10", Few},
		{"10.0", Few},
		{"10.00", Few},
		{103, Few},
		{"103", Few},
		{"103.0", Few},
		{"103.00", Few},
		{110, Few},
		{"110", Few},
		{"110.0", Few},
		{"110.00", Few},
		{11, Many},
		{"11", Many},
		{"11.0", Many},
		{"11.00", Many},
		{99, Many},
		{"99", Many},
		{"99.0", Many},
		{"99.00", Many},
		{111, Many},
		{"111", Many},
		{"111.0", Many},
		{"111.00", Many},
		{199, Many},
		{"199", Many},
		{"199.0", Many},
		{"199.00", Many},
		{100, Other},
		{"100", Other},
		{"100.0", Other},
		{"100.00", Other},
		{102, Other},
		{"102", Other},
		{"102.0", Other},
		{"102.00", Other},
		{200, Other},
		{"200", Other},
		{"200.0", Other},
		{"200.00", Other},
		{202, Other},
		{"202", Other},
		{"202.0", Other},
		{"202.00", Other},
	}
	tests = appendFloatTests(tests, 0.1, 0.9, Other)
	tests = appendFloatTests(tests, 1.1, 1.9, Other)
	tests = appendFloatTests(tests, 2.1, 2.9, Other)
	tests = appendFloatTests(tests, 3.1, 3.9, Other)
	tests = appendFloatTests(tests, 4.1, 4.9, Other)
	runTests(t, "ar", tests)
}

func TestBelarusian(t *testing.T) {
	tests := []pluralTest{
		{0, Many},
		{1, One},
		{2, Few},
		{3, Few},
		{4, Few},
		{5, Many},
		{19, Many},
		{20, Many},
		{21, One},
		{11, Many},
		{52, Few},
		{101, One},
		{"0.1", Other},
		{"0.7", Other},
		{"1.5", Other},
		{"1.0", One},
		{onePlusEpsilon, Other},
		{"2.0", Few},
		{"10.0", Many},
	}
	runTests(t, "be", tests)
}

func TestBurmese(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "my", tests)
}

func TestCatalan(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{"0", Other},
		{1, One},
		{"1", One},
		{"1.0", Other},
		{onePlusEpsilon, Other},
		{2, Other},
		{"2", Other},
	}
	tests = appendIntTests(tests, 2, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "ca", tests)
}

func TestChinese(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "zh", tests)
}

func TestCzech(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{"0", Other},
		{1, One},
		{"1", One},
		{onePlusEpsilon, Many},
		{2, Few},
		{"2", Few},
		{3, Few},
		{"3", Few},
		{4, Few},
		{"4", Few},
		{5, Other},
		{"5", Other},
	}
	tests = appendFloatTests(tests, 0, 10, Many)
	runTests(t, "cs", tests)
}

func TestDanish(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{onePlusEpsilon, One},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.1, 1.9, One)
	tests = appendFloatTests(tests, 2.0, 10.0, Other)
	runTests(t, "da", tests)
}

func TestDutch(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 10.0, Other)
	runTests(t, "nl", tests)
}

func TestEnglish(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 10.0, Other)
	runTests(t, "en", tests)
}

func TestFrench(t *testing.T) {
	tests := []pluralTest{
		{0, One},
		{1, One},
		{onePlusEpsilon, One},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 1.9, One)
	tests = appendFloatTests(tests, 2.0, 10.0, Other)
	runTests(t, "fr", tests)
}

func TestGerman(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 10.0, Other)
	runTests(t, "de", tests)
}

func TestIcelandic(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{2, Other},
		{11, Other},
		{21, One},
		{111, Other},
		{"0.0", Other},
		{"0.1", One},
		{"2.0", Other},
	}
	runTests(t, "is", tests)
}

func TestIndonesian(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "id", tests)
}

func TestItalian(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 10.0, Other)
	runTests(t, "it", tests)
}

func TestKorean(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "ko", tests)
}

func TestLatvian(t *testing.T) {
	tests := []pluralTest{
		{0, Zero},
		{"0", Zero},
		{"0.1", One},
		{1, One},
		{"1", One},
		{onePlusEpsilon, One},
		{"10.0", Zero},
		{"10.1", One},
		{"10.2", Other},
		{21, One},
	}
	tests = appendFloatTests(tests, 0.2, 0.9, Other)
	tests = appendFloatTests(tests, 1.2, 1.9, Other)
	tests = appendIntTests(tests, 2, 9, Other)
	tests = appendIntTests(tests, 10, 20, Zero)
	tests = appendIntTests(tests, 22, 29, Other)
	runTests(t, "lv", tests)
}

func TestJapanese(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "ja", tests)
}

func TestLithuanian(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{2, Few},
		{3, Few},
		{9, Few},
		{10, Other},
		{11, Other},
		{"0.1", Many},
		{"0.7", Many},
		{"1.0", One},
		{onePlusEpsilon, Many},
		{"2.0", Few},
		{"10.0", Other},
	}
	runTests(t, "lt", tests)
}

func TestMalay(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "ms", tests)
}

func TestPolish(t *testing.T) {
	tests := []pluralTest{
		{0, Many},
		{1, One},
		{2, Few},
		{3, Few},
		{4, Few},
		{5, Many},
		{19, Many},
		{20, Many},
		{10, Many},
		{11, Many},
		{52, Few},
		{"0.1", Other},
		{"0.7", Other},
		{"1.5", Other},
		{"1.0", Other},
		{onePlusEpsilon, Other},
		{"2.0", Other},
		{"10.0", Other},
	}
	runTests(t, "pl", tests)
}

func TestPortuguese(t *testing.T) {
	tests := []pluralTest{
		{0, One},
		{"0.0", One},
		{1, One},
		{"1.0", One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.1, 0.9, Other)
	tests = appendFloatTests(tests, 1.1, 10.0, Other)
	runTests(t, "pt", tests)
}

func TestMacedonian(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{"1.1", One},
		{"2.1", One},
		{onePlusEpsilon, One},
		{2, Other},
		{"2.2", Other},
		{11, One},
	}
	runTests(t, "mk", tests)
}

func TestPortugueseEuropean(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{"0.0", Other},
		{"0.1", Other},
		{"0.01", Other},
		{1, One},
		{"1", One},
		{"1.1", Other},
		{"1.01", Other},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 2.0, 10.0, Other)
	runTests(t, "pt-pt", tests)
}

func TestRussian(t *testing.T) {
	tests := []pluralTest{
		{0, Many},
		{1, One},
		{2, Few},
		{3, Few},
		{4, Few},
		{5, Many},
		{19, Many},
		{20, Many},
		{21, One},
		{11, Many},
		{52, Few},
		{101, One},
		{"0.1", Other},
		{"0.7", Other},
		{"1.5", Other},
		{"1.0", Other},
		{onePlusEpsilon, Other},
		{"2.0", Other},
		{"10.0", Other},
	}
	runTests(t, "ru", tests)
}

func TestSpanish(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{"1", One},
		{"1.0", One},
		{"1.00", One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 0.9, Other)
	tests = appendFloatTests(tests, 1.1, 10.0, Other)
	runTests(t, "es", tests)
}

func TestNorweigan(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{"1", One},
		{"1.0", One},
		{"1.00", One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 0.9, Other)
	tests = appendFloatTests(tests, 1.1, 10.0, Other)
	runTests(t, "no", tests)
}

func TestBulgarian(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{2, Other},
		{3, Other},
		{9, Other},
		{10, Other},
		{11, Other},
		{"0.1", Other},
		{"0.7", Other},
		{"1.0", One},
		{"1.001", Other},
		{onePlusEpsilon, Other},
		{"1.1", Other},
		{"2.0", Other},
		{"10.0", Other},
	}
	runTests(t, "bg", tests)
}

func TestSwedish(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{onePlusEpsilon, Other},
		{2, Other},
	}
	tests = appendFloatTests(tests, 0.0, 10.0, Other)
	runTests(t, "sv", tests)
}

func TestThai(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "th", tests)
}

func TestVietnamese(t *testing.T) {
	tests := appendIntTests(nil, 0, 10, Other)
	tests = appendFloatTests(tests, 0, 10, Other)
	runTests(t, "vi", tests)
}

func TestTurkish(t *testing.T) {
	tests := []pluralTest{
		{0, Other},
		{1, One},
		{"1", One},
		{"1.0", One},
		{"1.00", One},
		{"1.001", Other},
		{"1.100", Other},
		{"1.101", Other},
		{onePlusEpsilon, Other},
		{2, Other},
		{"0.7", Other},
		{"2.0", Other},
	}
	runTests(t, "tr", tests)
}

func TestUkrainian(t *testing.T) {
	tests := []pluralTest{
		{0, Many},
		{1, One},
		{2, Few},
		{3, Few},
		{4, Few},
		{5, Many},
		{19, Many},
		{20, Many},
		{21, One},
		{11, Many},
		{52, Few},
		{101, One},
		{"0.1", Other},
		{"0.7", Other},
		{"1.5", Other},
		{"1.0", Other},
		{onePlusEpsilon, Other},
		{"2.0", Other},
		{"10.0", Other},
	}
	runTests(t, "uk", tests)
}

func TestCroatian(t *testing.T) {
	tests := makeCroatianBosnianSerbianTests()
	runTests(t, "hr", tests)
}

func TestBosnian(t *testing.T) {
	tests := makeCroatianBosnianSerbianTests()
	runTests(t, "bs", tests)
}

func TestSerbian(t *testing.T) {
	tests := makeCroatianBosnianSerbianTests()
	runTests(t, "sr", tests)
}

func makeCroatianBosnianSerbianTests() []pluralTest {
	return []pluralTest{
		{1, One},
		{"0.1", One},
		{21, One},
		{101, One},
		{1001, One},
		{51, One},
		{"1.1", One},
		{"5.1", One},
		{"100.1", One},
		{"1000.1", One},
		{2, Few},
		{"0.2", Few},
		{22, Few},
		{"1.2", Few},
		{24, Few},
		{"2.4", Few},
		{102, Few},
		{"100.2", Few},
		{1002, Few},
		{"1000.2", Few},
		{5, Other},
		{"0.5", Other},
		{0, Other},
		{100, Other},
		{19, Other},
		{"0.0", Other},
		{"100.0", Other},
		{"1000.0", Other},
	}
}

func TestTigrinya(t *testing.T) {
	tests := []pluralTest{
		{0, One},
		{1, One},
	}
	tests = appendIntTests(tests, 2, 10, Other)
	tests = appendFloatTests(tests, 1.1, 10.0, Other)
	runTests(t, "ti", tests)
}

func appendIntTests(tests []pluralTest, from, to int, p Plural) []pluralTest {
	for i := from; i <= to; i++ {
		tests = append(tests, pluralTest{i, p})
	}
	return tests
}

func appendFloatTests(tests []pluralTest, from, to float64, p Plural) []pluralTest {
	stride := 0.1
	format := "%.1f"
	for f := from; f < to; f += stride {
		tests = append(tests, pluralTest{fmt.Sprintf(format, f), p})
	}
	tests = append(tests, pluralTest{fmt.Sprintf(format, to), p})
	return tests
}

func runTests(t *testing.T, pluralSpecID string, tests []pluralTest) {
	pluralSpecID = normalizePluralSpecID(pluralSpecID)
	if spec := pluralSpecs[pluralSpecID]; spec != nil {
		for _, test := range tests {
			if plural, err := spec.Plural(test.num); plural != test.plural {
				t.Errorf("%s: PluralCategory(%#v) returned %s, %v; expected %s", pluralSpecID, test.num, plural, err, test.plural)
			}
		}
	} else {
		t.Errorf("could not find plural spec for locale %s", pluralSpecID)
	}

}
