package internal

import (
	"bytes"
	"errors"
	"io"
	"math"
	"unsafe"
)

var errRingBufferFull = errors.New("lneto/ring: buffer full")

// Ring implements basic Ring buffer functionality.
type Ring struct {
	// Buf is used to store data written into Ring
	// with Write methods and then read out with Read methods.
	// The capacity of Buf is unused.
	// There is no readable data when End==0.
	Buf []byte
	// Start of readable data which indexes into Buf.
	// If Off==End and End!=0 the buffer is full and data begins at Off.
	Off int
	// End of readable data which indexes into Buf, not including byte at End index.
	// If End==0 then the buffer is empty. If End==Off and End!=0 the buffer is full.
	End int
}

// SizeLimited returns the amount of bytes that can be written up to the
// argument offset limitOffset. See [Ring.WriteLimited]
func (r *Ring) FreeLimited(limitOffset int) (free int) {
	end := r.End
	if r.End == 0 {
		end = r.Off
	}
	if limitOffset > end {
		free = limitOffset - end
	} else {
		free = len(r.Buf) - end + limitOffset
	}
	return free
}

// WriteLimited performs a write that does not write over the ring buffer's
// limitOffset index, which points to a position to r.Buf. Up to [Ring.FreeLimited] bytes can be written.
func (r *Ring) WriteLimited(b []byte, limitOffset int) (int, error) {
	if limitOffset > len(r.Buf) {
		panic("bad limit offset")
	}
	if len(b) > len(r.Buf) {
		return 0, io.ErrShortBuffer
	}
	limit := r.FreeLimited(limitOffset)
	if len(b) > limit {
		return 0, errRingBufferFull
	}
	return r.Write(b)
}

// WriteString is a wrapper around [Ring.Write] that avoids allocation of converting byte slice to string.
func (r *Ring) WriteString(s string) (int, error) {
	return r.Write(unsafe.Slice(unsafe.StringData(s), len(s)))
}

// Write appends data to the ring buffer that can then be read back in order with [Ring.Read] methods.
// An error is returned if length of data too large for buffer. Write is guaranteed to start at buffer index [Ring.Off].
func (r *Ring) Write(b []byte) (int, error) {
	free := r.Free()
	if len(b) > free {
		return 0, errRingBufferFull
	}
	midFree := r.midFree()
	if midFree > 0 {
		// start     end       off    len(buf)
		//   |  used  |  mfree  |  used  |
		n := copy(r.Buf[r.End:r.Off], b)
		r.End += n
		return n, nil
	} else if r.End == 0 {
		// To ensure Write begins on r.Off.
		// Specialised for when user controls Off manually instead of by internal calls to [Ring.onReadEnd] or calls to [Ring.Reset].
		r.End = r.Off
	}
	// start       off       end      len(buf)
	//   |  sfree   |  used   |  efree   |
	n := copy(r.Buf[r.End:], b)
	r.End += n
	if n < len(b) {
		n2 := copy(r.Buf, b[n:])
		r.End = n2
		n += n2
	}
	return n, nil
}

// ReadDiscard is a performance auxiliary method that performs a dummy read or no-op read
// for advancing the read pointer n bytes without actually copying data.
// This method panics if amount of bytes is more than buffered (see [Ring.Buffered]).
func (r *Ring) ReadDiscard(n int) error {
	if n <= 0 {
		return errors.New("invalid discard amount")
	}
	buffered := r.Buffered()
	switch {
	case n > buffered:
		return errors.New("discard exceeds length")
	case n == buffered:
		r.Reset()
	case n+r.Off > len(r.Buf):
		r.Off = n - (len(r.Buf) - r.Off)
	default:
		r.Off += n
	}
	return nil
}

// ReadAt reads data at an offset from start of readable data but does not advance read pointer. [io.EOF] returned when no data available.
func (r *Ring) ReadAt(p []byte, off64 int64) (int, error) {
	if math.MaxInt != math.MaxInt64 && off64+int64(len(p)) > math.MaxInt32 {
		return 0, errors.New("offset too large (32 bit overflow)") // Check only compiles for 32-bit platforms.
	}
	off := int(off64)
	if off+len(p) > r.Buffered() {
		return 0, io.ErrUnexpectedEOF
	}
	r2 := *r
	r2.Off = r.addOff(r2.Off, off)
	return r2.ReadPeek(p)
}

// ReadPeek reads up to len(b) bytes from the ring buffer but does not advance the read pointer. [io.EOF] returned when no data available.
func (r *Ring) ReadPeek(b []byte) (int, error) {
	n, err := r.read(b)
	return n, err
}

// Read reads up to len(b) bytes from the ring buffer and advances the read pointer. [io.EOF] returned when no data available.
func (r *Ring) Read(b []byte) (int, error) {
	n, err := r.read(b)
	if err != nil {
		return n, err
	}
	r.onReadEnd(n)
	return n, nil
}

func (r *Ring) read(b []byte) (n int, err error) {
	if r.Buffered() == 0 {
		return 0, io.EOF
	}
	if r.End > r.Off {
		// start       off       end      len(buf)
		//   |  sfree   |  used   |  efree   |
		n = copy(b, r.Buf[r.Off:r.End])
		return n, nil
	}
	// start     end       off     len(buf)
	//   |  used  |  mfree  |  used  |
	n = copy(b, r.Buf[r.Off:])
	if n < len(b) {
		n2 := copy(b[n:], r.Buf[:r.End])
		n += n2
	}
	return n, nil
}

// Reset flushes all data from ring buffer so that no data can be further read.
func (r *Ring) Reset() {
	r.Off = 0
	r.End = 0
}

// Size returns the capacity of the ring buffer.
func (r *Ring) Size() int {
	return len(r.Buf)
}

// Buffered returns amount of bytes ready to read from ring buffer. Always less than [ring.Size].
func (r *Ring) Buffered() int {
	return r.Size() - r.Free()
}

// Free returns amount of bytes that can be read into ring buffer before reaching maximum capacity given by [ring.Size]. Always less than [ring.Size].
func (r *Ring) Free() int {
	if r.End == 0 || r.Off == 0 {
		return len(r.Buf) - r.End
	}
	if r.Off < r.End {
		// start       off       end      len(buf)
		//   |  sfree   |  used   |  efree   |
		startFree := r.Off
		endFree := len(r.Buf) - r.End
		return startFree + endFree
	}
	// start     end       off     len(buf)
	//   |  used  |  mfree  |  used  |
	return r.Off - r.End
}

func (r *Ring) midFree() int {
	if r.End >= r.Off || r.End == 0 {
		return 0
	}
	return r.Off - r.End
}

// onReadEnd does some cleanup of [ring.off] and [ring.end] fields if possible for contiguous read performance benefits.
func (r *Ring) onReadEnd(totalRead int) {
	if totalRead <= 0 {
		panic("invalid onReadEnd bytes read")
	}
	newOff := r.addOff(r.Off, totalRead)
	if newOff == r.End {
		r.Reset()
	} else if newOff == len(r.Buf) {
		r.Off = 0 // Optimization case.
	} else {
		r.Off = newOff
	}
}

// addOff sums a and b to return an index within 1..[Ring.Size] supposing a and b are each less-equal than [Ring.Size].
// Result will never be 0 unless both a and b are 0.
func (r *Ring) addOff(a, b int) int {
	result := a + b
	if result > len(r.Buf) {
		result -= len(r.Buf)
	}
	return result
}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func (r *Ring) string() string {
	var b bytes.Buffer
	r2 := *r
	b.ReadFrom(&r2)
	return b.String()
}

func (r *Ring) _string(off int64) string {
	s := r.string()
	return s[off:]
}
