//go:build kvdb_postgres || (kvdb_sqlite && !(windows && (arm || 386)) && !(linux && (ppc64 || mips || mipsle || mips64)))

package sqlbase

import (
	"database/sql"

	"github.com/btcsuite/btcwallet/walletdb"
)

// readWriteCursor holds a reference to the cursors bucket, the value
// prefix and the current key used while iterating.
type readWriteCursor struct {
	bucket *readWriteBucket

	// currKey holds the current key of the cursor.
	currKey []byte
}

func newReadWriteCursor(b *readWriteBucket) *readWriteCursor {
	return &readWriteCursor{
		bucket: b,
	}
}

// First positions the cursor at the first key/value pair and returns
// the pair.
func (c *readWriteCursor) First() ([]byte, []byte) {
	var (
		key   []byte
		value []byte
	)
	row, cancel := c.bucket.tx.QueryRow(
		"SELECT key, value FROM " + c.bucket.table + " WHERE " +
			parentSelector(c.bucket.id) +
			" ORDER BY key LIMIT 1",
	)
	defer cancel()
	err := row.Scan(&key, &value)

	switch {
	case err == sql.ErrNoRows:
		return nil, nil

	case err != nil:
		panic(err)
	}

	// Copy current key to prevent modification by the caller.
	c.currKey = make([]byte, len(key))
	copy(c.currKey, key)

	return key, value
}

// Last positions the cursor at the last key/value pair and returns the
// pair.
func (c *readWriteCursor) Last() ([]byte, []byte) {
	var (
		key   []byte
		value []byte
	)
	row, cancel := c.bucket.tx.QueryRow(
		"SELECT key, value FROM " + c.bucket.table + " WHERE " +
			parentSelector(c.bucket.id) +
			" ORDER BY key DESC LIMIT 1",
	)
	defer cancel()
	err := row.Scan(&key, &value)

	switch {
	case err == sql.ErrNoRows:
		return nil, nil

	case err != nil:
		panic(err)
	}

	// Copy current key to prevent modification by the caller.
	c.currKey = make([]byte, len(key))
	copy(c.currKey, key)

	return key, value
}

// Next moves the cursor one key/value pair forward and returns the new
// pair.
func (c *readWriteCursor) Next() ([]byte, []byte) {
	var (
		key   []byte
		value []byte
	)
	row, cancel := c.bucket.tx.QueryRow(
		"SELECT key, value FROM "+c.bucket.table+" WHERE "+
			parentSelector(c.bucket.id)+
			" AND key>$1 ORDER BY key LIMIT 1",
		c.currKey,
	)
	defer cancel()
	err := row.Scan(&key, &value)

	switch {
	case err == sql.ErrNoRows:
		return nil, nil

	case err != nil:
		panic(err)
	}

	// Copy current key to prevent modification by the caller.
	c.currKey = make([]byte, len(key))
	copy(c.currKey, key)

	return key, value
}

// Prev moves the cursor one key/value pair backward and returns the new
// pair.
func (c *readWriteCursor) Prev() ([]byte, []byte) {
	var (
		key   []byte
		value []byte
	)
	row, cancel := c.bucket.tx.QueryRow(
		"SELECT key, value FROM "+c.bucket.table+" WHERE "+
			parentSelector(c.bucket.id)+
			" AND key<$1 ORDER BY key DESC LIMIT 1",
		c.currKey,
	)
	defer cancel()
	err := row.Scan(&key, &value)

	switch {
	case err == sql.ErrNoRows:
		return nil, nil

	case err != nil:
		panic(err)
	}

	// Copy current key to prevent modification by the caller.
	c.currKey = make([]byte, len(key))
	copy(c.currKey, key)

	return key, value
}

// Seek positions the cursor at the passed seek key.  If the key does
// not exist, the cursor is moved to the next key after seek.  Returns
// the new pair.
func (c *readWriteCursor) Seek(seek []byte) ([]byte, []byte) {
	// Convert nil to empty slice, otherwise sql mapping won't be correct
	// and no keys are found.
	if seek == nil {
		seek = []byte{}
	}

	var (
		key   []byte
		value []byte
	)
	row, cancel := c.bucket.tx.QueryRow(
		"SELECT key, value FROM "+c.bucket.table+" WHERE "+
			parentSelector(c.bucket.id)+
			" AND key>=$1 ORDER BY key LIMIT 1",
		seek,
	)
	defer cancel()
	err := row.Scan(&key, &value)

	switch {
	case err == sql.ErrNoRows:
		return nil, nil

	case err != nil:
		panic(err)
	}

	// Copy current key to prevent modification by the caller.
	c.currKey = make([]byte, len(key))
	copy(c.currKey, key)

	return key, value
}

// Delete removes the current key/value pair the cursor is at without
// invalidating the cursor.  Returns ErrIncompatibleValue if attempted
// when the cursor points to a nested bucket.
func (c *readWriteCursor) Delete() error {
	// Get first record at or after cursor.
	var key []byte
	row, cancel := c.bucket.tx.QueryRow(
		"SELECT key FROM "+c.bucket.table+" WHERE "+
			parentSelector(c.bucket.id)+
			" AND key>=$1 ORDER BY key LIMIT 1",
		c.currKey,
	)
	defer cancel()
	err := row.Scan(&key)

	switch {
	case err == sql.ErrNoRows:
		return nil

	case err != nil:
		panic(err)
	}

	// Delete record.
	result, err := c.bucket.tx.Exec(
		"DELETE FROM "+c.bucket.table+" WHERE "+
			parentSelector(c.bucket.id)+
			" AND key=$1 AND value IS NOT NULL",
		key,
	)
	if err != nil {
		panic(err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return err
	}

	// The key exists but nothing has been deleted. This means that the key
	// must have been a bucket key.
	if rows != 1 {
		return walletdb.ErrIncompatibleValue
	}

	return err
}
