package wav

import (
	"bytes"
	"io"
	"strings"
	"testing"

	"github.com/cheekybits/is"
)

var (
	riff             = []byte{0x52, 0x49, 0x46, 0x46} // "RIFF"
	chunkSize24      = []byte{0x24, 0x00, 0x00, 0x00} // chunkSize
	wave             = []byte{0x57, 0x41, 0x56, 0x45} // "WAVE"
	fmt20            = []byte{0x66, 0x6d, 0x74, 0x20} // "fmt "
	testRiffChunkFmt = []byte{
		0x10, 0x00, 0x00, 0x00, // LengthOfHeader
		0x01, 0x00, // AudioFormat
		0x01, 0x00, // NumOfChannels
		0x44, 0xac, 0x00, 0x00, // SampleRate
		0x88, 0x58, 0x01, 0x00, // BytesPerSec
		0x02, 0x00, // BytesPerBloc
		0x10, 0x00, // BitsPerSample
		0x64, 0x61, 0x74, 0x61, // "data"
	}

	wavWithOneSample []byte
)

func init() {
	var b bytes.Buffer
	b.Write(riff)
	b.Write([]byte{0x26, 0x00, 0x00, 0x00}) // chunkSize
	b.Write(wave)
	b.Write(fmt20)
	b.Write(testRiffChunkFmt)
	b.Write([]byte{0x02, 0x00, 0x00, 0x00}) // 2 bytes of samples - this part of the header is why wav length is capped at 32bit
	b.Write([]byte{0x01, 0x01})
	wavWithOneSample = b.Bytes()
}

func TestNewReader_inputTooLarge(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	_, err := NewReader(
		bytes.NewReader([]byte{}),
		99999999999999999)
	is.Equal(err, ErrInputToLarge)
}

func TestParseHeaders_complete(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	// Parsing the header of an wav with 0 samples
	var b bytes.Buffer
	b.Write(riff)
	b.Write(chunkSize24)
	b.Write(wave)
	b.Write(fmt20)
	b.Write(testRiffChunkFmt)
	b.Write([]byte{0x00, 0x00, 0x00, 0x00})
	wavFile := bytes.NewReader(b.Bytes())
	wavReader, err := NewReader(wavFile, int64(b.Len()))
	is.NoErr(err)
	is.Equal(uint32(0), wavReader.GetSampleCount())
	is.Equal(File{
		SampleRate:      44100,
		Channels:        1,
		SignificantBits: 16,
		AudioFormat:     1,
		Canonical:       true,
		BytesPerSecond:  88200,
	}, wavReader.GetFile())
}

func TestParseHeaders_tooShort(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	wavData := append(riff, 0x08, 0x00)
	wavFile := bytes.NewReader(wavData)
	_, err := NewReader(wavFile, int64(len(wavData)))
	is.Err(err)
	is.Equal(err, io.ErrUnexpectedEOF)
}

func TestParseHeaders_chunkFmtMissing(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	var b bytes.Buffer
	b.Write(riff)
	b.Write([]byte{0x04, 0x00, 0x00, 0x00}) // chunkSize
	b.Write(wave)
	wavFile := bytes.NewReader(b.Bytes())
	_, err := NewReader(wavFile, int64(b.Len()))
	is.Err(err)
	is.Equal(err, io.ErrUnexpectedEOF)
}

func TestParseHeaders_chunkFmtTooShort(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	var b bytes.Buffer
	b.Write(riff)
	b.Write([]byte{0x08, 0x00, 0x00, 0x00}) // chunkSize
	b.Write(wave)
	b.Write(fmt20)
	wavFile := bytes.NewReader(b.Bytes())
	_, err := NewReader(wavFile, int64(b.Len()))
	is.Err(err)
	is.Equal(err, io.ErrUnexpectedEOF)
}

func TestParseHeaders_chunkFmtTooShort2(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	var b bytes.Buffer
	b.Write(riff)
	b.Write([]byte{0x0a, 0x00, 0x00, 0x00}) // chunkSize
	b.Write(wave)
	b.Write(fmt20)
	b.Write([]byte{0, 0})
	wavFile := bytes.NewReader(b.Bytes())
	_, err := NewReader(wavFile, int64(b.Len()))
	is.Err(err)
	is.Equal(err, io.ErrUnexpectedEOF)
}

func TestParseHeaders_corruptRiff(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	var b bytes.Buffer
	b.Write([]byte{0x52, 0, 0x46, 0x46}) // "R\0FF"
	b.Write(chunkSize24)
	b.Write(wave)
	wavFile := bytes.NewReader(b.Bytes())
	_, err := NewReader(wavFile, int64(b.Len()))
	is.Err(err)
	is.Equal(ErrNotRiff, err)
}

func TestParseHeaders_chunkSizeNull(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	var b bytes.Buffer
	b.Write(riff)
	b.Write([]byte{0x00, 0x00, 0x00, 0x00}) // chunkSize
	b.Write(wave)
	b.Write(fmt20)
	wavFile := bytes.NewReader(b.Bytes())
	_, err := NewReader(wavFile, int64(b.Len()))
	is.Err(err)
	is.Equal(ErrIncorrectChunkSize{8, 16}, err)
	is.Equal("Incorrect ChunkSize. Got[8] Wanted[16]", err.Error())
}

func TestParseHeaders_notWave(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	var b bytes.Buffer
	b.Write(riff)
	b.Write([]byte{0x09, 0x00, 0x00, 0x00}) // chunkSize
	b.Write([]byte{0x57, 0x42, 0x56, 0x45}) // "WBVE"
	b.Write(fmt20)
	b.Write([]byte{0})
	wavFile := bytes.NewReader(b.Bytes())
	_, err := NewReader(wavFile, int64(b.Len()))
	is.Err(err)
	is.Equal(ErrNotWave, err)
}

func TestParseHeaders_fmtNotSupported(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	var b bytes.Buffer
	b.Write(riff)
	b.Write(chunkSize24)
	b.Write(wave)
	b.Write(fmt20)
	b.Write(testRiffChunkFmt)
	b.Write([]byte{0x00, 0x00, 0x00, 0x00})
	buf := b.Bytes()
	buf[21] = 2 // change byte 5 of riffChunk
	wavFile := bytes.NewReader(buf)
	_, err := NewReader(wavFile, int64(b.Len()))
	is.Err(err)
	is.Equal(ErrFormatNotSupported, err)
}

func TestReadSample_Raw(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	wavFile := bytes.NewReader(wavWithOneSample)
	wavReader, err := NewReader(wavFile, int64(len(wavWithOneSample)))
	is.NoErr(err)
	is.Equal(uint32(1), wavReader.GetSampleCount())
	rawSample, err := wavReader.ReadRawSample()
	is.NoErr(err)
	is.Equal([]byte{1, 1}, rawSample)
}

func TestReadSample(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	wavFile := bytes.NewReader(wavWithOneSample)
	wavReader, err := NewReader(wavFile, int64(len(wavWithOneSample)))
	is.NoErr(err)
	is.Equal(uint32(1), wavReader.GetSampleCount())
	sample, err := wavReader.ReadSample()
	is.NoErr(err)
	is.Equal(257, sample)
}

// panic: runtime error: invalid memory address or nil pointer dereference
// [signal 0xb code=0x1 addr=0x4 pc=0x4399fb]
//
// goroutine 1 [running]:
// github.com/cryptix/wav.(*Reader).parseHeaders(0xc208033720, 0x0, 0x0)
// 	/tmp/go-fuzz-build857960013/src/github.com/cryptix/wav/reader.go:191 +0xe3b
// github.com/cryptix/wav.NewReader(0x7f23a9550bd8, 0xc208037c80, 0x2d, 0xc208033720, 0x0, 0x0)
// 	/tmp/go-fuzz-build857960013/src/github.com/cryptix/wav/reader.go:64 +0x177
// github.com/cryptix/wav.Fuzz(0x7f23a92cf000, 0x2d, 0x100000, 0x2)
// 	/tmp/go-fuzz-build857960013/src/github.com/cryptix/wav/fuzz.go:12 +0x167
// github.com/dvyukov/go-fuzz/go-fuzz-dep.Main(0x570c60, 0x5d4200, 0x5f6, 0x5f6)
// 	/home/cryptix/go/src/github.com/dvyukov/go-fuzz/go-fuzz-dep/main.go:64 +0x309
// main.main()
// 	/tmp/go-fuzz-build857960013/src/go-fuzz-main/main.go:10 +0x4e
// exit status 2
func TestReadFuzzed_panic1(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	wavFile := strings.NewReader("RIFF%\x00\x00\x00WAVE0000\x10\x00\x00\x000000000000000000data00000")
	_, err := NewReader(wavFile, int64(wavFile.Len()))
	is.Err(err)
	is.Equal(ErrBrokenChunkFmt, err)
}

// panic: runtime error: integer divide by zero
// [signal 0x8 code=0x1 addr=0x439ae9 pc=0x439ae9]
//
// goroutine 1 [running]:
// github.com/cryptix/wav.(*Reader).parseHeaders(0xc208032cd0, 0x0, 0x0)
// 	/tmp/go-fuzz-build857960013/src/github.com/cryptix/wav/reader.go:200 +0xf29
// github.com/cryptix/wav.NewReader(0x7fbca32b6bd8, 0xc208037ef0, 0x2d, 0xc208032cd0, 0x0, 0x0)
// 	/tmp/go-fuzz-build857960013/src/github.com/cryptix/wav/reader.go:64 +0x177
// github.com/cryptix/wav.Fuzz(0x7fbca3035000, 0x2d, 0x100000, 0x2)
// 	/tmp/go-fuzz-build857960013/src/github.com/cryptix/wav/fuzz.go:12 +0x167
// github.com/dvyukov/go-fuzz/go-fuzz-dep.Main(0x570c60, 0x5d4200, 0x5f6, 0x5f6)
// 	/home/cryptix/go/src/github.com/dvyukov/go-fuzz/go-fuzz-dep/main.go:64 +0x309
// main.main()
// 	/tmp/go-fuzz-build857960013/src/go-fuzz-main/main.go:10 +0x4e
// exit status 2
func TestReadFuzzed_panic2(t *testing.T) {
	t.Parallel()
	is := is.New(t)
	wavFile := strings.NewReader("RIFF%\x00\x00\x00WAVEfmt \x10\x00\x00\x00\x01\x00000000000000\a\x00data00000")
	_, err := NewReader(wavFile, int64(wavFile.Len()))
	is.Err(err)
	is.Equal(ErrBrokenChunkFmt, err)
}
