// Copyright 2016 Google Inc. All Rights Reserved.
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT

package cbrotli_test

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"math/rand"
	"testing"
	"time"

	"github.com/google/brotli/go/cbrotli"
)

func checkCompressedData(compressedData, wantOriginalData []byte) error {
	uncompressed, err := cbrotli.Decode(compressedData)
	if err != nil {
		return fmt.Errorf("brotli decompress failed: %v", err)
	}
	if !bytes.Equal(uncompressed, wantOriginalData) {
		if len(wantOriginalData) != len(uncompressed) {
			return fmt.Errorf(""+
				"Data doesn't uncompress to the original value.\n"+
				"Length of original: %v\n"+
				"Length of uncompressed: %v",
				len(wantOriginalData), len(uncompressed))
		}
		for i := range wantOriginalData {
			if wantOriginalData[i] != uncompressed[i] {
				return fmt.Errorf(""+
					"Data doesn't uncompress to the original value.\n"+
					"Original at %v is %v\n"+
					"Uncompressed at %v is %v",
					i, wantOriginalData[i], i, uncompressed[i])
			}
		}
	}
	return nil
}

func TestEncoderNoWrite(t *testing.T) {
	out := bytes.Buffer{}
	e := cbrotli.NewWriter(&out, cbrotli.WriterOptions{Quality: 5})
	if err := e.Close(); err != nil {
		t.Errorf("Close()=%v, want nil", err)
	}
	// Check Write after close.
	if _, err := e.Write([]byte("hi")); err == nil {
		t.Errorf("No error after Close() + Write()")
	}
}

func TestEncoderEmptyWrite(t *testing.T) {
	out := bytes.Buffer{}
	e := cbrotli.NewWriter(&out, cbrotli.WriterOptions{Quality: 5})
	n, err := e.Write([]byte(""))
	if n != 0 || err != nil {
		t.Errorf("Write()=%v,%v, want 0, nil", n, err)
	}
	if err := e.Close(); err != nil {
		t.Errorf("Close()=%v, want nil", err)
	}
}

func TestWriter(t *testing.T) {
	// Test basic encoder usage.
	input := []byte("<html><body><H1>Hello world</H1></body></html>")
	out := bytes.Buffer{}
	e := cbrotli.NewWriter(&out, cbrotli.WriterOptions{Quality: 1})
	in := bytes.NewReader([]byte(input))
	n, err := io.Copy(e, in)
	if err != nil {
		t.Errorf("Copy Error: %v", err)
	}
	if int(n) != len(input) {
		t.Errorf("Copy() n=%v, want %v", n, len(input))
	}
	if err := e.Close(); err != nil {
		t.Errorf("Close Error after copied %d bytes: %v", n, err)
	}
	if err := checkCompressedData(out.Bytes(), input); err != nil {
		t.Error(err)
	}
}

func TestEncoderStreams(t *testing.T) {
	// Test that output is streamed.
	// Adjust window size to ensure the encoder outputs at least enough bytes
	// to fill the window.
	const lgWin = 16
	windowSize := int(math.Pow(2, lgWin))
	input := make([]byte, 8*windowSize)
	rand.Read(input)
	out := bytes.Buffer{}
	e := cbrotli.NewWriter(&out, cbrotli.WriterOptions{Quality: 11, LGWin: lgWin})
	halfInput := input[:len(input)/2]
	in := bytes.NewReader(halfInput)

	n, err := io.Copy(e, in)
	if err != nil {
		t.Errorf("Copy Error: %v", err)
	}

	// We've fed more data than the sliding window size. Check that some
	// compressed data has been output.
	if out.Len() == 0 {
		t.Errorf("Output length is 0 after %d bytes written", n)
	}
	if err := e.Close(); err != nil {
		t.Errorf("Close Error after copied %d bytes: %v", n, err)
	}
	if err := checkCompressedData(out.Bytes(), halfInput); err != nil {
		t.Error(err)
	}
}

func TestEncoderLargeInput(t *testing.T) {
	input := make([]byte, 1000000)
	rand.Read(input)
	out := bytes.Buffer{}
	e := cbrotli.NewWriter(&out, cbrotli.WriterOptions{Quality: 5})
	in := bytes.NewReader(input)

	n, err := io.Copy(e, in)
	if err != nil {
		t.Errorf("Copy Error: %v", err)
	}
	if int(n) != len(input) {
		t.Errorf("Copy() n=%v, want %v", n, len(input))
	}
	if err := e.Close(); err != nil {
		t.Errorf("Close Error after copied %d bytes: %v", n, err)
	}
	if err := checkCompressedData(out.Bytes(), input); err != nil {
		t.Error(err)
	}
}

func TestEncoderFlush(t *testing.T) {
	const payload = 32766 // Enough to force encoder emit 2 chunks
	input := make([]byte, payload)
	rand.Read(input)
	out := bytes.Buffer{}
	e := cbrotli.NewWriter(&out, cbrotli.WriterOptions{Quality: 5})
	in := bytes.NewReader(input)
	_, err := io.Copy(e, in)
	if err != nil {
		t.Fatalf("Copy Error: %v", err)
	}
	if err := e.Flush(); err != nil {
		t.Fatalf("Flush(): %v", err)
	}
	if out.Len() == 0 {
		t.Fatalf("0 bytes written after Flush()")
	}
	decompressed := make([]byte, payload)
	reader := cbrotli.NewReader(bytes.NewReader(out.Bytes()))
	n, err := reader.Read(decompressed)
	if n >= len(decompressed) || err != nil {
		t.Errorf("Expected {<%v, nil}, but got {%v, %v}", len(decompressed), n, err)
	}
	m, err := reader.Read(decompressed[n:])
	n += m
	if n != len(decompressed) || err != nil {
		t.Errorf("Expected {%v, nil}, but got {%v, %v}", len(decompressed), n, err)
	}
	reader.Close()
	if !bytes.Equal(decompressed, input) {
		t.Errorf(""+
			"Decompress after flush: %v\n"+
			"%q\n"+
			"want:\n%q",
			err, decompressed, input)
	}
	if err := e.Close(); err != nil {
		t.Errorf("Close(): %v", err)
	}
}

type readerWithTimeout struct {
	io.ReadCloser
}

func (r readerWithTimeout) Read(p []byte) (int, error) {
	type result struct {
		n   int
		err error
	}
	ch := make(chan result)
	go func() {
		n, err := r.ReadCloser.Read(p)
		ch <- result{n, err}
	}()
	select {
	case result := <-ch:
		return result.n, result.err
	case <-time.After(5 * time.Second):
		return 0, fmt.Errorf("read timed out")
	}
}

func TestDecoderStreaming(t *testing.T) {
	pr, pw := io.Pipe()
	writer := cbrotli.NewWriter(pw, cbrotli.WriterOptions{Quality: 5, LGWin: 20})
	reader := readerWithTimeout{cbrotli.NewReader(pr)}
	defer func() {
		if err := reader.Close(); err != nil {
			t.Errorf("reader.Close: %v", err)
		}
		go ioutil.ReadAll(pr) // swallow the "EOF" token from writer.Close
		if err := writer.Close(); err != nil {
			t.Errorf("writer.Close: %v", err)
		}
	}()

	ch := make(chan []byte)
	errch := make(chan error)
	go func() {
		for {
			segment, ok := <-ch
			if !ok {
				return
			}
			if n, err := writer.Write(segment); err != nil || n != len(segment) {
				errch <- fmt.Errorf("write=%v,%v, want %v,%v", n, err, len(segment), nil)
				return
			}
			if err := writer.Flush(); err != nil {
				errch <- fmt.Errorf("flush: %v", err)
				return
			}
		}
	}()
	defer close(ch)

	segments := [...][]byte{
		[]byte("first"),
		[]byte("second"),
		[]byte("third"),
	}
	for k, segment := range segments {
		t.Run(fmt.Sprintf("Segment%d", k), func(t *testing.T) {
			select {
			case ch <- segment:
			case err := <-errch:
				t.Fatalf("write: %v", err)
			case <-time.After(5 * time.Second):
				t.Fatalf("timed out")
			}
			wantLen := len(segment)
			got := make([]byte, wantLen)
			if n, err := reader.Read(got); err != nil || n != wantLen || !bytes.Equal(got, segment) {
				t.Fatalf("read[%d]=%q,%v,%v, want %q,%v,%v", k, got, n, err, segment, wantLen, nil)
			}
		})
	}
}

func TestReader(t *testing.T) {
	content := bytes.Repeat([]byte("hello world!"), 10000)
	encoded, _ := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
	r := cbrotli.NewReader(bytes.NewReader(encoded))
	var decodedOutput bytes.Buffer
	n, err := io.Copy(&decodedOutput, r)
	if err != nil {
		t.Fatalf("Copy(): n=%v, err=%v", n, err)
	}
	if err := r.Close(); err != nil {
		t.Errorf("Close(): %v", err)
	}
	if got := decodedOutput.Bytes(); !bytes.Equal(got, content) {
		t.Errorf(""+
			"Reader output:\n"+
			"%q\n"+
			"want:\n"+
			"<%d bytes>",
			got, len(content))
	}
	buf := make([]byte, 4)
	if _, err := r.Read(buf); err == nil {
		t.Errorf("Read-after-Close shoule have returned error")
	}
}

func TestDecode(t *testing.T) {
	content := bytes.Repeat([]byte("hello world!"), 10000)
	encoded, _ := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
	decoded, err := cbrotli.Decode(encoded)
	if err != nil {
		t.Errorf("Decode: %v", err)
	}
	if !bytes.Equal(decoded, content) {
		t.Errorf(""+
			"Decode content:\n"+
			"%q\n"+
			"want:\n"+
			"<%d bytes>",
			decoded, len(content))
	}
}

func TestDecodeFuzz(t *testing.T) {
	// Test that the decoder terminates with corrupted input.
	content := bytes.Repeat([]byte("hello world!"), 100)
	src := rand.NewSource(0)
	encoded, err := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
	if err != nil {
		t.Fatalf("Encode(<%d bytes>, _) = _, %s", len(content), err)
	}
	if len(encoded) == 0 {
		t.Fatalf("Encode(<%d bytes>, _) produced empty output", len(content))
	}
	for i := 0; i < 100; i++ {
		enc := append([]byte{}, encoded...)
		for j := 0; j < 5; j++ {
			enc[int(src.Int63())%len(enc)] = byte(src.Int63() % 256)
		}
		cbrotli.Decode(enc)
	}
}

func TestDecodeTrailingData(t *testing.T) {
	content := bytes.Repeat([]byte("hello world!"), 100)
	encoded, _ := cbrotli.Encode(content, cbrotli.WriterOptions{Quality: 5})
	_, err := cbrotli.Decode(append(encoded, 0))
	if err == nil {
		t.Errorf("Expected 'excessive input' error")
	}
}

func TestEncodeDecode(t *testing.T) {
	for _, test := range []struct {
		data    []byte
		repeats int
	}{
		{nil, 0},
		{[]byte("A"), 1},
		{[]byte("<html><body><H1>Hello world</H1></body></html>"), 10},
		{[]byte("<html><body><H1>Hello world</H1></body></html>"), 1000},
	} {
		t.Logf("case %q x %d", test.data, test.repeats)
		input := bytes.Repeat(test.data, test.repeats)
		encoded, err := cbrotli.Encode(input, cbrotli.WriterOptions{Quality: 5})
		if err != nil {
			t.Errorf("Encode: %v", err)
		}
		// Inputs are compressible, but may be too small to compress.
		if maxSize := len(input)/2 + 20; len(encoded) >= maxSize {
			t.Errorf(""+
				"Encode returned %d bytes, want <%d\n"+
				"Encoded=%q",
				len(encoded), maxSize, encoded)
		}
		decoded, err := cbrotli.Decode(encoded)
		if err != nil {
			t.Errorf("Decode: %v", err)
		}
		if !bytes.Equal(decoded, input) {
			var want string
			if len(input) > 320 {
				want = fmt.Sprintf("<%d bytes>", len(input))
			} else {
				want = fmt.Sprintf("%q", input)
			}
			t.Errorf(""+
				"Decode content:\n"+
				"%q\n"+
				"want:\n"+
				"%s",
				decoded, want)
		}
	}
}

func TestEncodeDecodeWithDictionary(t *testing.T) {
	q := 5
	l := 4096

	input := make([]byte, l)
	for i := 0; i < l; i++ {
		input[i] = byte(i*7 + i*i*5)
	}
	// use dictionary same as input
	pd := cbrotli.NewPreparedDictionary(input, cbrotli.DtRaw, q)
	defer pd.Close()

	encoded, err := cbrotli.Encode(input, cbrotli.WriterOptions{Quality: q, Dictionary: pd})
	if err != nil {
		t.Errorf("Encode: %v", err)
	}
	limit := 20
	if len(encoded) > limit {
		t.Errorf("Output length exceeds expectations: %d > %d", len(encoded), limit)
	}

	decoded, err := cbrotli.DecodeWithRawDictionary(encoded, input)
	if err != nil {
		t.Errorf("Decode: %v", err)
	}
	if !bytes.Equal(decoded, input) {
		var want string
		if len(input) > 320 {
			want = fmt.Sprintf("<%d bytes>", len(input))
		} else {
			want = fmt.Sprintf("%q", input)
		}
		t.Errorf(""+
			"Decode content:\n"+
			"%q\n"+
			"want:\n"+
			"%s",
			decoded, want)
	}
}
