package fuse

// all of the code for DirEntryList.

import (
	"bytes"
	"fmt"
	"unsafe"

	"github.com/hanwen/go-fuse/raw"
)

var _ = fmt.Print

// DirEntry is a type for PathFileSystem and NodeFileSystem to return
// directory contents in.
type DirEntry struct {
	Mode uint32
	Name string
}

type DirEntryList struct {
	buf     bytes.Buffer
	offset  *uint64
	maxSize int
}

func NewDirEntryList(max int, off *uint64) *DirEntryList {
	return &DirEntryList{maxSize: max, offset: off}
}

func (me *DirEntryList) AddString(name string, inode uint64, mode uint32) bool {
	return me.Add([]byte(name), inode, mode)
}

func (me *DirEntryList) AddDirEntry(e DirEntry) bool {
	return me.Add([]byte(e.Name), uint64(raw.FUSE_UNKNOWN_INO), e.Mode)
}

func (me *DirEntryList) Add(name []byte, inode uint64, mode uint32) bool {
	lastLen := me.buf.Len()
	(*me.offset)++

	dirent := raw.Dirent{
		Off:     *me.offset,
		Ino:     inode,
		NameLen: uint32(len(name)),
		Typ:     ModeToType(mode),
	}

	_, err := me.buf.Write(asSlice(unsafe.Pointer(&dirent), unsafe.Sizeof(raw.Dirent{})))
	if err != nil {
		panic("Serialization of Dirent failed")
	}
	me.buf.Write(name)

	padding := 8 - len(name)&7
	if padding < 8 {
		me.buf.Write(make([]byte, padding))
	}

	if me.buf.Len() > me.maxSize {
		me.buf.Truncate(lastLen)
		(*me.offset)--
		return false
	}
	return true
}

func (me *DirEntryList) Bytes() []byte {
	return me.buf.Bytes()
}

////////////////////////////////////////////////////////////////

type rawDir interface {
	ReadDir(input *ReadIn) (*DirEntryList, Status)
	Release()
}

type connectorDir struct {
	extra      []DirEntry
	stream     chan DirEntry
	leftOver   DirEntry
	lastOffset uint64
}

func (me *connectorDir) ReadDir(input *ReadIn) (*DirEntryList, Status) {
	if me.stream == nil && len(me.extra) == 0 {
		return nil, OK
	}

	list := NewDirEntryList(int(input.Size), &me.lastOffset)
	if me.leftOver.Name != "" {
		success := list.AddDirEntry(me.leftOver)
		if !success {
			panic("No space for single entry.")
		}
		me.leftOver.Name = ""
	}
	for len(me.extra) > 0 {
		e := me.extra[len(me.extra)-1]
		me.extra = me.extra[:len(me.extra)-1]
		success := list.AddDirEntry(e)
		if !success {
			me.leftOver = e
			return list, OK
		}
	}
	for {
		d, isOpen := <-me.stream
		if !isOpen {
			me.stream = nil
			break
		}
		if !list.AddDirEntry(d) {
			me.leftOver = d
			break
		}
	}
	return list, OK
}

// Read everything so we make goroutines exit.
func (me *connectorDir) Release() {
	for ok := true; ok && me.stream != nil; {
		_, ok = <-me.stream
		if !ok {
			break
		}
	}
}