package blockstore

import (
	"context"
	"sync"

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

// NewMemorySync returns a thread-safe in-memory blockstore.
func NewMemorySync() *SyncBlockstore {
	return &SyncBlockstore{bs: make(MemBlockstore)}
}

// SyncBlockstore is a terminal blockstore that is a synchronized version
// of MemBlockstore.
type SyncBlockstore struct {
	mu sync.RWMutex
	bs MemBlockstore // specifically use a memStore to save indirection overhead.
}

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

func (m *SyncBlockstore) DeleteBlock(ctx context.Context, k cid.Cid) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.bs.DeleteBlock(ctx, k)
}

func (m *SyncBlockstore) DeleteMany(ctx context.Context, ks []cid.Cid) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.bs.DeleteMany(ctx, ks)
}

func (m *SyncBlockstore) Has(ctx context.Context, k cid.Cid) (bool, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.bs.Has(ctx, k)
}

func (m *SyncBlockstore) View(ctx context.Context, k cid.Cid, callback func([]byte) error) error {
	m.mu.RLock()
	defer m.mu.RUnlock()

	return m.bs.View(ctx, k, callback)
}

func (m *SyncBlockstore) Get(ctx context.Context, k cid.Cid) (blocks.Block, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.bs.Get(ctx, k)
}

func (m *SyncBlockstore) GetSize(ctx context.Context, k cid.Cid) (int, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.bs.GetSize(ctx, k)
}

func (m *SyncBlockstore) Put(ctx context.Context, b blocks.Block) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.bs.Put(ctx, b)
}

func (m *SyncBlockstore) PutMany(ctx context.Context, bs []blocks.Block) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.bs.PutMany(ctx, bs)
}

func (m *SyncBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	// this blockstore implementation doesn't do any async work.
	return m.bs.AllKeysChan(ctx)
}

func (m *SyncBlockstore) HashOnRead(enabled bool) {
	// noop
}