package system

import (
	"math/rand"
	"strings"
	"sync"
	"testing"
	"time"

	. "github.com/franela/goblin"
	"github.com/goccy/go-json"
)

func Test_Utils(t *testing.T) {
	g := Goblin(t)

	g.Describe("ScanReader", func() {
		g.BeforeEach(func() {
			maxBufferSize = 10
		})

		g.It("should truncate and return long lines", func() {
			reader := strings.NewReader("hello world this is a long line\nof text that should be truncated\nnot here\nbut definitely on this line")

			var lines []string
			err := ScanReader(reader, func(line []byte) {
				lines = append(lines, string(line))
			})

			g.Assert(err).IsNil()
			g.Assert(lines).Equal([]string{"hello worl", "of text th", "not here", "but defini"})
		})

		g.It("should replace cariage returns with newlines", func() {
			reader := strings.NewReader("test\rstring\r\nanother\rline\nhodor\r\r\rheld the door\nmaterial gourl\n")
			var lines []string
			err := ScanReader(reader, func(line []byte) {
				lines = append(lines, string(line))
			})

			g.Assert(err).IsNil()
			g.Assert(lines).Equal([]string{"test\rstrin", "another\rli", "hodor\r\r\rhe", "material g"})
		})
	})

	g.Describe("AtomicBool", func() {
		var b *AtomicBool
		g.BeforeEach(func() {
			b = NewAtomicBool(false)
		})

		g.It("initalizes with the provided start value", func() {
			b = NewAtomicBool(true)
			g.Assert(b.Load()).IsTrue()

			b = NewAtomicBool(false)
			g.Assert(b.Load()).IsFalse()
		})

		g.Describe("AtomicBool#Store", func() {
			g.It("stores the provided value", func() {
				g.Assert(b.Load()).IsFalse()
				b.Store(true)
				g.Assert(b.Load()).IsTrue()
			})

			// This test makes no assertions, it just expects to not hit a race condition
			// by having multiple things writing at the same time.
			g.It("handles contention from multiple routines", func() {
				var wg sync.WaitGroup

				wg.Add(100)
				for i := 0; i < 100; i++ {
					go func(i int) {
						b.Store(i%2 == 0)
						wg.Done()
					}(i)
				}
				wg.Wait()
			})
		})

		g.Describe("AtomicBool#SwapIf", func() {
			g.It("swaps the value out if different than what is stored", func() {
				o := b.SwapIf(false)
				g.Assert(o).IsFalse()
				g.Assert(b.Load()).IsFalse()

				o = b.SwapIf(true)
				g.Assert(o).IsTrue()
				g.Assert(b.Load()).IsTrue()

				o = b.SwapIf(true)
				g.Assert(o).IsFalse()
				g.Assert(b.Load()).IsTrue()

				o = b.SwapIf(false)
				g.Assert(o).IsTrue()
				g.Assert(b.Load()).IsFalse()
			})
		})

		g.Describe("can be marshaled with JSON", func() {
			type testStruct struct {
				Value AtomicBool `json:"value"`
			}

			var o testStruct
			err := json.Unmarshal([]byte(`{"value":true}`), &o)

			g.Assert(err).IsNil()
			g.Assert(o.Value.Load()).IsTrue()

			b, err2 := json.Marshal(&o)
			g.Assert(err2).IsNil()
			g.Assert(b).Equal([]byte(`{"value":true}`))
		})
	})
}

func Benchmark_ScanReader(b *testing.B) {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	var str string
	for i := 0; i < 10; i++ {
		str += strings.Repeat("hello \rworld", r.Intn(2000)) + "\n"
	}
	reader := strings.NewReader(str)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_ = ScanReader(reader, func(line []byte) {
			// no op
		})
	}
}