package iotools

import "io"

// AtEOF returns true when reader at EOF,
// this is checked with a 0 length read.
func AtEOF(r io.Reader) bool {
	_, err := r.Read(nil)
	return (err == io.EOF)
}

// GetReadCloserLimit attempts to cast io.Reader to access its io.LimitedReader with limit.
func GetReaderLimit(r io.Reader) (*io.LimitedReader, int64) {
	lr, ok := r.(*io.LimitedReader)
	if !ok {
		return nil, -1
	}
	return lr, lr.N
}

// UpdateReaderLimit attempts to  update the limit of a reader for existing, newly wrapping if necessary.
func UpdateReaderLimit(r io.Reader, limit int64) (*io.LimitedReader, int64) {
	lr, ok := r.(*io.LimitedReader)
	if !ok {
		lr = &io.LimitedReader{r, limit}
		return lr, limit
	}

	if limit < lr.N {
		// Update existing.
		lr.N = limit
	}

	return lr, lr.N
}

// GetReadCloserLimit attempts to unwrap io.ReadCloser to access its io.LimitedReader with limit.
func GetReadCloserLimit(rc io.ReadCloser) (*io.LimitedReader, int64) {
	rct, ok := rc.(*ReadCloserType)
	if !ok {
		return nil, -1
	}
	lr, ok := rct.Reader.(*io.LimitedReader)
	if !ok {
		return nil, -1
	}
	return lr, lr.N
}

// UpdateReadCloserLimit attempts to update the limit of a readcloser for existing, newly wrapping if necessary.
func UpdateReadCloserLimit(rc io.ReadCloser, limit int64) (io.ReadCloser, *io.LimitedReader, int64) {

	// Check for our wrapped ReadCloserType.
	if rct, ok := rc.(*ReadCloserType); ok {

		// Attempt to update existing wrapped limit reader.
		if lr, ok := rct.Reader.(*io.LimitedReader); ok {

			if limit < lr.N {
				// Update existing.
				lr.N = limit
			}

			return rct, lr, lr.N
		}

		// Wrap the reader type with new limit.
		lr := &io.LimitedReader{rct.Reader, limit}
		rct.Reader = lr

		return rct, lr, lr.N
	}

	// Wrap separated types.
	rct := &ReadCloserType{
		Reader: rc,
		Closer: rc,
	}

	// Wrap separated reader part with limit.
	lr := &io.LimitedReader{rct.Reader, limit}
	rct.Reader = lr

	return rct, lr, lr.N
}