package fuse

import (
	"fmt"
	"os"
	"sort"
	"strings"
	"syscall"
)

var openFlagNames map[int]string
var initFlagNames map[int]string
var fuseOpenFlagNames map[int]string
var writeFlagNames map[int]string
var readFlagNames map[int]string
var releaseFlagNames map[int]string
var accessFlagName map[int]string

func init() {
	releaseFlagNames = map[int]string{
		RELEASE_FLUSH: "FLUSH",
	}
	openFlagNames = map[int]string{
		os.O_WRONLY:   "WRONLY",
		os.O_RDWR:     "RDWR",
		os.O_APPEND:   "APPEND",
		os.O_ASYNC:    "ASYNC",
		os.O_CREATE:   "CREAT",
		os.O_EXCL:     "EXCL",
		os.O_NOCTTY:   "NOCTTY",
		os.O_NONBLOCK: "NONBLOCK",
		os.O_SYNC:     "SYNC",
		os.O_TRUNC:    "TRUNC",

		syscall.O_CLOEXEC:   "CLOEXEC",
		syscall.O_DIRECT:    "DIRECT",
		syscall.O_DIRECTORY: "DIRECTORY",
		syscall.O_LARGEFILE: "LARGEFILE",
		syscall.O_NOATIME:   "NOATIME",
	}
	initFlagNames = map[int]string{
		CAP_ASYNC_READ:     "ASYNC_READ",
		CAP_POSIX_LOCKS:    "POSIX_LOCKS",
		CAP_FILE_OPS:       "FILE_OPS",
		CAP_ATOMIC_O_TRUNC: "ATOMIC_O_TRUNC",
		CAP_EXPORT_SUPPORT: "EXPORT_SUPPORT",
		CAP_BIG_WRITES:     "BIG_WRITES",
		CAP_DONT_MASK:      "DONT_MASK",
		CAP_SPLICE_WRITE:   "SPLICE_WRITE",
		CAP_SPLICE_MOVE:    "SPLICE_MOVE",
		CAP_SPLICE_READ:    "SPLICE_READ",
	}
	fuseOpenFlagNames = map[int]string{
		FOPEN_DIRECT_IO:   "DIRECT",
		FOPEN_KEEP_CACHE:  "CACHE",
		FOPEN_NONSEEKABLE: "NONSEEK",
	}
	writeFlagNames = map[int]string{
		WRITE_CACHE:     "CACHE",
		WRITE_LOCKOWNER: "LOCKOWNER",
	}
	readFlagNames = map[int]string{
		READ_LOCKOWNER: "LOCKOWNER",
	}
	accessFlagName = map[int]string{
		X_OK: "x",
		W_OK: "w",
		R_OK: "r",
	}
}

func flagString(names map[int]string, fl int, def string) string {
	s := []string{}
	for k, v := range names {
		if fl&k != 0 {
			s = append(s, v)
			fl ^= k
		}
	}
	if len(s) == 0 && def != "" {
		s = []string{def}
	}
	if fl != 0 {
		s = append(s, fmt.Sprintf("0x%x", fl))
	}

	return strings.Join(s, ",")
}

func (me *OpenIn) String() string {
	return fmt.Sprintf("{%s}", flagString(openFlagNames, int(me.Flags), "O_RDONLY"))
}

type OsFileInfo os.FileInfo
func (me OsFileInfo) String() string {
	return fmt.Sprintf(
		"{%s M0%o S=%d L=%d "+
			"%d:%d "+
			"%d*%d %d:%d "+
			"C %d.%09d "+
			"M %d.%09d "+
			"A %d.%09d}",
		me.Name,
		me.Mode, me.Size, me.Nlink,
		me.Uid, me.Gid,
		me.Blocks, me.Blksize,
		me.Rdev, me.Ino,
		me.Ctime_ns/1e9,
                me.Ctime_ns%1e9,
                me.Mtime_ns/1e9,
                me.Mtime_ns%1e9,
                me.Atime_ns/1e9,
                me.Atime_ns%1e9)
}

type OsFileInfos []*os.FileInfo

func (me OsFileInfos) String() string {
	out := []string{}
	for _, info := range me {
		out = append(out, OsFileInfo(*info).String())
	}
	sort.Strings(out)
	return fmt.Sprintf("[%v]", out)
}

func (me *SetAttrIn) String() string {
	s := []string{}
	if me.Valid&FATTR_MODE != 0 {
		s = append(s, fmt.Sprintf("mode 0%o", me.Mode))
	}
	if me.Valid&FATTR_UID != 0 {
		s = append(s, fmt.Sprintf("uid %d", me.Uid))
	}
	if me.Valid&FATTR_GID != 0 {
		s = append(s, fmt.Sprintf("uid %d", me.Gid))
	}
	if me.Valid&FATTR_SIZE != 0 {
		s = append(s, fmt.Sprintf("size %d", me.Size))
	}
	if me.Valid&FATTR_ATIME != 0 {
		s = append(s, fmt.Sprintf("atime %d %d", me.Atime, me.Atimensec))
	}
	if me.Valid&FATTR_MTIME != 0 {
		s = append(s, fmt.Sprintf("mtime %d %d", me.Mtime, me.Mtimensec))
	}
	if me.Valid&FATTR_MTIME != 0 {
		s = append(s, fmt.Sprintf("fh %d", me.Fh))
	}
	// TODO - FATTR_ATIME_NOW = (1 << 7), FATTR_MTIME_NOW = (1 << 8), FATTR_LOCKOWNER = (1 << 9)
	return fmt.Sprintf("{%s}", strings.Join(s, ", "))
}

func (me *Attr) String() string {
	return fmt.Sprintf(
		"{M0%o S=%d L=%d "+
			"%d:%d "+
			"%d*%d %d:%d "+
			"A %d.%09d "+
			"M %d.%09d "+
			"C %d.%09d}",
		me.Mode, me.Size, me.Nlink,
		me.Uid, me.Gid,
		me.Blocks, me.Blksize,
		me.Rdev, me.Ino, me.Atime, me.Atimensec, me.Mtime, me.Mtimensec,
		me.Ctime, me.Ctimensec)
}

func (me *AttrOut) String() string {
	return fmt.Sprintf(
		"{A%d.%09d %v}",
		me.AttrValid, me.AttrValidNsec, &me.Attr)
}

func (me *CreateIn) String() string {
	return fmt.Sprintf(
		"{0%o [%s] (0%o)}", me.Mode,
		flagString(openFlagNames, int(me.Flags), "O_RDONLY"), me.Umask)
}

func (me *EntryOut) String() string {
	return fmt.Sprintf("{%d E%d.%09d A%d.%09d %v}",
		me.NodeId, me.EntryValid, me.EntryValidNsec,
		me.AttrValid, me.AttrValidNsec, &me.Attr)
}

func (me *CreateOut) String() string {
	return fmt.Sprintf("{%v %v}", &me.EntryOut, &me.OpenOut)
}

func (me *OpenOut) String() string {
	return fmt.Sprintf("{Fh %d %s}", me.Fh,
		flagString(fuseOpenFlagNames, int(me.OpenFlags), ""))
}

func (me *GetAttrIn) String() string {
	return fmt.Sprintf("{Fh %d}", me.Fh)
}

func (me *InitIn) String() string {
	return fmt.Sprintf("{%d.%d Ra 0x%x %s}",
		me.Major, me.Minor, me.MaxReadAhead,
		flagString(initFlagNames, int(me.Flags), ""))
}
func (me *InitOut) String() string {
	return fmt.Sprintf("{%d.%d Ra 0x%x %s %d/%d Wr 0x%x}",
		me.Major, me.Minor, me.MaxReadAhead,
		flagString(initFlagNames, int(me.Flags), ""),
		me.CongestionThreshold, me.MaxBackground, me.MaxWrite)
}

func (me *ReadIn) String() string {
	return fmt.Sprintf("{Fh %d off %d sz %d %s L %d %s}",
		me.Fh, me.Offset, me.Size,
		flagString(readFlagNames, int(me.ReadFlags), ""),
		me.LockOwner,
		flagString(openFlagNames, int(me.Flags), "RDONLY"))
}

func (me *MkdirIn) String() string {
	return fmt.Sprintf("{0%o (0%o)}", me.Mode, me.Umask)
}

func (me *MknodIn) String() string {
	return fmt.Sprintf("{0%o (0%o), %d}", me.Mode, me.Umask, me.Rdev)
}

func (me *ReleaseIn) String() string {
	return fmt.Sprintf("{Fh %d %s %s L%d}",
		me.Fh, flagString(openFlagNames, int(me.Flags), ""),
		flagString(releaseFlagNames, int(me.ReleaseFlags), ""),
		me.LockOwner)
}

func (me *FlushIn) String() string {
	return fmt.Sprintf("{Fh %d}", me.Fh)
}

func (me *AccessIn) String() string {
	return fmt.Sprintf("{%s}", flagString(accessFlagName, int(me.Mask), ""))
}

func (me *Kstatfs) String() string {
	return fmt.Sprintf(
		"{b%d f%d fs%d ff%d bs%d nl%d frs%d}",
		me.Blocks, me.Bfree, me.Bavail, me.Files, me.Ffree,
		me.Bsize, me.NameLen, me.Frsize)
}

func (me *WithFlags) String() string {
	return fmt.Sprintf("File %s (%s) %s %s",
		me.File, me.Description, flagString(openFlagNames, int(me.OpenFlags), "O_RDONLY"),
		flagString(fuseOpenFlagNames, int(me.FuseFlags), ""))
}