// Copyright (c) Acrosync LLC. All rights reserved.
// Free for personal use and commercial trial
// Commercial use requires per-user licenses available from https://duplicacy.com

//go:build freebsd || netbsd || darwin
// +build freebsd netbsd darwin

package duplicacy

import (
	"bytes"
	"encoding/binary"
	"os"
	"path/filepath"
	"syscall"

	"github.com/pkg/xattr"
)

const bsdFileFlagsKey = "\x00bf"

func (entry *Entry) ReadAttributes(top string) {
	fullPath := filepath.Join(top, entry.Path)
	fileInfo, err := os.Lstat(fullPath)
	if err != nil {
		return
	}

	if !entry.IsSpecial() {
		attributes, _ := xattr.LList(fullPath)
		if len(attributes) > 0 {
			entry.Attributes = &map[string][]byte{}
			for _, name := range attributes {
				attribute, err := xattr.LGet(fullPath, name)
				if err == nil {
					(*entry.Attributes)[name] = attribute
				}
			}
		}
	}
	if err := entry.readFileFlags(fileInfo); err != nil {
		LOG_INFO("ATTR_BACKUP", "Could not backup flags for file %s: %v", fullPath, err)
	}
}

func (entry *Entry) SetAttributesToFile(fullPath string) {
	if !entry.IsSpecial() {
		names, _ := xattr.LList(fullPath)
		for _, name := range names {
			newAttribute, found := (*entry.Attributes)[name]
			if found {
				oldAttribute, _ := xattr.LGet(fullPath, name)
				if !bytes.Equal(oldAttribute, newAttribute) {
					xattr.LSet(fullPath, name, newAttribute)
				}
				delete(*entry.Attributes, name)
			} else {
				xattr.LRemove(fullPath, name)
			}
		}

		for name, attribute := range *entry.Attributes {
			if len(name) > 0 && name[0] == '\x00' {
				continue
			}
			xattr.LSet(fullPath, name, attribute)
		}
	}
	if err := entry.restoreLateFileFlags(fullPath); err != nil {
		LOG_DEBUG("ATTR_RESTORE", "Could not restore flags for file %s: %v", fullPath, err)
	}
}

func (entry *Entry) readFileFlags(fileInfo os.FileInfo) error {
	stat, ok := fileInfo.Sys().(*syscall.Stat_t)
	if ok && stat.Flags != 0 {
		if entry.Attributes == nil {
			entry.Attributes = &map[string][]byte{}
		}
		v := make([]byte, 4)
		binary.LittleEndian.PutUint32(v, stat.Flags)
		(*entry.Attributes)[bsdFileFlagsKey] = v
		LOG_DEBUG("ATTR_READ", "Read flags 0x%x for %s", stat.Flags, entry.Path)
	}
	return nil
}

func (entry *Entry) RestoreEarlyDirFlags(path string) error {
	return nil
}

func (entry *Entry) RestoreEarlyFileFlags(f *os.File) error {
	return nil
}

func (entry *Entry) RestoreSpecial(fullPath string) error {
	mode := entry.Mode & uint32(fileModeMask)

	if entry.Mode&uint32(os.ModeNamedPipe) != 0 {
		mode |= syscall.S_IFIFO
	} else if entry.Mode&uint32(os.ModeCharDevice) != 0 {
		mode |= syscall.S_IFCHR
	} else if entry.Mode&uint32(os.ModeDevice) != 0 {
		mode |= syscall.S_IFBLK
	} else {
		return nil
	}
	return syscall.Mknod(fullPath, mode, int(entry.GetRdev()))
}
