package sshfx

// FileMode represents a file’s mode and permission bits.
// The bits are defined according to POSIX standards,
// and may not apply to the OS being built for.
type FileMode uint32

// Permission flags, defined here to avoid potential inconsistencies in individual OS implementations.
const (
	ModePerm       FileMode = 0o0777 // S_IRWXU | S_IRWXG | S_IRWXO
	ModeUserRead   FileMode = 0o0400 // S_IRUSR
	ModeUserWrite  FileMode = 0o0200 // S_IWUSR
	ModeUserExec   FileMode = 0o0100 // S_IXUSR
	ModeGroupRead  FileMode = 0o0040 // S_IRGRP
	ModeGroupWrite FileMode = 0o0020 // S_IWGRP
	ModeGroupExec  FileMode = 0o0010 // S_IXGRP
	ModeOtherRead  FileMode = 0o0004 // S_IROTH
	ModeOtherWrite FileMode = 0o0002 // S_IWOTH
	ModeOtherExec  FileMode = 0o0001 // S_IXOTH

	ModeSetUID FileMode = 0o4000 // S_ISUID
	ModeSetGID FileMode = 0o2000 // S_ISGID
	ModeSticky FileMode = 0o1000 // S_ISVTX

	ModeType       FileMode = 0xF000 // S_IFMT
	ModeNamedPipe  FileMode = 0x1000 // S_IFIFO
	ModeCharDevice FileMode = 0x2000 // S_IFCHR
	ModeDir        FileMode = 0x4000 // S_IFDIR
	ModeDevice     FileMode = 0x6000 // S_IFBLK
	ModeRegular    FileMode = 0x8000 // S_IFREG
	ModeSymlink    FileMode = 0xA000 // S_IFLNK
	ModeSocket     FileMode = 0xC000 // S_IFSOCK
)

// IsDir reports whether m describes a directory.
// That is, it tests for m.Type() == ModeDir.
func (m FileMode) IsDir() bool {
	return (m & ModeType) == ModeDir
}

// IsRegular reports whether m describes a regular file.
// That is, it tests for m.Type() == ModeRegular
func (m FileMode) IsRegular() bool {
	return (m & ModeType) == ModeRegular
}

// Perm returns the POSIX permission bits in m (m & ModePerm).
func (m FileMode) Perm() FileMode {
	return (m & ModePerm)
}

// Type returns the type bits in m (m & ModeType).
func (m FileMode) Type() FileMode {
	return (m & ModeType)
}

// String returns a `-rwxrwxrwx` style string representing the `ls -l` POSIX permissions string.
func (m FileMode) String() string {
	var buf [10]byte

	switch m.Type() {
	case ModeRegular:
		buf[0] = '-'
	case ModeDir:
		buf[0] = 'd'
	case ModeSymlink:
		buf[0] = 'l'
	case ModeDevice:
		buf[0] = 'b'
	case ModeCharDevice:
		buf[0] = 'c'
	case ModeNamedPipe:
		buf[0] = 'p'
	case ModeSocket:
		buf[0] = 's'
	default:
		buf[0] = '?'
	}

	const rwx = "rwxrwxrwx"
	for i, c := range rwx {
		if m&(1<<uint(9-1-i)) != 0 {
			buf[i+1] = byte(c)
		} else {
			buf[i+1] = '-'
		}
	}

	if m&ModeSetUID != 0 {
		if buf[3] == 'x' {
			buf[3] = 's'
		} else {
			buf[3] = 'S'
		}
	}

	if m&ModeSetGID != 0 {
		if buf[6] == 'x' {
			buf[6] = 's'
		} else {
			buf[6] = 'S'
		}
	}

	if m&ModeSticky != 0 {
		if buf[9] == 'x' {
			buf[9] = 't'
		} else {
			buf[9] = 'T'
		}
	}

	return string(buf[:])
}
