// Copyright 2023 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package resultset

import (
	"context"

	"github.com/pingcap/tidb/pkg/util/chunk"
)

// RowIterator has similar method with `chunk.RowContainerReader`. The only difference is that
// it needs a `context.Context` for the `Next` and `Current` method.
type RowIterator interface {
	// Next returns the next Row.
	Next(context.Context) chunk.Row

	// Current returns the current Row.
	Current(context.Context) chunk.Row

	// End returns the invalid end Row.
	End() chunk.Row

	// Error returns none-nil error if anything wrong happens during the iteration.
	Error() error

	// Close closes the dumper
	Close()
}

// CursorResultSet extends the `ResultSet` to provide the ability to store an iterator
type CursorResultSet interface {
	ResultSet

	GetRowIterator() RowIterator
}

// WrapWithRowContainerCursor wraps a ResultSet into a CursorResultSet
func WrapWithRowContainerCursor(rs ResultSet, rowContainer chunk.RowContainerReader) CursorResultSet {
	return &tidbCursorResultSet{
		ResultSet: rs,
		reader: rowContainerReaderIter{
			RowContainerReader: rowContainer,
		},
	}
}

type tidbCursorResultSet struct {
	ResultSet

	reader rowContainerReaderIter
}

func (tcrs *tidbCursorResultSet) GetRowIterator() RowIterator {
	return &tcrs.reader
}

func (tcrs *tidbCursorResultSet) setCursorRUV2Tracker(tracker *CursorRUV2Tracker) {
	if rs, ok := tcrs.ResultSet.(cursorRUV2Trackable); ok {
		rs.setCursorRUV2Tracker(tracker)
	}
}

func (tcrs *tidbCursorResultSet) reportCursorRUV2Delta(resultChunkCellsDelta int64) {
	if rs, ok := tcrs.ResultSet.(cursorRUV2Trackable); ok {
		rs.reportCursorRUV2Delta(resultChunkCellsDelta)
	}
}

var _ RowIterator = &rowContainerReaderIter{}

type rowContainerReaderIter struct {
	chunk.RowContainerReader
}

func (iter *rowContainerReaderIter) Next(context.Context) chunk.Row {
	return iter.RowContainerReader.Next()
}

func (iter *rowContainerReaderIter) Current(context.Context) chunk.Row {
	return iter.RowContainerReader.Current()
}

// FetchNotifier represents notifier will be called in COM_FETCH.
type FetchNotifier interface {
	// OnFetchReturned be called when COM_FETCH returns.
	// it will be used in server-side cursor.
	OnFetchReturned()
}

var _ CursorResultSet = &tidbLazyCursorResultSet{}

type tidbLazyCursorResultSet struct {
	ResultSet
	iter lazyRowIterator
}

// WrapWithLazyCursor wraps a ResultSet into a CursorResultSet
func WrapWithLazyCursor(rs ResultSet, capacity, maxChunkSize int) CursorResultSet {
	chk := chunk.New(rs.FieldTypes(), capacity, maxChunkSize)

	return &tidbLazyCursorResultSet{
		ResultSet: rs,
		iter: lazyRowIterator{
			rs:  rs,
			chk: chk,
		},
	}
}

func (tcrs *tidbLazyCursorResultSet) GetRowIterator() RowIterator {
	return &tcrs.iter
}

func (tcrs *tidbLazyCursorResultSet) setCursorRUV2Tracker(tracker *CursorRUV2Tracker) {
	if rs, ok := tcrs.ResultSet.(cursorRUV2Trackable); ok {
		rs.setCursorRUV2Tracker(tracker)
	}
}

func (tcrs *tidbLazyCursorResultSet) reportCursorRUV2Delta(resultChunkCellsDelta int64) {
	if rs, ok := tcrs.ResultSet.(cursorRUV2Trackable); ok {
		rs.reportCursorRUV2Delta(resultChunkCellsDelta)
	}
}

type lazyRowIterator struct {
	rs       ResultSet
	err      error
	chk      *chunk.Chunk
	idxInChk int
	started  bool
}

func (iter *lazyRowIterator) Next(ctx context.Context) chunk.Row {
	if !iter.started {
		iter.started = true
	}

	iter.idxInChk++

	if iter.idxInChk >= iter.chk.NumRows() {
		// Reached the end, update the chunk
		err := iter.rs.Next(ctx, iter.chk)
		if err != nil {
			iter.err = err
			return chunk.Row{}
		}

		if iter.chk.NumRows() == 0 {
			// reached the end
			return chunk.Row{}
		}
		iter.idxInChk = 0
	}

	return iter.chk.GetRow(iter.idxInChk)
}

func (iter *lazyRowIterator) Current(ctx context.Context) chunk.Row {
	if !iter.started {
		return iter.Next(ctx)
	}

	if iter.chk.NumRows() == 0 {
		return chunk.Row{}
	}

	return iter.chk.GetRow(iter.idxInChk)
}

func (*lazyRowIterator) End() chunk.Row {
	return chunk.Row{}
}

func (iter *lazyRowIterator) Error() error {
	return iter.err
}

func (iter *lazyRowIterator) Close() {
	iter.rs.Close()
}
