package circ

import (
	"bytes"
	"errors"
	"io"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestNewReader(t *testing.T) {
	var size = 16
	var block = 4
	buf := NewReader(size, block)

	require.NotNil(t, buf.buf)
	require.Equal(t, size, len(buf.buf))
	require.Equal(t, size, buf.size)
	require.Equal(t, block, buf.block)
}

func TestNewReaderFromSlice(t *testing.T) {
	b := NewBytesPool(256)
	buf := NewReaderFromSlice(DefaultBlockSize, b.Get())
	require.NotNil(t, buf.buf)
	require.Equal(t, 256, cap(buf.buf))
}

func TestReadFrom(t *testing.T) {
	buf := NewReader(16, 4)

	b4 := bytes.Repeat([]byte{'-'}, 4)
	br := bytes.NewReader(b4)

	_, err := buf.ReadFrom(br)
	require.True(t, errors.Is(err, io.EOF))
	require.Equal(t, bytes.Repeat([]byte{'-'}, 4), buf.buf[:4])
	require.Equal(t, int64(4), buf.head)

	br.Reset(b4)
	_, err = buf.ReadFrom(br)
	require.True(t, errors.Is(err, io.EOF))
	require.Equal(t, int64(8), buf.head)

	br.Reset(b4)
	_, err = buf.ReadFrom(br)
	require.True(t, errors.Is(err, io.EOF))
	require.Equal(t, int64(12), buf.head)
}

func TestReadFromWrap(t *testing.T) {
	buf := NewReader(16, 4)
	buf.buf = bytes.Repeat([]byte{'-'}, 16)
	buf.SetPos(8, 14)
	br := bytes.NewReader(bytes.Repeat([]byte{'/'}, 8))

	o := make(chan error)
	go func() {
		_, err := buf.ReadFrom(br)
		o <- err
	}()
	time.Sleep(time.Millisecond * 100)
	go func() {
		atomic.StoreUint32(&buf.done, 1)
		buf.rcond.L.Lock()
		buf.rcond.Broadcast()
		buf.rcond.L.Unlock()
	}()
	<-o
	require.Equal(t, []byte{'/', '/', '/', '/', '/', '/', '-', '-', '-', '-', '-', '-', '-', '-', '/', '/'}, buf.Get())
	require.Equal(t, int64(22), atomic.LoadInt64(&buf.head))
	require.Equal(t, 6, buf.Index(atomic.LoadInt64(&buf.head)))
}

func TestReadOK(t *testing.T) {
	buf := NewReader(16, 4)
	buf.buf = []byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p'}

	tests := []struct {
		tail  int64
		head  int64
		n     int
		bytes []byte
		desc  string
	}{
		{tail: 0, head: 4, n: 4, bytes: []byte{'a', 'b', 'c', 'd'}, desc: "0, 4 OK"},
		{tail: 3, head: 15, n: 8, bytes: []byte{'d', 'e', 'f', 'g', 'h', 'i', 'j', 'k'}, desc: "3, 15 OK"},
		{tail: 14, head: 15, n: 6, bytes: []byte{'o', 'p', 'a', 'b', 'c', 'd'}, desc: "14, 2 wrapped OK"},
	}

	for i, tt := range tests {
		buf.SetPos(tt.tail, tt.head)
		o := make(chan []byte)
		go func() {
			p, _ := buf.Read(tt.n)
			o <- p
		}()

		time.Sleep(time.Millisecond)
		atomic.StoreInt64(&buf.head, buf.head+int64(tt.n))

		buf.wcond.L.Lock()
		buf.wcond.Broadcast()
		buf.wcond.L.Unlock()

		done := <-o
		require.Equal(t, tt.bytes, done, "Peeked bytes mismatch [i:%d] %s", i, tt.desc)

	}
}

func TestReadEnded(t *testing.T) {
	buf := NewBuffer(16, 4)
	o := make(chan error)
	go func() {
		_, err := buf.Read(4)
		o <- err
	}()
	time.Sleep(time.Millisecond)
	atomic.StoreUint32(&buf.done, 1)
	buf.wcond.L.Lock()
	buf.wcond.Broadcast()
	buf.wcond.L.Unlock()

	require.Error(t, <-o)
}
