package throttler

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

func TestThrottle(t *testing.T) {
	var tests = []struct {
		Desc       string
		Jobs       []string
		MaxWorkers int
		TotalJobs  int
	}{
		{
			"Standard implementation",
			[]string{"job01", "job02", "job03", "job04", "job05", "job06", "job07", "job08", "job09", "job10",
				"job11", "job12", "job13", "job14", "job15", "job16", "job17", "job18", "job19", "job20",
				"job21", "job22", "job23", "job24", "job25", "job26", "job27", "job28", "job29", "job30",
				"job31", "job32", "job33", "job34", "job35", "job36", "job37", "job38", "job39", "job40",
				"job41", "job42", "job43", "job44", "job45", "job46", "job47", "job48", "job49", "job50"},
			5,
			-1,
		}, {
			"Incorrectly has 0 as TotalWorkers",
			[]string{"job01", "job02", "job03", "job04", "job05", "job06", "job07", "job08", "job09", "job10",
				"job11", "job12", "job13", "job14", "job15", "job16", "job17", "job18", "job19", "job20",
				"job21", "job22", "job23", "job24", "job25", "job26", "job27", "job28", "job29", "job30",
				"job31", "job32", "job33", "job34", "job35", "job36", "job37", "job38", "job39", "job40",
				"job41", "job42", "job43", "job44", "job45", "job46", "job47", "job48", "job49", "job50"},
			5,
			0,
		}, {
			"More workers than jobs",
			[]string{"job01", "job02", "job03", "job04", "job05", "job06", "job07", "job08", "job09", "job10",
				"job11", "job12", "job13", "job14", "job15", "job16", "job17", "job18", "job19", "job20",
				"job21", "job22", "job23", "job24", "job25", "job26", "job27", "job28", "job29", "job30",
				"job31", "job32", "job33", "job34", "job35", "job36", "job37", "job38", "job39", "job40",
				"job41", "job42", "job43", "job44", "job45", "job46", "job47", "job48", "job49", "job50"},
			50000,
			-1,
		},
	}

	for _, test := range tests {
		totalJobs := len(test.Jobs)
		if test.TotalJobs != -1 {
			totalJobs = test.TotalJobs
		}
		th := New(test.MaxWorkers, totalJobs)
		for _, job := range test.Jobs {
			go func(job string, th *Throttler) {
				defer th.Done(nil)
				time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
			}(job, th)
			th.Throttle()
		}
		if th.Err() != nil {
			fmt.Println("err:", th.Err())
		}
	}
}

func TestThrottleWithErrors(t *testing.T) {
	var tests = []struct {
		Desc       string
		Jobs       []string
		MaxWorkers int
		TotalJobs  int
	}{
		{
			"Standard implementation",
			[]string{"job01", "job02", "job03", "job04", "job05", "job06", "job07", "job08", "job09", "job10",
				"job11", "job12", "job13", "job14", "job15", "job16", "job17", "job18", "job19", "job20",
				"job21", "job22", "job23", "job24", "job25", "job26", "job27", "job28", "job29", "job30",
				"job31", "job32", "job33", "job34", "job35", "job36", "job37", "job38", "job39", "job40",
				"job41", "job42", "job43", "job44", "job45", "job46", "job47", "job48", "job49", "job50"},
			5,
			-1,
		}, {
			"Standard implementation",
			[]string{"job01", "job02"},
			5,
			-1,
		},
	}

	for _, test := range tests {
		totalJobs := len(test.Jobs)
		if test.TotalJobs != -1 {
			totalJobs = test.TotalJobs
		}
		th := New(test.MaxWorkers, totalJobs)
		for _, job := range test.Jobs {
			go func(job string, th *Throttler) {
				jobNum, _ := strconv.ParseInt(job[len(job)-2:], 10, 8)
				var err error
				if jobNum%2 != 0 {
					err = fmt.Errorf("Error on %s", job)
				}
				defer th.Done(err)

				time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
			}(job, th)
			th.Throttle()
		}
		if len(th.Errs()) != totalJobs/2 {
			t.Fatal("The wrong number of errors were returned")
		}
		if th.Err() != nil {
			fmt.Println("err:", th.Err())
		}
	}
}

func TestThrottlePanic(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Fatal("Test failed to panic")
		}
	}()
	New(0, 100)
}

func TestBatchedThrottler(t *testing.T) {
	var tests = []struct {
		Desc                  string
		ToBeBatched           []string
		MaxWorkers            int
		BatchSize             int
		ExpectedBatchedSlices [][]string
	}{
		{
			"Standard implementation",
			[]string{"item01", "item02", "item03", "item04", "item05", "item06", "item07", "item08", "item09", "item10",
				"item11", "item12", "item13", "item14", "item15", "item16", "item17", "item18", "item19", "item20",
				"item21", "item22", "item23", "item24", "item25", "item26", "item27", "item28", "item29", "item30",
				"item31", "item32", "item33", "item34", "item35", "item36", "item37", "item38", "item39", "item40",
				"item41", "item42", "item43", "item44", "item45", "item46", "item47", "item48", "item49",
			},
			10,
			2,
			[][]string{
				{"item01", "item02"},
				{"item03", "item04"},
				{"item05", "item06"},
				{"item07", "item08"},
				{"item09", "item10"},
				{"item11", "item12"},
				{"item13", "item14"},
				{"item15", "item16"},
				{"item17", "item18"},
				{"item19", "item20"},
				{"item21", "item22"},
				{"item23", "item24"},
				{"item25", "item26"},
				{"item27", "item28"},
				{"item29", "item30"},
				{"item31", "item32"},
				{"item33", "item34"},
				{"item35", "item36"},
				{"item37", "item38"},
				{"item39", "item40"},
				{"item41", "item42"},
				{"item43", "item44"},
				{"item45", "item46"},
				{"item47", "item48"},
				{"item49"},
			},
		},
	}

	for _, test := range tests {
		th := NewBatchedThrottler(test.MaxWorkers, len(test.ToBeBatched), test.BatchSize)
		for i := 0; i < th.TotalJobs(); i++ {
			go func(tbbSlice []string, expectedSlice []string) {
				var err error
				if !reflect.DeepEqual(tbbSlice, expectedSlice) {
					err = fmt.Errorf("wanted: %#v | got: %#v", expectedSlice, tbbSlice)
				}
				th.Done(err)
			}(test.ToBeBatched[th.BatchStartIndex():th.BatchEndIndex()], test.ExpectedBatchedSlices[i])
			if errCount := th.Throttle(); errCount > 0 {
				break
			}
		}

		if th.Err() != nil {
			t.Fatal(th.Err())
		}
	}
}

func TestSetMaxWorkers(t *testing.T) {
	var tests = []struct {
		Desc              string
		Jobs              []string
		InitialMaxWorkers int
		EndMaxWorkers     int
		TotalJobs         int
	}{
		{
			"Standard implementation",
			[]string{"job01", "job02", "job03", "job04", "job05", "job06", "job07", "job08", "job09", "job10",
				"job11", "job12", "job13", "job14", "job15", "job16", "job17", "job18", "job19", "job20",
				"job21", "job22", "job23", "job24", "job25", "job26", "job27", "job28", "job29", "job30",
				"job31", "job32", "job33", "job34", "job35", "job36", "job37", "job38", "job39", "job40",
				"job41", "job42", "job43", "job44", "job45", "job46", "job47", "job48", "job49", "job50"},
			1,
			5,
			-1,
		}, {
			"Incorrectly has 0 as TotalWorkers",
			[]string{"job01", "job02", "job03", "job04", "job05", "job06", "job07", "job08", "job09", "job10",
				"job11", "job12", "job13", "job14", "job15", "job16", "job17", "job18", "job19", "job20",
				"job21", "job22", "job23", "job24", "job25", "job26", "job27", "job28", "job29", "job30",
				"job31", "job32", "job33", "job34", "job35", "job36", "job37", "job38", "job39", "job40",
				"job41", "job42", "job43", "job44", "job45", "job46", "job47", "job48", "job49", "job50"},
			1,
			5,
			0,
		}, {
			"More workers than jobs",
			[]string{"job01", "job02", "job03", "job04", "job05", "job06", "job07", "job08", "job09", "job10",
				"job11", "job12", "job13", "job14", "job15", "job16", "job17", "job18", "job19", "job20",
				"job21", "job22", "job23", "job24", "job25", "job26", "job27", "job28", "job29", "job30",
				"job31", "job32", "job33", "job34", "job35", "job36", "job37", "job38", "job39", "job40",
				"job41", "job42", "job43", "job44", "job45", "job46", "job47", "job48", "job49", "job50"},
			1,
			50000,
			-1,
		},
	}

	for _, test := range tests {
		totalJobs := len(test.Jobs)
		if test.TotalJobs != -1 {
			totalJobs = test.TotalJobs
		}
		th := New(test.InitialMaxWorkers, totalJobs)
		for i, job := range test.Jobs {
			if i == test.InitialMaxWorkers+1 {
				th.SetMaxWorkers(test.EndMaxWorkers)
			}
			go func(job string, th *Throttler) {
				defer th.Done(nil)
				time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
			}(job, th)
			th.Throttle()
		}
		if th.Err() != nil {
			fmt.Println("err:", th.Err())
		}
	}
}

func TestSetMaxWorkersPanic(t *testing.T) {
	defer func() {
		if r := recover(); r == nil {
			t.Fatal("Test failed to panic")
		}
	}()
	th := New(1, 10)
	th.SetMaxWorkers(-1)
}
