// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2016-2019 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

// Parts of this interface were inspired heavily by the excellent boltdb project
// at https://github.com/boltdb/bolt by Ben B. Johnson.

package database

import (
	"github.com/decred/dcrd/chaincfg/chainhash"
)

// Cursor represents a cursor over key/value pairs and nested buckets of a
// bucket.
//
// Note that open cursors are not tracked on bucket changes and any
// modifications to the bucket, with the exception of Cursor.Delete, invalidates
// the cursor.  After invalidation, the cursor must be repositioned, or the keys
// and values returned may be unpredictable.
type Cursor interface {
	// Bucket returns the bucket the cursor was created for.
	Bucket() Bucket

	// Delete removes the current key/value pair the cursor is at without
	// invalidating the cursor.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrIncompatibleValue if attempted when the cursor points to a
	//     nested bucket
	//   - ErrTxNotWritable if attempted against a read-only transaction
	//   - ErrTxClosed if the transaction has already been closed
	Delete() error

	// First positions the cursor at the first key/value pair and returns
	// whether or not the pair exists.
	First() bool

	// Last positions the cursor at the last key/value pair and returns
	// whether or not the pair exists.
	Last() bool

	// Next moves the cursor one key/value pair forward and returns whether
	// or not the pair exists.
	Next() bool

	// Prev moves the cursor one key/value pair backward and returns whether
	// or not the pair exists.
	Prev() bool

	// Seek positions the cursor at the first key/value pair that is greater
	// than or equal to the passed seek key.  Returns whether or not the
	// pair exists.
	Seek(seek []byte) bool

	// Key returns the current key the cursor is pointing to.
	Key() []byte

	// Value returns the current value the cursor is pointing to.  This will
	// be nil for nested buckets.
	Value() []byte
}

// Bucket represents a collection of key/value pairs.
type Bucket interface {
	// Bucket retrieves a nested bucket with the given key.  Returns nil if
	// the bucket does not exist.
	Bucket(key []byte) Bucket

	// CreateBucket creates and returns a new nested bucket with the given
	// key.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBucketExists if the bucket already exists
	//   - ErrBucketNameRequired if the key is empty
	//   - ErrIncompatibleValue if the key is otherwise invalid for the
	//     particular implementation
	//   - ErrTxNotWritable if attempted against a read-only transaction
	//   - ErrTxClosed if the transaction has already been closed
	CreateBucket(key []byte) (Bucket, error)

	// CreateBucketIfNotExists creates and returns a new nested bucket with
	// the given key if it does not already exist.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBucketNameRequired if the key is empty
	//   - ErrIncompatibleValue if the key is otherwise invalid for the
	//     particular implementation
	//   - ErrTxNotWritable if attempted against a read-only transaction
	//   - ErrTxClosed if the transaction has already been closed
	CreateBucketIfNotExists(key []byte) (Bucket, error)

	// DeleteBucket removes a nested bucket with the given key.  This also
	// includes removing all nested buckets and keys under the bucket being
	// deleted.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBucketNotFound if the specified bucket does not exist
	//   - ErrTxNotWritable if attempted against a read-only transaction
	//   - ErrTxClosed if the transaction has already been closed
	DeleteBucket(key []byte) error

	// ForEach invokes the passed function with every key/value pair in the
	// bucket.  This does not include nested buckets or the key/value pairs
	// within those nested buckets.
	//
	// WARNING: It is not safe to mutate data while iterating with this
	// method.  Doing so may cause the underlying cursor to be invalidated
	// and return unexpected keys and/or values.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrTxClosed if the transaction has already been closed
	//
	// NOTE: The slices returned by this function are only valid during a
	// transaction.  Attempting to access them after a transaction has ended
	// results in undefined behavior.  Additionally, the slices must NOT
	// be modified by the caller.  These constraints prevent additional data
	// copies and allows support for memory-mapped database implementations.
	ForEach(func(k, v []byte) error) error

	// ForEachBucket invokes the passed function with the key of every
	// nested bucket in the current bucket.  This does not include any
	// nested buckets within those nested buckets.
	//
	// WARNING: It is not safe to mutate data while iterating with this
	// method.  Doing so may cause the underlying cursor to be invalidated
	// and return unexpected keys and/or values.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrTxClosed if the transaction has already been closed
	//
	// NOTE: The keys returned by this function are only valid during a
	// transaction.  Attempting to access them after a transaction has ended
	// results in undefined behavior.  This constraint prevents additional
	// data copies and allows support for memory-mapped database
	// implementations.
	ForEachBucket(func(k []byte) error) error

	// Cursor returns a new cursor, allowing for iteration over the bucket's
	// key/value pairs and nested buckets in forward or backward order.
	//
	// You must seek to a position using the First, Last, or Seek functions
	// before calling the Next, Prev, Key, or Value functions.  Failure to
	// do so will result in the same return values as an exhausted cursor,
	// which is false for the Prev and Next functions and nil for Key and
	// Value functions.
	Cursor() Cursor

	// Writable returns whether or not the bucket is writable.
	Writable() bool

	// Put saves the specified key/value pair to the bucket.  Keys that do
	// not already exist are added and keys that already exist are
	// overwritten.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrKeyRequired if the key is empty
	//   - ErrIncompatibleValue if the key is the same as an existing bucket
	//   - ErrTxNotWritable if attempted against a read-only transaction
	//   - ErrTxClosed if the transaction has already been closed
	//
	// NOTE: The slices passed to this function must NOT be modified by the
	// caller.  This constraint prevents the requirement for additional data
	// copies and allows support for memory-mapped database implementations.
	Put(key, value []byte) error

	// Get returns the value for the given key.  Returns nil if the key does
	// not exist in this bucket.  An empty slice is returned for keys that
	// exist but have no value assigned.
	//
	// NOTE: The value returned by this function is only valid during a
	// transaction.  Attempting to access it after a transaction has ended
	// results in undefined behavior.  Additionally, the value must NOT
	// be modified by the caller.  These constraints prevent additional data
	// copies and allows support for memory-mapped database implementations.
	Get(key []byte) []byte

	// Delete removes the specified key from the bucket.  Deleting a key
	// that does not exist does not return an error.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrKeyRequired if the key is empty
	//   - ErrIncompatibleValue if the key is the same as an existing bucket
	//   - ErrTxNotWritable if attempted against a read-only transaction
	//   - ErrTxClosed if the transaction has already been closed
	Delete(key []byte) error
}

// BlockRegion specifies a particular region of a block identified by the
// specified hash, given an offset and length.
type BlockRegion struct {
	Hash   *chainhash.Hash
	Offset uint32
	Len    uint32
}

// BlockSerializer defines an interface that represents a block to be serialized
// and stored into the database.
type BlockSerializer interface {
	// Hash returns the block identifier hash for the block.
	Hash() *chainhash.Hash

	// Bytes returns the serialized bytes for the block.    This is expected
	// to return the raw bytes in the format returned by Serialize on a
	// wire.MsgBlock.
	Bytes() ([]byte, error)
}

// Tx represents a database transaction.  It can either by read-only or
// read-write.  The transaction provides a metadata bucket against which all
// read and writes occur.
//
// As would be expected with a transaction, no changes will be saved to the
// database until it has been committed.  The transaction will only provide a
// view of the database at the time it was created.  Transactions should not be
// long running operations.
type Tx interface {
	// Metadata returns the top-most bucket for all metadata storage.
	Metadata() Bucket

	// StoreBlock stores the provided block into the database.  There are no
	// checks to ensure the block connects to a previous block, contains
	// double spends, or any additional functionality such as transaction
	// indexing.  It simply stores the block in the database.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBlockExists when the block hash already exists
	//   - ErrTxNotWritable if attempted against a read-only transaction
	//   - ErrTxClosed if the transaction has already been closed
	//
	// Other errors are possible depending on the implementation.
	StoreBlock(block BlockSerializer) error

	// HasBlock returns whether or not a block with the given hash exists
	// in the database.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrTxClosed if the transaction has already been closed
	//
	// Other errors are possible depending on the implementation.
	HasBlock(hash *chainhash.Hash) (bool, error)

	// HasBlocks returns whether or not the blocks with the provided hashes
	// exist in the database.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrTxClosed if the transaction has already been closed
	//
	// Other errors are possible depending on the implementation.
	HasBlocks(hashes []chainhash.Hash) ([]bool, error)

	// FetchBlockHeader returns the raw serialized bytes for the block
	// header identified by the given hash.  The raw bytes are in the format
	// returned by Serialize on a wire.BlockHeader.
	//
	// It is highly recommended to use this function (or FetchBlockHeaders)
	// to obtain block headers over the FetchBlockRegion(s) functions since
	// it provides the backend drivers the freedom to perform very specific
	// optimizations which can result in significant speed advantages when
	// working with headers.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBlockNotFound if the requested block hash does not exist
	//   - ErrTxClosed if the transaction has already been closed
	//   - ErrCorruption if the database has somehow become corrupted
	//
	// NOTE: The data returned by this function is only valid during a
	// database transaction.  Attempting to access it after a transaction
	// has ended results in undefined behavior.  This constraint prevents
	// additional data copies and allows support for memory-mapped database
	// implementations.
	FetchBlockHeader(hash *chainhash.Hash) ([]byte, error)

	// FetchBlockHeaders returns the raw serialized bytes for the block
	// headers identified by the given hashes.  The raw bytes are in the
	// format returned by Serialize on a wire.BlockHeader.
	//
	// It is highly recommended to use this function (or FetchBlockHeader)
	// to obtain block headers over the FetchBlockRegion(s) functions since
	// it provides the backend drivers the freedom to perform very specific
	// optimizations which can result in significant speed advantages when
	// working with headers.
	//
	// Furthermore, depending on the specific implementation, this function
	// can be more efficient for bulk loading multiple block headers than
	// loading them one-by-one with FetchBlockHeader.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBlockNotFound if any of the request block hashes do not exist
	//   - ErrTxClosed if the transaction has already been closed
	//   - ErrCorruption if the database has somehow become corrupted
	//
	// NOTE: The data returned by this function is only valid during a
	// database transaction.  Attempting to access it after a transaction
	// has ended results in undefined behavior.  This constraint prevents
	// additional data copies and allows support for memory-mapped database
	// implementations.
	FetchBlockHeaders(hashes []chainhash.Hash) ([][]byte, error)

	// FetchBlock returns the raw serialized bytes for the block identified
	// by the given hash.  The raw bytes are in the format returned by
	// Serialize on a wire.MsgBlock.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBlockNotFound if the requested block hash does not exist
	//   - ErrTxClosed if the transaction has already been closed
	//   - ErrCorruption if the database has somehow become corrupted
	//
	// NOTE: The data returned by this function is only valid during a
	// database transaction.  Attempting to access it after a transaction
	// has ended results in undefined behavior.  This constraint prevents
	// additional data copies and allows support for memory-mapped database
	// implementations.
	FetchBlock(hash *chainhash.Hash) ([]byte, error)

	// FetchBlocks returns the raw serialized bytes for the blocks
	// identified by the given hashes.  The raw bytes are in the format
	// returned by Serialize on a wire.MsgBlock.
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBlockNotFound if the any of the requested block hashes do not
	//     exist
	//   - ErrTxClosed if the transaction has already been closed
	//   - ErrCorruption if the database has somehow become corrupted
	//
	// NOTE: The data returned by this function is only valid during a
	// database transaction.  Attempting to access it after a transaction
	// has ended results in undefined behavior.  This constraint prevents
	// additional data copies and allows support for memory-mapped database
	// implementations.
	FetchBlocks(hashes []chainhash.Hash) ([][]byte, error)

	// FetchBlockRegion returns the raw serialized bytes for the given
	// block region.
	//
	// For example, it is possible to directly extract transactions and/or
	// scripts from a block with this function.  Depending on the backend
	// implementation, this can provide significant savings by avoiding the
	// need to load entire blocks.
	//
	// The raw bytes are in the format returned by Serialize on a
	// wire.MsgBlock and the Offset field in the provided BlockRegion is
	// zero-based and relative to the start of the block (byte 0).
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBlockNotFound if the requested block hash does not exist
	//   - ErrBlockRegionInvalid if the region exceeds the bounds of the
	//     associated block
	//   - ErrTxClosed if the transaction has already been closed
	//   - ErrCorruption if the database has somehow become corrupted
	//
	// NOTE: The data returned by this function is only valid during a
	// database transaction.  Attempting to access it after a transaction
	// has ended results in undefined behavior.  This constraint prevents
	// additional data copies and allows support for memory-mapped database
	// implementations.
	FetchBlockRegion(region *BlockRegion) ([]byte, error)

	// FetchBlockRegions returns the raw serialized bytes for the given
	// block regions.
	//
	// For example, it is possible to directly extract transactions and/or
	// scripts from various blocks with this function.  Depending on the
	// backend implementation, this can provide significant savings by
	// avoiding the need to load entire blocks.
	//
	// The raw bytes are in the format returned by Serialize on a
	// wire.MsgBlock and the Offset fields in the provided BlockRegions are
	// zero-based and relative to the start of the block (byte 0).
	//
	// The interface contract guarantees at least the following errors will
	// be returned (other implementation-specific errors are possible):
	//   - ErrBlockNotFound if any of the requested block hashed do not
	//     exist
	//   - ErrBlockRegionInvalid if one or more region exceed the bounds of
	//     the associated block
	//   - ErrTxClosed if the transaction has already been closed
	//   - ErrCorruption if the database has somehow become corrupted
	//
	// NOTE: The data returned by this function is only valid during a
	// database transaction.  Attempting to access it after a transaction
	// has ended results in undefined behavior.  This constraint prevents
	// additional data copies and allows support for memory-mapped database
	// implementations.
	FetchBlockRegions(regions []BlockRegion) ([][]byte, error)

	// ******************************************************************
	// Methods related to both atomic metadata storage and block storage.
	// ******************************************************************

	// Commit commits all changes that have been made to the metadata or
	// block storage.  Depending on the backend implementation this could be
	// to a cache that is periodically synced to persistent storage or
	// directly to persistent storage.  In any case, all transactions which
	// are started after the commit finishes will include all changes made
	// by this transaction.  Calling this function on a managed transaction
	// will result in a panic.
	Commit() error

	// Rollback undoes all changes that have been made to the metadata or
	// block storage.  Calling this function on a managed transaction will
	// result in a panic.
	Rollback() error
}

// DB provides a generic interface that is used to store blocks and related
// metadata.  This interface is intended to be agnostic to the actual mechanism
// used for backend data storage.  The RegisterDriver function can be used to
// add a new backend data storage method.
//
// This interface is divided into two distinct categories of functionality.
//
// The first category is atomic metadata storage with bucket support.  This is
// accomplished through the use of database transactions.
//
// The second category is generic block storage.  This functionality is
// intentionally separate because the mechanism used for block storage may or
// may not be the same mechanism used for metadata storage.  For example, it is
// often more efficient to store the block data as flat files while the metadata
// is kept in a database.  However, this interface aims to be generic enough to
// support blocks in the database too, if needed by a particular backend.
type DB interface {
	// Type returns the database driver type the current database instance
	// was created with.
	Type() string

	// Begin starts a transaction which is either read-only or read-write
	// depending on the specified flag.  Multiple read-only transactions
	// can be started simultaneously while only a single read-write
	// transaction can be started at a time.  The call will block when
	// starting a read-write transaction when one is already open.
	//
	// NOTE: The transaction must be closed by calling Rollback or Commit on
	// it when it is no longer needed.  Failure to do so can result in
	// unclaimed memory and/or inability to close the database due to locks
	// depending on the specific database implementation.
	Begin(writable bool) (Tx, error)

	// View invokes the passed function in the context of a managed
	// read-only transaction.  Any errors returned from the user-supplied
	// function are returned from this function.
	//
	// Calling Rollback or Commit on the transaction passed to the
	// user-supplied function will result in a panic.
	View(fn func(tx Tx) error) error

	// Update invokes the passed function in the context of a managed
	// read-write transaction.  Any errors returned from the user-supplied
	// function will cause the transaction to be rolled back and are
	// returned from this function.  Otherwise, the transaction is committed
	// when the user-supplied function returns a nil error.
	//
	// Calling Rollback or Commit on the transaction passed to the
	// user-supplied function will result in a panic.
	Update(fn func(tx Tx) error) error

	// Close cleanly shuts down the database and syncs all data.  It will
	// block until all database transactions have been finalized (rolled
	// back or committed).
	Close() error

	// Flush writes all outstanding cached entries to disk.
	Flush() error
}
