package fuse

import (
	"fmt"
	"log"
	"os"
	"sync"
	"time"
)

var _ = log.Println

type MemNodeFs struct {
	DefaultNodeFileSystem
	backingStorePrefix string
	root               *memNode

	mutex    sync.Mutex
	nextFree int
}

func (fs *MemNodeFs) String() string {
	return fmt.Sprintf("MemNodeFs(%s)", fs.backingStorePrefix)
}

func (fs *MemNodeFs) Root() FsNode {
	return fs.root
}

func (fs *MemNodeFs) newNode() *memNode {
	fs.mutex.Lock()
	n := &memNode{
		fs: fs,
		id: fs.nextFree,
	}
	now := time.Now().UnixNano()
	n.info.SetNs(now, now, now)
	n.info.Mode = S_IFDIR | 0777
	fs.nextFree++
	fs.mutex.Unlock()
	return n
}

func NewMemNodeFs(prefix string) *MemNodeFs {
	me := &MemNodeFs{}
	me.backingStorePrefix = prefix
	me.root = me.newNode()
	return me
}

func (fs *MemNodeFs) Filename(n *Inode) string {
	mn := n.FsNode().(*memNode)
	return mn.filename()
}

type memNode struct {
	DefaultFsNode
	fs *MemNodeFs
	id int

	link string
	info Attr
}

func (n *memNode) newNode(isdir bool) *memNode {
	newNode := n.fs.newNode()
	n.Inode().New(isdir, newNode)
	return newNode
}

func (n *memNode) filename() string {
	return fmt.Sprintf("%s%d", n.fs.backingStorePrefix, n.id)
}

func (n *memNode) Deletable() bool {
	return false
}

func (n *memNode) Readlink(c *Context) ([]byte, Status) {
	return []byte(n.link), OK
}

func (n *memNode) Mkdir(name string, mode uint32, context *Context) (newNode FsNode, code Status) {
	ch := n.newNode(true)
	ch.info.Mode = mode | S_IFDIR
	n.Inode().AddChild(name, ch.Inode())
	return ch, OK
}

func (n *memNode) Unlink(name string, context *Context) (code Status) {
	ch := n.Inode().RmChild(name)
	if ch == nil {
		return ENOENT
	}
	return OK
}

func (n *memNode) Rmdir(name string, context *Context) (code Status) {
	return n.Unlink(name, context)
}

func (n *memNode) Symlink(name string, content string, context *Context) (newNode FsNode, code Status) {
	ch := n.newNode(false)
	ch.info.Mode = S_IFLNK | 0777
	ch.link = content
	n.Inode().AddChild(name, ch.Inode())

	return ch, OK
}

func (n *memNode) Rename(oldName string, newParent FsNode, newName string, context *Context) (code Status) {
	ch := n.Inode().RmChild(oldName)
	newParent.Inode().RmChild(newName)
	newParent.Inode().AddChild(newName, ch)
	return OK
}

func (n *memNode) Link(name string, existing FsNode, context *Context) (newNode FsNode, code Status) {
	n.Inode().AddChild(name, existing.Inode())
	return existing, code
}

func (n *memNode) Create(name string, flags uint32, mode uint32, context *Context) (file File, newNode FsNode, code Status) {
	ch := n.newNode(false)
	ch.info.Mode = mode | S_IFREG

	f, err := os.Create(n.filename())
	if err != nil {
		return nil, nil, ToStatus(err)
	}
	n.Inode().AddChild(name, ch.Inode())
	return ch.newFile(f), ch, OK
}

type memNodeFile struct {
	LoopbackFile
	node *memNode
}

func (n *memNodeFile) String() string {
	return fmt.Sprintf("memNodeFile(%s)", n.LoopbackFile.String())
}

func (n *memNodeFile) InnerFile() File {
	return &n.LoopbackFile
}

func (n *memNodeFile) Flush() Status {
	code := n.LoopbackFile.Flush()
	var a Attr
	n.LoopbackFile.GetAttr(&a)
	n.node.info.Size = a.Size
	n.node.info.Blocks = a.Blocks
	return code
}

func (n *memNode) newFile(f *os.File) File {
	return &memNodeFile{
		LoopbackFile: LoopbackFile{File: f},
		node:         n,
	}
}

func (n *memNode) Open(flags uint32, context *Context) (file File, code Status) {
	f, err := os.OpenFile(n.filename(), int(flags), 0666)
	if err != nil {
		return nil, ToStatus(err)
	}

	return n.newFile(f), OK
}

func (n *memNode) GetAttr(fi *Attr, file File, context *Context) (code Status) {
	*fi = n.info
	return OK
}

func (n *memNode) Truncate(file File, size uint64, context *Context) (code Status) {
	if file != nil {
		code = file.Truncate(size)
	} else {
		err := os.Truncate(n.filename(), int64(size))
		code = ToStatus(err)
	}
	if code.Ok() {
		n.info.SetNs(-1, -1, time.Now().UnixNano())
		// TODO - should update mtime too?
		n.info.Size = size
	}
	return code
}

func (n *memNode) Utimens(file File, atime int64, mtime int64, context *Context) (code Status) {
	n.info.SetNs(int64(atime), int64(mtime), time.Now().UnixNano())
	return OK
}

func (n *memNode) Chmod(file File, perms uint32, context *Context) (code Status) {
	n.info.Mode = (n.info.Mode ^ 07777) | perms
	n.info.SetNs(-1, -1, time.Now().UnixNano())
	return OK
}

func (n *memNode) Chown(file File, uid uint32, gid uint32, context *Context) (code Status) {
	n.info.Uid = uid
	n.info.Gid = gid
	n.info.SetNs(-1, -1, time.Now().UnixNano())
	return OK
}