package fwd

import (
	"bytes"
	"io"
	"math/rand"
	"testing"
)

type chunkedWriter struct {
	w *Writer
}

// writes 'p' in randomly-sized chunks
func (c chunkedWriter) Write(p []byte) (int, error) {
	l := len(p)
	n := 0
	for n < l {
		amt := max(rand.Intn(l-n), 1)      // number of bytes to write; at least 1
		nn, err := c.w.Write(p[n : n+amt]) //
		n += nn
		if err == nil && nn < amt {
			err = io.ErrShortWrite
		}
		if err != nil {
			return n, err
		}
	}
	return n, nil
}

// analogous to Write(), but w/ str
func (c chunkedWriter) WriteString(s string) (int, error) {
	l := len(s)
	n := 0
	for n < l {
		amt := max(rand.Intn(l-n), 1)            // number of bytes to write; at least 1
		nn, err := c.w.WriteString(s[n : n+amt]) //
		n += nn
		if err == nil && nn < amt {
			err = io.ErrShortWrite
		}
		if err != nil {
			return n, err
		}
	}
	return n, nil
}

// writes via random calls to Next()
type nextWriter struct {
	wr *Writer
}

func (c nextWriter) Write(p []byte) (int, error) {
	l := len(p)
	n := 0
	for n < l {
		amt := max(rand.Intn(l-n), 1) // at least 1 byte
		fwd, err := c.wr.Next(amt)    // get next (amt) bytes
		if err != nil {

			// this may happen occasionally
			if err == io.ErrShortBuffer {
				if cap(c.wr.buf) >= amt {
					panic("bad io.ErrShortBuffer")
				}
				continue
			}

			return n, err
		}
		if len(fwd) != amt {
			panic("bad Next() len")
		}
		n += copy(fwd, p[n:])
	}
	return n, nil
}

func TestWrite(t *testing.T) {
	nbts := 4096
	bts := randomBts(nbts)
	var buf bytes.Buffer
	wr := NewWriterSize(&buf, 512)

	if wr.BufferSize() != 512 {
		t.Fatalf("expected BufferSize() to be %d; found %d", 512, wr.BufferSize())
	}

	cwr := chunkedWriter{wr}
	nb, err := cwr.Write(bts)
	if err != nil {
		t.Fatal(err)
	}
	if nb != nbts {
		t.Fatalf("expected to write %d bytes; wrote %d bytes", nbts, nb)
	}
	err = wr.Flush()
	if err != nil {
		t.Fatal(err)
	}

	if wr.Buffered() != 0 {
		t.Fatalf("expected 0 buffered bytes; found %d", wr.Buffered())
	}

	if buf.Len() != nbts {
		t.Fatalf("wrote %d bytes, but buffer is %d bytes long", nbts, buf.Len())
	}
	if !bytes.Equal(bts, buf.Bytes()) {
		t.Fatal("buf.Bytes() is not the same as the input bytes")
	}
}

func TestWriteString(t *testing.T) {
	nbts := 3998
	str := string(randomBts(nbts))
	var buf bytes.Buffer
	wr := NewWriterSize(&buf, 1137)

	if wr.BufferSize() != 1137 {
		t.Fatalf("expected BufferSize() to return %d; returned %d", 1137, wr.BufferSize())
	}

	cwr := chunkedWriter{wr}
	nb, err := cwr.WriteString(str)
	if err != nil {
		t.Fatal(err)
	}
	if nb != nbts {
		t.Fatalf("expected to write %d bytes; wrote %d bytes", nbts, nb)
	}

	err = wr.Flush()
	if err != nil {
		t.Fatal(err)
	}

	if wr.Buffered() != 0 {
		t.Fatalf("expected 0 buffered bytes; found %d", wr.Buffered())
	}

	if buf.Len() != nbts {
		t.Fatalf("wrote %d bytes, buf buffer is %d bytes long", nbts, buf.Len())
	}
	if buf.String() != str {
		t.Fatal("buf.String() is not the same as input string")
	}
}

func TestWriteByte(t *testing.T) {
	nbts := 3200
	bts := randomBts(nbts)
	var buf bytes.Buffer
	wr := NewWriter(&buf)

	if wr.BufferSize() != DefaultWriterSize {
		t.Fatalf("expected BufferSize() to return %d; returned %d", DefaultWriterSize, wr.BufferSize())
	}

	// write byte-by-byte
	for _, b := range bts {
		if err := wr.WriteByte(b); err != nil {
			t.Fatal(err)
		}
	}

	err := wr.Flush()
	if err != nil {
		t.Fatal(err)
	}

	if buf.Len() != nbts {
		t.Fatalf("expected buf.Len() to be %d; got %d", nbts, buf.Len())
	}

	if !bytes.Equal(buf.Bytes(), bts) {
		t.Fatal("buf.Bytes() and input are not equal")
	}
}

func TestWriterNext(t *testing.T) {
	nbts := 1871
	bts := randomBts(nbts)
	var buf bytes.Buffer
	wr := NewWriterSize(&buf, 500)
	nwr := nextWriter{wr}

	nb, err := nwr.Write(bts)
	if err != nil {
		t.Fatal(err)
	}

	if nb != nbts {
		t.Fatalf("expected to write %d bytes; wrote %d", nbts, nb)
	}

	err = wr.Flush()
	if err != nil {
		t.Fatal(err)
	}

	if buf.Len() != nbts {
		t.Fatalf("expected buf.Len() to be %d; got %d", nbts, buf.Len())
	}

	if !bytes.Equal(buf.Bytes(), bts) {
		t.Fatal("buf.Bytes() and input are not equal")
	}
}

func TestReadFrom(t *testing.T) {
	nbts := 2139
	bts := randomBts(nbts)
	var buf bytes.Buffer
	wr := NewWriterSize(&buf, 987)

	rd := partialReader{bytes.NewReader(bts)}

	nb, err := wr.ReadFrom(rd)
	if err != nil {
		t.Fatal(err)
	}
	if nb != int64(nbts) {
		t.Fatalf("expeted to write %d bytes; wrote %d", nbts, nb)
	}
	err = wr.Flush()
	if err != nil {
		t.Fatal(err)
	}
	if buf.Len() != nbts {
		t.Fatalf("expected buf.Len() to be %d; got %d", nbts, buf.Len())
	}
	if !bytes.Equal(buf.Bytes(), bts) {
		t.Fatal("buf.Bytes() and input are not equal")
	}
}

func TestWriterBufCreation(t *testing.T) {
	tests := []struct {
		name   string
		buffer []byte
		size   int
	}{
		{name: "nil", buffer: nil, size: minWriterSize},
		{name: "empty", buffer: []byte{}, size: minWriterSize},
		{name: "allocated", buffer: make([]byte, 0, 200), size: 200},
		{name: "filled", buffer: make([]byte, 200), size: 200},
	}

	for _, test := range tests {
		var b bytes.Buffer
		w := NewWriterBuf(&b, test.buffer)

		if w.BufferSize() != test.size {
			t.Errorf("%s: unequal buffer size (got: %d, expected: %d)", test.name, w.BufferSize(), test.size)
		}
		if w.Buffered() != 0 {
			t.Errorf("%s: unequal buffered bytes (got: %d, expected: 0)", test.name, w.Buffered())
		}
	}
}
