// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.

package objstorageprovider

import (
	"context"

	"github.com/cockroachdb/errors"
	"github.com/cockroachdb/pebble/internal/base"
	"github.com/cockroachdb/pebble/objstorage"
	"github.com/cockroachdb/pebble/vfs"
)

func (p *provider) vfsPath(fileType base.FileType, fileNum base.DiskFileNum) string {
	return base.MakeFilepath(p.st.FS, p.st.FSDirName, fileType, fileNum)
}

func (p *provider) vfsOpenForReading(
	ctx context.Context,
	fileType base.FileType,
	fileNum base.DiskFileNum,
	opts objstorage.OpenOptions,
) (objstorage.Readable, error) {
	filename := p.vfsPath(fileType, fileNum)
	file, err := p.st.FS.Open(filename, vfs.RandomReadsOption)
	if err != nil {
		if opts.MustExist {
			base.MustExist(p.st.FS, filename, p.st.Logger, err)
		}
		return nil, err
	}
	return newFileReadable(file, p.st.FS, filename)
}

func (p *provider) vfsCreate(
	_ context.Context, fileType base.FileType, fileNum base.DiskFileNum,
) (objstorage.Writable, objstorage.ObjectMetadata, error) {
	filename := p.vfsPath(fileType, fileNum)
	file, err := p.st.FS.Create(filename)
	if err != nil {
		return nil, objstorage.ObjectMetadata{}, err
	}
	file = vfs.NewSyncingFile(file, vfs.SyncingFileOptions{
		NoSyncOnClose: p.st.NoSyncOnClose,
		BytesPerSync:  p.st.BytesPerSync,
	})
	meta := objstorage.ObjectMetadata{
		DiskFileNum: fileNum,
		FileType:    fileType,
	}
	return newFileBufferedWritable(file), meta, nil
}

func (p *provider) vfsRemove(fileType base.FileType, fileNum base.DiskFileNum) error {
	return p.st.FSCleaner.Clean(p.st.FS, fileType, p.vfsPath(fileType, fileNum))
}

// vfsInit finds any local FS objects.
func (p *provider) vfsInit() error {
	listing := p.st.FSDirInitialListing
	if listing == nil {
		var err error
		listing, err = p.st.FS.List(p.st.FSDirName)
		if err != nil {
			return errors.Wrapf(err, "pebble: could not list store directory")
		}
	}

	for _, filename := range listing {
		fileType, fileNum, ok := base.ParseFilename(p.st.FS, filename)
		if ok && fileType == base.FileTypeTable {
			o := objstorage.ObjectMetadata{
				FileType:    fileType,
				DiskFileNum: fileNum,
			}
			p.mu.knownObjects[o.DiskFileNum] = o
		}
	}
	return nil
}

func (p *provider) vfsSync() error {
	p.mu.Lock()
	shouldSync := p.mu.localObjectsChanged
	p.mu.localObjectsChanged = false
	p.mu.Unlock()

	if !shouldSync {
		return nil
	}
	if err := p.fsDir.Sync(); err != nil {
		p.mu.Lock()
		defer p.mu.Unlock()
		p.mu.localObjectsChanged = true
		return err
	}
	return nil
}

func (p *provider) vfsSize(fileType base.FileType, fileNum base.DiskFileNum) (int64, error) {
	filename := p.vfsPath(fileType, fileNum)
	stat, err := p.st.FS.Stat(filename)
	if err != nil {
		return 0, err
	}
	return stat.Size(), nil
}