package core

import (
	"errors"
	"io"
)

// ProbeSize
// in my tests MPEG-TS 40Mbit/s 4K-video require more than 1MB for probe
const ProbeSize = 5 * 1024 * 1024 // 5MB

const (
	BufferDisable       = 0
	BufferDrainAndClear = -1
)

// ReadBuffer support buffering and Seek over buffer
// positive BufferSize will enable buffering mode
// Seek to negative offset will clear buffer
// Seek with a positive BufferSize will continue buffering after the last read from the buffer
// Seek with a negative BufferSize will clear buffer after the last read from the buffer
// Read more than BufferSize will raise error
type ReadBuffer struct {
	io.Reader

	BufferSize int

	buf []byte
	pos int
}

func NewReadBuffer(rd io.Reader) *ReadBuffer {
	if rs, ok := rd.(*ReadBuffer); ok {
		return rs
	}
	return &ReadBuffer{Reader: rd}
}

func (r *ReadBuffer) Read(p []byte) (n int, err error) {
	// with zero buffer - read as usual
	if r.BufferSize == BufferDisable {
		return r.Reader.Read(p)
	}

	// if buffer not empty - read from it
	if r.pos < len(r.buf) {
		n = copy(p, r.buf[r.pos:])
		r.pos += n
		return
	}

	// with negative buffer - empty it and read as usual
	if r.BufferSize < 0 {
		r.BufferSize = BufferDisable
		r.buf = nil
		r.pos = 0

		return r.Reader.Read(p)
	}

	n, err = r.Reader.Read(p)
	if len(r.buf)+n > r.BufferSize {
		return 0, errors.New("probe reader overflow")
	}
	r.buf = append(r.buf, p[:n]...)
	r.pos += n
	return
}

func (r *ReadBuffer) Close() error {
	if closer, ok := r.Reader.(io.Closer); ok {
		return closer.Close()
	}
	return nil
}

func (r *ReadBuffer) Seek(offset int64, whence int) (int64, error) {
	var pos int
	switch whence {
	case io.SeekStart:
		pos = int(offset)
	case io.SeekCurrent:
		pos = r.pos + int(offset)
	case io.SeekEnd:
		pos = len(r.buf) + int(offset)
	}

	// negative offset - empty buffer
	if pos < 0 {
		r.buf = nil
		r.pos = 0
	} else if pos >= len(r.buf) {
		r.pos = len(r.buf)
	} else {
		r.pos = pos
	}

	return int64(r.pos), nil
}

func (r *ReadBuffer) Peek(n int) ([]byte, error) {
	r.BufferSize = n
	b := make([]byte, n)
	if _, err := io.ReadAtLeast(r, b, n); err != nil {
		return nil, err
	}
	r.Reset()
	return b, nil
}

func (r *ReadBuffer) Reset() {
	r.BufferSize = BufferDrainAndClear
	r.pos = 0
}
