// Copyright 2019 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package test

import (
	"io"
	"math"

	"github.com/google/uuid"
)

// TestError is an error that stores a uniquely generated id
type TestError struct {
	ErrId uuid.UUID
}

// NewTestError creates a TestError with a newly created id
func NewTestError() *TestError {
	id, _ := uuid.NewRandom()
	return &TestError{id}
}

// Error is the only method defined in the error interface
func (te *TestError) Error() string {
	return "Error! Error! Error!"
}

// TestReader is an io.Reader that will error after some number of bytes have been read
type TestReader struct {
	data       []byte
	errorAfter int
	totalRead  int
}

// NewTestReader takes a size, and the number of bytes that should be read before the
// reader errors.  The size is used to generate a buffer of random data that will be
// read by the caller.  After the caller has read the specified number of bytes the
// call to read will fail.
func NewTestReader(dataSize, errorAfter int) *TestReader {
	data := make([]byte, dataSize)
	for i := 0; i < dataSize; i++ {
		data[i] = byte(i % 256)
	}

	return NewTestReaderWithData(data, errorAfter)
}

// NewTestReaderWithData creates a TestReader with user supplied data and the number
// of bytes that can be read before errors start occurring on Read calls.
func NewTestReaderWithData(data []byte, errorAfter int) *TestReader {
	return &TestReader{data, errorAfter, 0}
}

// Read reads data from the internal buffer
func (tr *TestReader) Read(p []byte) (int, error) {
	dataRemaining := len(tr.data) - tr.totalRead

	if dataRemaining == 0 {
		return 0, io.EOF
	}

	remainingBeforeErr := math.MaxInt32
	if tr.errorAfter != -1 {
		remainingBeforeErr = tr.errorAfter - tr.totalRead
	}

	toRead := dataRemaining
	if dataRemaining > remainingBeforeErr {
		toRead = remainingBeforeErr
	}

	if toRead > len(p) {
		toRead = len(p)
	}

	var copied int
	if toRead > 0 {
		copied = copy(p[:toRead], tr.data[tr.totalRead:tr.totalRead+toRead])
		tr.totalRead += copied
	}

	var err error
	if tr.totalRead == tr.errorAfter && copied != len(p) {
		err = NewTestError()
	}

	return copied, err
}
