// Package memdb implements the "memdb" SQLite VFS.
//
// The "memdb" [vfs.VFS] allows the same in-memory database to be shared
// among multiple database connections in the same process,
// as long as the database name begins with "/".
//
// Importing package memdb registers the VFS:
//
//	import _ "github.com/ncruces/go-sqlite3/vfs/memdb"
package memdb

import (
	"crypto/rand"
	"fmt"
	"net/url"
	"sync"
	"testing"

	"github.com/ncruces/go-sqlite3/vfs"
)

func init() {
	vfs.Register("memdb", memVFS{})
}

var (
	memoryMtx sync.Mutex
	// +checklocks:memoryMtx
	memoryDBs = map[string]*memDB{}
)

// Create creates a shared memory database,
// using data as its initial contents.
// The new database takes ownership of data,
// and the caller should not use data after this call.
func Create(name string, data []byte) {
	db := &memDB{
		refs: 1,
		name: name,
		size: int64(len(data)),
	}

	// Convert data from WAL/2 to rollback journal.
	if len(data) >= 20 && (false ||
		data[18] == 2 && data[19] == 2 ||
		data[18] == 3 && data[19] == 3) {
		data[18] = 1
		data[19] = 1
	}

	sectors := divRoundUp(db.size, sectorSize)
	db.data = make([]*[sectorSize]byte, sectors)
	for i := range db.data {
		sector := data[i*sectorSize:]
		if len(sector) >= sectorSize {
			db.data[i] = (*[sectorSize]byte)(sector)
		} else {
			db.data[i] = new([sectorSize]byte)
			copy((*db.data[i])[:], sector)
		}
	}

	memoryMtx.Lock()
	memoryDBs[name] = db
	memoryMtx.Unlock()
}

// Delete deletes a shared memory database.
func Delete(name string) {
	memoryMtx.Lock()
	delete(memoryDBs, name)
	memoryMtx.Unlock()
}

// TestDB creates an empty shared memory database for the test to use.
// The database is automatically deleted when the test and all its subtests complete.
// Returns a URI filename appropriate to call Open with.
// Each subsequent call to TestDB returns a unique database.
//
//	func Test_something(t *testing.T) {
//		t.Parallel()
//		dsn := memdb.TestDB(t, url.Values{
//			"_pragma": {"busy_timeout(1000)"},
//		})
//
//		db, err := sql.Open("sqlite3", dsn)
//		if err != nil {
//			t.Fatal(err)
//		}
//		defer db.Close()
//
//		// ...
//	}
func TestDB(tb testing.TB, params ...url.Values) string {
	tb.Helper()

	name := fmt.Sprintf("%s_%s", tb.Name(), rand.Text())
	tb.Cleanup(func() { Delete(name) })
	Create(name, nil)

	p := url.Values{"vfs": {"memdb"}}
	for _, v := range params {
		for k, v := range v {
			for _, v := range v {
				p.Add(k, v)
			}
		}
	}

	return (&url.URL{
		Scheme:   "file",
		OmitHost: true,
		Path:     "/" + name,
		RawQuery: p.Encode(),
	}).String()
}
