// SPDX-License-Identifier: BSD-3-Clause
//go:build netbsd

package disk

import (
	"context"
	"unsafe"

	"golang.org/x/sys/unix"

	"github.com/shirou/gopsutil/v4/internal/common"
)

const (
	// see sys/fstypes.h and `man 5 statvfs`
	MNT_RDONLY      = 0x00000001 /* read only filesystem */
	MNT_SYNCHRONOUS = 0x00000002 /* file system written synchronously */
	MNT_NOEXEC      = 0x00000004 /* can't exec from filesystem */
	MNT_NOSUID      = 0x00000008 /* don't honor setuid bits on fs */
	MNT_NODEV       = 0x00000010 /* don't interpret special files */
	MNT_ASYNC       = 0x00000040 /* file system written asynchronously */
	MNT_NOATIME     = 0x04000000 /* Never update access times in fs */
	MNT_SOFTDEP     = 0x80000000 /* Use soft dependencies */
)

func PartitionsWithContext(ctx context.Context, all bool) ([]PartitionStat, error) {
	var ret []PartitionStat

	flag := uint64(1) // ST_WAIT/MNT_WAIT, see sys/fstypes.h

	// get required buffer size
	emptyBufSize := 0
	r, _, err := unix.Syscall(
		483, // SYS___getvfsstat90 syscall
		uintptr(unsafe.Pointer(nil)),
		uintptr(unsafe.Pointer(&emptyBufSize)),
		uintptr(unsafe.Pointer(&flag)),
	)
	if err != 0 {
		return ret, err
	}
	mountedFsCount := uint64(r)

	// calculate the buffer size
	bufSize := sizeOfStatvfs * mountedFsCount
	buf := make([]Statvfs, mountedFsCount)

	// request again to get desired mount data
	_, _, err = unix.Syscall(
		483, // SYS___getvfsstat90 syscall
		uintptr(unsafe.Pointer(&buf[0])),
		uintptr(unsafe.Pointer(&bufSize)),
		uintptr(unsafe.Pointer(&flag)),
	)
	if err != 0 {
		return ret, err
	}

	for _, stat := range buf {
		opts := []string{"rw"}
		if stat.Flag&MNT_RDONLY != 0 {
			opts = []string{"rw"}
		}
		if stat.Flag&MNT_SYNCHRONOUS != 0 {
			opts = append(opts, "sync")
		}
		if stat.Flag&MNT_NOEXEC != 0 {
			opts = append(opts, "noexec")
		}
		if stat.Flag&MNT_NOSUID != 0 {
			opts = append(opts, "nosuid")
		}
		if stat.Flag&MNT_NODEV != 0 {
			opts = append(opts, "nodev")
		}
		if stat.Flag&MNT_ASYNC != 0 {
			opts = append(opts, "async")
		}
		if stat.Flag&MNT_SOFTDEP != 0 {
			opts = append(opts, "softdep")
		}
		if stat.Flag&MNT_NOATIME != 0 {
			opts = append(opts, "noatime")
		}

		d := PartitionStat{
			Device:     common.ByteToString([]byte(stat.Mntfromname[:])),
			Mountpoint: common.ByteToString([]byte(stat.Mntonname[:])),
			Fstype:     common.ByteToString([]byte(stat.Fstypename[:])),
			Opts:       opts,
		}

		ret = append(ret, d)
	}

	return ret, nil
}

func IOCountersWithContext(ctx context.Context, names ...string) (map[string]IOCountersStat, error) {
	ret := make(map[string]IOCountersStat)
	return ret, common.ErrNotImplementedError
}

func UsageWithContext(ctx context.Context, path string) (*UsageStat, error) {
	stat := Statvfs{}
	flag := uint64(1) // ST_WAIT/MNT_WAIT, see sys/fstypes.h

	_path, e := unix.BytePtrFromString(path)
	if e != nil {
		return nil, e
	}

	_, _, err := unix.Syscall(
		484, // SYS___statvfs190, see sys/syscall.h
		uintptr(unsafe.Pointer(_path)),
		uintptr(unsafe.Pointer(&stat)),
		uintptr(unsafe.Pointer(&flag)),
	)
	if err != 0 {
		return nil, err
	}

	// frsize is the real block size on NetBSD. See discuss here: https://bugzilla.samba.org/show_bug.cgi?id=11810
	bsize := stat.Frsize
	ret := &UsageStat{
		Path:        path,
		Fstype:      getFsType(stat),
		Total:       (uint64(stat.Blocks) * uint64(bsize)),
		Free:        (uint64(stat.Bavail) * uint64(bsize)),
		InodesTotal: (uint64(stat.Files)),
		InodesFree:  (uint64(stat.Ffree)),
	}

	ret.InodesUsed = (ret.InodesTotal - ret.InodesFree)
	ret.InodesUsedPercent = (float64(ret.InodesUsed) / float64(ret.InodesTotal)) * 100.0
	ret.Used = (uint64(stat.Blocks) - uint64(stat.Bfree)) * uint64(bsize)
	ret.UsedPercent = (float64(ret.Used) / float64(ret.Total)) * 100.0

	return ret, nil
}

func getFsType(stat Statvfs) string {
	return common.ByteToString(stat.Fstypename[:])
}

func SerialNumberWithContext(ctx context.Context, name string) (string, error) {
	return "", common.ErrNotImplementedError
}

func LabelWithContext(ctx context.Context, name string) (string, error) {
	return "", common.ErrNotImplementedError
}
