// API:
//
// type Grade struct {
// 	int, []string
// }
//
// type School
// func New() *School
// func (s *School) Add(string, int)
// func (s *School) Grade(int) []string
// func (s *School) Enrollment() []Grade

package school

import (
	"fmt"
	"math/rand"
	"strconv"
	"testing"
)

func TestNewSchoolIsEmpty(t *testing.T) {
	if len(New().Enrollment()) != 0 {
		t.Error("New school not empty")
	}
}

func list(e []Grade) (s string) {
	for _, l := range e {
		s += fmt.Sprintln(l)
	}
	return s
}

func TestAddStudent(t *testing.T) {
	exp := list([]Grade{{2, []string{"Aimee"}}})
	s := New()
	s.Add("Aimee", 2)
	got := list(s.Enrollment())
	if got != exp {
		t.Errorf(`Add Aimee level 2, got
%sexpected:
%s`, got, exp)
	}
}

func TestAddMoreSameGrade(t *testing.T) {
	exp := list([]Grade{{2, []string{"Blair James Paul"}}})
	s := New()
	s.Add("Blair", 2)
	s.Add("James", 2)
	s.Add("Paul", 2)
	got := list(s.Enrollment())
	if got != exp {
		t.Errorf(`Add more same grade, got
%sexpected:
%s`, got, exp)
	}
}

func TestAddDifferentGrades(t *testing.T) {
	exp := list([]Grade{
		{3, []string{"Chelsea"}},
		{7, []string{"Logan"}},
	})
	s := New()
	s.Add("Chelsea", 3)
	s.Add("Logan", 7)
	got := list(s.Enrollment())
	if got != exp {
		t.Errorf(`Add different grades, got
%sexpected:
%s`, got, exp)
	}
}

func TestGetGrade(t *testing.T) {
	exp := []string{"Bradley", "Franklin"}
	s := New()
	s.Add("Bradley", 5)
	s.Add("Franklin", 5)
	s.Add("Jeff", 1)
	got := s.Grade(5)
	if len(got) == len(exp) {
		if got[0] == exp[0] && got[1] == exp[1] ||
			got[0] == exp[1] && got[1] == exp[0] { // accept out of order
			return
		}
	}
	t.Errorf(`Get grade, got
%v
expected
%v`, got, exp)
}

func TestNonExistantGrade(t *testing.T) {
	s := New()
	got := s.Grade(1)
	if len(got) != 0 {
		t.Errorf(`Get non-existant grade, got
%v
expected
[]`, got)
	}
}

func TestSortedEnrollment(t *testing.T) {
	exp := list([]Grade{
		{3, []string{"Kyle"}},
		{4, []string{"Christopher Jennifer"}},
		{6, []string{"Kareem"}},
	})
	s := New()
	s.Add("Jennifer", 4)
	s.Add("Kareem", 6)
	s.Add("Christopher", 4)
	s.Add("Kyle", 3)
	got := list(s.Enrollment())
	if got != exp {
		t.Errorf(`Sorted enrollment, got
%sexpected:
%s`, got, exp)
	}
}

const (
	minLevel   = 1
	maxLevel   = 9
	enrollment = 400
)

func BenchmarkAddStudents(b *testing.B) {
	const pool = 1e6 // pool of students
	names := make([]string, pool)
	levels := make([]int, pool)
	for i := range names {
		names[i] = strconv.Itoa(rand.Intn(1e5))
		levels[i] = minLevel + rand.Intn(maxLevel-minLevel+1)
	}
	p := 0
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// bench combined time to create a school and add
		// a number of students, drawn from a pool of students
		s := New()
		for t := 0; t < enrollment; t++ {
			s.Add(names[p], levels[p])
			p = (p + 1) % pool
		}
	}
}

func BenchmarkEnrollment(b *testing.B) {
	const pool = 1000 // pool of schools
	ss := make([]*School, pool)
	for i := range ss {
		s := New()
		for t := 0; t < enrollment; t++ {
			s.Add(
				strconv.Itoa(rand.Intn(1e5)),
				minLevel+rand.Intn(maxLevel-minLevel+1))
		}
		ss[i] = s
	}
	p := 0
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		// bench time to get enrollment of a full school,
		// averaged over a pool of schools.
		ss[p].Enrollment()
		p = (p + 1) % pool
	}
}
