package twirpmetric

import (
	"errors"
	"io"
	"sync"
)

var errIncompleteRead = errors.New("incomplete read")

// readMonitor wraps a ReadCloser and calls onReadComplete when the read is complete.
// It can optionally collect the bytes read and include it.
type readMonitor struct {
	readCloser       io.ReadCloser
	captureBytesRead bool
	onReadComplete   func([]byte, error)

	mutex     sync.Mutex
	buffer    []byte
	lastError error
}

func (monitor *readMonitor) Read(p []byte) (int, error) {
	var n int
	var err = io.EOF

	if monitor.readCloser != nil {
		n, err = monitor.readCloser.Read(p)
	}

	monitor.mutex.Lock()
	defer monitor.mutex.Unlock()

	if monitor.captureBytesRead {
		monitor.buffer = append(monitor.buffer, p[:n]...)
	}
	monitor.lastError = err
	if err == io.EOF {
		monitor.callOnReadComplete()
	}

	return n, err
}

func (monitor *readMonitor) Close() error {
	var err error
	if monitor.readCloser != nil {
		err = monitor.readCloser.Close()
	}

	monitor.mutex.Lock()
	defer monitor.mutex.Unlock()

	monitor.callOnReadComplete()

	return err
}

// Calls onReadComplete at most once.
func (monitor *readMonitor) callOnReadComplete() {
	if monitor.onReadComplete != nil {
		completionError := monitor.lastError
		if completionError == io.EOF {
			// This is the normal case, the read was completed.
			completionError = nil
		} else if completionError == nil {
			// The read was not completed.
			completionError = errIncompleteRead
		}
		monitor.onReadComplete(monitor.buffer, completionError)
		monitor.onReadComplete = nil
	}
}
