package zfs

import (
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
)

type VersionType string

const (
	Bookmark VersionType = "bookmark"
	Snapshot             = "snapshot"
)

func (t VersionType) DelimiterChar() string {
	switch t {
	case Bookmark:
		return "#"
	case Snapshot:
		return "@"
	default:
		panic(fmt.Sprintf("unexpected VersionType %#v", t))
	}
	return ""
}

type FilesystemVersion struct {
	Type VersionType

	// Display name. Should not be used for identification, only for user output
	Name string

	// GUID as exported by ZFS. Uniquely identifies a snapshot across pools
	Guid uint64

	// The TXG in which the snapshot was created. For bookmarks,
	// this is the GUID of the snapshot it was initially tied to.
	CreateTXG uint64

	// The time the dataset was created
	Creation time.Time
}

func (v FilesystemVersion) ToAbsPath(p DatasetPath) string {
	var b bytes.Buffer
	b.WriteString(p.ToString())
	b.WriteString(v.Type.DelimiterChar())
	b.WriteString(v.Name)
	return b.String()
}

type FilesystemVersionFilter interface {
	Filter(fsv FilesystemVersion) (accept bool, err error)
}

func ZFSListFilesystemVersions(fs DatasetPath, filter FilesystemVersionFilter) (res []FilesystemVersion, err error) {
	var fieldLines [][]string
	fieldLines, err = ZFSList(
		[]string{"name", "guid", "createtxg", "creation"},
		"-r", "-d", "1",
		"-t", "bookmark,snapshot",
		"-s", "createtxg", fs.ToString())
	if err != nil {
		return
	}
	res = make([]FilesystemVersion, 0, len(fieldLines))
	for _, line := range fieldLines {

		if len(line[0]) < 3 {
			err = errors.New(fmt.Sprintf("snapshot or bookmark name implausibly short: %s", line[0]))
			return
		}

		snapSplit := strings.SplitN(line[0], "@", 2)
		bookmarkSplit := strings.SplitN(line[0], "#", 2)
		if len(snapSplit)*len(bookmarkSplit) != 2 {
			err = errors.New(fmt.Sprintf("dataset cannot be snapshot and bookmark at the same time: %s", line[0]))
			return
		}

		var v FilesystemVersion
		if len(snapSplit) == 2 {
			v.Name = snapSplit[1]
			v.Type = Snapshot
		} else {
			v.Name = bookmarkSplit[1]
			v.Type = Bookmark
		}

		if v.Guid, err = strconv.ParseUint(line[1], 10, 64); err != nil {
			err = errors.New(fmt.Sprintf("cannot parse GUID: %s", err.Error()))
			return
		}

		if v.CreateTXG, err = strconv.ParseUint(line[2], 10, 64); err != nil {
			err = errors.New(fmt.Sprintf("cannot parse CreateTXG: %s", err.Error()))
			return
		}

		creationUnix, err := strconv.ParseInt(line[3], 10, 64)
		if err != nil {
			err = fmt.Errorf("cannot parse creation date '%s': %s", line[3], err)
			return nil, err
		} else {
			v.Creation = time.Unix(creationUnix, 0)
		}

		accept := true
		if filter != nil {
			accept, err = filter.Filter(v)
			if err != nil {
				err = fmt.Errorf("error executing filter: %s", err)
				return nil, err
			}
		}
		if accept {
			res = append(res, v)
		}

	}
	return
}

func ZFSDestroyFilesystemVersion(filesystem DatasetPath, version FilesystemVersion) (err error) {

	datasetPath := version.ToAbsPath(filesystem)

	// Sanity check...
	if strings.IndexAny(datasetPath, "@#") == -1 {
		return fmt.Errorf("sanity check failed: no @ character found in dataset path: %s", datasetPath)
	}

	err = ZFSDestroy(datasetPath)
	if err == nil {
		return
	}

	// Check for EBUSY, special meaning to us
	return

}