package blockstore

import (
	"context"

	"github.com/ipfs/go-cid"
	ipld "github.com/ipfs/go-ipld-format"
	blocks "github.com/ipfs/go-libipfs/blocks"
)

// NewMemory returns a temporary memory-backed blockstore.
func NewMemory() MemBlockstore {
	return make(MemBlockstore)
}

// MemBlockstore is a terminal blockstore that keeps blocks in memory.
// To match behavior of badger blockstore we index by multihash only.
type MemBlockstore map[string]blocks.Block

func (MemBlockstore) Flush(context.Context) error { return nil }

func (m MemBlockstore) DeleteBlock(ctx context.Context, k cid.Cid) error {
	delete(m, string(k.Hash()))
	return nil
}

func (m MemBlockstore) DeleteMany(ctx context.Context, ks []cid.Cid) error {
	for _, k := range ks {
		delete(m, string(k.Hash()))
	}
	return nil
}

func (m MemBlockstore) Has(ctx context.Context, k cid.Cid) (bool, error) {
	_, ok := m[string(k.Hash())]
	return ok, nil
}

func (m MemBlockstore) View(ctx context.Context, k cid.Cid, callback func([]byte) error) error {
	b, ok := m[string(k.Hash())]
	if !ok {
		return ipld.ErrNotFound{Cid: k}
	}
	return callback(b.RawData())
}

func (m MemBlockstore) Get(ctx context.Context, k cid.Cid) (blocks.Block, error) {
	b, ok := m[string(k.Hash())]
	if !ok {
		return nil, ipld.ErrNotFound{Cid: k}
	}
	if b.Cid().Prefix().Codec != k.Prefix().Codec {
		return blocks.NewBlockWithCid(b.RawData(), k)
	}
	return b, nil
}

// GetSize returns the CIDs mapped BlockSize
func (m MemBlockstore) GetSize(ctx context.Context, k cid.Cid) (int, error) {
	b, ok := m[string(k.Hash())]
	if !ok {
		return 0, ipld.ErrNotFound{Cid: k}
	}
	return len(b.RawData()), nil
}

// Put puts a given block to the underlying datastore
func (m MemBlockstore) Put(ctx context.Context, b blocks.Block) error {
	// Convert to a basic block for safety, but try to reuse the existing
	// block if it's already a basic block.
	k := string(b.Cid().Hash())
	if _, ok := b.(*blocks.BasicBlock); !ok {
		// If we already have the block, abort.
		if _, ok := m[k]; ok {
			return nil
		}
		// the error is only for debugging.
		b, _ = blocks.NewBlockWithCid(b.RawData(), b.Cid())
	}
	m[k] = b
	return nil
}

// PutMany puts a slice of blocks at the same time using batching
// capabilities of the underlying datastore whenever possible.
func (m MemBlockstore) PutMany(ctx context.Context, bs []blocks.Block) error {
	for _, b := range bs {
		_ = m.Put(ctx, b) // can't fail
	}
	return nil
}

// AllKeysChan returns a channel from which
// the CIDs in the Blockstore can be read. It should respect
// the given context, closing the channel if it becomes Done.
func (m MemBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
	ch := make(chan cid.Cid, len(m))
	for _, b := range m {
		ch <- b.Cid()
	}
	close(ch)
	return ch, nil
}

// HashOnRead specifies if every read block should be
// rehashed to make sure it matches its CID.
func (m MemBlockstore) HashOnRead(enabled bool) {
	// no-op
}
