package parquet

import (
	
	
	
	
	
)

// BufferPool is an interface abstracting the underlying implementation of
// page buffer pools.
//
// The parquet-go package provides two implementations of this interface, one
// backed by in-memory buffers (on the Go heap), and the other using temporary
// files on disk.
//
// Applications which need finer grain control over the allocation and retention
// of page buffers may choose to provide their own implementation and install it
// via the parquet.ColumnPageBuffers writer option.
//
// BufferPool implementations must be safe to use concurrently from multiple
// goroutines.
type BufferPool interface {
	// GetBuffer is called when a parquet writer needs to acquire a new
	// page buffer from the pool.
	GetBuffer() io.ReadWriteSeeker

	// PutBuffer is called when a parquet writer releases a page buffer to
	// the pool.
	//
	// The parquet.Writer type guarantees that the buffers it calls this method
	// with were previously acquired by a call to GetBuffer on the same
	// pool, and that it will not use them anymore after the call.
	PutBuffer(io.ReadWriteSeeker)
}

// NewBufferPool creates a new in-memory page buffer pool.
//
// The implementation is backed by sync.Pool and allocates memory buffers on the
// Go heap.
func () BufferPool { return new(memoryBufferPool) }

type memoryBuffer struct {
	data []byte
	off  int
}

func ( *memoryBuffer) () {
	.data, .off = .data[:0], 0
}

func ( *memoryBuffer) ( []byte) ( int,  error) {
	 = copy(, .data[.off:])
	.off += 
	if .off == len(.data) {
		 = io.EOF
	}
	return , 
}

func ( *memoryBuffer) ( []byte) (int, error) {
	 := copy(.data[.off:cap(.data)], )
	.data = .data[:.off+]

	if  < len() {
		.data = append(.data, [:]...)
	}

	.off += len()
	return len(), nil
}

func ( *memoryBuffer) ( io.Writer) (int64, error) {
	,  := .Write(.data[.off:])
	.off += 
	return int64(), 
}

func ( *memoryBuffer) ( int64,  int) (int64, error) {
	switch  {
	case io.SeekCurrent:
		 += int64(.off)
	case io.SeekEnd:
		 += int64(len(.data))
	}
	if  < 0 {
		return 0, fmt.Errorf("seek: negative offset: %d<0", )
	}
	if  > int64(len(.data)) {
		 = int64(len(.data))
	}
	.off = int()
	return , nil
}

type memoryBufferPool struct{ sync.Pool }

func ( *memoryBufferPool) () io.ReadWriteSeeker {
	,  := .Get().(*memoryBuffer)
	if  == nil {
		 = new(memoryBuffer)
	} else {
		.Reset()
	}
	return 
}

func ( *memoryBufferPool) ( io.ReadWriteSeeker) {
	if ,  := .(*memoryBuffer);  != nil {
		.Put()
	}
}

// NewChunkBufferPool creates a new in-memory page buffer pool.
//
// The implementation is backed by sync.Pool and allocates memory buffers on the
// Go heap in fixed-size chunks.
func ( int) BufferPool {
	return newChunkMemoryBufferPool()
}

func newChunkMemoryBufferPool( int) *chunkMemoryBufferPool {
	 := &chunkMemoryBufferPool{}
	.bytesPool.New = func() any {
		return make([]byte, )
	}
	return 
}

// chunkMemoryBuffer implements an io.ReadWriteSeeker by storing a slice of fixed-size
// buffers into which it copies data. (It uses a sync.Pool to reuse buffers across
// instances.)
type chunkMemoryBuffer struct {
	bytesPool *sync.Pool

	data [][]byte
	idx  int
	off  int
}

func ( *chunkMemoryBuffer) () {
	for  := range .data {
		.bytesPool.Put(.data[])
	}
	for  := range .data {
		.data[] = nil
	}
	.data, .idx, .off = .data[:0], 0, 0
}

func ( *chunkMemoryBuffer) ( []byte) ( int,  error) {
	if len() == 0 {
		return 0, nil
	}

	if .idx >= len(.data) {
		return 0, io.EOF
	}

	 := .data[.idx]

	if .idx == len(.data)-1 && .off == len() {
		return 0, io.EOF
	}

	 = copy(, [.off:])
	.off += 

	if .off == cap() {
		.idx++
		.off = 0
	}

	return , 
}

func ( *chunkMemoryBuffer) ( []byte) (int, error) {
	 := len()

	if  == 0 {
		return 0, nil
	}

	for len() > 0 {
		if .idx == len(.data) {
			.data = append(.data, .bytesPool.Get().([]byte)[:0])
		}
		 := .data[.idx]
		 := copy([.off:cap()], )
		.data[.idx] = [:.off+]
		.off += 
		 = [:]
		if .off >= cap() {
			.idx++
			.off = 0
		}
	}

	return , nil
}

func ( *chunkMemoryBuffer) ( io.Writer) (int64, error) {
	var  int64
	var  error
	for  == nil {
		 := .data[.idx]
		,  := .Write([.off:])
		 += int64()
		 = 
		if .idx == len(.data)-1 {
			.off = int()
			break
		}
		.idx++
		.off = 0
	}
	return , 
}

func ( *chunkMemoryBuffer) ( int64,  int) (int64, error) {
	// Because this is the common case, we check it first to avoid computing endOff.
	if  == 0 &&  == io.SeekStart {
		.idx = 0
		.off = 0
		return , nil
	}
	 := .endOff()
	switch  {
	case io.SeekCurrent:
		 += .currentOff()
	case io.SeekEnd:
		 += 
	}
	if  < 0 {
		return 0, fmt.Errorf("seek: negative offset: %d<0", )
	}
	if  >  {
		 = 
	}
	// Repeat this case now that we know the absolute offset. This is a bit faster, but
	// mainly protects us from an out-of-bounds if c.data is empty. (If the buffer is
	// empty and the absolute offset isn't zero, we'd have errored (if negative) or
	// clamped to zero (if positive) above.
	if  == 0 {
		.idx = 0
		.off = 0
	} else {
		 := cap(.data[0])
		.idx = int() / 
		.off = int() % 
	}
	return , nil
}

func ( *chunkMemoryBuffer) () int64 {
	if .idx == 0 {
		return int64(.off)
	}
	return int64((.idx-1)*cap(.data[0]) + .off)
}

func ( *chunkMemoryBuffer) () int64 {
	if len(.data) == 0 {
		return 0
	}
	 := len(.data)
	 := .data[-1]
	return int64(cap()*(-1) + len())
}

type chunkMemoryBufferPool struct {
	sync.Pool
	bytesPool sync.Pool
}

func ( *chunkMemoryBufferPool) () io.ReadWriteSeeker {
	,  := .Get().(*chunkMemoryBuffer)
	if  == nil {
		 = &chunkMemoryBuffer{bytesPool: &.bytesPool}
	} else {
		.Reset()
	}
	return 
}

func ( *chunkMemoryBufferPool) ( io.ReadWriteSeeker) {
	if ,  := .(*chunkMemoryBuffer);  != nil {
		for ,  := range .data {
			.bytesPool.Put()
		}
		for  := range .data {
			.data[] = nil
		}
		.data = .data[:0]
		.Put()
	}
}

type fileBufferPool struct {
	err     error
	tempdir string
	pattern string
}

// NewFileBufferPool creates a new on-disk page buffer pool.
func (,  string) BufferPool {
	 := &fileBufferPool{
		tempdir: ,
		pattern: ,
	}
	.tempdir, .err = filepath.Abs(.tempdir)
	return 
}

func ( *fileBufferPool) () io.ReadWriteSeeker {
	if .err != nil {
		return &errorBuffer{err: .err}
	}
	,  := os.CreateTemp(.tempdir, .pattern)
	if  != nil {
		return &errorBuffer{err: }
	}
	return 
}

func ( *fileBufferPool) ( io.ReadWriteSeeker) {
	if ,  := .(*os.File);  != nil {
		defer .Close()
		os.Remove(.Name())
	}
}

type errorBuffer struct{ err error }

func ( *errorBuffer) ([]byte) (int, error)          { return 0, .err }
func ( *errorBuffer) ([]byte) (int, error)         { return 0, .err }
func ( *errorBuffer) (io.Reader) (int64, error) { return 0, .err }
func ( *errorBuffer) (io.Writer) (int64, error)  { return 0, .err }
func ( *errorBuffer) (int64, int) (int64, error)    { return 0, .err }

var (
	defaultColumnBufferPool  = *newChunkMemoryBufferPool(256 * 1024)
	defaultSortingBufferPool memoryBufferPool

	_ io.ReaderFrom      = (*errorBuffer)(nil)
	_ io.WriterTo        = (*errorBuffer)(nil)
	_ io.ReadWriteSeeker = (*memoryBuffer)(nil)
	_ io.WriterTo        = (*memoryBuffer)(nil)
	_ io.ReadWriteSeeker = (*chunkMemoryBuffer)(nil)
	_ io.WriterTo        = (*chunkMemoryBuffer)(nil)
)

type readerAt struct {
	reader io.ReadSeeker
	offset int64
}

func ( *readerAt) ( []byte,  int64) (int, error) {
	if .offset < 0 ||  != .offset {
		,  := .reader.Seek(, io.SeekStart)
		if  != nil {
			return 0, 
		}
		.offset = 
	}
	,  := .reader.Read()
	.offset += int64()
	return , 
}

func newReaderAt( io.ReadSeeker) io.ReaderAt {
	if ,  := .(io.ReaderAt);  {
		return 
	}
	return &readerAt{reader: , offset: -1}
}