package embedded_filer

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"

	"github.com/chrislusf/seaweedfs/weed/filer"
	"github.com/chrislusf/seaweedfs/weed/util"
)

var writeLock sync.Mutex //serialize changes to dir.log

type DirectoryId int32

type DirectoryEntryInMap struct {
	sync.Mutex
	Name           string
	Parent         *DirectoryEntryInMap
	subDirectories map[string]*DirectoryEntryInMap
	Id             DirectoryId
}

func (de *DirectoryEntryInMap) getChild(dirName string) (*DirectoryEntryInMap, bool) {
	de.Lock()
	defer de.Unlock()
	child, ok := de.subDirectories[dirName]
	return child, ok
}
func (de *DirectoryEntryInMap) addChild(dirName string, child *DirectoryEntryInMap) {
	de.Lock()
	defer de.Unlock()
	de.subDirectories[dirName] = child
}
func (de *DirectoryEntryInMap) removeChild(dirName string) {
	de.Lock()
	defer de.Unlock()
	delete(de.subDirectories, dirName)
}
func (de *DirectoryEntryInMap) hasChildren() bool {
	de.Lock()
	defer de.Unlock()
	return len(de.subDirectories) > 0
}
func (de *DirectoryEntryInMap) children() (dirNames []filer.DirectoryName) {
	de.Lock()
	defer de.Unlock()
	for k, _ := range de.subDirectories {
		dirNames = append(dirNames, filer.DirectoryName(k))
	}
	return dirNames
}

type DirectoryManagerInMap struct {
	Root      *DirectoryEntryInMap
	max       DirectoryId
	logFile   *os.File
	isLoading bool
}

func (dm *DirectoryManagerInMap) newDirectoryEntryInMap(parent *DirectoryEntryInMap, name string) (d *DirectoryEntryInMap, err error) {
	d = &DirectoryEntryInMap{Name: name, Parent: parent, subDirectories: make(map[string]*DirectoryEntryInMap)}
	var parts []string
	for p := d; p != nil && p.Name != ""; p = p.Parent {
		parts = append(parts, p.Name)
	}
	n := len(parts)
	if n <= 0 {
		return nil, fmt.Errorf("Failed to create folder %s/%s", parent.Name, name)
	}
	for i := 0; i < n/2; i++ {
		parts[i], parts[n-1-i] = parts[n-1-i], parts[i]
	}
	dm.max++
	d.Id = dm.max
	dm.log("add", "/"+strings.Join(parts, "/"), strconv.Itoa(int(d.Id)))
	return d, nil
}

func (dm *DirectoryManagerInMap) log(words ...string) {
	if !dm.isLoading {
		dm.logFile.WriteString(strings.Join(words, "\t") + "\n")
	}
}

func NewDirectoryManagerInMap(dirLogFile string) (dm *DirectoryManagerInMap, err error) {
	dm = &DirectoryManagerInMap{}
	//dm.Root do not use newDirectoryEntryInMap, since dm.max will be changed
	dm.Root = &DirectoryEntryInMap{subDirectories: make(map[string]*DirectoryEntryInMap)}
	if dm.logFile, err = os.OpenFile(dirLogFile, os.O_RDWR|os.O_CREATE, 0644); err != nil {
		return nil, fmt.Errorf("cannot write directory log file %s: %v", dirLogFile, err)
	}
	return dm, dm.load()
}

func (dm *DirectoryManagerInMap) processEachLine(line string) error {
	if strings.HasPrefix(line, "#") {
		return nil
	}
	if line == "" {
		return nil
	}
	parts := strings.Split(line, "\t")
	if len(parts) == 0 {
		return nil
	}
	switch parts[0] {
	case "add":
		v, pe := strconv.Atoi(parts[2])
		if pe != nil {
			return pe
		}
		if e := dm.loadDirectory(parts[1], DirectoryId(v)); e != nil {
			return e
		}
	case "mov":
		newName := ""
		if len(parts) >= 4 {
			newName = parts[3]
		}
		if e := dm.MoveUnderDirectory(parts[1], parts[2], newName); e != nil {
			return e
		}
	case "del":
		if e := dm.DeleteDirectory(parts[1]); e != nil {
			return e
		}
	default:
		fmt.Printf("line %s has %s!\n", line, parts[0])
		return nil
	}
	return nil
}
func (dm *DirectoryManagerInMap) load() error {
	dm.max = 0
	lines := bufio.NewReader(dm.logFile)
	dm.isLoading = true
	defer func() { dm.isLoading = false }()
	for {
		line, err := util.Readln(lines)
		if err != nil && err != io.EOF {
			return err
		}
		if pe := dm.processEachLine(string(line)); pe != nil {
			return pe
		}
		if err == io.EOF {
			return nil
		}
	}
}

func (dm *DirectoryManagerInMap) findDirectory(dirPath string) (*DirectoryEntryInMap, error) {
	if dirPath == "" {
		return dm.Root, nil
	}
	dirPath = CleanFilePath(dirPath)
	if dirPath == "/" {
		return dm.Root, nil
	}
	parts := strings.Split(dirPath, "/")
	dir := dm.Root
	for i := 1; i < len(parts); i++ {
		if sub, ok := dir.getChild(parts[i]); ok {
			dir = sub
		} else {
			return dm.Root, filer.ErrNotFound
		}
	}
	return dir, nil
}
func (dm *DirectoryManagerInMap) findDirectoryId(dirPath string) (DirectoryId, error) {
	d, e := dm.findDirectory(dirPath)
	if e == nil {
		return d.Id, nil
	}
	return dm.Root.Id, e
}

func (dm *DirectoryManagerInMap) loadDirectory(dirPath string, dirId DirectoryId) error {
	dirPath = CleanFilePath(dirPath)
	if dirPath == "/" {
		return nil
	}
	parts := strings.Split(dirPath, "/")
	dir := dm.Root
	for i := 1; i < len(parts); i++ {
		sub, ok := dir.getChild(parts[i])
		if !ok {
			writeLock.Lock()
			if sub2, createdByOtherThread := dir.getChild(parts[i]); createdByOtherThread {
				sub = sub2
			} else {
				if i != len(parts)-1 {
					writeLock.Unlock()
					return fmt.Errorf("%s should be created after parent %s", dirPath, parts[i])
				}
				var err error
				sub, err = dm.newDirectoryEntryInMap(dir, parts[i])
				if err != nil {
					writeLock.Unlock()
					return err
				}
				if sub.Id != dirId {
					writeLock.Unlock()
					// the dir.log should be the same order as in-memory directory id
					return fmt.Errorf("%s should be have id %v instead of %v", dirPath, sub.Id, dirId)
				}
				dir.addChild(parts[i], sub)
			}
			writeLock.Unlock()
		}
		dir = sub
	}
	return nil
}

func (dm *DirectoryManagerInMap) makeDirectory(dirPath string) (dir *DirectoryEntryInMap, created bool) {
	dirPath = CleanFilePath(dirPath)
	if dirPath == "/" {
		return dm.Root, false
	}
	parts := strings.Split(dirPath, "/")
	dir = dm.Root
	for i := 1; i < len(parts); i++ {
		sub, ok := dir.getChild(parts[i])
		if !ok {
			writeLock.Lock()
			if sub2, createdByOtherThread := dir.getChild(parts[i]); createdByOtherThread {
				sub = sub2
			} else {
				var err error
				sub, err = dm.newDirectoryEntryInMap(dir, parts[i])
				if err != nil {
					writeLock.Unlock()
					return nil, false
				}
				dir.addChild(parts[i], sub)
				created = true
			}
			writeLock.Unlock()
		}
		dir = sub
	}
	return dir, created
}

func (dm *DirectoryManagerInMap) MakeDirectory(dirPath string) (DirectoryId, error) {
	dir, _ := dm.makeDirectory(dirPath)
	return dir.Id, nil
}

func (dm *DirectoryManagerInMap) MoveUnderDirectory(oldDirPath string, newParentDirPath string, newName string) error {
	writeLock.Lock()
	defer writeLock.Unlock()
	oldDir, oe := dm.findDirectory(oldDirPath)
	if oe != nil {
		return oe
	}
	parentDir, pe := dm.findDirectory(newParentDirPath)
	if pe != nil {
		return pe
	}
	dm.log("mov", oldDirPath, newParentDirPath, newName)
	oldDir.Parent.removeChild(oldDir.Name)
	if newName == "" {
		newName = oldDir.Name
	}
	parentDir.addChild(newName, oldDir)
	oldDir.Name = newName
	oldDir.Parent = parentDir
	return nil
}

func (dm *DirectoryManagerInMap) ListDirectories(dirPath string) (dirNames []filer.DirectoryName, err error) {
	d, e := dm.findDirectory(dirPath)
	if e != nil {
		return dirNames, e
	}
	return d.children(), nil
}
func (dm *DirectoryManagerInMap) DeleteDirectory(dirPath string) error {
	writeLock.Lock()
	defer writeLock.Unlock()
	if dirPath == "/" {
		return fmt.Errorf("Can not delete %s", dirPath)
	}
	d, e := dm.findDirectory(dirPath)
	if e != nil {
		return e
	}
	if d.hasChildren() {
		return fmt.Errorf("dir %s still has sub directories", dirPath)
	}
	d.Parent.removeChild(d.Name)
	d.Parent = nil
	dm.log("del", dirPath)
	return nil
}

func CleanFilePath(fp string) string {
	ret := filepath.Clean(fp)
	if os.PathSeparator == '\\' {
		return strings.Replace(ret, "\\", "/", -1)
	}
	return ret
}
