//go:build linux || (darwin && amd64)

package fuse

import (
	"os"
	"syscall"

	fusefs "github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
	"gitlink.org.cn/cloudream/jcs-pub/client/internal/mount/config"
)

type Fuse struct {
	config *config.Config
	fs     Fs
}

func NewFuse(config *config.Config, fs Fs) *Fuse {
	return &Fuse{
		config: config,
		fs:     fs,
	}
}

func (v *Fuse) Root() fusefs.InodeEmbedder {
	return newDirNode(v, v.fs.Root())
}

func translateError(err error) syscall.Errno {
	switch err {
	case nil:
		return 0

	case ErrNotExists, os.ErrNotExist:
		return syscall.ENOENT

	case ErrExists, os.ErrExist:
		return syscall.EEXIST

	case ErrPermission, os.ErrPermission:
		return syscall.EACCES

	case ErrNotEmpty:
		return syscall.ENOTEMPTY

	case ErrIOError:
		return syscall.EIO

	case ErrNotSupported:
		return syscall.ENOTSUP

	default:
		switch e := err.(type) {
		case syscall.Errno:
			return e
		case *os.PathError:
		case *os.LinkError:
		case *os.SyscallError:
			return translateError(e.Err)
		}

		return syscall.EIO
	}
}

// get the Mode from a fs Node
func (v *Fuse) getMode(node FsEntry) uint32 {
	mode := node.Perm()
	if node.IsDir() {
		mode |= fuse.S_IFDIR
	} else {
		mode |= fuse.S_IFREG
	}
	return uint32(mode)
}

// fill in attr from node
func (v *Fuse) fillAttr(node FsEntry, attr *fuse.Attr) {
	Size := uint64(node.Size())
	const BlockSize = 512
	Blocks := (Size + BlockSize - 1) / BlockSize
	attr.Owner.Gid = v.config.GID
	attr.Owner.Uid = v.config.UID
	attr.Mode = v.getMode(node)
	attr.Size = Size
	attr.Nlink = 1
	attr.Blocks = Blocks
	// attr.Blksize = BlockSize // not supported in freebsd/darwin, defaults to 4k if not set

	modTime := node.ModTime()

	attr.Atime = uint64(modTime.Unix())
	attr.Atimensec = uint32(modTime.Nanosecond())

	attr.Mtime = uint64(modTime.Unix())
	attr.Mtimensec = uint32(modTime.Nanosecond())

	attr.Ctime = uint64(modTime.Unix())
	attr.Ctimensec = uint32(modTime.Nanosecond())
}

func (v *Fuse) fillAttrOut(node FsEntry, out *fuse.AttrOut) {
	v.fillAttr(node, &out.Attr)
	out.SetTimeout(v.config.AttrTimeout)
}

func (v *Fuse) fillEntryOut(node FsEntry, out *fuse.EntryOut) {
	v.fillAttr(node, &out.Attr)
	out.SetAttrTimeout(v.config.AttrTimeout)
	out.SetEntryTimeout(v.config.AttrTimeout)
}
