package blefs

import (
	"fmt"
	"io/fs"
	"strconv"
	"time"
)

// Mkdir creates a directory at the given path
func (blefs *FS) Mkdir(path string) error {
	// Create make directory request
	err := blefs.request(
		FSCmdMkdir,
		true,
		uint16(len(path)),
		padding(4),
		uint64(time.Now().UnixNano()),
		path,
	)
	if err != nil {
		return err
	}
	var status int8
	// Upon receiving 0x41 (FSResponseMkdir), read status byte
	blefs.on(FSResponseMkdir, func(data []byte) error {
		return decode(data, &status)
	})
	// If status not ok, return error
	if status != FSStatusOk {
		return FSError{status}
	}
	return nil
}

// ReadDir returns a list of directory entries from the given path
func (blefs *FS) ReadDir(path string) ([]fs.DirEntry, error) {
	// Create list directory request
	err := blefs.request(
		FSCmdListDir,
		true,
		uint16(len(path)),
		path,
	)
	if err != nil {
		return nil, err
	}
	var out []fs.DirEntry
	for {
		// Create new directory entry
		listing := DirEntry{}
		// Upon receiving 0x50 (FSResponseListDir)
		blefs.on(FSResponseListDir, func(data []byte) error {
			// Read data into listing
			err := decode(
				data,
				&listing.status,
				&listing.pathLen,
				&listing.entryNum,
				&listing.entries,
				&listing.flags,
				&listing.modtime,
				&listing.size,
				&listing.path,
			)
			if err != nil {
				return err
			}
			return nil
		})
		// If status is not ok, return error
		if listing.status != FSStatusOk {
			return nil, FSError{listing.status}
		}
		// Stop once entry number equals total entries
		if listing.entryNum == listing.entries {
			break
		}
		// Append listing to slice
		out = append(out, listing)
	}
	return out, nil
}

// DirEntry represents an entry from a directory listing
type DirEntry struct {
	status   int8
	pathLen  uint16
	entryNum uint32
	entries  uint32
	flags    uint32
	modtime  uint64
	size     uint32
	path     string
}

// Name returns the name of the file described by the entry
func (de DirEntry) Name() string {
	return de.path
}

// IsDir reports whether the entry describes a directory.
func (de DirEntry) IsDir() bool {
	return de.flags&0b1 == 1
}

// Type returns the type bits for the entry.
func (de DirEntry) Type() fs.FileMode {
	if de.IsDir() {
		return fs.ModeDir
	} else {
		return 0
	}
}

// Info returns the FileInfo for the file or subdirectory described by the entry.
func (de DirEntry) Info() (fs.FileInfo, error) {
	return FileInfo{
		name:    de.path,
		size:    de.size,
		modtime: de.modtime,
		mode:    de.Type(),
		isDir:   de.IsDir(),
	}, nil
}

func (de DirEntry) String() string {
	var isDirChar rune
	if de.IsDir() {
		isDirChar = 'd'
	} else {
		isDirChar = '-'
	}

	// Get human-readable value for file size
	val, unit := bytesHuman(de.size)
	prec := 0
	// If value is less than 10, set precision to 1
	if val < 10 {
		prec = 1
	}
	// Convert float to string
	valStr := strconv.FormatFloat(val, 'f', prec, 64)

	// Return string formatted like so:
	// -  10 kB file
	// or:
	// d   0 B  .
	return fmt.Sprintf(
		"%c %3s %-2s %s",
		isDirChar,
		valStr,
		unit,
		de.path,
	)
}