// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Plan 9 directory marshaling. See intro(5).

package syscall

import (
	"errors"
	"internal/byteorder"
)

var (
	ErrShortStat = errors.New("stat buffer too short")
	ErrBadStat   = errors.New("malformed stat buffer")
	ErrBadName   = errors.New("bad character in file name")
)

// A Qid represents a 9P server's unique identification for a file.
type Qid struct {
	Path uint64 // the file server's unique identification for the file
	Vers uint32 // version number for given Path
	Type uint8  // the type of the file (syscall.QTDIR for example)
}

// A Dir contains the metadata for a file.
type Dir struct {
	// system-modified data
	Type uint16 // server type
	Dev  uint32 // server subtype

	// file data
	Qid    Qid    // unique id from server
	Mode   uint32 // permissions
	Atime  uint32 // last read time
	Mtime  uint32 // last write time
	Length int64  // file length
	Name   string // last element of path
	Uid    string // owner name
	Gid    string // group name
	Muid   string // last modifier name
}

var nullDir = Dir{
	Type: ^uint16(0),
	Dev:  ^uint32(0),
	Qid: Qid{
		Path: ^uint64(0),
		Vers: ^uint32(0),
		Type: ^uint8(0),
	},
	Mode:   ^uint32(0),
	Atime:  ^uint32(0),
	Mtime:  ^uint32(0),
	Length: ^int64(0),
}

// Null assigns special "don't touch" values to members of d to
// avoid modifying them during [Wstat].
func (d *Dir) Null() { *d = nullDir }

// Marshal encodes a 9P stat message corresponding to d into b
//
// If there isn't enough space in b for a stat message, [ErrShortStat] is returned.
func (d *Dir) Marshal(b []byte) (n int, err error) {
	n = STATFIXLEN + len(d.Name) + len(d.Uid) + len(d.Gid) + len(d.Muid)
	if n > len(b) {
		return n, ErrShortStat
	}

	for _, c := range d.Name {
		if c == '/' {
			return n, ErrBadName
		}
	}

	b = pbit16(b, uint16(n)-2)
	b = pbit16(b, d.Type)
	b = pbit32(b, d.Dev)
	b = pbit8(b, d.Qid.Type)
	b = pbit32(b, d.Qid.Vers)
	b = pbit64(b, d.Qid.Path)
	b = pbit32(b, d.Mode)
	b = pbit32(b, d.Atime)
	b = pbit32(b, d.Mtime)
	b = pbit64(b, uint64(d.Length))
	b = pstring(b, d.Name)
	b = pstring(b, d.Uid)
	b = pstring(b, d.Gid)
	b = pstring(b, d.Muid)

	return n, nil
}

// UnmarshalDir decodes a single 9P stat message from b and returns the resulting Dir.
//
// If b is too small to hold a valid stat message, [ErrShortStat] is returned.
//
// If the stat message itself is invalid, [ErrBadStat] is returned.
func UnmarshalDir(b []byte) (*Dir, error) {
	if len(b) < STATFIXLEN {
		return nil, ErrShortStat
	}
	size, buf := gbit16(b)
	if len(b) != int(size)+2 {
		return nil, ErrBadStat
	}
	b = buf

	var d Dir
	d.Type, b = gbit16(b)
	d.Dev, b = gbit32(b)
	d.Qid.Type, b = gbit8(b)
	d.Qid.Vers, b = gbit32(b)
	d.Qid.Path, b = gbit64(b)
	d.Mode, b = gbit32(b)
	d.Atime, b = gbit32(b)
	d.Mtime, b = gbit32(b)

	n, b := gbit64(b)
	d.Length = int64(n)

	var ok bool
	if d.Name, b, ok = gstring(b); !ok {
		return nil, ErrBadStat
	}
	if d.Uid, b, ok = gstring(b); !ok {
		return nil, ErrBadStat
	}
	if d.Gid, b, ok = gstring(b); !ok {
		return nil, ErrBadStat
	}
	if d.Muid, b, ok = gstring(b); !ok {
		return nil, ErrBadStat
	}

	return &d, nil
}

// pbit8 copies the 8-bit number v to b and returns the remaining slice of b.
func pbit8(b []byte, v uint8) []byte {
	b[0] = byte(v)
	return b[1:]
}

// pbit16 copies the 16-bit number v to b in little-endian order and returns the remaining slice of b.
func pbit16(b []byte, v uint16) []byte {
	byteorder.LePutUint16(b, v)
	return b[2:]
}

// pbit32 copies the 32-bit number v to b in little-endian order and returns the remaining slice of b.
func pbit32(b []byte, v uint32) []byte {
	byteorder.LePutUint32(b, v)
	return b[4:]
}

// pbit64 copies the 64-bit number v to b in little-endian order and returns the remaining slice of b.
func pbit64(b []byte, v uint64) []byte {
	byteorder.LePutUint64(b, v)
	return b[8:]
}

// pstring copies the string s to b, prepending it with a 16-bit length in little-endian order, and
// returning the remaining slice of b..
func pstring(b []byte, s string) []byte {
	b = pbit16(b, uint16(len(s)))
	n := copy(b, s)
	return b[n:]
}

// gbit8 reads an 8-bit number from b and returns it with the remaining slice of b.
func gbit8(b []byte) (uint8, []byte) {
	return uint8(b[0]), b[1:]
}

// gbit16 reads a 16-bit number in little-endian order from b and returns it with the remaining slice of b.
//
//go:nosplit
func gbit16(b []byte) (uint16, []byte) {
	return byteorder.LeUint16(b), b[2:]
}

// gbit32 reads a 32-bit number in little-endian order from b and returns it with the remaining slice of b.
func gbit32(b []byte) (uint32, []byte) {
	return byteorder.LeUint32(b), b[4:]
}

// gbit64 reads a 64-bit number in little-endian order from b and returns it with the remaining slice of b.
func gbit64(b []byte) (uint64, []byte) {
	return byteorder.LeUint64(b), b[8:]
}

// gstring reads a string from b, prefixed with a 16-bit length in little-endian order.
// It returns the string with the remaining slice of b and a boolean. If the length is
// greater than the number of bytes in b, the boolean will be false.
func gstring(b []byte) (string, []byte, bool) {
	n, b := gbit16(b)
	if int(n) > len(b) {
		return "", b, false
	}
	return string(b[:n]), b[n:], true
}
